SiamCafe · Blog
Healthchecks.io กับ Scaling Strategy วิธี Scale
บทความ

Healthchecks.io กับ Scaling Strategy วิธี Scale

เผยแพร่ 28 พฤษภาคม 2569

Healthchecks.io คืออะไร

Healthchecks.io กับ Scaling Strategy วิธี Scale

Healthchecks.io เป็นบริการ Cron Job Monitoring ที่ช่วยตรวจสอบว่า Scheduled Tasks ทำงานตามเวลาหรือไม่ หลักการคือ Cron Job ส่ง HTTP GET/POST ไปยัง Healthchecks.io เมื่อทำงานเสร็จ (Ping) ถ้าไม่ได้รับ Ping ภายในเวลาที่กำหนดจะแจ้งเตือน

เมื่อใช้ร่วมกับ Scaling Strategy ช่วยตรวจจับว่า Jobs เริ่มช้าลง (Duration เพิ่ม) หรือ Timeout เป็นสัญญาณว่าต้อง Scale Resources เพิ่ม

Setup Healthchecks.io

# === Healthchecks.io Setup ===

# 1. สมัครที่ https://healthchecks.io (Free: 20 Checks)
# หรือ Self-hosted ด้วย Docker

# === Self-hosted ด้วย Docker Compose ===
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  healthchecks:
    image: healthchecks/healthchecks:latest
    ports:
      - "8000:8000"
    environment:
      - DB=postgres
      - DB_HOST=postgres
      - DB_NAME=healthchecks
      - DB_USER=hc
      - DB_PASSWORD=SecurePass123
      - SECRET_KEY=your-secret-key-here
      - ALLOWED_HOSTS=*
      - SITE_ROOT=https://hc.example.com
      - DEFAULT_FROM_EMAIL=alerts@example.com
      - EMAIL_HOST=smtp.gmail.com
      - EMAIL_PORT=587
      - EMAIL_USE_TLS=True
      - EMAIL_HOST_USER=your@gmail.com
      - EMAIL_HOST_PASSWORD=app-password
      - SLACK_CLIENT_ID=xxx
      - SLACK_CLIENT_SECRET=xxx
    depends_on:
      - postgres
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      - POSTGRES_DB=healthchecks
      - POSTGRES_USER=hc
      - POSTGRES_PASSWORD=SecurePass123
    volumes:
      - pg-data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  pg-data:
EOF

docker compose up -d

# 2. Cron Job Integration
# แต่ละ Check มี UUID เฉพาะ เช่น:
# https://hc-ping.com/abc123-def456

# === Bash — Ping เมื่อ Job เสร็จ ===
# Backup Script
#!/bin/bash
set -e
CHECK_URL="https://hc-ping.com/abc123-def456"

# แจ้งเริ่มต้น
curl -fsS -m 10 --retry 5 "$CHECK_URL/start" > /dev/null

# ทำงาน
pg_dump mydb | gzip > /backups/mydb_$(date +%Y%m%d).sql.gz

# แจ้งเสร็จ (Ping)
curl -fsS -m 10 --retry 5 "$CHECK_URL" > /dev/null

# ถ้า Error — แจ้ง Fail
# curl -fsS -m 10 --retry 5 "$CHECK_URL/fail" > /dev/null

# 3. Crontab
# 0 2 * * * /opt/scripts/backup.sh 2>&1 | curl -fsS -m 10 \
#   --retry 5 --data-binary @- https://hc-ping.com/abc123-def456/log

# 4. Python Integration
# import requests
# CHECK_URL = "https://hc-ping.com/abc123-def456"
# requests.get(f"{CHECK_URL}/start")
# # ... do work ...
# requests.get(CHECK_URL)  # Success
# # requests.get(f"{CHECK_URL}/fail")  # Failure

echo "Healthchecks.io setup complete"

Scaling Strategy Script

Healthchecks.io กับ Scaling Strategy วิธี Scale
# scaling_strategy.py — Auto-scaling ตาม Healthchecks.io Metrics
import requests
import json
import subprocess
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import List, Dict, Optional

class HealthchecksClient:
    """Healthchecks.io API Client"""

    def __init__(self, api_key, base_url="https://healthchecks.io"):
        self.base_url = base_url
        self.headers = {"X-Api-Key": api_key}

    def list_checks(self):
        """ดู Checks ทั้งหมด"""
        resp = requests.get(f"{self.base_url}/api/v3/checks/",
                           headers=self.headers)
        return resp.json().get("checks", [])

    def get_check(self, uuid):
        """ดู Check เฉพาะ"""
        resp = requests.get(f"{self.base_url}/api/v3/checks/{uuid}",
                           headers=self.headers)
        return resp.json()

    def get_pings(self, uuid, limit=50):
        """ดู Ping History"""
        resp = requests.get(
            f"{self.base_url}/api/v3/checks/{uuid}/pings/",
            headers=self.headers,
        )
        return resp.json().get("pings", [])[:limit]

    def get_flips(self, uuid):
        """ดู Status Changes"""
        resp = requests.get(
            f"{self.base_url}/api/v3/checks/{uuid}/flips/",
            headers=self.headers,
        )
        return resp.json().get("flips", [])

class ScalingAdvisor:
    """วิเคราะห์ Metrics และแนะนำ Scaling"""

    def __init__(self, hc_client: HealthchecksClient):
        self.hc = hc_client

    def analyze_job_durations(self, uuid):
        """วิเคราะห์ Duration ของ Job"""
        pings = self.hc.get_pings(uuid)

        durations = []
        start_time = None

        for ping in reversed(pings):
            if ping.get("type") == "start":
                start_time = ping.get("date")
            elif ping.get("type") == "success" and start_time:
                start = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
                end = datetime.fromisoformat(ping["date"].replace("Z", "+00:00"))
                duration = (end - start).total_seconds()
                durations.append(duration)
                start_time = None

        if not durations:
            return None

        avg = sum(durations) / len(durations)
        latest = durations[-1] if durations else 0
        trend = "increasing" if len(durations) >= 3 and \
                durations[-1] > durations[-3] * 1.2 else "stable"

        return {
            "avg_seconds": round(avg),
            "latest_seconds": round(latest),
            "min_seconds": round(min(durations)),
            "max_seconds": round(max(durations)),
            "samples": len(durations),
            "trend": trend,
        }

    def scaling_recommendation(self, checks_analysis):
        """สร้างคำแนะนำ Scaling"""
        recommendations = []

        for name, analysis in checks_analysis.items():
            if not analysis:
                continue

            if analysis["trend"] == "increasing":
                recommendations.append({
                    "job": name,
                    "action": "SCALE UP",
                    "reason": f"Duration increasing: "
                              f"{analysis['latest_seconds']}s "
                              f"(avg: {analysis['avg_seconds']}s)",
                    "priority": "high",
                })
            elif analysis["latest_seconds"] > analysis["avg_seconds"] * 2:
                recommendations.append({
                    "job": name,
                    "action": "INVESTIGATE",
                    "reason": f"Latest run 2x slower than average",
                    "priority": "medium",
                })

        return recommendations

    def print_report(self, checks):
        """แสดง Scaling Report"""
        print(f"\n{'='*55}")
        print(f"Scaling Analysis Report — {datetime.now():%Y-%m-%d %H:%M}")
        print(f"{'='*55}")

        analyses = {}
        for check in checks:
            name = check.get("name", "Unknown")
            uuid = check.get("ping_url", "").split("/")[-1]

            if uuid:
                analysis = self.analyze_job_durations(uuid)
                analyses[name] = analysis

                if analysis:
                    trend_icon = "UP" if analysis["trend"] == "increasing" else "OK"
                    print(f"\n  [{trend_icon}] {name}")
                    print(f"    Latest: {analysis['latest_seconds']}s | "
                          f"Avg: {analysis['avg_seconds']}s | "
                          f"Max: {analysis['max_seconds']}s")

        recs = self.scaling_recommendation(analyses)
        if recs:
            print(f"\n  Recommendations:")
            for r in recs:
                print(f"    [{r['priority'].upper()}] {r['job']}: "
                      f"{r['action']} — {r['reason']}")

# hc = HealthchecksClient("your-api-key")
# advisor = ScalingAdvisor(hc)
# checks = hc.list_checks()
# advisor.print_report(checks)

Kubernetes HPA Integration

# === Kubernetes Auto-scaling ตาม Job Metrics ===
# hpa-config.yaml

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: worker-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: worker
  minReplicas: 2
  maxReplicas: 20
  metrics:
    # Scale ตาม CPU
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
    # Scale ตาม Queue Length (Custom Metric)
    - type: Pods
      pods:
        metric:
          name: job_queue_length
        target:
          type: AverageValue
          averageValue: "10"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Percent
          value: 50
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 25
          periodSeconds: 120
---
# === CronJob ที่ส่ง Ping ไป Healthchecks.io ===
apiVersion: batch/v1
kind: CronJob
metadata:
  name: data-sync
  namespace: production
spec:
  schedule: "*/30 * * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: sync
              image: myapp/data-sync:latest
              command:
                - /bin/sh
                - -c
                - |
                  curl -fsS -m 10 "$HC_PING_URL/start"
                  python sync.py
                  EXIT_CODE=$?
                  if [ $EXIT_CODE -eq 0 ]; then
                    curl -fsS -m 10 "$HC_PING_URL"
                  else
                    curl -fsS -m 10 "$HC_PING_URL/fail"
                  fi
              env:
                - name: HC_PING_URL
                  valueFrom:
                    secretKeyRef:
                      name: healthchecks
                      key: data-sync-url
          restartPolicy: OnFailure

Best Practices

  • Start/Success Pattern: ใช้ /start Ping ก่อนเริ่ม และ Ping เมื่อเสร็จ เพื่อวัด Duration
  • Grace Period: ตั้ง Grace Period ให้มากกว่า Max Duration ของ Job ป้องกัน False Alert
  • Log Output: ส่ง Log Output ไปกับ Ping ด้วย (POST body) สำหรับ Debug
  • Fail Ping: ส่ง /fail เมื่อ Job Error ไม่ใช่แค่รอ Timeout
  • Scale Gradually: Scale Up เร็ว Scale Down ช้า ป้องกัน Thrashing
  • Monitor Duration Trends: ติดตาม Duration ถ้าเพิ่มขึ้นต่อเนื่อง ต้อง Scale

Healthchecks.io คืออะไร

บริการ Cron Job Monitoring ตรวจสอบ Scheduled Tasks ทำงานตามเวลาหรือไม่ Cron Job ส่ง HTTP Ping เมื่อเสร็จ ถ้าไม่ได้รับ Ping แจ้งเตือน Email Slack PagerDuty Free Plan 20 Checks