SREの徳田です。
今回はCiliumのv1.7からGAになったKubernetes without kube-proxyのタイトルで出てる、Ciliumによるkube-proxyの置き換えの機能をGKE上で試してみようと思います。
Ciliumとは
こちら の記事の後半を見てみると若干書いてあります😌
要はBPFを使いこなし、Blazing Performanceで処理するぞ💪というProductです。
概要についてはこの記事からそれるのでリンクだけおいておきます。
クラスタの準備
それではGKEのクラスタの準備をしましょう。
google-cloud-sdk
とGCPに対しての認証とプロジェクトの指定は終わらせているものとします。
CLUSTER_NAME=cilium-cluster
CLUSTER_ZONE=asia-northeast1-a
gcloud beta container clusters create $CLUSTER_NAME \
--image-type COS \
--num-nodes 3 \
--machine-type e2-medium \
--release-channel "regular" \
--enable-ip-alias \
--zone $CLUSTER_ZONE
CLIじゃなくてもWeb上のコンソールで作成する際のデフォルトの設定かなと思います。
Ciliumの導入
さて、それでは早速作成したクラスタにCiliumを導入します。
NATIVE_ROUTING_CIDR=`gcloud beta container clusters describe $CLUSTER_NAME --zone $CLUSTER_ZONE --format 'value(clusterIpv4Cidr)'`
MASTER_ENDPOINT=`gcloud beta container clusters describe $CLUSTER_NAME --zone $CLUSTER_ZONE --format 'value(endpoint)'`
kubectl create ns cilium
helm install cilium cilium/cilium --version 1.8.1 \
--namespace cilium \
--set global.nodeinit.enabled=true \
--set nodeinit.reconfigureKubelet=true \
--set nodeinit.removeCbrBridge=true \
--set nodeinit.restartPods=true \
--set global.cni.binPath=/home/kubernetes/bin \
--set global.gke.enabled=true \
--set config.ipam=kubernetes \
--set global.nativeRoutingCIDR=$NATIVE_ROUTING_CIDR \
--set global.kubeProxyReplacement=strict \
--set global.k8sServiceHost=$MASTER_ENDPOINT \
--set global.k8sServicePort=443
オプションについて少しだけ解説。
オプション前半のこれらは Installation on Google GKE にあるものです。
--set global.nodeinit.enabled=true \
--set nodeinit.reconfigureKubelet=true \
--set nodeinit.removeCbrBridge=true \
--set nodeinit.restartPods=true \
--set global.cni.binPath=/home/kubernetes/bin \
--set global.gke.enabled=true \
--set config.ipam=kubernetes \
--set global.nativeRoutingCIDR=$NATIVE_ROUTING_CIDR \
一つだけオプションを追加していて、 nodeinit.restartPods
でデプロイされているPodの再起動を行っています。主には kube-system
にあるPod向けです。
オプション後半のこれらの設定がkube-proxy置き換えの機能有効化とそれにあたっての設定です。
--set global.kubeProxyReplacement=strict \\
--set global.k8sServiceHost=$MASTER_ENDPOINT \\
--set global.k8sServicePort=443
kubeProxyReplacement
にはパラメータの種類が4つあります。
strict
: 機能の有効化。kernelバージョンがサポートされていない場合にAgentがエラーを出すprobe
: kube-proxyの部分的な置き換えと、kernelバージョンがサポートされていない場合はBPFの機能を無効にし、kube-proxyで処理をするparial
:probe
に近いが、BPFで処理する機能の有無をユーザーが明示的に指定する必要がある。disabled
: 機能の無効化。
また、 k8sServiceHost
/ k8sServicePort
でKubernetesのMasterのEndpointを指定します。通常kube-proxyが kubernetes
サービスでうまくルーティングしてくれますが、kube-proxyを置き換えた環境ではCiliumが疎通性を確保する必要があるため設定でMasterのEndpointを教えて上げる必要があります。
kube-proxyを外す & iptablesのルールの初期化
動作する上ではこの手順は必要ないのですが、kube-proxyいなくても動作するよね?という意味でもやってみましょう。(但しこれが一番の沼だった😇)
GKEのkube-proxyはStatic Podとして動作しているため、Node上においてあるkube-proxyのManifestを所定のディレクトリから移動または削除でkube-proxyを停止させることができます。
これと合わせて、iptablesに挿入されていたルールを初期化しましょう。
以下のDaemonSetをデプロイします。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: disable-kube-proxy
namespace: kube-system
spec:
selector:
matchLabels:
name: disable-kube-proxy
template:
metadata:
labels:
name: disable-kube-proxy
spec:
initContainers:
- command:
- /bin/bash
- -c
- |
echo 'Install iptables'
apt-get update && apt-get install -y iptables
echo 'Kill kube-proxy(Move manifest)'
if [ -f /etc/kubernetes/manifests/kube-proxy.manifest ]; then
mkdir -p /etc/kubernetes/manifests.bak
mv /etc/kubernetes/manifests/kube-proxy.manifest /etc/kubernetes/manifests.bak
fi
echo -n 'Wait for kube-proxy to be killed...'
while ps x | awk '{print $5}' | grep 'kube-proxy' > /dev/null 2>&1
do
sleep 1;echo -n '.';
done
echo ' OK.'
echo 'Reconfigure the iptables rules'
. /home/kubernetes/kube-env
. /home/kubernetes/bin/configure-helper.sh
iptables -t nat -F && iptables -t nat -X && \
iptables -t mangle -F && iptables -t mangle -X && \
iptables -t filter -F && iptables -t filter -X && \
sh /usr/share/cloud/iptables-setup && \
config-ip-firewall
echo 'All Done!!!'
image: ubuntu:focal
name: disable-kube-proxy
securityContext:
privileged: true
volumeMounts:
- mountPath: /run/xtables.lock
name: iptableslock
- mountPath: /etc/kubernetes
name: kubernetes-configs
- mountPath: /home/kubernetes
name: kubernetes-home
readOnly: true
- mountPath: /lib/modules
name: lib-modules
readOnly: true
- mountPath: /usr/share/cloud
name: cloud-scripts
readOnly: true
containers:
- image: k8s.gcr.io/pause
name: pause
hostNetwork: true
hostPID: true
terminationGracePeriodSeconds: 0
volumes:
- hostPath:
path: /run/xtables.lock
type: FileOrCreate
name: iptableslock
- hostPath:
path: /etc/kubernetes
type: Directory
name: kubernetes-configs
- hostPath:
path: /home/kubernetes
type: Directory
name: kubernetes-home
- hostPath:
path: /lib/modules
type: Directory
name: lib-modules
- hostPath:
path: /usr/share/cloud
type: Directory
name: cloud-scripts
適用後、 Running
になるとkube-proxyのコンテナがなくなっていることが確認できると思います。
$ kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
disable-kube-proxy-ffg5z 1/1 Running 0 116s
disable-kube-proxy-grs8x 1/1 Running 0 117s
disable-kube-proxy-mxpck 1/1 Running 0 2m4s
event-exporter-gke-6c9d8bd8d8-fjp5g 2/2 Running 2 14m
fluentd-gke-dftcj 2/2 Running 0 13m
fluentd-gke-nffmq 2/2 Running 0 13m
fluentd-gke-scaler-cd4d654d7-wqc6j 1/1 Running 1 14m
fluentd-gke-zcfz4 2/2 Running 0 13m
gke-metrics-agent-fxp7g 1/1 Running 0 13m
gke-metrics-agent-grnn2 1/1 Running 0 13m
gke-metrics-agent-t6jcn 1/1 Running 0 13m
kube-dns-56d8cd994f-pc25z 4/4 Running 0 107s
kube-dns-56d8cd994f-s6xhd 4/4 Running 0 2m2s
kube-dns-autoscaler-645f7d66cf-hddrq 1/1 Running 1 14m
l7-default-backend-678889f899-c8mv8 1/1 Running 1 14m
metrics-server-v0.3.6-7b7d6c7576-d5f4j 2/2 Running 2 13m
prometheus-to-sd-5nwbm 1/1 Running 0 13m
prometheus-to-sd-bqq88 1/1 Running 0 13m
prometheus-to-sd-tjbsx 1/1 Running 0 13m
stackdriver-metadata-agent-cluster-level-fbd8c6c5-f988k 2/2 Running 2 12m
一応iptablesのルールも確認してみましょう。Node名を取得してsshして確認してみます。
$ gcloud beta compute ssh `kubectl get node -o jsonpath='{.items[0].metadata.name}'` --zone "asia-northeast1-a"
$ sudo iptables -L
Chain INPUT (policy DROP)
target prot opt source destination
KUBE-FIREWALL all -- anywhere anywhere
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT all -- anywhere anywhere
ACCEPT icmp -- anywhere anywhere
ACCEPT tcp -- anywhere anywhere
ACCEPT udp -- anywhere anywhere
ACCEPT icmp -- anywhere anywhere
ACCEPT sctp -- anywhere anywhere
Chain FORWARD (policy DROP)
target prot opt source destination
ACCEPT tcp -- anywhere anywhere
ACCEPT udp -- anywhere anywhere
ACCEPT icmp -- anywhere anywhere
ACCEPT sctp -- anywhere anywhere
Chain OUTPUT (policy DROP)
target prot opt source destination
KUBE-FIREWALL all -- anywhere anywhere
ACCEPT all -- anywhere anywhere state NEW,RELATED,ESTABLISHED
ACCEPT all -- anywhere anywhere
Chain KUBE-FIREWALL (2 references)
target prot opt source destination
DROP all -- anywhere anywhere /* kubernetes firewall for dropping marked packets */ mark match 0x8000/0x8000
$ sudo iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
KUBE-POSTROUTING all -- anywhere anywhere /* kubernetes postrouting rules */
IP-MASQ all -- anywhere anywhere /* ip-masq: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom IP-MASQ chain */ ADDRTYPE match dst-type !LOCAL
Chain IP-MASQ (1 references)
target prot opt source destination
RETURN all -- anywhere 169.254.0.0/16 /* ip-masq: local traffic is not subject to MASQUERADE */
RETURN all -- anywhere 10.0.0.0/8 /* ip-masq: RFC 1918 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 172.16.0.0/12 /* ip-masq: RFC 1918 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 192.168.0.0/16 /* ip-masq: RFC 1918 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 240.0.0.0/4 /* ip-masq: RFC 5735 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 192.0.2.0/24 /* ip-masq: RFC 5737 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 198.51.100.0/24 /* ip-masq: RFC 5737 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 203.0.113.0/24 /* ip-masq: RFC 5737 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 100.64.0.0/10 /* ip-masq: RFC 6598 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 198.18.0.0/15 /* ip-masq: RFC 6815 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 192.0.0.0/24 /* ip-masq: RFC 6890 reserved range is not subject to MASQUERADE */
RETURN all -- anywhere 192.88.99.0/24 /* ip-masq: RFC 7526 reserved range is not subject to MASQUERADE */
MASQUERADE all -- anywhere anywhere /* ip-masq: outbound traffic is subject to MASQUERADE (must be last in chain) */
Chain KUBE-MARK-DROP (0 references)
target prot opt source destination
MARK all -- anywhere anywhere MARK or 0x8000
Chain KUBE-MARK-MASQ (0 references)
target prot opt source destination
MARK all -- anywhere anywhere MARK or 0x4000
Chain KUBE-POSTROUTING (1 references)
target prot opt source destination
MASQUERADE all -- anywhere anywhere /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000
kube-proxyによるルールはなくなっていることがわかるかと思います。
( KUBE-FIREWALL
はkubeletが挿入します。)
動作確認
動作確認のためデモアプリをデプロイして動作するか見てみましょう。以下のリポジトリのデモアプリを利用します。
以下のコマンドを実行します。
$ kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/microservices-demo/master/release/kubernetes-manifests.yaml
deployment.apps/emailservice created
service/emailservice created
deployment.apps/checkoutservice created
service/checkoutservice created
deployment.apps/recommendationservice created
service/recommendationservice created
deployment.apps/frontend created
service/frontend created
service/frontend-external created
deployment.apps/paymentservice created
service/paymentservice created
deployment.apps/productcatalogservice created
service/productcatalogservice created
deployment.apps/cartservice created
service/cartservice created
deployment.apps/loadgenerator created
deployment.apps/currencyservice created
service/currencyservice created
deployment.apps/shippingservice created
service/shippingservice created
deployment.apps/redis-cart created
service/redis-cart created
deployment.apps/adservice created
service/adservice created
暫くすると service/frontend-external
にEXTENAL-IPが割り当てられます。以下のコマンドで確認できます。
$ kubectl get svc frontend-external -o jsonpath={.status.loadBalancer.ingress[0].ip}
34.84.88.110
表示されたIPにブラウザでアクセスすることでデモアプリを確認することができるかと思います。
転送ルールの確認
最後にCiliumがどのような転送を行っているかを確認してみましょう。 cilium
のPodでCLIを使用して確認することができます。
$ kubectl exec -it -n cilium `kubectl get po -n cilium -l k8s-app=cilium -o jsonpath='{.items[0].metadata.name}'` -- cilium service list
ID Frontend Service Type Backend
1 10.0.0.1:443 ClusterIP 1 => 35.190.238.99:443
2 10.0.0.10:53 ClusterIP 1 => 10.32.0.18:53
2 => 10.32.1.76:53
3 10.0.11.64:80 ClusterIP 1 => 10.32.0.10:8080
4 10.146.0.20:30275 NodePort 1 => 10.32.0.10:8080
5 0.0.0.0:30275 NodePort 1 => 10.32.0.10:8080
6 10.0.4.132:443 ClusterIP 1 => 10.32.1.183:443
7 10.0.11.213:5000 ClusterIP 1 => 10.32.2.254:8080
8 10.0.6.151:5050 ClusterIP 1 => 10.32.0.41:5050
9 10.0.12.180:8080 ClusterIP 1 => 10.32.1.2:8080
10 10.0.12.101:80 ClusterIP 1 => 10.32.1.151:8080
11 10.0.15.133:80 ClusterIP 1 => 10.32.1.151:8080
12 10.146.0.20:31047 NodePort 1 => 10.32.1.151:8080
13 0.0.0.0:31047 NodePort 1 => 10.32.1.151:8080
14 10.0.3.147:50051 ClusterIP 1 => 10.32.1.111:50051
15 10.0.12.224:3550 ClusterIP 1 => 10.32.1.158:3550
16 10.0.13.114:7070 ClusterIP 1 => 10.32.2.100:7070
17 10.0.8.222:7000 ClusterIP 1 => 10.32.2.21:7000
18 10.0.6.98:50051 ClusterIP 1 => 10.32.1.99:50051
19 10.0.1.24:6379 ClusterIP 1 => 10.32.0.28:6379
20 10.0.15.58:9555 ClusterIP 1 => 10.32.0.221:9555
21 34.84.88.110:80 LoadBalancer 1 => 10.32.1.151:8080
以上のような転送をBPFを用いて行っているようです。
最後に
Ciliumのkube-proxyの置き換え機能を試してみました。
この機能が出る前からCilium(BPF)でiptablesの置き換えしたら良さそうだな!!と思ったらほんとに実装されてびっくりしました。
また、どうやらistioとの連携も実装を進めているようですが、ある構成ではistioを抜いてCiliumとenvoyのみで構成するものもあるようです。
他の機能ではL3/L4/L7のPolicy制御やhubbleを使ったObservabilityなどもあり、これから実装される機能もあるので、目が離せないですね!!
それでは!