DAEMON / ACR

ECS部署改ACR

2026.09.16 · 11 MIN READ
ACR · CI/CD · MONOREPO · CONTAINERIZATION

将 MonoRepo 部署从 ECS 本地构建迁移至云效+ACR 流水线,实现构建与运行分离。通过基础镜像缓存依赖、业务镜像绑定 Commit SHA 版本,配合 Docker Compose 在 ECS 拉取运行,提升部署速度并保障前后端版本一致性。

1. 整体流程

旧方式是在 ECS 上下载源码并构建镜像:

text
提交代码 → ECS 下载源码 → ECS 构建镜像 → 启动容器

这种方式构建慢,还会占用生产服务器资源。

现在改为:

text
提交代码
  → 本地脚本push 基础镜像server web到ACR
  → 云效流水线构建业务镜像
  → 推送到 ACR
  → ECS 拉取镜像
  → 启动容器

各组件的作用:

组件作用
代码仓库保存代码和版本记录
云效流水线自动构建、推送和部署
ACR保存 Docker 镜像
ECS拉取并运行镜像
Docker Compose管理多个容器

核心原则:云效负责构建,ACR 负责保存,ECS 只负责运行。

部署脑图

text
ECS 部署 ACR
├─ 代码仓库
│  └─ 保存业务代码和 Git 版本
├─ 基础镜像
│  ├─ 本地脚本构建
│  ├─ 包含 Python、uv 和后端依赖
│  └─ 推送到 ACR
├─ 云效流水线
│  ├─ 克隆当前提交
│  ├─ 从 ACR 拉取 Server 基础镜像
│  ├─ 构建 Server、Web 业务镜像
│  ├─ 使用 Commit SHA 标记版本
│  ├─ 推送业务镜像到 ACR
│  └─ 上传部署制品
├─ ACR
│  ├─ 保存基础镜像
│  └─ 保存每次提交的业务镜像
└─ ECS
   ├─ 下载并解压部署制品
   ├─ 从 ACR 拉取指定版本
   ├─ Docker Compose 启动容器
   └─ Caddy → Web → Server → PostgreSQL

2. 一次性准备

首次搭建时需要准备:

这些内容只需配置一次,日常部署不需要重复操作。

3. 基础镜像和业务镜像

基础镜像

基础镜像保存不经常变化、下载比较慢的运行环境和依赖,例如:

text
Python + uv + 后端依赖

普通业务代码变化时,不需要重新制作基础镜像。

业务镜像

业务镜像包含当前提交的业务代码,由云效流水线构建并推送到 ACR。

Server 业务镜像的实际构建过程:

text
云效从 ACR 拉取 Server 基础镜像
  → 复制当前提交的后端业务代码
  → 构建 Server 业务镜像
  → 将业务镜像推送回 ACR

因此,基础镜像只负责提供 Python、uv 和后端依赖;每次提交的业务代码仍然由云效加入并生成新的业务镜像。

Web也是类似处理,目前占用Server做记录说明。

每个业务镜像使用 Git Commit SHA 作为版本标签:

text
server:<commit-sha>
web:<commit-sha>

这样可以知道 ECS 运行的是哪次提交,也方便回滚。

Server 和 Web 必须使用同一个 ${CI_COMMIT_SHA}。部署时 ECS 也使用这个值拉取两个镜像,避免前后端版本不一致。

PostgreSQL、Caddy 等不包含业务代码的服务直接使用官方镜像,不需要为它们构建业务镜像。

4. 本地发布后端基础镜像

项目提供了本地发布脚本:

text
scripts/publish-server-base.ps1

在项目根目录执行:

powershell
pnpm docker:publish-server-base

脚本会自动:

  1. 根据后端依赖锁文件生成镜像版本。
  2. 登录 ACR。
  3. 构建基础镜像。
  4. 推送基础镜像到 ACR。
  5. 更新后端 Dockerfile 使用的基础镜像版本。

完成后,把依赖文件、基础镜像配置和脚本更新的 Dockerfile 一起提交:

powershell
git add server/pyproject.toml server/uv.lock server/Dockerfile.base server/Dockerfile
git commit -m "build(server): 更新后端依赖基础镜像"
git push

正确顺序是:先把基础镜像推送成功,再提交引用这个镜像版本的 Dockerfile。否则云效可能拉不到对应的基础镜像。

以下情况需要重新发布基础镜像:

只修改业务代码时,直接运行普通流水线即可。

5. 云效流水线

流水线主要步骤:

text
克隆代码
  → 打包部署文件
  → 从 ACR 拉取 Server 基础镜像
  → 加入后端代码,构建并推送 Server 业务镜像
  → 构建并推送 Web 镜像
  → 上传制品
  → 部署到 ECS

镜像流转过程:

text
ACR 基础镜像 → 云效构建业务镜像 → ACR 业务镜像 → ECS 拉取运行

镜像构建任务

两个业务镜像构建任务都使用项目根目录作为构建上下文:

任务Dockerfile推送版本
Serverserver/Dockerfile${CI_COMMIT_SHA}
Webweb/Dockerfile${CI_COMMIT_SHA}

Server 构建任务会通过 FROM 自动从 ACR 拉取基础镜像,再复制当前提交的后端代码。Web 构建任务会安装前端依赖并构建当前提交的页面。两个任务完成后,都会把业务镜像推送到 ACR。

云效构建缓存只用于加速构建,不是 ECS 部署的镜像版本。

打包部署文件

使用当前 Git 提交生成部署包,避免旧文件和缓存被重复打包:

bash
set -euo pipefail

ROOT_DIR="$(git rev-parse --show-toplevel)"
OUTPUT_FILE="$(pwd)/hahame.tgz"

rm -f "${OUTPUT_FILE}"

git -C "${ROOT_DIR}" archive --format=tar HEAD |
  gzip > "${OUTPUT_FILE}"

echo "打包提交:$(git -C "${ROOT_DIR}" rev-parse HEAD)"
tar -xOzf "${OUTPUT_FILE}" scripts/deploy-production.sh |
  grep "pull postgres server web caddy"

ls -lh "${OUTPUT_FILE}"

ECS 部署

云效先把 hahame.tgz 上传为制品,再由主机部署任务下载到 ECS。主机部署脚本需要:

  1. 清空上次解压目录,避免新旧文件混用。
  2. 解压本次制品。
  3. 确认其中包含当前版本的 compose.yml 和部署脚本。
  4. 进入解压后的项目目录。
  5. 执行仓库中的部署脚本:
bash
scripts/deploy-production.sh

如果制品平台在外层再次包装了 hahame.tgz,需要先解压外层,再解压里面的 hahame.tgz。不能直接复用 ECS 上一次遗留的源码目录。

部署脚本主要执行:

bash
docker compose pull postgres server web caddy
docker compose up -d --remove-orphans
docker compose ps

也就是先从镜像仓库拉取指定版本,再重建并启动容器。

部署脚本会使用云效传入的 CI_COMMIT_SHA 作为镜像版本,并在拉取私有镜像前登录 ACR。生产密码和密钥由云效变量传入,不保存在代码仓库中。

正常日志会出现:

text
Login Succeeded
server Pulled
web Pulled
server Healthy
web Healthy
caddy Started

如果日志中再次出现 [web build ...] 或 [server build ...],说明 ECS 仍在使用旧的现场构建方式。

6. 生产服务结构

text
浏览器
  → Caddy
  → Web
  → Server
  → PostgreSQL

生产环境的密码和密钥保存在云效加密变量中,不要写进代码或提交 .env。

7. 日常操作

只修改业务代码

text
提交代码 → 推送 → 手动运行一条新流水线

修改后端依赖

text
更新依赖
  → pnpm docker:publish-server-base
  → 提交 Dockerfile
  → 推送代码
  → 运行云效流水线

部署回滚

确认 ACR 中仍保留目标 Commit SHA 对应的 Server 和 Web 镜像,然后使用该 SHA 重新部署。不要把构建缓存标签当成业务版本。

8. 总结

text
基础镜像:解决依赖下载慢
业务镜像:保存每次提交的代码
云效:构建并推送镜像
ACR:保存镜像
ECS:拉取并运行镜像