ざっくりとKubernetesの通信を探る

## はじめに

業務で必要になってくるので最近ようやくKubernetesについてキャッチアップしています。今回はKubernetesのワーカーノード周りの通信がどのように行われているかを探ってみたかったのでGKEを用いて確認していきます。ワーカーノードをじっくり触っていくため、クラスタモードはGKE Standardの一般公開クラスタを使います。クラスタは作成済み&ローカルで接続済みの状況を想定として話を進めていきたいと思います。

## Google Kubernetes Engineの構成図

![gke.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/f183fe6e-8c7a-1644-9881-c0b8db0877ef.png)


## 通信を確認するために作ったもの

GolangでAPI Serverを作成しました。HTTPリクエストするとIPアドレスを返してくれるものです。これでどのように通信が行われているかを確認していきたいと思います。

```go:main.go
package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		interfaces, err := net.Interfaces()
		if err != nil {
			fmt.Println(err)
			return
		}
		for _, inter := range interfaces {
			addrs, err := inter.Addrs()
			if err != nil {
				fmt.Println(err)
				return
			}
			for _, a := range addrs {
				if ipnet, ok := a.(*net.IPNet); ok {
					if ipnet.IP.To4() != nil {
						fmt.Fprintf(w, ipnet.IP.String() + "\n")
					}
				}
			}
		}
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}
```

## コンテナのデプロイ

deploymentを作成します。
```
kubectl create deployment go-app --image=asia-northeast1-docker.pkg.dev/${projectId}/go-repo/go-app:latest
```

Podを3つに増やします。
```
kubectl scale deployment --replicas 3 go-app
```

## NodeとPodの関係

以下のコマンドを実行してnodeの詳細を確認します。
```
kubectl get node -o wide
```

nodeが３台起動していることを確認できます。
![スクリーンショット 2022-05-19 14.41.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/c158582a-0d74-6d11-876a-ddfdfde77b0c.png)


以下のコマンドを実行してpodの詳細を確認します。
```
kubectl get pod -o wide
```

podが３台起動していることを確認できます。しかし、3台のワーカーノードに１つずつPodが起動しているようではないみたいです。
![スクリーンショット 2022-05-19 14.42.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/acbc0743-55a0-b6cb-0a7f-4251a5bb4997.png)



図にすると以下のような関係になっています。ワーカーノードにPodが1つずつ配置されるというわけではないみたいです。

![k8s (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/748e274f-275d-5b83-d300-c6f6dce7434c.png)


## Serviceの作成(L4ロードバランサの設置)

Serviceの80番ポートにアクセスするとpodの8080番ポートに転送します
```
kubectl expose deployment go-app --type=LoadBalancer --port 80 --target-port 8080
```

以下のコマンドでServiceを確認することができます。
```
kubectl get svc -o wide
```

ワーカーノードの32192番ポートにアクセスすればCLUSTER-IPに転送するようになりました。
![スクリーンショット 2022-05-19 14.52.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/36b42a30-f911-8eb0-0117-036ad68724cd.png)


## L7ロードバランサにHTTPリクエスト

```
curl http://35.200.9.207
```

こんな感じで各ノードにランダムで通信が行われていることがわかります。
![スクリーンショット 2022-05-19 14.55.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/4f6f61c8-3a8c-1ee8-0b2d-8b211477ca25.png)



## ワーカーノードにHTTPリクエスト

今回はGKEを一般公開クラスタとしているので各ワーカーノードにPublicIPアドレスが割り当てられています。各ノードのファイヤーウォールのルールに32192番ポートの通信を許可し、各ノードに対してHTTPリクエストを行います。
```
curl 34.146.102.39:32192
curl 34.85.123.215:32192
curl 34.85.115.12:32192
```

結果は以下のようになりました。
![スクリーンショット 2022-05-19 15.05.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/eff12b6d-30de-ed72-c8b8-3ea6fc82999e.png)

図にまとめると以下のような感じです。　アクセスするワーカーノード上にPodがなかったとしても問題なく別のワーカーノードに対してパケットが振り分けられている。
![k8s (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/2a06ae23-84cd-d48c-3a8a-13382ecb2c83.png)

## クラスタ内から直接PodにHTTPリクエスト

クラスタ内にubuntuのPodを立て、そこからHTTPリクエストを行います。
```
kubectl run -it ubuntu --image=ubuntu bash
```

curlコマンドのインストール
```
apt-get update
apt-get install curl
```

試しにCLUSTER-IPにHTTPリクエストを行います。

```
curl http://10.56.11.169
```

結果は以下のようになりました。ちゃんと各Podに振り分けられていますね。
![スクリーンショット 2022-05-19 23.23.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/52b930de-f8c6-525a-f736-106f64af34a7.png)

本命の各Podに直接HTTPリクエストを行います。
```
curl http://10.52.2.5:8080
curl http://10.52.2.6:8080
curl http://10.52.1.4:8080
```
結果は以下のようになりました。こちらもちゃんと各Podに振り分けられていますね。
![スクリーンショット 2022-05-19 23.26.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/c35dd7d8-7c54-10eb-396a-4537d6c690a9.png)

## どうやってPodに通信しているのか

各ワーカーノードに存在するkube-proxyがアクセスを振り分けているように見えるが、実際はapiserverの変更を検知してiptablesのDNATルールを追加、変更、削除を行うだけであり、アクセスの振り分けはiptablesというOS側の仕組みで実行する。CLUSTER-IPの実態はiptablesとなっている。

以下のようにiptables上のチェインを処理しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/52dfe977-4ee9-cefe-f456-cf8c506b40b8.png)
Ref: https://www.atmarkit.co.jp/ait/articles/1002/09/news119.html


ここからはiptablesどのようになっているかざっくり見ていきます。PREROUTINGチェインのKUBE-SERVICESを見ていきます

```
kubectl exec -it Pod名(kube-proxyなんとか) -n kube-system -- iptables -t nat -nL KUBE-SERVICES
```

CLUSTER-IPを発見した。
![スクリーンショット 2022-05-19 20.28.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/5b4c9b17-d001-cd47-c7f7-dbdf147304c2.png)

KUBE-SVC-7WFRCR3BAURI7IH5を見ていきます。
```
kubectl exec -it Pod名(kube-proxyなんとか) -n kube-system -- iptables -t nat -nL KUBE-SVC-7WFRCR3BAURI7IH5
```

iptablesでは、ルールは上から下に順番に一致します。１番目のルールがヒットする確率が33％、2番目のルールがヒットする確率が50%、３番目は必ずヒットするので3つのルールに当たる確率は同じであることがわかります。
![スクリーンショット 2022-05-19 20.26.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/214ee149-0e57-2375-5e98-296bcbc4b336.png)



KUBE-SEP-WWRWIUIMT3RFPW3Fを見ていきます。
```
kubectl exec -it Pod名(kube-proxyなんとか) -n kube-system -- iptables -t nat -nL KUBE-SEP-WWRWIUIMT3RFPW3F 
```

10.52.1.4:8080(Pod)にDNATされることが確認できました。
![スクリーンショット 2022-05-19 20.31.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/87005bd4-cae4-7926-d735-1e204c5f282a.png)

ざっくりとiptablesについて探りました。ここから先の内容は以下の記事がとても参考になりますよ。

https://qiita.com/sugimount/items/d0f5232b983e1deac556

## 参考文献

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

https://cloud.google.com/kubernetes-engine/docs/concepts/network-overview?hl=ja

https://recruit.gmo.jp/engineer/jisedai/blog/kubernetes_service/

https://christina04.hatenablog.com/entry/iptables-outline

https://blog.vpantry.net/2020/07/k8s-7/

https://qiita.com/tkusumi/items/c2a92cd52bfdb9edd613

https://qiita.com/inductor/items/c5bf93bc9d9c4081a1fc

https://qiita.com/sugimount/items/d0f5232b983e1deac556