Files
novalon-website/docs/deployment/cicd/01-installation.md
T
zhangxiang 4ab2f3cd8e chore(infra): 新增 Gitea+Jenkins CI/CD 部署与凭据整改
- infra/cicd:docker-compose(gitea/jenkins)、JCasC(凭据统一 ${ENV} 注入,无硬编码)、
  备份/恢复、健康检查、凭据轮换与 git 历史清除脚本
- docs/deployment/cicd:安装、高可用备份监控、凭据事故复盘
- .env.example 仅为占位模板;真实 .env 由 .gitignore 排除
2026-09-20 10:37:57 +08:00

165 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 01 · 安装部署 / 安全配置 / Gitea↔Jenkins 集成
配置源码:[`infra/cicd/`](../../../infra/cicd/)。本文描述**目标态**与从现状迁移的步骤。
## 1. 安装部署
### 1.1 前置条件
```bash
# Docker 20.10+docker compose v2
docker --version && docker compose version
# 外部网络(nginx 反代依赖同一网络做服务名解析)
docker network create novalon-network 2>/dev/null || true
```
### 1.2 部署 Gitea
```bash
cd /home/novalon/docker-app/infra/cicd
cp .env.example .env && vim .env # 填 GITEA_DB_PASSWORD 等,.env 绝不入库
docker compose -f docker-compose.gitea.yml up -d
docker compose -f docker-compose.gitea.yml ps # 等 health=healthy
```
要点:
- **资源限制用 `cpus`/`mem_limit` 顶层键,不要用 `deploy.resources`**。
现状 compose 写的是 `deploy:`,非 swarm 下被 `docker compose` **静默忽略**
等于"看起来限制了实际没有"。这是本次审计确认的实锤。
- SSH 映射 `2222:22`HTTP 仅 `127.0.0.1:3001`,由 nginx 终止 TLS。
### 1.3 部署 Jenkins
```bash
# ⚠️ 现状卷属主是 root(因历史 user: root),切非 root 前必须先改属主
docker stop jenkins
docker run --rm -v jenkins_jenkins_home:/v alpine chown -R 1000:1000 /v
cd /home/novalon/docker-app/infra/cicd
docker compose -f docker-compose.jenkins.yml up -d
```
### 1.4 迁移时的破坏性变更(需维护窗口)
| 变更 | 为什么 | 影响 |
|------|--------|------|
| Jenkins `user: root``1000:1000` | root 容器 = 宿主 root | 需先 `chown` 卷,服务中断 ~2 分钟 |
| 移除 `/var/run/docker.sock` | 作业 config.xml 实测 **0 处** docker 调用 | 若未来作业要用 docker,需改用 dind/socket-proxy |
| 移除 `/home/novalon/docker-app` 挂载 | `deploy.sh``rsync` 到远端,不读本地该目录 | 无 |
| `/root/.ssh``./jenkins-ssh` | 不再暴露宿主 root 私钥 | 需生成专用部署密钥并放服务器 `authorized_keys` |
## 2. 基础安全配置
### 2.1 HTTPS
`nginx-static-production.conf` + `conf.d/*.conf` 终止,无需应用自配。
```nginx
# 已有配置(conf.d/git.f.novalon.cn.conf
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { return 301 https://$host$request_uri; }
```
**已知小瑕疵(P3**nginx `add_header` 与 Gitea 自身都注入 `x-frame-options`/`x-content-type-options`
响应里出现两次。不影响安全(重复值同语义),如要清理:nginx 侧改用 `proxy_hide_header` 后再 `add_header`
### 2.2 访问权限
| 层 | 配置 | 实测状态 |
|----|------|----------|
| Gitea 注册 | `DISABLE_REGISTRATION=true` + `REQUIRE_SIGNIN_VIEW=true` | 环境变量已设;**注意**:env 只在首次安装写库,需核对 `app.ini [service]` 是否同步 |
| Gitea 匿名读 | `REQUIRE_SIGNIN_VIEW` | 未登录访问 `/` 返回 303 → `/explore` |
| Jenkins 匿名读 | `allowAnonymousRead: false`casc.yaml | 待 JCasC 生效后验证 |
| Jenkins 自助注册 | `allowsSignup: false` | 同上 |
| agent 协议 | 仅 `Ping`,关 JNLP 入站 | 无 agent,可整段删 `50000` 映射 |
### 2.3 凭据管理(重点)
**原则:任何密码/令牌只允许出现在两个地方 —— 服务器 `.env`600 权限)或 Jenkins Credentials。**
```
infra/cicd/.env ← 服务器本地,600,不入库
infra/cicd/.env.example ← 模板,只有占位符,可入库
jenkins/casc.yaml ← 凭据用 ${VAR} 引用环境变量,不写明文
```
**禁止**再出现 `cicd.config` 那种明文密码入库。相关 P0 事故见 [03](03-credential-incident.md)。
Gitea 令牌最小权限:Jenkins 拉代码只需 `read:repository`,不要给 `write:administration`
## 3. Gitea ↔ Jenkins 集成
### 3.1 数据流
```
git push (main/develop)
└─► Gitea webhook → https://ci.f.novalon.cn/generic-webhook-trigger/invoke?token=***
└─► GenericTrigger 解析 $.ref / $.repository.full_name / $.after
└─► regexpFilterExpression ^refs/heads/(main|develop)$ 通过 → 触发构建
└─► Jenkinsfile 各 stage
```
### 3.2 作业定义(现状)
```xml
<!-- /var/jenkins_home/jobs/novalon-website-ci-cd/config.xml -->
<definition class="...CpsScmFlowDefinition">
<scm class="hudson.plugins.git.GitSCM">
<url>https://git.f.novalon.cn/novalon/novalon-website.git</url>
<credentialsId>gitea-credentials</credentialsId>
<branches><name>*/main</name></branches>
</scm>
<scriptPath>Jenkinsfile</scriptPath>
<lightweight>true</lightweight>
</definition>
<triggers/>
```
### 3.3 ⚠️ 排障:推送不触发构建
这是本次排查中最容易踩的坑,按顺序核对:
1. **`<triggers/>` 为空是正常的吗?** 是 —— `GenericTrigger` 声明在 Jenkinsfile 的
`triggers {}` 块里,**第一次成功构建之后**才会在作业上注册 trigger。
所以:全新作业 push 后不触发,**先手动 Build Once 一次**。
2. **Token 是否一致**Jenkinsfile 里 `token: '${PROJECT_NAME}-ci-token'`
**Groovy 字符串插值**,在双引号字符串里才会展开。
写在 `token: '...'`(单引号)里就是字面量 `${PROJECT_NAME}-ci-token`
Gitea webhook 里填的 token 必须与 Jenkins 实际生效值一致(去作业配置页看真实 token)。
3. **Webhook 状态**:Gitea → 仓库 → 设置 → Web 钩子 → 最近推送记录,
看 HTTP 状态码。`403` = token 错;`404` = 插件端点错;`200/204` = 已接受。
4. **过滤表达式**`regexpFilterExpression: '^(refs/heads/main|refs/heads/develop)$'`
—— 只在这两个分支触发,其它分支 push 静默忽略属预期。
### 3.4 流水线阶段(Jenkinsfile 现状)
| Stage | 内容 | 分支条件 |
|-------|------|----------|
| 🔧 环境检测 | 工具版本 + SSH 连通性 + registry | 全部 |
| 📥 安装依赖 | `npm ci`(失败回退 `npm install --legacy-peer-deps` | 全部 |
| 🔍 质量检查 | ESLint ∥ TypeScript | 全部 |
| 🧪 单元测试 | `test:coverage:check` + Coverage Report | 全部 |
| 🌐 E2E | build + `@smoke\|@critical` + `@journey` | main |
| 👁️ 视觉回归 | `visual-regression.spec.ts` | main |
| 🔒 安全扫描 | `npm audit` + 安全响应头 | main |
| 🏗️ 构建 | `build:clean``dist/`archiveArtifacts | 全部 |
| 🚀 部署 | `deploy.sh deploy --skip-build`,失败自动 rollback | main + 参数开关 |
> 注意:E2E/视觉回归 stage 里 `|| echo "⚠️ ..."` 会**吞掉失败退出码**
> 测试挂了流水线仍然绿。若要严格门禁需改为 `exit 1`。
### 3.5 集成自检命令
```bash
# 1. Gitea 可达
curl -s -o /dev/null -w '%{http_code}\n' https://git.f.novalon.cn/api/v1/version
# 2. Jenkins 通用 webhook 端点存在(错误 token 应 403 而非 404
curl -s -o /dev/null -w '%{http_code}\n' \
'https://ci.f.novalon.cn/generic-webhook-trigger/invoke?token=wrong'
# 3. Gitea webhook 配置(需管理员或有仓库权限的令牌)
# Gitea UI → 仓库 → 设置 → Web 钩子
```