一見普通なのにKubernetesを破壊するyaml


まずはこちらをご覧ください

お分かりいただけただろうか?

  • instance-1がmaster, instance-2がnodeのk8sクラスタ上で
  • kubectl create -fでyamlを投げた
    • yamlの中身はinstance-2のIPをexternalIPに持つService
投げたdestroy.yaml
apiVersion: v1
kind: Service
metadata:
  name: destroy
spec:
  ports:
  - port: 6666
  externalIPs:
  - 10.128.0.9 # ip of instance-2
  • するとinstance-2が落ちた
  • instance-2にSSHするとなぜかinstance-1にログインした

一見普通のyamlなのになぜ?

解説

この現象は次の要素を組み合わせると起きます

  • kube-proxy mode に IPVS modeを指定したKubernetesクラスタ
  • externalIPにノードのIPを指定したサービスを作る

kube-proxy mode, IPVS mode, externalIPについて説明してから、
なぜその組み合わせがノード障害を起こすのか見ていきます

kube-proxy modeとは?

  • Kubernetesdでは、Serviceをトラフィックの受け口として複数Podへのネットワークの振り分けが実現できます
    • クライアントはServiceのIPを介することで、Podにアクセスできます
  • この振り分けの実現方法を指定するのが kube-proxy mode です。
    • デフォルトではiptablesを使うiptables proxy modeです
    • IPVSというLinux Kernelのロードバランサ機能を用いるのがIPVS proxy mode です
      • v1.11でGAになりました

IPVS modeの挙動

IPVSは次の2つのコンポーネントを組み合わせてロードバランスを実現します

  • Virtual Server(IPVSサーバー): トラフィックの受け口
  • Real Server: トラフィックの転送先

IPVSでServiceのトラフィックの振り分けを実装することを考えると、
Real ServerにはPodを指定すればよいですが、IPVSサーバーに相当するものがありません。

そこで、KubernetesではノードをIPVSサーバーにしてしまいます。kube-ipvs0というダミーのインターフェース(NIC)を作り、
ServiceのIPはそのインターフェースに割り当てます

ExternalIPとは

  • デフォルトではServiceにはKubernetes内部でのみ使用可能なIPアドレスであるClusterIPだけが自動で割り当てられます
    • Kubernetes外部からはPodにアクセスできません
  • 外部からアクセス可能なIPをサービスに紐づけるのがExternalIPです
    • nodeのIPを指定することで、nodeのIPを介してPodへアクセスできるようになります

以上でやっと、冒頭の障害はなぜ起きるのかを説明できる準備ができました

冒頭の障害はなぜ起きるのか

  • destroy.ymlを投げると、クラスタは次の状態になっています
    • 作成するサービスのExternalIPにinstance-2のIPを指定しています
    • ipvsモードでは、サービスのipをノードのkube-ipvs0インターフェースに割り当てます
    • そのため、instance-1kube-ipvs0がExternalIPであるinstance-2のIPを持ってしまいます
      • instance-1instance-2の通信はすべてkube-ipvs0に吸収され、instance-1に戻ってしまいます
        • 通信不能でinstance-2downとKubernetesが判断
        • instance-2にsshするとinstance-1にログイン

補足

自分のクラスタのkube-proxy modeの設定を確認する方法

  • masterで次を実行すると確認できます
kubectl get configmap kube-proxy -n kube-system -o yaml | grep 'mode:'

IPVSモードは必要?

大規模環境では有効です

  • iptablesモードは次の理由から、大規模なクラスタでは重くなってしまいます
    • Podやサービスが増えるほどにiptablesのルールが大量に増える
    • パケットの転送はiptablesのテーブルを辿りながらマッチするルールを探索することになり、ルールが多いほど転送も重くなる(シーケンシャルリスト)
  • そのためIPVSモードが導入されました。
    • IPVSはハッシュテーブルのため、iptablesのような負荷は起きません

参考