ECS部署改ACR
1. 整体流程
旧方式是在 ECS 上下载源码并构建镜像:
提交代码 → ECS 下载源码 → ECS 构建镜像 → 启动容器
这种方式构建慢,还会占用生产服务器资源。
现在改为:
提交代码
→ 云效构建业务镜像
→ 推送到 ACR
→ ECS 拉取镜像
→ 启动容器
各组件的作用:
| 组件 | 作用 |
|---|---|
| 代码仓库 | 保存代码和版本记录 |
| 云效流水线 | 自动构建、推送和部署 |
| ACR | 保存 Docker 镜像 |
| ECS | 拉取并运行镜像 |
| Docker Compose | 管理多个容器 |
核心原则:云效负责构建,ACR 负责保存,ECS 只负责运行。
部署脑图
ECS 部署 ACR
├─ 代码仓库
│ └─ 保存业务代码和 Git 版本
├─ 基础镜像
│ ├─ 本地脚本构建
│ ├─ 包含 Python、uv 和后端依赖
│ └─ 推送到 ACR
├─ 云效流水线
│ ├─ 克隆当前提交
│ ├─ 从 ACR 拉取 Server 基础镜像
│ ├─ 构建 Server、Web 业务镜像
│ ├─ 使用 Commit SHA 标记版本
│ ├─ 推送业务镜像到 ACR
│ └─ 上传部署制品
├─ ACR
│ ├─ 保存基础镜像
│ └─ 保存每次提交的业务镜像
└─ ECS
├─ 下载并解压部署制品
├─ 从 ACR 拉取指定版本
├─ Docker Compose 启动容器
└─ Caddy → Web → Server → PostgreSQL
2. 一次性准备
首次搭建时需要准备:
- 在 ACR 创建 Server 业务镜像、Web 业务镜像和 Server 基础镜像仓库。
- 在云效配置 ACR 登录账号和密码,密码使用加密变量。
- 在 ECS 安装 Docker 和 Docker Compose,并加入云效主机组。
- 在云效创建 Server、Web 两个镜像构建任务和一个主机部署任务。
- 在安全组开放网站需要的端口。
这些内容只需配置一次,日常部署不需要重复操作。
3. 基础镜像和业务镜像
基础镜像
基础镜像保存不经常变化、下载比较慢的运行环境和依赖,例如:
Python + uv + 后端依赖
普通业务代码变化时,不需要重新制作基础镜像。
业务镜像
业务镜像包含当前提交的业务代码,由云效流水线构建并推送到 ACR。
Server 业务镜像的实际构建过程:
云效从 ACR 拉取 Server 基础镜像
→ 复制当前提交的后端业务代码
→ 构建 Server 业务镜像
→ 将业务镜像推送回 ACR
因此,基础镜像只负责提供 Python、uv 和后端依赖;每次提交的业务代码仍然由云效加入并生成新的业务镜像。
Web 当前直接使用 Node 基础镜像构建业务代码,然后将生成的 Web 业务镜像推送到 ACR。
每个业务镜像使用 Git Commit SHA 作为版本标签:
server:<commit-sha>
web:<commit-sha>
这样可以知道 ECS 运行的是哪次提交,也方便回滚。
Server 和 Web 必须使用同一个 ${CI_COMMIT_SHA}。部署时 ECS 也使用这个值拉取两个镜像,避免前后端版本不一致。
PostgreSQL、Caddy 等不包含业务代码的服务直接使用官方镜像,不需要为它们构建业务镜像。
4. 本地发布后端基础镜像
项目提供了本地发布脚本:
scripts/publish-server-base.ps1
在项目根目录执行:
pnpm docker:publish-server-base
脚本会自动:
- 根据后端依赖锁文件生成镜像版本。
- 登录 ACR。
- 构建基础镜像。
- 推送基础镜像到 ACR。
- 更新后端 Dockerfile 使用的基础镜像版本。
完成后,把依赖文件、基础镜像配置和脚本更新的 Dockerfile 一起提交:
git add server/pyproject.toml server/uv.lock server/Dockerfile.base server/Dockerfile
git commit -m "build(server): 更新后端依赖基础镜像"
git push
正确顺序是:先把基础镜像推送成功,再提交引用这个镜像版本的 Dockerfile。否则云效可能拉不到对应的基础镜像。
以下情况需要重新发布基础镜像:
- 新增、删除或升级后端依赖。
- 后端依赖锁文件发生变化。
- Python、uv 或基础镜像配置发生变化。
只修改业务代码时,直接运行普通流水线即可。
Web 当前使用公共 Node 镜像和云效构建缓存,还没有独立的 Web 基础镜像发布脚本。如果以后依赖下载仍然经常很慢,可以按相同思路增加 Web 基础镜像。
5. 云效流水线
流水线主要步骤:
克隆代码
→ 打包部署文件
→ 从 ACR 拉取 Server 基础镜像
→ 加入后端代码,构建并推送 Server 业务镜像
→ 构建并推送 Web 镜像
→ 上传制品
→ 部署到 ECS
镜像流转过程:
ACR 基础镜像 → 云效构建业务镜像 → ACR 业务镜像 → ECS 拉取运行
镜像构建任务
两个业务镜像构建任务都使用项目根目录作为构建上下文:
| 任务 | Dockerfile | 推送版本 |
|---|---|---|
| Server | server/Dockerfile | ${CI_COMMIT_SHA} |
| Web | web/Dockerfile | ${CI_COMMIT_SHA} |
Server 构建任务会通过 FROM 自动从 ACR 拉取基础镜像,再复制当前提交的后端代码。Web 构建任务会安装前端依赖并构建当前提交的页面。两个任务完成后,都会把业务镜像推送到 ACR。
云效构建缓存只用于加速构建,不是 ECS 部署的镜像版本。
打包部署文件
使用当前 Git 提交生成部署包,避免旧文件和缓存被重复打包:
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。主机部署脚本需要:
- 清空上次解压目录,避免新旧文件混用。
- 解压本次制品。
- 确认其中包含当前版本的
compose.yml和部署脚本。 - 进入解压后的项目目录。
- 执行仓库中的部署脚本:
scripts/deploy-production.sh
如果制品平台在外层再次包装了 hahame.tgz,需要先解压外层,再解压里面的 hahame.tgz。不能直接复用 ECS 上一次遗留的源码目录。
部署脚本主要执行:
docker compose pull postgres server web caddy
docker compose up -d --remove-orphans
docker compose ps
也就是先从镜像仓库拉取指定版本,再重建并启动容器。
部署脚本会使用云效传入的 CI_COMMIT_SHA 作为镜像版本,并在拉取私有镜像前登录 ACR。生产密码和密钥由云效变量传入,不保存在代码仓库中。
正常日志会出现:
Login Succeeded
server Pulled
web Pulled
server Healthy
web Healthy
caddy Started
如果日志中再次出现 [web build ...] 或 [server build ...],说明 ECS 仍在使用旧的现场构建方式。
6. 生产服务结构
浏览器
→ Caddy
→ Web
→ Server
→ PostgreSQL
- Caddy:网站入口和自动 HTTPS。
- Web:前端页面。
- Server:后端接口。
- PostgreSQL:数据库。
生产环境的密码和密钥保存在云效加密变量中,不要写进代码或提交 .env。
7. 日常操作
只修改业务代码
提交代码 → 推送 → 手动运行一条新流水线
当前关闭了代码提交自动触发,因此 git push 不会自动部署。需要在云效页面手动运行新流水线,不要重试旧提交的历史任务。
修改后端依赖
更新依赖
→ pnpm docker:publish-server-base
→ 提交 Dockerfile
→ 推送代码
→ 运行云效流水线
部署回滚
确认 ACR 中仍保留目标 Commit SHA 对应的 Server 和 Web 镜像,然后使用该 SHA 重新部署。不要把构建缓存标签当成业务版本。
8. 故障判断
| 现象 | 通常表示 |
|---|---|
ECS 日志出现 Pulled | 正在使用 ACR 镜像部署 |
ECS 日志出现 [web build ...] | 仍在执行旧版 ECS 现场构建 |
提示找不到 compose.yml | 制品打包或解压路径错误 |
容器显示 unhealthy | 查看对应容器日志和健康检查 |
| 容器健康但域名无法访问 | 检查安全组、DNS、HTTPS 和备案状态 |
9. 总结
基础镜像:解决依赖下载慢
业务镜像:保存每次提交的代码
云效:构建并推送镜像
ACR:保存镜像
ECS:拉取并运行镜像