Kubernetes: kubectl 上の Pod のステータス表記について

`kubectl` で Pod を表示した場合、`Running` や `Terminating` などのステータスが表示されます。しかし、このステータスは Pod オブジェクトの単一フィールドを表示しているわけではなく、いくつかのフィールドと条件によって表示が分けられています。

この記事ではよく見る Pod のステータス表記について整理してみます。

## kubectl のステータスの出し分け

ご存じの通りkubectl で Pod を表示したとき、下記のように STATUS カラムで Pod のステータスを見ることができます。

```bash
$ kubectl get pods -w
NAME      READY     STATUS    RESTARTS   AGE
myapp     0/1       Pending   0          0s
myapp     0/1       ContainerCreating   0         0s
myapp     1/1       Running   0         1s
myapp     1/1       Terminating   0         8s
```

Pod の状態は主に `pod.status` フィールドにて [`PodStatus`](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/#podstatus-v1-core) というオブジェクトで管理されています。`PodStatus` には `Phase` というフィールドがありますが、kubectl は単純にこれを表示しているわけではありません。

kubectl の Pod のステータス表示のロジックは主に [printers.go の printPod()](https://github.com/kubernetes/kubernetes/blob/v1.12.2/pkg/printers/internalversion/printers.go#L560) という関数になります。

この関数では下記の Pod のフィールドを参照して Pod のステータスを出しています。より詳細な情報があればそれを優先して表示されるようになっています。例えば、`pod.status.phase` が `pending` で `containerStatuses.state.waiting.reason` が `ContainerCreating` の場合は、`ContainerCreating` が表示されます。


- `pod.status ` [PodStatus](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/#podstatus-v1-core) オブジェクト
    - `phase` Pod のフェーズ。`Pending`, `Running`, `Succeede`, `Failed`, `Unknown` の値が入る
    - `reason` Pod の今の状態に至った理由
- `pod.status.initContainerStatuses` initContainer 用の [ContainerStatus](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/#containerstatus-v1-core) の配列
    - `restartCount` 再起動した階数
    - `state.waiting.reason` 処理を待っている理由 (`CrashLoopBackoff` など)
    - `state.terminated.exitCode` 終了した場合の終了コード
    - `state.terminated.reason` 終了した場合の理由
    - `state.terminated.signal` 終了した場合のシグナル
- `pod.status.containerStatuses` 通常のコンテナ用の [ContainerStatus](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/#containerstatus-v1-core) の配列
    - `restartCount` 再起動した階数
    - `state.waiting.reason` 処理を待っている理由 (`CrashLoopBackoff` など)
    - `state.terminated.exitCode` 終了した場合の終了コード
    - `state.terminated.reason` 終了した場合の理由
    - `state.terminated.signal` 終了した場合のシグナル
    - `state.running` 動作中かどうか
    - `ready` ready 状態かどうか
- `pod.metadata` [ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/#objectmeta-v1-meta) オブジェクト
    - `deletionTimestamp` 削除が始まったときに入る時間

また Pod は複数のコンテナ(通常のコンテナ + initContainers) から構成されるので、エラーがあるコンテナの情報が優先的に表示されたりといった工夫をしているため注意が必要です。コンテナすべての情報を見たい場合は、`kubectl describe` か `kubectl get pod -o yaml` でオブジェクト全体を見たほうが良いでしょう。

## 代表的な Pod のステータス表記

ステータス表示のもととなるフィールドで分類して、代表的な kubectl の Pod のステータス表記を以下にまとめました。

#### pod.status.phase
- **`Pending`**  Pod オブジェクトが作成され、まだ全てのコンテナが起動していない状態。フィールドの値としてはコンテナの起動中なども含むが、`ContainerCreating` などの表記が優先されて表示されるため、kubectl ではコンテナ起動を行う前までがこの表記となる。
- **`Running`** すべてのコンテナが起動している状態

#### pod.status.reason
- **`Unknown`** Pod の削除前 (`deletionTimestamp` が入る前)にノードが未応答 (`NodeLost`)になったもの
- **`NodeLost`** Pod の削除中 (`deletionTimestamp` が入っている)にノード未応答になったもの
- **`Evicted`** Pod が退避 (Evict)対象となった。Evict はノードのリソースが足りなくなった場合に行われる。

#### metadata.deletionTimestamp
- **`Terminating`** Pod の削除時間 (`deletionTimestamp`) が入っていて、`reason` が `NodeLost` ではないもの。終了処理 (preStop やシグナルハンドリング)中の Pod。終了処理が終わると Pod オブジェクト自体が削除される

#### initContainerStatuses.state.terminated.exitCode
- **`Init:0/3`** initContainers のうち処理が終わった(終了コードが0)コンテナ数
- **`Init:Error`** initContainers のコンテナのいずれかエラーが発生した

#### containerStatuses.state.waiting.reason
- **`ContainerCreating`** すべてのコンテナが起動するのを待っている状態。コンテナイメージの pull もここに含まれる。(initContainer がない場合)
- **`PodInitializing`** すべてのコンテナが起動するのを待っている状態。コンテナイメージの pull もここに含まれる。 (initContainer がある場合)
- **`CreateContainerError`** いずれかのコンテナの作成時にコンテナランタイム側で処理に失敗した。`kubectl describe` で詳細を確認する必要がある。
- **`PostStartHookError`**  いずれかのコンテナの postStart フックが異常終了した
- **`CrashLoopBackOff`** コンテナのいずれかが再起動を待っている状態 (後述)
- **`ErrImagePull`** コンテナのいずれかが pull に失敗した
- **`ImagePullBackOff`**  コンテナのいずれかが再 pull を待っている状態 (後述)

#### containerStatuses.state.terminated.reason
- **`Completed`** `restartPolicy` が `never` (コンテナを再起動しない設定)ですべてのコンテナが正常終了した
- **`Error`** コンテナのいずれかが異常終了 (終了ステータスが 0 以外)した
- **`OOMKilled`** メモリが足りず OOM (Out of Memory) Killer によって kill された。resources の limit のメモリが足りているか要確認

## BackOff について

`CrashLoopBackoff` や `ImagePullBackOff` の **BackOff** はリトライの間隔を [Exponential Backoff](https://en.wikipedia.org/wiki/Exponential_backoff) というアルゴリズムで、間隔を指数関数的に増やしていくことを意味しています。

Kubernetes 1.12.3 の実装では `CrashLoopBackoff` と `ImagePullBackOff` のリトライ間隔(backoff)は以下のようになっています。

- [リトライ間隔は2倍づつ増えていく](https://github.com/kubernetes/kubernetes/blob/v1.12.3/staging/src/k8s.io/client-go/util/flowcontrol/backoff.go#L78)
- [リトライの初期値が 10秒](https://github.com/kubernetes/kubernetes/blob/v1.12.3/pkg/kubelet/kubelet.go#L165)
- [最大値が 300秒 (5分)](https://github.com/kubernetes/kubernetes/blob/v1.12.3/pkg/kubelet/kubelet.go#L131)

例えばコンテナが起動時に異常終了した(終了コードが 0 以外) 場合は、下記のように `Error` と `CrashLoopBackoff` のステータスが交互に現れることになります。

```bash
$ kubectl get pods -w
myapp   1/2   Error   0     7s
myapp   1/2   Error   1     11s
myapp   1/2   CrashLoopBackOff   1     12s
myapp   1/2   Error   2     27s
myapp   1/2   CrashLoopBackOff   2     38s
myapp   1/2   Error   3     52s
myapp   1/2   CrashLoopBackOff   3     63s
myapp   1/2   Error   4     100s
```

まずコンテナが異常終了したため、終了理由 `containerStatuses.state.terminated.reason` に `Error` が入り、次に BackOff の時間を待っている間、待ち理由 `containerStatuses.state.waiting.reason` に `CrashLoopBackoff` が入ります。再度コンテナが起動して異常終了し、これを繰り返すためこのような表示になります。

## Pod のステータスの例

### initContainer を持たない場合

initContainer を持たない Pod の場合、下記のようにステータスが変化していきます。

```bash
$ kubectl get pods -w

NAME    READY   STATUS    RESTARTS   AGE
# Pod オブジェクトが作成された時点で Pending になる
myapp   0/1   Pending   0     0s
# Pod がスケジュールされ kubelet でこの状態になる
myapp   0/1   ContainerCreating   0     0s
# すべてのコンテナが立ち上がると Running になる
myapp   1/1   Running   0     6s
# 終了処理中は Terminating となる
myapp   1/1   Terminating   0     10s
myapp   0/1   Terminating   0     10s
```

### initContainer を持つ場合

initContainer を持つ Pod の場合、少しステータスの変化が異なります。以下の例では initContainer 3 つ、コンテナ 3つの Pod の起動例です。

```bash
$ kubectl get pods -w

NAME    READY   STATUS    RESTARTS   AGE
# Pod オブジェクトが作成された時点で Pending になる
myapp   0/3   Pending   0     0s
# initContainers の初期化が始まる (3つ分)
myapp   0/3   Init:0/3   0     0s
myapp   0/3   Init:1/3   0     4s
myapp   0/3   Init:2/3   0     8s
# initContainers の初期化が終わると、PodInitializing の表記になる。
myapp   0/3   PodInitializing   0     12s
# コンテナが起動していく (3つ分)
myapp   0/3   Running   0     23s
myapp   1/3   Running   0     34s
myapp   2/3   Running   0     49s
myapp   3/3   Running   0     55s
# 終了中は Terminating の表記となる
myapp   3/3   Terminating   0     69s
myapp   0/3   Terminating   0     99s
```


## まとめ
kubectl のステータスは様々なフィールドを参照して表示しています。`kubectl get pods -w` で遷移をウォッチしてみるとステータスの遷移がわかりデバッグ時にも役立ちます。

---

このエントリは、弊社 Z Lab のメンバーによる Z Lab Advent Calendar 2018 の3日目として業務時間中に書きました。4日目は @watawuwu の担当です。