TFRSManager 部署排查手册¶
本文档基于首次生产部署中遇到的实际问题整理,覆盖 CNB 流水线 → SSH 传输 → 服务器部署全链路。
部署架构概览¶
CNB 流水线 (web_trigger_deploy_prod)
├── generate env file → 从密钥仓库生成 .env.prod
├── prepare deploy files → 打包到 deploy_pkg/
└── ssh-deploy 插件 → 传输文件 + 执行 deploy.sh
↓
生产服务器 /opt/tfrs/
├── deploy_pkg/ → deploy.sh 自动解包到上级目录
├── docker-compose.prod.yml
├── .env.prod
└── scripts/deploy.sh
↓
Docker Compose
├── postgres (127.0.0.1:5432)
├── redis (127.0.0.1:6379)
├── user-service (127.0.0.1:8080)
└── admin-service (127.0.0.1:8081)
一、CNB 流水线阶段¶
1.1 Pipeline init error: GetFileContent: not found¶
现象: 流水线在 init 阶段直接失败,未执行任何 stage。
原因: imports 引用的密钥仓库文件不存在或文件名不匹配。
排查步骤:
1. 检查 .cnb.yml 中所有 imports 的 URL 路径
2. 到 turingfocus/build_env 密钥仓库确认文件名完全一致(含大小写、后缀)
3. 确认文件在 main 分支上
实际案例: 仓库中文件名为 tfrsmanager_env.prod.example.yml,但 imports 引用的是 tfrsmanager_env.prod.yml。
涉及文件:
- .cnb.yml — pipeline 级别 imports(第 267-272 行)
- .cnb.yml — ssh-deploy 插件级别 imports(第 348 行)
1.2 镜像标签找不到 / 部署失败¶
现象: [错误] 最近 20 个提交中未找到已构建的镜像
原因: 部署前未构建镜像,或构建时的 commit 已不在最近 20 个提交内。
排查步骤:
1. 确认已触发过 web_trigger_build_all_images
2. 在 CNB 制品页确认镜像标签存在
3. 手动指定 IMAGE_TAG 触发部署
解决: 部署流水线会自动遍历最近 20 个 commit,优先匹配 git tag(如 v1.0.0),其次匹配 commit SHA。
二、SSH 连接与传输阶段¶
2.1 SSH private key is missing¶
现象: ssh-deploy 插件报 Error: SSH private key is missing.
原因: 插件 settings 字段名错误。
正确的 ssh-deploy 插件参数:
| 参数 | 说明 |
|------|------|
| ssh_host | 服务器 IP(不是 host) |
| ssh_port | SSH 端口(不是 port) |
| ssh_user | 用户名(不是 username) |
| ssh_private_key | SSH 私钥(不是 key) |
| transfer_files | yes/no |
| source_file_path | 本地文件/目录路径(不支持逗号分隔多文件) |
| destination_path | 远程绝对路径 |
| execute_remote_script | yes/no |
| deploy_script | 远程脚本绝对路径 |
| copy_script | 是否先上传脚本 yes/no |
| source_script | 本地脚本路径(copy_script=yes 时) |
关键注意:
- 插件的 imports 要单独声明在插件步骤内,不继承 pipeline 级别的 imports
- source_file_path 不支持逗号分隔,传多文件需先打包到一个目录
- 传目录时,目录本身会成为 destination 的子目录(如 deploy_pkg/ → /opt/tfrs/deploy_pkg/)
2.2 Failed to set permissions / sudo: a password is required¶
现象: SSH 连接成功,但插件内部 sudo 失败。
原因: deploy 用户无免密 sudo 权限,或目标目录不属于 deploy 用户。
解决(在服务器上以 root 执行):
# 创建部署目录并授权
mkdir -p /opt/tfrs/{scripts,backups}
chown -R deploy:deploy /opt/tfrs
# 配置免密 sudo(ssh-deploy 插件内部会用 sudo)
echo 'deploy ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/deploy
chmod 440 /etc/sudoers.d/deploy
2.3 Remote script does not exist¶
现象: Error: Remote script does not exist: /opt/tfrs/scripts/deploy.sh
原因: deploy_script 路径与实际传输的文件路径不一致。
排查: SSH 到服务器 find /opt/tfrs/ -name "deploy.sh" 确认实际路径,然后修改 deploy_script 设置。
当前方案: 文件传输到 /opt/tfrs/deploy_pkg/,deploy.sh 位于 /opt/tfrs/deploy_pkg/scripts/deploy.sh,脚本内部 Step 0 会自动将文件移动到 /opt/tfrs/ 下。
三、服务器部署阶段(deploy.sh)¶
3.1 Docker Registry 登录失败 / 镜像拉取失败¶
现象: docker pull 报认证错误。
排查步骤:
1. 检查 .env.prod 中是否包含 REGISTRY、PROD_DOCKER_USER、PROD_DOCKER_PASSWORD
2. 确认密钥仓库 tfrsmanager_env.prod.yml 中有这三个变量
3. 确认 scripts/env-keys.txt 白名单中包含 PROD_DOCKER_USER 和 PROD_DOCKER_PASSWORD
4. SSH 到服务器手动验证: cat /opt/tfrs/.env.prod | grep DOCKER
deploy.sh 自动登录逻辑(Step 2):
REGISTRY=$(grep -E '^REGISTRY=' .env.prod | cut -d= -f2-)
DOCKER_USER=$(grep -E '^PROD_DOCKER_USER=' .env.prod | cut -d= -f2-)
DOCKER_PASSWORD=$(grep -E '^PROD_DOCKER_PASSWORD=' .env.prod | cut -d= -f2-)
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USER}" --password-stdin "${REGISTRY}"
3.2 健康检查超时¶
现象: [错误] admin-service 健康检查超时(或 user-service)
排查步骤:
1. SSH 到服务器检查容器状态: docker ps | grep tfrs
2. 手动测试健康端点: curl -sv http://127.0.0.1:8081/health
3. 查看容器日志: docker logs --tail=50 tfrs_admin
4. 检查容器是否在反复重启: docker ps -a | grep tfrs
常见原因:
| 现象 | 原因 | 解决 |
|---|---|---|
Connection reset by peer |
容器内监听 127.0.0.1,Docker 端口映射无法转发 | compose 中加 ADMIN_BIND_ADDR: "0.0.0.0" |
Connection refused |
服务未启动/崩溃 | 查日志,检查环境变量是否完整 |
容器 Restarting |
启动时 Fatal 错误 | docker logs tfrs_xxx 查看崩溃原因 |
| 日志停在迁移后无输出 | 日志写入文件非 stdout | 正常现象(production 模式),检查 /app/logs/ |
SECRET_ENCRYPTION_KEY 未配置 |
.env.prod 缺少必要变量 | 检查 env-keys.txt 和密钥仓库 |
重要: admin-service 默认监听 127.0.0.1:8081(代码中 ADMIN_BIND_ADDR 默认值),在 Docker 容器内必须改为 0.0.0.0 才能被端口映射访问。已通过 docker-compose.prod.yml 的 environment 注入 ADMIN_BIND_ADDR: "0.0.0.0"。
3.3 traces export 连接拒绝¶
现象: traces export: Post "http://localhost:14268/v1/traces": dial tcp [::1]:14268: connect: connection refused
原因: Jaeger 未部署,OpenTelemetry 导出失败。
影响: 不影响服务运行,仅丢失链路追踪数据。生产环境如不需要 Jaeger 可忽略。
四、快速排查流程图¶
部署失败
├── Pipeline init 失败?
│ └── → 检查 imports 文件名是否匹配(§1.1)
├── SSH 连接失败?
│ └── → 检查 ssh-deploy settings 参数名(§2.1)
├── SSH 权限失败?
│ └── → 服务器上配置 deploy 用户权限(§2.2)
├── 远程脚本不存在?
│ └── → 检查 deploy_script 路径与传输路径是否一致(§2.3)
├── 镜像拉取失败?
│ └── → 检查 .env.prod 中 Registry 凭证(§3.1)
├── 健康检查超时?
│ └── → SSH 到服务器 curl + docker logs 排查(§3.2)
└── 服务启动但功能异常?
└── → 检查 .env.prod 环境变量是否完整(对比 env-keys.txt)
五、常用排查命令¶
# === 服务器端 ===
# 查看所有 TFRS 容器状态
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep tfrs
# 健康检查
curl -s http://127.0.0.1:8080/health
curl -s http://127.0.0.1:8081/health
# 查看服务日志
docker logs --tail=100 tfrs_user
docker logs --tail=100 tfrs_admin
# 查看 .env.prod 内容(脱敏)
grep -E '^[A-Z]' /opt/tfrs/.env.prod | cut -d= -f1
# 查看部署目录结构
ls -la /opt/tfrs/
# 手动重启服务
cd /opt/tfrs && docker compose -f docker-compose.prod.yml --env-file .env.prod up -d
# 回滚到上次配置
cp /opt/tfrs/backups/.env.prod.latest /opt/tfrs/.env.prod
cd /opt/tfrs && docker compose -f docker-compose.prod.yml --env-file .env.prod up -d
# === CNB 侧 ===
# 不重新构建,指定旧标签部署
# 在 CNB 界面触发 web_trigger_deploy_prod,IMAGE_TAG 填旧的 commit SHA 或 git tag
六、关键文件清单¶
| 文件 | 作用 | 修改后是否需要重新构建镜像 |
|---|---|---|
.cnb.yml |
CI/CD 流水线定义 | 否 |
.cnb/web_trigger.yml |
CNB 界面手动触发按钮 | 否 |
docker-compose.prod.yml |
生产 compose 编排 | 否(重新部署即可) |
scripts/deploy.sh |
服务器端部署脚本 | 否(重新部署即可) |
scripts/env-keys.txt |
环境变量白名单 | 否(重新部署即可) |
cmd/*/main.go |
服务入口代码 | 是 |
internal/**/*.go |
业务代码 | 是 |
Dockerfile.* |
镜像构建定义 | 是 |
七、密钥仓库文件对照¶
turingfocus/build_env 密钥仓库中需要的文件:
| 文件名 | 引用位置 | 内容 |
|---|---|---|
cnb_env.yaml |
pipeline imports | 企业微信机器人 webhook |
tfrsmanager_env.prod.yml |
pipeline imports | 所有生产环境变量(对应 env-keys.txt) |
tfrsmanager_deploy_ssh.yml |
ssh-deploy 插件 imports | PROD_SSH_HOST, PROD_SSH_PORT, PROD_SSH_USER, PROD_SSH_KEY, PROD_DOCKER_USER, PROD_DOCKER_PASSWORD |