kubernetes

Kubernetesのポッドが起動しない原因と対策

More than 3 years have passed since last update.

Kubernetesの導入を検討するにあたって、エラーの状況確認から解決に至るまでのオペレーションは押さえておくべき事柄だ。まだ導入に至っていない場合はこういう問題が発生するのかと雑に感じてもらい、導入している場合は問題を解決する際の参考にしてもらえればと思う。

当エントリでは、自分が遭遇した失敗のケースについて原因と解決方法を列挙する。尚、経験ベースなので同じエラーがでても場合によっては別の原因や解決方法があるかもしれない。また、記載したログや設定ファイルのリソース名や値などは適当にマスキングしているので適宜読み替えてほしい。


どのようなタイミングでポッドが起動しなくなるか

Kubernetesはポッドが落ちた際に自動的にポッドを再起動するなどのマネジメントを行ってくれる。ただし、ポッドを初めて作る際や、構成を変えたポッドやレプリケーションコントローラをローリングアップデートする際に、誤った設定を行ってしまいポッドが起動しない事態に陥る事がよくある。


ローリングアップデートの失敗

ローリングアップデートをした際に新しいポッドが何かしらの原因で起動しなかった場合、error: timed out waiting for any update progress to be madeのような雑なメッセージを吐いて失敗する。このような事態に陥ってもあまり慌てる必要がない。なぜなら、ポッド内のすべてのコンテナが正常に稼動することをもってポッドが正常であると判断され、正常なポッドが生成されない限りはローリングアップデートつまりポッドの入れ替えが始まらないからだ。このようにローリングアップデートが始まらなかったケースでは、前の構成がそのまま動いていて起動に失敗する新しい構成は動かずに放置される。そのため、外部からはただただアップデートが反映されてないように観測される1


ローリングアップデート実行時に失敗を検知する方法

CIから自動的にローリングアップデートを実行しているなどの場合、失敗を検知したい場合があるだろう。kubectl rolling-update rcコマンドは--output=jsonとしても結果をJSONで取得できないので、エラーしたのかはexitコードベースで判定する。


失敗時にプロセスを落としたい時

単純にテスト自体を失敗としたい場合は、デプロイ用のスクリプトファイル内でset -eしておいて失敗した時点でデプロイプロセスを落とす。


deploy.sh

set -e

kubectl rolling-update rc --filename=rc.yml


失敗時に処理を行いたい時

失敗したことを検知し通知などの処理を行いたいことがあるだろう。この場合は$?でexitコードを取得して評価し、0以外で終わっている場合は処理を行いプロセスを終了する。


deploy.sh

kubectl rolling-update rc --filename=rc.yml

if [[ $? != 0 ]]; then
# do something
exit $?
fi


原因の調査方法

kubectl get podsコマンドで起動に失敗しているポッドを見つけ、kubectl describe pod <pod-name>でポッドの詳細を表示する。下記のようにステータスが表示されるだろう。


terminal

Name:                           my-pod

...
Status: Pending
...
Containers:
client:
Image: gcr.io/my-prj/client:v0
State: Waiting
Reason: PullImageError
Ready: False
Restart Count: 0
nginx:
Image: gcr.io/my-prj/nginx:v0
State: Waiting
Reason: PullImageError
Ready: False
Restart Count: 0
app:
Image: gcr.io/my-prj/app:v0
State: Waiting
Reason: PullImageError
Ready: False
Restart Count: 0
Conditions:
Type Status
Ready False
...
Events:
FirstSeen LastSeen Count From SubobjectPath Reason Message
───────── ──────── ───── ──── ───────────── ────── ───────
33m 33m 1 {scheduler } Scheduled Successfully assigned my-pod to my-node-a
33m 33m 1 {kubelet my-node-a} implicitly required container POD Created Created with docker id XXXXXXXXXXXX
33m 33m 1 {kubelet my-node-a} implicitly required container POD Started Started with docker id XXXXXXXXXXXX
33m 33m 1 {kubelet my-node-a} implicitly required container POD Pulled Container image "gcr.io/google_containers/pause:0.8.0" already present on machine
33m 8s 199 {kubelet my-node-a} spec.containers{client} Pulling Pulling image "gcr.io/my-prj/client:v0"
32m 5s 199 {kubelet my-node-a} spec.containers{client} Failed Failed to pull image "gcr.io/my-prj/client:v0": Tag v0 not found in repository gcr.io/my-prj/client
33m 5s 199 {kubelet my-node-a} spec.containers{nginx} Pulling Pulling image "gcr.io/my-prj/nginx:v0"
33m 3s 199 {kubelet my-node-a} spec.containers{app} Pulling Pulling image "gcr.io/my-prj/app:v0"
33m 3s 199 {kubelet my-node-a} spec.containers{nginx} Failed Failed to pull image "gcr.io/my-prj/nginx:v0": Tag v0 not found in repository gcr.io/my-prj/nginx
33m 1s 199 {kubelet my-node-a} spec.containers{app} Failed Failed to pull image "gcr.io/my-prj/app:v0": Tag v0 not found in repository gcr.io/my-prj/app

起動に失敗しているコンテナは.Conteinrs.<ContainerName>.StateRunning以外になっていて、.Conteinrs.<ContainerName>.State.Reasonに失敗した理由が表示されている。また、Eventsにポッド内で行われたコンテナ立ち上げのイベントが記録されているので、このReasonFailedになっているイベントにも注目する。


エラー内容と解決方法

エラー毎に解決方法を示す。


PullImageError


原因

イメージの取得に失敗している。


解決


  • コンテナが使用するイメージがpushされているか確認しよう。


    • イメージのレジストリのURLが間違っていないか。

    • イメージのタグが間違っていないか。




rc.yml

apiVersion: v1

kind: ReplicationController
...
spec:
template:
spec:
containers:
- name: app
image: gcr.io/my-prj/app:v0 # <- ココ


CrashLoopBackOff


原因

コンテナ内のプロセスの終了を検知してコンテナの再起動を繰り返している。


解決

コンテナで終了するプロセスを動かしていないだろうか?コンテナが正常に稼動しているかはプロセスが落ちていないかで判断するので、プロセスが正常終了した場合であっても「あっプロセスが落ちてる」となって再起動される。そのようなコンテナは再起動したところで再びプロセスが正常終了するだろう。つまり、無限に起動し続けるということになる。

シングルコンテナポッドの場合は、コンテナの再起動ポリシーをプロセスが異常終了した場合のみ再起動するように変更する。2


rc.yml

apiVersion: v1

kind: ReplicationController
...
spec:
template:
spec:
restartPolicy: OnFailure # <- ココ

マルチコンテナポッドの場合は、前述のオプションは設定できない。コンテナはDockerfileのCMDにお馴染みtail -f /dev/nullを追加して永続化する。


Dockerfile

CMD <your-command> && tail -f /dev/null


終了しないはずのプロセスを動かしている場合は、不正に終了している可能性がある。kubectl -c <container-name> logs <pod-name>でコンテナで吐かれたログを確認する。


PodExceedsFreeCPU


原因

ノードに余っているCPUリソース以上のCPUリソースを要求している。


解決

まずはノードが持っているリソースの量を把握する。kubectl get nodesでノードの一覧を表示し、kubectl describe nodes <node-name>でノードの詳細を表示する。


terminal

Name:                   my-node-a

...
Capacity:
pods: 40
cpu: 2
memory: 7679608Ki
...
Non-terminated Pods: (10 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits
───────── ──── ──────────── ────────── ─────────────── ─────────────
default my-pod-a 400m (20%) 400m (20%) 512Mi (6%) 512Mi (6%)
default my-pod-b 400m (20%) 400m (20%) 512Mi (6%) 512Mi (6%)
default my-pod-c 400m (20%) 400m (20%) 512Mi (6%) 512Mi (6%)
default my-pod-d 400m (20%) 400m (20%) 512Mi (6%) 512Mi (6%)
Allocated resources:
(Total limits may be over 100%, i.e., overcommitted. More info: http://releases.k8s.io/HEAD/docs/user-guide/compute-resources.md)
CPU Requests CPU Limits Memory Requests Memory Limits
──────────── ────────── ─────────────── ─────────────
1600m (80%) 1600m (80%) 2048Mi (24%) 2048Mi (24%)
No events.

CPUリソースの80%を使っている状況なので、残りの20%しか余剰がない事がわかる。


解決策1. コンテナに割り当てるリソース量を適切に設定する。

一つ一つのポッドに割り当てているCPUリソースを削減してもアプリケーションが機能する場合は、要求するリソースを削減する。3


rc.yml

apiVersion: v1

kind: ReplicationController
...
spec:
template:
spec:
containers:
- name: app
resources:
requests:
cpu: 100m # <- ココ


解決策2. レプリカの数を適切に設定する。

レプリカの数を削減してもアプリケーションが機能する場合は、立ち上げるポッドの数を減らす。


rc.yml

apiVersion: v1

kind: ReplicationController
...
spec:
replicas: 2 # <- ココ


解決策3. ノード数を増やす。

解決策1,2のように調整で済むような状況ではなく、絶対的にリソースが足りない場合はgcloud container cluster resize <cluster-name> --size <num-nodes>4でノードを増やす。


OOMKilled


原因

コンテナに割り当てられたメモリを使いきって、コンテナ内で OOM Killer にプロセスを殺されている。


解決

.spec.template.spec.containers[].resources.limits.memoryの数値を増やして、コンテナが使用できるメモリの上限を上げる。3


rc.yml

apiVersion: v1

kind: ReplicationController
...
spec:
template:
spec:
containers:
- name: app
resources:
limits:
memory: 1024Mi # <- ココ


修正のオペレーション

原因が判明したら起動に至らなかったレプリケーションコントローラを削除する。その後、ローリングアップデートを行うと、現在稼働しているレプリケーションコントローラが生成したポッドが問題解決したレプリケーションコントローラが生成するポッドに順次置き換えられる。


terminal

kubectl delete rc <failed-rc>

kubectl rolling-update rc --filename=rc.yml


さいごに

構成を変更しない限りはこのようなエラーに出会うことは滅多にないので、一度構成を固定した後は安定的にローリングアップデートできる。Kubernetesのスケーラブルなコンテナマネジメントという特徴からみたリソース管理の雑感としては、カツカツのリソースでやりくりしているといざ水平オートスケールが必要とされる状況でリソースがなくてスケールされない事態が起こりうるので、カツカツのリソースの中でポッドのリソースを細かく設定して運用するよりはお金が許す限りはノードはサクサクと増やしていくのが良いと感じている。

:moneybag: is :whale2::whale2::whale2::whale2::whale2::whale2::whale2::whale2::whale2::whale2:





  1. うまく起動しない構成ではなんとか動かそうとコンテナの構築フローが何度もリトライされる。リトライ時にポッドが一瞬正常に起動してしまいその後落ちるという状況になっている場合、外部からもその不安定なポッドに一瞬アクセスが可能になってしまい、何かがうまくいっていない状況が外部から観測できる状態になる事もある。 



  2. RestartPolicy - The life of a pod - Kubernetes Docs 



  3. Compute Resources - Kubernetes Docs 



  4. Resizing a Container Cluster - Container Engine — Google Cloud Platform