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

7.1 KiB
Raw Blame History

01 · 安装部署 / 安全配置 / Gitea↔Jenkins 集成

配置源码:infra/cicd/。本文描述目标态与从现状迁移的步骤。

1. 安装部署

1.1 前置条件

# Docker 20.10+docker compose v2
docker --version && docker compose version
# 外部网络(nginx 反代依赖同一网络做服务名解析)
docker network create novalon-network 2>/dev/null || true

1.2 部署 Gitea

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:22HTTP 仅 127.0.0.1:3001,由 nginx 终止 TLS。

1.3 部署 Jenkins

# ⚠️ 现状卷属主是 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: root1000:1000 root 容器 = 宿主 root 需先 chown 卷,服务中断 ~2 分钟
移除 /var/run/docker.sock 作业 config.xml 实测 0 处 docker 调用 若未来作业要用 docker,需改用 dind/socket-proxy
移除 /home/novalon/docker-app 挂载 deploy.shrsync 到远端,不读本地该目录
/root/.ssh./jenkins-ssh 不再暴露宿主 root 私钥 需生成专用部署密钥并放服务器 authorized_keys

2. 基础安全配置

2.1 HTTPS

nginx-static-production.conf + conf.d/*.conf 终止,无需应用自配。

# 已有配置(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; }

已知小瑕疵(P3nginx 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: falsecasc.yaml 待 JCasC 生效后验证
Jenkins 自助注册 allowsSignup: false 同上
agent 协议 Ping,关 JNLP 入站 无 agent,可整段删 50000 映射

2.3 凭据管理(重点)

原则:任何密码/令牌只允许出现在两个地方 —— 服务器 .env600 权限)或 Jenkins Credentials。

infra/cicd/.env                 ← 服务器本地,600,不入库
infra/cicd/.env.example         ← 模板,只有占位符,可入库
jenkins/casc.yaml               ← 凭据用 ${VAR} 引用环境变量,不写明文

禁止再出现 cicd.config 那种明文密码入库。相关 P0 事故见 03

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 作业定义(现状)

<!-- /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:cleandist/archiveArtifacts 全部
🚀 部署 deploy.sh deploy --skip-build,失败自动 rollback main + 参数开关

注意:E2E/视觉回归 stage 里 || echo "⚠️ ..."吞掉失败退出码, 测试挂了流水线仍然绿。若要严格门禁需改为 exit 1

3.5 集成自检命令

# 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 钩子