🎆
KubernetesのServiceの挙動を確認する
目的
- 普段、Kubernetesを触ってはいるのですが、表面的な使い方しか知らないので動きを確認してみます
環境
- OS: Arch Linux(5.17.9-arch1-1)
- k8sの環境: kind
- https://kind.sigs.k8s.io/
- version 0.14.0
- デフォルトのk8sのバージョンは1.24
ひとまず、ローカルでクラスタを立てる
- 環境に応じてkindをインストール
- クラスタの作成
$ kind create cluster --name kind-iptables --config=kind-config.yaml
- デフォルトではノードが1台になるので、設定ファイルを用意してノードを増やしておきます
kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
クラスタの状態確認
-
$ docker ps
- コントロールプレーンとワーカー2台が動いてます
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
34b28d9094db kindest/node:v1.24.0 "/usr/local/bin/entr…" 41 hours ago Up 2 hours kind-iptables-worker
7a9586854130 kindest/node:v1.24.0 "/usr/local/bin/entr…" 41 hours ago Up 2 hours kind-iptables-worker2
f5915419a00a kindest/node:v1.24.0 "/usr/local/bin/entr…" 41 hours ago Up 2 hours 127.0.0.1:44651->6443/tcp kind-iptables-control-plane
サンプルのリソースをデプロイ
-
$ kubectl apply -f deployment.yaml
- レプリカ数を3にします
- nginxが80ポートで待ち受けてます
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
-
$ kubectl apply -f svc.yaml
- 3000ポートで待ち受けます
svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: default
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 3000
targetPort: 80
リソースの確認
-
$ kubectl -n default get all -o wide
- Podがkind-iptables-workerに1つ、kind-iptables-worker2に2つで動いてます
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-deployment-7dd97694d-bp8tg 1/1 Running 1 (121m ago) 15h 10.244.2.3 kind-iptables-worker2 <none> <none>
pod/nginx-deployment-7dd97694d-lllnh 1/1 Running 1 (121m ago) 15h 10.244.2.2 kind-iptables-worker2 <none> <none>
pod/nginx-deployment-7dd97694d-xkx5f 1/1 Running 1 (121m ago) 15h 10.244.1.2 kind-iptables-worker <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 40h <none>
service/nginx-svc ClusterIP 10.96.115.121 <none> 3000/TCP 15h app=nginx
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/nginx-deployment 3/3 3 3 15h nginx nginx:latest app=nginx
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/nginx-deployment-7dd97694d 3 3 3 15h nginx nginx:latest app=nginx,pod-template-hash=7dd97694d
クラスタのネットワークを確認
- 各々ノード内に入って
ip
コマンドを使って情報を整理すると以下の図のようになっていることが確認できます-
$ docker exec -it 7a9586854130 ip -4 a
- インターフェースのIPアドレスを確認
-
$ docker exec -it 7a9586854130 ip r
- ルーティングテーブルを確認
-
$ docker exec -it 7a9586854130 ip netns exec cni-a8a3f9b5-afdc-aefd-d054-00ee392bd326 ip -4 a
- コンテナのために分けられたNetwork Namespace内のインターフェースのIPアドレスを確認
- Network Namespaceとは
-
ルーティングの確認
- まず、ServiceのIPアドレスを知りたいので、コントロールプレーンにあるCoreDNSに問い合わせます
- Serviceは
my-svc.my-namespace.svc.cluster.local
という形式でDNSレコードが割り当てられています
# CoreDNSのIPアドレスを確認 $ kubectl -n kube-system get pods -o wide -l k8s-app=kube-dns NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-6d4b75cb6d-hqvp2 1/1 Running 2 (4h49m ago) 43h 10.244.0.4 kind-iptables-control-plane <none> <none> coredns-6d4b75cb6d-ppnk9 1/1 Running 2 (4h49m ago) 43h 10.244.0.2 kind-iptables-control-plane <none> <none> # ノードに入る $ docker exec -it 7a9586854130 bash # digがないのでインストール $ apt update && apt install dnsutils $ dig nginx-svc.default.svc.cluster.local +short @10.244.0.2 10.96.115.121
10.96.115.121
- Serviceは
- ServiceのIPアドレスは分かったので、次にPodにリダイレクトしている部分を覗いてみます
- iptablesでリダイレクトの処理をしているようなので確認してみます
- iptablesとは
-
$ docker exec -it 7a9586854130 iptables -L KUBE-SERVICES -t nat
-
KUBE-SVC-HL5LMXD5JFHQZ6LN
が今回作成したServiceのチェイン
-
target prot opt source destination KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- anywhere 10.96.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:https KUBE-SVC-HL5LMXD5JFHQZ6LN tcp -- anywhere 10.96.115.121 /* default/nginx-svc cluster IP */ tcp dpt:3000 KUBE-SVC-TCOU7JCQXEZGVUNU udp -- anywhere 10.96.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:domain KUBE-SVC-ERIFXISQEP7F7OF4 tcp -- anywhere 10.96.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:domain KUBE-SVC-JD5MR3NA4I4DYORP tcp -- anywhere 10.96.0.10 /* kube-system/kube-dns:metrics cluster IP */ tcp dpt:9153 KUBE-NODEPORTS all -- anywhere anywhere /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
-
$ docker exec -it 7a9586854130 iptables -L KUBE-SVC-HL5LMXD5JFHQZ6LN -t nat
- ServiceのバックエンドとなるPodの数だけチェインがあります
Chain KUBE-SVC-HL5LMXD5JFHQZ6LN (1 references) target prot opt source destination KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.96.115.121 /* default/nginx-svc cluster IP */ tcp dpt:3000 KUBE-SEP-SADCJIHRQW7RJ62U all -- anywhere anywhere /* default/nginx-svc -> 10.244.1.2:80 */ statistic mode random probability 0.33333333349 KUBE-SEP-QX4RFH7MAHSLU35M all -- anywhere anywhere /* default/nginx-svc -> 10.244.2.2:80 */ statistic mode random probability 0.50000000000 KUBE-SEP-ZXD3C5GBUWQNCJML all -- anywhere anywhere /* default/nginx-svc -> 10.244.2.3:80 */
-
$ docker exec -it 7a9586854130 iptables -L KUBE-SEP-QX4RFH7MAHSLU35M -t nat
-
10.244.2.2:80
に宛先が変換されてます - DNATは拡張ターゲットモジュールに含まれているターゲット
-
target prot opt source destination KUBE-MARK-MASQ all -- 10.244.2.2 anywhere /* default/nginx-svc */ DNAT tcp -- anywhere anywhere /* default/nginx-svc */ tcp to:10.244.2.2:80
- 上記で確認した情報はdescribeで見れます
$ kubectl describe services nginx-svc
Name: nginx-svc Namespace: default Labels: <none> Annotations: <none> Selector: app=nginx Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.96.115.121 IPs: 10.96.115.121 Port: <unset> 3000/TCP TargetPort: 80/TCP Endpoints: 10.244.1.2:80,10.244.2.2:80,10.244.2.3:80 Session Affinity: None Events: <none>
kube-proxyのログを確認
- Serviceを見てiptablesを触っているのはkube-proxyです
- Serviceの作成でどんなログが流れているか見てみます
verbosityの変更
- システムログがデフォルトでは最低限しか出力されないようになっているみたいなので変更します
- 値を0からTrace levelの5まで引き上げます
$ kubectl -n kube-system patch daemonsets.apps kube-proxy --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/command/-", "value": "--v=5" }]'
Service作成時のログ
-
$ kubectl logs -f daemonsets/kube-proxy
$ kubectl apply -f svc.yaml
- ログは長いので端折って貼ってます
- 確かにiptablesを更新しているようです
- これが各々ノードに載っているkube-proxyのログから見れます
I0528 11:35:33.263396 1 config.go:336] "Calling handler.OnServiceAdd"
I0528 11:35:33.263930 1 service.go:322] "Service updated ports" service="default/nginx-svc" portCount=1
I0528 11:35:33.264020 1 service.go:437] "Adding new service port" portName="default/nginx-svc" servicePort="10.96.169.188:3000/TCP"
I0528 11:35:33.264052 1 proxier.go:853] "Syncing iptables rules"
I0528 11:35:33.271804 1 config.go:245] "Calling handler.OnEndpointSliceAdd" endpoints="default/nginx-svc-kbg7l"
I0528 11:35:33.286849 1 proxier.go:1464] "Reloading service iptables data" numServices=5 numEndpoints=7 numFilterChains=4 numFilterRules=4 numNATChains=15 numNATRules=34
I0528 11:35:33.289487 1 proxier.go:820] "SyncProxyRules complete" elapsed="25.512602ms"
I0528 11:35:33.289607 1 endpointslicecache.go:358] "Setting endpoints for service port name" portName="default/nginx-svc" endpoints=[10.244.1.2:80 10.244.2.2:80 10.244.2.3:80]
I0528 11:35:33.289646 1 proxier.go:853] "Syncing iptables rules"
I0528 11:35:33.311661 1 proxier.go:1492] "Network programming" endpoint="default/nginx-svc" elapsed=0.311622221
$ kubectl describe service nginx-svc
Name: nginx-svc
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=nginx
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.96.169.188
IPs: 10.96.169.188
Port: <unset> 3000/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.2:80,10.244.2.2:80,10.244.2.3:80
Session Affinity: None
Events: <none>
- ログに出力されたIPアドレスと同じ
1 service.go:437] "Adding new service port" portName="default/nginx-svc" servicePort="10.96.169.188:3000/TCP"
まとめ
- デフォルトでは
- kube-proxyがServiceを見てiptablesを更新している
- ServiceのIPアドレスはコントロールプレーンのDNSに問い合わせている
- iptablesが宛先をServiceから各Podに変換している
Discussion