Veritabanı izleme konusu birçok sysadmin’in ertelediği ama sonradan pişman olduğu bir alan. “Her şey çalışıyor, neden izleyeyim?” diye düşünürsün, ta ki üretim veritabanın gece 2’de yanıt vermeyi bırakana kadar. Prometheus ve Grafana kombinasyonu bu iş için gerçekten iyi bir çözüm sunuyor: birincisi veriyi toplar, ikincisi anlamlı hale getirir. Bu yazıda sıfırdan kurulumdan gerçek dünyada kullanılan alert kurallarına kadar her şeyi ele alacağız.
Neden Prometheus + Grafana?
Piyasada MySQL izleme için onlarca araç var. Percona Monitoring and Management (PMM) hazır gelir, Datadog ücretlidir, Zabbix kurulumu karmaşıktır. Ama Prometheus ve Grafana kombinasyonu özellikle şu sebeplerle öne çıkıyor:
- Açık kaynak ve ücretsiz: Lisans maliyeti yok, topluluk desteği güçlü
- Esnek mimari: Kendi metrik toplayıcılarını yazabilirsin
- Grafana ekosistemi: Hazır dashboard’lar mevcut, özelleştirme kolay
- Uzun vadeli depolama: PromQL ile güçlü sorgular yazabilirsin
- Alert yönetimi: Alertmanager ile gelişmiş bildirim sistemi
Küçük bir startup’tan büyük ölçekli bir altyapıya kadar uyarlanabiliyor. Biz bu yazıda Ubuntu 22.04 üzerinde çalışacağız ama CentOS/RHEL için de adımlar büyük ölçüde benzer.
Mimariyi Anlamak
Kuruluma geçmeden önce kafanızda bir resim oluşturalım. Sistem şu şekilde çalışıyor:
- mysqld_exporter: MySQL sunucusuna bağlanır, metrikleri HTTP üzerinden expose eder
- Prometheus: Belirli aralıklarla mysqld_exporter’dan metrikleri çeker (scrape)
- Grafana: Prometheus’u data source olarak kullanır, dashboard’lar oluşturur
- Alertmanager: Prometheus’tan gelen alert’leri işler, bildirimleri iletir
Veri akışı tek yönlü: MySQL -> mysqld_exporter -> Prometheus -> Grafana. Bu mimari sayesinde bileşenler birbirinden bağımsız. Grafana çökse Prometheus veri toplamaya devam eder.
Ön Hazırlık
Ortamımızı netleştirelim:
- MySQL sunucusu: 192.168.1.10
- Prometheus + Grafana sunucusu: 192.168.1.20
- İşletim sistemi: Ubuntu 22.04 LTS
Gerçek dünyada mysqld_exporter’ı MySQL sunucusunun üzerine kurmanı öneririm. Ağ gecikmesi azalır ve güvenlik tarafında MySQL’in local socket’ini kullanabilirsin.
MySQL’de İzleme Kullanıcısı Oluşturma
mysqld_exporter’ın MySQL’e bağlanabilmesi için özel bir kullanıcı oluşturmak gerekiyor. Bu kullanıcıya sadece gereken izinleri ver, asla root kullanma.
mysql -u root -p
CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'GucluBirSifre123!' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';
GRANT SELECT ON performance_schema.* TO 'exporter'@'localhost';
FLUSH PRIVILEGES;
EXIT;
WITH MAX_USER_CONNECTIONS 3 kısmı önemli. Exporter’ın aşırı bağlantı açmasını engeller. REPLICATION CLIENT izni slave durumunu izlemek için gerekiyor.
mysqld_exporter Kurulumu
MySQL sunucusunda (192.168.1.10) çalışıyoruz:
# En son versiyonu kontrol et
wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.15.1/mysqld_exporter-0.15.1.linux-amd64.tar.gz
tar xzf mysqld_exporter-0.15.1.linux-amd64.tar.gz
sudo mv mysqld_exporter-0.15.1.linux-amd64/mysqld_exporter /usr/local/bin/
sudo chmod +x /usr/local/bin/mysqld_exporter
# Versiyon kontrolü
mysqld_exporter --version
Şimdi kimlik bilgileri dosyasını oluşturalım. Şifreyi komut satırına yazmak güvenli değil:
sudo mkdir -p /etc/mysqld_exporter
sudo tee /etc/mysqld_exporter/.my.cnf << 'EOF'
[client]
user=exporter
password=GucluBirSifre123!
host=localhost
socket=/var/run/mysqld/mysqld.sock
EOF
sudo chmod 600 /etc/mysqld_exporter/.my.cnf
sudo chown root:root /etc/mysqld_exporter/.my.cnf
Systemd servis dosyası oluşturalım:
sudo tee /etc/systemd/system/mysqld_exporter.service << 'EOF'
[Unit]
Description=MySQL Exporter for Prometheus
After=network.target mysql.service
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/mysqld_exporter
--config.my-cnf=/etc/mysqld_exporter/.my.cnf
--collect.global_status
--collect.global_variables
--collect.slave_status
--collect.info_schema.innodb_metrics
--collect.info_schema.processlist
--collect.info_schema.tables
--collect.perf_schema.eventswaits
--collect.perf_schema.file_events
--collect.perf_schema.tablelocks
--web.listen-address=:9104
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable mysqld_exporter
sudo systemctl start mysqld_exporter
sudo systemctl status mysqld_exporter
Çalışıp çalışmadığını test edelim:
curl -s http://localhost:9104/metrics | head -50
Ekranda mysql_global_status_connections gibi metrikler göreceksin. Eğer görüyorsan kurulum başarılı.
Firewall Ayarı
Prometheus sunucusunun bu porta erişebilmesi için:
sudo ufw allow from 192.168.1.20 to any port 9104
Tüm ağa açma. Sadece Prometheus sunucusuna izin ver.
Prometheus Kurulumu
Prometheus sunucusunda (192.168.1.20) çalışıyoruz:
wget https://github.com/prometheus/prometheus/releases/download/v2.48.0/prometheus-2.48.0.linux-amd64.tar.gz
tar xzf prometheus-2.48.0.linux-amd64.tar.gz
sudo useradd --no-create-home --shell /bin/false prometheus
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo mv prometheus-2.48.0.linux-amd64/prometheus /usr/local/bin/
sudo mv prometheus-2.48.0.linux-amd64/promtool /usr/local/bin/
sudo mv prometheus-2.48.0.linux-amd64/consoles /etc/prometheus/
sudo mv prometheus-2.48.0.linux-amd64/console_libraries /etc/prometheus/
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus
Prometheus konfigürasyonunu yazalım:
sudo tee /etc/prometheus/prometheus.yml << 'EOF'
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_timeout: 10s
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093
rule_files:
- "/etc/prometheus/rules/mysql_alerts.yml"
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'mysql_production'
static_configs:
- targets: ['192.168.1.10:9104']
labels:
environment: production
team: backend
- job_name: 'mysql_staging'
static_configs:
- targets: ['192.168.1.11:9104']
labels:
environment: staging
team: backend
EOF
Birden fazla MySQL sunucusu izliyorsan her biri için ayrı job tanımlamak yerine file_sd_configs kullanabilirsin. Dinamik ortamlarda çok kullanışlı oluyor.
Alert kuralları dizinini oluşturalım:
sudo mkdir -p /etc/prometheus/rules
sudo chown prometheus:prometheus /etc/prometheus/rules
Systemd servis dosyası:
sudo tee /etc/systemd/system/prometheus.service << 'EOF'
[Unit]
Description=Prometheus Monitoring
After=network.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus
--config.file=/etc/prometheus/prometheus.yml
--storage.tsdb.path=/var/lib/prometheus
--storage.tsdb.retention.time=30d
--web.enable-lifecycle
--web.listen-address=0.0.0.0:9090
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable prometheus
sudo systemctl start prometheus
--storage.tsdb.retention.time=30d ile 30 günlük veri saklıyoruz. Disk alanına göre bunu ayarlayabilirsin. Genellikle yoğun bir MySQL sunucusu için 30 günlük veri yaklaşık 2-5 GB yer tutar.
Grafana Kurulumu
sudo apt-get install -y apt-transport-https software-properties-common
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install -y grafana
sudo systemctl enable grafana-server
sudo systemctl start grafana-server
Grafana varsayılan olarak 3000 portunda çalışır. Tarayıcıdan http://192.168.1.20:3000 adresine git. Varsayılan kullanıcı adı ve şifre admin/admin. İlk girişte şifreyi değiştirmeni isteyecek, değiştir.
Prometheus’u Data Source Olarak Ekleme
Grafana arayüzünde:
- Sol menüden Configuration > Data Sources yolunu izle
- Add data source tıkla
- Prometheus seç
- URL olarak
http://localhost:9090gir - Save & Test tıkla
Yeşil “Data source is working” mesajı görmelisin.
Hazır Dashboard Yükleme
Sıfırdan dashboard yazmak yerine topluluk tarafından hazırlanan dashboard’ları kullanabilirsin. Grafana.com üzerinde MySQL için popüler olan ID’ler:
- 7362: MySQL Overview (mysqld_exporter için optimize edilmiş)
- 14057: MySQL Exporter Quickstart
Dashboard import etmek için:
- Sol menüden Dashboards > Import
- Dashboard ID’sini gir (örn: 7362)
- Load tıkla
- Prometheus data source seç
- Import tıkla
Anında onlarca metriği gösteren bir dashboard karşına çıkacak.
MySQL Alert Kuralları
Grafana güzel görünüm sağlar ama asıl kritik olan erken uyarı sistemi. Şimdi production ortamında gerçekten işe yarayan alert kuralları yazalım:
sudo tee /etc/prometheus/rules/mysql_alerts.yml << 'EOF'
groups:
- name: mysql_alerts
interval: 30s
rules:
# MySQL down oldu
- alert: MySQLDown
expr: mysql_up == 0
for: 30s
labels:
severity: critical
team: dba
annotations:
summary: "MySQL {{ $labels.instance }} erişilemiyor"
description: "MySQL sunucusu 30 saniyeden fazladır yanıt vermiyor."
# Çok fazla aktif bağlantı
- alert: MySQLTooManyConnections
expr: (mysql_global_status_threads_connected / mysql_global_variables_max_connections) * 100 > 80
for: 2m
labels:
severity: warning
annotations:
summary: "MySQL bağlantıları kritik seviyede"
description: "Bağlantı kullanımı %{{ $value | printf "%.1f" }}"
# Yavaş query oranı yüksek
- alert: MySQLSlowQueriesHigh
expr: rate(mysql_global_status_slow_queries[5m]) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "MySQL yavaş query oranı yüksek"
description: "Son 5 dakikada saniyede {{ $value | printf "%.2f" }} yavaş query"
# InnoDB buffer pool doluluk oranı
- alert: MySQLInnoDBBufferPoolFull
expr: (1 - (mysql_global_status_innodb_buffer_pool_pages_free / mysql_global_status_innodb_buffer_pool_pages_total)) * 100 > 95
for: 5m
labels:
severity: warning
annotations:
summary: "InnoDB Buffer Pool neredeyse dolu"
description: "Buffer pool doluluk oranı %{{ $value | printf "%.1f" }}"
# Replication lag
- alert: MySQLReplicationLag
expr: mysql_slave_status_seconds_behind_master > 30
for: 1m
labels:
severity: critical
annotations:
summary: "MySQL replikasyon gecikmesi yüksek"
description: "Slave {{ $labels.instance }} masterin {{ $value }}s gerisinde"
# Disk I/O beklemeleri
- alert: MySQLHighDiskIOWait
expr: rate(mysql_global_status_innodb_data_reads[5m]) > 1000
for: 5m
labels:
severity: warning
annotations:
summary: "MySQL disk okuma yoğunluğu yüksek"
description: "Saniyede {{ $value | printf "%.0f" }} disk okuma işlemi"
EOF
sudo chown prometheus:prometheus /etc/prometheus/rules/mysql_alerts.yml
Kural dosyasını doğrulayalım:
promtool check rules /etc/prometheus/rules/mysql_alerts.yml
Prometheus’u yeniden yükleyelim:
sudo systemctl reload prometheus
Ya da API üzerinden:
curl -X POST http://localhost:9090/-/reload
Alertmanager Kurulumu
Alert kuralları yazdık ama bunları e-posta veya Slack’e göndermek için Alertmanager gerekiyor:
wget https://github.com/prometheus/alertmanager/releases/download/v0.26.0/alertmanager-0.26.0.linux-amd64.tar.gz
tar xzf alertmanager-0.26.0.linux-amd64.tar.gz
sudo mv alertmanager-0.26.0.linux-amd64/alertmanager /usr/local/bin/
sudo mv alertmanager-0.26.0.linux-amd64/amtool /usr/local/bin/
sudo mkdir -p /etc/alertmanager /var/lib/alertmanager
Alertmanager konfigürasyonu. Slack ve e-posta birlikte kullanıyoruz:
sudo tee /etc/alertmanager/alertmanager.yml << 'EOF'
global:
resolve_timeout: 5m
smtp_smarthost: 'smtp.gmail.com:587'
smtp_from: '[email protected]'
smtp_auth_username: '[email protected]'
smtp_auth_password: 'uygulama-sifresi'
slack_api_url: 'https://hooks.slack.com/services/XXXXX/YYYYY/ZZZZZ'
route:
group_by: ['alertname', 'environment']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'default'
routes:
- match:
severity: critical
receiver: 'critical-alerts'
repeat_interval: 1h
- match:
severity: warning
receiver: 'warning-alerts'
receivers:
- name: 'default'
slack_configs:
- channel: '#db-alerts'
title: 'Prometheus Alert'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
- name: 'critical-alerts'
slack_configs:
- channel: '#db-critical'
title: 'KRITIK: {{ .CommonAnnotations.summary }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
email_configs:
- to: '[email protected]'
subject: '[KRITIK] {{ .CommonAnnotations.summary }}'
- name: 'warning-alerts'
slack_configs:
- channel: '#db-alerts'
title: 'UYARI: {{ .CommonAnnotations.summary }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['instance']
EOF
inhibit_rules kısmı önemli bir detay. Bir instance için critical alert tetiklendiğinde aynı instance’ın warning alert’lerini susturur. Yoksa onlarca alert mesajı alırsın.
Gerçek Dünya Senaryosu: Yavaş Query Tespiti
Diyelim ki monitoring sistemin kurulu ve gece 3’te Slow Queries alert’i geldi. Sabah geldiğinde Grafana’dan baktın, belirli bir saatte yavaş query sayısının patladığını gördün. Şimdi ne yapacaksın?
Performance Schema aktifleştir ve detaylı analiz yap:
mysql -u root -p << 'EOF'
-- Performance schema açık mı?
SELECT @@performance_schema;
-- En yavaş queryler
SELECT
DIGEST_TEXT,
COUNT_STAR as calisma_sayisi,
ROUND(AVG_TIMER_WAIT/1000000000, 2) as ort_sure_sn,
ROUND(MAX_TIMER_WAIT/1000000000, 2) as max_sure_sn
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME IS NOT NULL
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 10;
EOF
Grafana’da özel bir panel oluşturmak için şu PromQL sorgusunu kullanabilirsin:
# Son 1 saatte toplam yavaş query sayısı
increase(mysql_global_status_slow_queries[1h])
# Saniyedeki yavaş query oranı (5 dakika penceresi)
rate(mysql_global_status_slow_queries[5m])
# Buffer pool hit ratio - %98 üzerinde olmalı
(mysql_global_status_innodb_buffer_pool_read_requests /
(mysql_global_status_innodb_buffer_pool_read_requests +
mysql_global_status_innodb_buffer_pool_reads)) * 100
Buffer pool hit ratio düştüyse genellikle innodb_buffer_pool_size‘ı artırmak gerekir. Bu metriği Grafana’da takip etmek, sunucuya ne zaman RAM eklenmesi gerektiğini planlamamı sağlıyor.
İzlenmesi Gereken Kritik Metrikler
Grafana dashboard’unda mutlaka olması gereken paneller ve onların PromQL sorguları:
Aktif bağlantı sayısı: mysql_global_status_threads_connected
Saniyede işlem sayısı (TPS): rate(mysql_global_status_commands_total{command="commit"}[5m])
Query önbellek hit oranı (MySQL 5.7 için): rate(mysql_global_status_qcache_hits[5m]) / (rate(mysql_global_status_qcache_hits[5m]) + rate(mysql_global_status_qcache_inserts[5m]))
InnoDB row lock beklemeleri: rate(mysql_global_status_innodb_row_lock_waits[5m])
Replikasyon durumu: mysql_slave_status_slave_sql_running
Row lock beklemeleri aniden yükseldiyse deadlock problemi yaşanıyor olabilir. Bu durumda:
mysql -u root -p -e "SHOW ENGINE INNODB STATUSG" | grep -A 30 "LATEST DETECTED DEADLOCK"
Ek İpuçları
Metric cardinality dikkat: mysqld_exporter’ın --collect.info_schema.tables seçeneği çok sayıda tablo olan veritabanlarında Prometheus’u yavaşlatabilir. Yüzlerce tablo varsa bu toplama özelliğini ya kapat ya da tabloları filtrele.
Retention vs. depolama dengesi: 30 günlük veri tutmak yerine Thanos veya VictoriaMetrics kullanarak uzun vadeli depolama yapabilirsin. Özellikle kapasite planlaması için aylık trendlere bakmak gerekiyor.
Güvenlik: Prometheus ve Grafana portlarını internete açma. Nginx reverse proxy arkasına al ve HTTPS kullan. mysqld_exporter için TLS de yapılandırabilirsin.
Test ortamı uyarısı: Alert eşiklerini test ortamında ayarlarken production’dan farklı değerler kullan. Test ortamında bağlantı sayısı 5’i geçince alert alırsan birkaç haftada bildirimlerden bıkarsın.
Sonuç
Bu kurulumu tamamladıktan sonra MySQL sunucunun ne durumda olduğunu artık tahmin etmek zorunda değilsin. Prometheus verisi topluyor, Grafana görselleştiriyor, Alertmanager seni uyarıyor. Gece 3’teki o “veritabanı neden yavaş?” sorusunun cevabı artık Grafana dashboard’unda seni bekliyor olacak.
Kurulumun ilk haftasında alert eşiklerini mutlaka gözden geçir. Çok hassas eşikler yorgunluk yaratır, çok gevşek eşikler ise asıl sorunu gözden kaçırır. Bir ay boyunca metrikleri gözlemleyip kendi ortamına özgü “normal” değerleri bulduktan sonra alert kurallarını buna göre fine-tune et. Her ortam farklı: bir e-ticaret sitesinin trafiği gün içinde dalgalanır, bir raporlama sisteminin yükü gece artar. Bu farklılıklara göre dinamik eşikler yazmak da mümkün ama bu konuyu başka bir yazıya bırakalım.