CrashLooping アプリケーションのデバッグ

KubernetesBeginner
オンラインで実践に進む

はじめに

これまでの実験では、ログ、イベント、kubectl exec、ポートフォワーディング、およびプローブについて学習しました。CrashLoop(クラッシュループ)状態にあるアプリケーションのデバッグには、これら複数のスキルを組み合わせる必要があります。Pod が起動し、コンテナが終了し、Kubernetes がそれを再起動するというサイクルの中で、何かを変更する前にその証拠を読み解くことが重要です。

この実験では、必要な環境変数が不足しているために終了してしまうアプリケーションをデプロイします。再起動の挙動を観察し、現在および過去のログを確認し、イベントを調査して Deployment 内の設定不足を特定します。その後、Deployment を修正し、アプリケーションが正常にアクセス可能になることを検証します。

CrashLoop デバッグ用名前空間の準備

このステップでは、CrashLoop デバッグの演習を行うためのクリーンな名前空間を準備します。

/home/labex/project から開始します。このディレクトリ内でアプリケーションのマニフェストを作成および更新します。

mkdir -p /home/labex/project
cd /home/labex/project

Kubernetes API サーバーに到達可能か確認します。VM が起動した直後でノードの準備ができていない場合は、数秒待ってから再度コマンドを実行してください。

kubectl get nodes --request-timeout=5s

期待される出力例:

NAME         STATUS   ROLES           AGE   VERSION
labex-v135   Ready    control-plane   ...   v1.35...

クラスターのネットワーキング Pod が Ready になるまで待ちます。修正後のアプリケーションは、後ほど Service を通じてテストされます。

kubectl wait --namespace kube-system --for=condition=Ready pod -l k8s-app=calico-node --timeout=120s

期待される出力:

pod/calico-node-... condition met

ckad-prep-crashloop という名前の名前空間を作成し、現在の名前空間として設定します。

kubectl create namespace ckad-prep-crashloop
kubectl config set-context --current --namespace=ckad-prep-crashloop

現在の名前空間を確認します。

kubectl config view --minify --output 'jsonpath={..namespace}'; echo

期待される出力:

ckad-prep-crashloop

CrashLoop のデプロイと観察

このステップでは、繰り返し終了するアプリケーションをデプロイします。

コンテナのスクリプトは環境変数 APP_MESSAGE を必要とします。この変数が不足している場合、スクリプトは明確なエラーを出力してステータス 1 で終了します。Kubernetes はコンテナを再起動し、これがデバッグ対象となる CrashLoop の挙動を引き起こします。

crash-demo.yaml を作成します。

cat > crash-demo.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: crash-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: crash-demo
  template:
    metadata:
      labels:
        app: crash-demo
    spec:
      terminationGracePeriodSeconds: 0
      containers:
      - name: web
        image: busybox:1.36
        command:
        - /bin/sh
        - -c
        - |
          if [ -z "${APP_MESSAGE:-}" ]; then
            echo "startup failed: APP_MESSAGE is required"
            exit 1
          fi
          mkdir -p /www
          echo "$APP_MESSAGE" > /www/index.html
          httpd -f -p 8080 -h /www
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: crash-demo
spec:
  selector:
    app: crash-demo
  ports:
  - port: 80
    targetPort: 8080
EOF

マニフェストを適用します。

kubectl apply -f crash-demo.yaml

期待される出力:

deployment.apps/crash-demo created
service/crash-demo created

Kubernetes が失敗しているコンテナを再起動するまで少し待ち、Pod を調査します。

sleep 12
kubectl get pods -l app=crash-demo

期待される出力には、正常ではない Pod が含まれます。タイミングによってステータスは Error または CrashLoopBackOff になります。

NAME                          READY   STATUS             RESTARTS   AGE
crash-demo-...                0/1     CrashLoopBackOff   ...        ...

Pod 名を保存し、コンテナの状態を調査します。JSONPath を使用すると、オブジェクト全体をスクロールすることなく、重要なフィールドのみを確認できます。最初の値は再起動回数です。2 番目の値は待機理由ですが、Pod が CrashLoopBackOff で待機中ではなく Error 状態のタイミングで取得した場合、空になることがあります。

CRASH_POD="$(kubectl get pods -l app=crash-demo -o jsonpath='{.items[0].metadata.name}')"
echo "$CRASH_POD"
kubectl get pod "$CRASH_POD" -o jsonpath='{.status.containerStatuses[0].restartCount}{" "}{.status.containerStatuses[0].state.waiting.reason}{"\n"}'

期待される出力には 0 より大きい再起動回数が含まれます。タイミングによっては、理由は空であるか、CrashLoopBackOff のような値が表示されます。

2 

または:

... CrashLoopBackOff

ログとイベントによる原因の特定

このステップでは、ログ、過去のログ、およびイベントを使用して、コンテナが終了する理由を特定します。

コンテナが急速に再起動している場合、kubectl logs は現在のコンテナの試行を表示し、kubectl logs --previous は以前に終了したコンテナの試行を読み取ります。CrashLoop のデバッグにおいて、--previous は有用な起動エラーを明らかにするための重要なコマンドです。

現在のログを読み取ります。

kubectl logs "$CRASH_POD" --container web

期待される出力には以下が含まれます。

startup failed: APP_MESSAGE is required

以前のコンテナログを読み取ります。Kubernetes が最後に終了したコンテナのログを保持していれば、以前の試行からの同じ起動エラーが表示されます。高速な CrashLoop の場合、ランタイムが以前のログを取得できないと報告することがあります。その場合でも、重要なデバッグのポイントは「利用可能な場合は --previous を使用し、常に現在のログや Pod イベントと比較する」ということです。

kubectl logs "$CRASH_POD" --container web --previous

期待される出力には以下が含まれることが多いです。

startup failed: APP_MESSAGE is required

以下のようなメッセージが表示されることもあります。

unable to retrieve container logs for ...

Pod のイベントを調査します。イベントには、Kubernetes が失敗したコンテナを再起動している様子が表示されます。

kubectl describe pod "$CRASH_POD" | sed -n '/Events:/,$p'

期待される出力には BackOff や Started などのイベントが含まれます。

Warning  BackOff  ...  Back-off restarting failed container

次に、Deployment の環境設定を調査します。env リストが欠落していることが、ログメッセージの理由です。

kubectl get deployment crash-demo -o jsonpath='{.spec.template.spec.containers[0].env}'; echo

期待される出力は空です。


Deployment の修正と復旧の検証

このステップでは、不足している環境変数を修正し、アプリケーションが復旧することを確認します。

エラーメッセージには不足している設定 APP_MESSAGE が明記されていました。コンテナのコマンドを変更する代わりに、Deployment に必要な環境変数を設定します。Pod テンプレートが変更されるため、Kubernetes は新しい ReplicaSet を作成します。

環境変数を設定します。

kubectl set env deployment/crash-demo APP_MESSAGE=crashloop-fixed

期待される出力:

deployment.apps/crash-demo env updated

ロールアウトを待ちます。

kubectl rollout status deployment/crash-demo --timeout=120s

期待される出力:

deployment "crash-demo" successfully rolled out

新しい Pod を調査します。Running かつ Ready になっているはずです。

kubectl get pods -l app=crash-demo

期待される出力:

NAME                          READY   STATUS    RESTARTS   AGE
crash-demo-...                1/1     Running   0          ...

クライアント Pod を作成し、Service をテストします。

kubectl run crash-client --image=busybox:1.36 --restart=Never --command -- sleep 3600
kubectl wait --for=condition=Ready pod/crash-client --timeout=120s
kubectl exec crash-client -- wget -qO- http://crash-demo

期待される出力:

crashloop-fixed

最後に、Deployment に環境変数が含まれていることを確認します。

kubectl get deployment crash-demo -o jsonpath='{.spec.template.spec.containers[0].env[0].name}{"="}{.spec.template.spec.containers[0].env[0].value}{"\n"}'

期待される出力:

APP_MESSAGE=crashloop-fixed

まとめ

ステージ 5 で紹介された証拠ソースを使用して、CrashLoop 状態のアプリケーションをデバッグしました。失敗している Pod のステータスを観察し、現在および過去のコンテナログを読み取り、イベントを調査し、Deployment 内の環境変数の不足を特定しました。その後、kubectl set env を使用して Pod テンプレートを修正し、Service を通じてアプリケーションが復旧したことを検証しました。