📊

Grafana/Prometheus で Kubernetes のリソースモニタリングをしてみる

に公開

Kind を使って簡単なリソースモニタリング環境を構築します。

前提

  • Docker がインストールされていること
  • Kind がインストールされていること
  • Helm 3 がインストールされていること

セットアップ手順

まずは、Kind でコントロールプレーンのみのシンプルなクラスタを作成します。

30000, 30001, 30002 に関してホストのポートと NodePort をマッピングさせることでホストの環境から Grafana にアクセスできるようにします。
NodePort を使うことで、Kind のコンテナ外部(ホストマシン)から直接サービスにアクセスできるようになります。

cat <<EOF | kind create cluster --config -
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: monitoring-cluster
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30000
        hostPort: 30000
        protocol: TCP
      - containerPort: 30001
        hostPort: 30001
        protocol: TCP
      - containerPort: 30002
        hostPort: 30002
        protocol: TCP
EOF

次に HPA によるスケーリングを可能にするために metrics-server をデプロイします。

kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: metrics-server
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: metrics-server
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: metrics-server
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: metrics-server
  template:
    metadata:
      labels:
        app: metrics-server
    spec:
      serviceAccountName: metrics-server
      containers:
      - name: metrics-server
        image: registry.k8s.io/metrics-server/metrics-server:v0.7.0
        args:
        - --cert-dir=/tmp
        - --secure-port=10250
        - --kubelet-insecure-tls
        - --kubelet-preferred-address-types=InternalIP
        ports:
        - containerPort: 10250
          name: https
        volumeMounts:
        - name: tmp-dir
          mountPath: /tmp
      volumes:
      - name: tmp-dir
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: metrics-server
  namespace: kube-system
spec:
  ports:
  - port: 443
    targetPort: https
  selector:
    app: metrics-server
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.metrics.k8s.io
spec:
  service:
    name: metrics-server
    namespace: kube-system
  group: metrics.k8s.io
  version: v1beta1
  groupPriorityMinimum: 100
  versionPriority: 100
  insecureSkipTLSVerify: true
EOF

起動するまで待つ

kubectl wait --for=condition=ready pod -l app=metrics-server -n kube-system --timeout=60s

次に Prometheus Stack をインストールします。

各サービスは先ほど設定した NodePort に対応付けます。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm install prometheus-stack prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set prometheus.service.type=NodePort \
  --set prometheus.service.nodePort=30000 \
  --set grafana.service.type=NodePort \
  --set grafana.service.nodePort=30001 \
  --set alertmanager.service.type=NodePort \
  --set alertmanager.service.nodePort=30002 \
  --set grafana.adminPassword=admin

インストール確認をしてください

kubectl get pods -n monitoring

# すべての Pod が Running になるまで待機
kubectl wait --namespace monitoring \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/name=grafana \
  --timeout=300s

Pod が立ち上がると各サービスには以下の URL からアクセスできるようになります。

サービス URL ユーザー名 パスワード
Grafana http://localhost:30001 admin admin
Prometheus http://localhost:30000 - -
AlertManager http://localhost:30002 - -

kube-prometheus-stack には以下のダッシュボードがプリインストールされています。

  • Kubernetes / Compute Resources / Cluster - クラスタ全体のリソース使用状況
  • Kubernetes / Compute Resources / Namespace (Pods) - Namespace ごとの Pod のリソース使用状況
  • Kubernetes / Compute Resources / Pod - 個別 Pod の詳細メトリクス
  • Node Exporter / Nodes - ノードレベルのメトリクス

監視対象のサンプルアプリをデプロイします

# サンプル Deployment の作成
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        resources:
          requests:
            memory: "64Mi"
            cpu: "50m"
          limits:
            memory: "128Mi"
            cpu: "100m"
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: sample-app
  namespace: default
spec:
  selector:
    app: sample-app
  ports:
  - port: 80
    targetPort: 80
EOF

# HPA (Horizontal Pod Autoscaler) の設定
kubectl autoscale deployment sample-app --cpu-percent=50 --min=1 --max=10

ダッシュボード構築

Prometheus クエリについて

http://localhost:30000 にアクセスすることで Prometheus ダッシュボードにアクセスできます。

検索窓に入力することでどんなメトリクスがあるかどうか補完でみることができます。

メトリクスの探し方のコツとしてはよく使うメトリクスの接頭辞を覚えることです。

接頭辞 説明
container_ コンテナレベルのメトリクス
kube_ Kubernetes オブジェクトの状態
node_ ノードレベルのメトリクス

まずは、特定の Deployment の Pod 単位の CPU 使用量及び メモリの使用量、Deployment のレプリカ数に関してどんなクエリで取得できるかを考えていきます。

Pod 単位の CPU 使用量

container_cpu_usage_seconds_total というメトリクスを利用して算出します。
container_cpu_usage_seconds_total累積値(Counter型) で、以下のような意味を持つメトリクスです。

  時刻 10:00:00 → 値: 100秒
  時刻 10:05:00 → 値: 115秒
  → 5分間で 15秒分 CPU を使用した

CPU 使用率を計算するには単位時間あたりにどれくらいの CPU 使用量が増えるかどうか分かり、それの計算に使えるのが rate() 関数です。
例えば、rate(container_cpu_usage_seconds_total[N]) というクエリは直近 N 秒間の CPU 使用量の平均を表します。

# rate(container_cpu_usage_seconds_total[5m]): 5分間の平均

rate = (115秒 - 100秒) / (5分 × 60秒/分) = 15秒 / 300秒 = 0.05

以上を元に rate(container_cpu_usage_seconds_total[1m]) を Prometheus UI 上で実行すると大量のメトリクスが出力されるのでさらに絞り込む必要があります。
Promethues においてはメトリクスの後に {} でフィールドを指定することでメトリクスを絞り込むことができます。
sample-app という Deployment の Pod のコンテナのみをフィルターしたいので例えば以下のように絞り込むことができます。

rate(container_cpu_usage_seconds_total{namespace="default", pod=~"sample-app-.*", container!=""}[1m])

container!="" は pause コンテナなどの補助的なコンテナを除外し、実際のアプリケーションコンテナのみを対象にするための条件です。

最終的にこれを 100 倍することで Pod が使用している CPU のコア数をメトリクスとして取得することができます。

Pod 単位の RAM 使用量

コンテナのメモリ使用量は container_memory_working_set_bytes で取得できます。

Pod 単位でのメモリ使用量を取得するには以下のクエリを実行すれば良いです。

sum(container_memory_working_set_bytes{namespace="default", pod=~"sample-app-.*", container!=""}) by (pod) /1024/1024

Deployment のレプリカ数

Deployment のレプリカ数 status.replicas は以下のクエリで確認できます。

kube_deployment_status_replicas{namespace="default"}

Grafana ダッシュボード作成

取得方法を確認した3つのメトリクスをダッシュボード化します

まずは Grafana を開いて左のタブから「Dashboard」を選択します。

「New」->「New Dashboard」->「Save Dashboard」と進んでまずは空のダッシュボードを保存しましょう。

続いて「Add Visualization」を選択し、データソースの指定が出るので Prometheus を選択してグラフを追加していきます。

まずは CPU の使用コア数からいきます。
以下のように入力して終わったら「Save」してください。

  • Title: CPU 使用量
  • Query: rate(container_cpu_usage_seconds_total{namespace="default", pod=~"sample-app-.*", container!=""}[1m])
  • Legend: {{pod}}

「My First Dashboard」に戻って次にメモリ使用量も同様に入力してください。

デプロイメントの数に関しても同様です。

ダッシュボードのページに戻ると以下のようなダッシュボードが表示されています

Pod に負荷をかけてスケールの様子を観測してみる

現在の HPA の様子を確認すると対象の Pod の平均CPU使用率が 50% を切っているので最低稼働数である 1 にスケールインしていることが分かります。

❯ kubectl get hpa
NAME         REFERENCE               TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
sample-app   Deployment/sample-app   cpu: 0%/50%   1         10        1          145m

ダッシュボードを見ても現在のPod数が 1、メモリ使用量は 10MB 程度、CPU 使用量はほぼ 0 であることが分かります。

クラスタ内に別の Pod をデプロイしてApache Bench (ab)を使って負荷をかけてみます。
この動作により、CPU 使用率が最大の 100m まで急激に上昇し HPA のスケールが発動して Pod の数が増えると予想されます。

cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: ab-extreme-load
spec:
  containers:
  - name: ab-load
    image: httpd:alpine
    resources:
      requests:
        cpu: "500m"
        memory: "512Mi"
    command: ["/bin/sh"]
    args:
    - -c
    - |
      START_TIME=$(date +%s)
      END_TIME=$((START_TIME + 600))
      echo "Starting EXTREME 10 minute load test with 10 parallel processes..."
      echo "Start time: $START_TIME"
      echo "End time: $END_TIME"

      # 10個の並列abプロセスを起動
      for i in $(seq 1 10); do
        (
          echo "Starting worker $i"
          while [ $(date +%s) -lt $END_TIME ]; do
            CURRENT_TIME=$(date +%s)
            ELAPSED=$((CURRENT_TIME - START_TIME))
            echo "[Worker $i] Elapsed: ${ELAPSED}s - Sending 5000 requests with 500 concurrent connections"
            ab -n 5000 -c 500 -s 30 -r http://sample-app.default.svc.cluster.local/ 2>&1 | grep -E "Requests per second|Time per request|Failed"
          done
          echo "Worker $i completed"
        ) &
      done

      # 全プロセスの終了を待つ
      wait
      echo "All workers completed after 10 minutes"
      sleep 3600
EOF

検証の途中で最大 Pod 数を 30 台まで増やしました

kubectl patch hpa sample-app --patch '{"spec":{"maxReplicas":30}}'

負荷をかけ終わって数分経ったのちに確認したダッシュボードの様子です。

最大 18 台まで Pod の台数がスケールしていることが分かります。

HPA は CPU の使用率に対して設定されており、resources.requests.cpu の値を基準に割合を出すので絶対で言うと 0.025 (25m) になるようにワーカーの台数が K8s の機能で調整されます。

おわりに

本記事では Kind を使用して Prometheus と Grafana によるモニタリング環境を構築し、実際に負荷をかけて HPA によるオートスケーリングの様子を可視化しました。

体験できたこと

オブザーバビリティの実践

  • リアルタイムな可視化: CPU やメモリの使用状況、Pod 数の変化をリアルタイムで確認できることで、システムの挙動を直感的に理解できました
  • トラブルシューティング: メトリクスを見ることで、なぜ Pod がスケールしたのか、どの程度のリソースを消費しているのかが明確になりました
  • パフォーマンス分析: 負荷テスト時の応答性能や、リソース使用効率を数値で把握できました

Prometheus/Grafana の基礎

  • PromQL の実践: rate()sum() などの関数を使った実用的なクエリの書き方を学びました
  • ダッシュボード作成: 必要なメトリクスを組み合わせて意味のある可視化を作る方法を習得しました
  • メトリクスの理解: container_kube_node_ などの接頭辞から適切なメトリクスを探す方法を理解しました

クリーンアップ

検証が終わったら、以下のコマンドで環境を削除しましょう

kind delete cluster --name monitoring-cluster

モニタリングはシステム運用において不可欠な要素です。今回のハンズオンを通じて、実際にメトリクスが変化する様子を目で見て、オブザーバビリティの重要性を実感していただけたなら幸いです!

GitHubで編集を提案

Discussion