Monitoring & Logging
Prometheus, Alertmanager, PromQL, journald, Grafana, ו-Distributed Tracing — כפי שנראים ב-production
תיאוריה
ב-2026, כמעט כל חברת DevOps ישראלית בגודל בינוני ומעלה רצה Prometheus + Grafana. Datadog ו-New Relic פופולריים בסטארטאפים עם תקציב, אבל בראיונות עבודה ובסביבות ייצור — Prometheus הוא שם המשחק. ה-market_frequency של הנושא עומד על 2.4 בשוק הנוכחי, מה שמשמעותו שאנשי DevOps מתחילים מופתעים כמה מהר מצופה מהם לכתוב alerting rules מהיום הראשון.
הגישה הנפוצה בישראל: Prometheus scrapes metrics מ-exporters ומ-application endpoints, Grafana מציג dashboards, ו-Alertmanager שולח התראות ל-PagerDuty, Slack, או OpsGenie. AWS CloudWatch מופעל בצד עבור ה-infra ה-managed (RDS, EKS nodes).
נקודת כשל שכיחה: צוות שמקים Prometheus ו-Grafana, יוצר dashboards יפים, אבל לא מגדיר אף alert rule. המוניטורינג קיים, אבל לא מזהיר על כלום. התגלית הזו קורית בדרך כלל ב-3 לפנות בוקר.
Prometheus פועל על מודל pull: הוא שולח בקשת HTTP GET ל-/metrics endpoint של כל target, ומאחסן את התוצאות ב-TSDB (Time Series Database) הפנימי שלו. זאת ההיפוך ממערכות push כמו StatsD.
RETENTION ברירת המחדל היא 15 יום. ב-production תמצאו --storage.tsdb.retention.time=30d או יותר, ולעיתים Thanos או Cortex לאחסון ארוך טווח.
המבנה:
- **prometheus.yml** מגדיר את scrape_configs — רשימת targets, intervals, relabeling
- **Exporters** חושפים /metrics עבור תוכנות שלא תומכות ב-Prometheus natively (node_exporter, blackbox_exporter, postgres_exporter)
- **Pushgateway** עבור jobs קצרי מועד (batch jobs, cron) שלא ניתן ל-scrape אותם
- **Alertmanager** מקבל alerts מ-Prometheus ומנתב אותם לנמענים
דוגמת prometheus.yml בסיסית:
PromQL היא שפת השאילתות של Prometheus. טעות שכיחה של מתחילים: משתמשים ב-raw counter במקום ב-rate(). Counter רק עולה — אם השרת restart, הוא מתאפס, והגרף קופץ. rate() מחשב שינוי לשנייה בתוך חלון זמן ומטפל ב-resets אוטומטית.
**rate() vs irate()**
- rate(metric[5m]) — ממוצע על 5 דקות, מחליק spikes, טוב ל-alerts ו-dashboards
- irate(metric[5m]) — רגע אחרון בתוך 5 דקות, רגיש ל-spikes, טוב ל-real-time debugging
**increase()** — כמה עלה counter בחלון זמן (sum, לא per-second):
increase(http_requests_total[1h]) # סה"כ בקשות בשעה האחרונה**histogram_quantile()** — לחישוב percentiles מ-histogram:
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))זה חישוב ה-p99 latency. הטעות השכיחה: לשכוח את [le] label ב-by() clause כאשר מחשבים per-label.
**label matching ו-vector operations**:
# error rate as ratio:
rate(http_requests_total{status=~"5.."}[5m])
/
rate(http_requests_total[5m])חשוב: שני vectors חייבים להיות matchable על ה-labels. אם labels שונים — משתמשים ב-on() או ignoring().
**label_replace()** — לשנות label values בשאילתה:
label_replace(up, "short_name", "$1", "job", "(.+)-service")SLI (Service Level Indicator) הוא מדד שמייצג את בריאות השירות. SLO (Service Level Objective) הוא היעד שהסכמנו עליו. בישראל, SLO קלאסי: 99.9% availability ב-30 יום, p99 latency < 500ms.
**PromQL לאחוז ה-availability** (מבוסס על probe/blackbox_exporter):
# availability over 30-day window (requires recording rules for efficiency)
avg_over_time(up{job="api"}[30d]) * 100**error_rate SLI**:
(
1 - (
rate(http_requests_total{status!~"5.."}[5m])
/
rate(http_requests_total[5m])
)
) * 100**Recording Rules**: שאילתות SLO כבדות עדיף לחשב כ-recording rules — Prometheus מחשב אותן מראש ושומר כ-metric חדש:
groups:
- name: slo_recording
interval: 1m
rules:
- record: job:http_request_duration_seconds:p99
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m]))
by (le, job)
)
- record: job:http_error_rate:ratio
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
/
sum(rate(http_requests_total[5m])) by (job)**Error Budget**: אחוז זמן שנותר מה-error budget:
# error budget remaining % (99.9% SLO)
(1 - (
sum(rate(http_requests_total{status=~"5.."}[30d]))
/
sum(rate(http_requests_total[30d]))
)) / 0.001 * 100כאשר הערך מגיע ל-0%, ה-error budget אזל — הצוות צריך להפסיק feature work ולהתמקד ב-reliability.
Alert rule היא PromQL expression שאם היא מחזירה ערכים — Prometheus יוצר alert. ה-for: duration מגדיר כמה זמן ה-condition חייב להיות אמיתי לפני שה-alert יורה — זה מונע flapping על spikes קצרים.
מבנה של alert rule תקין:
groups:
- name: api_alerts
rules:
- alert: HighErrorRate
expr: |
(
rate(http_requests_total{status=~"5.."}[5m])
/
rate(http_requests_total[5m])
) > 0.05
for: 2m
labels:
severity: critical
team: backend
annotations:
summary: "High error rate on {{ $labels.job }}"
description: "Error rate is {{ $value | humanizePercentage }} (threshold: 5%)"
runbook_url: "https://wiki.example.com/runbooks/high-error-rate"**חוקי for: duration בייצור**:
- for: 0m — alert מיידי, רק לדברים קריטיים שצריכים תגובה מידית (disk full at 99%)
- for: 2m - for: 5m — ברירת מחדל סבירה לרוב ה-alerts
- for: 15m — לסטטיסטיקות איטיות כמו memory growth
**Labels וחשיבותם**: ה-labels בתוך alert rule הם מה ש-Alertmanager משתמש בו לניתוב. אם severity=critical הולך ל-PagerDuty, ו-severity=warning הולך ל-Slack — זה קורה בגלל label matching.
**Annotations**: אינפורמציה הניתנת לקריאה על ידי אנשים. summary קצר (כותרת), description מפורט, runbook_url — הקישור שה-on-call engineer יפתח ב-3 לפנות בוקר. ה-runbook_url הוא לא nice-to-have, הוא חובה.
**Template variables**: בתוך annotations, ניתן להשתמש ב-Go template syntax — {{ $labels.instance }}, {{ $value | humanize }}.
Alert fatigue הוא המצב בו צוות מקבל כל כך הרבה התראות שהם מתחילים להתעלם מהן. זה לא תיאורטי: 200 alerts ביום כאשר 95% הם noise מביא לכך שהצוות מוחה את ה-Slack alert-channel ב-DND, וה-critical alerts האמיתיים מוחמצים.
**Alertmanager מנגנוני ניהול fatigue**:
1. **Grouping**: מרכז alerts דומים לתוך notification אחת
2. **inhibit_rules**: מדכא alerts נגזרים כאשר alert ראשי כבר פועל
3. **group_wait** / **group_interval** / **repeat_interval**: שולט תזמון notifications
4. **Silences**: השתקה ידנית לחלון זמן (בזמן maintenance)
**מבנה alertmanager.yml**:
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/...'
route:
group_by: ['alertname', 'job']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'slack-low'
routes:
- match:
severity: critical
receiver: 'pagerduty'
repeat_interval: 1h
- match:
severity: warning
receiver: 'slack-warnings'
receivers:
- name: 'pagerduty'
pagerduty_configs:
- routing_key: '<pagerduty-key>'
- name: 'slack-warnings'
slack_configs:
- channel: '#alerts-warnings'
title: '{{ .GroupLabels.alertname }}'
text: '{{ .CommonAnnotations.summary }}'
- name: 'slack-low'
slack_configs:
- channel: '#alerts-info'
inhibit_rules:
- source_match:
alertname: 'InstanceDown'
target_match_re:
alertname: 'HighErrorRate|SlowP99Latency'
equal: ['instance']**inhibit_rules בפירוט**: כאשר InstanceDown פועל על instance X — כל alert אחר על אותו instance (HighErrorRate, SlowP99Latency) מושתק. בלי זה, כשל בשרת אחד מייצר 10+ alerts שכולם מגיעים ל-PagerDuty — alert fatigue קלאסי.
**repeat_interval**: כמה זמן להמתין לפני שולחים reminder על alert שממשיך. 4h לרוב ה-warnings זה סביר — אחרת Slack מתמלא כל שעה.
**group_wait**: כמה זמן לחכות לפני שולחים את ה-notification ראשונה, כדי לאגד alerts שמגיעים יחד (דוגמה: deployment שמוריד 5 שירותים — מגיע כ-notification אחת).
כל שירות systemd כותב logs ל-journald. ב-Ubuntu/Debian production servers, זה אומר שאם אתם מחפשים logs של nginx, api, postgres — הם ב-journal, לא בהכרח ב-/var/log/.
**אחסון journal**: ברירת המחדל היא volatile (נמחק ב-reboot). ל-persistent storage:
mkdir -p /var/log/journal
systemctl restart systemd-journald**journalctl flags חיוניים**:
# logs של unit ספציפי
journalctl -u nginx.service
# logs מ-timeframe ספציפי
journalctl -u nginx --since "2026-04-17 03:00:00" --until "2026-04-17 03:30:00"
# שורות אחרונות + follow
journalctl -u api -n 100 -f
# רק errors (PRIORITY <= 3: emerg, alert, crit, err)
journalctl -u nginx -p err
# PRIORITY levels: 0=emerg, 1=alert, 2=crit, 3=err, 4=warning, 5=notice, 6=info, 7=debug
journalctl -u nginx -p 4 # warning ומעלה (כלומר, severity 4 ומטה)
# JSON output לעיבוד
journalctl -u nginx -o json --since "1 hour ago" | jq '.MESSAGE'
# חיפוש לפי שדה מובנה
journalctl _SYSTEMD_UNIT=nginx.service _PID=1234
# גודל journal
journalctl --disk-usage
# ניקוי journal ישן
journalctl --vacuum-time=7d**JSON output fields**:
כאשר משתמשים ב--o json, כל שורה היא JSON עם fields כגון:
- MESSAGE — תוכן הלוג
- PRIORITY — מספר 0-7 (7=debug, 0=emerg)
- _SYSTEMD_UNIT — שם ה-unit
- _PID — process ID
- __REALTIME_TIMESTAMP — epoch microseconds
- SYSLOG_IDENTIFIER — שם התהליך
**Scenario ב-production**: שירות קרס ב-3 לפנות בוקר:
# מצא מתי בדיוק קרס
journalctl -u api --since "2026-04-17 02:50:00" --until "2026-04-17 03:10:00" -p err
# הבא JSON לעיבוד
journalctl -u api --since "2026-04-17 02:50:00" -o json | \
python3 -c "
import sys, json
for line in sys.stdin:
entry = json.loads(line)
if int(entry.get('PRIORITY', 7)) <= 3:
ts = entry.get('__REALTIME_TIMESTAMP', '')
msg = entry.get('MESSAGE', '')
print(f'[{ts}] {msg}')
"**journald + logrotate**: journald מנהל את ה-rotation שלו (SystemMaxUse ב-journald.conf). ל-log files שה-service כותב ל-/var/log/ בנפרד (לא דרך journald) — logrotate נדרש, כפי שלמדנו ב-Linux Advanced.
Grafana מחבר בין Prometheus לבין visibility. הגדרת data source בסיסית בממשק: Settings → Data sources → Add → Prometheus → URL: http://prometheus:9090.
בייצור, Grafana מוגדר as-code:
# grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus:9090
isDefault: true
editable: false
jsonData:
timeInterval: '15s'
httpMethod: POST**Dashboard Variables**: מאפשרים פאנלים דינמיים — בחר instance, job, או namespace מ-dropdown:
# Variable definition (type: query)
label_values(up{job="api"}, instance)אז בשאילתה: rate(http_requests_total{instance="$instance"}[5m])
**Repeating Panels**: פאנל שמשכפל את עצמו לכל ערך של variable — מאד שימושי לבנות service overview dashboard שמכסה כל ה-instances.
**Alert Rules ב-Grafana**: מ-Grafana 8+, ניתן להגדיר alert rules ישירות ב-Grafana (Unified Alerting). ב-production רוב הצוותים מעדיפים Prometheus-native alerts (שנשמרים ב-git), אבל Grafana alerts שימושיים לצוותים שעובדים יותר ב-UI.
**Dashboard as Code**: ייצוא dashboard ל-JSON ושמירה ב-git. grafana-dashboard-watcher / Terraform grafana provider מאפשרים GitOps לדashboards.
Metrics עונים על 'מה קורה'. Logs עונים על 'מה קרה'. Traces עונות על 'למה הבקשה הזו הייתה איטית / נכשלה'.
**מושגים בסיסיים**:
- **Trace**: ייצוג מלא של מסע בקשה אחת דרך המערכת (מ-client ועד DB ובחזרה)
- **Span**: יחידת עבודה אחת בתוך trace (call ל-service, query ל-DB). לכל span יש start_time, end_time, operation_name, ו-attributes
- **Trace ID**: מזהה ייחודי לכל trace (128-bit hex)
- **Span ID**: מזהה ייחודי לכל span (64-bit hex)
- **Parent Span ID**: קשר בין spans — יוצר עץ
**W3C traceparent header**: הסטנדרט לפרופגציה של trace context:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
| | | | |
| version trace-id | flags (01=sampled)
span-idכל service שמקבל בקשה HTTP חייב:
1. לקרוא את ה-traceparent header
2. ליצור span חדש עם parent_span_id = ה-span-id מה-header
3. להעביר traceparent header מעודכן לכל call שהוא שולח downstream
**OpenTelemetry (OTel)**: הסטנדרט המוביל ל-2026. SDK ל-Python, Node.js, Java, Go. מייצא לבחירה: Jaeger, Zipkin, Grafana Tempo, AWS X-Ray.
דוגמת instrumentation ב-Python:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
provider = TracerProvider()
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4317"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
@app.route('/api/users/<id>')
def get_user(id):
with tracer.start_as_current_span("get-user") as span:
span.set_attribute("user.id", id)
result = db.query(f"SELECT * FROM users WHERE id={id}")
span.set_attribute("db.rows_returned", len(result))
return jsonify(result)**Sampling**: ב-production, tracing 100% של בקשות זה יקר. Sampling strategies:
- **Head-based**: החלטה בתחילת trace (פשוטה, מחמיצה errors נדירים)
- **Tail-based**: החלטה בסוף trace — שומר 100% של errors, מדגם את ה-rest (מורכב יותר, OTel Collector נדרש)
**Grafana Tempo**: backend לאחסון traces. מתחבר ל-Grafana ומאפשר: מ-dashboard panel של Prometheus (high latency) → drill-down לexemplar → פתיחת trace ב-Tempo — הכל בממשק אחד.
CloudWatch הוא ה-built-in monitoring של AWS. ב-production, Prometheus + CloudWatch משלימים זה את זה: Prometheus לאפליקציה, CloudWatch ל-AWS infra (RDS, EKS nodes, Lambda).
**CloudWatch Alarms**:
- Alarm מוגדר על metric + threshold + period + evaluation_periods
- State: OK → ALARM → INSUFFICIENT_DATA
- Actions: SNS notification → Lambda / PagerDuty / Slack
דוגמה ב-AWS CLI:
aws cloudwatch put-metric-alarm \
--alarm-name "api-cpu-high" \
--alarm-description "API CPU above 80% for 5 minutes" \
--metric-name CPUUtilization \
--namespace AWS/ECS \
--dimensions Name=ServiceName,Value=api \
--period 300 \
--evaluation-periods 2 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--statistic Average \
--alarm-actions arn:aws:sns:us-east-1:123456789:alerts**CloudWatch Log Insights**: SQL-like query language לניתוח logs:
fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 50**Metric Filters**: יוצרים CloudWatch metric מ-log patterns:
aws logs put-metric-filter \
--log-group-name /aws/ecs/api \
--filter-name "error-count" \
--filter-pattern "[level=ERROR]" \
--metric-transformations \
metricName=ErrorCount,metricNamespace=Custom/API,metricValue=1,defaultValue=0**CloudWatch + Prometheus**: AWS Managed Prometheus (AMP) ו-AWS Managed Grafana (AMG) מאפשרים להריץ Prometheus/Grafana fully managed ב-AWS. ADOT (AWS Distro for OpenTelemetry) מאפשר לשלוח traces ל-AWS X-Ray.
**Cross-service integration**: ב-EKS, CloudWatch Container Insights + Prometheus Operator + Grafana Tempo יחד מייצרים observability stack מלא: metrics + logs + traces — מה שמכונה 'The Three Pillars of Observability'.
תרגול מעשי
1.
2.
3.
שאלות חיבור
חבר את מה שלמדת בנושא זה לנושאים קודמים. אין תשובה אחת נכונה — חשיבה ביקורתית היא המטרה.
שאלת חיבור 1
systemd (Linux Advanced, topic #1) ו-journald (Monitoring, topic #10) עובדים יחד. הסבר: (1) כיצד systemd service שכתבנו ב-Linux Advanced מייצר logs שנכנסים ל-journald, (2) כיצד לחלץ logs של שירות ספציפי עם journalctl מ-unit שהגדרנו, ו-(3) כיצד logrotate (Linux Advanced) ו-journald מנהלים retention בצורה שונה.
שאלת חיבור 2
Kubernetes (topic #9) מריץ Pods ב-cluster — כיצד Prometheus monitoring משתלב? הסבר: (1) כיצד Prometheus scrapes metrics מPods ב-Kubernetes (service discovery), (2) מה ה-annotations שמוסיפים ל-Pod/Service כדי שPrometheus יגלה אותם אוטומטית, ו-(3) כיצד alerting rule על p99 latency משתלב עם HPA (HorizontalPodAutoscaler) שלמדנו.
מוכן לבחינה?
בצע הערכה תיאורטית, תרגול CLI ושאלות חיבור כדי לסיים את הנושא.