Docker容器"僵尸状态"问题排查与自动重启方案
Docker容器"僵尸状态"问题排查与自动重启方案
问题背景
在运行基于BSC(币安智能链)的节点集群时,遇到了一个诡异的问题:归档节点(archive node)的Docker容器显示为运行状态,但实际上内部的geth进程已经停止工作。
环境信息
- 节点类型: 5个验证节点 + 1个归档节点
- Docker镜像: myorg/chain:0.0.4
- 同步模式: full sync + archive模式
- 运行时长: 2天
问题现象
1. 容器状态显示正常
$ sudo docker ps
CONTAINER ID IMAGE STATUS NAMES
6f96594ef9a4 myorg/chain:0.0.4 Up 2 days bsc-archive-1
容器状态显示 Up 2 days,看起来一切正常。
2. 资源使用异常
$ sudo docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT
6f96594ef9a4 bsc-archive-1 0.00% 0B / 0B
关键异常点:
- CPU使用率:0%
- 内存使用:0B
- 网络I/O:0B
- 磁盘I/O:0B
这明显不正常!归档节点正常运行时应该有持续的资源消耗。
3. 无法执行命令
$ sudo docker exec -it 6f96594ef9a4 geth attach /root/.ethereum/geth.ipc --exec "eth.blockNumber"
cannot exec in a stopped state
$ sudo docker exec -it 6f96594ef9a4 ps aux
cannot exec in a stopped state
虽然 docker ps 显示容器运行中,但无法在容器内执行任何命令。
4. 日志停滞
最后一条日志停留在昨天:
INFO [01-08|22:31:37.346] Re-queue blocks number=1,681,468
hash=0x2a9ad1f2fb1235dd3a84a06f42f85ef853049acbde9e1062e90b50a8a167e63e
日志停在了区块重组(re-queue blocks)操作,之后再无输出。
问题分析
什么是"僵尸容器"状态?
这是一种特殊的容器故障模式:
┌─────────────────────────────────────┐
│ Docker Container (Running) │
│ ┌───────────────────────────────┐ │
│ │ tini (PID 1) - 存活 ✓ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ geth 进程 - 已挂 ✗ │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
关键点:
- 容器的init进程(tini)仍在运行 → Docker认为容器是健康的
- geth主进程已经停止或卡死 → 实际服务已失效
- Docker没有进程级别的健康检查 → 无法发现内部进程故障
为什么会出现这种情况?
从日志分析,可能的原因包括:
1. 区块重组时数据库问题
Re-queue blocks number=1,681,468
这个操作可能触发了:
- 数据库锁死(database lock)
- 数据损坏(corruption)
- 状态树重建失败
2. 内存不足
配置中设置了:
--cache 8000 # 8GB缓存
--gcmode archive # 归档模式,不清理历史状态
--txlookuplimit 0 # 无限制事务索引
归档模式 + 大缓存 + 全量索引,内存需求极高,可能触发OOM。
3. 磁盘I/O瓶颈
从统计数据看,validator1的磁盘读取达到了345GB,archive节点可能在密集I/O时遇到了问题。
为什么配置了 restart: always 却不重启?
这是Docker的设计机制:
restart: always # 仅在容器退出时重启
restart策略只关注容器退出:
- 容器崩溃(exit code ≠ 0)→ 重启 ✓
- 容器正常退出(exit code = 0)→ 重启 ✓
- 容器运行但内部进程挂了 → 不重启 ✗
Docker无法感知容器内部进程的健康状态!
解决方案
方案一:添加健康检查(HealthCheck)
修改 docker-compose.yml,添加健康检查配置:
archive:
image: myorg/chain:0.0.4
user: "1000:1000"
command:
- --datadir
- /data
- --gcmode
- archive
# ... 其他参数 ...
restart: unless-stopped
# 核心配置:健康检查
healthcheck:
test: ["CMD-SHELL", "geth attach --exec 'eth.blockNumber' /data/geth.ipc || exit 1"]
interval: 60s # 每60秒检查一次
timeout: 30s # 30秒超时
retries: 3 # 连续失败3次才标记为unhealthy
start_period: 300s # 启动后5分钟才开始检查
volumes:
- ./data/archive-data:/data
- ./logs/archive:/data/logs
network_mode: "host"
stop_grace_period: 120s
工作原理:
- Docker每60秒尝试连接geth IPC并获取区块高度
- 如果命令失败(进程卡死/IPC不响应),标记为失败
- 连续3次失败后,容器状态变为
unhealthy
查看健康状态:
$ sudo docker ps
CONTAINER ID STATUS NAMES
6f96594ef9a4 Up 2 hours (healthy) bsc-archive-1
方案二:自动治愈容器(Autoheal)
仅有healthcheck还不够,因为Docker不会自动重启 unhealthy 的容器。需要添加一个监控容器:
services:
archive:
# ... 上面的配置 ...
healthcheck:
test: ["CMD-SHELL", "geth attach --exec 'eth.blockNumber' /data/geth.ipc || exit 1"]
interval: 60s
timeout: 30s
retries: 3
start_period: 300s
validator1:
# ... 其他验证节点 ...
# 自动治愈容器
autoheal:
image: willfarrell/autoheal:latest
container_name: autoheal
restart: always
environment:
- AUTOHEAL_CONTAINER_LABEL=all # 监控所有容器
- AUTOHEAL_INTERVAL=10 # 每10秒检查一次
- AUTOHEAL_START_PERIOD=300 # 启动后5分钟才开始监控
volumes:
- /var/run/docker.sock:/var/run/docker.sock # 访问Docker API
工作流程:
┌─────────────────────────────────────────────────┐
│ 1. Archive容器geth进程卡死 │
│ ↓ │
│ 2. HealthCheck连续3次失败 (3分钟) │
│ ↓ │
│ 3. 容器状态变为 unhealthy │
│ ↓ │
│ 4. Autoheal检测到unhealthy状态 │
│ ↓ │
│ 5. 自动执行 docker restart bsc-archive-1 │
│ ↓ │
│ 6. 容器重启,geth进程恢复正常 │
└─────────────────────────────────────────────────┘
方案三:自定义监控脚本(推荐用于生产环境)
如果不想依赖第三方镜像,可以自己写监控脚本。
创建 ./scripts/container-monitor.sh:
#!/bin/bash
# 配置
CONTAINER_NAME="bsc-archive-1"
CHECK_INTERVAL=60
LOG_FILE="/data/chain/logs/monitor.log"
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 检查容器健康状态
check_container_health() {
local health=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null)
echo "$health"
}
# 检查geth进程是否存在
check_geth_process() {
docker exec "$CONTAINER_NAME" pgrep -x "geth" > /dev/null 2>&1
return $?
}
# 检查geth是否响应
check_geth_responsive() {
timeout 30 docker exec "$CONTAINER_NAME" \
geth attach --exec 'eth.blockNumber' /data/geth.ipc > /dev/null 2>&1
return $?
}
# 重启容器
restart_container() {
log "🔄 重启容器: $CONTAINER_NAME"
docker restart "$CONTAINER_NAME"
# 等待容器启动
sleep 30
# 验证重启是否成功
if docker ps | grep -q "$CONTAINER_NAME"; then
log "✅ 容器重启成功"
else
log "❌ 容器重启失败,请人工介入!"
# 可以在这里添加告警通知
fi
}
# 主监控循环
main() {
log "🚀 监控服务启动,监控容器: $CONTAINER_NAME"
while true; do
# 检查容器是否存在
if ! docker ps | grep -q "$CONTAINER_NAME"; then
log "⚠️ 容器不存在或已停止"
sleep "$CHECK_INTERVAL"
continue
fi
# 方法1:检查健康状态(如果配置了healthcheck)
health=$(check_container_health)
if [ "$health" == "unhealthy" ]; then
log "❌ 容器健康检查失败 (状态: unhealthy)"
restart_container
sleep 120 # 重启后等待2分钟再继续监控
continue
fi
# 方法2:检查geth进程
if ! check_geth_process; then
log "❌ geth进程不存在"
restart_container
sleep 120
continue
fi
# 方法3:检查geth响应性
if ! check_geth_responsive; then
log "❌ geth进程无响应"
restart_container
sleep 120
continue
fi
# 一切正常
log "✓ 容器运行正常 (health: $health)"
sleep "$CHECK_INTERVAL"
done
}
# 捕获退出信号
trap 'log "📛 监控服务停止"; exit 0' SIGTERM SIGINT
# 启动监控
main
使用systemd管理监控脚本:
创建 /etc/systemd/system/chain-monitor.service:
[Unit]
Description=BSC Container Health Monitor
After=docker.service
Requires=docker.service
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/data/chain
ExecStart=/bin/bash /data/chain/scripts/container-monitor.sh
Restart=always
RestartSec=10
# 日志配置
StandardOutput=append:/data/chain/logs/monitor-service.log
StandardError=append:/data/chain/logs/monitor-service.log
[Install]
WantedBy=multi-user.target
启用服务:
# 1. 创建脚本
sudo mkdir -p /data/chain/scripts
sudo vim /data/chain/scripts/container-monitor.sh
# 粘贴上面的脚本内容
# 2. 设置权限
sudo chmod +x /data/chain/scripts/container-monitor.sh
sudo chown ubuntu:ubuntu /data/chain/scripts/container-monitor.sh
# 3. 创建systemd服务
sudo vim /etc/systemd/system/chain-monitor.service
# 粘贴服务配置
# 4. 重载并启动
sudo systemctl daemon-reload
sudo systemctl enable chain-monitor
sudo systemctl start chain-monitor
# 5. 查看状态
sudo systemctl status chain-monitor
# 6. 查看日志
sudo journalctl -u chain-monitor -f
方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| HealthCheck | 简单,Docker原生支持 | 只标记状态,不会自动重启 | 配合其他方案使用 |
| Autoheal容器 | 配置简单,开箱即用 | 依赖第三方镜像,需要Docker socket权限 | 测试/开发环境 |
| 自定义脚本 | 完全可控,可自定义逻辑,无第三方依赖 | 需要额外维护脚本 | 生产环境(推荐) |
完整配置示例
docker-compose.yml(推荐配置)
version: '3.8'
services:
archive:
image: myorg/chain:0.0.4
container_name: bsc-archive-1
user: "1000:1000"
command:
- --datadir
- /data
- --http
- --http.addr
- "0.0.0.0"
- --http.port
- "8555"
- --http.api
- "eth,net,web3,debug,admin,txpool"
- --syncmode
- full
- --gcmode
- archive
- --cache
- "4000" # 降低缓存,避免OOM (原来是8000)
- --txlookuplimit
- "0"
- --ipcpath
- /data/geth.ipc
- --log.file=/data/logs/node.log
- --log.rotate
- --log.maxsize=100
- --log.maxbackups=10
- --verbosity=3 # 降低日志级别 (原来是5)
volumes:
- ./data/archive-data:/data
- ./logs/archive:/data/logs
- ./config:/chain/config
network_mode: "host"
restart: unless-stopped
stop_grace_period: 120s
# 健康检查配置
healthcheck:
test: ["CMD-SHELL", "geth attach --exec 'eth.blockNumber' /data/geth.ipc || exit 1"]
interval: 60s
timeout: 30s
retries: 3
start_period: 300s
# 资源限制(可选)
deploy:
resources:
limits:
memory: 10G
reservations:
memory: 6G
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "10"
# 验证节点配置类似...
validator1:
# ... 配置 ...
# 自动治愈容器
autoheal:
image: willfarrell/autoheal:latest
container_name: autoheal
restart: always
environment:
- AUTOHEAL_CONTAINER_LABEL=all
- AUTOHEAL_INTERVAL=10
- AUTOHEAL_START_PERIOD=300
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
验证和测试
1. 应用新配置
# 停止现有容器
sudo docker-compose down
# 启动新配置
sudo docker-compose up -d
# 查看容器状态
sudo docker ps
2. 查看健康状态
# 方法1:docker ps 会显示健康状态
$ sudo docker ps
CONTAINER ID STATUS NAMES
6f96594ef9a4 Up 5 minutes (healthy) osc-archive-1
# 方法2:查看详细健康检查信息
$ sudo docker inspect osc-archive-1 | grep -A 20 "Health"
3. 模拟故障测试
# 手动杀掉geth进程,测试自动恢复
sudo docker exec -it bsc-archive-1 pkill geth
# 观察容器状态变化
watch -n 5 'sudo docker ps | grep archive'
# 大约3-5分钟后,应该看到容器自动重启
4. 查看autoheal日志
# 实时查看autoheal的工作日志
sudo docker logs -f autoheal
# 应该能看到类似输出:
# [2026-01-09 15:30:45] Container bsc-archive-1 is unhealthy - Restarting
# [2026-01-09 15:30:50] Container bsc-archive-1 restarted successfully
额外优化建议
1. 降低资源配置
原配置可能导致内存不足:
--cache 8000 # 8GB → 改为 4000 (4GB)
--verbosity 5 # debug → 改为 3 (info)
2. 添加资源限制
防止单个容器耗尽系统资源:
deploy:
resources:
limits:
memory: 10G
cpus: '4'
3. 定期清理日志
# 添加到crontab
0 2 * * * find /data/osc/logs -name "*.log" -mtime +7 -delete
4. 监控告警(可选)
在监控脚本中添加告警通知:
# 发送邮件告警
send_alert() {
echo "Archive node restarted at $(date)" | \
mail -s "OSC Node Alert" admin@example.com
}
# 发送Telegram通知
send_telegram() {
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d chat_id="${CHAT_ID}" \
-d text="⚠️ OSC Archive容器已自动重启"
}
总结
问题本质
Docker容器的"僵尸状态"是由于:
- Docker只监控容器的init进程,不关心内部应用进程
- 当geth进程挂掉但容器init进程存活时,Docker认为一切正常
restart: always只在容器退出时生效,无法处理内部进程故障
解决关键
实现完整的自动恢复机制需要三个组件:
HealthCheck → 检测问题
↓
Autoheal/Script → 执行重启
↓
restart: always → 确保重启
最佳实践
生产环境推荐配置:
- ✅ 配置
healthcheck- 及时发现问题 - ✅ 使用自定义监控脚本 + systemd - 安全可控
- ✅ 降低资源配置 - 避免OOM
- ✅ 添加资源限制 - 防止资源耗尽
- ✅ 配置日志轮转 - 避免磁盘满
- ✅ 添加告警通知 - 及时了解故障
测试/开发环境:
- 可以使用 autoheal 容器,配置简单快速
预防措施
除了自动重启,还应该:
- 定期检查磁盘空间
- 监控内存使用情况
- 定期备份关键数据
- 查看日志中的warning信息
- 关注区块同步进度
通过这套完整的监控和自动恢复方案,可以大大提高BSC节点的稳定性和可用性。
相关资源:

浙公网安备 33010602011771号