Ciliumのkube-proxy置き換えをGKEで試してみた

AvatarPosted by

SREの徳田です。

今回はCiliumのv1.7からGAになったKubernetes without kube-proxyのタイトルで出てる、Ciliumによるkube-proxyの置き換えの機能をGKE上で試してみようと思います。

Ciliumとは

こちら の記事の後半を見てみると若干書いてあります😌

要はBPFを使いこなし、Blazing Performanceで処理するぞ💪というProductです。

概要についてはこの記事からそれるのでリンクだけおいておきます。

https://cilium.io/

クラスタの準備

それでは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が挿入します。)

動作確認

動作確認のためデモアプリをデプロイして動作するか見てみましょう。以下のリポジトリのデモアプリを利用します。

GoogleCloudPlatform/microservices-demo: Sample cloud-native application with 10 microservices showcasing Kubernetes, Istio, gRPC and OpenCensus.

以下のコマンドを実行します。

$ 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などもあり、これから実装される機能もあるので、目が離せないですね!!

それでは!