Edited at

Kubernetes Network Deep Dive (NodePort, ClusterIP, Flannel)

More than 1 year has passed since last update.


Kubernetes Network Deep Dive (NodePort, ClusterIP, Flannel)


初めに

Kubernetes には、Kubeadmをはじめとした半自動インストーラーがあり、比較的簡単にインストールを行うことが出来ます。

このままでもKubernetesクラスタを利用することはできますが、本番運用を考えると、Networkの疎通経路を正しく理解する必要があります。

私のローカル環境のKubernetesで検証・確認した内容をいかに共有します。

皆様のお役に立てましたら幸いです。


  • Pod同士の通信 (同一マシン)

  • Pod同士の通信 (異なるマシン)

  • Service(ClusterIP) を経由した通信

  • Service(NodePort) を経由した通信

Kubernetesなどのバージョン情報を以下に記載します

- Kubernetes 1.10

- Kubeadm 1.10

- CentOS 7.5


Podの準備

nginx Pod を 4個作成します

以下のマニフェストファイルを準備します

cat <<'EOF' > deployment.yaml 

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 4 # tells deployment to run 2 pods matching the template
template: # create pods using pod definition in this template
metadata:
# unlike pod-nginx.yaml, the name is not included in the meta data as a unique name is
# generated from the deployment name
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
EOF


NW構成図


  • Master1台

  • Node2台

001.png


NW構成の確認コマンド一覧

Masterサーバを使用して、各種ネットワーク要素を確認していきます

MasterもNodeも基本的には違いはありません。


Master01

物理 Interface です

[root@sugi-kubeadm-master01 ~(kubernetes kube-system kubernetes-admin)]# ip -d a show ens192

2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:98:f7:02 brd ff:ff:ff:ff:ff:ff promiscuity 0 numtxqueues 2 numrxqueues 2 gso_max_size 65536 gso_max_segs 65535
inet 192.168.120.225/24 brd 192.168.120.255 scope global noprefixroute ens192
valid_lft forever preferred_lft forever
inet6 fe80::72f:48e1:638c:e8f6/64 scope link tentative dadfailed
valid_lft forever preferred_lft forever

物理 Interface に紐づく、vxlanの Interface が flannel.1 です

4行目あたりに 「dev ens192」と表示されており、紐づけされていることがわかります。

[root@sugi-kubeadm-master01 ~(kubernetes kube-system kubernetes-admin)]# ip -d a show flannel.1

4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether f2:4a:3d:b6:4e:95 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 local 192.168.120.225 dev ens192 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.1.4.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
inet6 fe80::f04a:3dff:feb6:4e95/64 scope link
valid_lft forever preferred_lft forever

次に cni0 です。これは Linux Bridgeです

cni0 という名前の Linix Bridge に、同一物理Nodeで稼働する Pod の veth が接続される形となります

[root@sugi-kubeadm-master01 ~(kubernetes kube-system kubernetes-admin)]# ip -d a show cni0

5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether 0a:58:0a:01:04: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 bridge_id 8000.a:58:a:1:4:1 designated_root 8000.a:58:a:1:4:1 root_port 0 root_path_cost 0 topology_change 0 topology_change_detected 0 hello_timer 0.00 tcn_timer 0.00 topology_change_timer 0.00 gc_timer 135.01 vlan_default_pvid 1 vlan_stats_enabled 0 group_fwd_mask 0 group_address 01:80:c2:00:00:00 mcast_snooping 1 mcast_router 1 mcast_query_use_ifaddr 0 mcast_querier 0 mcast_hash_elasticity 4 mcast_hash_max 512 mcast_last_member_count 2 mcast_startup_query_count 2 mcast_last_member_interval 100 mcast_membership_interval 26000 mcast_querier_interval 25500 mcast_query_interval 12500 mcast_query_response_interval 1000 mcast_startup_query_interval 3125 mcast_stats_enabled 0 mcast_igmp_version 2 mcast_mld_version 1 nf_call_iptables 0 nf_call_ip6tables 0 nf_call_arptables 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.1.4.1/24 scope global cni0
valid_lft forever preferred_lft forever
inet6 fe80::a0b1:82ff:fe32:b41e/64 scope link
valid_lft forever preferred_lft forever

brctlコマンドで、Linux Bridge の接続を確認することが出来ます

[root@sugi-kubeadm-master01 ~(kubernetes kube-system kubernetes-admin)]# brctl show

bridge name bridge id STP enabled interfaces
cni0 8000.0a580a010401 no veth4a0982ff
docker0 8000.02420aebdea5 no

brctlコマンドを実行するためには、以下のパッケージをinstallします

yum install -y bridge-utils 

veth4a0982ff も同様に ip コマンドで確認することが出来ます

[root@sugi-kubeadm-master01 ~(kubernetes kube-system kubernetes-admin)]# ip -d a show veth4a0982ff

6: veth4a0982ff@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether de:56:b4:74:4e:b1 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 1
veth
bridge_slave state forwarding priority 32 cost 2 hairpin on guard off root_block off fastleave off learning on flood on port_id 0x8001 port_no 0x1 designated_port 32769 designated_cost 0 designated_bridge 8000.a:58:a:1:4:1 designated_root 8000.a:58:a:1:4:1 hold_timer 0.00 message_age_timer 0.00 forward_delay_timer 0.00 topology_change_ack 0 config_pending 0 proxy_arp off proxy_arp_wifi off mcast_router 1 mcast_fast_leave off mcast_flood on numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet6 fe80::dc56:b4ff:fe74:4eb1/64 scope link
valid_lft forever preferred_lft forever

上記の表示結果は「veth4a0982ff@if3」と表示されており、Interface 3 の docker0 と ペアの veth なのか? と思いきや、そうではありません

3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 

link/ether 02:42:0a:eb:de:a5 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 bridge_id 8000.2:42:a:eb:de:a5 designated_root 8000.2:42:a:eb:de:a5 root_port 0 root_path_cost 0 topology_change 0 topology_change_detected 0 hello_timer 0.00 tcn_timer 0.00 topology_change_timer 0.00 gc_timer 148.93 vlan_default_pvid 1 vlan_stats_enabled 0 group_fwd_mask 0 group_address 01:80:c2:00:00:00 mcast_snooping 1 mcast_router 1 mcast_query_use_ifaddr 0 mcast_querier 0 mcast_hash_elasticity 4 mcast_hash_max 512 mcast_last_member_count 2 mcast_startup_query_count 2 mcast_last_member_interval 100 mcast_membership_interval 26000 mcast_querier_interval 25500 mcast_query_interval 12500 mcast_query_response_interval 1000 mcast_startup_query_interval 3125 mcast_stats_enabled 0 mcast_igmp_version 2 mcast_mld_version 1 nf_call_iptables 0 nf_call_ip6tables 0 nf_call_arptables 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 172.17.0.1/16 scope global docker0
valid_lft forever preferred_lft forever

上記の id3 の数字は、コンテナ内の interface 番号に紐づいています

kube-dns のコンテナにsh を起動してログインします

[root@sugi-kubeadm-master01 ~(kubernetes kube-system kubernetes-admin)]# kubectl exec -it kube-dns-86f4d74b45-zspb9 sh

Defaulting container name to kubedns.
Use 'kubectl describe pod/kube-dns-86f4d74b45-zspb9 -n kube-system' to see all of the containers in this pod.

コンテナ上で eth0 を表示すると、3番目の interface となっており、 if 6 の 「veth4a0982ff@if3」とペアになっていることがわかります

/ # ip a show eth0

3: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP
link/ether 0a:58:0a:01:04:02 brd ff:ff:ff:ff:ff:ff
inet 10.1.4.2/24 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::4466:97ff:feff:bdfb/64 scope link tentative flags 08
valid_lft forever preferred_lft forever


Pod同士の通信(同一のNode)

node01で稼働しているPod同士の通信を確認します

同一のnodeで稼働しているPod同士は、必ず同一のセグメントとなります。

そのため、LinuxBridgeの cni0 で折り返して通信を行うことが出来ます

注意点として、Podが通信を行う時には、Serviceを介して通信を行うことが一般的です。LBの結果同一のNodeと通信する場合もあれば、異なるNodeと通信する場合もあります

002.png


Pod同士の通信(異なるNode)

node01で稼働しているPodとnode02で稼働しているPodの通信を確認します

Flannelはvxlanでトンネリングを実施しているため、そこに注目して通信を見てみましょう

003.png


  • node01のPod(10.1.5.4) から node02の Pod (10.1.6.5) へ Pingを行います

  • node01のPod(10.1.5.4) から node02の Pod (10.1.6.5)へPingを送付する際に、同一セグメントに居ないため、DefaultGW (10.1.5.1) にパケットを転送します
    以下は Pod内のルーティングテーブルを表示しています

004.png


  • DefaultGW (10.1.5.1)は、Nodeサーバ上の LinuxGridge cni0 インターフェースとなっています
    cni0はnode02の Pod (10.1.6.5)へパケット転送を行うため、Node01のルーティングテーブルに従って、flannel.1 へパケットを転送します
    このルーティングテーブルはどこから来ているかというと、後述のflannelのサービスが追加をしています

005.png


  • flannel.1 は、VXLANのEndpoint (VTEP) なので、トンネリングして次の宛先へ転送します。
    次の宛先を決めるための情報は、ARPテーブルと、カーネル内部の FDB (Forwarding Database) に既に格納されています。
    誰がこの情報を登録しているのかというと、flannelのサービスが、RoutingTable, ARP, FDB にエントリを追加しています
    https://github.com/coreos/flannel/blob/master/backend/vxlan/vxlan.go#L39

006.png

RoutingTableの確認

10.1.6.0/24 宛のパケットは、flannel.1 デバイスから、10.1.6.0 宛に転送すればよい事がわかります

[root@sugi-kubeadm-node01 ~]# ip r

default via 192.168.120.254 dev ens192 proto static metric 100
10.1.4.0/24 via 10.1.4.0 dev flannel.1 onlink
10.1.5.0/24 dev cni0 proto kernel scope link src 10.1.5.1
10.1.6.0/24 via 10.1.6.0 dev flannel.1 onlink
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.120.0/24 dev ens192 proto kernel scope link src 192.168.120.226 metric 100

ARPテーブルの確認

10.1.6.0のMacAddressは「36:dc:08:bc:b6:23」とすれば良いことが分かります

[root@sugi-kubeadm-node01 ~]# ip n

192.168.120.227 dev ens192 lladdr 00:50:56:98:f6:4b STALE
10.1.5.5 dev cni0 lladdr 0a:58:0a:01:05:05 STALE
192.168.120.252 dev ens192 lladdr 00:50:56:98:d2:4e REACHABLE
192.168.120.254 dev ens192 lladdr 00:50:56:aa:36:d5 DELAY
10.1.5.4 dev cni0 lladdr 0a:58:0a:01:05:04 STALE
10.1.6.0 dev flannel.1 lladdr 36:dc:08:bc:b6:23 PERMANENT
10.1.5.10 dev cni0 lladdr 0a:58:0a:01:05:0a STALE
10.1.4.0 dev flannel.1 lladdr f2:4a:3d:b6:4e:95 PERMANENT
192.168.120.225 dev ens192 lladdr 00:50:56:98:f7:02 REACHABLE

Forwarding DataBase の確認

「36:dc:08:bc:b6:23」は、92.168.120.227宛に送付すれば良いことがわかります。

これらを使用してVXLANパケットを作成します

[root@sugi-kubeadm-node01 ~]# bridge fdb show dev flannel.1

36:dc:08:bc:b6:23 dst 192.168.120.227 self permanent
f2:4a:3d:b6:4e:95 dst 192.168.120.225 self permanent


  • ARPとFDBに従って、VXLANでカプセル化されたパケットが、node02の flannel.1 へ到着し、VXLANのカプセル化が解除されます

007.png


  • ルーティングテーブルに従って、cni0へパケットを転送します

008.png


  • cni0 が 対象のPodのMacAddressを学習していない場合は、ARP Request を投げます


  • 対象のPodがArp Reply を返し、NWの疎通が出来る


009.png


Service(ClusterIP)の通信


ClusterIPの準備

ClusterIPのServiceを作成します

cat <<'EOF' > /root/kube_yaml/201_walkthrough/service.yaml

apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
ports:
- port: 8000 # the port that this service should serve on
# the container on each pod to connect to, can be a name
# (e.g. 'www') or a number (e.g. 80)
targetPort: 80
protocol: TCP
# just like the selector in the deployment,
# but this time it identifies the set of pods to load balance
# traffic to.
selector:
app: nginx
EOF

Serviceを作成します

kubectl apply -f /root/kube_yaml/201_walkthrough/service.yaml

以下の nginx-service が作成されます

[root@sugi-kubeadm-master01 201_walkthrough(kubernetes default kubernetes-admin)]# kubectl get svc -o wide

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 10h <none>
nginx-service ClusterIP 10.1.2.120 <none> 8000/TCP 4m app=nginx

上記のServiceは、既存のnginxPodの4個と紐づいています

[root@sugi-kubeadm-master01 201_walkthrough(kubernetes default kubernetes-admin)]# kubectl describe endpoints nginx-service

Name: nginx-service
Namespace: default
Labels: <none>
Annotations: <none>
Subsets:
Addresses: 10.1.5.4,10.1.5.5,10.1.6.5,10.1.6.6
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 80 TCP

Events: <none>

上記のPodにアクセスするため、CentOSのPodを1個作成します

cat <<'EOF' > /root/kube_yaml/201_walkthrough/centos_deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: centos-deployment
spec:
selector:
matchLabels:
app: centos
replicas: 1 # tells deployment to run 2 pods matching the template
template: # create pods using pod definition in this template
metadata:
# unlike pod-nginx.yaml, the name is not included in the meta data as a unique name is
# generated from the deployment name
labels:
app: centos
spec:
containers:
- name: centos
image: centos:latest
command: [ "sleep", "3600000" ]
EOF

kubectl apply -f /root/kube_yaml/201_walkthrough/centos_deployment.yaml

CentOSでcurlのテストを行います

[root@centos-deployment-748dc5bfb9-4jpgx /]# curl http://10.1.2.120:8000/

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>


ClusterIPの通信の確認

PodからClusterIPを通じて、別のPodへ通信する経路を確認します。

ClusterIPの実体はIPtablesなので、そこに注目しつつ、順番に追っていきたいと思います。


  • Pod CentOSから、ClusterIPへ curl を実行します

010.png


  • ClusterIP は別セグメントなので、DefaultGWのcni0にパケットを転送します

011.png


  • cni0のインターフェースを持つ Node がパケットを受け取る際に、iptablesのnatテーブルが作用し、DNAT が使用されパケットのDestinationIPが変換されます
    IPtableのチェインは、評価される順番があります。パケット受信の際には、natテーブルのPREROUTINGチェインが最初に評価されます

memo.png

詳細は、http://www.atmarkit.co.jp/ait/articles/1002/09/news119.html

を確認するとわかりやすいです

iptablesコマンドを使用して、PREROUTINGチェインを確認します。なお、この時に -v を入れて詳細を表示すると、ルールに適用されたパケットの数を確認できるため、複雑なiptablesを追いやすくなるのでお勧めです。

in, out が アスタリスクなので全てのインターフェース、 source, destination が 0.0.0.0/0 なのですべての宛先パケットがルールに該当します。

初めに記載されているルールに従って、「KUBE-SERVICES」チェインが評価されます

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL PREROUTING

Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
1612 252K KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
487 95515 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL


  • 「KUBE-SERVICES」チェインを確認します

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SERVICES

Chain KUBE-SERVICES (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ udp -- * * !10.1.4.0/22 10.1.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
0 0 KUBE-SVC-TCOU7JCQXEZGVUNU udp -- * * 0.0.0.0/0 10.1.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
0 0 KUBE-MARK-MASQ tcp -- * * !10.1.4.0/22 10.1.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
0 0 KUBE-SVC-ERIFXISQEP7F7OF4 tcp -- * * 0.0.0.0/0 10.1.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
0 0 KUBE-MARK-MASQ tcp -- * * !10.1.4.0/22 10.1.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
0 0 KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- * * 0.0.0.0/0 10.1.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
0 0 KUBE-MARK-MASQ tcp -- * * !10.1.4.0/22 10.1.2.120 /* default/nginx-service: cluster IP */ tcp dpt:8000
0 0 KUBE-SVC-GKN7Y2BSGW4NJTYL tcp -- * * 0.0.0.0/0 10.1.2.120 /* default/nginx-service: cluster IP */ tcp dpt:8000
0 0 KUBE-NODEPORTS all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

source ip が10.1.5.10 と、dest ipが10.1.2.120、 dest port が 8000 なので、下の方にある以下のルールにマッチします

    0     0 KUBE-SVC-GKN7Y2BSGW4NJTYL  tcp  --  *      *       0.0.0.0/0            10.1.2.120           /* default/nginx-service: cluster IP */ tcp dpt:8000

ルールにマッチし、KUBE-SVC-GKN7Y2BSGW4NJTYL チェインが評価されます


  • 「KUBE-SVC-GKN7Y2BSGW4NJTYL」チェインを確認します

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SVC-GKN7Y2BSGW4NJTYL

Chain KUBE-SVC-GKN7Y2BSGW4NJTYL (1 references)
pkts bytes target prot opt in out source destination
1 60 KUBE-SEP-E5HSR3R2B7URHFJI all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ statistic mode random probability 0.25000000000
0 0 KUBE-SEP-R2MSMBFMMKEJBLAL all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ statistic mode random probability 0.33332999982
0 0 KUBE-SEP-CAKGWV44IP65RES6 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-2XH37R654LGYZT6Q all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */

上記チェインの末尾の方に「statistic mode random probability 0.XXXX」と記載があります

これは、パケットを評価する際に、指定した確率を使用して、ルールを実行するか否かを指定できます。

上記の例では、一番最初に「probability 0.25000000000」とあるので、25%の確率でルールが実行されます。

なお、iptablesコマンドのoptionにvを付けているので、ルールに該当したパケット数が表示されています。

上記実行例では、pkts列が1となっているルールが、使用されていることが分かります (この値は定期的にクリアされ0に戻ります)

下記URLに statistic についての説明がされています

https://www.sssg.org/blogs/naoya/archives/1695


  • 一番はじめの「KUBE-SEP-E5HSR3R2B7URHFJI」チェインを確認します

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-E5HSR3R2B7URHFJI

Chain KUBE-SEP-E5HSR3R2B7URHFJI (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.5.4 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.5.4:80

一番はじめの「KUBE-MARK-MASQ」のルールは、よくわかりません。sourceの「10.1.5.4」は、nginx Pod のうちの1個ですが、なぜこれをMark付けるのかが理解できていません。

次のルールは、DNATをまさに実行している箇所となります。パケットのDestinationを「10.1.5.4:80」と変更しています。

これは、nginx Podのうちの1個のIPアドレスを指し示しています。

同様に他のルールもDNATを実施しています。

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-R2MSMBFMMKEJBLAL

Chain KUBE-SEP-R2MSMBFMMKEJBLAL (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.5.5 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.5.5:80
[root@sugi-kubeadm-node01 iptables]#
[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-CAKGWV44IP65RES6
Chain KUBE-SEP-CAKGWV44IP65RES6 (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.6.5 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.6.5:80
[root@sugi-kubeadm-node01 iptables]#
[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-2XH37R654LGYZT6Q
Chain KUBE-SEP-2XH37R654LGYZT6Q (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.6.6 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.6.6:80

012.png

これ以降の通信は、上の方で説明した「Pod同士の通信(同一のNode)」や「Pod同士の通信(異なるのNode)」と同じ通信となるため、省略します


Service(NodePort)の通信


NodePortの準備

上の章で作成したClusterIPは一度削除します

[root@sugi-kubeadm-master01 ~(kubernetes default kubernetes-admin)]# kubectl delete svc nginx-service

service "nginx-service" deleted

NodePortを定義するために Manifest ファイルを作成します

cat <<'EOF' > /root/kube_yaml/201_walkthrough/nodeport-service.yaml

apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
ports:
- port: 8000
targetPort: 80
nodePort: 30080
protocol: TCP
selector:
app: nginx
EOF

NodePortを作成します

kubectl apply -f /root/kube_yaml/201_walkthrough/nodeport-service.yaml

作成されたことを確認します

[root@sugi-kubeadm-master01 ~(kubernetes default kubernetes-admin)]# kubectl get svc -o wide

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 13h <none>
nginx-service NodePort 10.1.0.125 <none> 8000:30080/TCP 7s app=nginx

クラスタ外のサーバからNodePortへ接続出来ることを確認します

[root@sugi-bastion-centos ~]# curl http://192.168.120.2266:30080/

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>


NodePort通信の確認(同一のNode)

NodePortの通信経路を確認します。IPtablesが複雑なので、特にIPtablesに注目して通信経路を確認していきましょう。


  • Kubernetesクラスタ外のマシンから、Node01のNodePortへアクセスします

013.png


  • Node01のens192がパケットを受信することで、iptablesのNATテーブルが作用します。「PREROUTING」チェインが初めに実行されます

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL PREROUTING

Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
1669 262K KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
498 96593 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL


  • 「KUBE-SERVICES」が実行されるので、確認します

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SERVICES

Chain KUBE-SERVICES (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ tcp -- * * !10.1.4.0/22 10.1.0.125 /* default/nginx-service: cluster IP */ tcp dpt:8000
0 0 KUBE-SVC-GKN7Y2BSGW4NJTYL tcp -- * * 0.0.0.0/0 10.1.0.125 /* default/nginx-service: cluster IP */ tcp dpt:8000
0 0 KUBE-MARK-MASQ udp -- * * !10.1.4.0/22 10.1.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
0 0 KUBE-SVC-TCOU7JCQXEZGVUNU udp -- * * 0.0.0.0/0 10.1.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
0 0 KUBE-MARK-MASQ tcp -- * * !10.1.4.0/22 10.1.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
0 0 KUBE-SVC-ERIFXISQEP7F7OF4 tcp -- * * 0.0.0.0/0 10.1.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
0 0 KUBE-MARK-MASQ tcp -- * * !10.1.4.0/22 10.1.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
0 0 KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- * * 0.0.0.0/0 10.1.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
0 0 KUBE-NODEPORTS all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

ClusterIPとNodePortのSVCが混在しているチェインとなっています。

コメントに記載されている通り、NodePortはこのチェインの一番最後に評価される必要があるようです。

DestIPが「192.168.120.226 」なので、「KUBE-NODEPORTS」のルールのみ実行されます。


  • 「KUBE-NODEPORTS」を確認します

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-NODEPORTS

Chain KUBE-NODEPORTS (1 references)
pkts bytes target prot opt in out source destination
1 60 KUBE-MARK-MASQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp dpt:30080
1 60 KUBE-SVC-GKN7Y2BSGW4NJTYL tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp dpt:30080

ClusterIPとは違い、「KUBE-MARK-MASQ」の行が実行されます。


  • 「KUBE-MARK-MASQ」を確認します

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-MARK-MASQ

Chain KUBE-MARK-MASQ (12 references)
pkts bytes target prot opt in out source destination
0 0 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000

iptables-save で確認すると、下記行が該当します

# iptables-save | less

snip
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
snip

該当のパケットにMark「0x4000」を付けています。このMarkは、この後に続くiptableの処理の中で、SNATの条件として使用するマークとなります


  • 「KUBE-MARK-MASQ」から戻り、「KUBE-SVC-GKN7Y2BSGW4NJTYL」ルールに該当するため、これを確認します
    ClusterIPでもありましたが、probabilityで指定した確率を使用して、4つのルールから1個が選ばれます

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SVC-GKN7Y2BSGW4NJTYL

Chain KUBE-SVC-GKN7Y2BSGW4NJTYL (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-SEP-E5HSR3R2B7URHFJI all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ statistic mode random probability 0.25000000000
1 60 KUBE-SEP-R2MSMBFMMKEJBLAL all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ statistic mode random probability 0.33332999982
0 0 KUBE-SEP-CAKGWV44IP65RES6 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-2XH37R654LGYZT6Q all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */


  • それぞれ確認すると、DNATのルールを確認出来ます

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-E5HSR3R2B7URHFJI

Chain KUBE-SEP-E5HSR3R2B7URHFJI (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.5.4 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.5.4:80
[root@sugi-kubeadm-node01 iptables]#
[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-R2MSMBFMMKEJBLAL
Chain KUBE-SEP-R2MSMBFMMKEJBLAL (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.5.5 0.0.0.0/0 /* default/nginx-service: */
1 60 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.5.5:80
[root@sugi-kubeadm-node01 iptables]#
[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-CAKGWV44IP65RES6
Chain KUBE-SEP-CAKGWV44IP65RES6 (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.6.5 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.6.5:80
[root@sugi-kubeadm-node01 iptables]#
[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-2XH37R654LGYZT6Q
Chain KUBE-SEP-2XH37R654LGYZT6Q (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.6.6 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.6.6:80

sourceが「192.168.120.252」のため、「KUBE-MARK-MASQ」チェインには行かず、DNATチェインが使用されています。

DNATの結果、同一のNodeで稼働するPodへDNATされた場合を確認します。IPアドレスは「10.1.5.4」です

014.png


  • 下記のパケット受信の表か順を確認すると、「PREROUTING」の次は、ルーティングが実行されます。

memo.png

Node01のルーティングでテーブルを確認すると、下記の内容となっており、10.1.5.4 宛のパケットは、cni0へ転送されます

[root@sugi-kubeadm-node01 ~]# ip r

default via 192.168.120.254 dev ens192 proto static metric 100
10.1.4.0/24 via 10.1.4.0 dev flannel.1 onlink
10.1.5.0/24 dev cni0 proto kernel scope link src 10.1.5.1
10.1.6.0/24 via 10.1.6.0 dev flannel.1 onlink
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.120.0/24 dev ens192 proto kernel scope link src 192.168.120.226 metric 100

015.png


  • ルーティング後は、cni0 はさらに Podへパケットを転送するので、iptablesのFilterテーブルにある「FORWARD」チェインが評価されます。

「FORWARD」チェインを確認します

[root@sugi-kubeadm-node01 ~]# iptables -t filter -vnL FORWARD

Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
78372 55M KUBE-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */
793 57969 DOCKER-ISOLATION all -- * * 0.0.0.0/0 0.0.0.0/0
0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
0 0 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
793 57969 ACCEPT all -- * * 10.1.4.0/22 0.0.0.0/0
0 0 ACCEPT all -- * * 0.0.0.0/0 10.1.4.0/22


  • 「KUBE-FORWARD」チェインが評価されるので、こちらを確認します

[root@sugi-kubeadm-node01 ~]# iptables -t filter -vnL KUBE-FORWARD

Chain KUBE-FORWARD (1 references)
pkts bytes target prot opt in out source destination
1 60 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ mark match 0x4000/0x4000
5 1117 ACCEPT all -- * * 10.1.4.0/22 0.0.0.0/0 /* kubernetes forwarding conntrack pod source rule */ ctstate RELATED,ESTABLISHED
6 397 ACCEPT all -- * * 0.0.0.0/0 10.1.4.0/22 /* kubernetes forwarding conntrack pod destination rule */ ctstate RELATED,ESTABLISHED

「PREROUTING」チェインで受信したパケットに mark 0x4000 が付いているため、 ACCEPT されます

016.png


  • 「KUBE-FORWARD」チェインの次は nat テーブルの「POSTROUTING」チェインが評価されます。確認します

[root@sugi-kubeadm-node01 ~]# iptables -t nat -vnL POSTROUTING

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
34849 2894K KUBE-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes postrouting rules */
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
482 36384 RETURN all -- * * 10.1.4.0/22 10.1.4.0/22
121 7396 MASQUERADE all -- * * 10.1.4.0/22 !224.0.0.0/4
0 0 RETURN all -- * * !10.1.4.0/22 10.1.5.0/24
0 0 MASQUERADE all -- * * !10.1.4.0/22 10.1.4.0/22


  • 「KUBE-POSTROUTING」チェインに該当するため、こちらを確認します

[root@sugi-kubeadm-node01 ~]# iptables -t nat -vnL KUBE-POSTROUTING

Chain KUBE-POSTROUTING (1 references)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000

パケットに mark 0x4000 が付与されているため、MASQUERADEが実行されます。

MASQUERADEは、該当のインターフェースのIPアドレスでSNATする動作となります。

このパケットは現在、cni0に居るため、cni0のIPアドレス(10.1.5.1)でSNATされることとなります。

017.png


  • Podのアプリケーションで処理されたあと、Podはcni0へパケットを返します

018.png


  • cni0インターフェースでsnatを実施したため、パケットのDestinationIPを基に戻します。
    その後、ルーティングテーブルに従って、ens192へパケットを転送します

019.png


  • ens192インターフェースでdnatを実施したため、パケットのSourceIPを基に戻します。その後、NodePortへrequestをしたclientにパケットが返ります

020.png


NodePort通信の確認(異なるNode)

NodePortでDNATされた結果、異なるNodeへ転送する場合の経路を確認しましょう。

同一のNodeへ転送する場合と異なり、SNATするインターフェースが別になっています。


  • Kubernetes外部のマシンから、Node01のNodePortへアクセスします

013.png


  • Node01のens192がパケットを受信することで、iptablesのNATテーブルが作用します。「PREROUTING」チェインが初めに実行されます

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL PREROUTING

Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
1669 262K KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
498 96593 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL


  • 「KUBE-SERVICES」が実行されるので、確認します

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SERVICES

Chain KUBE-SERVICES (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ tcp -- * * !10.1.4.0/22 10.1.0.125 /* default/nginx-service: cluster IP */ tcp dpt:8000
0 0 KUBE-SVC-GKN7Y2BSGW4NJTYL tcp -- * * 0.0.0.0/0 10.1.0.125 /* default/nginx-service: cluster IP */ tcp dpt:8000
0 0 KUBE-MARK-MASQ udp -- * * !10.1.4.0/22 10.1.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
0 0 KUBE-SVC-TCOU7JCQXEZGVUNU udp -- * * 0.0.0.0/0 10.1.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
0 0 KUBE-MARK-MASQ tcp -- * * !10.1.4.0/22 10.1.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
0 0 KUBE-SVC-ERIFXISQEP7F7OF4 tcp -- * * 0.0.0.0/0 10.1.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
0 0 KUBE-MARK-MASQ tcp -- * * !10.1.4.0/22 10.1.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
0 0 KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- * * 0.0.0.0/0 10.1.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
0 0 KUBE-NODEPORTS all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

ClusterIPとNodePortのSVCが混在しているチェインとなっています。

コメントに記載されている通り、NodePortはこのチェインの一番最後に評価される必要があるようです。

DestIPが「192.168.120.226 」なので、「KUBE-NODEPORTS」のルールのみ実行されます。


  • 「KUBE-NODEPORTS」を確認します

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-NODEPORTS

Chain KUBE-NODEPORTS (1 references)
pkts bytes target prot opt in out source destination
1 60 KUBE-MARK-MASQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp dpt:30080
1 60 KUBE-SVC-GKN7Y2BSGW4NJTYL tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp dpt:30080

ClusterIPとは違い、「KUBE-MARK-MASQ」の行が実行されます。


  • 「KUBE-MARK-MASQ」を確認します

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-MARK-MASQ

Chain KUBE-MARK-MASQ (12 references)
pkts bytes target prot opt in out source destination
0 0 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000

iptables-save で確認すると、下記行が該当します

# iptables-save | less

snip
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
snip

該当のパケットにMark「0x4000」を付けています。これは、後のiptablesのSNATの処理に使用する条件となります。


  • 「KUBE-MARK-MASQ」から戻り、「KUBE-SVC-GKN7Y2BSGW4NJTYL」ルールに該当するため、これを確認します
    ClusterIPでもありましたが、probabilityで指定した確率を使用して、4つのルールから1個が選ばれます

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SVC-GKN7Y2BSGW4NJTYL

Chain KUBE-SVC-GKN7Y2BSGW4NJTYL (2 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-SEP-E5HSR3R2B7URHFJI all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ statistic mode random probability 0.25000000000
1 60 KUBE-SEP-R2MSMBFMMKEJBLAL all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ statistic mode random probability 0.33332999982
0 0 KUBE-SEP-CAKGWV44IP65RES6 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-2XH37R654LGYZT6Q all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */


  • それぞれ確認すると、DNATのルールを確認出来ます

[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-E5HSR3R2B7URHFJI

Chain KUBE-SEP-E5HSR3R2B7URHFJI (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.5.4 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.5.4:80
[root@sugi-kubeadm-node01 iptables]#
[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-R2MSMBFMMKEJBLAL
Chain KUBE-SEP-R2MSMBFMMKEJBLAL (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.5.5 0.0.0.0/0 /* default/nginx-service: */
1 60 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.5.5:80
[root@sugi-kubeadm-node01 iptables]#
[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-CAKGWV44IP65RES6
Chain KUBE-SEP-CAKGWV44IP65RES6 (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.6.5 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.6.5:80
[root@sugi-kubeadm-node01 iptables]#
[root@sugi-kubeadm-node01 iptables]# iptables -t nat -vnL KUBE-SEP-2XH37R654LGYZT6Q
Chain KUBE-SEP-2XH37R654LGYZT6Q (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.1.6.6 0.0.0.0/0 /* default/nginx-service: */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-service: */ tcp to:10.1.6.6:80

sourceが「192.168.120.252」のため、「KUBE-MARK-MASQ」チェインには行かず、DNATチェインが使用されています。

DNATの結果、別のNodeで稼働するPodへの通信経路を確認したいため、DestinationIPを「10.1.6.5」と変換したパケットを追っていきます。

021.png


  • 「PREROUTING」チェインが終わった後は、ルーティングが実行する流れとなります。
    DestIP 「10.1.6.5」のパケットは、ルーティングテーブルに従い、flannel.1 へ転送されます

022.png


  • ルーティング後は、flannel.1 はさらに Podへパケットを転送するので、iptablesのFilterテーブルにある「FORWARD」チェインが評価されます。
    パケットに Mark 0x4000 が付与されているため、ACCEPTされます。

023.png


  • 次は、NATテーブルの「POSTROUTING」が評価されます

[root@sugi-kubeadm-node01 ~]# iptables -t nat -vnL POSTROUTING

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
42559 3533K KUBE-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes postrouting rules */
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
482 36384 RETURN all -- * * 10.1.4.0/22 10.1.4.0/22
121 7396 MASQUERADE all -- * * 10.1.4.0/22 !224.0.0.0/4
0 0 RETURN all -- * * !10.1.4.0/22 10.1.5.0/24
0 0 MASQUERADE all -- * * !10.1.4.0/22 10.1.4.0/22

「KUBE-POSTROUTING」チェインのみが該当します

-「KUBE-POSTROUTING」を確認します

[root@sugi-kubeadm-node01 ~]# iptables -t nat -vnL KUBE-POSTROUTING

Chain KUBE-POSTROUTING (1 references)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000

パケットにMark 0x4000 が付与されており、このルールにパケットは当てはまります。

targetが「MASQUERADE」となっているため、該当のインターフェースのIPアドレスを使用してSNATされます。

flannel.1インターフェースにいるため、パケットのSourceIPは、flannel.1インターフェースのIPアドレス(10.1.5.0 )となります

024.png


  • SNAT後、ARP, RoutingTable, FDB(Forwarding DataBase) を参照し、別のNodeへ 転送することとなります。flannel.1でVXLANとしてカプセル化します

RoutingTableの確認

10.1.6.0/24 宛のパケットは、flannel.1 デバイスから、10.1.6.0 宛に転送すればよい事がわかります

[root@sugi-kubeadm-node01 ~]# ip r

default via 192.168.120.254 dev ens192 proto static metric 100
10.1.4.0/24 via 10.1.4.0 dev flannel.1 onlink
10.1.5.0/24 dev cni0 proto kernel scope link src 10.1.5.1
10.1.6.0/24 via 10.1.6.0 dev flannel.1 onlink
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.120.0/24 dev ens192 proto kernel scope link src 192.168.120.226 metric 100

ARPテーブルの確認

10.1.6.0のMacAddressは「36:dc:08:bc:b6:23」とすれば良いことが分かります

[root@sugi-kubeadm-node01 ~]# ip n

192.168.120.227 dev ens192 lladdr 00:50:56:98:f6:4b STALE
10.1.5.5 dev cni0 lladdr 0a:58:0a:01:05:05 STALE
192.168.120.252 dev ens192 lladdr 00:50:56:98:d2:4e REACHABLE
192.168.120.254 dev ens192 lladdr 00:50:56:aa:36:d5 DELAY
10.1.5.4 dev cni0 lladdr 0a:58:0a:01:05:04 STALE
10.1.6.0 dev flannel.1 lladdr 36:dc:08:bc:b6:23 PERMANENT
10.1.5.10 dev cni0 lladdr 0a:58:0a:01:05:0a STALE
10.1.4.0 dev flannel.1 lladdr f2:4a:3d:b6:4e:95 PERMANENT
192.168.120.225 dev ens192 lladdr 00:50:56:98:f7:02 REACHABLE

FDB の確認

「36:dc:08:bc:b6:23」は、92.168.120.227宛に送付すれば良いことがわかります。

これらを使用してVXLANパケットを作成します

[root@sugi-kubeadm-node01 ~]# bridge fdb show dev flannel.1

36:dc:08:bc:b6:23 dst 192.168.120.227 self permanent
f2:4a:3d:b6:4e:95 dst 192.168.120.225 self permanent

025.png


  • Node02のflannel.1でVXLANのカプセル化を解除します

026.png


  • ルーティングテーブルに従ってパケットをcni0へ転送します・

027.png


  • cni0 が PodのMacAddressを学習していない場合は、ARP Request を投げます。その後、Podへパケットを転送します

028.png


  • Podは、別セグメントへパケットをReplyするために、DefaultGW(10.1.6.1) を転送します

029.png


  • cni0 インターフェースはルーティングテーブルに従って、flannel.1 へ転送します

030.png


  • RoutingTable, ARP, FDB を参照して、flannel.1は VXLANでカプセル化の後、node01へ転送します

031.png


  • VXLANのパケットを、カプセル化を解除します

032.png


  • パケット受信時にSNATを行ったため、DestIPを基に戻します

033.png


  • ルーティングテーブルに従って、ens192へ転送します

034.png


  • パケット受信時にDNATを実施したため、SourceIPをもとにもどします。その後、Request元へパケットを返します

035.png


memo IPtablesの確認方法

ServiceのClusterIPを作成する前に、各サーバの iptables を file として保管しておきます

以下コマンドを全サーバで実行します

mkdir /root/iptables/

iptables -t nat -vnL > /root/iptables/001_init_save.txt
iptables-save > /root/iptables/001_init_cmd.txt

ClusterIPのServiceを作成します

cat <<'EOF' > /root/kube_yaml/201_walkthrough/service.yaml

apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
ports:
- port: 8000 # the port that this service should serve on
# the container on each pod to connect to, can be a name
# (e.g. 'www') or a number (e.g. 80)
targetPort: 80
protocol: TCP
# just like the selector in the deployment,
# but this time it identifies the set of pods to load balance
# traffic to.
selector:
app: nginx
EOF

Serviceを作成します

kubectl apply -f /root/kube_yaml/201_walkthrough/service.yaml

以下の nginx-service が作成されます

[root@sugi-kubeadm-master01 201_walkthrough(kubernetes default kubernetes-admin)]# kubectl get svc -o wide

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 10h <none>
nginx-service ClusterIP 10.1.2.120 <none> 8000/TCP 4m app=nginx

iptablesをfile化します

iptables -t nat -vnL > /root/iptables/002_clusterip_save.txt

iptables-save > /root/iptables/002_clusterip_cmd.txt

比較してみます

diff -u /root/iptables/001_init_cmd.txt /root/iptables/002_clusterip_cmd.txt


参考URL

http://enakai00.hatenablog.com/entry/2015/04/02/173739

https://www.slideshare.net/hichihara/container-networking-deep-dive-94307233

https://speakerdeck.com/hhiroshell/kubernetes-network-deep-dive/?slide=13

https://tech-lab.sios.jp/archives/7857

https://github.com/coreos/flannel/blob/master/backend/vxlan/vxlan.go#L39

IPtableの良い解説

http://www.atmarkit.co.jp/ait/articles/1002/09/news119.html

IPtablesのランダム DNAT について解説

https://www.sssg.org/blogs/naoya/archives/1695