- infra/cicd:docker-compose(gitea/jenkins)、JCasC(凭据统一 ${ENV} 注入,无硬编码)、
备份/恢复、健康检查、凭据轮换与 git 历史清除脚本
- docs/deployment/cicd:安装、高可用备份监控、凭据事故复盘
- .env.example 仅为占位模板;真实 .env 由 .gitignore 排除
177 lines
8.1 KiB
Markdown
177 lines
8.1 KiB
Markdown
# 02 · 高可用 / 数据备份 / 日志监控
|
||
|
||
脚本源码:[`infra/cicd/backup/`](../../../infra/cicd/backup/)、[`infra/cicd/monitoring/`](../../../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 薄备份原本 222MB,**99% 是 `plugins/`**。
|
||
> 排除后降到 18KB,磁盘压力从"不可持续"变为"可忽略"。
|
||
> 恢复时用 `jenkins-plugins-<ts>.txt` + `jenkins-plugin-cli` 重装即可。
|
||
|
||
### 2.2 备份脚本
|
||
|
||
[`backup-cicd.sh`](../../../infra/cicd/backup/backup-cicd.sh) 的关键设计:
|
||
|
||
```bash
|
||
# 每份产物落盘后立刻校验:非空 + 归档可列出,坏备份当场失败
|
||
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=1`、`BACKUP_THIN_RETENTION=3`):
|
||
|
||
> 踩坑记录:若统一按「最近 N 份」裁剪,周中的薄备份会把周日全量挤出窗口,
|
||
> 仓库实际只受保护 3 天。分层计数后才真正保住全量。
|
||
|
||
### 2.4 恢复
|
||
|
||
[`restore-cicd.sh`](../../../infra/cicd/backup/restore-cicd.sh) 内置三道防线:
|
||
|
||
1. 归档完整性校验(tar/gzip 可列出)
|
||
2. **`secrets/master.key` 存在性校验** —— 缺失则直接拒绝恢复
|
||
(否则恢复完所有 Jenkins 凭据都解不开,等于白做)
|
||
3. 恢复前先对当前库做一份快照
|
||
|
||
```bash
|
||
./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` 安装后在 `.env` 填 `RCLONE_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/monitoring/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%)。
|
||
|
||
```bash
|
||
# FAIL 时 exit 1,cron 可直接接告警
|
||
./healthcheck-cicd.sh # 全量输出
|
||
./healthcheck-cicd.sh --quiet # 只在 FAIL/WARN 时输出
|
||
./healthcheck-cicd.sh --json # 供采集器
|
||
```
|
||
|
||
### 4.2 告警
|
||
|
||
`.env` 配 `ALERT_WEBHOOK_URL`(企业微信/钉钉 webhook)后,
|
||
健康检查 FAIL 和备份失败都会推送。
|
||
|
||
### 4.3 现有监控资产
|
||
|
||
`scripts/monitoring/` 已有轻量方案(`setup-lightweight-monitoring.sh` 等),
|
||
本次未改动。若要上 Prometheus,注意这台机器磁盘余量,指标留存周期要设短。
|
||
|
||
## 5. 已知环境陷阱(排障速查)
|
||
|
||
| 症状 | 真因 | 处理 |
|
||
|------|------|------|
|
||
| compose 配了资源限制但没生效 | `deploy:` 段非 swarm 下被忽略 | 用 `cpus`/`mem_limit` |
|
||
| compose 配了日志轮转但没生效 | logging 参数创建时固化 | 重建容器 |
|
||
| `grep -q` 在 `set -o pipefail` 下恒假 | `-q` 提前退出 → 上游 SIGPIPE(141) | 用 herestring 或 `grep -c` |
|
||
| 函数做布尔用却返回非 0 | `f() { [[ cond ]] && echo; }` 在 cond 为假时返回 1,`cond2 && f \|\| g` 会跌进 g | 函数末尾显式 `return 0` |
|
||
| 健康检查 FAIL 数随 `--quiet`/`--json` 抖动 | 同上:输出被抑制 → 助手函数返回 1 | 同上 |
|
||
| `curl -w '%{http_code}' \|\| echo 000` 得到 `200000` | curl 已输出 200 但退出码非 0,`echo` 追加拼接 | 用 `|| true` + 正则校验三位数字 |
|
||
| tar 报错但文件都存在 | 归档列表里写了不存在的路径(如 `nodes/`) | 归档 `.` + `--exclude` |
|
||
| `for x in "$@"` 内 `shift` 取错值 | for 迭代的是展开后的固定列表,shift 不影响序列 | 用 `while [[ $# -gt 0 ]]` 解析 |
|
||
| SSH 备份脚本中途死掉 | 断连触发 SIGHUP | cron 场景无此问题;手动跑用 `setsid` |
|
||
| `/root` 占 2.7G | `/root/.trae-cn-server`(bin 2.1G + manager-logs 522M) | 属 AI 工具数据,清理需 owner 确认 |
|