Prometheus Federation ile Çok Bölgeli İzleme Mimarisi

Büyük ölçekli altyapı yönetiyorsanız, tek bir Prometheus instance’ının tüm dünyaya dağılmış servislerinizi izlemesi hem pratik hem de güvenilir değil. İstanbul’daki veri merkezinizden Frankfurt’taki Kubernetes cluster’ınıza metric çekmeye çalışmak, network gecikmeleri ve bant genişliği sorunları yüzünden kısa sürede kabus haline gelir. İşte tam bu noktada Prometheus Federation devreye giriyor.

Federation mimarisi, temelde bir hiyerarşi oluşturuyor: Her bölgede kendi yerel Prometheus’ları çalışıyor, bunların üzerinde de tüm bölgeleri konsolide eden bir “global” Prometheus oturuyor. Bu yazıda gerçek dünya senaryoları üzerinden bu mimarinin nasıl kurulacağını, yaygın tuzaklardan nasıl kaçınılacağını ve Grafana ile nasıl entegre edileceğini anlatacağım.

Federation Mimarisi Nasıl Çalışır?

Prometheus Federation’ı anlamak için önce standart Prometheus’un nasıl çalıştığını hatırlayalım. Normal senaryoda Prometheus, tanımladığınız hedeflerden /metrics endpoint’ini scrape ediyor. Federation’da ise bir Prometheus, başka bir Prometheus’un özel endpoint’i olan /federate adresini scrape ediyor.

Bu mimaride iki katman var:

  • Leaf (Yaprak) Prometheus’lar: Her bölgede çalışan, yerel servisleri izleyen instance’lar. Yüksek scrape sıklığıyla çalışır, detaylı metric tutar.
  • Global Prometheus: Leaf’lerden sadece belirli metric’leri toplar, daha uzun aralıklarla çalışır, cross-region sorgular için kullanılır.

Senaryo olarak üç bölgeli bir yapı düşünelim: tr-istanbul, de-frankfurt, us-virginia. Her bölgede uygulama sunucuları, veritabanları ve Kubernetes cluster’ları var. Ops ekibi tek bir Grafana dashboard’undan her şeyi görmek istiyor.

Leaf Prometheus Kurulumu

Her bölge için ayrı bir Prometheus instance’ı kuruyoruz. Bunu Docker Compose ile yapalım, production’da Kubernetes kullanıyor olsanız bile mantık aynı.

# /opt/prometheus/tr-istanbul/docker-compose.yml

version: '3.8'
services:
  prometheus:
    image: prom/prometheus:v2.48.0
    container_name: prometheus-istanbul
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - ./rules/:/etc/prometheus/rules/
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
      - '--web.enable-lifecycle'
      - '--web.listen-address=0.0.0.0:9090'
      - '--web.external-url=http://prometheus-istanbul.internal:9090'
    ports:
      - "9090:9090"
    restart: unless-stopped

volumes:
  prometheus_data:

Şimdi İstanbul leaf Prometheus konfigürasyonunu yazalım:

# /opt/prometheus/tr-istanbul/prometheus.yml

global:
  scrape_interval: 15s
  evaluation_interval: 15s
  external_labels:
    region: 'tr-istanbul'
    environment: 'production'

rule_files:
  - '/etc/prometheus/rules/*.yml'

scrape_configs:
  - job_name: 'node-exporter'
    static_configs:
      - targets:
          - 'web-01.istanbul.internal:9100'
          - 'web-02.istanbul.internal:9100'
          - 'db-01.istanbul.internal:9100'
          - 'db-02.istanbul.internal:9100'
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
      - target_label: datacenter
        replacement: 'IST-DC1'

  - job_name: 'postgres-exporter'
    static_configs:
      - targets:
          - 'db-01.istanbul.internal:9187'
          - 'db-02.istanbul.internal:9187'
    relabel_configs:
      - target_label: region
        replacement: 'tr-istanbul'

  - job_name: 'nginx-exporter'
    static_configs:
      - targets:
          - 'web-01.istanbul.internal:9113'
          - 'web-02.istanbul.internal:9113'

external_labels kısmı kritik. Global Prometheus metric’leri topladığında hangi bölgeden geldiğini bu label’lardan anlayacak. Bunu atlamak, sonradan büyük sorunlara yol açıyor.

Aynı yapıyı Frankfurt ve Virginia için de kuruyorsunuz, sadece external_labels içindeki region değeri değişiyor.

Global Prometheus Kurulumu

Global Prometheus genellikle merkezi bir konumda, Ops ekibinin ağına yakın bir yerde çalışır. Bu instance leaf’leri scrape eder.

# /opt/prometheus/global/prometheus.yml

global:
  scrape_interval: 60s
  evaluation_interval: 30s
  external_labels:
    monitor: 'global-prometheus'
    tier: 'aggregation'

scrape_configs:
  - job_name: 'federate-istanbul'
    scrape_interval: 60s
    honor_labels: true
    metrics_path: '/federate'
    params:
      match[]:
        - '{job="node-exporter"}'
        - '{job="postgres-exporter"}'
        - 'up'
        - 'scrape_duration_seconds'
        - '{__name__=~"process_.*"}'
    static_configs:
      - targets:
          - 'prometheus-istanbul.internal:9090'
        labels:
          source_region: 'tr-istanbul'

  - job_name: 'federate-frankfurt'
    scrape_interval: 60s
    honor_labels: true
    metrics_path: '/federate'
    params:
      match[]:
        - '{job="node-exporter"}'
        - '{job="postgres-exporter"}'
        - 'up'
        - 'scrape_duration_seconds'
    static_configs:
      - targets:
          - 'prometheus-frankfurt.internal:9090'
        labels:
          source_region: 'de-frankfurt'

  - job_name: 'federate-virginia'
    scrape_interval: 60s
    honor_labels: true
    metrics_path: '/federate'
    params:
      match[]:
        - '{job="node-exporter"}'
        - '{job="postgres-exporter"}'
        - 'up'
    static_configs:
      - targets:
          - 'prometheus-virginia.internal:9090'
        labels:
          source_region: 'us-virginia'

Burada dikkat edilmesi gereken birkaç nokta var:

  • honor_labels: true: Bu olmadan global Prometheus kendi label’larıyla leaf’in label’larını eziyor. Bölge bilgisi kaybolur.
  • scrape_interval: 60s: Global’ın scrape sıklığını leaf’ten düşük tutun. Her 15 saniyede bir 3 bölgeden metric çekmek hem network hem de Prometheus için ağır.
  • match[] parametreleri: Sadece ihtiyacınız olan metric’leri çekin. Tüm metric’leri federate etmek çok büyük veri transferine yol açar.

Güvenlik: TLS ve Kimlik Doğrulama

Production ortamında leaf Prometheus’lar açık olmamalı. Basic auth ve TLS şart.

# Leaf Prometheus için basic auth konfigürasyonu
# Önce htpasswd ile şifre oluşturun
apt-get install -y apache2-utils
htpasswd -nBC 10 "" | tr -d ':n'
# Bu komut sadece şifre hash'i üretir, kullanıcı adı ayrıca verilecek

# web.yml oluşturun
cat > /opt/prometheus/tr-istanbul/web.yml << 'EOF'
basic_auth_users:
  global_scraper: '$2b$10$YourHashedPasswordHere'
EOF

Docker Compose’a web konfigürasyonunu ekleyin:

# docker-compose.yml komut satırına eklenecek flag
command:
  - '--config.file=/etc/prometheus/prometheus.yml'
  - '--web.config.file=/etc/prometheus/web.yml'
  - '--storage.tsdb.path=/prometheus'
  - '--storage.tsdb.retention.time=15d'
  - '--web.enable-lifecycle'

Global Prometheus tarafında kimlik bilgilerini tanımlayın:

# global prometheus.yml içinde federate job'larına ekleyin
  - job_name: 'federate-istanbul'
    scrape_interval: 60s
    honor_labels: true
    metrics_path: '/federate'
    basic_auth:
      username: 'global_scraper'
      password_file: '/etc/prometheus/secrets/istanbul_password'
    scheme: https
    tls_config:
      ca_file: '/etc/prometheus/certs/ca.crt'
      insecure_skip_verify: false
    params:
      match[]:
        - '{job="node-exporter"}'
        - 'up'

Şifreyi düz metin yerine dosyadan okumak, Git’e commit edilme riskini ortadan kaldırır.

Recording Rules ile Performans Optimizasyonu

Federation mimarisinde recording rules hem leaf hem de global Prometheus’ta kritik rol oynuyor. Ağır sorgular yerine önceden hesaplanmış metric’ler kullanmak, cross-region sorgu gecikmesini ciddi ölçüde azaltır.

# /opt/prometheus/tr-istanbul/rules/aggregations.yml

groups:
  - name: node_aggregations
    interval: 30s
    rules:
      - record: job:node_cpu_usage:avg
        expr: |
          100 - (
            avg by (instance, job) (
              rate(node_cpu_seconds_total{mode="idle"}[5m])
            ) * 100
          )

      - record: job:node_memory_usage_percent:avg
        expr: |
          100 - (
            (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
          )

      - record: job:node_disk_usage_percent:max
        expr: |
          max by (instance, device) (
            100 - (
              node_filesystem_avail_bytes{fstype!="tmpfs"} /
              node_filesystem_size_bytes{fstype!="tmpfs"} * 100
            )
          )

  - name: postgres_aggregations
    interval: 60s
    rules:
      - record: job:pg_active_connections:sum
        expr: sum by (instance) (pg_stat_activity_count{state="active"})

      - record: job:pg_replication_lag_seconds:max
        expr: max by (instance) (pg_replication_slots_confirmed_flush_lsn)

Global Prometheus’ta ise bölgeler arası karşılaştırma için recording rules yazıyoruz:

# /opt/prometheus/global/rules/cross_region.yml

groups:
  - name: cross_region_health
    interval: 60s
    rules:
      - record: region:node_cpu_usage:avg
        expr: |
          avg by (region) (
            job:node_cpu_usage:avg
          )

      - record: region:up_ratio:avg
        expr: |
          avg by (region, job) (up)

      - record: region:node_memory_pressure:bool
        expr: |
          (
            avg by (region) (job:node_memory_usage_percent:avg) > 85
          )

Alerting Stratejisi: Nerede Alert Tanımlayalım?

Federation mimarisinde alertleri nerede tanımlayacağınız önemli bir mimari karar. Pratik yaklaşım şu:

Bölgesel alertler leaf’te tanımlanır. Bir sunucunun CPU’su yüzde doksanı geçtiyse bunu bölgesel Prometheus tespit etmeli, yerel Alertmanager’a iletmeli. Global’in bunu bilmesini beklemeyin, geç kalır.

Cross-region alertler global’de tanımlanır. “İki bölgede birden servis düştü” veya “global latency arttı” gibi alertler ancak global’den görülebilir.

# /opt/prometheus/tr-istanbul/rules/alerts.yml

groups:
  - name: local_alerts
    rules:
      - alert: HighCPUUsage
        expr: job:node_cpu_usage:avg > 90
        for: 5m
        labels:
          severity: warning
          region: tr-istanbul
        annotations:
          summary: "Yüksek CPU kullanımı: {{ $labels.instance }}"
          description: "{{ $labels.instance }} sunucusunda CPU kullanımı {{ $value | humanize }}% seviyesinde, 5 dakikadır devam ediyor."

      - alert: DiskSpaceCritical
        expr: job:node_disk_usage_percent:max > 90
        for: 2m
        labels:
          severity: critical
          region: tr-istanbul
        annotations:
          summary: "Kritik disk doluluk: {{ $labels.instance }}"
          description: "{{ $labels.device }} diskinde doluluk oranı %{{ $value | humanize }} seviyesine ulaştı."
# /opt/prometheus/global/rules/cross_region_alerts.yml

groups:
  - name: global_alerts
    rules:
      - alert: MultiRegionOutage
        expr: count(region:up_ratio:avg < 0.5) >= 2
        for: 3m
        labels:
          severity: critical
          team: infrastructure
        annotations:
          summary: "Çoklu bölge kesintisi tespit edildi"
          description: "{{ $value }} bölgede servis erişilebilirliği %50'nin altına düştü."

      - alert: RegionCompletelyDown
        expr: region:up_ratio:avg == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Bölge tamamen erişilemez: {{ $labels.region }}"

Grafana Entegrasyonu

Grafana tarafında hem leaf Prometheus’ları hem de global Prometheus’u datasource olarak eklemeniz gerekiyor. Bölgesel detaylar için leaf’i, genel bakış için global’i kullanacaksınız.

Datasource konfigürasyonunu kod olarak yönetmek için Grafana provisioning kullanın:

# /etc/grafana/provisioning/datasources/prometheus.yml

apiVersion: 1

datasources:
  - name: Prometheus-Global
    type: prometheus
    access: proxy
    url: http://prometheus-global.internal:9090
    isDefault: true
    jsonData:
      timeInterval: '60s'
      queryTimeout: '60s'
      httpMethod: POST

  - name: Prometheus-Istanbul
    type: prometheus
    access: proxy
    url: http://prometheus-istanbul.internal:9090
    jsonData:
      timeInterval: '15s'
      httpMethod: POST
    secureJsonData:
      basicAuthPassword: 'your_password_here'
    basicAuth: true
    basicAuthUser: 'global_scraper'

  - name: Prometheus-Frankfurt
    type: prometheus
    access: proxy
    url: http://prometheus-frankfurt.internal:9090
    jsonData:
      timeInterval: '15s'
      httpMethod: POST
    basicAuth: true
    basicAuthUser: 'global_scraper'
    secureJsonData:
      basicAuthPassword: 'frankfurt_password_here'

Cross-region dashboard için örnek bir panel sorgusu:

# Bölgelere göre ortalama CPU kullanımı - Global Prometheus sorgusu
avg by (region) (
  job:node_cpu_usage:avg
)

# Bölge bazında up/down sunucu sayısı
count by (region, instance) (
  up == 0
)

# Son 1 saatte en yüksek CPU kullanan 5 sunucu (tüm bölgeler)
topk(5,
  max_over_time(job:node_cpu_usage:avg[1h])
)

Yaygın Sorunlar ve Çözümleri

Label çakışması sorunu: Global Prometheus metric’leri toplarken kendi instance label’ını leaf’in instance label’ının üzerine yazabilir. honor_labels: true bunu engelliyor, ama bu flag olmadan federation kurarsanız saatler boyu neden metric’lerinizin instance bilgisi yanlış diye kafanızı yorabilirsiniz.

Yüksek kardinality: Leaf’ten her şeyi federate etmeye çalışmayın. match[] parametresini dikkatli seçin. Kubernetes ortamlarında pod-level metric’lerin hepsini global’e çekmek, global Prometheus’u saatler içinde patlatabilir. Sadece ihtiyaç duyulan aggregate metric’leri gönderin.

Scrape timeout sorunları: Bölgeler arası network gecikmeleri yüksekse global Prometheus’un scrape timeout’unu artırın:

# global prometheus.yml
scrape_configs:
  - job_name: 'federate-virginia'
    scrape_interval: 60s
    scrape_timeout: 30s
    honor_labels: true
    metrics_path: '/federate'

Default scrape_timeout 10 saniyedir. Trans-atlantik bağlantıda bu yetmeyebilir.

Storage yönetimi: Global Prometheus’un retention süresini leaf’lerden farklı tutun. Leaf’lerde 15 gün, global’de 90 gün mantıklı bir ayrım. Detaylı metric’ler kısa süre leaf’te kalır, önemli aggregate’ler uzun süre global’de.

# Global Prometheus storage konfigürasyonu
--storage.tsdb.retention.time=90d
--storage.tsdb.retention.size=50GB

# Leaf Prometheus storage konfigürasyonu
--storage.tsdb.retention.time=15d
--storage.tsdb.retention.size=10GB

Operasyonel Kontroller

Sistemi canlıya aldıktan sonra düzenli kontrol etmeniz gereken metrikler:

  • prometheus_tsdb_head_series: Global Prometheus’ta bu sayı hızla büyüyorsa fazla metric federate ediyorsunuzdur.
  • prometheus_remote_storage_samples_dropped_total: Veri kaybı yaşanıyor mu kontrol edin.
  • up{job=~"federate-.*"}: Leaf’lerin global tarafından başarıyla scrape edilip edilmediğini gösterir.
  • scrape_duration_seconds{job=~"federate-.*"}: Federate scrape’lerin ne kadar sürdüğünü izleyin. 20 saniyeyi geçiyorsa timeout ayarlarını gözden geçirin.
# Federate job'larının durumunu kontrol etmek için
curl -s http://prometheus-global.internal:9090/api/v1/query 
  --data-urlencode 'query=up{job=~"federate-.*"}' | 
  python3 -m json.tool

# Leaf'lerden kaç metric geldiğini görmek için
curl -s http://prometheus-global.internal:9090/api/v1/query 
  --data-urlencode 'query=scrape_samples_scraped{job=~"federate-.*"}' | 
  python3 -m json.tool

Sonuç

Prometheus Federation, çok bölgeli altyapılarda izleme sorununu ele almanın pratik ve savaşta test edilmiş bir yolu. Ama sihirli değnek değil. Doğru yapılandırılmadığında, yani honor_labels atlanırsa, çok fazla metric federate edilirse veya bölgesel alert’ler global’e bırakılırsa, sizi kurtarmak yerine başka sorunların kaynağı olabilir.

Benim önerim şu: Küçük başlayın. Önce sadece up metric’lerini ve temel node metric’lerini federate edin. Sistemin nasıl davrandığını görün, global Prometheus’un yükünü izleyin. Sonra kademeli olarak ihtiyaç duyduğunuz metric’leri ekleyin. Recording rules’ları baştan iyi tasarlarsanız, sonradan global Prometheus’a binen yükü ciddi ölçüde kontrol altında tutabilirsiniz.

Alternatif olarak Thanos veya Cortex gibi araçlara bakan ekipler de var, bunlar daha karmaşık ama daha ölçeklenebilir çözümler sunuyor. Ancak on veya yirmi bölgeye ulaşmadan Federation genellikle ihtiyacı karşılıyor ve operasyonel karmaşıklığı çok daha düşük tutuyor.

Benzer Konular

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir