EKSクラスタである程度規模の大きいアプリケーションを動かしたところサブネットのIPアドレスが枯渇してしまった。
IPアドレス枯渇の要因
Pod の IP アドレスは VPC CNI の ipamd (IP Address Management Daemon) によって割り当てられるが、これはインスタンスのセカンダリIPアドレスから払い出されている。
セカンダリIPアドレスの数は VPC CNI の設定の予備 ENI 数である WARM_ENI_TARGET (Default: 1) または 予備 IPアドレス数の WARM_IP_TARGET (Default: None) によって制御される。 デフォルトでは最初の Pod が動くときに ENI の残り IP アドレスが 1 つ減ることによって 2 つ目の ENI がアタッチされ、ENI にアタッチできる最大数の IP アドレスが新たに保持される。 インスタンスにアタッチできる ENI と ENI にアタッチできるアドレスの最大数はインスタンスタイプによって異なる。
これにより、特に大きいインスタンスタイプのノードで少数の Pod が動いているような場合に必要以上に多くの IP アドレスが保持されてしまうことになる。 そのような場合は WARM_IP_TARGET を設定することで必要最小限の IP アドレスを保持することができる。 クラスタの規模が大きいと EC2 API の呼び出しが大量に発生しスロットリングしてしまうことがあるようで、これを回避するため MINIMUM_IP_TARGET を併せて設定することが推奨されている。
$ kubectl set env ds aws-node -n kube-system WARM_IP_TARGET=2
CDK で変更することもできる。
new eks.KubernetesPatch(this, 'LimitWarmIPAddr', {
cluster,
resourceName: 'daemonset/aws-node',
resourceNamespace: 'kube-system',
applyPatch: {
spec: {
template: {
spec: {
containers: [{
name: 'aws-node',
env: [{
name: 'WARM_IP_TARGET',
value: '2',
}]
}],
},
},
},
},
restorePatch: {},
})
この例の場合 5 pod に対して 7 アドレスが保持されている。
IPv6への移行
ただ、大量の Pod が動くことによる IP アドレスの枯渇はサブネットの CIDR ブロックを大きくする以外に根本的な解決法がなく規模や構成によっては難しいことがある。 そんな場合はクラスタの ipFamily を IPv6 にすることで Pod に IPv6 アドレスが割り当てられるようにすることができる。この設定はクラスタの作成時にしか行うことができない。また、サブネットに IPv6 ブロックが割り当てられている必要があり、Nitro ベースでない古いインスタンスタイプには対応していない。
new eks.Cluster(this, 'cluster', {
...
ipFamily: eks.IpFamily.IP_V6
})
アドレスは IPv6 プレフィックスから払い出される。IPv6 プレフィックスが設定されていなかったり ec2:AssignIpv6Addresses 権限がないと failed to assign an IP address to container エラーになる。 ちなみに IPv4 でも ENABLE_PREFIX_DELEGATION=true にすると同様にプレフィックスから払い出されるため 1 ノードで多くの Pod を起動することはできるようになる。
nodeRole.attachInlinePolicy(new iam.Policy(scope, 'IPV6Policy', {
statements: [
new iam.PolicyStatement({
actions: ["ec2:AssignIpv6Addresses", "ec2:UnassignIpv6Addresses"],
resources: ["arn:aws:ec2:*:*:network-interface/*"],
}),
],
}))
Pod 間は IPv6 で通信するが、外部の IPv4 エンドポイントとも通信できるように、 ホストローカルな IPv4 アドレスも割り当てられ、ホストの ENI の IPv4 アドレスに NAT されるようになっている。
パブリックIPv4アドレスを持たないEC2インスタンスがIPv6で外部と通信するために必要なリソースをCDKで作成する - sambaiz-net
マネージドノードグループでない場合、bootstrap.sh のパラメータを渡さないと Pod に IPv6 アドレスが割り当てられなかった。
CDKのAwsCustomResourceでAWSのAPIを呼ぶ - sambaiz-net
const clusterInfo = new custom_resources.AwsCustomResource(this, 'EksDescribeCluster', {
onUpdate: {
service: 'eks',
action: 'describeCluster',
parameters: {
name: cluster.clusterName,
},
physicalResourceId: custom_resources.PhysicalResourceId.fromResponse("cluster.arn")
},
policy: custom_resources.AwsCustomResourcePolicy.fromSdkCalls({
resources: [cluster.clusterArn]
}),
})
cluster.addAutoScalingGroupCapacity('EKSClusterDefaultCapacity', {
...
bootstrapOptions: {
additionalArgs: `--ip-family ipv6 --service-ipv6-cidr ${clusterInfo.getResponseField("cluster.kubernetesNetworkConfig.serviceIpv6Cidr")}`,
},
})