diff --git a/.codebuddy/plans/管理后台_CI_CD_流水线_1e4d6d0a.md b/.codebuddy/plans/管理后台_CI_CD_流水线_1e4d6d0a.md new file mode 100644 index 0000000..b000e62 --- /dev/null +++ b/.codebuddy/plans/管理后台_CI_CD_流水线_1e4d6d0a.md @@ -0,0 +1,125 @@ +--- +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 清单、生产机准备、触发方式) +``` \ No newline at end of file diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 4d71ccb..2987d4f 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 拉取代码 - uses: actions/checkout@v4 + uses: https://gitee.com/liu_zhengchi/checkout@v4 - name: 确认 docker 可用 run: |