はじめに

Kubernetes でのネットワーク実装についていくつかの例を挙げて解説していきます。

Kubernetes でのネットワークと言った場合、以下の2つの解釈ができます。

  1. Kubernetes 上のアプリケーション(Pod)側から見たネットワーク
  2. 上記の(仮想)ネットワークを実現するための Kubernetes 側の実装 (下回り)

本書では 1. を簡単に説明した後、主に 2. について解説していきます。なお、kubernetes のバージョンは 1.7 を用いています。

Kubernetes 上のアプリケーション(Pod)側から見たネットワーク

Kubernetes は Pod 毎に IPアドレスを割り当てます。同一 Kubernetes クラスタ内の Pod はその IPアドレスを使って相互に通信できます。

 

この図の例では、10.3.0.0/16 が pod network CIDR であり、pod 間の通信はこのネットワーク上でおこなわれます。なお、L2 接続性は保証されません。

テナント間の分離がなく、単一のネットワークしかないなど、 OpenStack Neutron が提供する機能に比べるとずいぶんと単純であることが分かります。なお、namespace と次節の Network Policy を組み合わせることで多少のアクセス制御はできます。

ここで、外部から Kubernetes の Pod にどう接続するのか、というしごく当然な疑問が生じるかと思います。その疑問は、Kubernetes の service という抽象レイヤによって半分は解決されますが、Kubernetes として提供される機能で解決はできず、クラウドプロバイダの提供するロードバランササービス等を併用するという形になります。今回はこの点について詳しくは述べませんが、機会があれば次回解説したいと思います。

なお、Kubernetes はバージョン 1.8 以降で限定的な IPv6 サポートが入る予定ですが、現状では IPv4 しか使用できません。以下に参考情報を記載します。

https://github.com/kubernetes/community/blob/master/contributors/design-proposals/network/networking.md

Network Policy

kubernetes のデフォルトではアクセス制限がかかっていませんが、Network Policy を使うことで Pod に対して ingress rule を記述することができます。ingress rule によって Pod単位 (IPアドレス単位)、または TCP/UDP ポート単位でのアクセス制御をかけることが可能です。

Kubernetes 側のネットワーク実装

Kubernetes クラスタは GCE や AWS や Azure などのパブリッククラウド上に構築することもできますが、ここでは自前のハードウェア (または仮想マシン) 上に構築するケースを解説します。構築にもいくつか方法がありますが、以下の手順に従って進めていく場合について記述していきます。
https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/

この場合、Calico や Flannel などの pod network add-on を1つ選んでインストールすることになります。pod network add-on 毎に Pod ネットワークの構成方法、 Network Policy サポートの有無などの違いがあります。

以下では CNI を使った実装を 2つ解説します (それ以外にも kubenet を使った実装もあります)。

Container Network Interface

CNI は CNCF プロジェクトで開発されています。コンテナにネットワーク接続を提供することにフォーカスしており、仕様とライブラリ (libcni) と参照実装の CNI プラグインなどから構成されています。また、Kubernetes 以外にも rkt, OpenShift, Mesos などでも使うことが可能です。

CNI の現行の仕様は以下 URL を参照下さい。
https://github.com/containernetworking/cni/blob/master/SPEC.md

CNI プラグインは /opt/cni/bin などのパスに置かれた実行ファイルであり、CNI_COMMAND 等の環境変数を指定した上で標準入力に json を与えると結果が標準出力に json 形式で返ってくるという仕様になっています。

【例】

toshii@kub1:~$ echo '{}'| CNI_COMMAND=VERSION /opt/cni/bin/calico
{"cniVersion":"0.3.1","supportedVersions":["0.1.0","0.2.0","0.3.0","0.3.1"]}

CNI_COMMAND には VERSION の他に ADD または DEL を指定することができ、ADD が指定された場合、コンテナ NIC の作成および IP アドレスの確保が行われ、結果が json 形式で返されます。DEL が指定された場合はそれらの解放処理が行われます。

以下では、pod network add-on の例として flannel と calico を取り上げ、それぞれの動作について解説していきます。
これらの例では kub1(192.168.122.111), kub2(192.168.122.112), kub3(192.168.122.113) の3台の仮想マシンを使用し、kub1 を master としています。

flannel

以下の URL の yaml を kubectl apply -f に与えて flannel をインストールした環境に基づいて解説していきます。なお、使用した flannel のバージョンは v0.8.0-amd64 です。
https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

この kube-flannel.yml は kube-flannel-ds という DaemonSet を定義しており、kube-flannel と install-cni という 2つのコンテナが実行されます。

kub1:~$ kubectl get po --all-namespaces -o wide
NAMESPACE     NAME                           READY     STATUS    RESTARTS   AGE       IP                NODE
kube-system   etcd-kub1                      1/1       Running   0          23m       192.168.122.111   kub1
kube-system   kube-apiserver-kub1            1/1       Running   0          23m       192.168.122.111   kub1
kube-system   kube-controller-manager-kub1   1/1       Running   1          23m       192.168.122.111   kub1
kube-system   kube-dns-2425271678-c7jsd      3/3       Running   0          23m       10.244.1.4        kub2
kube-system   kube-flannel-ds-1g0f0          2/2       Running   1          21m       192.168.122.113   kub3
kube-system   kube-flannel-ds-1smzk          2/2       Running   1          21m       192.168.122.112   kub2
kube-system   kube-flannel-ds-rgrtt          2/2       Running   2          22m       192.168.122.111   kub1
kube-system   kube-proxy-0zwc7               1/1       Running   0          21m       192.168.122.113   kub3
kube-system   kube-proxy-6rvn1               1/1       Running   0          21m       192.168.122.112   kub2
kube-system   kube-proxy-sgpbz               1/1       Running   0          23m       192.168.122.111   kub1
kube-system   kube-scheduler-kub1            1/1       Running   0          22m       192.168.122.111   kub1


kub1:~$ sudo docker ps|grep rgrtt
4ae19df35977        9db3bab8c19e                               "/opt/bin/flanneld --"   24 minutes ago      Up 24 minutes                           k8s_kube-flannel_kube-flannel-ds-rgrtt_kube-system_4d16b30f-9135-11e7-be0d-5254002e3c7d_2
b5bef230a41b        9db3bab8c19e                               "/bin/sh -c 'set -e -"   24 minutes ago      Up 24 minutes                           k8s_install-cni_kube-flannel-ds-rgrtt_kube-system_4d16b30f-9135-11e7-be0d-5254002e3c7d_0
d05176a38829        gcr.io/google_containers/pause-amd64:3.0   "/pause"                 24 minutes ago      Up 24 minutes                           k8s_POD_kube-flannel-ds-rgrtt_kube-system_4d16b30f-9135-11e7-be0d-5254002e3c7d_0

pod network が実際にどうなっているか確認するために pod を起動して割り当てられたアドレスと仮想 NIC を確認します。例として nginx を起動します。この例では node kub2 で nginx が pid 15466 で起動したので、以下のコマンドでこの pod のネットワークを確認します。

  kub2:~$ sudo nsenter -t 15466 -n ip -d addr                              
  1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1                                                                        
      link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0         
      inet 127.0.0.1/8 scope host lo                                              
  valid_lft forever preferred_lft forever                                  
      inet6 ::1/128 scope host                                                    
  valid_lft forever preferred_lft forever                                  
  3: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default                                                                    
      link/ether 0a:58:0a:f4:01:05 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0                                                                             
      veth                                                                        
      inet 10.244.1.5/24 scope global eth0                                        
  valid_lft forever preferred_lft forever                                  
      inet6 fe80::340f:30ff:fe5d:3db7/64 scope link                               
  valid_lft forever preferred_lft forever

  kub2:~$ sudo nsenter -t 15466 -n ethtool -S eth0                         
  NIC statistics:                                                                 
peer_ifindex: 7
  kub2:~$ ip -d link|grep ^7
  7: veth6c5d78d5@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default 

この veth6c5d78d5 は cni0 に接続されています。他 node の pod との通信は flannel.1 を介して行なわれます。また、flannel.1 は vxlan デバイスです。

kub2:~$ ip -d addr
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether fa:c1:6b:bc:30:cf brd ff:ff:ff:ff:ff:ff promiscuity 0 
    vxlan id 1 local 192.168.122.112 dev ens3 srcport 0 0 dstport 8472 nolearning ageing 300 
    inet 10.244.1.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
    inet6 fe80::f8c1:6bff:febc:30cf/64 scope link 
valid_lft forever preferred_lft forever
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
    link/ether 0a:58:0a:f4:01:01 brd ff:ff:ff:ff:ff:ff promiscuity 0 
    bridge forward_delay 1500 hello_time 200 max_age 2000 ageing_time 30000 stp_state 0 priority 32768 vlan_filtering 0 vlan_protocol 802.1Q 
    inet 10.244.1.1/24 scope global cni0
valid_lft forever preferred_lft forever
    inet6 fe80::98ad:9dff:fe21:b9b4/64 scope link 
valid_lft forever preferred_lft forever

kub2:~$ ip route
default via 192.168.122.1 dev ens3 onlink 
10.244.0.0/16 dev flannel.1 
10.244.1.0/24 dev cni0  proto kernel  scope link  src 10.244.1.1 
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1 linkdown 
192.168.122.0/24 dev ens3  proto kernel  scope link  src 192.168.122.112 

以上のネットワーク構成を図にまとめると下図のようになります。

 

 

flanneld は主に vxlan インターフェイスの維持を行います。また、以下の様な形式で、flannel の動作に必要な情報を node の metadata.annotations に書き込みます。この情報を使って他ホスト上の Pod の ARP エントリを更新するようなコード があります (flannel/backend/vxlan/vxlan.go)。

- apiVersion: v1
  kind: Node
  metadata:
    annotations:
      flannel.alpha.coreos.com/backend-data: '{"VtepMAC":"76:8c:03:8b:ed:01"}'
      flannel.alpha.coreos.com/backend-type: vxlan
      flannel.alpha.coreos.com/kube-subnet-manager: "true"
      flannel.alpha.coreos.com/public-ip: 192.168.122.111

kube-controller-manager がノード毎に podCIDR を割り当て (デフォルト値では /24 ずつ)、pod への IPアドレス割り当ては CNI の host-local プラグインで行われます。

flannel が使う cni プラグインは coreos のリポジトリではなく、以下の github リポジトリにあります。
https://github.com/containernetworking/plugins

この flannel プラグインは bridge プラグインと host-local プラグインを実行して実際の処理を行います。bridge プラグインは veth を cni0 ブリッジに接続します。host-local プラグインはノードに与えられたサブネット内のアドレス (上で podCIDR として割り当てられたもの) のうち使われていないものを払い出します。使用中のアドレスは /var/lib/cni/networks/cbr0 の下にファイルとして保存されます。

calico

calico にもいくつかのインストールオプションがありますが、ここでは以下の例に示されているものに従い、以下の URL を kubectl apply -f に与えてインストールします。ただしノードの IP アドレスとの衝突を避けるため、CALICO_IPV4POOL_CIDR を 10.168.0.0/16 に変更し、 kubeadm init –pod-network-cider にはその CIDR を指定しました。
https://docs.projectcalico.org/v2.5/getting-started/kubernetes/installation/hosted/kubeadm/1.6/calico.yaml

この例での設定を簡単にまとめると以下のようになります。

  • kubeadm を使ってインストール
  • backend は etcd
  • bgp speaker には bird を使用
  • IPIP を使用

この calico.yaml は calico-etcd と calico-node という 2つの DaemonSet を定義します。calico-etcd は master 上でのみ動作し、calico の情報を管理するための etcd が実行されます。また、calico-node は各ノードで動作します。以下に flannel での例と同様に、 nginx を起動して動作例を示します。

kub1:~$ kubectl get po -o wide                                           
NAME                               READY     STATUS    RESTARTS   AGE       IP               NODE                                                               
nginx-deployment-431080787-4jlbd   1/1       Running   0          7s        10.168.154.193   kub2                                                               
nginx-deployment-431080787-973hb   1/1       Running   0          7s        10.168.1.1       kub3 

kub2 で動作する nginx の PID (29967) を使って以下のように確認できます。

  kub2:~$ sudo nsenter -t 29967 -n ip -d addr
  1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
      link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 
      inet 127.0.0.1/8 scope host lo
  valid_lft forever preferred_lft forever
      inet6 ::1/128 scope host 
  valid_lft forever preferred_lft forever
  2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1
      link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0 
      ipip remote any local any ttl inherit nopmtudisc 
  4: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
      link/ether e6:3a:5b:56:38:d0 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0 
      veth 
      inet 10.168.154.193/32 scope global eth0
  valid_lft forever preferred_lft forever
      inet6 fe80::e43a:5bff:fe56:38d0/64 scope link 
  valid_lft forever preferred_lft forever
  kub2:~$ sudo nsenter -t 29967 -n ethtool -S eth0
  NIC statistics:
peer_ifindex: 6
  kub2:~$ ip -d link|grep ^6
  6: calid98d647455a@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default 

以上を図にまとめると次のようになります。

 

 

flannel と違い、この calid98d647455a は bridge に接続されていません。また、ホスト netns に出た先は routing によって通信先が決まります。

kub2:~$ ip route
 default via 192.168.122.1 dev ens3 onlink 
 10.168.1.0/26 via 192.168.122.113 dev tunl0  proto bird onlink 
 10.168.141.192/26 via 192.168.122.111 dev tunl0  proto bird onlink 
 blackhole 10.168.154.192/26  proto bird 
 10.168.154.193 dev calid98d647455a  scope link 
 172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1 linkdown 
 192.168.122.0/24 dev ens3  proto kernel  scope link  src 192.168.122.112 

10.168.154.193 宛ての外部からの通信が正しくルーティングされるような経路が設定されています。また、10.168.1.0/26 のブロックは kub3 (192.168.122.113) に収容されてますが、その中の Pod への通信が tunl0 デバイスを経由して行われるような経路も設定されています。

以下では、calico-node コンテナの動作をみていきます。このコンテナでは、以下のようなプロセスが動作します。

  • calico-felix
  • confd
  • bird

calico-felix は calico の主要なバイナリで、以下のようなスレッドから構成されています。iptables の設定や IPIP トンネルの設定、経路情報の監視および更新、カーネルの IP フォワーディングの設定などを行います。

  • dataplane driver
  • health monitor
  • syncer

confd は etcd の変更を見張ってテンプレートに従って設定ファイルを更新するデーモンです。今回の例では etcd を使っているので直接関係はありませんが、calico では etcd 以外の backend も使えるような独自拡張を加えたものを使用しています。etcd に置かれた情報を元に、BGP ピアの情報や IPIP トンネルのための設定を bird の設定ファイルに書き出しています。

bird は BGP デーモンです。IPIP トンネルに対応するために calico 独自の変更が加えられています。以下のような設定により、IPIP で通信するアドレス範囲については、BGP ピアから受けとった経路情報をカーネルの routing table に反映する際に tunl0 デバイス経由であるという情報が追加され、この処理によって他ノード上の Pod との通信が IPIP にカプセル化されます。
また、BGP で交換される経路はコンテナの veth の経路ではなく、各ノードに割り当てられた affine block (後述) の blackhole route を confd が bird の設定ファイルに書き出したものを BGP ピアと交換します。

# cat /etc/calico/confd/config/bird_ipam.cfg 
# Generated by confd
filter calico_pools {
  calico_aggr();
  custom_filters();

  if ( net ~ 10.168.0.0/16 ) then {
    accept;
  }

  reject;
}


filter calico_ipip {

  if ( net ~ 10.168.0.0/16 ) then {

    krt_tunnel = "tunl0";         
    accept;
  }  

  accept;                                  
}

calico の CNI は https://github.com/projectcalico/cni-plugin にあります。

calico の CNI は calico という名前で、orchestrator の情報を得て Kubernetes の場合とそれ以外では違うコードを実行するようになっています。今回は Kubernetes に関する執筆なので、前者のみ解説します。

以下の処理が行われます。

 

  • calico-ipam プラグインを呼びだして IP アドレスの確保 (後述)
  • veth の作成
  • container 側の veth にダミーの next hop と default route を設定
  • container 側の veth に calico-ipam から確保したアドレス (*) を設定
  • host 側の veth を host 側の netns に移動して、 (*) のアドレスがこの veth に届くように経路を設定
  • veth の proxy ARP を有効化 (host 側 veth にアドレスをつけないで済ませるため)
  • veth の IP forwarding を有効化

calico-ipam プラグインは IPAM を担当し、calico の ipPool (実体は calico-etcd 内) から各ノードに affine block という /26 のブロックを割り当て、そのブロック内から優先して IP アドレスが Pod に割り当てられます。割り当て状態は calico-etcd で管理されます。

まとめ

以上、Kubernetes のネットワーク実装について、 flannel と calico を例に解説してきました。性能面を考慮すると、 flannel では host-gw モード、 calico では IPIP を使用しないモードも選べますが、今回はそれぞれのデフォルト設定に従いました。各ノードが直接 L2 接続されているとは限らず、例えば AWS のような hosted 環境を使った場合はノードがフィルタリングされていたりするので、確実に通信ができることを重視して安全側の設定がデフォルトになっているものと思われます。

提供されるネットワークは OpenStack Neutron のものに比べるとずっと単純ではありますが、実装はそれなりに複雑であることがお分かりいただけたのではないでしょうか。