↓メインコンテンツへスキップ
  1. Blogs/

GKEでPodの退避を起こしたら、監視に必要なメトリクスがCloud Monitoringに来なかった

8 分
Terraform GoogleCloud GKE Kubernetes CloudLogging CloudMonitoring
0222-nnn
著者
0222-nnn
猫が好き
目次
Terraform-GoogleCloud - この記事は連載の一部です
パート 41: この記事

概要
#

ノードのメモリやディスクが逼迫すると、kubelet が Pod を退避(Eviction)します。退避された Pod は Failed として API 上に残り、PodGC が片付けるまで一覧に並び続けます。

監視していなければ、原因の分からない Failed Pod が増えていくだけです。

そこで退避を監視しようとして、最初につまずきました。kube_pod_status_reason{reason="Evicted"} を Cloud Monitoring で引いても、何も返ってきません。

これは kube-state-metrics が出すメトリクスで、Pod の status.reason をそのままラベルにしたものです。退避された Pod には reason="Evicted" のラベルが付きます。

Gauge なので、sum() で分かるのは現在 API 上に残っている Evicted の Pod 数です。累積の退避回数ではありませんが、退避を検知するアラートなら、まず候補に挙がります。

sum(kube_pod_status_reason{reason="Evicted"}) > 0

収集そのものは動いていました。Managed Service for Prometheus(GMP)は有効で、up{job="kube-state-metrics"} は 1 を返します。それでも、退避を数えるためのメトリクスだけが Cloud Monitoring に入っていません。

この状態でどこを見れば退避に気づけるのか。GKE で実際に退避を起こして確かめました。

対象読者
#

  • GKE でワークロードを動かしたことがある
  • kubectl describe で Pod の状態を読める

扱わないもの
#

  • Kubernetes と GKE の入門
  • Pod Disruption Budget、kubectl drain による退去(今回はノード逼迫による退避)
  • HorizontalPodAutoscaler、Cluster Autoscaler

退避が起きたときに、何をどこで観測できるかに絞ります。

検証すること
#

  • GKE の退避しきい値の既定値はいくつなのか
  • どの Pod から退避されるか
  • GMP で退避を検知できるか。コンポーネントを増やせば取れるようになるか
  • Cloud Logging には何が残るか

前提環境
#

  • Google Cloud CLI、Terraform
  • Billing が有効な検証用 Google Cloud Project
  • 実行元のグローバル IPv4
  • 検証時のバージョン:Terraform 1.14.3、google 7.x、GKE v1.35.7-gke.1027000

使用するTerraformコード
#

退避を追える経路は3つある
#

kubelet が退避を決めたあと、その事実は3つの経路に分かれて流れます。経路ごとに、届くものと届かないものが違います。

経路 見るコマンド 何がわかるか
kubelet の Event と /metrics kubectl get events いつ・何が・なぜ退避されたか
Cloud Logging gcloud logging read 同上。クラスタを消したあとも残る
Cloud Monitoring PromQL / Monitoring API v3 数として集計・アラートできるか
flowchart LR
    K["kubelet"] -->|"退避を判断"| POD["Pod を Evict"]
    K -->|"Event"| API["kube-apiserver"]
    API -->|"収集"| KSM["kube-state-metrics"]
    API -->|"転送"| LOG["Cloud Logging"]
    K -->|"/metrics"| KM["kubelet メトリクス"]
    KM -->|"GMP が収集"| MON["Cloud Monitoring"]
    KSM -->|"GMP が収集"| MON
    K -->|"ノードの実測値"| GCP["kubernetes.io/* メトリクス"]
    GCP --> MON

アラートを組むなら Cloud Monitoring の経路が必要です。以降は、実際に退避を起こしてから、この3経路を順に見ていきます。

検証環境の構成
#

ノード1台のゾーンクラスタに、QoS クラスの違う Pod を並べます。そこへ、メモリを大量に使う Pod とディスクを埋める Pod を追加で起動します。

flowchart TB
    subgraph NODE["ノード e2-standard-2 / allocatable 6170292Ki"]
        BE["besteffort
BestEffort
requests なし"] BU["burstable
Burstable
requests 64Mi"] GU["guaranteed
Guaranteed
requests = limits 128Mi"] HOG["hog
Burstable
requests 64Mi / stress 6000M"] DF["diskfill
BestEffort
emptyDir に 20GB"] end HOG -.->|"MemoryPressure"| NODE DF -.->|"DiskPressure"| NODE

退避を起こす手順
#

ここから先の出力は、すべて次の手順で再現できます。

terraform apply
eval "$(terraform output -raw get_credentials)"

kubectl apply -f k8s/01-qos.yaml          # QoS クラス3種を並べる

ここで GMP の収集 Pod が揃うのを待ちます。 揃う前に退避させると、肝心の時間帯のメトリクスが欠けます。

kubectl -n gmp-system get pods          # collector が 2/2 Running
kubectl -n gke-managed-cim get pods     # kube-state-metrics-0 が 2/2 Running

揃ってから負荷をかけます。

kubectl apply -f k8s/02-memory-hog.yaml   # MemoryPressure を起こす
kubectl apply -f k8s/03-disk-fill.yaml    # DiskPressure を起こす

観測に使うノード名を変数に入れておきます。

NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')

観測データの収集は scripts/collect.sh にまとめています。クラスタを destroy する前に実行してください。 kubectl の Event と kubelet の /metrics は、クラスタを消すと取れなくなります。

一方、Cloud Logging と Cloud Monitoring に入ったデータは保持期間内なら残ります。この記事の status_condition と evictable の推移は、destroy 後に引き直したものです。

kubectlで退避を確認する
#

既定のしきい値は memory.available 100Mi
#

kubelet の実効設定は configz から読めます。

kubectl get --raw "/api/v1/nodes/${NODE}/proxy/configz" | python3 -m json.tool
"evictionHard": {
  "memory.available": "100Mi",
  "nodefs.available": "10%",
  "nodefs.inodesFree": "5%",
  "pid.available": "10%"
},
"evictionPressureTransitionPeriod": "5m0s",
"kubeReserved": {
  "cpu": "70m",
  "ephemeral-storage": "15Gi",
  "memory": "1819Mi"
}

memory.available が 100Mi を下回るとハードしきい値に達します。余裕はほとんどありません。

しきい値に達しても、いきなり Pod が消えるわけではありません。kubelet は先にノードレベルのリソース回収を試みます。使っていないコンテナイメージの削除などで、それでも足りないときに退避へ進みます。

見落としやすいのが ephemeral-storage: 15Gi の予約です。30GB のディスクを付けても、Pod が使えるのは10GB程度になります。

Eventに書かれているのはrequestsとの比較
#

退避の Event だけを絞り込みます。

kubectl -n evict-test get events --field-selector reason=Evicted \
  -o custom-columns='TIME:.firstTimestamp,POD:.involvedObject.name,MSG:.message'
TIME                  POD          MSG
2026-09-06T09:38:19Z  besteffort   The node was low on resource: memory.
                                   Threshold quantity: 100Mi, available: 17348Ki.
                                   Container app was using 188Ki, request is 0,
                                   has larger consumption of memory.
2026-09-06T09:45:23Z  hog          The node was low on resource: ephemeral-storage.
                                   Threshold quantity: 2722654248, available: 1600496Ki.
2026-09-06T09:45:42Z  diskfill     The node was low on resource: ephemeral-storage.
                                   Threshold quantity: 2722654248, available: 371692Ki.
                                   Container fill was using 40Ki, request is 0,
                                   has larger consumption of ephemeral-storage.

diskfill の 40Ki は Pod 全体の使用量ではありません。 メッセージが載せるのはコンテナの書き込み層とログの合計で、20GB を書いた emptyDir は含まれません。順位付けにはローカルボリュームも入ります。

Event には、しきい値・その時点の空き・対象コンテナの使用量・requests が記録されています。一方で、QoS クラスは出てきません。

公式ドキュメントは、退避順序を3つの基準で決めると書いています。

  1. requests を超えているか
  2. Pod Priority
  3. requests に対する超過量

「kubelet は退避順序の決定に QoS クラスを使わない」と明記されています。 QoS クラスは requests と limits の書き方から決まる分類で、順位付けの入力ではありません。

つまり requests が 0 の BestEffort は、188Ki 使っただけで基準1に引っかかります。結果として BestEffort が先に選ばれやすくなるだけで、QoS を見ているわけではありません。

ディスク逼迫でも3段階の形は同じです。rankDiskPressureFunc は orderedBy(exceedDiskRequests, priority, disk) で並べます。

変わるのは何を使用量として数えるかです。imagefs や containerfs を分けていなければ、ローカルボリューム・ログ・書き込み層を合わせた値になります。

ただし QoS クラスによる説明は当てはまりません。

QoS classification does not apply to EphemeralStorage requests, so the above scenario will not apply if the node is, for example, under DiskPressure.

QoS クラスは CPU とメモリの requests / limits から決まるので、ephemeral-storage の超過とは対応しません。

Pod 側の状態も見ておきます。

kubectl -n evict-test get pods \
  -o custom-columns='NAME:.metadata.name,QOS:.status.qosClass,STATUS:.status.phase,REASON:.status.reason'
NAME         QOS          STATUS    REASON
besteffort   BestEffort   Failed    Evicted
burstable    Burstable    Running   <none>
diskfill     BestEffort   Failed    Evicted
guaranteed   Guaranteed   Running   <none>
hog          Burstable    Failed    Evicted

生き残ったのは burstable と guaranteed でした。 どちらも requests を設定しており、実使用がそれを下回っています。

DiskPressureは208秒で出た
#

emptyDir は nodefs 上にあります。書いた分だけ nodefs.available が減ります。

kubectl get node "$NODE" \
  -o jsonpath='{range .status.conditions[?(@.type=="DiskPressure")]}{.type}={.status} ({.reason}){"\n"}{end}'
09:42:07  diskfill 投入(20GB 書き込み)
09:45:35  DiskPressure=True (KubeletHasDiskPressure)

空きが戻っても DiskPressure=True は続きます。evictionPressureTransitionPeriod が5分に設定されているためです。今回は10分後に False へ戻り、そのとき nodefs.available は 17.95GiB(70.8%)まで回復していました。

kubelet は退避をカウントしている
#

ノードの /metrics には、退避の回数がそのまま出ています。

kubectl get --raw "/api/v1/nodes/${NODE}/proxy/metrics" \
  | grep -E '^# TYPE kubelet_evictions |^kubelet_evictions'
# TYPE kubelet_evictions counter
kubelet_evictions{eviction_signal="allocatableMemory.available"} 2
kubelet_evictions{eviction_signal="nodefs.available"} 2

合計は4回で、Cloud Logging の件数とも一致します。一方、上で載せた Event は3件でした。

ただし集計の範囲が揃っていません。 Event は evict-test 名前空間だけ、カウンタはノード全体の累積、ログはクラスタを絞らない直近2時間の検索です。差の原因は特定できていません。

揃えるなら、負荷投入の前後でカウンタの差分を取ります。そのうえで、全名前空間の Event と同じ期間のログを Pod の UID で突き合わせます。

今回取得した /metrics では、メトリクス名は kubelet_evictions で _total が付いていません。kubelet のソースでも Subsystem: kubelet / Name: evictions の CounterVec として登録されています。

_total は OpenMetrics が Counter に付ける接尾辞です。取得するフォーマットで見え方が変わります。

_total 付きで grep すると0件になります。綴りを疑わずに「存在しない」と判断しやすいところです。

もう一つ、ALPHA の kubelet_eviction_stats_age_seconds があります。型は Histogram で、定義はこうです。

Time between when stats are collected, and when pod is evicted based on those stats by eviction signal

統計を収集した時点から、その統計に基づいて Pod が退避された時点までの時間です。判定が走った回数ではありません。

kubectl get --raw "/api/v1/nodes/${NODE}/proxy/metrics" \
  | grep '^kubelet_eviction_stats_age_seconds_count'
kubelet_eviction_stats_age_seconds_count{eviction_signal="allocatableMemory.available"} 2
kubelet_eviction_stats_age_seconds_count{eviction_signal="containerfs.available"} 2
kubelet_eviction_stats_age_seconds_count{eviction_signal="nodefs.available"} 2

_count は Histogram の観測数です。シグナル別の退避回数としては使えません。

kubelet_evictions は2シグナルなのに、kubelet_eviction_stats_age_seconds_count は3シグナルあります。kubelet のソースを読むと理由が分かります。

  • EvictionStatsAge は、そのとき条件を満たしていたすべてのしきい値に対して Observe() される
  • Evictions は、退避の理由として選ばれた1つのシグナルにだけ Inc() される

退避の回数を数えるなら kubelet_evictions を使います。

記録されたシグナルは allocatableMemory.available でした。 evictionHard に書いた memory.available とは別のシグナルです。

kubelet の実装では、memory.available がノード全体の統計を見るのに対し、allocatableMemory.available は Pod 用 cgroup(SystemContainerPods)の統計を見ます。綴りの違いではありません。 どちらも MemoryPressure に対応します。

メモリの退避を監視するなら、両方のシグナルを見ます。

Cloud Loggingから見た退避
#

Cloud Logging には退避のイベントが残りました。

gcloud logging read 'resource.type="k8s_pod" AND jsonPayload.reason="Evicted"' \
  --limit=200 --freshness=2h --format='value(timestamp, jsonPayload.message)'

4つのクエリの結果件数です。

resource.type="k8s_pod" AND jsonPayload.reason="Evicted"           4 件
resource.type="k8s_node" AND jsonPayload.reason=~"Evict|Pressure"  8 件
resource.type="k8s_node" AND jsonPayload.MESSAGE=~"eviction"      36 件
protoPayload.serviceName="container.googleapis.com"                7 件

Pod 側とノード側の両方に残り、本文は Event と同じです。内容そのものは kubectl get events でも読めます。

違うのは置き場所です。ログはクラスタの外に保存され、消したあとも検索できます。Event は etcd に短時間しか残らず、クラスタごと消えます。

ログからアラートを作る
#

jsonPayload.reason="Evicted" を条件にカウンタのログベースメトリクスを作ると、Cloud Monitoring のアラートポリシーで使えます。 マネージド収集に退避のメトリクスが無くても、この経路なら組めます。

ログベースメトリクスは、作成後に届いたログしか数えません。 過去に遡っては集計されないので、退避が起きる前に作っておく必要があります。今回の検証では試していません。

Cloud Monitoringから見た退避
#

退避を数えるメトリクスが来ない
#

まず、収集ジョブはすべて動いています。

PROJECT=$(gcloud config get-value project)
ST=$(date -u -d '-30 min' +%Y-%m-%dT%H:%M:%SZ)   # 以降の v3 API で使う
END=$(date -u +%Y-%m-%dT%H:%M:%SZ)

curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://monitoring.googleapis.com/v1/projects/${PROJECT}/location/global/prometheus/api/v1/query" \
  --data-urlencode 'query=up'
up{job="gmp-kubelet-metrics"}   = 1
up{job="gmp-kubelet-cadvisor"}  = 1
up{job="kube-state-metrics"}    = 1

同じエンドポイントに、退避の検知に使いたいクエリを送ります。

curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://monitoring.googleapis.com/v1/projects/${PROJECT}/location/global/prometheus/api/v1/query" \
  --data-urlencode 'query=kube_pod_status_reason{reason="Evicted"}'

結果は空でした。値が 0 なのではなく、data.result が空配列で返ります。

この1回のクエリだけでは「一度も作られていない」とまでは言えません。instant query が返すのは評価時刻から一定時間さかのぼった範囲のサンプルだけです。あとで名前を列挙して、そもそも存在しないことを確かめます。

クエリ 返った数 使いたかった用途
kube_pod_status_reason 0 退避された Pod を数える
kube_node_status_condition 0 逼迫を前兆としてつかむ
kube_pod_container_resource_requests 0 requests と実使用の比較
kubelet_evictions 0 退避の回数をレート化する
kubelet_eviction_stats_age_seconds_count 0 Histogram の観測数

一方で、取れているメトリクスもあります。

クエリ 返った数
kube_pod_status_phase 25
kubelet_running_pods 1
kubelet_running_containers 3
container_memory_working_set_bytes 76

kubelet_evictions はノードの /metrics に実在していました。それでも Cloud Monitoring には入っていません。 ジョブが up=1 でも、そのジョブが出すメトリクスがすべて転送されるわけではないということです。

コンポーネントを9種類に増やしても変わらない
#

最初に疑うのは enable_components の設定です。有効にしていたのは4つでした。

SYSTEM_COMPONENTS;POD;CADVISOR;KUBELET

kube-state-metrics 系はほかにも指定できます。すべて有効にして試しました。

gcloud container clusters update tf-adv-evict --zone asia-northeast1-a \
  --monitoring=SYSTEM,POD,DAEMONSET,DEPLOYMENT,HPA,STATEFULSET,STORAGE,CADVISOR,KUBELET
SYSTEM_COMPONENTS;STORAGE;HPA;POD;DAEMONSET;DEPLOYMENT;STATEFULSET;CADVISOR;KUBELET

反映を待ってから再度クエリした結果です。

クエリ 4コンポーネント 9コンポーネント
kube_pod_status_phase 25 25
kube_pod_status_reason 0 0
kube_node_status_condition 0 0
kube_pod_container_resource_requests 0 0
kube_node_status_allocatable 0 0
kubelet_evictions 0 0

退避に使いたいメトリクスは1つも増えませんでした。少なくとも enable_components の不足が原因ではありません。

この環境で入っていた kube_* は7種類だった
#

何が入っているのかを、名前で列挙しました。

curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://monitoring.googleapis.com/v1/projects/${PROJECT}/location/global/prometheus/api/v1/label/__name__/values"

メトリクス名は全体で 18,624 種類ありました。そのうち kube_ で始まるものは、このプロジェクトではこれで全部でした。

この一覧はクラスタと期間を絞っていません。 検証中に実際に収集されていたかまで確かめるなら、query_range で期間を指定し、cluster と location で絞ります。

kube_deployment_spec_replicas
kube_deployment_status_replicas_available
kube_deployment_status_replicas_updated
kube_pod_container_status_ready
kube_pod_container_status_waiting_reason
kube_pod_status_phase
kube_pod_status_unschedulable

7種類でした。全コンポーネントを有効にして増えたのは kube_deployment_* の3つだけでした。

この数はワークロードの構成で変わります。DaemonSet や StatefulSet を動かしていれば、対応するメトリクスも作られるはずです。要点は、退避に使いたい名前が1つも無いことのほうです。

kubelet_ で始まるものは11種類です。

kubelet_certificate_manager_server_ttl_seconds
kubelet_node_name
kubelet_pleg_relist_duration_seconds_{bucket,count,sum}
kubelet_pod_worker_duration_seconds_{bucket,count,sum}
kubelet_running_containers
kubelet_running_pods
kubelet_runtime_operations_total

同じノードの /metrics では、kubelet_ で始まるメトリクスを 119種類確認できました。この名前一覧で確認できたのは11種類です。

これは GMP の収集上限ではありません。公式の収集対象には kubelet_volume_stats_available_bytes などもあり、実際に出る名前は構成と収集設定で変わります。

これは仕様どおりでした。GKE のマネージド収集は、集めるメトリクスをコンポーネントごとに公開しています。

この2つの一覧に、退避に関するものは1つも載っていません。

監視の設計は up からではなく、この一覧から始めるほうが確実でした。

足りない分は収集対象を足す
#

一覧に無いメトリクスは、収集対象を自分で足せば取れます。取り方は出どころで変わります。

kube-state-metrics のメトリクス(kube_pod_status_reason など)は、kube-state-metrics を自分でデプロイして PodMonitoring で拾います。手元で kube-prometheus-stack を立てた環境では、同じクエリが返りました。

kube_pod_status_reason{reason="Evicted"}  →  pod=hog  1

kubelet のメトリクス(kubelet_evictions)は、ノードの /metrics にすでに公開されています。追加の Exporter は要りません。ClusterNodeMonitoring で直接取り込みます。

apiVersion: monitoring.googleapis.com/v1
kind: ClusterNodeMonitoring
metadata:
  name: kubelet-evictions
spec:
  selector:
    matchLabels: {}
  endpoints:
  - path: "/metrics"
    scheme: "https"
    interval: "30s"
    tls:
      insecureSkipVerify: true
    metricRelabeling:
    - action: keep
      sourceLabels: [__name__]
      regex: kubelet_evictions

metricRelabeling で絞らないとノードが出す119種類が全部入ります。必要な名前だけ keep します。 この設定は今回の検証では試していません。

標準のメトリクスパッケージだけでは、退避の理由や回数を直接表すメトリクスが得られません。 監視を組むなら、phase による間接的な検知、退避ログに直接一致させるアラート、ログベースメトリクス、追加のPrometheus収集を使い分けます。

組み込みで済ませるなら phase を使う
#

退避された Pod は Failed になるので、phase 経由なら間接的に見えます。

kube_pod_status_phase{namespace="evict-test"} == 1
  pod=besteffort  phase=Failed   1
  pod=diskfill    phase=Failed   1
  pod=hog         phase=Failed   1
  pod=burstable   phase=Running  1
  pod=guaranteed  phase=Running  1

ただし phase=Failed は退避以外の理由でも発生するので、理由を知るには別の経路が要ります。 クラスタが残っていれば Pod の status.reason と Event、消したあとなら Cloud Logging です。

逼迫そのものは status_condition で取れる
#

GCP ネイティブのメトリクスは、Monitoring API v3 から取れます。kube_node_status_condition の代わりになるのが kubernetes.io/node/status_condition です。

curl -s -G -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://monitoring.googleapis.com/v3/projects/${PROJECT}/timeSeries" \
  --data-urlencode 'filter=metric.type="kubernetes.io/node/status_condition" AND
      metric.labels.condition=monitoring.regex.full_match("MemoryPressure|DiskPressure")' \
  --data-urlencode "interval.startTime=${ST}" \
  --data-urlencode "interval.endTime=${END}"
condition=MemoryPressure  status=True   6点   True の期間 09:40:00 〜 09:45:00
condition=DiskPressure    status=True   6点   True の期間 09:47:00 〜 09:52:00
condition=MemoryPressure  status=False  34点
condition=DiskPressure    status=False  34点

逼迫の期間がそのまま取れています。condition と status の組み合わせごとにデータがあり、値は真偽値です。PIDPressure や Ready も同じ形で入ります。

退避が起きた事実そのものは取れませんが、ノードが逼迫していることは分かります。

ただし前兆として使えるとは限りません。 今回のタイムラインでは、退避のほうが先に起きています。

09:38:19  besteffort 退避
09:40:00  MemoryPressure=True の最初の点

このメトリクスは60秒間隔でサンプリングされ、反映まで時間がかかります。アラートを組むなら、退避を検知したあとの状況確認に使うほうが実態に合います。

evictable は「退避で回収できるメモリ」ではない
#

メモリの内訳も取れます。

kubernetes.io/node/memory/used_bytes
  memory_type=evictable       685,723,648
  memory_type=non-evictable  1,502,375,936

名前から「退避すれば空くメモリ」と読みたくなりますが、公式の定義は 「カーネルが容易に回収できるメモリ」 です。Pod の退避とは関係がありません。

実測の推移がそれを示しています。

時刻   evictable      non-evictable
09:43    749,879,296  1,444,921,344
09:44  4,678,520,832  1,430,188,032   ← evictable が急増
09:45  6,274,232,320  1,466,175,488   ← MemoryPressure=True
09:46    666,390,528  1,478,160,384   ← 退避後

逼迫の最中に evictable が 6.27GB まで上がっています。ノードの allocatable は 6,170,292Ki です。「回収できるメモリが 6GB あるのに退避が起きた」と読めてしまいます。

この推移だけでは、増減の原因も退避との対応も特定できません。 09:45台の退避はディスクが理由で、メモリ理由の退避は 09:38:19 です。対応を調べるなら、退避のシグナルと時刻に加えて active_file / inactive_file を記録する必要があります。

kubelet が判断に使う memory.available は別物で、cgroupfs から取った値から inactive_file を除いて計算されます。free -m とも evictable とも一致しません。

ノード逼迫を監視するなら status_condition を見ます。 メモリの内訳は容量設計の材料であって、退避のアラートには向きません。

つまずいたところ
#

gcloud monitoring に時系列のサブコマンドが無い
#

$ gcloud monitoring time-series list ...
ERROR: (gcloud.monitoring) Invalid choice: 'time-series'.
Maybe you meant:
  gcloud monitoring dashboards list
  gcloud monitoring policies list

gcloud monitoring にあるのは dashboards / policies / snoozes / uptime の4グループで、時系列を引くサブコマンドがありません。 Monitoring API v3 を直接呼び出します。

GMP の PromQL はさらに別のエンドポイント(/v1/.../prometheus/api/v1/query)で、両者は互換ではありません。

承認済みネットワークにIPv6を渡すと検証エラーになる
#

Error: expected master_authorized_networks_config.0.cidr_blocks.0.cidr_block
       to contain a valid network Value, expected 240f:73::/32, got 240f:73:...

curl -s https://ifconfig.me が IPv6 を返していました。-4 を付けて IPv4 を明示します。

e2-mediumではGMPの収集Podが乗らない
#

最初 e2-medium(4GB)で作ったところ、kube-state-metrics が Pending のままでした。kubeReserved を引くと allocatable は 2,866,868Ki です。収集コンポーネントを配置すると、逼迫を起こすための余力がほとんど残りませんでした。

e2-standard-2(8GB)にして解決しています。ノードをわざと逼迫させる用途では余白が要る、という話です。e2-medium で GMP が動かないわけではありません。

まとめ
#

3つの経路で観測した結果です。

見たいものによって、使う経路が変わります。

見たいもの 使う経路
退避が起きた事実と理由 kubectl の Event、gcloud logging
退避の回数 ノードの kubelet_evictions(既定では Cloud Monitoring に来ない)
退避のアラート ログベースメトリクス、または ClusterNodeMonitoring
ノードの逼迫状態(退避より後に観測されることもある) kubernetes.io/node/status_condition
Failed 状態の Pod の存在 kube_pod_status_phase{phase="Failed"} == 1(退避以外も含む)

退避の情報はノードに揃っています。 kubelet_evictions はシグナル別にカウントされ、Event には判断根拠がそのまま書かれています。届かないのは Cloud Monitoring への既定の経路だけでした。

up=1 が意味するのは、収集ジョブがエンドポイントに到達できていることだけです。 マネージド収集が集めるのは公開された一覧の範囲です。この環境で名前を列挙したところ kube_* は7種類、kubelet_* は11種類で、同じノードの /metrics が出す119種類の一部でした。収集コンポーネントを4種類から9種類に増やしても、退避の監視に使いたいメトリクスは増えませんでした。

不足するメトリクスは PodMonitoring と ClusterNodeMonitoring で追加収集でき、ログベースメトリクスを使う方法もあります。既定で入っていないだけだと捉えると、設計の選択肢が残ります。

退避そのものについては、メッセージが判断根拠をそのまま書いているのが収穫でした。

メモリ逼迫時の順位付けは、requests の超過・Pod Priority・超過量で決まります。QoS クラスそのものは使われません。生き残った2つはどちらも requests を設定しており、実使用がそれを下回っていました。

ディスク逼迫でも3段階の形は同じで、ephemeral-storage の requests で判定されます。 変わるのは何を使用量として数えるかです。QoS クラスは CPU とメモリから決まるので、ここでは説明になりません。

名前と意味の取り違えにも注意が要ります。kubelet_evictions に _total は付かず、シグナルはラベル側の allocatableMemory.available でした。

evictable も、退避ではなくカーネルが回収できるメモリの分類でした。クエリが0件を返したら綴りを、値が直感に合わなければ定義を疑うほうが早いです。

参考資料
#

Terraform-GoogleCloud - この記事は連載の一部です
パート 41: この記事

関連記事

標準出力に1行JSONを書くだけなら、GKEではFluentdサイドカーが要らなかった
2 分
Terraform GoogleCloud GKE CloudLogging Fluentd
terraform applyが成功してもVMの中は設定されていなかった話
4 分
Terraform GoogleCloud Ansible ComputeEngine CloudLogging
GKEのdefault_compute_class_enabledを有効にしても何も起きなかったので調べた
3 分
Terraform GoogleCloud GKE
GKEノードプールのSURGEアップグレードをBlue/Greenと同じ条件で測って比べてみた
3 分
Terraform GoogleCloud GKE
GKEノードプールをBlue/Greenでアップグレードして、soak期間中にロールバックしてみた
5 分
Terraform GoogleCloud GKE CloudKMS
GKEノードのブートディスクをCMEKで暗号化して鍵をローテーションしてみた
6 分
Terraform GoogleCloud GKE CloudKMS CMEK