仮想化通信

日本仮想化技術株式会社の公式エンジニアブログ

Kubernetesでネットワークを検査する方法

本稿の内容は以下「How To Inspect Kubernetes Networking」の記事の前半部分を参考に、Kubernetes 1.18.6、CRIとしてDocker、CNIとしてFlannelで動作確認したものをまとめています。

www.digitalocean.com

前書き

Kubernetesは、サーバーノードのクラスター全体でコンテナー化されたアプリケーションを管理できるコンテナーオーケストレーションシステムです。 クラスター内のすべてのコンテナー間のネットワーク接続を維持するには、いくつかの高度なネットワークに関する技術が必要です。 この記事では、Kubernetesのネットワークを検査するためのいくつかのツールと手法について簡単に説明します。

これらのツールは、接続の問題をデバッグしたり、ネットワークスループットの問題を調査したり、Kubernetesを調べてその動作を確認したりする場合に役立ちます。

Getting Started

このチュートリアルでは、次の項目で説明する構成のKubernetesクラスターを用いた場合の結果です。 kubectlはKubernetesホストのローカルにインストールされており、そのノード上で実行することを前提としています。

ノード構成

以下の構成で実験していますが、OpenStackインスタンス以外で動かした場合も基本的に一緒です。 CRI,CNIによって若干異なります。

  • OpenStackインスタンスでKubernetesクラスターを稼働
  • Kubernetes CRIとしてDockerを利用
  • Kubernetes CNIとしてFlannelを利用
  • Kubernetes Master + Worker構成

ノードを確認

ノード一覧をコマンドを実行して確認します。

root@k8s-demo-1:~# kubectl get no
NAME         STATUS   ROLES    AGE     VERSION
k8s-demo-1   Ready    master   3h55m   v1.18.6
k8s-demo-2   Ready    <none>   3h43m   v1.18.6

Podネットワークの確認

まず、適当なPodを作成します。

root@k8s-demo-1:~# kubectl run --generator=run-pod/v1 --tty=true -it hello-k8s --image=alpine -- sh

WorkerノードでPodのCONTAINER ID,CONTAINER NAMEを確認します。

root@k8s-demo-2:~# docker ps
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS               NAMES
27b2de56b27c        alpine                  "sh"                     10 minutes ago      Up 10 minutes                           k8s_hello-k8s_hello-k8s_default_ef3142f3-ad83-464c-b781-b3a589e0b2e0_0

確認したCONTAINER ID,CONTAINER NAMEを用いて、コンテナーのプロセスID(上記コマンドのコンテナー名を指定)を確認

root@k8s-demo-2:~# docker inspect --format '{{ .State.Pid }}' k8s_hello-k8s_hello-k8s_default_ef3142f3-ad83-464c-b781-b3a589e0b2e0_0
3624

プロセスIDを用いて、そのプロセスのネットワーク名前空間でipコマンドを実行します。 これにより、コンテナーのNICに割り当てられたIPアドレスやポッドの仮想イーサネットインターフェイスを確認できます。 以下の場合は「eth0@if11」がそれであり、ノードのインターフェイス11に接続されており、IPアドレスとして10.244.1.5が割り当てられていることがわかります。

root@k8s-demo-2:~# nsenter -t 3624 -n ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
5: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1408 qdisc noqueue state UP group default 
    link/ether 52:d0:a7:66:d3:53 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.1.5/24 brd 10.244.1.255 scope global eth0
       valid_lft forever preferred_lft forever

ノード上でip addrコマンドを実行し、インターフェイス11を探します。

root@k8s-demo-2:~# ip a
...
11: veth75a6ccd3@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1408 qdisc noqueue master cni0 state UP group default 
    link/ether 86:87:d3:13:bf:09 brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet6 fe80::8487:d3ff:fe13:bf09/64 scope link 
       valid_lft forever preferred_lft forever

この出力例では、11番目のインターフェースの「veth75a6ccd3」が該当のインターフェイスです。

tcpdumpコマンドでdumpを取ってみます。

root@k8s-demo-2:~# tcpdump -i veth75a6ccd3 -Q inout icmp

コンテナーの中でpingを実行

/ # ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=111 time=3.045 ms
64 bytes from 8.8.8.8: seq=1 ttl=111 time=2.774 ms
64 bytes from 8.8.8.8: seq=2 ttl=111 time=11.785 ms
...

tcpdumpコマンドにより、PODが外部に通信する際、指定したインターフェイスが使われていることが確認できました。

root@k8s-demo-2:~# tcpdump -i veth75a6ccd3 -Q inout icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth75a6ccd3, link-type EN10MB (Ethernet), capture size 262144 bytes
06:32:38.008431 IP 10.244.1.5 > dns.google: ICMP echo request, id 3584, seq 0, length 64
06:32:38.011485 IP dns.google > 10.244.1.5: ICMP echo reply, id 3584, seq 0, length 64
06:32:39.008740 IP 10.244.1.5 > dns.google: ICMP echo request, id 3584, seq 1, length 64
06:32:39.011403 IP dns.google > 10.244.1.5: ICMP echo reply, id 3584, seq 1, length 64
...
^C
16 packets captured
16 packets received by filter
0 packets dropped by kernel

「veth75a6ccd3」というインターフェイスはcni0ブリッジに集約されていることがわかります。

root@k8s-demo-2:~# brctl show
bridge name bridge id       STP enabled interfaces
cni0        8000.d6d734a198c5   no      veth4be74028
                                        veth75a6ccd3
                                        vethf81b42a8

cni0はKubernetes CNIのルーターのようなものです。インターフェイス名はCRI、CNIの組み合わせによっては異なりますが、規則性があるのでわかると思います。

7: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1408 qdisc noqueue state UP group default qlen 1000
    link/ether d6:d7:34:a1:98:c5 brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.1/24 brd 10.244.1.255 scope global cni0
       valid_lft forever preferred_lft forever
    inet6 fe80::d4d7:34ff:fea1:98c5/64 scope link 
       valid_lft forever preferred_lft forever

Pod内でtracerouteを実行すると、Workerノードのcni0に行った後に10.0.1.1を経由してパケットが流れることがわかります。

/ # traceroute 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 46 byte packets
 1  10.244.1.1 (10.244.1.1)  0.020 ms  0.012 ms  0.008 ms
 2  10.0.1.1 (10.0.1.1)  0.610 ms  0.512 ms^C

WorkerノードのIPアドレスは以下のように設定されており...

2: ens2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1458 qdisc pfifo_fast state UP group default qlen 1000
    link/ether fa:16:3e:e1:a0:fb brd ff:ff:ff:ff:ff:ff
    inet 10.0.1.167/24 brd 10.0.1.255 scope global dynamic ens2
       valid_lft 71230sec preferred_lft 71230sec
    inet6 fe80::f816:3eff:fee1:a0fb/64 scope link 
       valid_lft forever preferred_lft forever

このノードのゲートウェイとして設定されているIPアドレスは「10.0.1.1」であることがわかります。

root@k8s-demo-2:~# ip r
default via 10.0.1.1 dev ens2 proto dhcp src 10.0.1.167 metric 100 
10.0.1.0/24 dev ens2 proto kernel scope link src 10.0.1.167 
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink 
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1 
169.254.169.254 via 10.0.1.1 dev ens2 proto dhcp src 10.0.1.167 metric 100 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown 

このことから、Podからの通信はPodの仮想インターフェイスからWorkerノードのveth、cni0を経由して、ノードのルートネットと通信します。ノードのルートネットから外部のノードと通信できます。