Docker 运行时治理:资源限制、健康检查与日志边界

容器能启动不等于服务可长期运行。生产治理至少要覆盖资源上限、进程退出、健康状态、日志轮转、数据持久化和网络暴露。多数线上事故不是 Docker 本身复杂,而是默认值没有被明确设计。

给资源设置边界

内存无限制时,一个异常容器可能挤压整台主机;限制过紧则会频繁 OOM。先根据压测设置初值,再监控峰值:

services:
  api:
    mem_limit: 1g
    cpus: 1.5
    pids_limit: 300

应用自身也要感知限制,例如 Go 设置合理 GOMEMLIMIT,JVM 使用容器感知的堆比例。容器内存不仅是语言堆,还包括线程栈、Direct Buffer、页缓存和运行时开销。

Healthcheck 检查“能否服务”

healthcheck:
  test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1"]
  interval: 10s
  timeout: 3s
  retries: 5
  start_period: 20s

Liveness 只判断进程是否需要重启,Readiness 判断是否能接流量。若把所有下游故障都视为 Liveness 失败,数据库短暂抖动可能导致所有 API 同时重启,放大事故。健康接口要快、无副作用,并设置严格超时。

正确处理 PID 1 与退出信号

容器主进程必须接收 SIGTERM,停止新请求,等待在途任务并在宽限期内退出。Shell 入口脚本应使用 exec 把信号交给应用:

#!/bin/sh
exec /app/server "$@"

需要回收子进程时使用 init: true 或合适 Init。不要依赖 SIGKILL,强杀会中断事务、文件写入和消息确认。

日志必须轮转

直接使用默认 JSON 日志而不限制大小,最终可能写满磁盘:

logging:
  driver: json-file
  options:
    max-size: 20m
    max-file: "3"

应用写结构化日志到 stdout/stderr,采集系统负责汇总。容器内不要再维护一套无限增长日志文件。错误日志要包含请求 ID 和业务上下文,但不得打印 Token、验证码和密码。

数据和网络边界

  • 数据库、上传文件映射到明确持久卷,并定期备份。
  • 只有入口服务映射宿主机端口,内部服务留在私有网络。
  • 配置文件只读挂载,运行目录按最小权限开放。
  • 不挂载 Docker Socket 给普通业务容器。
  • 使用固定服务名发现,不把临时容器 IP 写入配置。

发布前演练

重启容器、重启 Docker、磁盘接近满、下游不可用、进程收到 SIGTERM、数据卷迁移、日志快速增长,这些场景都应有预期行为。能从故障中恢复,才算完成部署。

参考资料