Compose 文件往往是你手头对一个系统最诚实的描述。在笔记本上、在 CI 里,有时甚至在生产环境中,真正启动起来的就是它。但它也是一堵 YAML 墙,新同事仍然得问:哪个服务在和哪个服务通信?
本文教你把 Compose 文件变成一张值得信赖的架构图:渲染出 Compose 实际运行的文件,把每个键映射到图元素,补上文件无法表达的调用,并让图与文件保持同步。最后,我们用 Archboard 的免费工具在几秒内生成第一版草图。
示例:一个小型 shop 应用栈
下面是我们要画的 compose.yaml。它很短,但常见的坑一个不少:一个接入两个网络的服务、一个只存在于配置文件里的依赖,以及一个只在开发时使用的工具。
name: shop
services:
web:
image: nginx:1.27
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
networks: [frontend]
api:
build: ./api
environment:
DATABASE_URL: postgres://shop:${POSTGRES_PASSWORD}@db:5432/shop
REDIS_URL: redis://cache:6379/0
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
networks: [frontend, backend]
worker:
build: ./api
command: ["node", "worker.js"]
environment:
DATABASE_URL: postgres://shop:${POSTGRES_PASSWORD}@db:5432/shop
REDIS_URL: redis://cache:6379/0
depends_on:
- db
deploy:
replicas: 2
networks: [backend]
db:
image: postgres:16
environment:
POSTGRES_USER: shop
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shop"]
interval: 5s
networks: [backend]
cache:
image: redis:7
networks: [backend]
adminer:
image: adminer
ports:
- "8081:8080"
profiles: [debug]
networks: [backend]
volumes:
db-data:
networks:
frontend:
backend:
第 1 步:渲染 Compose 实际运行的文件
团队很少原样运行单个文件。override 文件会添加端口,.env 文件会填入变量,extends 会从另一个服务拉取字段。动手画之前,先让 Compose 给出最终结果:
docker compose config > rendered.yaml
docker compose config 会合并 override 文件、填入变量、解析 extends,然后输出 Compose 将要运行的模型。请依据这份输出来画,而不是依据你碰巧打开的那个文件。
提醒一句:输出中包含变量的真实值,包括 .env 里的任何机密信息。请像对待 .env 文件本身一样对待 rendered.yaml,不要提交,也不要分享。
第 2 步:把 Compose 键映射到图元素
图的大部分内容可以直接从文件中得到:
| Compose 键 | 图元素 |
|---|---|
services.<name> |
每个服务一张卡片,以其键名命名 |
image / build |
写在卡片上:镜像标签或构建上下文 |
ports (8080:80) |
写在卡片上,以及从外部进入的连线上::8080 → 80 |
deploy.replicas / scale |
卡片上的副本数,而不是额外的方框 |
networks |
每个网络一个框;每个服务放在其第一个网络的框中 |
具名 volumes |
一个卷节点,连接到挂载它的服务 |
secrets、configs |
连接到使用它们的服务的节点,只写名称 |
depends_on |
启动顺序,写在卡片上或画成虚线连线 |
profiles |
可选服务:省略,或标记为可选 |
有两条规则能让图保持诚实。像 api 这样加入两个网络的服务,是这两个网络之间的桥梁,所以把它放在第一个网络的框的边缘,让它的连线跨进另一个网络。另外,副本是一个数字,而不是一摞方框:worker ×2 在你扩容到五个时依然正确。
第 3 步:补上文件里看不到的调用
这一步工具替你做不了。depends_on 关心的是启动顺序:Compose 会先启动 db 再启动 api,并且在设置了 condition: service_healthy 时等待健康检查通过。至于运行时谁调用谁,它什么也没说。
真正的调用藏在三个地方:
- 连接字符串。
DATABASE_URL和REDIS_URL指明了主机(db、cache)和端口。每一个都是一条连线。 - 挂载进容器的配置文件。
nginx.conf中写着proxy_pass http://api:3000;。这就是从web到api的连线,而它位于 Compose 文件之外。 - 代码。 一个服务可以调用另一个服务,而 Compose 里没有任何一行体现这一点。
worker使用了REDIS_URL,却没有针对cache的depends_on。如果 Redis 启动得晚,worker 会在启动时失败,而架构图是这个缺口最先暴露出来的地方。
简单搜索一下就能找到其中大部分:
grep -rnE "://([^/@[:space:]]*@)?(api|web|db|cache)[:/]|proxy_pass" --include="*.conf" --include="*.yaml" --include="*.yml" --include="*.env" .
然后与每个服务的负责人确认。把运行时调用画成实线连线,并标注协议和端口;把 depends_on 保留为注释或虚线连线,这样就没人会把启动顺序误当成流量。
第 4 步:决定省略什么
Compose 文件把系统本身和它周边的工具混在了一起。通常可以省略:
- 仅用于开发的服务。 也就是放在
profiles后面的服务,例如adminer、邮件捕获工具或调试代理。 - 一次性任务。 例如数据库迁移或数据填充脚本,除非这张图讲的就是部署。
- 源代码和配置文件的绑定挂载。 配置文件只通过它带来的依赖来体现,比如这里从
nginx.conf得出的proxy_pass连线。 - 环境变量的值。 变量名可以帮助解释一条连线;值应该放在你的密钥管理工具里,而不是画在图上。
第 5 步:完成布局
把流量从左到右排列:先是浏览器,然后是发布的端口,再往后是端口背后的服务。把每个网络画成一个框,这样跨网络的调用一眼就能看出来。把数据存储放在一起,并把卷放在挂载它的服务下方。每条连线都标注协议和端口,例如 HTTP :3000 或 SQL :5432。
用免费工具生成第一版草图
你不必手工画这些卡片。把文件(或渲染后的输出;工具不会读取环境变量的值)粘贴到免费工具 Docker Compose 转图表 中即可。查看无需账号。每个服务会变成一张卡片,显示镜像、端口和副本数;网络会变成框;具名卷、secret 和 config 会连接到使用它们的服务;depends_on 则会变成服务之间的连接。
工具只画文件里写明的内容。它不显示运行时调用,不读取环境变量的值或 env_file 的内容,也不跟进 include;对于使用 extends 的服务,它只画该服务中直接写出的字段。这就是第 1 步和第 3 步依然重要的原因。
有了免费账号,你可以在 Archboard 中把结果作为新图表打开,然后补上第 3 步的运行时连线,并去掉第 4 步提到的噪音。经过这一轮整理,shop 应用栈是这样的:
图表内容
- Browser
- frontend
- backend
- web
- api
- cache
- db
- worker
- db-data
- HTTP :8080 → 80
- proxy_pass :3000
- SQL :5432
- RESP :6379
- jobs :6379
- /var/lib/postgresql/data
卡片来自 Docker 节点包,浏览器来自网络节点包,整张图也在 Free 套餐 15 个图表节点的限制之内。如果想看另一种 Docker 布局,包含容器、镜像仓库和负载均衡器,请参阅 Docker Runtime Mesh 示例。
保持最新
根据 Compose 文件画出的图,在有人新增一个服务的那天就会过时。下面三个习惯会有帮助:
- 在 pull request 模板里加一行:“是否新增了服务、网络、发布端口或连接字符串?请更新架构图。”
- 评审时重新运行
docker compose config,并把服务列表与图进行对比。 - 在 README 中链接到实时的图,而不是粘贴截图,这样需要更新的只有一份。
当应用栈迁移到 Kubernetes 时,同样的方法也适用于清单;参见从 Kubernetes YAML 到架构图。
常见问题
depends_on 是否意味着一个服务调用另一个服务?
不是。它控制启动顺序,在设置了 condition: service_healthy 时还会等待健康检查。运行时调用来自连接字符串、配置文件和代码。
可以一次把多个 Compose 文件画成图吗?
先用 docker compose config 渲染,它会把 override 文件合并成一个模型。免费工具每次只读取一个文件。
工具会读取我的 .env 文件吗?
不会。变量不会被替换:用变量写的镜像标签或端口,例如 image: shop-api:${TAG},会按原样显示;environment 下的值和 env_file 中的值也不会被读取。
每个容器都要画一个方框吗? 每个服务画一张卡片,并在上面写明副本数。单个容器来来去去,团队思考的单位是服务。
修改 compose.yaml 后,图会自动更新吗? 不会。它只是一个起点。用上面的习惯让它保持最新,或者用工具重新画一遍,再把你的运行时连线搬过去。