Docker 容器健康检查配置指南:实现自动故障恢复
在实际项目中,我发现很多团队用 Docker 部署服务,但很少有人注意到健康检查这个功能。容器显示"running"不等于服务可用——这是一个很常见的坑。
背景
Docker 监控的是进程是否存活,不是应用是否健康。进程启动了,容器就是 running 状态,哪怕应用已经挂死。这是一个设计取舍,但确实带来了麻烦:服务明明不可用了,容器还显示一切正常。
问题
用一个具体例子说明。你运行了这样一个容器:
docker run -d --name my-api myregistry/my-api:latest
容器在运行,但以下几种情况都会导致服务实际不可用:
- Node.js 进程没退出,但事件循环被阻塞死了
- 应用内存耗尽,无法响应请求
- 依赖的数据库挂了,但进程还活着
默认情况下,你只能手动发现问题,或者写外部脚本定时探测。
配置步骤
Docker 提供了 HEALTHCHECK 指令解决这个问题。
参数说明
HEALTHCHECK 支持几个参数:
INTERVAL:两次检查之间的间隔,默认 30 秒TIMEOUT:单次检查的超时时间,默认 30 秒RETRIES:连续失败多少次算不健康,默认 3 次START_PERIOD:容器启动后多久开始第一次检查,默认 0 秒
检测方式选择
不同应用用不同方式检测:
- HTTP 服务:访问一个专门的健康检查端点
- 数据库:用客户端执行测试查询
- Redis:发送 PING 命令
- 命令行应用:检查端口或某个文件
Dockerfile 配置
以 Flask 应用为例:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD python healthcheck.py
CMD ["python", "app.py"]
healthcheck.py 可以这样写:
#!/usr/bin/env python3
import sys
import urllib.request
import urllib.error
def check_health():
try:
url = "http://localhost:5000/health"
req = urllib.request.Request(url)
with urllib.request.urlopen(req, timeout=5) as response:
if response.status == 200:
print("Health check passed")
sys.exit(0)
else:
print(f"Health check failed: status {response.status}")
sys.exit(1)
except urllib.error.URLError as e:
print(f"Health check failed: {e}")
sys.exit(1)
except Exception as e:
print(f"Health check error: {e}")
sys.exit(1)
if __name__ == "__main__":
check_health()
docker-compose 配置
version: '3.8'
services:
api:
build: .
ports:
- "5000:5000"
healthcheck:
test: ["CMD", "python", "healthcheck.py"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
nginx:
image: nginx:latest
ports:
- "80:80"
depends_on:
api:
condition: service_healthy
restart: unless-stopped
depends_on 的 condition: service_healthy 很有用——它确保 Nginx 只有在 API 容器通过健康检查后才会启动。
查看状态
docker ps
STATUS 列会显示健康检查结果:
CONTAINER ID IMAGE STATUS PORTS NAMES
abc123def456 my-api:latest Up 2 minutes (healthy) 0.0.0.0:5000->5000/tcp my-api
不健康时会显示 (unhealthy)。
查看详细日志:
docker inspect --format='{{json .State.Health}}' my-api
完整示例
完整代码如下:
# app.py
from flask import Flask, jsonify
import time
import random
app = Flask(__name__)
@app.route('/')
def index():
return jsonify({"status": "ok", "message": "API is running"})
@app.route('/health')
def health():
return jsonify({"status": "healthy"})
@app.route('/simulate-failure')
def simulate_failure():
# 模拟故障:长时间阻塞
time.sleep(300)
return jsonify({"status": "ok"})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install --no-cache-dir flask
COPY app.py healthcheck.py ./
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python healthcheck.py
CMD ["python", "app.py"]
# docker-compose.yml
version: '3.8'
services:
api:
build: .
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
healthcheck:
test: ["CMD", "python", "healthcheck.py"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped
networks:
- api-network
networks:
api-network:
driver: bridge
运行结果
启动服务:
docker-compose up -d
docker ps
初始状态是 (starting):
CONTAINER ID IMAGE STATUS PORTS NAMES
abc123 my-api Up 5 seconds (starting) 0.0.0.0:5000->5000/tcp myapi-api-1
等待 start_period 后,变成 (healthy):
abc123 my-api Up 35 seconds (healthy) 0.0.0.0:5000->5000/tcp myapi-api-1
模拟故障:
curl http://localhost:5000/simulate-failure
应用被阻塞后,健康检查会连续失败。检查状态:
docker inspect --format='{{.State.Health.Status}}' myapi-api-1
会返回 unhealthy。如果配置了 restart: unless-stopped,Docker 会自动重启容器:
docker events --filter 'event=restart'
容器重启后会重新经历 (starting) 到 (healthy) 的过程。
总结
健康检查解决几个实际问题:
- 故障自动发现——不用靠用户投诉才知道服务挂了
- 自动恢复——结合 restart 策略,容器可以自己重启
- 服务依赖管理——用
condition: service_healthy确保依赖就绪再启动下游 - 状态可见——容器状态一目了然,便于接入监控系统
配置时注意几点:根据应用类型选检测方式,HTTP 服务直接检测端点最靠谱。有数据库依赖的,在健康检查里包含连接测试。interval、timeout、retries 这些参数要结合应用的实际启动时间和响应时间来调整。
这个功能会增加一点资源开销,但换来的可靠性提升很值得。花几分钟配置一下,关键时刻能救命。