KubernetesのVXLAN,ClusterIP,NodePort,LoadBalancerをデプロイしつつ図に落とし込む

こんにちは
株式会社クラスアクト インフラストラクチャ事業部の大塚です。

今回はタイトルにもあるようにk8sのネットワーク周りを不完全ながらも図に落とし込んでみて、自分の中でk8sのネットワーク周りの解像度をあげていければと思っています。『k8sのネットワークはおおよそタイトルにある4つが分かればイメージ出来るんじゃないかなぁ』と思う今日この頃。本当はVXLANの手前というか裏にLinuxBridgeみたいなのがいるんだろうなぁとは思いますが、確認しきれませんでした汗
※勉強途中の人間がそれっぽく図式化しているので精度はイマイチだと思われます。
※ClusterIPやNodePort,LoadBalancer辺りは図に落とし込まれているのを見るのですが、それより前のPod周りのネットワークを図に書いているのが印象になく、そのあたりを図に落とし込みたいなと思ったのが発端です。

# 環境
master nodeが1台、worker nodeが3台の計4台でk8sクラスタを構成しています。
nodeのOSはubuntu22.04。microk8sをインストールしているのですがmicrok8sのversionはv1.26.4になります。
```
root@k8s-master:~/yaml# kubectl get node -o wide
NAME           STATUS   ROLES    AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
k8s-worker01   Ready    <none>   83m   v1.26.4   192.168.2.31   <none>        Ubuntu 22.04.2 LTS   5.15.0-71-generic   containerd://1.6.15
k8s-master     Ready    <none>   91m   v1.26.4   192.168.2.30   <none>        Ubuntu 22.04.2 LTS   5.15.0-71-generic   containerd://1.6.15
k8s-worker02   Ready    <none>   73m   v1.26.4   192.168.2.32   <none>        Ubuntu 22.04.2 LTS   5.15.0-71-generic   containerd://1.6.15
k8s-worker03   Ready    <none>   83m   v1.26.4   192.168.2.33   <none>        Ubuntu 22.04.2 LTS   5.15.0-71-generic   containerd://1.6.15
```

# 用語

ClusterIP
---
>クラスター内部のIPでServiceを公開する。このタイプではServiceはクラスター内部からのみ疎通性があります。このタイプはデフォルトのServiceTypeです。

NodePort
---
>各NodeのIPにて、静的なポート(NodePort)上でServiceを公開します。そのNodePort のServiceが転送する先のClusterIP Serviceが自動的に作成されます。"NodeIP:NodePort"にアクセスすることによってNodePort Serviceにアクセスできるようになります。

LoadBalancer
---
>クラウドプロバイダーのロードバランサーを使用して、Serviceを外部に公開します。クラスター外部にあるロードバランサーが転送する先のNodePortとClusterIP Serviceは自動的に作成されます。

https://kubernetes.io/ja/docs/concepts/services-networking/service/#publishing-services-service-types

Calico
---
>Calico は MicroK8 のデフォルトの CNI であり、VXLAN オーバーレイ ネットワークはポッド ネットワークの構成に使用されます。
>Calico は、コンテナー、仮想マシン、およびネイティブ ホスト ベースのワークロード向けのオープン ソース ネットワーキングおよびネットワーク セキュリティ ソリューションです。Calico は、Kubernetes、OpenShift、Mirantis Kubernetes Engine (MKE)、OpenStack、ベアメタル サービスなど、幅広いプラットフォームをサポートしています。

IPAMやVXLAN環境をk8s（microk8s）に提供しているものみたいですね。

https://microk8s.io/docs/configure-host-interfaces

https://docs.tigera.io/calico/latest/about/

https://docs.tigera.io/calico/latest/networking/ipam/get-started-ip-addresses

DeamonSet
---
>DaemonSetは、すべての（または一部の）ノードがPodのコピーを実行することを保証します。ノードがクラスタに追加されると、Podが追加されます。ノードがクラスタから削除されると、これらのPodはガベージコレクションされます。DaemonSetを削除すると、それが作成したPodがクリーンアップされます。

https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/

# DeamonSetをデプロイしてpod周りのネットワークを確認する

今回は3つのdsを用意しました。nginx用、apache2用、redis用になります。yamlファイルはそれぞれ以下となります。tolerationの設定は気にしないでください。
```nginx-ds.yaml 
apiVersion: apps/v1 
kind: DaemonSet 
metadata: 
  name: nginx-ds 
spec: 
  selector: 
    matchLabels: 
      name: nginx 
  template: 
    metadata: 
      labels: 
        name: nginx 
    spec: 
      tolerations: 
      - key: "env" 
        operator: "Equal" 
        value: "master" 
        effect: NoSchedule 
      containers: 
      - name: nginx-container 
        image: nginx:latest
```

```apache2-ds.yaml 
apiVersion: apps/v1 
kind: DaemonSet 
metadata: 
  name: apache-ds 
spec: 
  selector: 
    matchLabels: 
      name: apache 
  template: 
    metadata: 
      labels: 
        name: apache 
    spec: 
      tolerations: 
      - key: "env" 
        operator: "Equal" 
        value: "master" 
        effect: NoSchedule 
      containers: 
      - name: apache-container 
        image: shotaohtsuka/my-httpd-image
```

```redis-ds.yaml 
apiVersion: apps/v1 
kind: DaemonSet 
metadata: 
  name: redis-ds 
spec: 
  selector: 
    matchLabels: 
      name: redis 
  template: 
    metadata: 
      labels: 
        name: redis 
    spec: 
      tolerations: 
      - key: "env" 
        operator: "Equal" 
        value: "master" 
        effect: NoSchedule 
      containers: 
      - name: redis-container 
        image: redis
```

これをデプロイしていきます。
IPアドレスが各node毎に概ね連番となっていることが分かります。連番になっていないところがあるのはデプロイに失敗して2,3回やり直したことによるものだと思われます。
```
root@k8s-master:~/yaml# kubectl get pod -o wide | grep -i k8s-master 
nginx-ds-tkhgg    1/1     Running   0          23m   10.1.235.211   k8s-master     <none>           <none> 
apache-ds-t6th6   1/1     Running   0          23m   10.1.235.212   k8s-master     <none>           <none> 
redis-ds-zw869    1/1     Running   0          10m   10.1.235.215   k8s-master     <none>           <none>

root@k8s-master:~/yaml# kubectl get pod -o wide | grep -i k8s-worker01 
nginx-ds-6nw9w    1/1     Running   0          23m   10.1.79.75     k8s-worker01   <none>           <none> 
apache-ds-xv25j   1/1     Running   0          23m   10.1.79.76     k8s-worker01   <none>           <none> 
redis-ds-bv2t9    1/1     Running   0          11m   10.1.79.79     k8s-worker01   <none>           <none>

root@k8s-master:~/yaml# kubectl get pod -o wide | grep -i k8s-worker02 
nginx-ds-k4ndq    1/1     Running   0          24m   10.1.69.201    k8s-worker02   <none>           <none> 
apache-ds-bmtwp   1/1     Running   0          24m   10.1.69.202    k8s-worker02   <none>           <none> 
redis-ds-d2ps6    1/1     Running   0          11m   10.1.69.205    k8s-worker02   <none>           <none>

root@k8s-master:~/yaml# kubectl get pod -o wide | grep -i k8s-worker03 
nginx-ds-gvx8m    1/1     Running   0          24m   10.1.39.199    k8s-worker03   <none>           <none> 
apache-ds-kzpns   1/1     Running   0          24m   10.1.39.200    k8s-worker03   <none>           <none> 
redis-ds-pfrpl    1/1     Running   0          11m   10.1.39.203    k8s-worker03   <none>           <none>
```

k8sのpodに割り当てられるIPアドレスのレンジについては"/var/snap/microk8s/current/args/cni-network/cni.yaml"の以下の部分だと思われる。node毎に均等にレンジが割り当てられている様子。。。
```/var/snap/microk8s/current/args/cni-network/cni.yaml
# The default IPv4 pool to create on startup if none exists. Pod IPs will be 
# chosen from this range. Changing this value after installation will have 
# no effect. This should fall within `--cluster-cidr`. 
- name: CALICO_IPV4POOL_CIDR 
  value: "10.1.0.0/16"
```

また、それぞれのnodeのvxlanのinterfaceを確認してみます。
```k8s-master
root@k8s-master:~# ip a
11: vxlan.calico: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether 66:d5:cf:d3:6b:7e brd ff:ff:ff:ff:ff:ff 
    inet 10.1.235.192/32 scope global vxlan.calico 
       valid_lft forever preferred_lft forever 
    inet6 fe80::64d5:cfff:fed3:6b7e/64 scope link 
       valid_lft forever preferred_lft forever
```

```k8s-worker01
root@k8s-worker01:~# ip a
5: vxlan.calico: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether 66:c1:17:77:92:d1 brd ff:ff:ff:ff:ff:ff 
    inet 10.1.79.64/32 scope global vxlan.calico 
       valid_lft forever preferred_lft forever 
    inet6 fe80::64c1:17ff:fe77:92d1/64 scope link 
       valid_lft forever preferred_lft forever
```

```k8s-worker02
root@k8s-worker02:~# ip a
25: vxlan.calico: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether 66:ff:2b:bc:03:a3 brd ff:ff:ff:ff:ff:ff 
    inet 10.1.69.192/32 scope global vxlan.calico 
       valid_lft forever preferred_lft forever 
    inet6 fe80::64ff:2bff:febc:3a3/64 scope link 
       valid_lft forever preferred_lft forever
```

```k8s-worker03
root@k8s-worker03:~# ip a
5: vxlan.calico: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether 66:fc:06:ba:9d:0e brd ff:ff:ff:ff:ff:ff 
    inet 10.1.39.192/32 scope global vxlan.calico 
       valid_lft forever preferred_lft forever 
    inet6 fe80::64fc:6ff:feba:9d0e/64 scope link 
       valid_lft forever preferred_lft forever
```

この辺りを図に落とし込むと以下の様になるかと思います。
赤い線がpod間通信に使われる経路になります。podにはvxlanのIPが振られてるのでnodeを跨いだ通信が既に出来るということですね。なんとなくClusterIPとかをデプロイして初めてnode間での通信が出来るのかと思ったのですがそうではないようです。
![aaaq2023050601](https://user-images.githubusercontent.com/127835743/236586445-7ceef7b6-71a7-46bb-8cef-867c6213283c.png)

# ClusterIPをデプロイする
用意したyamlファイルは以下となります。
portがClusterIPでListenするポート、targetPortがpodでListenするポートになりますね。
```clusterip-nginx-ds.yaml
apiVersion: v1 
kind: Service 
metadata: 
  name: clusterip-nginx-ds 
spec: 
  selector: 
    name: nginx 
  type: ClusterIP 
  ports: 
  - name: nginx 
    port: 80 
    protocol: TCP 
    targetPort: 80
```

```clusterip-apache2-ds.yaml 
apiVersion: v1 
kind: Service 
metadata: 
  name: clusterip-apache2-ds 
spec: 
  selector: 
    name: apache 
  type: ClusterIP 
  ports: 
  - name: apache 
    port: 90 
    protocol: TCP 
    targetPort: 90
```

```clusterip-redis-ds.yaml 
apiVersion: v1 
kind: Service 
metadata: 
  name: clusterip-redis-ds 
spec: 
  selector: 
    name: redis 
  type: ClusterIP 
  ports: 
  - name: redis 
    port: 6379 
    protocol: TCP 
    targetPort: 6379
```

デプロイしていきます。
kubectl get svc -o wideのCLUSTER-IPに振られているIPアドレスがClusterIP serviceに実際に割り当てられているIPアドレスになります。
```
root@k8s-master:~/yaml# kubectl create -f clusterip-nginx-ds.yaml
service/clusterip-nginx-ds created 
root@k8s-master:~/yaml# kubectl create -f clusterip-apache2-ds.yaml 
service/clusterip-apache2-ds created
root@k8s-master:~/yaml# kubectl create -f clusterip-redis-ds.yaml 
service/clusterip-redis-ds created

root@k8s-master:~/yaml# kubectl get svc -o wide 
NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE     SELECTOR 
kubernetes             ClusterIP   10.152.183.1     <none>        443/TCP    3d2h    <none> 
clusterip-nginx-ds     ClusterIP   10.152.183.123   <none>        80/TCP     84m     name=nginx 
clusterip-apache2-ds   ClusterIP   10.152.183.109   <none>        90/TCP     4m51s   name=apache 
clusterip-redis-ds     ClusterIP   10.152.183.179   <none>        6379/TCP   8s      name=redis 
```

これを図に落とし込むと以下となります。
緑の線がnginx関連の通信経路、ピンクの線がapache関連の通信経路、黄色の線がredis関連の通信経路の認識です。
![aaaq2023050602](https://user-images.githubusercontent.com/127835743/236587731-ce771f56-e04a-4265-a9e9-e18824f0cb71.png)

例えばnginxのClusterIPのIPアドレスにポートを紐づけてcurlをすることで以下の様に負荷分散しつつpodに接続をします。ただClusterIPはあくまで内部用のものになりますので、k8sクラスタ外から同様のことは出来ません。
```
root@k8s-master:~/yaml# curl 10.152.183.123:80 
<!DOCTYPE html> 
<html> 
<head> 
<title>Welcome to nginx!</title> 
<style> 
html { color-scheme: light dark; } 
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をデプロイする
用意したyamlファイルは以下となります。
portがClusterIPでListenするポート、targetPortがpodでListenするポート。nodePortがそれぞれのnodeでListenするポートとなります。NodePort Serviceをデプロイすると関連して内部でClusterIPもデプロイされるっぽいですね。
```nodeport-nginx-ds.yaml 
apiVersion: v1 
kind: Service 
metadata: 
  name: nodeport-nginx-ds 
spec: 
  type: NodePort 
  selector: 
    name: nginx 
  ports: 
    - port: 80 
      targetPort: 80 
      nodePort: 30080
```

```nodeport-apache2-ds.yaml 
apiVersion: v1 
kind: Service 
metadata: 
  name: nodeport-apache2-ds 
spec: 
  type: NodePort 
  selector: 
    name: apache 
  ports: 
    - port: 90 
      targetPort: 90 
      nodePort: 30090
```

```nodeport-redis-ds.yaml 
apiVersion: v1 
kind: Service 
metadata: 
  name: nodeport-redis-ds 
spec: 
  type: NodePort 
  selector: 
    name: redis 
  ports: 
    - port: 6379 
      targetPort: 6379 
      nodePort: 30379
```

デプロイしていきます。
kubectl get svc -o wideのCLUSTER-IPに振られているIPアドレスがClusterIP serviceに実際に割り当てられているIPアドレスになります。NodePort serviceデプロイ時に付与されているCLUSTER-IPとClusterIP serviceデプロイ時に付与されているCLUSTER-IPが違いますが、これがNodePortデプロイ時に関連するClusterIPもデプロイされることを示しているのかなと思います。
```
root@k8s-master:~/yaml# kubectl create -f nodeport-nginx-ds.yaml 
service/nodeport-nginx-ds created 
root@k8s-master:~/yaml# kubectl create -f nodeport-apache2-ds.yaml 
service/nodeport-apache2-ds created 
root@k8s-master:~/yaml# kubectl create -f nodeport-redis-ds.yaml 
service/nodeport-redis-ds created 

root@k8s-master:~/yaml# kubectl get svc -o wide 
NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE     SELECTOR 
kubernetes             ClusterIP   10.152.183.1     <none>        443/TCP          3d3h    <none> 
clusterip-nginx-ds     ClusterIP   10.152.183.123   <none>        80/TCP           166m    name=nginx 
clusterip-apache2-ds   ClusterIP   10.152.183.109   <none>        90/TCP           86m     name=apache 
clusterip-redis-ds     ClusterIP   10.152.183.179   <none>        6379/TCP         81m     name=redis 
nodeport-nginx-ds      NodePort    10.152.183.23    <none>        80:30080/TCP     17m     name=nginx 
nodeport-apache2-ds    NodePort    10.152.183.61    <none>        90:30090/TCP     3m45s   name=apache 
nodeport-redis-ds      NodePort    10.152.183.165   <none>        6379:30379/TCP   8s      name=redis 
```

図に落とすと以下となる認識です。
ここで少し気を付けないといけない点としてはClusterIPのIPは一個上で図式化した時のIPと違うということです。この図に落とし込めていませんが一個上で図式化したClusterIPもクラスタ上に存在しています。
![aaaq2023050603](https://user-images.githubusercontent.com/127835743/236588349-26980571-7ed4-42ac-9f3f-715208e3ffe1.png)

# LoadBalancerをデプロイする
lbをデプロイする前に検証環境のメモリが限界っぽかったので上記でデプロイしてきたsvcをdeleteします。
```
root@k8s-master:~/yaml# kubectl delete svc clusterip-nginx-ds clusterip-apache2-ds clusterip-redis-ds nodeport-nginx-ds nodeport-apache2-ds nodeport-redis-ds 
service "clusterip-nginx-ds" deleted 
service "clusterip-apache2-ds" deleted 
service "clusterip-redis-ds" deleted 
service "nodeport-nginx-ds" deleted 
service "nodeport-apache2-ds" deleted 
service "nodeport-redis-ds" deleted 

root@k8s-master:~/yaml# kubectl get svc 
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE 
kubernetes   ClusterIP   10.152.183.1   <none>        443/TCP   3d4h
```

lbデプロイ用に用意したyamlファイルは以下です。
portがLoadBalancer及びClusterIPでListenするポート、targetPortがpodでListenするポート。nodePortがそれぞれのnodeでListenするポートとなります。LoadBalancer Serviceをデプロイすると関連して内部でNodePort,ClusterIPもデプロイされるっぽいですね。
```lb-nginx-ds.yaml 
apiVersion: v1 
kind: Service 
metadata: 
  name: lb-nginx-ds 
spec: 
  type: LoadBalancer 
  selector: 
    name: nginx 
  ports: 
    - name: nginx 
      port: 8080 
      targetPort: 80 
      nodePort: 30800
```
```lb-apache2-ds.yaml
apiVersion: v1
kind: Service
metadata:
  name: lb-apache-ds
spec:
  type: LoadBalancer
  selector:
    name: apache
  ports:
    - name: apache
      port: 9090
      targetPort: 90
      nodePort: 30900
```
```lb-redis-ds.yaml
apiVersion: v1
kind: Service
metadata:
  name: lb-redis-ds
spec:
  type: LoadBalancer
  selector:
    name: redis
  ports:
    - name: redis
      port: 6379
      targetPort: 6379
      nodePort: 30379
```

デプロイしていきます。
kubectl get svc -o wideのCLUSTER-IPに振られているIPアドレスがClusterIP serviceに実際に割り当てられているIPアドレスになります。NodePortのIPはそれぞれのnodeのIPになります。EXTERNAL-IPがLoadBalancerに振られるはずのIPになりますがpendingとなっています。これはMetalLBがdisableとなっている為です。このあと有効化します。
```
root@k8s-master:~/yaml# kubectl create -f lb-nginx-ds.yaml 
service/lb-apache-ds created
root@k8s-master:~/yaml# kubectl create -f lb-apache2-ds.yaml
service/lb-apache-ds created
root@k8s-master:~/yaml# kubectl create -f lb-redis-ds.yaml
service/lb-redis-ds created

root@k8s-master:~/yaml# kubectl get svc -o wide
NAME           TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE    SELECTOR
kubernetes     ClusterIP      10.152.183.1     <none>        443/TCP          41m    <none>
lb-nginx-ds    LoadBalancer   10.152.183.130   <pending>     8080:30800/TCP   6m3s   name=nginx
lb-apache-ds   LoadBalancer   10.152.183.172   <pending>     9090:30900/TCP   88s    name=apache
lb-redis-ds    LoadBalancer   10.152.183.250   <pending>     6379:30379/TCP   7s     name=redis
```

MetalLBを有効化します。
今回は192.168.2.36-40までをMetalLB用に確保しました。
```
root@k8s-master:~/yaml# microk8s enable metallb
Infer repository core for addon metallb
Enabling MetalLB
Enter each IP address range delimited by comma (e.g. '10.64.140.43-10.64.140.49,192.168.0.105-192.168.0.111'): 192.168.2.36-192.168.2.40
Applying Metallb manifest
customresourcedefinition.apiextensions.k8s.io/addresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgpadvertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.metallb.io created
customresourcedefinition.apiextensions.k8s.io/communities.metallb.io created
customresourcedefinition.apiextensions.k8s.io/ipaddresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/l2advertisements.metallb.io created
namespace/metallb-system created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/controller created
role.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/controller created
secret/webhook-server-cert created
service/webhook-service created
rolebinding.rbac.authorization.k8s.io/pod-lister created
daemonset.apps/speaker created
deployment.apps/controller created
validatingwebhookconfiguration.admissionregistration.k8s.io/validating-webhook-configuration created
Waiting for Metallb controller to be ready.
error: timed out waiting for the condition on deployments/controller
MetalLB controller is still not ready
deployment.apps/controller condition met
ipaddresspool.metallb.io/default-addresspool created
l2advertisement.metallb.io/default-advertise-all-pools created
MetalLB is enabled
```

MetalLB有効化後、再度kubectl get svc -o wideを実行するとpendingステートだったものが変わり、IPアドレスが振られていますね。
```
root@k8s-master:~/yaml# kubectl get svc -o wide
NAME           TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)          AGE     SELECTOR
kubernetes     ClusterIP      10.152.183.1     <none>         443/TCP          44m     <none>
lb-apache-ds   LoadBalancer   10.152.183.172   192.168.2.36   9090:30900/TCP   3m58s   name=apache
lb-redis-ds    LoadBalancer   10.152.183.250   192.168.2.37   6379:30379/TCP   2m37s   name=redis
lb-nginx-ds    LoadBalancer   10.152.183.130   192.168.2.38   8080:30800/TCP   8m33s   name=nginx
```

この時の通信経路イメージです。
クラスタ上にはLoadBalancerしかなさそうに見えますが、実際はNodePort,ClusterIPがありPC→LB→NodePort→ClusterIP→podという順で通信をしているようです。
![aaaq2023050604](https://user-images.githubusercontent.com/127835743/236589088-0ddad0f6-b755-4a4a-a981-9dea2737f6b0.png)