chore(infra): 新增 Gitea+Jenkins CI/CD 部署与凭据整改
- infra/cicd:docker-compose(gitea/jenkins)、JCasC(凭据统一 ${ENV} 注入,无硬编码)、
备份/恢复、健康检查、凭据轮换与 git 历史清除脚本
- docs/deployment/cicd:安装、高可用备份监控、凭据事故复盘
- .env.example 仅为占位模板;真实 .env 由 .gitignore 排除
This commit was merged in pull request #27.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
# 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 钩子
|
||||
```
|
||||
Reference in New Issue
Block a user