Files
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

8.1 KiB
Raw Permalink Blame History

02 · 高可用 / 数据备份 / 日志监控

脚本源码:infra/cicd/backup/infra/cicd/monitoring/。 本文所有数字均为 2026-09-14 在生产实测所得。

1. 高可用:先说清现状边界

当前是单机部署,没有真正的 HA。 不要被 compose 里的 deploy.replicas 迷惑 —— 那是 Swarm 语法,docker compose 非 swarm 模式下完全忽略replicas 实际恒为 1。

组件 现状 单点风险 建议(按性价比排序)
Jenkins 单实例 构建历史丢失(可重建) 接受单点,靠备份,不值得上 HA
Gitea 单实例 代码托管中断(clone 有本地副本) 接受单点 + 异地备份;量大了再考虑主从
PostgreSQL 单实例 用户/权限/PR/Issue/Webhook 唯一副本 最高优先级,每日备份必保
nginx 单实例 全站入口 20G/2C 小机,先做日志轮转与监控,不建议硬上 keepalived
宿主机 单机 全部 异地备份是唯一真正有效的容灾

结论:这台 2C/20G 机器的 HA 策略 = 快速恢复能力(备份 + 恢复演练 + 运维手册), 而不是冗余部署。冗余的成本(至少 3 台机器 + 运维复杂度)远超当前业务规模所需。

2. 数据备份

2.1 备份什么(以及为什么这样分)

数据 体积(实测) 丢失后果 策略
Gitea PostgreSQL 1.7MB 不可重建:用户、权限、PR、Issue、webhook 每日必备
Jenkins 配置 18KB 作业定义、凭据、用户 每日必备
Jenkins plugins 265M 可从清单重装 不备份,导出清单
Gitea 裸库 939M Git 分布式,每份 clone 都是完整历史 每周全量 + 异地
jenkins_home/war 111M 镜像自带 不备份

关键取舍:Jenkins 薄备份原本 222MB99% 是 plugins/。 排除后降到 18KB,磁盘压力从"不可持续"变为"可忽略"。 恢复时用 jenkins-plugins-<ts>.txt + jenkins-plugin-cli 重装即可。

2.2 备份脚本

backup-cicd.sh 的关键设计:

# 每份产物落盘后立刻校验:非空 + 归档可列出,坏备份当场失败
verify_archive "$f" tar

# 磁盘预检:剩余 < MIN_FREE_GB 直接中止(防写满拖垮 nginx)
if (( FREE_GB < MIN_FREE_GB )); then fail "..."; fi

# Jenkins 归档容忍退出码 1"读取时文件变化"),>=2 才算失败
if (( TAR_RC >= 2 )); then fail "..."; fi

实测输出(首份全量备份):

✅ gitea-db-20260914_174059.sql.gz      1.7MB
✅ gitea-repos-20260914_174059.tar.gz   939MB   (3177 条目)
✅ jenkins-config-20260914_174059.tar.gz 222MB  (2829 条目)
✅ 凭据解密所需的 master key 与 hudson.util.Secret 均已包含

2.3 调度(已安装到生产 crontab)

30 3 * * 1-6  backup-cicd.sh --skip-repos   # 周一至周六:薄(~2MB
30 3 * * 0    backup-cicd.sh                # 周日:全量(~940MB

保留策略按类型分开计数BACKUP_FULL_RETENTION=1BACKUP_THIN_RETENTION=3):

踩坑记录:若统一按「最近 N 份」裁剪,周中的薄备份会把周日全量挤出窗口, 仓库实际只受保护 3 天。分层计数后才真正保住全量。

2.4 恢复

restore-cicd.sh 内置三道防线:

  1. 归档完整性校验(tar/gzip 可列出)
  2. secrets/master.key 存在性校验 —— 缺失则直接拒绝恢复 (否则恢复完所有 Jenkins 凭据都解不开,等于白做)
  3. 恢复前先对当前库做一份快照
./restore-cicd.sh --from /home/novalon/backups/cicd/<ts> --component jenkins --dry-run
./restore-cicd.sh --from /home/novalon/backups/cicd/<ts> --component jenkins

铁律:未做过恢复演练的备份等于没有备份。 每季度至少跑一次 --dry-run

2.5 ⚠️ 当前最大缺口:仅本地备份

同盘备份不抗主机故障(盘坏/被删/勒索 = 全没)。脚本会持续警告:

⚠️  未配置 RCLONE_REMOTE —— 备份仅存本地。同盘备份不抗主机故障。

待办(需要决策)

  • 配置对象存储(COS/OSS/S3 任一),rclone 安装后在 .envRCLONE_REMOTE
  • 或扩容磁盘后仍建议至少一份异地

3. 日志

3.1 已知事故:849MB 单份日志

2026-09-14 实测:novalon-cicd/docker-compose.yml 里明确配了 logging: { max-size: 10m, max-file: 3 },但运行中容器的 HostConfig.LogConfig 实际是 {"Type":"json-file","Config":{}} —— 空配置

根因:logging 参数在容器创建时固化,事后改 compose 不重建就不生效。 Gitea 日志因此涨到 849MB,占整盘 4%。

已做的两件事:

  1. 立即 truncate -s 0 释放(磁盘 85% → 81%
  2. 兜底 cron 每日 05:10 跑 truncate-container-logs.sh

终态修复(待维护窗口):用 infra/cicd/docker-compose.gitea.yml 重建容器, 使 logging 配置真正生效,之后兜底脚本可退化为纯监控。

3.2 日志留存

来源 位置 轮转
容器 stdout /var/lib/docker/containers/<id>/*-json.log 重建后 10m×3;兜底 cron 每日截断 >100MB
nginx access/error /var/log/nginx/*.log docker-compose-nginx.yml 挂载宿主
备份 /var/log/cicd-backup.log 无(体积小,建议加 logrotate)
健康检查 /var/log/cicd-healthcheck.log 无(同上)

4. 监控

4.1 healthcheck-cicd.sh(每 10 分钟)

6 个维度,全部实测可跑:

[1] 容器状态        gitea/jenkins/postgresql/nginx 的 healthy
[2] HTTPS 端点      三个域名 200/303
[3] TLS 证书        剩余天数(<21 天 WARN<7 天 FAIL
[4] 磁盘与卷        使用率(>=80 WARN>=90 FAIL+ jenkins_home 体积
[5] 备份新鲜度      最新备份距今 <36h + 产物数 >=2
[6] Jenkins 队列    积压 >10 任务 FAIL

实测首跑即抓到 2 个真问题(容器 healthcheck 判定 bug 已修 + 磁盘 90%)。

# FAIL 时 exit 1cron 可直接接告警
./healthcheck-cicd.sh            # 全量输出
./healthcheck-cicd.sh --quiet    # 只在 FAIL/WARN 时输出
./healthcheck-cicd.sh --json     # 供采集器

4.2 告警

.envALERT_WEBHOOK_URL(企业微信/钉钉 webhook)后, 健康检查 FAIL 和备份失败都会推送。

4.3 现有监控资产

scripts/monitoring/ 已有轻量方案(setup-lightweight-monitoring.sh 等), 本次未改动。若要上 Prometheus,注意这台机器磁盘余量,指标留存周期要设短。

5. 已知环境陷阱(排障速查)

症状 真因 处理
compose 配了资源限制但没生效 deploy: 段非 swarm 下被忽略 cpus/mem_limit
compose 配了日志轮转但没生效 logging 参数创建时固化 重建容器
grep -qset -o pipefail 下恒假 -q 提前退出 → 上游 SIGPIPE(141) 用 herestring 或 grep -c
函数做布尔用却返回非 0 f() { [[ cond ]] && echo; } 在 cond 为假时返回 1cond2 && f || g 会跌进 g 函数末尾显式 return 0
健康检查 FAIL 数随 --quiet/--json 抖动 同上:输出被抑制 → 助手函数返回 1 同上
curl -w '%{http_code}' || echo 000 得到 200000 curl 已输出 200 但退出码非 0,echo 追加拼接 用 `
tar 报错但文件都存在 归档列表里写了不存在的路径(如 nodes/ 归档 . + --exclude
for x in "$@"shift 取错值 for 迭代的是展开后的固定列表,shift 不影响序列 while [[ $# -gt 0 ]] 解析
SSH 备份脚本中途死掉 断连触发 SIGHUP cron 场景无此问题;手动跑用 setsid
/root 占 2.7G /root/.trae-cn-serverbin 2.1G + manager-logs 522M 属 AI 工具数据,清理需 owner 确认