Kubernetes: kubectl debug node でノードをデバッグする

Kubernetes クラスタのトラブルシュートで、ssh でノードに直接アクセスしてデバッグしたい場面があります。しかし、OS アカウントやネットワーク管理の都合で、ssh の利用が難しい場合があります。そんなとき、 `kubectl debug node` を利用すると、ssh に相当する操作を`kubectl` で簡単に行えます。

## デモ
`kubectl debug node` でノードに Pod を作成し、ホスト上のコマンド(`systemctl`, `htop`) を実行したデモです。

<script id="asciicast-e0Ab0r9W02MuhtcZ1pEVtRkLe" src="https://asciinema.org/a/e0Ab0r9W02MuhtcZ1pEVtRkLe.js" async></script>

## kubectl debug node の使い方
`kubectl debug pod` (Ephemeral Containers) と違い、`kubectl debug node` は[特権を持った Pod を作成](https://github.com/kubernetes/kubectl/blob/v0.21.0/pkg/cmd/debug/debug.go#L474-L504)しているだけなので、権限さえあれば Kubernetes クラスタ側の設定は不要です。

使い方は非常にシンプルで、ノード名と利用するコンテナイメージを指定するだけです。`kubectl exec` のように `-it` を指定すると、インタラクティブに操作できます。なお実行には、クラスタの管理者権限 (RBAC の `cluster-admin` 相当) が必要になります。

以下は [busybox](https://hub.docker.com/_/busybox) イメージを指定した例です。[後述](#chroot-でホストのコマンドを使う)のように chroot でホストのコマンド(シェル)を利用する場合は、busybox など軽量なイメージがおすすめです。

```console
$ kubectl debug node/<NODE_NAME> -it --image=busybox
```

上記を実行後、`kubectl exec` のように Pod のシェルをそのまま操作できるようになります。`/host` 以下にノードのルートディレクトリがマウントされているため、ノード上のファイルシステム全てにアクセスできます。

```console
root@<NODE_NAME>:/# ls /host
# ノードのルートディレクトリが表示される
bin             initrd.img      media           run             tmp
boot            initrd.img.old  mnt             sbin            usr
dev             lib             opt             snap            var
etc             lib64           proc            srv             vmlinuz
home            lost+found      root            sys             vmlinuz.old
```

デバッグ用に作成される Pod は、以下の権限を持っています。これは、多くの環境でノードの root に相当する権限を持った Pod であるため、扱いは十分に注意しましょう。

- HostPath: ホストのルートディレクトリを、Pod の `/host` にマウントしている
- HostPID: ホストのプロセス空間を利用。`ps` などで直接プロセスが見れる
- HostIPC: ホストの IPC 名前空間を使用
- HostNetwork: ホストのネットワークを利用

ただし、`privileged` は指定されていないため、特別な Linux Capabilities が必要なコマンドは動かない場合があります。([後述](#特別な-capabilities-が必要な場合))

終了後、この Pod は手動で削除する必要があります。Kubernetes 1.21 時点では、終了時に Pod を削除する `--rm` のようなオプションは存在していません。

## chroot でホストのコマンドを使う
デバッグ用 Pod のシェルからホストのコマンドをそのまま実行すると、共有ライブラリなどパスの問題でエラーが出て実行できない場合があります。

```console
# /host/bin/systemctl status
/host/bin/systemctl: error while loading shared libraries: libsystemd-shared-237.so: cannot open shared object file: No such file or directory
```

Pod のシェル内で、`/host` に `chroot` し、`/` をホストのルートディレクトリにすると正常に動くようになります。

```console
# chroot /host
```

コマンドが正しく動くようになりました :tada:。chroot すると ssh したときと同様、ホストの環境がそのまま利用できるので非常に便利です。

```console
# systemctl status
● demo-tkusumi-worker-default-f59fa5d5-d7qbs
    State: running
     Jobs: 0 queued
   Failed: 0 units
...
```

root のまま操作することに不安がある場合、`su` で一般ユーザに切り替えておくと安心です。

```
# su - ubuntu
```

## ホストのファイルをコピーする
`kubectl cp` または `kubectl exec` を利用すると、scp のようにノード上のファイルを簡単にコピーできます。

`kubectl cp` の方が利用はシンプルですが、セキュリティ上の理由で symlink がサポートされません [^symlink]。そのため、symlink の追従が必要で、かつ信頼できるノードである場合は、`kubectl exec` を利用します。

[^symlink]: [CVE-2019-11251: Remove symlink support from kubectl cp #82143](https://github.com/kubernetes/kubernetes/pull/82143)

### デバッグ Pod の作成
`cp`, `exec` どちらも、まずは `kubectl debug node` でデバッグ用 Pod を立ち上げます。このとき Pod が終了しないように `sleep infinity` などを指定しておきます。

```
$ kubectl debug node/<NODE_NAME> --image busybox -- sleep infinity
```

デバッグの Pod 名が表示されます。以下の場合、`node-debugger-demo-tkusumi-worker-default-f59fa5d5-xjkc9-9xj69` が Pod 名です。

```
Creating debugging pod node-debugger-demo-tkusumi-worker-default-f59fa5d5-xjkc9-9xj69 with container debugger on node demo-tkusumi-worker-default-f59fa5d5-xjkc9.
```

コピーが終わったら、この Pod を忘れずに削除しましょう。

### kubectl cp の例
ホストの `/etc/kubernetes` 以下を、操作元の `/tmp` にコピーする例です。パスに `/host` が入る点に注意してください。

```
$ kubectl cp <DEBUG_POD>:/host/etc/kubernetes/ /tmp/
```

操作元の `/tmp/` 以下にファイルがコピーされました :tada:。

```
$ ls /tmp/host/etc/kubernetes
drain-kubeconfig.yaml      kube-proxy.yaml            manifests
extra                      kubelet-kubeconfig.yaml    probe-kubeconfig.yaml
kube-proxy-kubeconfig.yaml kubelet.yaml               ssl
```

### kubectl exec の例
`cp` 同様に、ホストの `/etc/kubernetes` 以下を、操作元の `/tmp` にコピーする例です。パスに `/host` が入る点に注意してください。

```
$ kubectl exec <DEBUG_POD> tar cf - /host/etc/kubernetes | tar xf - -C /tmp/
```

操作元の `/tmp/` 以下にファイルがコピーされました :tada:。

```
$ ls /tmp/host/etc/kubernetes
drain-kubeconfig.yaml      kube-proxy.yaml            manifests
extra                      kubelet-kubeconfig.yaml    probe-kubeconfig.yaml
kube-proxy-kubeconfig.yaml kubelet.yaml               ssl
```

## 特別な Capabilities が必要な場合
Kubernetes 1.21 時点では、`kubectl debug node` で、Linux Capabilities や privileged を指定できません。そのため、特別な Capabilities が必要なコマンドは実行できないことがあります。privileged は [Add --privileged flag to kubectl debug node #101897](https://github.com/kubernetes/kubernetes/pull/101897) の PR がマージされれば、指定できるようになります。

### ホストの Docker を使った workaround
現状どうしても、Linux Capabilities や privileged を使いたい場合は、ホストの Docker を使って権限昇格する方法があります。

まず `kubectl debug node` で Pod のシェルを立ち上げます。

```console
$ kubectl debug node/<NODE_NAME> -it --image=busybox
```

Pod のシェル内で、ホストのルートディレクトリに `chroot` します。

```console
# chroot /host
```

例えば、strace は `CAP_SYS_PTRACE` の Capabilitiy が必要なため、利用できません。

```console
# strace -e trace=file -p 1
strace: Could not attach to process. If your uid matches the uid of the target process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf: Operation not permitted
strace: attach: ptrace(PTRACE_SEIZE, 1): Operation not permitted
```

ホストの Docker で `--cap-add SYS_PTRACE` を指定し、コンテナを立ち上げます。引数で再度、`chroot` している点に留意してください。

```
# docker run -it --cap-add SYS_PTRACE --pid=host --net=host --ipc=host -v /:/host busybox chroot /host
```

strace が実行できるようになりました :tada:。

```
# strace -e trace=file -p 1
strace: Process 1 attached
[pid 23001] execve("/usr/bin/strace", ["strace", "-e", "trace=file", "-p", "1"], 0x7ffde9127f70 /* 10 vars */) = 0
[pid 23001] access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
[pid 23001] access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
[pid 23001] openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
```

#### 補足: HostPath による権限昇格について
もし、HostPath による権限昇格ができることに不安を覚えた場合は、[Pod Security Policy](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) (将来的には[その後継](https://kubernetes.io/blog/2021/04/06/podsecuritypolicy-deprecation-past-present-and-future/)) による Pod のセキュリティポリシーの見直しが必要かもしれません。多くの環境で、HostPath でホストの任意のディレクトリをマウントできる Pod の作成権限は、ノードの root 相当の権限を持っていることを意味しています。

## 既知の問題と今後の機能追加
Kubernetes v1.21 時点での、kubectl debug node の既知の問題と、今後の入る可能性のある機能です。

- NoExecute の taint が付与されたノードで `kubectl debug node` が動作しない
    - [kubectl alpha debug node does not work on tainted nodes #98253](https://github.com/kubernetes/kubernetes/issues/98253)
    - 修正 PR が出ている [fix kubectl alpha debug node does not work on tainted(NoExecute) nodes #98431](https://github.com/kubernetes/kubernetes/pull/98431)
- `--overrides` フラグの追加
    - JSON 形式のパッチでデバッグ用 Pod をカスタマイズできる
    - [Support kubectl debug overload from overrides json #97887](https://github.com/kubernetes/kubernetes/pull/97887)
- `--privileged` フラグの追加
    - [Add --privileged flag to kubectl debug node #101897](https://github.com/kubernetes/kubernetes/pull/101897) で PR 中

## まとめ
`kubectl debug node` は、ノードのデバッグに非常に便利です。特に `chroot` を使うと、ホストのコマンドがそのまま利用できるので、ssh と同じ感覚で操作が可能です。また、`kubectl` の `cp` や `exec` と組み合わせると、scp のようなファイルのコピーも可能です。

## 参考
- https://kubernetes.io/docs/tasks/debug-application-cluster/debug-running-pod/#node-shell-session
- [kubectl debug #1441](https://github.com/kubernetes/features/issues/1441)
- [KEP-1441: kubectl debug](https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/1441-kubectl-debug)