Technology

Betteruptime GitOps Workflow

betteruptime gitops workflow
Betteruptime GitOps Workflow | SiamCafe Blog
2026-05-15· อ. บอม — SiamCafe.net· 8,111 คำ

GitOps สำหรับ Monitoring

GitOps เป็นแนวทางที่ใช้ Git เป็น Single Source of Truth สำหรับทุกอย่างในระบบ รวมถึง Monitoring Configuration เมื่อรวม BetterUptime กับ GitOps ได้ระบบ Monitoring ที่จัดการผ่าน Code ทุกการเปลี่ยนแปลงผ่าน PR มี Review มี Audit Trail

ข้อดีคือ Version Control ย้อนกลับได้ Consistency ทุก Environment เหมือนกัน Automation ไม่ต้อง Manual Setup และ Review Process ป้องกันผิดพลาด

BetterUptime Configuration as Code

# === BetterUptime GitOps Configuration ===
# monitoring/config.yaml — Monitoring Configuration in Git

monitors:
  - name: "Production API"
    url: "https://api.example.com/health"
    monitor_type: "status"
    check_frequency: 30
    request_timeout: 15
    confirmation_period: 3
    regions: ["us", "eu", "as"]
    expected_status_codes: [200]
    alert_channels: ["slack-ops", "pagerduty-oncall"]

  - name: "Production Frontend"
    url: "https://www.example.com"
    monitor_type: "status"
    check_frequency: 60
    request_timeout: 30
    regions: ["us", "eu", "as", "au"]
    alert_channels: ["slack-ops"]

  - name: "Database Health"
    url: "https://api.example.com/db-health"
    monitor_type: "status"
    check_frequency: 60
    request_timeout: 10
    alert_channels: ["slack-ops", "pagerduty-oncall"]

  - name: "Staging API"
    url: "https://staging-api.example.com/health"
    monitor_type: "status"
    check_frequency: 120
    alert_channels: ["slack-dev"]

  - name: "SSL Certificate"
    url: "https://www.example.com"
    monitor_type: "ssl"
    check_frequency: 86400
    alert_channels: ["slack-ops"]

heartbeats:
  - name: "Backup Job"
    period: 86400
    grace: 3600
    alert_channels: ["slack-ops"]

  - name: "Data Sync"
    period: 3600
    grace: 600
    alert_channels: ["slack-ops"]

status_pages:
  - name: "Example Status"
    subdomain: "status-example"
    custom_domain: "status.example.com"
    sections:
      - name: "Core Services"
        monitors: ["Production API", "Production Frontend"]
      - name: "Infrastructure"
        monitors: ["Database Health"]

alert_channels:
  slack-ops:
    type: "slack"
    webhook: ""
  slack-dev:
    type: "slack"
    webhook: ""
  pagerduty-oncall:
    type: "pagerduty"
    key: ""

Python — GitOps Sync Script

# gitops_sync.py — Sync BetterUptime Config จาก Git
# pip install requests pyyaml

import yaml
import requests
import os
import json
import sys
from pathlib import Path

class BetterUptimeGitOps:
    """Sync BetterUptime Configuration จาก YAML Config"""

    BASE_URL = "https://betteruptime.com/api/v2"

    def __init__(self, api_token):
        self.session = requests.Session()
        self.session.headers = {
            "Authorization": f"Bearer {api_token}",
            "Content-Type": "application/json",
        }

    def load_config(self, config_path):
        """โหลด Config จาก YAML"""
        with open(config_path) as f:
            raw = f.read()

        # Replace environment variables
        for key, value in os.environ.items():
            raw = raw.replace(f"}}", value)

        return yaml.safe_load(raw)

    def get_existing_monitors(self):
        """ดึง Monitors ที่มีอยู่"""
        resp = self.session.get(f"{self.BASE_URL}/monitors")
        monitors = resp.json().get("data", [])
        return {m["attributes"]["pronounceable_name"]: m for m in monitors}

    def sync_monitors(self, config):
        """Sync Monitors ตาม Config"""
        existing = self.get_existing_monitors()
        desired = {m["name"]: m for m in config.get("monitors", [])}

        created, updated, deleted = 0, 0, 0

        # Create or Update
        for name, spec in desired.items():
            payload = {
                "pronounceable_name": name,
                "url": spec["url"],
                "monitor_type": spec.get("monitor_type", "status"),
                "check_frequency": spec.get("check_frequency", 60),
                "request_timeout": spec.get("request_timeout", 15),
                "confirmation_period": spec.get("confirmation_period", 3),
                "regions": ",".join(spec.get("regions", ["us", "eu"])),
            }

            if name in existing:
                # Update
                monitor_id = existing[name]["id"]
                resp = self.session.patch(
                    f"{self.BASE_URL}/monitors/{monitor_id}",
                    json=payload,
                )
                if resp.status_code == 200:
                    updated += 1
                    print(f"  Updated: {name}")
            else:
                # Create
                resp = self.session.post(
                    f"{self.BASE_URL}/monitors",
                    json=payload,
                )
                if resp.status_code in [200, 201]:
                    created += 1
                    print(f"  Created: {name}")

        # Delete monitors not in config
        for name, monitor in existing.items():
            if name not in desired:
                resp = self.session.delete(
                    f"{self.BASE_URL}/monitors/{monitor['id']}"
                )
                if resp.status_code in [200, 204]:
                    deleted += 1
                    print(f"  Deleted: {name}")

        return {"created": created, "updated": updated, "deleted": deleted}

    def sync_heartbeats(self, config):
        """Sync Heartbeats"""
        for hb in config.get("heartbeats", []):
            payload = {
                "name": hb["name"],
                "period": hb.get("period", 3600),
                "grace": hb.get("grace", 300),
            }
            resp = self.session.post(f"{self.BASE_URL}/heartbeats", json=payload)
            status = "created" if resp.status_code in [200, 201] else "exists"
            print(f"  Heartbeat {hb['name']}: {status}")

    def plan(self, config_path):
        """Plan — แสดงการเปลี่ยนแปลงที่จะเกิดขึ้น (Dry Run)"""
        config = self.load_config(config_path)
        existing = self.get_existing_monitors()
        desired = {m["name"]: m for m in config.get("monitors", [])}

        print("\n=== GitOps Plan ===")
        for name in desired:
            if name in existing:
                print(f"  ~ Update: {name}")
            else:
                print(f"  + Create: {name}")

        for name in existing:
            if name not in desired:
                print(f"  - Delete: {name}")

    def apply(self, config_path):
        """Apply — Sync Configuration"""
        config = self.load_config(config_path)

        print("\n=== GitOps Apply ===")
        print("\nMonitors:")
        result = self.sync_monitors(config)
        print(f"\n  Summary: {result['created']} created, "
              f"{result['updated']} updated, {result['deleted']} deleted")

        print("\nHeartbeats:")
        self.sync_heartbeats(config)

        print("\n=== Apply Complete ===")

# Usage:
# gitops = BetterUptimeGitOps(os.environ["BETTERUPTIME_TOKEN"])
# gitops.plan("monitoring/config.yaml")
# gitops.apply("monitoring/config.yaml")

CI/CD Pipeline สำหรับ GitOps Monitoring

# === GitHub Actions — GitOps Monitoring Pipeline ===
# .github/workflows/monitoring-gitops.yml

name: Monitoring GitOps
on:
  push:
    branches: [main]
    paths: ["monitoring/**"]
  pull_request:
    branches: [main]
    paths: ["monitoring/**"]

env:
  BETTERUPTIME_TOKEN: }
  SLACK_OPS_WEBHOOK: }
  SLACK_DEV_WEBHOOK: }
  PAGERDUTY_KEY: }

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install Dependencies
        run: pip install pyyaml jsonschema

      - name: Validate Config
        run: |
          python -c "
          import yaml, sys
          with open('monitoring/config.yaml') as f:
              config = yaml.safe_load(f.read())
          monitors = config.get('monitors', [])
          print(f'Monitors: {len(monitors)}')
          for m in monitors:
              assert 'name' in m, f'Missing name in monitor'
              assert 'url' in m, f'Missing url in {m[\"name\"]}'
              print(f'  OK: {m[\"name\"]}')
          print('Validation passed!')
          "

  plan:
    runs-on: ubuntu-latest
    needs: validate
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install requests pyyaml

      - name: Plan Changes
        run: python scripts/gitops_sync.py plan monitoring/config.yaml

      - name: Comment PR
        uses: actions/github-script@v7
        with:
          script: |
            const output = `### Monitoring GitOps Plan
            Changes detected in monitoring configuration.
            Review the plan output above before approving.`;
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            });

  apply:
    runs-on: ubuntu-latest
    needs: validate
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install requests pyyaml

      - name: Apply Changes
        run: python scripts/gitops_sync.py apply monitoring/config.yaml

      - name: Notify Slack
        if: success()
        run: |
          curl -X POST $SLACK_OPS_WEBHOOK \
            -H 'Content-Type: application/json' \
            -d '{"text":"Monitoring config updated via GitOps"}'

# === ArgoCD Application สำหรับ Monitoring Stack ===
# argocd/monitoring-app.yaml
# apiVersion: argoproj.io/v1alpha1
# kind: Application
# metadata:
#   name: monitoring-stack
#   namespace: argocd
# spec:
#   project: default
#   source:
#     repoURL: https://github.com/myorg/monitoring-config
#     path: k8s/monitoring
#     targetRevision: main
#   destination:
#     server: https://kubernetes.default.svc
#     namespace: monitoring
#   syncPolicy:
#     automated:
#       prune: true
#       selfHeal: true
#     syncOptions:
#       - CreateNamespace=true

Best Practices

GitOps คืออะไร

แนวทางจัดการ Infrastructure โดยใช้ Git เป็น Single Source of Truth ทุกการเปลี่ยนแปลงผ่าน PR มี Review ใช้ ArgoCD Flux CD Reconcile สถานะจริงให้ตรงกับ Git อัตโนมัติ

BetterUptime ใช้กับ GitOps อย่างไร

ใช้ BetterUptime API จัดการ Monitors Alerts Status Pages ผ่าน Code เก็บ Config ใน Git ทุกการเปลี่ยนแปลงผ่าน PR CI/CD Pipeline Apply Config อัตโนมัติ Monitoring เป็น Infrastructure as Code

ข้อดีของ GitOps สำหรับ Monitoring คืออะไร

Version Control ย้อนกลับได้ Audit Trail ดูว่าใครเปลี่ยนอะไร Consistency ทุก Environment เหมือนกัน Automation ไม่ต้อง Manual Review Process ป้องกันผิดพลาด

ArgoCD คืออะไร

GitOps Controller สำหรับ Kubernetes ดึง Config จาก Git Sync กับ Cluster อัตโนมัติ มี Web UI รองรับ Helm Kustomize Plain YAML ตรวจจับ Drift ระหว่าง Git กับ Cluster

สรุป

BetterUptime ร่วมกับ GitOps ทำให้ Monitoring Configuration เป็น Code เก็บใน Git ทุกการเปลี่ยนแปลงผ่าน PR มี Review CI/CD Pipeline Plan และ Apply อัตโนมัติ มี Version Control ย้อนกลับได้ Audit Trail Consistency ทุก Environment ใช้ ArgoCD สำหรับ Kubernetes Monitoring Stack Drift Detection ตรวจสอบว่า Config จริงตรงกับ Git

📖 บทความที่เกี่ยวข้อง

PostgreSQL Full Text Search GitOps Workflowอ่านบทความ → WordPress Headless GitOps Workflowอ่านบทความ → Apache Arrow GitOps Workflowอ่านบทความ → Betteruptime MLOps Workflowอ่านบทความ → TensorRT Optimization GitOps Workflowอ่านบทความ →

📚 ดูบทความทั้งหมด →