kubectl debugを使ってKubernetesのコンテナをデバッグする

| 7 min read
Author: noboru-kudo noboru-kudoの画像

2022-08-23にKubernetesのv1.25がリリースされました。

PSP(Pod Security Policy)の削除等、多くの変更がありますが、Ephemeral ContainersがBetaからStableバージョンとなりました。
この機能はkubectl debugコマンド[1]で使用できますが、あまり使ったことがなく、これを機に改めて使い方を調べてみましたのでご紹介します。

一般的にKubernetes上で動作するPodのコンテナをデバッグするにはkubectl execを使うことが多いかと思います。
しかし、昨今はセキュリティリスク低減や軽量化による起動速度向上の目的で、Distrolessイメージを使うことが多くなってきています[2]

この欠点はデバッグが難しいことです。Distrolessイメージにはシェルを含めて余計なものは一切含まれていませんので、kubectl execでコンテナに入って状態を調べることはできません。

ここでは、これを解消するためのkubectl debugの使い方を簡単に見てみます。

DistrolessイメージでPodデプロイ

#

まず今回デバッグ対象のPodをデプロイします。
ここでは、以下のPodを用意しました。

apiVersion: v1
kind: ConfigMap
metadata:
  name: server
data:
  server.js: |
    const http = require('http');
    const server = http.createServer((req, res) => {
      res.statusCode = 200;
      res.end('hello sample app!\n');
    });
    server.listen(8080, '0.0.0.0', () => console.log('Server running'));
---
apiVersion: v1
kind: Pod
metadata:
  name: sample-app
spec:
  containers:
    - name: sample-app
      image: gcr.io/distroless/nodejs:16 # Distrolessイメージ
      ports: [ { name: http, containerPort: 8080 } ]
      args: [ "/app/server.js" ]
      volumeMounts: [ { mountPath: /app, name: server } ]
  volumes:
    - name: server
      configMap:
        name: server

Node.jsのDistrolessイメージを使用したものです。アプリ自体(ConfigMapからボリュームマウント)は固定文字列を返す単純なHTTPサーバー(server.js)です。
これに対してkubectl execを使ってデバッグしようとすると、以下のようなエラーが発生します。

kubectl exec sample-app -it -- sh
> OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: "sh": executable file not found in $PATH: unknown
> command terminated with exit code 126

このようにDistrolessコンテナにはシェル(sh)がないため、kubectl execでは何もできません。

Ephemeral Containersでコンテナをデバッグする

#

その名の通りEphemeral Containersは一時的なコンテナで、デバッグ対象のPodにアタッチすることで、Pod内の他のコンテナにアクセスできます。
kubectl debugにデバッグ対象のPod名を指定し、Ephemeral Containersでデバッグに使う任意のイメージ(--image)を指定します。
以下のように使います。

curlでAPIにアクセスする

#

curlコマンドを持つイメージ(curlimages/curl)をEphemeral ContainersとしてPod内に配置し、Node.jsのHTTPサーバーにアクセスしてみます。

kubectl debug sample-app -it --image=curlimages/curl -- curl localhost:8080
> Defaulting debug container name to debugger-wj662.
> hello sample app!

期待通りのレスポンスが返ってきました。
サイドカーコンテナ同様に、Ephemeral Containersはアプリコンテナと同一ネックワークを共有していますので、ローカルアクセス(localhost:8080)できます。

コンテナのファイルシステムを調べる

#

ConfigMapからボリュームマウントして配置したserver.jsを、Ephemeral Containers経由で確認してみます。
この場合は、コンテナをまたがってファイルシステムを参照する必要がありますので、--targetオプションにプロセス名前空間を共有するコンテナ(sample-app)を指定します。

# Ephemeral Containersでシェル(sh)実行
kubectl debug sample-app -it --image=alpine --target sample-app -- sh
> Targeting container "sample-app". If you don't see processes from this container it may be because the container runtime doesn't support this feature.
> Defaulting debug container name to debugger-jgp9q.
> If you don't see a command prompt, try pressing enter.
/ # 

以降は、Ephemeral Containers内での操作です。

# Node.jsのプロセス確認
ps x
> PID   USER     TIME  COMMAND
>    1 root      0:00 /nodejs/bin/node /app/server.js
>   31 root      0:00 sh
>   38 root      0:00 ps x
# ファイルシステムアクセス
ls -l /proc/1/root/app
> total 0
> lrwxrwxrwx    1 root     root            16 Aug 24 07:59 server.js -> ..data/server.js

まず、psコマンドで対象コンテナのプロセスを特定します。--targetでプロセス名前空間を共有していますので、対象コンテナのプロセスを参照できます[3]
共有したコンテナのファイルシステムは/proc/{pid}/rootより確認できます。

デバッグ対象Podのマニフェスト

#

Ephemeralコンテナの状態は、デバッグ対象のPodから確認できます(kubectl get pod sample-app -o yaml)。
Podのspecは以下のようになっていました。

spec:
  containers:
    - name: sample-app
      # 省略
  ephemeralContainers:
    - command:
        - curl
        - localhost:8080
      image: curlimages/curl
      imagePullPolicy: Always
      name: debugger-wj662
      # 省略
    - command:
        - sh
      image: alpine
      imagePullPolicy: Always
      name: debugger-jgp9q
      # 省略

spec.ephemeralContainersが追加され、終了済みのものも含めて実行したkubectl debugの内容が定義されています。
statusにも、コンテナの状態が追加されます。

status:
  containerStatuses:
    - # 省略
  ephemeralContainerStatuses:
    - containerID: docker://.....
      image: alpine:latest
      name: debugger-jgp9q
      ready: false
      restartCount: 0
      state:
        running:
          startedAt: "2022-08-24T09:06:24Z"
    - containerID: docker://.....
      image: curlimages/curl:latest
      name: debugger-wj662
      ready: false
      restartCount: 0
      state:
        terminated:
          containerID: docker://.....
          exitCode: 0
          finishedAt: "2022-08-24T09:06:14Z"
          reason: Completed
          startedAt: "2022-08-24T09:06:14Z"

Ephemeral Containersのステータスが追加されています。
上記はcurlを実行したコンテナ(debugger-wj662)は完了済み(Completed)で、シェルを実行しているコンテナ(debugger-jgp9q)はまだ実行中であることが分かります。

Podをコピーしてサイドカーコンテナからデバッグする

#

Ephemeral Containersは便利ですが、内部的にはPodのspecを書き換えていますので、商用環境ではあまりやりたくない(or できない)かもしれません。
kubectl debugでは、Podをコピーして、その中にサイドカーとしてデバッグコンテナを実行することも可能です。
コピーするといってもPodのラベルはコピーされませんので、既存のServiceやReplicaSet等のラベルセレクターの対象とはなりません(実際のトラフィックは来ない)。

この場合は以下のように実行します。

kubectl debug sample-app -it --image=alpine \
  --share-processes --copy-to debug-sample-app -- sh

ここでは--share-processesでプロセス名前空間を共有するよう指定し、--copy-toでコピー先のPodを指定します。
その後のデバッグ操作は先程と同様です。

ただし、今回はEphemeral Containersではなく、サイドカーコンテナになっています。
以下はkubectl get podした内容です。

NAME               READY   STATUS    RESTARTS   AGE
debug-sample-app   2/2     Running   0          27s
sample-app         1/1     Running   0          35m

debug-sample-appというPodが生成されていることが分かります。READYでは2/2となっており、2つのコンテナが実行中となっています。
Podのマニフェストは以下のようになります(関連部分抜粋・編集)。

spec:
  containers:
    - name: sample-app
      image: gcr.io/distroless/nodejs:16
      imagePullPolicy: IfNotPresent
      args:
        - /app/server.js
    - name: debugger-nglnx
      image: alpine
      imagePullPolicy: Always
      command:
        - sh
  shareProcessNamespace: true # プロセス名前空間共有(--share-processes)

今回はサイドカーコンテナとしてデバッグ用のコンテナがデプロイされています。
また、プロセス名前空間共有を表すshareProcessNamespacetrueが指定されています。
これにより、先程のようにデバッグコンテナから対象コンテナのファイルシステムにアクセスできるようになっています。

コピーしたデバッグ用のPodは自動では削除されませんので、デバッグ作業が終わったら手動で削除する必要があります。

kubectl delete pod debug-sample-app

まとめ

#

簡単ですが、kubectl debugの機能をご紹介しました。
始めにも触れましたが、セキュリティリスク削減や軽量化が推し進められ、それに伴ってコンテナのデバッグ方法も変遷していると感じます。
コンテナを使った開発では、この辺りのやり方もウォッチしておかないといけないなと感じました。


参照資料


  1. kubectl v1.18~ではkubectl alpha debugでv1.20~からはkubectl debugとして利用できました。 ↩︎

  2. Kubernetes本体もコンテナのベースイメージにはDistrolessを使っています。 ↩︎

  3. プロセス名前空間の共有についての詳細は公式ドキュメントを参照しくてださい。 ↩︎

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。