00 Neden metrik izleme?
Gömülü sistemler genellikle uzak konumlarda, müdahale imkânı kısıtlı ortamlarda çalışır. Sorun yaşandıktan sonra log incelemek yerine, sorun oluşmadan önce eşik aşımlarını tespit etmek — gözlemlenebilirlik (observability) — prodüksiyon kalitesinin temel göstergesidir.
Gömülü sistemde monitoring gereksinimleri
Gözlemlenebilirlik üçgeni
Metrics (Prometheus)
/ sayısal veriler \
/ \
/ \
Logs --+---- Observability ----+-- Traces
(loki) üçgeni tamamlar (jaeger/tempo)
Bu rehberde odak noktamız metrics — sayısal zaman serisi verisi. Logs ve traces ayrı konulardır; Loki ve Tempo Grafana ekosisteminde bu eksikleri kapatır ancak gömülü sistemlerde genellikle metrics tek başına yeterlidir.
01 Prometheus mimarisi
Prometheus, pull (çekme) tabanlı bir izleme sistemidir. Hedef sistemler HTTP /metrics endpoint'i sunar; Prometheus periyodik olarak bu endpoint'i scrape eder ve TSDB'ye (Time Series Database) yazar.
┌──────────────┐ scrape ┌──────────────────────────┐
│ node_exporter│◄──────────────│ │
│ :9100/metrics│ │ Prometheus Server │
└──────────────┘ │ │
│ ┌──────────────────┐ │
┌──────────────┐ │ │ TSDB (chunks) │ │
│custom_exporter◄──────────────│ │ /data/chunks_ │ │
│ :8080/metrics│ │ └──────────────────┘ │
└──────────────┘ │ │ │
│ PromQL engine │
┌──────────────┐ push │ │ │
│ Pushgateway │───────────────►│ │ │
└──────────────┘ └─────────┬─┘ │
│ │
┌─────────────▼───────────────┐│
│ Alertmanager ││
│ (email/slack/pagerduty) ││
└─────────────────────────────┘│
│
┌─────────────▼───────────────┐
│ Grafana │
│ Datasource: Prometheus │
└─────────────────────────────┘
prometheus.yml temel yapılandırma
global:
scrape_interval: 15s # Her 15 saniyede bir scrape
evaluation_interval: 15s # Alert kurallarını değerlendir
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
rule_files:
- "alerts/*.yml"
scrape_configs:
# Prometheus'un kendisini izlemesi
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Statik hedef listesi
- job_name: 'embedded_devices'
scrape_interval: 30s
scrape_timeout: 10s
static_configs:
- targets:
- '192.168.1.10:9100' # cihaz-1
- '192.168.1.11:9100' # cihaz-2
- '192.168.1.12:9100' # cihaz-3
relabel_configs:
- source_labels: [__address__]
regex: '(.*):.*'
target_label: instance
# Dinamik keşif (file-based SD)
- job_name: 'dynamic_devices'
file_sd_configs:
- files: ['/etc/prometheus/targets/*.json']
refresh_interval: 1m
TSDB saklama ayarları
# Prometheus başlatma parametreleri
/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--storage.tsdb.retention.time=30d \ # 30 gün saklama
--storage.tsdb.retention.size=10GB \ # veya 10GB ile sınırla
--web.listen-address=0.0.0.0:9090 \
--web.enable-lifecycle # /-/reload endpoint'i
02 node_exporter
node_exporter, Linux sistem metriklerini Prometheus formatında sunan resmi exporter'dır. /proc ve /sys dosya sistemlerinden okuyarak CPU, bellek, disk, ağ ve sıcaklık metriklerini yayınlar.
ARM binary kurulumu (gömülü cihaz)
# ARMv7 (Raspberry Pi 2/3 32-bit)
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/\
node_exporter-1.8.2.linux-armv7.tar.gz
tar xf node_exporter-1.8.2.linux-armv7.tar.gz
sudo cp node_exporter-1.8.2.linux-armv7/node_exporter /usr/local/bin/
# ARM64 (Raspberry Pi 4/5, 64-bit OS)
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/\
node_exporter-1.8.2.linux-arm64.tar.gz
# systemd servisi
sudo tee /etc/systemd/system/node_exporter.service << 'EOF'
[Unit]
Description=Prometheus Node Exporter
After=network.target
[Service]
Type=simple
User=node_exporter
ExecStart=/usr/local/bin/node_exporter \
--collector.systemd \
--collector.processes \
--collector.hwmon \
--collector.thermal_zone \
--collector.cpu.info \
--web.listen-address=:9100
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
sudo useradd -rs /bin/false node_exporter
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
Önemli metrikler
| Metrik | Tip | Açıklama |
|---|---|---|
node_cpu_seconds_total | Counter | Mod bazında CPU zamanı (user/system/idle/iowait) |
node_memory_MemAvailable_bytes | Gauge | Kullanılabilir bellek — MemFree + cache/buffer |
node_disk_io_time_seconds_total | Counter | Disk I/O'da geçen toplam süre — utilization için kullanılır |
node_network_receive_bytes_total | Counter | Arayüz başına alınan byte sayısı |
node_thermal_zone_temp | Gauge | Termal zon sıcaklığı (Celsius × 1000) |
node_hwmon_temp_celsius | Gauge | hwmon sensör sıcaklıkları (CPU, GPU, board) |
node_filesystem_avail_bytes | Gauge | Dosya sistemi kullanılabilir alan |
node_load1 | Gauge | 1 dakikalık yük ortalaması |
node_vmstat_oom_kill | Counter | OOM killer tetiklenme sayısı |
Sıcaklık metrikleri için --collector.hwmon ve --collector.thermal_zone collector'larının açık olması gerekir. node_thermal_zone_temp değeri Kelvin × 1000 cinsinden gelir — Celsius'a çevirmek için / 1000 yapmanız gerekir. node_hwmon_temp_celsius ise doğrudan Celsius cinsinden gelir.
03 Custom exporter yazma
node_exporter'ın sunmadığı uygulama düzeyi veya donanıma özgü metrikleri yayınlamak için custom exporter yazılır. Python veya Go tercih edilir; Go binary boyutu nedeniyle gömülü hedefler için Python daha pratiktir.
Python ile custom exporter
pip install prometheus_client
#!/usr/bin/env python3
"""
Özel gömülü sistem exporter'ı
Raspberry Pi ADC sensör değerlerini ve GPIO durumunu izler
"""
import time
import os
from prometheus_client import start_http_server, Gauge, Counter, Histogram
# Metrik tanımlamaları
adc_voltage = Gauge(
'embedded_adc_voltage_volts',
'ADC kanalı gerilim değeri',
['channel'] # label
)
gpio_state = Gauge(
'embedded_gpio_state',
'GPIO pin durumu (0=low, 1=high)',
['pin', 'direction']
)
i2c_errors = Counter(
'embedded_i2c_errors_total',
'I2C okuma hataları',
['bus', 'device']
)
sensor_read_duration = Histogram(
'embedded_sensor_read_seconds',
'Sensör okuma süresi',
['sensor_type'],
buckets=[.001, .005, .01, .025, .05, .1, .25, .5, 1.0]
)
def read_adc_channel(channel: int) -> float:
"""
/sys/bus/iio/devices/iio:device0/in_voltageN_raw oku
ve voltaja çevir (12-bit ADC, 3.3V ref)
"""
path = f"/sys/bus/iio/devices/iio:device0/in_voltage{channel}_raw"
try:
with open(path) as f:
raw = int(f.read().strip())
return raw * 3.3 / 4095.0
except (FileNotFoundError, ValueError):
return -1.0
def read_gpio_sysfs(pin: int) -> int:
"""GPIO değerini sysfs'ten oku"""
try:
with open(f"/sys/class/gpio/gpio{pin}/value") as f:
return int(f.read().strip())
except FileNotFoundError:
return -1
def collect_metrics():
"""Periyodik metrik toplama"""
for ch in range(4):
with sensor_read_duration.labels('adc').time():
v = read_adc_channel(ch)
if v >= 0:
adc_voltage.labels(channel=str(ch)).set(v)
for pin in [17, 27, 22, 23]:
state = read_gpio_sysfs(pin)
if state >= 0:
gpio_state.labels(pin=str(pin), direction='input').set(state)
if __name__ == '__main__':
start_http_server(8080)
print("Exporter başladı: http://0.0.0.0:8080/metrics")
while True:
collect_metrics()
time.sleep(10) # 10 saniyede bir güncelle
Go ile minimal exporter
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var cpuFreqMHz = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "embedded_cpu_freq_mhz",
Help: "CPU çekirdek frekansı MHz cinsinden",
},
[]string{"cpu"},
)
func init() {
prometheus.MustRegister(cpuFreqMHz)
}
func main() {
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":9200", nil)
}
04 PromQL sorgu dili
PromQL (Prometheus Query Language), zaman serisi veri üzerinde anlık sorgular ve range sorgular yapmanızı sağlar. Grafana panelleri, alerting kuralları ve API çağrıları PromQL kullanır.
Temel fonksiyonlar
# CPU kullanımı (son 5 dakikanın ortalaması, %)
100 - (avg by (instance) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100)
# Belirli instance için:
100 - (avg by (instance) (
rate(node_cpu_seconds_total{mode="idle", instance="192.168.1.10:9100"}[5m])
) * 100)
# Disk I/O utilization (%):
rate(node_disk_io_time_seconds_total{device="mmcblk0"}[5m]) * 100
# Ağ bant genişliği (Mbps):
rate(node_network_receive_bytes_total{device="eth0"}[1m]) * 8 / 1e6
# Kullanılabilir bellek yüzdesi:
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100
# Sıcaklık (thermal_zone — Celsius):
node_thermal_zone_temp / 1000
# Dosya sistemi doluluk %:
100 - (node_filesystem_avail_bytes{mountpoint="/"} /
node_filesystem_size_bytes{mountpoint="/"} * 100)
Aggregation operatörleri
# Tüm cihazların ortalama CPU kullanımı:
avg(100 - rate(node_cpu_seconds_total{mode="idle"}[5m]) * 100)
# En yüksek CPU kullanan 5 cihaz:
topk(5, 100 - avg by (instance) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100)
# Bellek ortalaması by job:
avg by (job) (node_memory_MemAvailable_bytes)
# Her instance için maksimum sıcaklık:
max by (instance) (node_hwmon_temp_celsius)
# Toplam ağ trafiği (tüm cihazlar):
sum(rate(node_network_receive_bytes_total[5m]))
# irate: anlık rate (spike tespiti için):
irate(node_cpu_seconds_total{mode="user"}[1m])
# histogram_quantile: P95 yanıt süresi:
histogram_quantile(0.95,
rate(embedded_sensor_read_seconds_bucket[5m])
)
Alert kuralı örneği
# /etc/prometheus/alerts/embedded.yml
groups:
- name: embedded_alerts
rules:
- alert: HighCPUTemperature
expr: node_hwmon_temp_celsius > 80
for: 2m
labels:
severity: warning
annotations:
summary: "Yüksek CPU sıcaklığı {{ $labels.instance }}"
description: "Sıcaklık {{ $value }}°C — kritik eşik: 85°C"
- alert: LowDiskSpace
expr: (node_filesystem_avail_bytes{mountpoint="/"} /
node_filesystem_size_bytes{mountpoint="/"}) * 100 < 10
for: 5m
labels:
severity: critical
annotations:
summary: "Disk dolmak üzere {{ $labels.instance }}"
- alert: OOMKillerTriggered
expr: increase(node_vmstat_oom_kill[1h]) > 0
labels:
severity: critical
05 Grafana kurulumu ve dashboard
Grafana, Prometheus TSDB verilerini görselleştirmek için en yaygın kullanılan açık kaynak araçtır. Panel, dashboard, variable ve annotation özellikleri ile esnek izleme arayüzleri oluşturulur.
Docker Compose ile hızlı kurulum
# docker-compose.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:v2.51.0
ports: ['9090:9090']
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./alerts:/etc/prometheus/alerts
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-lifecycle'
grafana:
image: grafana/grafana:10.4.0
ports: ['3000:3000']
environment:
- GF_SECURITY_ADMIN_PASSWORD=embedded123
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
depends_on: [prometheus]
alertmanager:
image: prom/alertmanager:v0.27.0
ports: ['9093:9093']
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
volumes:
prometheus_data:
grafana_data:
Grafana provisioning ile otomatik datasource
# grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true
jsonData:
timeInterval: "15s"
queryTimeout: "60s"
Dashboard özellikleri
$instance değişkeni ile cihaz seçimi — tek dashboard, çok cihaz06 Embedded device dashboard
Gömülü sistem dashboard'u, genel sunucu dashboard'undan farklı metriklere odaklanır: CPU frekans ölçekleme, termal yönetim, bellek baskısı ve özel donanım metrikleri.
Dashboard JSON panel örnekleri
# CPU Frekans Skalama (ölçekleme governors ile)
# Metrik: node_cpu_frequency_hertz (cpufreq collector)
# Panel tipi: Time series
# PromQL:
node_cpu_frequency_hertz{instance="$instance"} / 1e9
# Legend: {{cpu}} GHz
# Thermal Zone Sıcaklığı
# PromQL:
node_thermal_zone_temp{instance="$instance"} / 1000
# Threshold: Yellow=70, Red=80, Unit: Celsius
# Bellek Baskısı (memory pressure)
# PromQL - kswapd aktivitesi:
rate(node_vmstat_pgpgout[5m]) + rate(node_vmstat_pgpgin[5m])
# OOM Kill sayacı (stat panel)
# PromQL:
increase(node_vmstat_oom_kill{instance="$instance"}[24h])
# eMMC I/O gecikme dağılımı
# PromQL:
rate(node_disk_read_time_seconds_total{instance="$instance",device="mmcblk0"}[5m]) /
rate(node_disk_reads_completed_total{instance="$instance",device="mmcblk0"}[5m]) * 1000
# Unit: Milliseconds
Dashboard JSON template (kısaltılmış)
{
"title": "Embedded Device Overview",
"uid": "embedded-overview",
"templating": {
"list": [{
"name": "instance",
"type": "query",
"datasource": "Prometheus",
"query": "label_values(node_uname_info, instance)",
"refresh": 2
}]
},
"panels": [
{
"title": "CPU Kullanımı",
"type": "timeseries",
"targets": [{
"expr": "100 - avg by(cpu)(rate(node_cpu_seconds_total{mode='idle',instance='$instance'}[5m]))*100",
"legendFormat": "{{cpu}}"
}]
}
]
}
07 collectd entegrasyonu
Bazı gömülü sistemlerde Prometheus exporter çalıştırmak uygun olmayabilir — collectd, onlarca yıllık olgunluğa sahip hafif bir metrik toplama daemonu'dur ve Prometheus ile köprülenir.
write_prometheus plugin
# /etc/collectd/collectd.conf
Hostname "gömülü-cihaz-01"
Interval 30
LoadPlugin cpu
LoadPlugin memory
LoadPlugin interface
LoadPlugin disk
LoadPlugin sensors # lm-sensors
LoadPlugin thermal
LoadPlugin processes
# Prometheus bridge — /metrics endpoint açar
LoadPlugin write_prometheus
<Plugin write_prometheus>
Port "9103"
</Plugin>
# CPU detayı
<Plugin cpu>
ReportByCpu true
ReportByState true
ValuesPercentage true
</Plugin>
# Network interface
<Plugin interface>
Interface "eth0"
Interface "wlan0"
IgnoreSelected false
</Plugin>
collectd → Prometheus scrape config
# prometheus.yml
scrape_configs:
- job_name: 'collectd_bridge'
static_configs:
- targets:
- '192.168.1.10:9103' # collectd write_prometheus
metric_relabel_configs:
# collectd metrik adlarını normalize et
- source_labels: [__name__]
regex: 'collectd_(.*)'
target_label: __name__
replacement: 'node_${1}'
collectd ile Prometheus arasında bir diğer yol collectd-exporter köprüsüdür. collectd binary protokolüyle collectd-exporter'a veri gönderir, exporter bunu Prometheus formatına çevirir. Bu yaklaşım write_prometheus plugin'ine göre daha esnek metrik dönüşümü imkânı sunar.
08 Pratik: Raspberry Pi fleet izleme
Birden fazla Raspberry Pi'yi merkezi Prometheus + Grafana ile izleyen eksiksiz bir kurulum.
Hedef mimari
Raspberry Pi 1 (192.168.1.10) → node_exporter :9100
Raspberry Pi 2 (192.168.1.11) → node_exporter :9100
Raspberry Pi 3 (192.168.1.12) → node_exporter :9100 + custom_exporter :8080
│
│ HTTP scrape (pull model)
▼
Linux Sunucu (192.168.1.1)
├── Prometheus :9090
├── Grafana :3000
└── Alertmanager :9093 → Email/Slack
Raspberry Pi'ye node_exporter kurulumu (Ansible)
# hosts.yml
all:
hosts:
pi1: {ansible_host: 192.168.1.10}
pi2: {ansible_host: 192.168.1.11}
pi3: {ansible_host: 192.168.1.12}
vars:
ansible_user: pi
ansible_become: yes
# playbook.yml
- hosts: all
tasks:
- name: node_exporter ARM64 indir
get_url:
url: "https://github.com/prometheus/node_exporter/releases/\
download/v1.8.2/node_exporter-1.8.2.linux-arm64.tar.gz"
dest: /tmp/node_exporter.tar.gz
- name: Arşivden çıkar
unarchive:
src: /tmp/node_exporter.tar.gz
dest: /tmp/
remote_src: yes
- name: Binary kopyala
copy:
src: /tmp/node_exporter-1.8.2.linux-arm64/node_exporter
dest: /usr/local/bin/node_exporter
mode: '0755'
remote_src: yes
- name: Systemd servisi kur
copy:
content: |
[Unit]
Description=Node Exporter
[Service]
ExecStart=/usr/local/bin/node_exporter \
--collector.hwmon \
--collector.thermal_zone \
--collector.systemd
Restart=always
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/node_exporter.service
- name: Servisi başlat
systemd:
name: node_exporter
enabled: yes
state: started
daemon_reload: yes
Raspberry Pi özel metrikleri
# vcgencmd ile GPU sıcaklığı ve throttle durumu
# /usr/local/bin/rpi_exporter.sh
#!/bin/bash
echo "# HELP rpi_temp_celsius Raspberry Pi SoC sıcaklığı"
echo "# TYPE rpi_temp_celsius gauge"
TEMP=$(vcgencmd measure_temp | grep -oP '\d+\.\d+')
echo "rpi_temp_celsius $TEMP"
echo "# HELP rpi_throttled Raspberry Pi throttling durumu"
echo "# TYPE rpi_throttled gauge"
THROTTLE=$(vcgencmd get_throttled | grep -oP '0x\w+')
echo "rpi_throttled $((THROTTLE))"
echo "# HELP rpi_cpu_freq_hz CPU frekansı"
echo "# TYPE rpi_cpu_freq_hz gauge"
FREQ=$(vcgencmd measure_clock arm | grep -oP '\d+$')
echo "rpi_cpu_freq_hz $FREQ"
# textfile collector ile Prometheus'a sun:
# node_exporter başlatma:
/usr/local/bin/node_exporter \
--collector.textfile.directory=/var/lib/node_exporter/textfile_collector
# Cron ile her dakika güncelle:
* * * * * root /usr/local/bin/rpi_exporter.sh > \
/var/lib/node_exporter/textfile_collector/rpi.prom
Raspberry Pi'nin vcgencmd get_throttled çıktısı hex bit maskesidir. Bit 0: under-voltage, Bit 1: arm frequency capped, Bit 2: currently throttled, Bit 3: soft temperature limit. Grafana'da bu bitleri ayrıştırmak için PromQL'de % 2 ve bitwise operatörler kullanılabilir.