用一个文件声明整个栈意味着什么
一句话总结
Compose 文件是以声明方式记录多个容器运行参数的文件。它并非魔法,而是与 docker run 的选项一一对应。
为什么需要它
如果手动启动三个容器,就必须创建网络、创建卷,并按正确顺序为每个容器添加 --network、-v、-e、-p。一个人操作一次没有问题,但从第二个人试图复现同一套栈开始,就会陷入困境,因为这些信息没有记录在任何地方。
Compose 真正解决的就是这个问题。整个栈的形态写在一个文件中,并将该文件提交到代码仓库。
工作原理
核心对应关系如下。
| Compose 键 | 对应的 docker run 参数 |
|---|---|
image |
镜像参数 |
command |
镜像后的命令 |
ports |
-p |
volumes |
-v / --mount |
environment |
-e |
networks |
--network |
depends_on |
(无对应参数——仅控制启动顺序) |
这里必须指出两点。
第一,Compose 会为每个项目自动创建自定义网络,并把服务名称注册为别名。 因此,有些配置“在 Compose 中正常,改成 docker run 就不能工作”。原因不是 Compose 的魔法,而是手动迁移时没有创建网络。
第二,depends_on 只保证顺序,不保证服务已经就绪。 DB 容器“已启动”和数据库“已准备好接受连接”是两回事。因此,必须同时设置健康检查条件,或让应用程序进行重试。忽略这一差别,每次部署后的最初几秒都会涌现连接错误。
实际现场中的情况
实战配置中常用的模式是划分两个网络。前端网络只放反向代理,后端网络放数据库和缓存,只有应用同时加入两边。这样,代理一侧甚至无法解析数据库的名称。
也不要混淆 expose 和 ports。**expose 不会在主机上开放端口,**它只用于文档说明。仅供容器之间通信的服务根本不应使用 ports。
最后,声明与实际状态开始偏离的那一刻,就是事故的起点。有人手动修改容器后,文件与现实便会分离,而且在下次部署前无人察觉。因此,养成核对声明与实际状态的习惯非常重要。
正确设置依赖顺序
仅有 depends_on 并不足够。它只表示容器已启动,不表示服务已经就绪。 DB 容器虽已运行,但 PostgreSQL 仍在初始化时,应用会因连接失败而退出。
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s # 이 동안의 실패는 세지 않는다
app:
build: .
depends_on:
db:
condition: service_healthy # ← 건강해질 때까지 기다린다
start_period 很重要。如果没有它,初始化过程中的失败会消耗重试次数,使启动较慢的服务最终一直被判定为 unhealthy。
即便如此,应用程序仍必须具备重新连接能力。 生产环境中可能没有 Compose,数据库也可能重启。健康检查只是开发便利功能,不能替代重连逻辑。
按环境叠加文件
可以叠加使用多个 Compose 文件。
docker compose -f compose.yaml -f compose.dev.yaml up
后面的文件覆盖前面的文件。公共配置放在 compose.yaml,开发用挂载和调试端口放在 compose.dev.yaml。compose.override.yaml 仅凭文件名就会自动应用,因此适合存放开发者个人设置(应加入 .gitignore)。
# compose.dev.yaml — 소스를 마운트해 즉시 반영
services:
app:
volumes: ["./src:/app/src"]
environment: {DEBUG: "1"}
command: ["python", "-m", "uvicorn", "app:app", "--reload"]
Compose 与 Kubernetes 的边界
在 Compose 中正常运行的配置,无法原样搬到 Kubernetes。迁移时会遇到以下差异。
| Compose | Kubernetes |
|---|---|
depends_on |
无——改用 initContainer 或重试 |
| 按服务名称进行 DNS 解析 | 相同(Service 名称) |
volumes: ./src:/app |
hostPath——生产环境应避免 |
restart: always |
默认行为(restartPolicy) |
ports: 8080:80 |
Service + Ingress |
虽然有 kompose 这类转换工具,但不能直接使用其输出结果,因为其中资源请求与限制、探针、安全上下文全都为空。转换结果只是起点,必须手动补全。
下一项实验要做什么
用 Compose 文件声明一个由两个服务组成的栈,再手动启动相同的栈,然后亲自核对声明的值与实际运行的值是否一致。