GitHub Actions Pipeline故障排查与自动修复实践
从心跳告警触发到完整修复的端到端DevOps故障处理流程
- 心跳告警可提前5分钟检测Runner离线,避免被动响应
- 80%常见故障可自动修复,无需人工介入
- 磁盘满是最常见的Runner故障根因
- 幂等修复脚本是自动化的关键设计
- 监控脚本本身也需要监控,防止告警失效
问题背景
凌晨3点17分,我被钉钉告警吵醒。监控面板显示有3个 GitHub Actions Runner 已经连续5分钟没有发送心跳。作为团队唯一的 DevOps 负责人,我的第一反应是:又来了。
这不是我们第一次遇到这个问题。上个月,由于某个 Runner 突然离线,我们有 23 个 CI Pipeline 被卡在「等待 Runner」状态,直到运维人员手动重启服务。事后复盘,从故障发生到人工介入,平均耗时 47 分钟——这段时间里,开发者的 PR 无法合并,进度被严重阻塞。
我们的团队有 12 个 GitHub Actions Runner,每天处理约 350+ 次 Pipeline 执行,故障率约 2.5%。算下来,每天至少有 8-9 次 Pipeline 失败是由基础设施问题引起的,而非代码本身的问题。这个数字让我意识到:我们不能每次都靠人肉排查。
为什么难排查
我们一开始以为问题出在 GitHub 官方服务。根据监控数据,Runner 离线的时间点正好是 GitHub 状态页面显示「部分服务降级」的时段。直觉告诉我们:等 GitHub 恢复就好了。
但实际上,当我们查看那台离线 Runner 的日志时,发现问题完全在本地。错误信息是:
# /var/log/actions-runner/talker.log
[ERROR] JobRunner: Failed to update job runner status: No space left on device
磁盘满了。但为什么磁盘会满?进一步排查发现:GitHub Actions 的 Runner 会在本地缓存构建依赖,默认缓存上限是 10GB。某个项目的 npm install 产生了大量嵌套的 node_modules,在没有正确配置 .dockerignore 的情况下,Docker 构建产物也堆积在 Runner 的工作目录。
更深层的问题是:GitHub Actions Runner 的状态显示是「在线」的——它只是无法接收新任务而已。这意味着单纯依赖 GitHub 提供的 Runner 状态 API 是无法发现问题的。我们需要一个更细粒度的健康检查机制。
根因/核心设计决策
问题的本质是:Runner 的「在线状态」和「可用状态」是两个不同的概念。GitHub 认为 Runner 开机就算在线,但我们需要知道它是否能真正处理任务。
基于这个认知,我们设计了一套心跳监控 + 自动修复系统。核心逻辑如下:
#!/usr/bin/env python3
import requests
import time
import subprocess
import logging
from datetime import datetime, timedelta
class RunnerHealthMonitor:
def __init__(self, owner, repo, token):
self.api_base = "https://api.github.com"
self.headers = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json"
}
self.owner = owner
self.repo = repo
def check_runner_disk_space(self, runner_name):
"""检查 Runner 本地磁盘空间"""
# 通过在 Runner 上执行诊断命令
# 实际部署时通过 SSH 或其他远程执行方式
cmd = [
"ssh",
f"runner@{runner_name}",
"df -h / | tail -1 | awk '{print $5}'"
]
result = subprocess.run(cmd, capture_output=True, text=True)
usage_percent = int(result.stdout.strip().replace('%', ''))
return usage_percent
def check_runner_heartbeat(self, runner_name):
"""检查 Runner 是否在正常发送心跳"""
response = requests.get(
f"{self.api_base}/repos/{self.owner}/{self.repo}/actions/runners",
headers=self.headers
)
runners = response.json().get("runners", [])
for runner in runners:
if runner["name"] == runner_name:
# 检查最后活跃时间
last_activity = runner.get("last_activity_at")
if last_activity:
last_time = datetime.strptime(
last_activity, "%Y-%m-%dT%H:%M:%SZ"
)
return datetime.utcnow() - last_time
return None
def auto_recover(self, runner_name):
"""自动修复 Runner"""
修复策略 = [
("clean_disk", self.clean_disk),
("restart_service", self.restart_runner_service),
("drain_jobs", self.drain_stuck_jobs)
]
for action_name, action_func in 修复策略:
logging.info(f"执行修复: {action_name}")
if action_func(runner_name):
logging.info(f"修复成功: {action_name}")
return True
logging.warning(f"修复失败,尝试下一个策略: {action_name}")
return False
def clean_disk(self, runner_name):
"""清理磁盘空间"""
cmd = [
"ssh",
f"runner@{runner_name}",
"sudo docker system prune -af --volumes"
]
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode == 0
monitor = RunnerHealthMonitor(
owner="your-org",
repo="your-repo",
token="your-pat-token"
)
配合 GitHub Actions Workflow 的配置:
name: Runner Health Check
on:
schedule:
- cron: '*/5 * * * *' # 每5分钟检查一次
workflow_dispatch:
jobs:
health-check:
runs-on: self-hosted
steps:
- name: Run Health Check
env:
GITHUB_TOKEN: ${{ secrets.GH_MONITOR_TOKEN }}
run: |
python3 /opt/monitor/runner_health.py \
--action check \
--threshold-disk 85 \
--threshold-heartbeat 300
这套系统的关键决策是:监控脚本本身也部署在 Runner 上,形成自监控闭环。当监控脚本本身所在的 Runner 离线时,其他 Runner 上的监控任务会检测到并触发告警。
自动修复的价值不在于「完全替代人工」,而在于「在人工介入前保持服务可用」。80% 的常见故障(磁盘满、网络抖动、服务假死)都可以自动化处理,让人聚焦在真正复杂的根因分析上。
可移植的原则
- 如果你在管理有状态服务,为每种已知故障类型编写幂等的自动修复脚本。不要依赖手动 SSH 操作——人肉操作不可重现,且容易出错。
- 如果你在设计监控系统,将健康检查逻辑与服务自身功能分离。GitHub Actions Runner 的「在线」状态不等于「健康」状态,你需要定义自己的健康标准。
- 如果你在处理间歇性故障,先自动重试,再通知人工。我们统计过,42% 的 Pipeline 失败在重试一次后成功。但重试要有上限,防止无限循环。
- 如果你在构建分布式系统,监控脚本本身也需要被监控。自监控是防止单点故障的关键——我们的心跳检测每 5 分钟执行一次,脚本执行时间超过 3 分钟即触发「监控失效」告警。
结尾
这套心跳监控 + 自动修复系统上线后,我们把 Runner 相关的平均故障恢复时间(MTTR)从 47 分钟降到了 9 分钟。更重要的是,它把 DevOps 从「凌晨被叫醒处理磁盘满」的低价值工作中解放出来。
如果你也在使用自托管的 GitHub Actions Runner,建议先从心跳监控开始——不需要复杂的自动化修复,先做到「及时发现问题」。修复脚本可以逐步添加,先处理最常见的 2-3 种故障场景,效果就会很明显。
我们把监控脚本开源在 GitHub 上,有兴趣可以参考:github.com/your-org/actions-runner-monitor。里面包含了完整的健康检查逻辑和告警集成示例。