8.7 KiB
8.7 KiB
name, overview, todos
| name | overview | todos | |||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 管理后台 CI/CD 流水线 | 参照业务服务 /root/zt-closet-backend 的 Gitea Actions CI/CD(.gitea/workflows/deploy.yaml + 多阶段 Dockerfile + .dockerignore),为本服务 /root/zt-closet-manage-backend 建立等价的构建部署流水线:tag 推送或手动触发 → 构建镜像推送到 Gitea 仓库 → SSH 部署生产机 → 健康检查失败自动回滚。 |
|
产品概述
为管理后台服务(zt-closet-manage-backend)建立与业务服务一致的 CI/CD 体系。参照业务服务已有的 Gitea Actions 流水线与 Docker 构建方案,实现「打 tag 自动触发 + 手动触发」的镜像构建、推送与生产机部署,并带健康检查与失败自动回滚能力。
核心功能
- 构建部署流水线(Gitea Actions):推送
v*标签或手动触发后,构建 Docker 镜像推送到 Gitea 镜像仓库(git.lzcwy.cn),再通过 SSH 部署到生产机。 - 多阶段 Dockerfile:Go 静态编译(golang:1.26-alpine)→ 精简运行镜像(alpine:3.22),非 root 用户运行,内置
/ping健康检查,配置与日志目录以卷挂载方式接入(密钥不进镜像层)。 - 部署安全机制:部署前记录旧镜像 ID,新容器启动 5 秒后健康检查,失败自动输出日志并回滚到旧镜像。
- .dockerignore:剔除配置密钥、日志、文档等非构建资产,防止密钥进入镜像层。
- 文档:README 增加 CI/CD 章节,说明流水线触发方式、Gitea Secrets/Vars 配置清单、生产机目录准备要求。
- 不包含数据库迁移流水线:管理后台无独立迁移命令(admin_users 表由服务启动时 GORM AutoMigrate 幂等创建),故无需业务服务的 mysqlup/mysqldown 工作流。
与业务服务的差异适配
业务服务(zt-closet-backend)CI/CD 的调研结论:Gitea Actions 三套工作流(deploy.yaml、mysqlup.yaml、mysqldown.yaml)+ scripts/dockerfile 下两个 Dockerfile + .dockerignore。管理后台按自身特性适配:Go 版本 1.26(业务 1.27)、无上传功能(挂载仅 configs + storage/logs)、独立部署目录与 Secrets 命名(ZT_CLOSET_MANAGE_*)。
技术栈
- CI/CD 平台:Gitea Actions(与业务服务同一 Gitea 实例 git.lzcwy.cn,runner 支持 docker)
- 容器:Docker(多阶段构建,golang:1.26-alpine → alpine:3.22)
- 部署:SSH 免密部署到生产机 + docker run(--network host)+ curl/wget 健康检查 + 旧镜像回滚
- 验证:pyyaml 语法校验 + 本机 docker build 构建验证(本机 docker 26.1.3 可用)
实现方案
1. 流水线文件 .gitea/workflows/deploy.yaml
结构复刻业务 deploy.yaml,参数按管理后台调整:
name: 构建并部署
on:
push:
tags: ['v*']
workflow_dispatch:
concurrency:
group: deploy
cancel-in-progress: false
env:
REGISTRY: git.lzcwy.cn
IMAGE: git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend
Job 步骤(runs-on: ubuntu-latest):
actions/checkout@v4拉取代码。- 确认 docker 可用(
docker version)。 - 计算镜像标签:
refs/tags/*取 tag 名,否则sha-<7位SHA>。 docker login登录 Gitea 镜像仓库(secrets:ZT_CLOSET_MANAGE_REGISTRY_USER/ZT_CLOSET_MANAGE_REGISTRY_TOKEN)。docker build --build-arg GOPROXY=https://goproxy.cn,direct -f scripts/dockerfile/Dockerfile -t <IMAGE>:<tag> -t <IMAGE>:latest .并推送 tag + latest。- SSH 部署生产机(secrets:
ZT_CLOSET_MANAGE_PROD_SSH_KEY;vars:ZT_CLOSET_MANAGE_PROD_SSH_HOST/ZT_CLOSET_MANAGE_PROD_SSH_USER),远端脚本:
DIR=/opt/zt-closet-manage、APP=zt-closet-manage-backend;mkdir -p $DIR/configs $DIR/storage/logs并chown -R 10001:10001 $DIR/storage(容器内非 root 用户 uid 10001 需日志写权限);- 记录旧镜像 ID →
docker pull→docker rm -f→docker run -d --name $APP --network host --restart unless-stopped -v $DIR/configs:/app/configs:ro -v $DIR/storage/logs:/app/storage/logs -e TZ=Asia/Shanghai --log-opt max-size=50m --log-opt max-file=5 $IMAGE:latest; sleep 5后curl/wget http://127.0.0.1:8080/ping健康检查(端口须与生产 config.yaml 的Server.HttpPort一致,注释说明);- 失败:
docker logs --tail 50→ 移除新容器 → 若有旧镜像则以相同参数启动旧镜像回滚 →exit 1; - 成功:
docker image prune -f清理旧镜像。
- 部署结果步骤(
if: always())输出镜像标签。
2. 多阶段 Dockerfile scripts/dockerfile/Dockerfile
复刻业务 Dockerfile,差异:ARG GO_VERSION=1.26(管理后台 go.mod 为 go 1.26.1)、二进制名 zt-closet-manage-backend、runtime 仅建 configs + storage/logs 两个挂载点(无 uploads)。
- 构建阶段:
FROM golang:${GO_VERSION}-alpine AS builder;ARG TARGETOS/TARGETARCH(默认 linux/amd64,兼容未启用 buildx 场景);ARG GOPROXY默认https://proxy.golang.org,direct(流水线内以 build-arg 覆盖为 goproxy.cn);先COPY go.mod go.sum+go mod download复用缓存层,再COPY . .;ENV CGO_ENABLED=0,GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w" -o /out/zt-closet-manage-backend .静态编译。 - 运行阶段:
FROM alpine:${ALPINE_VERSION}(3.22);apk add --no-cache ca-certificates tzdata(HTTPS 依赖根证书、本地时区计算每日额度展示);addgroup -g 10001 -S app && adduser -u 10001 -S -G app app非 root;WORKDIR /app;COPY 二进制;mkdir -p /app/configs /app/storage/logs && chown -R app:app /app;ENV TZ=Asia/Shanghai、ENV APP_PORT=8080;USER app;EXPOSE ${APP_PORT};HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 CMD wget -q -O - "http://127.0.0.1:${APP_PORT}/ping" || exit 1;CMD ["/app/zt-closet-manage-backend"]。
3. .dockerignore
复刻业务按实际目录裁剪:.git、.gitignore、*.md、docs/、design/、storage/、configs/(密钥不进构建上下文)、scripts/、Dockerfile、.dockerignore。.gitea/ 与业务一致不排除。
4. README 新增「CI/CD 与部署」章节
- 流水线说明:打
v*标签或 Gitea 页面手动触发;触发后构建推送镜像并部署到生产机,健康检查失败自动回滚。 - Gitea 仓库需创建的 Secrets:
ZT_CLOSET_MANAGE_REGISTRY_USER、ZT_CLOSET_MANAGE_REGISTRY_TOKEN、ZT_CLOSET_MANAGE_PROD_SSH_KEY;Variables:ZT_CLOSET_MANAGE_PROD_SSH_HOST、ZT_CLOSET_MANAGE_PROD_SSH_USER。 - 生产机准备:目录
/opt/zt-closet-manage(configs/config.yaml 必填、storage/logs 自动创建并授权 uid 10001);config.yaml的Server.HttpPort须为 8080(与健康检查一致)。 - 说明无迁移流水线:
admin_users表由服务启动时 AutoMigrate 自动创建。
5. 验证
python3 -c "import yaml; yaml.safe_load(...)"校验 deploy.yaml 语法。- 本机
docker build -f scripts/dockerfile/Dockerfile --build-arg GOPROXY=https://goproxy.cn,direct -t zt-closet-manage-backend:test .验证 Dockerfile 可正常构建出镜像(不推送、不部署)。
目录结构
/root/zt-closet-manage-backend/
├── .gitea/
│ └── workflows/
│ └── deploy.yaml # [NEW] 构建部署流水线(tag v* / 手动触发,SSH 部署 + 健康检查回滚)
├── scripts/
│ └── dockerfile/
│ └── Dockerfile # [NEW] 多阶段构建(golang:1.26-alpine → alpine:3.22,非 root,HEALTHCHECK /ping)
├── .dockerignore # [NEW] 排除配置密钥/日志/文档等非构建资产
└── README.md # [MODIFY] 新增「CI/CD 与部署」章节(Secrets/Vars 清单、生产机准备、触发方式)