ざっくりとKubernetesの通信について探る
はじめに
業務で必要になってくるので最近ようやくKubernetesについてキャッチアップしています。今回はKubernetesのワーカーノード周りの通信がどのように行われているかを探ってみたかったのでGKEを用いて確認していきます。ワーカーノードをじっくり触っていくため、クラスタモードはGKE Standardの一般公開クラスタを使います。クラスタは作成済み&ローカルで接続済みの状況を想定として話を進めていきたいと思います。
Google Kubernetes Engineのアーキテクチャ
通信を確認するために作ったもの
GolangでAPI Serverを作成しました。HTTPリクエストするとIPアドレスを返してくれるものです。これでどのように通信が行われているかを確認していきたいと思います。
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が3台起動していることを確認できます。
以下のコマンドを実行してpodの詳細を確認します。
kubectl get pod -o wide
podが3台起動していることを確認できます。しかし、3台のワーカーノードに1つずつPodが起動しているようではないみたいです。
図にすると以下のような関係になっています。ワーカーノードにPodが1つずつ配置されるというわけではないみたいです。
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に転送するようになりました。
L7ロードバランサにHTTPリクエスト
curl http://35.200.9.207
こんな感じで各ノードにランダムで通信が行われていることがわかります。
ワーカーノードにHTTPリクエスト
今回はGKEを一般公開クラスタとしているので各ワーカーノードにPublicIPアドレスが割り当てられています。各ノードのファイヤーウォールのルールに32192番ポートの通信を許可し、各ノードに対してHTTPリクエストを行います。
curl 34.146.102.39:32192
curl 34.85.123.215:32192
curl 34.85.115.12:32192
結果は以下のようになりました。
図にまとめると以下のような感じです。 アクセスするワーカーノード上にPodがなかったとしても問題なく別のワーカーノードに対してパケットが振り分けられている。
クラスタ内から直接PodにHTTPリクエスト
クラスタ内にubuntuのPodを立て、そこからHTTPリクエストを行います。
kubectl run -it ubuntu --image=ubuntu bash
curlコマンドのインストール
apt-get update
apt-get install curl -y
試しにCLUSTER-IPにHTTPリクエストを行います。
curl http://10.56.11.169
結果は以下のようになりました。ちゃんと各Podに振り分けられていますね。
本命の各Podに直接HTTPリクエストを行います。
curl http://10.52.2.5:8080
curl http://10.52.2.6:8080
curl http://10.52.1.4:8080
結果は以下のようになりました。こちらもちゃんと各Podに振り分けられていますね。
どうやってPodに通信しているのか
各ワーカーノードに存在するkube-proxyがアクセスを振り分けているように見えるが、実際はapiserverの変更を検知してiptablesのDNATルールを追加、変更、削除を行うだけであり、アクセスの振り分けはiptablesというOS側の仕組みで実行する。CLUSTER-IPの実態はiptablesとなっている。
以下のようにiptables上のチェインを処理しています。
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を発見した。
KUBE-SVC-7WFRCR3BAURI7IH5を見ていきます。
kubectl exec -it Pod名(kube-proxyなんとか) -n kube-system -- iptables -t nat -nL KUBE-SVC-7WFRCR3BAURI7IH5
iptablesでは、ルールは上から下に順番に一致します。1番目のルールがヒットする確率が33%、2番目のルールがヒットする確率が50%、3番目は必ずヒットするので3つのルールに当たる確率は同じであることがわかります。
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されることが確認できました。
ざっくりとiptablesについて探りました。ここから先の内容は以下の記事がとても参考になりますよ。
参考文献
Discussion