K8s 実戦 #5 モニタリング・アラーム — Prometheus / CloudWatch / Alertmanager
K8s 実戦シリーズの 5 番目の記事です。#4 まで経て myshop-api は新しいバージョンが入ってくる流れまで自動化されましたが、運用段階の半分は その動作を観測すること です。CPU・メモリ・リクエスト latency・エラー率がどこでどう変わるかが見えなければ、カナリー自動 promote も不可能で事故対応も遅れます。この記事はオブザーバビリティスタックを EKS の上に載せる流れです。上級 #5 で扱った標準スタック(Prometheus + Grafana + Loki + Alertmanager)を EKS 環境に合わせて具体化し、AWS 管理型オプションである CloudWatch Container Insights との結合も一緒に見ます。
このシリーズは K8s 実戦 6 編です。
- #1 EKS クラスタセットアップ — Terraform / eksctl / IRSA / アドオン
- #2 アプリデプロイ骨格 — Deployment / Service / Ingress / Helm
- #3 DB 連動 — RDS / Secrets Manager / External Secrets / コネクションプール
- #4 CI/CD パイプライン — GitHub Actions / ECR / ArgoCD
- #5 モニタリング・アラーム — Prometheus / CloudWatch / Alertmanager ← この記事
- #6 運用チェックリスト — アップグレード / バックアップ・リカバリ / コスト / セキュリティ
2 軸の結合 — in-cluster Prometheus + 管理型 CloudWatch #
EKS 環境のオブザーバビリティは通常 2 軸の結合で行われます。
| 軸 | 責務 |
|---|---|
| In-cluster (Prometheus + Grafana + Loki) | ワークロードメトリクス、ビジネスメトリクス、アラーム、ダッシュボード |
| CloudWatch (Container Insights + Logs) | AWS 管理型メトリクス、ログ長期保管、AWS コンソール統合 |
どちらか一方だけ使う方式も可能ですが、運用クラスタの標準は両方の結合です。Prometheus が運用メトリクスとアラームの source of truth であり、CloudWatch が長期保管と AWS 自体のリソース(RDS、ALB、EBS)メトリクスの統合ポイントとなります。AWS の管理型 Prometheus(AMP)と管理型 Grafana(AMG)が in-cluster 運用負担を減らすオプションとして位置づけられつつありますが、この記事ではもっとも一般的な in-cluster モデルを中心に見ます。
kube-prometheus-stack — 一度に入る標準の束 #
上級 #5 で押さえた標準 Helm chart です。1 つのコマンドに Prometheus + Grafana + Alertmanager + kube-state-metrics + node-exporter + Prometheus Operator の CRD がすべて入ってきます。
インストール #
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/kube-prometheus-stack \
-n monitoring --create-namespace \
--values prometheus-values.yamlprometheus:
prometheusSpec:
retention: 30d
retentionSize: "50GB"
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
ruleSelectorNilUsesHelmValues: false
additionalScrapeConfigs:
- job_name: ec2-spot-instance
ec2_sd_configs:
- region: ap-northeast-2
remoteWrite:
- url: https://aps-workspaces.ap-northeast-2.amazonaws.com/workspaces/ws-xxx/api/v1/remote_write
sigv4:
region: ap-northeast-2
grafana:
adminPassword: ""
ingress:
enabled: true
ingressClassName: alb
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:...
hosts:
- grafana.myshop.example.com
persistence:
enabled: true
storageClassName: gp3
size: 10Gi
alertmanager:
alertmanagerSpec:
storage:
volumeClaimTemplate:
spec:
storageClassName: gp3
resources:
requests:
storage: 10Gi
config:
route:
receiver: default
receivers:
- name: default主要設定をいくつか押さえます。
retention: 30d+storageSpec— メトリクス 30 日保管 + EBS 100GB。30 日以上保管するにはremoteWriteで AMP や Thanos に一緒に送る。serviceMonitorSelectorNilUsesHelmValues: false— すべての namespace の ServiceMonitor を自動認識。myshop namespace の ServiceMonitor が monitoring namespace なしでも動作。remoteWrite— AWS Managed Prometheus(AMP)にメトリクス long-term 保存。30 日以上の分析が必要なケース。
インストール直後の点検 #
kubectl get pods -n monitoring
kubectl get servicemonitors -A
kubectl get prometheusrules -ANAME READY STATUS RESTARTS
prometheus-grafana-xxx 3/3 Running 0
prometheus-kube-prometheus-operator-xxx 1/1 Running 0
prometheus-kube-state-metrics-xxx 1/1 Running 0
prometheus-prometheus-kube-prometheus-prometheus-0 2/2 Running 0
prometheus-prometheus-node-exporter-xxx 1/1 Running 0
alertmanager-prometheus-kube-prometheus-alertmanager-0 2/2 Running 0基本 PrometheusRule が約 100 個自動で入ってきます。ノードダウン、etcd 障害、kubelet 問題のような K8s 自体のアラームがその中に事前定義されているので、クラスタ事故は別途作業なしですぐにアラームになります。
myshop-api にメトリクス公開を追加 #
標準スタックがインストールされたクラスタに myshop-api のメトリクスを公開する 1 サイクルです。
1. アプリケーションが /metrics を公開 #
Prometheus クライアントライブラリがほぼすべての言語にあります。Python(FastAPI)なら次の 1 行で始めます。
from fastapi import FastAPI
from prometheus_fastapi_instrumentator import Instrumentator
app = FastAPI()
Instrumentator().instrument(app).expose(app)この 1 行が次のメトリクスを自動で公開します。
http_requests_total{handler, method, status}— リクエストカウンターhttp_request_duration_seconds_bucket{handler, method}— latency ヒストグラムhttp_request_size_bytes/http_response_size_bytes— ペイロードサイズ- 標準 Python runtime メトリクス(GC、threads、memory)
ドメインメトリクス(例: 注文生成カウンター、決済成功率)はその上に追加します。
from prometheus_client import Counter, Histogram
orders_created = Counter(
"myshop_orders_created_total",
"Total orders created",
["status"]
)
checkout_duration = Histogram(
"myshop_checkout_duration_seconds",
"Checkout flow duration"
)2. ServiceMonitor マニフェスト #
Prometheus Operator が watch する ServiceMonitor を作ります。
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "myshop-api.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: myshop-api
spec:
selector:
matchLabels:
app.kubernetes.io/name: myshop-api
endpoints:
- port: http
interval: 30s
path: /metricsこのマニフェストが適用された瞬間から Prometheus が 30 秒ごとに myshop-api のすべての Pod の /metrics を集め始めます。Grafana の Explore で http_requests_total{namespace="myshop"} でデータが見えればメトリクス収集が正常です。
4 golden signals — アラームルールセットの骨格 #
上級 #5 で扱った 4 golden signals(Latency / Traffic / Errors / Saturation)を myshop-api の PrometheusRule で書きます。
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: {{ include "myshop-api.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
release: prometheus
spec:
groups:
- name: myshop-api.golden-signals
interval: 30s
rules:
# Errors — 5xx 比率
- alert: MyshopApiHighErrorRate
expr: |
sum(rate(http_requests_total{app="myshop-api",status=~"5.."}[5m]))
/ sum(rate(http_requests_total{app="myshop-api"}[5m])) > 0.05
for: 5m
labels:
severity: critical
team: backend
annotations:
summary: "myshop-api 5xx rate > 5% ({{ "{{ $value | humanizePercentage }}" }})"
description: "5xx 比率が 5% を超えた状態が 5 分以上維持された。"
runbook_url: "https://runbooks.myshop.example.com/myshop-api-5xx"
# Latency — P95
- alert: MyshopApiHighLatencyP95
expr: |
histogram_quantile(0.95,
sum by (le) (rate(http_request_duration_seconds_bucket{app="myshop-api"}[5m]))
) > 1.0
for: 10m
labels:
severity: warning
team: backend
annotations:
summary: "myshop-api P95 latency > 1s ({{ "{{ $value | printf \"%.2f\" }}" }}s)"
# Traffic — トラフィック急減 (downstream 障害シグナル)
- alert: MyshopApiTrafficDrop
expr: |
sum(rate(http_requests_total{app="myshop-api"}[5m]))
< 0.3 * sum(rate(http_requests_total{app="myshop-api"}[5m] offset 1h))
for: 10m
labels:
severity: warning
team: backend
annotations:
summary: "myshop-api トラフィック急減 (過去 1 時間比 30% 未満)"
# Saturation — Pod メモリ使用率
- alert: MyshopApiPodMemoryHigh
expr: |
sum by (pod) (
container_memory_working_set_bytes{namespace="myshop",pod=~"myshop-api-.*"}
) / sum by (pod) (
kube_pod_container_resource_limits{namespace="myshop",pod=~"myshop-api-.*",resource="memory"}
) > 0.85
for: 10m
labels:
severity: warning
team: backend
annotations:
summary: "myshop-api Pod memory > 85% of limit"各ルールの重要なパターンを 3 つ押さえます。
for期間 — 短いスパイクにアラームが鳴らないように 5~10 分の持続時間要求。severityラベル —criticalは即時呼び出し、warningは次の営業日に検討。Alertmanager ルーティングのキー。runbook_url— アラームを受けた人が即時に追える対応手順ドキュメントです。アラーム 1 件 = 明確な対応 1 つという原則に従います。
Alertmanager ルーティング — Slack と PagerDuty の分岐 #
アラームの流れは Prometheus → Alertmanager → チャンネルです。Alertmanager がラベルを見てルーティングを決定します。
route:
receiver: default
group_by: ['alertname', 'team', 'namespace']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- matchers:
- severity = "critical"
receiver: pagerduty-backend
continue: true
routes:
- matchers:
- severity = "critical"
- team = "backend"
receiver: pagerduty-backend
- matchers:
- severity = "critical"
- team = "platform"
receiver: pagerduty-platform
- matchers:
- severity = "warning"
receiver: slack-warnings
group_wait: 1m
repeat_interval: 12h
receivers:
- name: default
slack_configs:
- api_url: ${SLACK_WEBHOOK}
channel: '#alerts'
- name: slack-warnings
slack_configs:
- api_url: ${SLACK_WEBHOOK}
channel: '#alerts-warning'
title: '⚠️ {{ "{{ .GroupLabels.alertname }}" }}'
- name: pagerduty-backend
pagerduty_configs:
- service_key: ${PAGERDUTY_BACKEND_KEY}
- name: pagerduty-platform
pagerduty_configs:
- service_key: ${PAGERDUTY_PLATFORM_KEY}
inhibit_rules:
- source_matchers: [severity = "critical"]
target_matchers: [severity = "warning"]
equal: [alertname, namespace]主なパターンが 3 つあります。
- severity 別分岐 — critical は PagerDuty で呼び出し、warning は Slack で通知。
- team 別分岐 — 同じ critical でも backend / platform チームを別々に呼び出し。
inhibit_rules— 同じ alertname の critical が鳴っている間は同じ namespace の warning を束ねて無音処理。アラーム氾濫防止。
秘密(SLACK_WEBHOOK、PAGERDUTY_*)は #3 で扱った External Secrets で注入します。
Loki — ログスタック追加 #
メトリクス以外にログも一緒に確保することが標準です。上級 #5 で見た Loki スタックをそのまま適用します。
helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki-stack \
-n monitoring \
--set promtail.enabled=true \
--set loki.persistence.enabled=true \
--set loki.persistence.storageClassName=gp3 \
--set loki.persistence.size=100Giインストール後 Grafana に Loki データソースが自動で追加され、Explore で LogQL クエリが可能になります。
{namespace="myshop", app="myshop-api"} |= "ERROR"sum(rate({namespace="myshop", app="myshop-api"} |= "ERROR" [5m]))長期保管を S3 に置くには Loki の storage backend を S3 に設定します。標準運用セットアップです。
CloudWatch Container Insights — 2 つ目の軸 #
同じ EKS クラスタに CloudWatch Container Insights を一緒にインストールすれば AWS コンソールでクラスタ・ノード・Pod・コンテナメトリクスを即座に確認できます。運用チームが AWS コンソールに慣れていれば日常点検負担が減ります。
helm repo add aws-observability https://aws-observability.github.io/helm-charts
helm install amazon-cloudwatch-observability \
aws-observability/amazon-cloudwatch-observability \
-n amazon-cloudwatch --create-namespace \
--set clusterName=myshop-prod \
--set region=ap-northeast-2この chart が Fluent Bit を DaemonSet として立てて各ノードの stdout/stderr を CloudWatch Logs に送り、CloudWatch Agent でメトリクスも一緒に収集します。
Fluent Bit の役割 #
Fluent Bit がノードの /var/log/containers/ を読んで次の 2 か所にルーティングするのが標準です。
コンテナログ
│
├─→ Loki (in-cluster、短期検索)
└─→ CloudWatch Logs (S3 export、長期保管)同じログを 2 か所に送る理由は責務が違うからです — Loki は日常デバッグ、CloudWatch はコンプライアンス・監査・長期分析。コスト面では Loki だけ使う方式がより軽いですが、規制環境では CloudWatch が通常一緒に入ります。
Grafana ダッシュボード標準 #
運用クラスタの Grafana に入る標準ダッシュボードセットを整理します。
| ダッシュボード | source |
|---|---|
| Kubernetes / Compute Resources / Cluster | kube-prometheus-stack 標準 (ID 7249) |
| Kubernetes / Compute Resources / Namespace (Workloads) | 標準 (ID 7250) |
| Kubernetes / Compute Resources / Pod | 標準 (ID 7251) |
| Kubernetes / Networking / Cluster | 標準 (ID 7253) |
| Node Exporter / Nodes | 標準 (ID 1860) |
| myshop-api 運用ダッシュボード | 自作 — golden signals + ビジネスメトリクス |
標準ダッシュボード 5 つは kube-prometheus-stack が自動で登録してくれるので別途作業なしですぐに見えます。自作ダッシュボード 1 つだけドメインに合わせて作れば日常点検の視野がほぼ完成します。
自作ダッシュボードの標準パネルセット #
Row 1: Latency P50 / P95 / P99
Row 2: Request rate (ドメイン別、status 別)
Row 3: Error rate (4xx / 5xx)
Row 4: Pod CPU / メモリ使用率
Row 5: HPA 現在 replicas
Row 6: PgBouncer 活性接続 / 待機キュー
Row 7: ビジネスメトリクス (orders/min、checkout success rate)
Row 8: 上位 ERROR ログ (Loki)
Row 9: 最近のデプロイ (annotations)最後のパネルの「最近のデプロイ annotations」は Grafana に ArgoCD または GitHub Actions イベントを annotation として注入したものです。メトリクスグラフの上にデプロイ時点が縦線で表示されて、「この latency スパイクはどのデプロイ直後に発生したか」を一目で見られます。
on-call 流れ — runbook と一緒に #
アラームが鳴ること自体が終わりではありません。受けた人が 5 分以内にどこを見るべきかが明確でなければなりません。次が運用の標準的な流れです。
1. PagerDuty でアラーム本文を確認 (alertname、team、severity)
2. annotation の runbook_url をクリック
3. Runbook の「1 次点検」セクションを追う — 関連 Grafana ダッシュボード / ログクエリ / kubectl コマンド提示
4. 1 次対応 (スケールアップ、再起動、トラフィック遮断など)
5. Slack 事故チャンネルに状態共有Runbook は別途の git repo の markdown で管理するのが標準です。アラームルールの runbook_url がその repo の 1 ページを指し、新しいアラームを追加するときに Runbook も一緒に PR で入ってきます。
最初の運用 1 サイクル後の点検 #
スタックをインストールして数日回した時点で点検する項目です。
kubectl exec -n monitoring prometheus-prometheus-kube-prometheus-prometheus-0 -c prometheus -- \
promtool tsdb analyze /prometheus/wal | head -50kubectl exec -n monitoring alertmanager-prometheus-kube-prometheus-alertmanager-0 -c alertmanager -- \
amtool alert query --alertmanager.url=http://localhost:9093kubectl exec -n monitoring loki-0 -- df -h /data運用 1 か月が過ぎるとカーディナリティ急増、アラーム SNR 低下、ログディスク圧迫が通常一度ずつ来ます。この点検を定期点検として置くのが標準です。
締めくくり #
myshop-api の上にオブザーバビリティスタックを載せる 1 サイクルを追いました。kube-prometheus-stack で Prometheus + Grafana + Alertmanager を一度にインストールし、ServiceMonitor + PrometheusRule で myshop-api の 4 golden signals アラームを標準化し、Alertmanager ルーティングで severity・team 別 Slack / PagerDuty 分岐まで押さえました。Loki と CloudWatch の 2 軸を一緒に置き、runbook_url でアラームと対応手順を組み合わせる運用パターンまで押さえました。この時点で myshop-api はコードからデプロイ・運用・観測まで 1 サイクルがすべて自動化された状態です。次の記事でありシリーズの最後の記事ではこのクラスタを 1 か月、四半期、1 年単位で安全に回す定期運用サイクルを扱います — EKS アップグレード、RDS バックアップ・リカバリ、コスト点検、セキュリティ点検のチェックリストと K8s 実戦 6 編の振り返りまでが最後の記事の範囲です。