この記事について
Kubernetes のサービスとは (2) リソース、API Server、コントローラーの続き。
Kubernetes の Service/Endpoints/EndpointSlice リソースに基づき、通信に必要なネットワーク設定を行う kube-proxy の話。
内容は大体こんな感じ:
- kube-proxy はノードのネットワーク設定を実際に行うコンポーネント
- Service の IP とネットワーク、ロードバランサーは実体はなく、単なる iptables 上のルールである
kube-proxy って何?
kube-proxy は Kubernetes のコンポーネントで、Service/Endpoints/EndpointSlice リソースを監視し、それらの変更(作成・更新・削除)を検知した場合にリソースに登録されている情報をもとにノードのネットワーク設定を行う。
kube-proxy は DaemonSet としてデプロイされているので各ノードで1つずつ Pod が動作している。
# DaemonSet として動作
$ kubectl get daemonset -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-proxy 2 2 2 2 2 kubernetes.io/os=linux 32h
# DaemonSet なので、kube-proxy の Pod は各ノードで実行される
$ kubectl get pod -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE
coredns-565d847f94-p79gc 1/1 Running 1 (37m ago) 32h 192.168.219.71 master
coredns-565d847f94-r794g 1/1 Running 1 (37m ago) 32h 192.168.219.73 master
etcd-master 1/1 Running 1 (37m ago) 32h 10.0.0.5 master
kube-apiserver-master 1/1 Running 1 (37m ago) 32h 10.0.0.5 master
kube-controller-manager-master 1/1 Running 1 (37m ago) 32h 10.0.0.5 master
kube-proxy-fkpfb 1/1 Running 1 (37m ago) 32h 10.0.0.4 node01 ★これ
kube-proxy-mm4rc 1/1 Running 1 (37m ago) 32h 10.0.0.5 master ★これ
kube-scheduler-master 1/1 Running 1 (37m ago) 32h 10.0.0.5 master
kube-proxy が行うネットワーク設定とは?
kube-proxy には3つの動作モードがあり、デフォルトは iptables を構成する iptables
モードになる。つまり、iptables の設定により Service の IP やロードバランスを実現する。
他にも user-space proxy モードと IPVS モードがあるがここでは割愛する。仮想IPとサービスプロキシー にこれらのモードの説明があるので必要に応じて参照いただければ。
kube-proxy による iptables の設定
では、kube-proxy が実際にどんな設定を行うか見てみる。nginx の Deployment(レプリカ数=2)と Service(Type=ClusterIP) が以下のようにデプロイされているとする。Service の IP は 10.110.64.222
で、Pod の IP は 192.168.219.77
, 192.168.196.131
。
# Deployment に含まれる Pod
$ kubectl get pod -l app=nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-b8c585cf6-67wkb 1/1 Running 0 21m 192.168.219.77 master
nginx-b8c585cf6-stsmr 1/1 Running 0 22m 192.168.196.131 node01
# Service
kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.110.64.222 <none> 8080/TCP 32s
kube-proxy は主に NAT テーブルを操作して Service やロードバランスを実現する。上記のリソースをデプロイした後の sudo iptables -n -t nat -L
を実行して関係するところを抜粋したのが以下。
# 入口となる PREROUTING チェイン
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
# 全 Service を含むチェイン
Chain KUBE-SERVICES (2 references)
target prot opt source destination
KUBE-SVC-TCOU7JCQXEZGVUNU udp -- 0.0.0.0/0 10.96.0.10 /* kube-system/kube-dns:dns cluster IP */ udp dpt:53
KUBE-SVC-ERIFXISQEP7F7OF4 tcp -- 0.0.0.0/0 10.96.0.10 /* kube-system/kube-dns:dns-tcp cluster IP */ tcp dpt:53
KUBE-SVC-JD5MR3NA4I4DYORP tcp -- 0.0.0.0/0 10.96.0.10 /* kube-system/kube-dns:metrics cluster IP */ tcp dpt:9153
+ KUBE-SVC-P4Q3KNUAWJVP4ILH tcp -- 0.0.0.0/0 10.110.64.222 /* default/nginx:http cluster IP */ tcp dpt:8080
KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- 0.0.0.0/0 10.96.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
# 個々の Service に該当するチェイン
+ Chain KUBE-SVC-P4Q3KNUAWJVP4ILH (1 references)
+ target prot opt source destination
+ KUBE-MARK-MASQ tcp -- !192.168.0.0/16 10.110.64.222 /* default/nginx:http cluster IP */ tcp dpt:8080
+ KUBE-SEP-5V2UTJDVBQ73MZHD all -- 0.0.0.0/0 0.0.0.0/0 /* default/nginx:http -> 192.168.196.131:80 */ statistic mode random probability 0.50000000000
+ KUBE-SEP-6KKQPHGRLUODOJRX all -- 0.0.0.0/0 0.0.0.0/0 /* default/nginx:http -> 192.168.219.77:80 */
# そのサービスに紐づく Endpoint(Pod の IP:Port) に該当するチェイン : 1個目
+ Chain KUBE-SEP-5V2UTJDVBQ73MZHD (1 references)
+ target prot opt source destination
+ KUBE-MARK-MASQ all -- 192.168.196.131 0.0.0.0/0 /* default/nginx:http */
+ DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/nginx:http */ tcp to:192.168.196.131:80
# そのサービスに紐づく Endpoint(Pod の IP:Port) に該当するチェイン : 2個目
+ Chain KUBE-SEP-6KKQPHGRLUODOJRX (1 references)
+ target prot opt source destination
+ KUBE-MARK-MASQ all -- 192.168.219.77 0.0.0.0/0 /* default/nginx:http */
+ DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/nginx:http */ tcp to:192.168.219.77:80
ややこしく見えるかもしれないが、図にすると結構シンプルに見えると思う。Service 一覧 → Service → Endpoint と段階的に iptables のルールを追って、最後にパケットの宛先を Pod に変換している。
なお、ロードバランスは以下の部分で実現している。この場合は Pod が2つなので50%ずつ振り分けるようになっている。
Chain KUBE-SVC-P4Q3KNUAWJVP4ILH (1 references)
target prot opt source destination
...
KUBE-SEP-5V2UTJDVBQ73MZHD all -- 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.50000000000
KUBE-SEP-6KKQPHGRLUODOJRX all -- 0.0.0.0/0 0.0.0.0/0
statistic の設定については、https://linuxjm.osdn.jp/html/iptables/man8/iptables-extensions.8.html の説明を引用しておく。
statistic
このモジュールは統計的な条件に基づいたパケットのマッチングを行う。 二つのモードがサポートされており、 --mode オプションで設定できる。
サポートされているオプション:
--mode mode
マッチングルールのマッチングモードを設定する。 サポートされているモードは random と nth である。
[!] --probability p
ランダムにパケットがマッチする確率を設定する。 random モードでのみ機能する。 p は 0.0 と 1.0 の範囲でなければならない。 サポートされている粒度は 1/2147483648 である。
NodePort の場合
NodePort とは?
ここまでは ClsuterIP という種類の Service の場合で、この IP は Kubernetes クラスターの中に閉じていて外部から直接アクセスできない。クラスターの外からアクセスするには NodePort という種類の Service が必要になる。すごく概念的な図だがこんなイメージ。
NodePort は実際にはクラスターを構成する各ノード上の IP:Port(上図の例では 172.16.0.1:30037
)で、イメージ的にはここから NodePort → Servcie → Pod という風にパケットが流れる感じである。
NodePort はどういう構成になる?
では NodePort の場合はどう構成されるかというと、実は大きくは変わらない。これまた iptables 上の定義に過ぎず、パケットの宛先ポートが NodePort にマッチすると結局該当の Service(ClusterIP) に行く。 Service(ClusterIP)の前段に NodePort がいるという形になる。
Service(NodePort) を作成すると自動で Service(ClusterIP) も作成されるが、それはこのような通信経路になるためと言える。
いちおう、iptables の nat テーブルに追加された部分を一部抜粋して記載しておく。
# Service 一覧のチェインに NodePort へ行くルールが追加される
Chain KUBE-SERVICES (2 references)
target prot opt source destination
...
KUBE-SVC-P4Q3KNUAWJVP4ILH tcp -- 0.0.0.0/0 10.110.64.222 /* default/nginx:http cluster IP */ tcp dpt:8080
...
KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- 0.0.0.0/0 10.96.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
+ KUBE-NODEPORTS all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
# NodePort 一覧チェイン
+ Chain KUBE-NODEPORTS (1 references)
+ target prot opt source destination
+ KUBE-EXT-P4Q3KNUAWJVP4ILH tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/nginx:http */ tcp dpt:30170
# 個々の NodePort のチェイン。 ペアとなる Service(Cluster)に行く
+ Chain KUBE-EXT-P4Q3KNUAWJVP4ILH (1 references)
+ target prot opt source destination
+ KUBE-MARK-MASQ all -- 0.0.0.0/0 0.0.0.0/0 /* masquerade traffic for default/nginx:http external destinations */
+ KUBE-SVC-P4Q3KNUAWJVP4ILH all -- 0.0.0.0/0 0.0.0.0/0
結局実体は?
これまでも何度か掲載した以下の図で言えば、赤色の Service Network、IP(Service の ClusterIP)、ロードバランサーは実体がなく iptables 上の定義として存在しているだけということになる。上で見たように NodePort も同様である。
続き
執筆中。。。
補足・おまけ
Pod からの iptables 設定
kube-proxy の Pod(つまりコンテナ)の中からノード側の iptables を設定できるのかという話があるが、privileged: true
という特権モード(root 相当)で動いているので可能になる。
# DaemonSet のマニフェスト(一部のみ抜粋して記載)
$ kubectl get daemonset -n kube-system -o yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-proxy
namespace: kube-system
spec:
template:
spec:
containers:
- command:
- /usr/local/bin/kube-proxy
- --config=/var/lib/kube-proxy/config.conf
- --hostname-override=$(NODE_NAME)
image: registry.k8s.io/kube-proxy:v1.25.2
name: kube-proxy
securityContext:
privileged: true ★これ
kube-proxy のデバッグログ表示
マネージドの Kubernetes だと出来ない場合が多いと思うが、自前で構築した場合はログレベルの変更を変えることができる。
以下のコマンドを実行する。
kubectl edit daemonset -n kube-system kube-proxy
マニフェストが開くので、以下の行を追加して保存する。数字の 5
の部分が大きいほどより詳細なログが出る(たぶん 0 ~ 9 まで指定できる)。これだけで OK。
spec:
containers:
- command:
- /usr/local/bin/kube-proxy
- --config=/var/lib/kube-proxy/config.conf
- --hostname-override=$(NODE_NAME)
- -v5 ★この行を追加
例えば、以下のようなログが見れて kube-proxy の動作を把握しやすくなる。
I0925 02:11:33.975296 1 service.go:440] "Adding new service port" portName="default/nginx:http" servicePort="10.102.246.227:8080/TCP"
I0925 02:11:33.975336 1 proxier.go:850] "Syncing iptables rules"
I0925 02:11:33.975343 1 iptables.go:467] running iptables: iptables [-w 5 -W 100000 -N KUBE-EXTERNAL-SERVICES -t filter]
iptables のチェイン名の Prefix
ソースで以下のように定義されている。
const (
servicePortPolicyClusterChainNamePrefix = "KUBE-SVC-"
servicePortPolicyLocalChainNamePrefix = "KUBE-SVL-"
serviceFirewallChainNamePrefix = "KUBE-FW-"
serviceExternalChainNamePrefix = "KUBE-EXT-"
servicePortEndpointChainNamePrefix = "KUBE-SEP-"
// For cleanup. This can be removed after 1.26 is released.
deprecatedServiceLBChainNamePrefix = "KUBE-XLB-"
)