[EKS] Separate pod and worker node subnet

andy.chuang
13 min readJun 21, 2022

--

You need to know before study

AWS EKS 預設 CNI為 aws-vpc-cni (daemonSet name: aws-node),這個CNI的特性是EKS Pod IP 會直接使用AWS VPC Subnet裡面的ip,並且直接綁定在Worker node身上的Network interface(primary and secondary ENI).這麼做的理由,我想應該是為了結合AWS的各種原生feature 🤔 (例如 aws-loadbalancer-controller 可以直接將Pod ip跟port註冊到 loadbalence上面 or pod綁定security group…etc).

也因為這個特性,在選擇哪一種instance加入EKS的時候,Document 會提及不同instance type可支援的Pod數量會因為支持的ENI數量而有所不同.

另外,要注意的是,在worker node加入cluster後,會根據instance type不同,來決定預先allocate AWS VPC subnet ip的數量.這麼做的理由是加速我們在scale out pod的時候,能更快速地取得pod ip.

aws-vpc-cni 預設會預先 create 1張 secondary ENI在worker node上,並且將ENI支援最大ip數量先allocated,加入所謂的ip warm pool中.

背景

在EKS cluster越來越多服務塞進來之後,我們面臨到了subnet ip address不足的問題.

幸好我們有針對cluster的 available ip address 設定了監控及Alert.

既然叫了…那接著我們需要開始尋找解藥了…

解藥[1] 建立新的EKS Cluster,並且把一些服務轉移過去.

解藥[2] 使用其他K8S CNI solution來取代EKS default 的 AWS VPC CNI

解藥[3] 新增 Subnet 設定AWS VPC CNI Custom Network,讓Pod放到獨立的Subnet.

開始腦補優缺點比較各個解藥

解藥[1]: 建立新的EKS Cluster

  • Pros: 不會動到既有的環境,既有服務不受解藥影響
  • Cons:
    [1] 遷移後要考慮network peering
    [2] 要盤是否有對外白名單的服務
    [3] 要修改Proxy layer(CDN,GA…etc)的Backend endpoint

解藥[2]: 使用其他K8S CNI solution

  • Pros:
    [1] 可以將pod cidr擴增到超過 /16 (aws vpc limit cidr between /16 ~ /28 ),可用pod數量極限最高.
    [2] 或許可以順便引進功能更多更潮的CNI Solution 😏
  • Cons:
    [1] 修改既有cluster CNI會有Downtime
    [2] 需要長時間的POC來驗證
    [3] 既有架構大量使用aws loadbalancer controller的target group ip mode,讓ALB直接proxy to pod ip.這個模式的服務想像中應該全部會受到影響.或許還要再考慮更換ingress solution 🤢

解藥[3]: AWS VPC CNI Custom Network

  • Pros:
    [1] 能快速解決subnet ip不足的問題.
    [2] 對於既有服務影響最小
    [3] 可以繼續享有AWS VPC CNI優勢,讓我們使用target group ip mode在 NLB 以及 ALB上面
  • Cons:
    [1] 當使用Custom Network時,max-pods 的數量會減少,因為 pod 不再被安排在主機的Primary ENI 上.
    原本的公式:(number of ENIs*(number of secondary IP addresses-1))+2
    使用Custom Network的公式:((number of ENIs-1)*(number of secondary IP addresses-1))+2

經過一番腦補後,決定嚐嚐看解藥[3],而且副作用我們也是能接受的.

修改前後架構示意圖:

修改前:

修改後:

LAB過程:

新增VPC CIDR

10.200.0.0/16是這次要拿來擺放Pod的新CIDR

新增Subnet

既然是專屬pod用的那就不要浪費,將新的VPC CIDR用 /17 subnet mask 切成2份

設定aws-node(AWS VPC CNI Custom Network)

kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true

Apply AWS CNI CRD到eks cluster

準備兩份aws cni crd來完成 custom network setting.
(這邊naming請使用az id,這將會幫助我們下一個步驟設定AWS VPC CNI(aws-node)的自動化.)

  • ap-southeast-1a.yaml
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
name: ap-southeast-1a
spec:
securityGroups:
- sg-069faf764bd0ca2e4 # eks cluster security group
subnet: subnet-0e23943fb8c765d61 # new subnet id
  • ap-southeast-1b.yaml
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
name: ap-southeast-1b
spec:
securityGroups:
- sg-069faf764bd0ca2e4 # eks cluster security group
subnet: subnet-00ff596a8e79872df # new subnet id

kubectl apply -f ap-southeast-1a.yaml -f ap-southeast-1b.yaml

設定aws-node自動化抓取對應的az ENI config

kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone

修改ASG launch configuration

# 自動取得custom network的max pod count
curl -o max-pods-calculator.sh https://raw.githubusercontent.com/awslabs/amazon-eks-ami/master/files/max-pods-calculator.sh
chmod +x max-pods-calculator.sh
# 取得instance type
export instance_type=$(curl -s --retry 5 -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/dynamic/instance-identity/document | jq .instanceType -r)
# 取得所在Region
export AWS_DEFAULT_REGION=$(curl -s --retry 5 -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/dynamic/instance-identity/document | jq .region -r)
# 用AWS提供的script算出custom network的 max pod count
max_pod_count=$(./max-pods-calculator.sh --instance-type ${instance_type} --cni-version 1.11.0-eksbuild.1 --cni-custom-networking-enabled)
# 上面忙老半天就是為了--max-pods 參數,記得最後--use-max-pods要關掉唷
/etc/eks/bootstrap.sh eric-devops-test-cluster --kubelet-extra-args "--node-labels=name=apps,eks.amazonaws.com/nodegroup-image=ami-0a620d8210b5d94ac,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup=eric-app-node-group --max-pods=${max_pod_count}" --b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL --dns-cluster-ip $K8S_CLUSTER_DNS_IP --use-max-pods false

這是我整個步驟研究最久的地方…我不想要跟AWS document一樣 --max-pods參數用hardcode的…無私分享給大家省去測試shell script的時間 🎉

script ref link: https://docs.amazonaws.cn/eks/latest/userguide/choosing-instance-type.html#determine-max-pods

Scale out ASG node for add new node with new launch configuration

這一步驟,記得檢查一下node要能順利加入Cluster,如果失敗可以看一下kubelet的log (他會告訴你為什麼加入cluster失敗)

⚠️重要 ⚠️

If you had nodes in a production cluster with running pods before you switched to using the custom networking feature, complete the following tasks:

  1. Make sure that you have available nodes that are using the custom networking feature.
  2. Cordon and drain the nodes to gracefully shut down the pods. For more information, see Safely Drain a Node in the Kubernetes documentation.

驗證

確認Worker node的ENI狀態

這邊可以看到 10.2.4.92就是primary ENI,在舊的subnet中.而其他pod ip則是全數使用新增的subnet

來看一下Primary ENI 以及 Secondary ENI的狀態

截圖驗證了AWS VPC CNI使用custom network後的缺點,Primary的ENI(10.2.4.92) 上不能再擺Secondary ENI (pod ip)了.

確認ingress能繼續使用 target group ip mode

確認pod對外ip與既有 worker node 相同(保持原有的routing)

確認node max pod capacity有調整成 Custom Network的最大值

kubectl get nodes ip-10-2-4-92.ap-southeast-1.compute.internal -o json | jq '.status.allocatable.pods'
"12"

總結:

這次分享的3種解藥,沒有特別推薦哪一種,畢竟每個人遇到EKS IP不夠的時候,要考量的點都不太一樣.只能選一個最適合自己的解藥囉.
另外,我在撰寫這一篇的同時,又發現了另一種解藥[4](調整AWS VPC CNI Config),等過個幾天有空再來分享給大家 😄

--

--