【Datadog】KubernetesのOOMKilledを監視するためのメトリクス

# 概要

この記事の内容は以下のとおりです。

- KubernetesのコンテナのStateとLast Stateの違い
- Datadogで`kubernetes.containers.state.terminated`を使う際の注意事項
- `kubernetes.containers.last_state.terminated`を用いたOOMKilledの監視方法

# やりたいこと

この記事では以下のことを実現します。

- KubernetesのコンテナでOOMKilledが起きたときに、Datadog Monitorでアラートを飛ばしたい
- そのためにOOMKilledだけをメトリクスとして取得したい（他のエラーとは混ぜたくない）

# 準備: Datadog AgentをKubernetesクラスタに入れる

この章は本題ではなく、公式ドキュメント通りの内容です。適宜読み飛ばしてください。

DatadogでKubernetesのメトリクスを収集するには、**Datadog Agent**をクラスタに入れます。入れ方は3つあります（Helm, DeamonSet, Operator）

https://docs.datadoghq.com/agent/kubernetes/


**Datadog Agent**の仕組みについては、以下の日本語記事をお読みください。この記事はDatadog Cluster Agentの解説ですが、「Datadog Cluster Agentを利用できなかった時代のクラスターの監視」という章で**Datadog Agent**のアーキテクチャが紹介されており、分かりやすいです。

https://www.datadoghq.com/ja/blog/monitoring-kubernetes-datadog/

![Datadog Agentのアーキテクチャ。上記記事より引用](https://imgix.datadoghq.com/img/blog/datadog-cluster-agent/kubernetes_diagrams_before_181012v3.png)

※図は上記記事より引用

また、必須ではありませんが、クラスタに**kube-state-metrics**を入れておくと、**[公式ドキュメント: Kubernetes Data Collected](https://docs.datadoghq.com/agent/kubernetes/data_collected/)**の`Kubernetes state`の表にあるメトリクスを使えるようになって便利です。（今回のアラートには不要です）

https://github.com/kubernetes/kube-state-metrics

そもそもKubernetesの監視って何やるの？ という方は、以下の記事で全4パートで学べます。オススメです。

https://www.datadoghq.com/blog/monitoring-kubernetes-era/

# OOMKilledの監視に使えそうなメトリクス

本題に入ります。

**[公式ドキュメント: Kubernetes Data Collected](https://docs.datadoghq.com/agent/kubernetes/data_collected/)**には、なんだか使えそうなメトリクスが4つ書かれています。いったんそのまま引用します↓

| メトリクス                                                | 説明                                                                                       | 
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------ | 
| kubernetes.containers.state.terminated                    | The number of currently terminated containers                                              | 
| kubernetes.containers.last_state.terminated               | The number of containers that were previously terminated                                   | 
| kubernetes_state.container.terminated                     | Whether the container is currently in terminated state                                     | 
| kubernetes_state.container.status_report.count.terminated | Count of the containers currently reporting a in terminated state with the reason as a tag | 

説明が少ないですね... 私のような素人には厳しいです。

そこで実際にDatadogのMetrics ExplorerやMonitorをゴチャゴチャしながら、**OOMKilledの監視に使えるのはどれかを調べてみました。**

表の下の2つ（`kubernetes_state`からはじまるメトリクス）は前述の通り**kube-state-metrics**が必要なので、本記事では上の2つ（`kubernetes.containers`からはじまるメトリクス）を対象にします。

# kubernetes.containers.state.terminated

`kubernetes.containers.state.terminated`は「**現在進行系でstateがterminatedになっているコンテナの数**」です(sumの場合)。runnning→terminatedになった回数ではなく、コンテナの数である点に注意してください。同じコンテナがrunnning→terminatedを繰り返していても、このメトリクスは1のままです。

なお、ここでいうStateとは`.status.containerStatuses[].state`のことです。コンテナはWaiting, Running, Terminatedの3つのStateを持ちます（[公式ドキュメント](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-states)）。Waiting, Terminatedの場合はReasonも持ち、以下のような値を取ります（[stack overflow](https://stackoverflow.com/questions/57821723/list-of-all-reasons-for-container-states-in-kubernetes)）。

- WaitingのReason
  - ContainerCreating
  - CrashLoopBackOff
  - ErrImagePull
  - ImagePullBackOff
  - CreateContainerConfigError
  - InvalidImageName
  - CreateContainerError
- TerminatedのReason
  - OOMKilled
  - Error
  - Completed
  - ContainerCannotRun
  - DeadlineExceeded

なので、Datadogのメトリクスにも`kubernetes.containers.state.terminated`以外に`(略).state.waiting`と`(略).state.running`があります。

また、これが最重要なのですが、**`waiting`と`terminated`についてはreasonも取得することができます。**

例えばMonitorのクエリを`sum:kubernetes.containers.state.terminated{reason:oomkilled}`とすれば、「**現在進行系でstateがterminatedになっていて、かつreasonがOOMKilledであるコンテナの数**」という意味になります。

やった！ やりたいことにマッチしていますね。さっそくMonitorを作ってみました。

↓

(((( ;ﾟдﾟ))) **なんかアラートが飛ばないときがある......**

そう、なんとうまくいかなかったのです。OOMKilledに限らず、`sum:kubernetes.containers.state.terminated`が正しく増えないことがありました（増えることもありました）。なぜでしょうか？

あくまで推測ですが、おそらく**Datadog Agentがデータを取得したタイミングでコンテナのstateがterminatedになっていなかった**のだと思います。

PodのrestartPolicyをNeverにしていない限り、コンテナが死んだ場合はすぐに再起動されます。そのためstateがterminatedになるのは一瞬です。そのタイミングがDatadog Agentのデータ取得タイミングと被ったらメトリクスに反映され、そうでない場合は反映されないのだと思います（素人の推測です。訂正・補足等お待ちしております）

そのため、このメトリクスはアラートに使いづらいことが分かりました。なのでもう片方を使おうと思います。

# kubernetes.containers.last_state.terminated

`kubernetes.containers.last_state.terminated`は、「**Last Stateがterminatedになっているコンテナの数**」です(sumの場合)。

StateとLast Stateは別物です。Stateがterminatedになったコンテナが再起動するとき、Last Stateに終了時刻や理由が入ります。`kubectl get pod`の結果からStateとLast Stateを比較してみましょう。コンテナ「xxx」がOOMKilledがあったコンテナで、コンテナ「yyy」は一度も再起動していないコンテナです↓

※関係のない行は省略しています

```json
"status": {
  "containerStatuses": [
    {
      "containerID": "xxx",
      "image": "xxx",
      "imageID": "xxx",
      "lastState": {
        "terminated": {
          "containerID": "xxx",
          "exitCode": 0,
          "finishedAt": "2021-12-02T18:09:45Z",
          "reason": "OOMKilled",
          "startedAt": "2021-11-29T08:47:55Z"
        }
      },
      "name": "xxx",
      "ready": true,
      "restartCount": 10,
      "started": true,
      "state": {
        "running": {
          "startedAt": "2021-12-02T18:09:48Z"
        }
      }
    },
    {
      "containerID": "yyy",
      "image": "yyy",
      "imageID": "yyy",
      "lastState": {},
      "name": "yyy",
      "ready": true,
      "restartCount": 0,
      "started": true,
      "state": {
        "running": {
          "startedAt": "2021-09-14T05:31:22Z"
        }
      }
    }
  ]
}
```

一度も再起動していないコンテナ「yyy」はLast Stateが空です。

よって、例えばMonitorのクエリを`sum:kubernetes.containers.last_state.terminated{reason:oomkilled}`とすれば、「**Last Stateがterminatedになっていて、かつreasonがOOMKilledであるコンテナの数**」という意味になり、OOMKilledの発生を検知できます。

ただし、下記の点は注意が必要です。

- **OOMKilledが起きた回数ではない。**同じコンテナがOOMKilledを繰り返していても、この値は1のまま
- 一度1になったら、Podが生まれ変わらない限り0に戻らない。なぜならLast Stateが空に戻ることはないから(※reasonが別の理由になることはあり得る)
- よって、**OOMKilledが発生しなくなったタイミングは検知できない。また、同じPodでOOMKilledが再発しても検知できない**

つまりなかなか不便です。

弊チームの場合、OOMKilledが発生したら必ず調査・対応をしています。多くのケースではコンテナのメモリのlimitを増やすためにマニフェストを書き換えてkubectl applyします。つまりPodが生まれ変わるので、このとき0に戻ります。なので困ってはいませんが...「運用でカバー」は避けるべきですよね...

まだやっていませんが、Datadog Monitorの機能で色々頑張ることは可能かもしれません。

# 2つのメトリクスの比較

Dashboardで2つのメトリクスを並べてみましょう。

【上】`sum:kubernetes.containers.state.terminated{reason:oomkilled}`

【下】`sum:kubernetes.containers.last_state.terminated{reason:oomkilled}`

※いずれも`defalut_zero`にしています

![2つのメトリクスの比較](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502255/4c573d7d-7da0-12ae-df69-a0e820338ea9.png)

前述した通り、上の`kubernetes.containers.state.terminated`はほとんど検知できていません（下のグラフが上がっているのに上のグラフが0のままのタイミングは検知できていません）。

また、下の`kubernetes.containers.last_state.terminated`はPodが生まれ変わらない限り値が減らない様子もよく分かりますね。

# まとめ

KubernetesのOOMKilledを監視するのに使うDatadogメトリクスは`kubernetes.containers.last_state.terminated{reason:oomkilled}`が良さそうですが、アラートに使うには不便な点が多いです。