Kubernetes 1.18 で kubectl alpha debug を使って Pod をデバッグする

## はじめに

この記事は、先日リリースされた Kubernetes 1.18 [^1] でエフェメラルコンテナ関連機能の実装がほぼ完了して、デバッグ目的のエフェメラルコンテナ [^2] を作成する `kubectl alpha debug` コマンドが追加されたということで、これらを使って Pod をデバッグするまでの流れをまとめたものとなります。

エフェメラルコンテナ? kubectl debug コマンドってそもそもなんだ? :thinking:  となった方は [Kubernetes 1.18: SIG-CLI の変更内容](https://qiita.com/superbrothers/items/eef275853b0c2d14e95c) で詳細に解説されていますので、まずはそちらを一読するのが良いかと思います。

それでは実際にやっていきます。

## エフェメラルコンテナ機能を有効にしたクラスタ作成

Feature Gates で `EphemeralContainers` を指定してエフェメラルコンテナ機能を有効にして Kubernetes 1.18 クラスタを作成します。

```shell
minikube start --kubernetes-version=v1.18.0 --feature-gates=EphemeralContainers=true
```

`kubectl` のバージョンが 1.18 未満では `kubectl alpha debug` がサポートされていないので、以下のように 1.18 にバージョンアップしておくことも忘れずに。

```shell
$ kubectl version --client -o yaml
clientVersion:
  buildDate: "2020-03-26T06:16:15Z"
  compiler: gc
  gitCommit: 9e991415386e4cf155a24b1da15becaa390438d8
  gitTreeState: clean
  gitVersion: v1.18.0
  goVersion: go1.14
  major: "1"
  minor: "18"
  platform: darwin/amd64
```

## 検証用の Pod 作成

`k8s.gcr.io/pause:3.1` [^3] という scratch イメージ（中身が空のコンテナイメージで）がベースとなった、デバッグツールが無ければシェルさえも含まれていないイメージを使って検証用の Pod を作成します。1.16 時点でプロセス名前空間を共有するために必要だった `shareProcessNamespace: true` は 1.18 での機能追加により指定不要となっています。

```shell
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: config
data:
  demo.conf: demo
---
apiVersion: v1
kind: Pod
metadata:
  name: ephemeral-demo
spec:
  containers:
  - name: ephemeral-demo-container
    image: k8s.gcr.io/pause:3.1
    env:
    - name: DEMO
      value: demo
    volumeMounts:
    - name: config
      mountPath: /config
  volumes:
  - name: config
    configMap:
      name: config
EOF
```

Pod が作成された後で `kubectl exec` でシェルさえ起動できない状態であることを確認します。これでは Pod 内で何かしらの問題が起きたときにトラブルシューティングを実施できません。

```shell
$ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
ephemeral-demo   1/1     Running   0          22s

$ kubectl exec -it ephemeral-demo -- sh
OCI runtime exec failed: exec failed: container_linux.go:349: starting container process caused "exec: \"sh\": executable file not found in $PATH": unknown
command terminated with exit code 126
```

## kubectl alpha debug でのデバッグ

`kubectl alpha debug` を実行するとエフェメラルコンテナが作成されて Pod にアタッチされます。`-it` が指定されているとシェルが起動する仕様です。実行時には `--image` でエフェメラルコンテナのイメージを `--container` でエフェメラルコンテナ名を `--target` でプロセス名前空間を共有するコンテナを指定することができます。

```shell
$ kubectl alpha debug -it ephemeral-demo --image=busybox --container=debugger --target=ephemeral-demo-container
If you don't see a command prompt, try pressing enter.
/ #
```

プロセス名前空間を共有しているのでエフェメラルコンテナに含まれる ps コマンドで `ephemeral-demo-container` コンテナ内で実行されている /pause プロセスを確認できました。

```shell
/ # ps --help 2>&1 | head -n 1
BusyBox v1.31.1 (2020-03-09 21:47:38 UTC) multi-call binary.

/ # ps -ef
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    6 root      0:00 sh
   11 root      0:00 ps -ef
```

エフェメラルコンテナに含まれるデバッグツールで `ephemeral-demo-container` コンテナに対して様々なオペレーションが行えることが確認できました。トラブルシューティングで実施したい作業に合わせてイメージを指定すると良いと思います。

```shell
/ # netstat
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags       Type       State         I-Node Path

/ # nslookup google.com
Server:		10.96.0.10
Address:	10.96.0.10:53

Non-authoritative answer:
Name:	google.com
Address: 172.217.24.142
```

`/proc/<PID>/root` 直下を参照することで `ephemeral-demo-container` コンテナのファイルシステムにアクセスできることが確認できました。[^4]

```shell
/ # ps -ef
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    6 root      0:00 sh
   11 root      0:00 ps -ef

/ # ls -l /proc/1/root/
total 740
drwxrwxrwx    3 root     root          4096 Apr  3 19:46 config
drwxr-xr-x    5 root     root           360 Apr  3 19:46 dev
drwxr-xr-x    2 root     root          4096 Apr  3 19:46 etc
-rwxr-xr-x    1 root     root        742472 Dec 20  2017 pause
dr-xr-xr-x  144 root     root             0 Apr  3 19:46 proc
dr-xr-xr-x   12 root     root             0 Apr  3 19:46 sys
drwxr-xr-x    3 root     root          4096 Apr  3 19:46 var

/ # cat /proc/1/root/config/demo.conf
demo
```

`/proc/<PID>/environ` 直下を参照することで `ephemeral-demo-container` コンテナの環境変数を参照できることが確認できました。

```shell
/ # cat -e /proc/1/environ | sed -e 's/\^@/\n/g'
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=ephemeral-demo
DEMO=demo
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
```

エフェメラルコンテナを抜けたあとで Pod マニフェストを確認してみると、エフェメラルコンテナ関連の Spec と Status が追加されていることが確認できます。

```shell
/ # exit
Session ended, resume using 'kubectl alpha attach ephemeral-demo -c debugger -i -t' command when the pod is running

$ kubectl get pods ephemeral-demo -o yaml
apiVersion: v1
kind: Pod
metadata:
  name: ephemeral-demo
  namespace: default
spec:
  ...
  ephemeralContainers:
  - image: busybox
    imagePullPolicy: IfNotPresent
    name: debugger
    resources: {}
    stdin: true
    targetContainerName: ephemeral-demo-container
    terminationMessagePolicy: File
    tty: true
  ...
status:
  ...
  ephemeralContainerStatuses:
  - containerID: docker://afc7f76a5ef59bdb514f081d045827eabde834b0c97a91d8e03233e40fa91ba6
    image: busybox:latest
    imageID: docker-pullable://busybox@sha256:b26cd013274a657b86e706210ddd5cc1f82f50155791199d29b9e86e935ce135
    lastState: {}
    name: debugger
    ready: false
    restartCount: 0
    state:
      terminated:
        containerID: docker://afc7f76a5ef59bdb514f081d045827eabde834b0c97a91d8e03233e40fa91ba6
        exitCode: 0
        finishedAt: "2020-04-03T19:47:56Z"
        reason: Completed
        startedAt: "2020-04-03T19:46:48Z"
...
```

最後に Pod でエラーが発生して起動していない状態で `kubectl alpha debug` を実行するとどうなるか見ていきます。以下のマニフェストを適用して、crash コマンドが存在しないことによりエラーとなる Pod を作成します。

```shell
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: ephemeral-crash-demo
spec:
  containers:
  - name: ephemeral-crash-demo-container
    image: k8s.gcr.io/pause:3.1
    command:
    - "crash"
EOF
```

エラーになっていることを確認して `kubectl alpha debug` を実行すると、`--target` で指定した `ephemeral-crash-demo-container` コンテナに接続できずに出力が何も得られないまま無言状態となります。

```shell
$ kubectl get pods
NAME                   READY   STATUS             RESTARTS   AGE
ephemeral-crash-demo   0/1     CrashLoopBackOff   1          25s

$ kubectl alpha debug -it ephemeral-crash-demo --image=busybox --container=debugger --target=ephemeral-crash-demo-container
```

`ephemeral-crash-demo-container` コンテナが起動していないのでデバッグできないのは当たり前ですが、`--target` なしにすることで Pod のネットワークをデバッグすることは可能になっております。具体的なケースがパッと浮かんではきませんが、これでなにかしらトラブルシューティングは行えるかもしれません。

```shell
$ kubectl alpha debug -it ephemeral-crash-demo --image=busybox --container=no-taret-debugger
If you don't see a command prompt, try pressing enter.
/ # ps -ef
PID   USER     TIME  COMMAND
    1 root      0:00 sh
    6 root      0:00 ps -ef
```

以上が kubectl alpha debug でのデバッグの流れとなります。

## さいごに

ここまでで Kubernetes 1.18 で `kubectl alpha debug` を使って Pod をデバッグする流れをまとめてみました。alpha 機能なので本番環境での使用は出来ませんが非常に便利そうなので、minikube や kind などの開発用途のクラスタで活用していこうかと思います :hugging:

## 参考資料

- [Debugging with an ephemeral debug container - Kubernetes Documentation v1.18](https://kubernetes.io/docs/tasks/debug-application-cluster/debug-running-pod/#ephemeral-container)
- [New in Kubernetes 1.18 — kubectl alpha debug](https://medium.com/icetek/new-in-kubernetes-1-18-kubectl-alpha-debug-2f5243581025)

## おまけ: 今後改善されると良いなと思ったところ

現時点で alpha なので今後改善されていくであろうという前提で書き留めておきます。

### その1: 作成済みのエフェメラルコンテナに再アタッチすることができない

エフェメラルコンテナから抜ける際にサジェストされる `kubectl alpha attach` コマンドが現時点でサポートされておらず、作成済みのエフェメラルコンテナに再アタッチすることができない。

```shell
$ kubectl alpha debug -it ephemeral-demo --image=busybox --container=debugger --target=ephemeral-demo-container

If you don't see a command prompt, try pressing enter.
/ # exit
Session ended, resume using 'kubectl alpha attach ephemeral-demo -c debugger -i -t' command when the pod is running

$ kubectl alpha attach ephemeral-demo -c debugger -i -t
Error: unknown shorthand flag: 'c' in -c
See 'kubectl alpha --help' for usage.
```

同じコマンドを再実行するとエフェメラルコンテナ名が重複していると怒られるので、`--container` の値を変更して新たにエフェメラルコンテナを作成することは可能な状態であることは把握した。

```shell
$ kubectl alpha debug -it ephemeral-demo --image=busybox --container=debugger --target=ephemeral-demo-container
error: error updating ephemeral containers: Pod "ephemeral-demo" is invalid: spec.ephemeralContainers[1].name: Duplicate value: "debugger"

$ kubectl alpha debug -it ephemeral-demo --image=busybox --container=new-debugger --target=ephemeral-demo-container
If you don't see a command prompt, try pressing enter.
/ #
```

### その2: エフェメラルコンテナ名なしは便利だが Pod マニフェストが肥大化する

`--container` が指定されていない場合にはエフェメラルコンテナ名が自動生成されるので、その1 のようなオペレーションが不要になるので手間が掛からない。

```shell
$ kubectl alpha debug -it ephemeral-demo --image=busybox --target=ephemeral-demo-container

Defaulting debug container name to debugger-xsztc.
If you don't see a command prompt, try pressing enter.
/ # Session ended, resume using 'kubectl alpha attach ephemeral-demo -c debugger-xsztc -i -t' command when the pod is running

$ kubectl alpha debug -it ephemeral-demo --image=busybox --target=ephemeral-demo-container

Defaulting debug container name to debugger-s5p2h.
If you don't see a command prompt, try pressing enter.
/ # Session ended, resume using 'kubectl alpha attach ephemeral-demo -c debugger-s5p2h -i -t' command when the pod is running

$ kubectl alpha debug -it ephemeral-demo --image=busybox --target=ephemeral-demo-container

Defaulting debug container name to debugger-z9gbv.
If you don't see a command prompt, try pressing enter.
/ # Session ended, resume using 'kubectl alpha attach ephemeral-demo -c debugger-z9gbv -i -t' command when the pod is running
```

ただし、Pod マニフェストに実行数分のエフェメラルコンテナ関連の Spec. Status が追加されるので Pod マニフェストが肥大化していきます。Pod マニフェストの肥大化が Kubernetes クラスタにどのような影響を及ぼすのかはわかりませんが良くはないような気がしています。

```shell
$ kubectl get pods ephemeral-demo -o yaml
apiVersion: v1
kind: Pod
metadata:
  ...
  name: ephemeral-demo
  namespace: default
  ...
spec:
  ...
  ephemeralContainers:
  - image: busybox
    name: debugger-xsztc
    ...
  - image: busybox
    name: debugger-s5p2h
    ...
  - image: busybox
    name: debugger-z9gbv
    ...
  ...
status:
  ephemeralContainerStatuses:
  - containerID: docker://aa66c73b8811367fdef8c440dff5606f91e3dfe7a05424754cbc463705df1d40
    image: busybox:latest
    imageID: docker-pullable://busybox@sha256:b26cd013274a657b86e706210ddd5cc1f82f50155791199d29b9e86e935ce135
    name: debugger-s5p2h
    ...
  - containerID: docker://1d7223f454e1439fa61a5e81da855160aa67fc0e49d19b8af446e9a3921a4f26
    image: busybox:latest
    imageID: docker-pullable://busybox@sha256:b26cd013274a657b86e706210ddd5cc1f82f50155791199d29b9e86e935ce135
    name: debugger-xsztc
    ...
  - containerID: docker://9c95e166c151f5f7113843680d6afb6d03eb70dfec5c81c5d2179f2610eca899
    image: busybox:latest
    imageID: docker-pullable://busybox@sha256:b26cd013274a657b86e706210ddd5cc1f82f50155791199d29b9e86e935ce135
    name: debugger-z9gbv
    ...
```

[^1]: https://github.com/kubernetes/kubernetes/releases/tag/v1.18.0
[^2]: https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/
[^3]: https://github.com/kubernetes/kubernetes/blob/v1.18.0/build/pause/Dockerfile
[^4]: OS に詳しくないので正確な情報はわかりませんが `/proc/<PID>` 配下のファイルを使用したオペレーションはエフェメラルコンテナのディストリビューションによって仕様が異なるかと思います。