--- name: 管理后台 CI/CD 流水线 overview: 参照业务服务 /root/zt-closet-backend 的 Gitea Actions CI/CD(.gitea/workflows/deploy.yaml + 多阶段 Dockerfile + .dockerignore),为本服务 /root/zt-closet-manage-backend 建立等价的构建部署流水线:tag 推送或手动触发 → 构建镜像推送到 Gitea 仓库 → SSH 部署生产机 → 健康检查失败自动回滚。 todos: - id: create-deploy-workflow content: 新增 .gitea/workflows/deploy.yaml 构建部署流水线(tag v* 与手动触发、镜像推送、SSH 部署、健康检查失败回滚) status: completed - id: create-dockerfile content: 新增 scripts/dockerfile/Dockerfile 多阶段构建(golang:1.26-alpine 静态编译、alpine:3.22 非 root 运行、TZ 与 /ping 健康检查) status: completed - id: create-dockerignore content: 新增 .dockerignore(排除 configs/storage/文档/scripts 等,防密钥进入镜像层) status: completed - id: update-readme content: 更新 README 新增「CI/CD 与部署」章节(流水线说明、Gitea Secrets/Vars 清单、生产机目录与配置准备) status: completed dependencies: - create-deploy-workflow - id: verify-cicd content: 验证:pyyaml 校验 workflow 语法,docker build 本地构建确认 Dockerfile 可产出镜像 status: completed dependencies: - create-deploy-workflow - create-dockerfile - create-dockerignore --- ## 产品概述 为管理后台服务(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): 1. `actions/checkout@v4` 拉取代码。 2. 确认 docker 可用(`docker version`)。 3. 计算镜像标签:`refs/tags/*` 取 tag 名,否则 `sha-<7位SHA>`。 4. `docker login` 登录 Gitea 镜像仓库(secrets:`ZT_CLOSET_MANAGE_REGISTRY_USER` / `ZT_CLOSET_MANAGE_REGISTRY_TOKEN`)。 5. `docker build --build-arg GOPROXY=https://goproxy.cn,direct -f scripts/dockerfile/Dockerfile -t : -t :latest .` 并推送 tag + latest。 6. 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` 清理旧镜像。 7. 部署结果步骤(`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 清单、生产机准备、触发方式) ```