Help us understand the problem. What is going on with this article?

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 

minodisk
冷やし中華はじめました
resily
OKR導入・運用改善コンサルティングと、自社開発のクラウドOKRツール『Resily』を展開するスタートアップ
https://resily.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away