Linux
docker
kubernetes
GKE

Kubernetes ネットワークの仕組み (GKE)

Kubernetes のネットワークがどの様に実現されているか知りたかったので調べてメモしました。ここに書いているのは...

  • コンテナ (POD) のIPアドレスがどのようにルーティングされるか
  • SERVICE の Cluster-IP が、複数いる POD の IPアドレスにどうルーティングされるか
  • コンテナ (POD) 内のDNS名前解決はどうされるか

普通の Docker のネットワークについては こちら

Kubernetes ネットワークの仕組み (Azure) は こちら 。ほぼ同じです。

検証環境

新規に Project を作りました。Zone は asia-northeast1-a です。

# gcloud init
> [34] asia-northeast1-a

Enable Kubernetes APIs する。

コンテナクラスタを作ります。

# gcloud container clusters create gke-test

クラスタに認証する。

# gcloud container clusters get-credentials gke-test

3台の Agent Node。

# kubectl get nodes

NAME                                      STATUS   ROLES    AGE   VERSION
gke-gke-test-default-pool-5dd7587d-jfwx   Ready    <none>   2h    v1.8.10-gke.0
gke-gke-test-default-pool-5dd7587d-lwgr   Ready    <none>   2h    v1.8.10-gke.0
gke-gke-test-default-pool-5dd7587d-v1t8   Ready    <none>   2h    v1.8.10-gke.0

特に何も追加でデプロイはしませんでした。Kubernetes 標準の POD kube-dns が、いい感じに2台いて SERVICE で分散されているので、これを題材にします。

# kubectl get pods --namespace=kube-system

NAME                          READY    STATUS    RESTARTS   AGE
kube-dns-778977457c-89f5w     3/3      Running   0          2h
kube-dns-778977457c-c27bf     3/3      Running   0          2h
...
# kubectl get services --namespace=kube-system

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)         AGE
kube-dns     ClusterIP   10.11.240.10    <none>        53/UDP,53/TCP   2h
...

まとめ

Kubernetes (GKE).png

主要な3つのネットワーク範囲があります。

  • 物理ホストのネットワークが 10.146.0.0/20
  • それぞれの物理ホスト上に、コンテナのネットワーク 10.8.X.0/24 がある
    • Agent Node #0 には 10.8.0.0/24
    • Agent Node #1 には 10.8.1.0/24
  • SERVICE の Cluster-IP は 10.11.0.0/16 の範囲で振られる

コンテナのネットワーク。別々のホスト上にいるコンテナはこう↓ルーティングされます。

  • Destination=10.8.0.0/16 へのアクセスは Default Gateway 10.146.0.1 へ送信されます
  • 物理ゲートウェイのルートテーブルで物理ホストのIPアドレスへルーティングされます
    • 例えば、Destination=10.8.2.0/24 → Next Hop=10.146.0.3
  • 物理ホストに到達すると、ホストOSのルートテーブルで仮想NIC cbr0 に送信
  • 仮想NIC cbr0 の先に、宛先のコンテナのNICがあります

SERVICE の Cluster-IP 宛に送信すると...

  • 10.11.0.0/16 は全て ホストOSの iptables で解決されます (このアドレス範囲に該当する NIC はない)
  • 宛先 10.11.0.0/16DNAT で宛先 10.8.X.0/24 (コンテナIP) に書き換えられます
  • コンテナ (POD) が複数いる時は random probability 0... でランダムに選択されます

また、コンテナ内の /etc/resolv.conf を見ると nameserver は固定で 10.11.240.10 です。
これは kube-dns SERVICE の Cluster-IP です。

実際に追ってみた編

書いてあるIPとかは 今回たまたまこうだった です。

Gateway (10.146.0.1) のルートテーブル

コンテナネットワーク 10.8.X.0/24 へのアクセスが、そのネットワークが存在する物理ホスト宛にルーティングされています。

$ gcloud compute routes list

NAME                                                        NETWORK  DEST_RANGE     NEXT_HOP...
default-route-1318533de36be5ea                              default  10.146.0.0/20
...
gke-gke-test-a8a70139-3ee5fa90-621b-11e8-b095-42010a920113  default  10.8.0.0/24    10.146.0.2
gke-gke-test-a8a70139-3f4bd69a-621b-11e8-b095-42010a920113  default  10.8.1.0/24    10.146.0.4
gke-gke-test-a8a70139-443440a0-621b-11e8-b095-42010a920113  default  10.8.2.0/24    10.146.0.3

分かりにくかったので、NEXT_HOP は インスタンス名 → IP に書き直しました。

ホスト (Agent Node) のネットワーク

ifconfig の結果。

  • cbr0 ... ホストOS上の仮想Bridge。この先にコンテナのNICがあります
  • docker0 ... Docker 標準のNICだけど Kubernetes 環境では使われていない?
  • eth0 ... ホストOSの物理NICです
  • vethXXXX ... ホストNIC→コンテナNIC間の仮想Ethernetケーブル
# ifconfig

cbr0: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>  mtu 1460
        inet 10.8.0.1  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::a878:31ff:fe18:b268  prefixlen 64  scopeid 0x20<link>
        ether 0a:58:0a:08:00:01  txqueuelen 1000  (Ethernet)
        RX packets 76160  bytes 18592370 (17.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 79863  bytes 17812809 (16.9 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 169.254.123.1  netmask 255.255.255.0  broadcast 0.0.0.0
        ether 02:42:bf:98:cf:97  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1460
        inet 10.146.0.2  netmask 255.255.255.255  broadcast 10.146.0.2
        inet6 fe80::4001:aff:fe92:2  prefixlen 64  scopeid 0x20<link>
        ether 42:01:0a:92:00:02  txqueuelen 1000  (Ethernet)
        RX packets 103819  bytes 109670772 (104.5 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 102925  bytes 19371896 (18.4 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth44c43d08: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1460
        inet6 fe80::f074:dcff:fe85:89cd  prefixlen 64  scopeid 0x20<link>
        ether f2:74:dc:85:89:cd  txqueuelen 0  (Ethernet)
        RX packets 6183  bytes 591309 (577.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6536  bytes 589035 (575.2 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

...

ルートテーブル。

  • 外部への通信は Default Gateway 10.146.0.1
  • 別の物理ホストへの通信は Default Gateway 10.146.0.1
  • このホスト上にあるコンテナへの通信は cbr0 NIC に投げる
  • 別のホスト上にあるコンテナへの通信は Default Gateway 10.146.0.1
# route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.146.0.1      0.0.0.0         UG    1024   0        0 eth0
10.8.0.0        0.0.0.0         255.255.255.0   U     0      0        0 cbr0
...

コンテナのネットワーク

ifconfig の結果。

  • eth0 ... コンテナのIPアドレスが振られている仮想NICです。この先にホストOSの Bridge cbr0 があります
# ifconfig

eth0      Link encap:Ethernet  HWaddr 0A:58:0A:08:00:04  
          inet addr:10.8.0.4  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::88b8:61ff:fe87:588e/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1460  Metric:1
          RX packets:48015 errors:0 dropped:0 overruns:0 frame:0
          TX packets:43494 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:10550980 (10.0 MiB)  TX bytes:10681974 (10.1 MiB)
...

コンテナのIPは 10.8.{ホスト毎の連番}.{ホスト上のコンテナ毎の連番} になります

ルートテーブル。

  • 外部への通信は Gateway 10.8.0.1
  • 同じホスト上のコンテナへの通信は eth0 NIC へ
  • 違うホスト上のコンテナへの通信は Gateway 10.8.0.1

コンテナ内部から見た Gateway 10.8.0.1 はホストの Bridge cbr0 のこと。

# route -n

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.8.0.1        0.0.0.0         UG    0      0        0 eth0
10.8.0.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0

SERVICE の Cluster-IP

題材として kube-dns SERVICE の Cluster-IP 10.11.240.10 に着目しました。ホストOSの iptables を見てみます。

# iptables -t nat -L -n -v
...

Chain KUBE-SERVICES (2 references)
 pkts bytes target                     prot opt in     out   source        destination
    0     0 KUBE-MARK-MASQ             tcp  --  *      *    !10.8.0.0/14    10.11.240.10  tcp dpt:53
    0     0 KUBE-SVC-ERIFXISQEP7F7OF4  tcp  --  *      *     0.0.0.0/0      10.11.240.10  tcp dpt:53

Chain KUBE-SVC-ERIFXISQEP7F7OF4. ランダムで別々のルールに転送される様になっています。今回は kube-dns POD は2台だったので、それぞれ 50% の確率が割り当てられていますね。

# iptables -t nat -L -n -v
...

Chain KUBE-SVC-ERIFXISQEP7F7OF4 (1 references)
 pkts bytes target                     prot opt in     out   source        destination
    0     0 KUBE-SEP-AETEL5TCYEIYP6LO  all  --  *      *     0.0.0.0/0      0.0.0.0/0 statistic mode random probability 0.50000000000
    0     0 KUBE-SEP-O3HFREXOOMGV2B4D  all  --  *      *     0.0.0.0/0      0.0.0.0/0

確率50% その1 --> Chain KUBE-SEP-AETEL5TCYEIYP6LO. DNAT で宛先をコンテナ (POD) のIPアドレスに書き換え。

# iptables -t nat -L -n -v
...

Chain KUBE-SEP-AETEL5TCYEIYP6LO (1 references)
 pkts bytes target                     prot opt in     out   source        destination
    0     0 KUBE-MARK-MASQ             all  --  *      *     10.8.0.4       0.0.0.0/0       
    0     0 DNAT                       tcp  --  *      *     0.0.0.0/0      0.0.0.0/0 tcp to:10.8.0.4:53

確率50% その2 --> Chain KUBE-SEP-O3HFREXOOMGV2B4D. これは その1 とは異なる物理ホスト上のコンテナIPを示しています。

# iptables -t nat -L -n -v
...

Chain KUBE-SEP-O3HFREXOOMGV2B4D (1 references)
 pkts bytes target                     prot opt in     out   source        destination
    0     0 KUBE-MARK-MASQ             all  --  *      *     10.8.2.5       0.0.0.0/0       
    0     0 DNAT                       tcp  --  *      *     0.0.0.0/0      0.0.0.0/0 tcp to:10.8.2.5:53

コンテナの DNS Nameserver

コンテナを一つ作って中に入ってみます。

# kubectl run --image=nginx:alpine nginx-app
# kubectl exec -it nginx-app-0000000000-xxxxx sh

このコンテナの /etc/resolv.conf を確認。

# cat /etc/resolv.conf

nameserver 10.11.240.10
search default.svc.cluster.local svc.cluster.local cluster.local c.gke-test-000000.internal google.internal
options ndots:5

この 10.11.240.10 は前述の通り kube-dns SERVICE の Cluster-IP なので、2台の kube-dns POD のうちどちらかで名前解決されることになります。