kOps + Amazon VPC CNI で IP prefixes を使って多くのコンテナを EC2 インスタンスにたてる

2021年7月までは KubernetesAWS VPC CNI を使い EC2 を Node として使用していた場合の Pod の最大数は Elastic Network Interface (ENI) の数と各 ENI の IP Address の数に依存していた。

計算式は下記に記載されていて max Pods = ENI * (IPv4 per ENI - 1) + 2 となっている。
amazon-eks-ami/eni-max-pods.txt at cf97438d38cc6d593eb298660793360de5aced65 · awslabs/amazon-eks-ami · GitHub

インスタンスタイプごとの ENI の数や ENI ごとの IPv4 は下記から確認できる。
Elastic Network Interface - Amazon Elastic Compute Cloud

例えば t2.medium の場合は ENI の最大数は 3 で各 ENI のIPv4 は 6 なので max Pods = ENI * (IPv4 per ENI - 1) + 2 = 3 * (6 - 1) + 2 = 17 となる。

インスタンスタイプとアプリケーションが要求する CPU や Memory にもよるがインスタンスの CPU, Memory がまだ余裕があるが Pod 数の上限がきてしまうこともあったかもしれない。

それが 2021年7月の下記のリリースにあるように IP prefixes を EC2 に割り当てることが可能になったことによって、より多くの Pod を起動できるようになった。
https://aws.amazon.com/jp/about-aws/whats-new/2021/07/amazon-virtual-private-cloud-vpc-customers-can-assign-ip-prefixes-ec2-instances/

kOps では当初ある問題によってこれをサポートできていなかったが無事 11月20日にリリースされた 1.22.2 で対応した。
このエントリでは kOps が当時もっていた問題を共有して kOps でこの機能を使えるまでを紹介する。

IP prefix を EC2 に割り当てる

まず IP prefix を追加すると下記のドキュメントにあるように IPv4 だと /28 IPv6 だと /80 を割り当てることができる。
つまり IPv4 だと 16 IP Address が割り当てられる。
Amazon EC2 ネットワークインターフェイスへのプレフィックスの割り当て - Amazon Elastic Compute Cloud

新規で作成する ENI や既存の ENI への IP prefix の割り当て方法は下記のドキュメントに記載されている。
プレフィックスの使用 - Amazon Elastic Compute Cloud

この機能を使用するにはいくつかの制約があり下記のドキュメントに記載されているが、その中でも Nitro ベースのインスタンスを使用している必要があると記載されている。
Amazon EC2 ネットワークインターフェイスへのプレフィックスの割り当て - Amazon Elastic Compute Cloud

Nitro は AWS Nitro System として 2017 年にリリースされたコンポーネントの集合で、ハードウェアとソフトウェアコンポーネントを組み合わせて高いパフォーマンスと可用性、セキュリティを実現している。
自分の使いたいインスタンスが Nitro System をサポートしているかは下記のコマンドで確認できる。

$ INSTANCE_TYPE="t3a.small"
$ aws ec2 describe-instance-types --instance-type $INSTANCE_TYPE --query 'InstanceTypes[0].Hypervisor'
"nitro"

AWS VPC CNI を使う Kubernetes クラスタで IP prefix を割り当てる

AWS VPC CNI は 1.9.0 から IP prefix をサポートしている。
github.com

そのためまずは自分のクラスタがどのバージョンを使っているかを確認する必要がある。
どのバージョンを使っているかは下記のコマンドで確認できる。

kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2

EKS に関しては下記のドキュメントに従って ENABLE_PREFIX_DELEGATIONWARM_PREFIX_TARGET環境変数を追加して上限の Pods 数を kubelet に指定するなどして IP prefix を EC2 に割り当てる。
Amazon EC2 ノードで使用可能な IP アドレスの量を増やす - Amazon EKS

上限の Pod 数は EC2 インスタンスごとの ENI とそれぞれの ENI に割り当てることができる IP Address の数に依存するため自分で計算して指定する必要がある。

計算式としては max Pods = ENI * ((IPv4 per ENI - 1) * 16) + 2 となっている。
例えば t3.medium だと max Pods = 3 * ((6 - 1) * 16) + 2 = 242 となる。

ただ Kubernetes のベストプラクティスとしては Node ごとの Pod 数は 110 としているので、 t3.medium だとネットワーク的には 242 Pods が起動はできるが現実的には 110 Pods を指定する。 kubernetes.io

このあたりの計算を手動でやるのは面倒だけど公式からスクリプトが提供されているのでこれを使用すれば簡単に計算ができる。

github.com

➜ ./max-pods-calculator.sh --instance-type t3.medium --cni-version 1.9.3 --cni-prefix-delegation-enabled
110

kOps で作成する Kubernetes クラスタでも IP prefix を割り当てる

kOps でも EKS とやることは変わらなくて、 AWS VPC CNI を 1.9.0 以上で使用して必要な環境変数を追加して max Pods を指定するだけ。

kOps では 1.22.0 で下記の AWS VPC CNI 1.9.0 を使用する PR が取り込まれたので使用できる。
github.com

YAML ファイルとしては下記のようになる。

apiVersion: kops/v1alpha2
kind: Cluster
metadata:
  name: test
spec:
  ~~~
  networking:
    amazonvpc:
      env:
      - name: ENABLE_PREFIX_DELEGATION
        value: "true"
      - name: WARM_PREFIX_TARGET
        value: "1"
---
apiVersion: kops/v1alpha2
kind: InstanceGroup
metadata:
  name: nodes
spec:
  ~~~
  kubelet:
    maxPods: 110

ただ 1.22.0, 1.22.1 を使用した場合は上記の設定を使用しても期待した Pod の起動数の上限は変更されない。

kOps 1.22.0, 1.22.1 ではなぜ Pod 数の上限が増えないのか

1.22.1 までは kOps 内の kubelet のオプションを生成する下記のコードで max pods が IP prefix を指定しない時の max pods を超えないように制限される。
github.com

その結果 IP prefix を割り当りあてて、max pods に大きい値を指定しても今まで通り ENI * (IPv4 per ENI - 1) + 2 で計算されたものが使用されてしまう。

問題は分かったので直すためにまずは issue を作成をしました。

github.com

週末にでも修正する PR 作成しようと思っていたら次の日には修正する PR がでて無事その日にマージされ 1.22.2 に入った。

github.com

最後に

1.22.2 以降の kOps を使えば AWS VPC CNI を使用した時に IP prefix を割り当てて Node ごとの 最大 Pod 数を増やすことができる。