Hướng dẫn giám sát Self-hosted GitHub Actions Runner với Prometheus và Grafana: Theo dõi Runner Health, Job Queue và Build Duration

Monitoring tutorial - IT technology blog
Monitoring tutorial - IT technology blog

1. Vấn đề thực tế khi vận hành Self-hosted GitHub Actions Runner

Khi quy mô team chạm mốc 20–30 developer, chi phí cho GitHub-hosted runner tăng rất nhanh. Chuyển sang cụm Self-hosted Runner trên AWS EC2, VPS hay Kubernetes là lựa chọn hợp lý để tối ưu ngân sách. Thế nhưng, tự quản lý hạ tầng cũng kéo theo hàng tá rắc rối trong khâu vận hành:

  • Runner âm thầm biến mất hoặc bị treo: Docker build cache phình to làm đầy ổ cứng (100% disk), process Runner.Listener bị crash do OOM (Out Of Memory). Hậu quả là pipeline đứng im vô thời hạn mà không hề báo lỗi rõ ràng.
  • Hàng đợi (Job Queue) nghẽn kéo dài: Dev phàn nàn vì một commit fix typo nhỏ cũng phải đợi 15–20 phút mới bắt đầu chạy. Trong khi đó, DevOps team không rõ hạ tầng đang thiếu máy hay có ai đó trigger matrix build 50 job cùng lúc chiếm trọn worker pool.
  • Thời gian build tăng bất thường: Pipeline từ 5 phút bỗng vọt lên 18 phút. Thiếu số liệu lịch sử khiến bạn không biết bước nào chậm: do mạng pull base image Docker bị bóp băng thông, khâu compile code ngốn CPU hay do test suite phình to.

Trước đây khi chưa có monitoring, mỗi lần dev réo “CI hỏng rồi anh ơi”, mình lại phải SSH vào từng server gõ htop hay df -h để mò mẫm. Giám sát tập trung giải quyết triệt để vấn đề này: bạn nhìn thấy sự cố trên dashboard trước cả khi team kịp mở Slack réo tên.

2. Phân tích nguyên nhân gốc rễ

Tình trạng mất kiểm soát này thường đến từ hai lỗ hổng thông tin lớn:

1. Giao diện GitHub che giấu toàn bộ thông tin phần cứng

Mục Runner Settings trên GitHub UI chỉ hiện đúng 3 trạng thái: Idle, Active hoặc Offline. Bạn không thể biết một máy đang Active có bị thắt nút cổ chai ở 99% CPU hay không, IOPS ổ cứng có đang quá tải vì cache đè lên nhau không, hay vì sao nó mất tận 10 phút chỉ để giải nén artifacts.

2. Thiếu metrics dạng time-series và cảnh báo chủ động

GitHub không lưu trữ metrics hiệu năng theo dạng chuỗi thời gian. Đến khung giờ release cao điểm (thường là 16h–17h thứ Sáu), runner thiếu hụt nghiêm trọng. Lúc này, bạn không có dữ liệu định lượng nào trong tay: hàng đợi trung bình bao nhiêu job, thời gian chờ (queue latency) dài bao lâu, hay tỷ lệ build fail phân bổ theo từng runner pool ra sao.

3. So sánh các cách giải quyết phổ biến

Các DevOps engineer thường cân nhắc 3 hướng tiếp cận:

  • Viết script cronjob gọi GitHub REST API gửi Slack/Telegram: Triển khai nhanh trong 30 phút. Nhược điểm lớn: dễ chạm giới hạn GitHub API rate limit (5.000 req/giờ), không có biểu đồ trực quan và không lưu được xu hướng dài hạn.
  • Đẩy toàn bộ log runner về ELK/Loki: Rất tốt để tra cứu stack trace chi tiết. Tuy nhiên, phân tích log để tính toán queue time và utilization theo thời gian thực vừa tốn tài nguyên vừa cồng kềnh.
  • Giám sát chuẩn đa tầng với Prometheus + Exporter + Grafana: Thu thập song song cả metric hạ tầng (Node Exporter) lẫn metric workflow/queue (github-actions-exporter). Đây là mô hình chuẩn production, nhẹ và khả năng cảnh báo cực kỳ linh hoạt.

4. Hướng dẫn triển khai: Giám sát toàn diện với Prometheus và github-actions-exporter

Chúng ta sẽ dựng một stack gọn gàng gồm github-actions-exporter (lấy dữ liệu runner và job từ GitHub), Node Exporter (theo dõi CPU, RAM, Disk của máy runner), Prometheus (lưu trữ metric) và Grafana (hiển thị dashboard).

Bước 1: Tạo GitHub Personal Access Token (PAT)

Vào GitHub > Settings > Developer settings > Personal access tokens. Chọn token dạng Classic hoặc Fine-grained và cấp quyền đọc:

  • repo: Giám sát runner ở phạm vi Repository đơn lẻ.
  • admin:org hoặc read:org: Giám sát toàn bộ runner pool trong cấp Organization.

Bước 2: Triển khai stack monitoring bằng Docker Compose

Tạo file docker-compose.yml trên server giám sát hoặc chính cụm runner:

version: '3.8'

services:
  github-actions-exporter:
    image: cmauto/github-actions-exporter:latest
    container_name: github-actions-exporter
    restart: unless-stopped
    environment:
      - GITHUB_TOKEN=ghp_yourPersonalAccessTokenHere123456
      - GITHUB_ORGANIZATION=your-org-name
      - REFRESH_INTERVAL=30s
    ports:
      - "9999:9999"

  node-exporter:
    image: prom/node-exporter:v1.8.1
    container_name: runner-node-exporter
    restart: unless-stopped
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.rootfs=/rootfs'
      - '--path.sysfs=/host/sys'
    ports:
      - "9100:9100"

  prometheus:
    image: prom/prometheus:v2.53.0
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"

volumes:
  prometheus_data:

Bước 3: Cấu hình Prometheus Scrape Targets

Tạo file cấu hình prometheus.yml ngay cạnh file Compose:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'github_actions'
    static_configs:
      - targets: ['github-actions-exporter:9999']

  - job_name: 'runner_nodes'
    static_configs:
      - targets: ['node-exporter:9100']

Khởi chạy cụm dịch vụ:

docker compose up -d

# Kiểm tra exporter đã kết nối thành công với GitHub API hay chưa
docker logs -f github-actions-exporter

Bước 4: PromQL queries thực chiến để dựng Grafana Dashboard

Sau khi add Prometheus làm Data Source trên Grafana, bạn có thể tạo ngay các panel cốt lõi sau:

1. Theo dõi trạng thái máy Runner (Health Check)

Phát hiện tức thì runner nào rớt mạng hoặc crash:

# Số lượng runner đang online theo từng pool
sum by (os, status) (github_runner_status{status="online"})

# Cảnh báo runner bị offline (giá trị = 1 nghĩa là mất kết nối)
github_runner_status{status="offline"} == 1

2. Đo độ nghẽn hàng đợi (Job Queue Latency)

Biết chính xác lượng job đang xếp hàng chờ tài nguyên để kích hoạt autoscaling kịp thời:

# Tổng số job đang ở trạng thái Queued theo từng repo
sum(github_workflow_job_status_total{status="queued"}) by (repo)

# Tỷ lệ job in_progress so với queued
sum(github_workflow_job_status_total{status="in_progress"}) / 
sum(github_workflow_job_status_total{status="queued"})

3. Giám sát thời lượng build (Build Duration)

Đo thời gian chạy trung bình của workflow theo từng repository nhằm tối ưu hóa build cache:

# Thời gian chạy trung bình (giây) của job trong 30 phút gần nhất
rate(github_workflow_job_duration_seconds_sum[30m]) / rate(github_workflow_job_duration_seconds_count[30m])

Bước 5: Thiết lập Alert Rules bắn thông báo tức thì

Thêm Alert Rule vào Prometheus để nhận thông báo qua Slack/Telegram ngay khi có sự cố:

groups:
  - name: github_runner_alerts
    rules:
      - alert: RunnerOffline
        expr: github_runner_status{status="offline"} == 1
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "Runner {{ $labels.name }} đang bị Offline"
          description: "Runner {{ $labels.name }} trong pool {{ $labels.runner_group }} mất kết nối quá 3 phút."

      - alert: HighJobQueueCount
        expr: sum(github_workflow_job_status_total{status="queued"}) > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Hàng đợi CI/CD đang bị nghẽn"
          description: "Hiện có hơn 10 jobs đang chờ runner trống trong hơn 5 phút. Cần xem xét scale thêm worker."

Chỉ với vài chục phút thiết lập, bạn đã chuyển từ thế bị động “chữa cháy” sang chủ động hoàn toàn. Mọi nút thắt cổ chai về tài nguyên, thời gian chờ job và tình trạng sức khỏe của runner pool giờ đây đều nằm gọn trong tầm mắt.

Share: