Kubernetesに触れる の続き。KubernetesのNetwork関連の話題について。
Networkについて
Pod間の通信
Kubernetesクラスタをkubeadmなどを使って構築しようとすると、以下のようなコマンドを実行することになる。
kubectl apply -n kube-system -f \
"https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 |tr -d '\n')"
この作業で、Kubernetes上でプライベートネットワークが作られる。Weave Netはデフォルトでは10.32.0.0/12
というセグメントでネットワークを作る。このセグメントの指定は変更可能である。ホストネットワークと被ってはいけないという制約があるので、ホストネットワークが例えば10.0.0.0/8
である場合は、明示的に別のセグメントを指定する必要がある。
今回はWeave Netを適用したが、他にも選択しがあってFlannelやCalicoなどがある。kubeadmのサイト参照。
Podを2つデプロイしてみる。以下はcentos7のContainerイメージを動かしてsleep 3600
を実行したPodがデプロイされている様子。
[node1 ~]$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
cent-1 1/1 Running 0 17s 10.44.0.1 node3 <none>
cent-2 1/1 Running 0 13s 10.36.0.1 node2 <none>
Podを見てみると、10.32.0.0/12
の中にあるIPが割り当てられている。このように、Kubernetes上ではPod一つにつき一つのIPが割り当てられる。
色々なところからpingしてみる。今回は、node1
がMasterでnode2, node3, node4
がNodesである。
Nodenode2
からPodcent-1
へping
[node2 ~]$ ping 10.44.0.1
PING 10.44.0.1 (10.44.0.1) 56(84) bytes of data.
64 bytes from 10.44.0.1: icmp_seq=1 ttl=64 time=1.45 ms
64 bytes from 10.44.0.1: icmp_seq=2 ttl=64 time=0.769 ms
^C
--- 10.44.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.769/1.110/1.452/0.343 ms
Podcent-1
からPodcent-2
へping
[node1 ~]$ kubectl exec -it cent-1 /bin/sh
sh-4.2# ping 10.36.0.1
PING 10.36.0.1 (10.36.0.1) 56(84) bytes of data.
64 bytes from 10.36.0.1: icmp_seq=1 ttl=64 time=2.54 ms
64 bytes from 10.36.0.1: icmp_seq=2 ttl=64 time=0.781 ms
^C
--- 10.36.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.781/1.663/2.545/0.882 ms
このように、Kubernetesの中ではクラスタ固有のプライベートネットワークがある。この仕組みによって、Kubernetesクラスタ毎に、ホストのネットワークに依存しない、独立したネットワーク環境が実現できる。
Pod内部のContainer間の通信
翻って、Pod内部に複数のContainerがある場合を考える。
先に説明したように、Kubernetes上ではPodに一つのIPが割り当てられる。
ではPod内のContainer間の通信はどのように行われるか?
yum install tcpdump net-toolsをする。
apiVersion: v1
kind: Pod
metadata:
name: cent-cent
labels:
app: myapp
spec:
containers:
- name: cent7-1
image: centos:7
command: ['/bin/sh', '-c', '/usr/bin/yum install tcpdump net-tools -y && sleep 3600']
- name: cent7-2
image: centos:7
command: ['/bin/sh', '-c', '/usr/bin/yum install tcpdump net-tools -y && sleep 3600']
ifconfigでたしかめると、同じIPがついている。
[node1 ~]$ kubectl exec -it cent-cent -c cent7-1 /usr/sbin/ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 65535
inet 10.47.0.1 netmask 255.240.0.0 broadcast 10.47.255.255
ether da:aa:b4:3f:6b:87 txqueuelen 0 (Ethernet)
RX packets 14553 bytes 21243468 (20.2 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2958 bytes 210548 (205.6 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1 (Local Loopback)
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
[node1 ~]$ kubectl exec -it cent-cent -c cent7-2 /usr/sbin/ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 65535
inet 10.47.0.1 netmask 255.240.0.0 broadcast 10.47.255.255
ether da:aa:b4:3f:6b:87 txqueuelen 0 (Ethernet)
RX packets 14553 bytes 21243468 (20.2 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2958 bytes 210548 (205.6 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1 (Local Loopback)
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
ためしに別のノードからpingしてtcpdumpしてみる。
[node1 ~]$ kubectl exec -it cent-cent -c cent7-1 /bin/sh
sh-4.2# tcpdump -i eth0 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
08:30:37.554918 IP 10.36.0.0 > cent-cent: ICMP echo request, id 38327, seq 39, length 64
08:30:37.554953 IP cent-cent > 10.36.0.0: ICMP echo reply, id 38327, seq 39, length 64
08:30:38.554826 IP 10.36.0.0 > cent-cent: ICMP echo request, id 38327, seq 40, length 64
08:30:38.554899 IP cent-cent > 10.36.0.0: ICMP echo reply, id 38327, seq 40, length 64
[node1 ~]$ kubectl exec -it cent-cent -c cent7-2 /bin/sh
sh-4.2# tcpdump -i eth0 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
08:31:03.570873 IP 10.36.0.0 > cent-cent: ICMP echo request, id 38327, seq 65, length 64
08:31:03.570908 IP cent-cent > 10.36.0.0: ICMP echo reply, id 38327, seq 65, length 64
08:31:04.570800 IP 10.36.0.0 > cent-cent: ICMP echo request, id 38327, seq 66, length 64
08:31:04.570836 IP cent-cent > 10.36.0.0: ICMP echo reply, id 38327, seq 66, length 64
どちらもきてる。
VMの比喩を引きずってるとわかりにくい現象であるが、ここからもわかるように、podはIPを共有しているため、このPodへpingすると、それぞれのContainerにあたる。したがってこの内のContainer一にアクセスするには、ポートでわけるしかない。実際、Pod内のContainerが同じポートをバインドすることはできない。例えばPodに二つのNginxを立てようとして80番ポートをそれぞれのContainerがバインドしようとした場合、エラーとなってPodは起動できない。
DNSについて
いろいろな名前解決の方法
以前にも紹介したが、ClusterIPはこれはロードバランシングの機能を提供してくれる。今回はこれの名前解決について。
KubernetesはDNSも提供してくれる。ClusterIPには名前解決可能な名前がつく。
前回と同じnginxの例で考える。
[node1 ~]$ k get pods
NAME READY STATUS RESTARTS AGE
my-nginx-b685dc965-4hc7w 1/1 Running 0 1m
my-nginx-b685dc965-7527g 1/1 Running 0 1m
my-nginx-b685dc965-mkv9l 1/1 Running 0 1m
[node1 ~]$ k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 1h
my-nginx-svc ClusterIP 10.110.220.5 <none> 8080/TCP 1m
それぞれのPodは10.96.0.10
をnameserver
として指定している。
[node1 ~]$ k exec -it my-nginx-b685dc965-4hc7w /bin/sh
# cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
ここに向けてmy-nginx-svc.default.svc.cluster.local
という名前をひくとIPがかえってくる。帰ってきたものは、ClusterIPのIPになっている。したがって、Podからあるクラスターにランダムにアクセスするには(たとえばREST APIを投げるとかの用途では)、自分で定義したmy-nginx-svc
という名前に対してリクエストを投げつければよい。
[node1 ~]$ dig my-nginx-svc.default.svc.cluster.local @10.96.0.10
; <<>> DiG 9.9.4-RedHat-9.9.4-73.el7_6 <<>> my-nginx-svc.default.svc.cluster.local @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21647
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;my-nginx-svc.default.svc.cluster.local. IN A
;; ANSWER SECTION:
my-nginx-svc.default.svc.cluster.local. 1 IN A 10.110.220.5
;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sat Mar 09 09:05:23 UTC 2019
;; MSG SIZE rcvd: 121
上記の例ではAレコードを返した。これをCNAMEレコードとして扱うこともできる。そのためにはPodでデプロイしていたものを、StatefulSetとしてデプロイしておく必要がある。StatefulSetとは、Kubernetes上でStatefulな役割を持つContainerを扱いたいときに使うリソース。たとえばディスクへのアクセスが必要なDBやKafkaをデプロイしたい場合はこれを利用する。
---
kind: Service
metadata:
name: my-nginx-svc
labels:
app: nginx
spec:
type: ClusterIP
clusterIP: None
ports:
- port: 8080
targetPort: 80
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: my-nginx
labels:
app: nginx
spec:
serviceName: my-nginx-svc
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
これを適用するとServiceとStatefulSetがデプロイされる。まずServiceにはClusterIPがついていない。一方、それぞれのPodの名前が-0
、-1
、... といった、連番になっている。
[node1 ~]$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-nginx-0 1/1 Running 0 2m
my-nginx-1 1/1 Running 0 1m
my-nginx-2 1/1 Running 0 1m
[node1 ~]$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 1h
my-nginx-svc ClusterIP None <none> 8080/TCP 2m
ここでmy-nginx-svc
の名前を引いてみるとCNAMEでかえってくることがわかる。また、この状態ならばそれぞれのPodに対しても名前解決して通信をすることができる。
[node1 ~]$ dig my-nginx-svc.default.svc.cluster.local @10.96.0.10
; <<>> DiG 9.9.4-RedHat-9.9.4-73.el7_6 <<>> my-nginx-svc.default.svc.cluster.local @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9330
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;my-nginx-svc.default.svc.cluster.local. IN A
;; ANSWER SECTION:
my-nginx-svc.default.svc.cluster.local. 5 IN A 10.36.0.1
my-nginx-svc.default.svc.cluster.local. 5 IN A 10.44.0.1
my-nginx-svc.default.svc.cluster.local. 5 IN A 10.47.0.1
;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sat Mar 09 09:19:23 UTC 2019
;; MSG SIZE rcvd: 229
[node1 ~]$ dig my-nginx-0.my-nginx-svc.default.svc.cluster.local @10.96.0.10
; <<>> DiG 9.9.4-RedHat-9.9.4-73.el7_6 <<>> my-nginx-0.my-nginx-svc.default.svc.cluster.local @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8834
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;my-nginx-0.my-nginx-svc.default.svc.cluster.local. IN A
;; ANSWER SECTION:
my-nginx-0.my-nginx-svc.default.svc.cluster.local. 5 IN A 10.44.0.1
;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sat Mar 09 09:21:35 UTC 2019
;; MSG SIZE rcvd: 143
DNSについての最後に、Kubernetesクラスタ内から外部への通信の際の名前解決について。
以下のようなServiceを作れば、外部のリソースの名前もラップできる。
kind: Service
apiVersion: v1
metadata:
name: google-externalname
namespace: default
spec:
type: ExternalName
externalName: google.com
名前をひくと、CNAMEでgoogle.comがかえってきた。
[node1 ~]$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
google-externalname ExternalName <none> google.com <none> 5s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2h
my-nginx-svc ClusterIP None <none> 8080/TCP 16m
[node1 ~]$ dig google-externalname.default.svc.cluster.local @10.96.0.10
; <<>> DiG 9.9.4-RedHat-9.9.4-73.el7_6 <<>> google-externalname.default.svc.cluster.local @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20827
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;google-externalname.default.svc.cluster.local. IN A
;; ANSWER SECTION:
google-externalname.default.svc.cluster.local. 5 IN CNAME google.com.
google.com. 5 IN A 172.217.168.206
;; Query time: 10 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sat Mar 09 09:31:56 UTC 2019
;; MSG SIZE rcvd: 169
何がうれしいのか
これまでは、IPやエンドポイントの管理を自分たちで行う必要があった。特にProduction環境やStaging環境などでエンドポイントが異なる場合に、アプリケーションのコンフィグファイルにそれぞれのエンドポイントのホストネームを記述して、実行時に適用仕分けたりだとか。
一方、上の仕組みならIPやエンドポイントの名前の管理をする必要がそもそもなくなる。Kubernetesでこの機能が一番感動した。
外部からのアクセス
Webサイトを公開するときなど、外部からアクセスする際は、選択しとしてLoadBalancerかIngressがある。あまり詳細はしない。
これらの違いはここに詳しい。
それぞれ以下のような特徴がある。
- LoadBalancer
- L4のロードバランシング
- Ingress
- L7のロードバランシング
- パスベースでのルーティング
- SSL/TSLの終端になる。
- 実装はNginxなど。
書こうと思ったけどやめたこと
- NetworkPolicy
- Kafka Cluster(Zookeeperも含む) をStatefulSetでk8s上にデプロイしてみる。
参考
- AKSでNetworkPolicyを有効にする: https://docs.microsoft.com/ja-jp/azure/aks/use-network-policies#code-try-0