个人项目和小团队的常见误判:容器化之后觉得"不用 K8s 就不专业"。事实是单机 docker-compose 能稳定支撑相当大的规模——本博客所在的那台 ECS 上跑着二十多个服务,全是 compose 管理,稳定运行一年以上。关键是纪律。
骨架:一份能活下去的 compose
services:
app:
image: myapp:1.4.2 # 用具体 tag,不用 latest
restart: unless-stopped # 崩溃自愈的底线
networks: [frontend, backend]
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:3000/healthz"]
interval: 30s
timeout: 3s
retries: 3
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" } # 不设就会吃满磁盘
db:
image: postgres:16
restart: unless-stopped
networks: [backend] # 数据库不进 frontend 网络
volumes: [pgdata:/var/lib/postgresql/data]
networks:
frontend: {}
backend: { internal: true } # 后端网不开出站,少一层暴露面
volumes:
pgdata: {}
四个要点:固定 tag、restart 策略、日志轮转、网络隔离。最后一条最常被忽略——internal: true 让数据库所在网络无法主动出站,应用被攻破也难横向外联。
环境变量与密钥
.env 进 .gitignore,仓库里放 .env.example。更敏感的(生产密钥)直接放宿主机 systemd 的 EnvironmentFile,不进项目目录。密钥泄露的第一大来源就是 compose 文件被提交进仓库。
升级与回滚
docker compose pull app && docker compose up -d app # 升级
docker tag myapp:1.4.1 myapp:rollback && docker compose up -d app # 回滚(保留旧镜像)
单机回滚的底气来自:镜像 tag 固定 + 数据都在 volume 里 + 配置在环境变量里。三者齐备,回滚就是 30 秒的事。
备份:compose 之外的定时任务
数据不在容器里,在 volume 里——备份 volume 就是备份全部状态:
# 每日凌晨:压缩卷 + 上传对象存储 + 保留 7 份
docker run --rm -v pgdata:/data -v /backup:/backup alpine \
tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .
没做备份的 compose 集群等于写在沙滩上。restore 演练至少做一次——没验证过恢复的备份不算备份。
什么时候才需要 K8s
出现以下信号再考虑:多机调度需求(单机资源到顶)、服务需要按优先级抢占、团队有了专职运维。在那之前,compose + 一台好机器 + 这份纪律,就是单人运维的最优解:所有状态一目了然,一个 YAML 讲清全部架构。