kubectl debugを使ってKubernetesのコンテナをデバッグする
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)
今回はサイドカーコンテナとしてデバッグ用のコンテナがデプロイされています。
また、プロセス名前空間共有を表すshareProcessNamespace
にtrue
が指定されています。
これにより、先程のようにデバッグコンテナから対象コンテナのファイルシステムにアクセスできるようになっています。
コピーしたデバッグ用のPodは自動では削除されませんので、デバッグ作業が終わったら手動で削除する必要があります。
kubectl delete pod debug-sample-app
まとめ
#簡単ですが、kubectl debug
の機能をご紹介しました。
始めにも触れましたが、セキュリティリスク削減や軽量化が推し進められ、それに伴ってコンテナのデバッグ方法も変遷していると感じます。
コンテナを使った開発では、この辺りのやり方もウォッチしておかないといけないなと感じました。
参照資料