MonoRepo前后端项目部署到阿里云 ECS
1. 部署架构
系统由两个应用服务和一个数据库服务组成:
| 服务 | 技术 | 容器 |
|---|---|---|
| Web | Next.js + Node.js | hahame-web-1 |
| Server | FastAPI + Python | hahame-server-1 |
| Database | PostgreSQL + pgvector | hahame-postgres-1 |
当前部署链路:
Local
↓ git push
Codeup
↓
云效 Flow
↓
源码制品 hahame.tgz
↓
Runner
↓
ECS
↓
Docker Compose Build
↓
PostgreSQL → Server → Web
各组件的职责:
| 组件 | 职责 |
|---|---|
| Codeup | 代码版本管理 |
| 云效 Flow | CI/CD 流程编排 |
| 制品仓库 | 保存源码发布包 |
| Runner | 连接 ECS 并执行部署 |
| Docker | 构建和运行容器 |
| Docker Compose | 多服务编排 |
当前由云效负责 CI/CD 和源码传输,ECS 同时承担 Build 和 Runtime。
2. ECS 环境
ECS 宿主机主要准备:
- Docker Engine
- Docker Buildx
- Docker Compose
- 必要的 Shell 工具
- Swap
Node.js、Python 和 PostgreSQL 都运行在容器中,宿主机不需要单独安装。
宿主机尽量只提供 Docker,业务运行环境由镜像决定。
Swap
当前 ECS 配置为 2 GiB 内存和 4 GiB Swap。
Next.js 和 Python 的 Docker Build 可能瞬时占用较多内存,2 GiB 机器容易发生 OOM。Swap 可以降低构建进程被系统杀掉的概率。
Swap 是低内存机器的安全垫,不是性能优化,也不能替代真实内存。
Docker 镜像加速
国内 ECS 拉取 Docker Hub 镜像时,可能遇到:
Client.Timeout exceeded while awaiting headers
检查 Registry Mirror:
docker info | grep -A 5 "Registry Mirrors"
ECS 的 Docker Mirror 只影响 ECS,不会自动同步到云效构建环境。
3. 云效流水线
当前流水线的执行顺序:
- 拉取
main分支。 - 打包源码。
- 上传
hahame.tgz到制品仓库。 - Runner 将制品发送到 ECS。
- ECS 解压制品。
- 执行
deploy-production.sh。 - Docker Compose 构建并启动服务。
源码制品需要排除:
.git.envnode_modulesweb/.nextserver/.venv
这样可以确保制品只包含构建需要的文件,不携带本地依赖、缓存和生产 Secret。
部署脚本
云效主机部署阶段只负责下载和解压制品,然后进入项目目录执行:
scripts/deploy-production.sh
具体部署逻辑放在 Repository Script 中,可以获得以下好处:
- 通过 Git 管理版本。
- 支持 Code Review。
- 部署逻辑与代码版本保持对应。
- 减少云效页面中的脚本复杂度。
云效负责编排流程,Repository Script 负责具体部署逻辑。
4. 生产环境变量
生产配置通过云效变量注入,主要包括:
- 数据库:
POSTGRES_DB、POSTGRES_USER、POSTGRES_PASSWORD - 后台账号:
ADMIN_USERNAME、ADMIN_PASSWORD - 身份认证:
JWT_SECRET - 百炼服务:
DASHSCOPE_API_KEY、DASHSCOPE_BASE_URL - RAG:
RAG_EMBEDDING_MODEL、RAG_EMBEDDING_DIMENSIONS
其中以下变量需要开启私密模式:
POSTGRES_PASSWORDADMIN_PASSWORDJWT_SECRETDASHSCOPE_API_KEY
部署时生成 /opt/hahame/app/.env,并限制文件权限:
chmod 600 /opt/hahame/app/.env
代码、配置和 Secret 必须分开管理。Secret 不进入 Git,也不进入源码制品。
5. Docker Compose
当前启动命令:
docker compose \
--project-name hahame \
--env-file .env \
-f compose.yml \
up -d --build --remove-orphans
服务启动关系:
PostgreSQL
↓ healthy
FastAPI
↓ Alembic Migration
↓ healthy
Next.js
Docker Compose 负责容器、网络、Volume、环境变量、服务依赖和健康检查的统一编排。
数据持久化
PostgreSQL 数据保存在 Volume hahame_postgres_data 中:
- Image:应用模板。
- Container:Image 的运行实例。
- Volume:需要长期保留的数据。
应用 Container 可以重新构建,但数据库 Volume 必须保护好。
不要在生产环境随意执行
docker compose down -v。参数-v会删除 Volume,可能直接删除 PostgreSQL 数据。
6. 日常发布
完成本地开发后提交代码:
git add .
git commit -m "<message>"
git push origin main
然后执行云效流水线:
Codeup 最新代码
↓
生成 hahame.tgz
↓
上传制品
↓
Runner → ECS
↓
执行部署脚本
↓
Docker Build
↓
Compose 重建服务
Docker 会复用没有变化的 Layer,因此后续构建通常比第一次更快。
Dockerfile 中变化少的内容放在前面,频繁变化的源码
COPY放在后面,可以提高 Layer Cache 利用率。
7. 常用运维
查看服务
docker compose \
--project-name hahame \
--env-file .env \
-f compose.yml \
ps
也可以按容器名称过滤:
docker ps --filter name=hahame
查看日志
查看最近 100 行日志:
docker logs --tail 100 hahame-server-1
持续跟踪日志:
docker logs -f --tail 100 hahame-server-1
按下 Ctrl + C 只会退出日志跟踪,不会停止 Container。
重启服务
docker compose restart server
restart只会重启现有 Container,不会重新构建 Image。代码发生变化后仍然需要重新 Build。
查看资源
free -h
df -h
docker stats
docker system df
这些命令分别用于查看:
| 命令 | 作用 |
|---|---|
free -h | Memory 和 Swap |
df -h | 磁盘占用 |
docker stats | 容器 CPU 和内存 |
docker system df | Docker 磁盘占用 |
清理 Build Cache
清理前先检查 Docker 磁盘占用:
docker system df
确认后清理 Build Cache:
docker builder prune
清理时不要误删数据库 Volume
hahame_postgres_data。
8. Multi-stage Build 踩坑
后端曾在 Docker Multi-stage Build 和 uv Virtual Environment 组合下出现 Exit Code 127。
原因是 uv 创建的虚拟环境引用了 Build Stage 中的 Python 路径,但该路径在最终 Runtime Image 中不存在。
处理方式:
UV_PYTHON=/usr/local/bin/python3
UV_PYTHON_DOWNLOADS=never
Multi-stage Build 不仅要确认文件已经复制到 Runtime Stage,还要确认文件引用的解释器、动态库和软链接等运行时依赖仍然存在。
9. 当前架构的问题
当前生产服务器同时负责构建和运行:
云效
↓
发送源码
↓
ECS
├── Pull Base Image
├── Build Business Image
└── Runtime
对于当前 2 GiB ECS,这会带来:
- 构建期间的 CPU 和内存压力。
- Docker Build Cache 持续占用磁盘。
- 发布时间较长。
- 构建可能影响正在运行的线上服务。
更合理的职责边界是:CI 负责 Build,ECS 专注 Runtime。
10. 优化方向一:ACR 镜像发布
推荐的目标架构:
Developer
↓
Codeup
↓
云效 Flow
↓
Build Web / Server Image
↓
ACR
↓
ECS Pull
↓
Docker Compose Run
各组件重新分工:
| 组件 | 职责 |
|---|---|
| Codeup | Source |
| 云效 | Build |
| ACR | Image Artifact |
| ECS | Runtime |
| Compose | Service Orchestration |
架构变化可以概括为:
当前:源码 → ECS → Build → Run
优化:源码 → CI Build → ACR → ECS Pull → Run
也就是把 Build 从生产服务器移到 CI。
镜像版本
生产镜像不要只使用 latest,推荐使用 Git Commit SHA 或 Pipeline Build Number,例如:
hahame-web:a81f29c
hahame-server:a81f29c
这样可以实现:
- 版本可追踪。
- 镜像不可变。
- 快速回滚。
- Build Once,Run Same Image。
ECS 部署命令也会从本机构建:
docker compose up -d --build
调整为拉取已经构建好的镜像:
docker compose pull server web
docker compose up -d --remove-orphans
优化后 ECS 不再 Build,只负责 Pull 和 Run。
11. 优化方向二:蓝绿部署
ACR 解决的是构建、镜像版本、发布产物和 ECS 构建压力,但 Container 替换时仍然可能出现短暂中断。
如果需要进一步降低发布中断,可以引入 Blue/Green:
Nginx
│
┌───────┴───────┐
│ │
Blue Green
Web + Server Web + Server
│ │
└───────┬───────┘
│
PostgreSQL
一次发布的大致流程:
- Blue 当前对外提供服务。
- Green 拉取并启动新镜像。
- 等待 Green Health Check 通过。
- Nginx 将流量从 Blue 切换到 Green。
- 验证新版本。
- 确认稳定后停止 Blue。
下一次发布再从 Green 切换回 Blue。
数据库迁移需要保持新旧版本兼容,一般采用:
- Expand:增加新字段或新表。
- Migrate:逐步迁移数据和应用逻辑。
- Contract:确认旧版本退出后删除旧结构。
12. 最终架构演进
阶段 1:当前
Codeup
↓
云效
↓
源码制品
↓
ECS
├── Build
└── Runtime
阶段 2:推荐
Codeup
↓
云效 Build
↓
ACR
↓
ECS Runtime
阶段 3:低中断发布
Codeup
↓
云效 Build
↓
ACR
↓
ECS
├── Blue
└── Green
↓
Nginx Traffic Switch
最后记忆
| 组件 | 负责内容 |
|---|---|
| Codeup | Source |
| 云效 | CI/CD |
| ACR | Image Artifact |
| ECS | Runtime |
| Docker Compose | Service Orchestration |
| Volume | Persistent Data |
.env / CI Variable | Configuration 和 Secret |
| Nginx + Blue/Green | Traffic Switch 和 Low Downtime |
CI 负责 Build,Registry 负责保存镜像,ECS 负责 Runtime,Compose 负责服务编排;需要进一步降低发布中断时,再引入 Blue/Green。