KubernetesとロードバランサーやAPIのエンドポイントなどの外部のデバイスと通信がうまくいっていなかったり、途中で切れたりしたときにtcpdumpを使ってパケットを解析すると、早期の問題解決につながります。Kubernetes環境でないVMでは、VM上でtcpdumpを実行すれば簡単にパケットをとれますが、Kubernetesの場合は少し工夫が必要なのでそのあたりをまとめてみました。
準備
tcpdumpを実行すると、フィルターを使用しても大量のデータを取得してしまいます。時によっては意図せずに、コンプライアンス上やセキュリティー上とるべきでないパケットなんかも見れてしまいます。可能であればテスト環境で問題の再現を行って、テストデータをtcpdumpで取得することをお勧めします。また実際にそのほうが、余分なパケットが少なく必要なデータだけを取得しやすいので解析時間も短くなるはずです。
先ずは複数あるノードから1つtcpdumpを実行するノードを選択し、そのノードに問題が発生しているアプリケーションをデプロイします。
ノードmyk8snode2
を使用するので、myk8snode0
、myk8snode1
をcordonします。
kubectl get no
NAME STATUS ROLES AGE VERSION
myk8snode0 Ready <none> 10d v1.24.4
myk8snode1 Ready <none> 10d v1.24.4
myk8snode2 Ready <none> 10d v1.24.4
kubectl cordon myk8snode0 myk8snode1
もしくはnodeAffinity
を使って指定のノードにPodをデプロイします。
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- myk8snode2
確認すると、Podmyapppod-1bw5d-1234
がmyk8snode2
にデプロイされているのがわかります。
kubectl -n myapp get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapppod-1bw5d-1234 1/1 Running 1 (1d2h ago) 4d1h 100.111.2.111 myk8snode2 <none> <none>
次にtcpdumpを実行するためのPodをデプロイします。
apiVersion: v1
kind: Pod
metadata:
name: nsenter
namespace: nsenter
spec:
hostPID: true
hostNetwork: true
hostIPC: true
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- myk8snode2
containers:
- name: nsenter
image: ubuntu
command: ['tail', '-f', '/dev/null']
securityContext:
privileged: true
ノード上すべてのPodの通信を取得したいので、管理者権限などが付いたPodです。トラブルシューティング以外の時は使わないほうがいいと思います。
デプロイしたPodにexecします。
kubectl -n nsenter exec -it nsenter -- nsenter --target 1 --mount --uts --ipc --net /bin/bash
これで、Podnsenter
からmyk8snode2
内の通信を見ることができます。Pod内で
ip link
とかするとノード上の全てのPodのインターフェースを確認できます。
tcpdumpで特定のPodだけのパケットを取得したい場合は、Podのインターフェースを指定する必要があります。
Podmyapppod-1bw5d-1234
のインターフェースは、以下のように確認できます。
kubectl exec myapppod-1bw5d-1234 -it -- cat /sys/class/net/eth0/iflink
32
これでnsenter
Podからip linkで32を探すと、こんな感じで確認できます。
ip link|grep 32
32: veth32d4edf@if4: .........
ノードmyk8snode2
上ではveth32d4edf
がPodmyapppod-1bw5d-1234
のインターフェースとなります。
tcpdumpの実行
Podnsenter
からtcpdumpを実行します。
先ほどのPodmyapppod-1bw5d-1234
のパケットのみを取得する場合は、
tcpdump -v -XX -tt -i veth32d4edf port 443 -w /dump/tcpdump.pcap
のような感じで実行すると、Podmyapppod-1bw5d-1234
のport 443のパケットを全て取得できます。フィルターを使って、解析に必要な最小限のパケットを取得します。
また、どのPodが問題を起こしているの分からないときや、nginx
などを使ってサービスを公開して外部と通信している場合に通信の全体を見るには
tcpdump -v -XX -tt -i any port 443 -w /dump/tcpdump.pcap
とするとノードmyk8snode2
上のすべてのPodのおよび外部との通信のport 443のパケットを取得できます。
解析
先ほど取得したtcpdumpをWiresharkなんかを使って解析します。
ノード上のすべてのインターフェースから取得すると、例えば以下のようなIPを通ったパケットを見ることができます。
- 外部デバイスのIP
- ノードのIP
- ロードバランサーのIP
-
nginx
Ingress controllerのIP - バックエンドサービスのPodのIP
例えば外部からのリクエストが、nginx
を通してバックエンドのサービスのPodmyapppod-1bw5d-1234
にどのように送信され、どのIPからRSTを出しているかなどがわかります。これによって通信の遮断の原因がアプリケーションなのか、ロードバランサーなのかもしくは外部のクライアントなのかがわかります。
No. Time Source Destination Protocol Length TTL Info
349 4.8666 100.111.2.111 10.23.45.12 TCP 56 63 58780 → 443 [RST] Seq=98 Win=0 Len=0
この例だとPodmyapppod-1bw5d-1234
のIP100.111.2.111
が、外部のエンドポイント10.23.45.12
にRSTのパケットを送っています。何らかの原因で、Podとエンドポイントとの通信が正常に終了せず中断されています。考えられる原因としては、タイムアウトなど時間がかかりすぎて中断しているなどあります。
使用しているPodでタイムアウトなどのエラーをしっかりログで出してくれれば、ログ上でエラーを確認できるのでここまでしてパケットの中を見る必要もないです。しかし、すべてのアプリケーションでログが期待できるわけでもないので、ログで分からない場合はこのような方法で通信障害を解析することもできます。