baron tech blog

学びをアウトプットしていきたいです

DockerとKubernetesのPodのネットワーキングについてまとめました

KubernetesにおけるPod内/Pod間の通信について、本記事にまとめました。

次の公式ドキュメントを参考にしています。

Podとは

KubernetesにおけるPodは、コンテナを管理する最小単位のリソースであり、 Podは1つ以上のコンテナで構成されます。 Pod内のコンテナは、一連のnamespaceを共有しています。

f:id:foobaron:20190203075354p:plain
KubernetesクラスタとPod

KubernetesのPodは、次のような観点での下位互換性を維持するように、 ネットワーキング機能が実装されています。

そのため、Podは仮想マシンや物理ホストのように扱うことができます。

クラスタの観点からは、Podはどのノードで起動していても他のPodと通信できる必要があります。 Kubernetesの実装では、Pod間通信のためにPod間に明示的にリンクを作成する必要はなく、 コンテナとホストのポート番号のマッピングを扱う必要もありません。

Docker bridge ネットワーキング

Pod間通信の前に、まずは基本となるDockerの通信について見ていきます。

f:id:foobaron:20190203075437p:plain
Docker bridge networking

Dockerにおけるデフォルトの birdge ネットワーキングでは、 docker0 という名前の仮想ブリッジを作成し、 そのブリッジに対してプライベートネットワークのサブネットを割り当てます。 コンテナごとに、コンテナ内で eth0 と表示されているveth (Linuxにおける仮想L2インタフェース) を使います。

vethは次のようなペアで作成されます。

  • eth0 :コンテナのnetwork napespace用
  • vethXXXXXXX :ホストのnetwork namespace用

ホスト側の vethXXXXXXXdocker0 にブリッジングされます。 そして、コンテナ側の eth0 には、 docker0 に割り当てられているサブネットの範囲からIPアドレスが割り当てられます。

docker0 とホストのインタフェース (図では ens160 ) の間は、IPマスカレードによってIPアドレスとポート番号が変換されて転送されます。 DockerのIPマスカレードについては次の記事が詳しいです。

作成されたコンテナは、同じホスト上にある場合のみ、他のコンテナと通信できます。 異なるホスト上のコンテナ同士は疎通性がありません。

そのため、コンテナがノード間で通信できるようにするには、ホストのIPアドレスにポート番号が割り当てられている必要があります。 このポート番号がコンテナに転送またはプロキシされます。 ですので、コンテナが使用するポート番号が重複しないよう調整したり、ポート番号を動的に割り当てたりする必要があります。

Kubernetesのネットワーク実装の基本要件

コンテナが利用するポート番号の調整は困難ですし、ポート番号の動的な割り当てはシステムを複雑化させてしまいます。 それを踏まえ、Kubernetesにおけるネットワーク実装は、次の基本要件を満たすことで複雑さを減らし、仮想マシンからコンテナへのアプリケーション移植性を高めています。

  • 全てのコンテナはNATなしで他の全てのコンテナと通信可能
  • 全てのノードはNATなしで全てのコンテナと通信可能 (逆も可)
  • コンテナが使用するIPアドレス他のユーザから見えるコンテナのIPアドレス は同一

アプリケーションを仮想マシンで実行している場合、仮想マシンIPアドレスを使って他の仮想マシンと通信していますが、 Kubernetesではそれと同じことをコンテナで実現します。

Pod内ネットワーキングとPodの実装

KubernetesのPod内のコンテナ間のネットワーキングとPodの実装について見ていきます。

f:id:foobaron:20190203075639p:plain
IP-per-Pod model

KubernetesはPodにIPアドレスを割り当てる、 IP-per-Pod モデルを採用しています。 Pod内のコンテナは、それらのIPアドレスが割り当てられたインタフェースを含む、 network namespaceを共有します。

そのため、Pod内のコンテナ同士は localhost 上の互いのポート番号に対して通信できます。 Pod内の複数コンテナはnetwork namespaceを共有しているため、重複するポート番号を使用することはできません。 ホストで利用するプロセスのように、Pod内のコンテナのポート番号が重複しないようにする必要があります。

IP-per-podモデルは、Dockerの機能により実装されています。 Podコンテナがnetwork namespaceを作成し、ユーザが作成するPod内のアプリケーションコンテナはそのnetwork namespaceに参加します。 Dockerでは --net=container:<id> オプションにより、指定するコンテナと同一のnetwork namespaceに加わります。

では、実際にPodをデプロイして確認してみます。 sample-pod.yaml を作成し、Podに2台のコンテナを作成してみます。

sample-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: sample-pod
spec:
  containers:
    - name: busybox-01
      image: busybox:1.30
      command: ["sleep", "3600"]
    - name: busybox-02
      image: busybox:1.30
      command: ["sleep", "3600"]
$ kubectl apply -f sample-pod.yaml

クラスタノード上で作成されたPodとその内部のコンテナを確認してみます。

k8s-node:~$ docker ps
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS               NAMES
575ffdf9c5d3        3a093384ac30            "sleep 3600"             32 minutes ago      Up 32 minutes                           k8s_busybox-02_sample-pod_default_8e79fad5-17c7-11e9-87b0-000c29cd2791_7
31381a2cd6a7        3a093384ac30            "sleep 3600"             32 minutes ago      Up 32 minutes                           k8s_busybox-01_sample-pod_default_8e79fad5-17c7-11e9-87b0-000c29cd2791_7
ba0d8b12e056        k8s.gcr.io/pause:3.1    "/pause"                 8 hours ago         Up 8 hours                              k8s_POD_sample-pod_default_8e79fad5-17c7-11e9-87b0-000c29cd2791_0

f:id:foobaron:20190203075714p:plain
デプロイしたPod内のコンテナ

明示的に作成したBusyBoxコンテナ2台のほかに、 pauseコンテナが自動で作成されています。 このpauseコンテナが、2台のコンテナに対してnetwork namespaceを提供しています。 pauseコンテナは、あくまでnetwork namespaceを提供するだけであり、 SIGINTSIGTERMSIGCHLD が実行されるまで pause() を実行するだけの実装です。

それぞれのコンテナでインタフェースを確認すると、 eth0

が共通で割り当てられていることがわかります。

k8s-node:~$ docker exec k8s_busybox-01_sample-pod_default_8e79fad5-17c7-11e9-87b0-000c29cd2791_5 ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
3: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
    link/ether 0a:58:0a:00:02:03 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.3/24 scope global eth0
       valid_lft forever preferred_lft forever
k8s-node:~$ docker exec k8s_busybox-02_sample-pod_default_8e79fad5-17c7-11e9-87b0-000c29cd2791_5 ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
3: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
    link/ether 0a:58:0a:00:02:03 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.3/24 scope global eth0
       valid_lft forever preferred_lft forever
k8s-node:~$ bridge link show
8: vethcd95a6f2 state UP @ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 master cni0 state forwarding priority 32 cost 2

余談ですが、Dockerと同様に、Podでもホストのポート番号を利用可能です。 その場合にはPodに hostPort を設定しますが、Podをスケジューリングできる場所が制限されてしまいます。 Podのポートを明示的に外部に公開する場合は、 hostPortの前に nodePortの利用が推奨されます (参考)。

Pod間ネットワーキング

前述したKubernetesのネットワーク実装の基本要件

  • 全てのコンテナはNATなしで他の全てのコンテナと通信可能
  • 全てのノードはNATなしで全てのコンテナと通信可能 (逆も可)
  • コンテナが使用するIPアドレス他のユーザから見えるコンテナのIPアドレス は同一

を満足するように、様々なKubernetesのネットワーキングモデルが実装されています。これらを利用することで、Pod間の通信が可能となります。

  • Cisco ACI (Application Centric Infrastructure)
  • AOS from Apstra
  • Big Cloud Fabric from Big Switch Networks
  • Cilium
  • CNI-Genie from Huawei
  • cni-ipvlan-vpc-k8s
  • Contiv
  • Contrails / Tungten Fabric
  • DANM
  • flannel
  • GCE (Google Compute Engine)
  • Jaguar
  • Knitter
  • Kube-router
  • L2 networks and linux bridging
  • Multus (a Multi Network plugin)
  • VMware NSX-T
  • Nuage Networks VCS (Virtualized Cloud Services)
  • OpenVSwitch
  • OVN (Open Virtual Networking)
  • Project Calico
  • Romana
  • Weave Net from Weaveworks

個人的には、flannelProject Calicoの採用事例を多く見かける気がします。 GithubのStar数については次の記事が参考になります。

flannelはL2 over L3ネットワーキング (デフォルトではVXLAN) で、CalicoではBGPによるピュアL3ネットワーキングで、 Pod間の到達性を確保します。

本記事では最もシンプルな実装であるflannelを動かしてみます。

flannelによるPod間ネットワーキング

まず、Kubernetesクラスタにて、flannelをデプロイしておきます。

$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

そして、同一クラスタ内の2台のクラスタノード上にPodを1台ずつデプロイします。 nodeSelector で明示的にデプロイ先を指定しておきました。

pod-01用 ( pod-01.yaml )

apiVersion: v1
kind: Pod
metadata:
  name: pod-01
spec:
  containers:
    - name: ubuntu-01
      image: borkmann/misc
      command: ["sleep", "3600"]
  nodeSelector:
    name: node-01

pod-02用 ( pod-02.yaml )

apiVersion: v1
kind: Pod
metadata:
  name: pod-02
spec:
  containers:
    - name: ubuntu-02
      image: borkmann/misc
      command: ["sleep", "3600"]
  nodeSelector:
    name: node-02
$ kubectl apply -f pod-01.yaml
$ kubectl apply -f pod-02.yaml

f:id:foobaron:20190203093037p:plain
Inter-pod networking (flannel)

flannelをデプロイすると、図のように、 cni0flannel.1 というインタフェースが作成されます。

  • cni0bridge インタフェース
  • flannel.1 :VTEP (VXLAN Tunnel End Point) インタフェース

クラスタノード node-01 にて、 flannel.1 インタフェースを確認してみます。

node-01:~ $ ip -4 --detail address show dev flannel.1
15: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
    link/ether 06:57:e6:f4:23:41 brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local 192.168.20.11 dev ens160 srcport 0 0 dstport 8472 nolearning ttl inherit ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    inet 10.0.1.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever

flannel.1 にはVNI (VXLAN Network Identifier) 1が割り当てられており、ホストのインタフェース ( ens160 ) に紐付いています。 VXLANでカプセル化されたパケットは、 ens160 から送出されます。

次に、ARPテーブルを確認してみます。

node-01:~$ ip neigh | grep flannel.1
10.0.2.0 dev flannel.1 lladdr 9a:61:55:41:8d:06 PERMANENT

対向のPodが起動しているクラスタノードの flannel.1MACアドレスに対するエントリが存在することが確認できます。 これによりPod間の通信が可能となっています。

node-02:~ $ ip -4 --detail address show dev flannel.1
14: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
    link/ether 9a:61:55:41:8d:06 brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local 192.168.20.12 dev ens160 srcport 0 0 dstport 8472 nolearning ttl inherit ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
    inet 10.0.2.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever

簡単にですが、flannelを動かしてPod間ネットワーキングについて確認しました。 次回があればCalicoを動かしてみたいと思います。

2019/04/17 追記

Calicoを動かした結果を次の記事にまとめました。