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


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

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

> 普通の Docker のネットワークについては [こちら](https://qiita.com/megmogmog1965/items/f5682de2068a79d196a2)

> Kubernetes ネットワークの仕組み (Azure) は [こちら](https://qiita.com/megmogmog1965/items/7a2a729068fe605b8c41) 。ほぼ同じです。

## 検証環境

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

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

Enable Kubernetes APIs する。

* https://console.cloud.google.com/apis/api/container.googleapis.com/overview?project=gke-test-000000

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

```
# gcloud container clusters create gke-test
```

クラスタに認証する。

```
# gcloud container clusters get-credentials gke-test
```

3台の Agent Node。

```console
# 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] で分散されているので、これを題材にします。

```console
# 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
...
```

```console
# 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](https://qiita-image-store.s3.amazonaws.com/0/60131/da6bdff2-6ae1-7079-c5b7-dd7b1bd8c2dc.png "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/16`` は [DNAT] で宛先 ``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ケーブル

```console
# 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`` へ

```console
# 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`` があります

```console
# 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`` のこと。

```console
# 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`` を見てみます。

```console
# 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% の確率が割り当てられていますね。

```console
# 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% その１ --> Chain ``KUBE-SEP-AETEL5TCYEIYP6LO``. [DNAT] で宛先をコンテナ (POD) のIPアドレスに書き換え。

```console
# 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% その２ --> Chain ``KUBE-SEP-O3HFREXOOMGV2B4D``. これは *その１* とは異なる物理ホスト上のコンテナIPを示しています。

```console
# 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

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

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

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

```console
# 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 なので、２台の ``kube-dns`` POD のうちどちらかで名前解決されることになります。


[Kubernetes]:https://kubernetes.io/
[SERVICE]:https://kubernetes.io/docs/concepts/services-networking/service/
[GKE]:https://cloud.google.com/kubernetes-engine/?hl=ja
[Azure Container Service]:https://azure.microsoft.com/ja-jp/pricing/details/container-service/
[GKE Tutorial]:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
[GitHub kubernetes/examples/guestbook]:https://github.com/kubernetes/kubernetes/tree/release-1.9/examples/guestbook
[DNAT]:http://www.asahi-net.or.jp/~aa4t-nngk/ipttut/output/dnattarget.html