Fixed “NotReady” nodes using warm pools on Amazon EKS

2022-03-26 AWS, Kubernetes

今天在 DevAx Alliance meetup 活動中跟客戶聊到關於 Amazon EKS 運行 Worker node 的方法,大家最為知道的不外乎 Self-managed nodes, Managed node groups 以及 AWS Fargate pods 這三種方法,AWS Fargate 通常是最容易管理且符合實際 Pod 成本的一種方法,但是在某些情境下 AWS Fargate 並不能支援 e.g. daemonSet。這意味者 EKS Cluster 就必須採用 mixed instance 的方法針對不同的情境使用特定 managed nodes。而在這篇作者將重點放在 Amazon EKS 搭配 Warm pool 來進行 Scale-out。

Warm pool on Amazon EKS

Amazon EKS 使用 Amazon EC2 作為底層 Worker node 最容易遇到 Worker node 啟動速度不夠快或是 scale-out 拿不到機器的情況,而 Auto Scaling Warm Pools 是在 2021 年支援的一個非常好用的功能,讓你能夠先 provisioned 一定數量的 Amazon EC2 先拿到手上,然後再 stop instance 只需要支付 EBS 的費用。同樣的這個功能也可以實現在以 Amazon EC2 Worker node 的 Amazon EKS。

為何啟動 warm pool 會產生 “NotReady” nodes?

如果你嘗試將 Amazon EKS Autoscaling group 加上 warm instance 時你可能會發現 kubectl get nodes 會出現一些 NotReady nodes,而這些 NotReady nodes 正是 warm node。你必須等待 Kubernetes 自己刪除這些 provisioned 的 warm node。雖然這不是很大的影響,但始終需要 Kubernetes 額外付出一些執行成本,我們需要一個漂亮的解法來避免造成 Kubernetes 的負擔。

首先先了解在 Warm pool 文件中有提到 Warm pool instance 的執行過程如下圖:

簡單來講就是以下步驟:

  1. 請求 Amazon EC2 機器並初始化
  2. 初始化過程包含 health check 以及 user-data 執行
  3. 初始化完成後即 Stop instance

由於 Amazon EKS worker node 在 user-data 都必須透過 bootstrap.sh 來加入 EKS Cluster,導致 warm pool lifecycle 都會執行一次 user-data 加入 EKS Cluster 後 stop worker node 節點而留下 NotReady node 等待 EKS Cluster 移除他。

要解決這個問題,只有在 warming up 再跑 bootstrap.sh 加入 EKS Cluster 就好,而 provisioning 時就先不跑這段 bootstrap。作者在 Google 找到一篇「Rapid Auto-Scaling on EKS — Part 2」談到了相同的問題,精準而簡單的解決方法是直接對 user-data 動手腳:

if [[ $(type -P $(which aws)) ]] && [[ $(type -P $(which jq)) ]] ; then
	TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
	INSTANCE_ID=$(curl url -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
	REGION=$(curl url -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region)
	LIFECYCLE=$(aws autoscaling describe-auto-scaling-instances --region $REGION --instance-id $INSTANCE_ID | jq ".AutoScalingInstances[].LifecycleState" || true)
	if [[ $LIFECYCLE == *"Warmed"* ]]; then
		rm /var/lib/cloud/instances/$INSTANCE_ID/sem/config_scripts_user
		exit 0
	fi
fi

# Call bootstrap.sh here

如果是 Windows 可以用以下 user-data

[string]$IMDSToken=(curl -UseBasicParsing -Method PUT "http://169.254.169.254/latest/api/token" -H @{ "X-aws-ec2-metadata-token-ttl-seconds" = "21600"} | % { Echo $_.Content})
[string]$InstanceID=(curl -UseBasicParsing -Method GET "http://169.254.169.254/latest/meta-data/instance-id" -H @{ "X-aws-ec2-metadata-token" = "$IMDSToken"} | % { Echo $_.Content})
[string]$Lifecycle = Get-ASAutoScalingInstance $InstanceID | % { Echo $_.LifecycleState}
if ($Lifecycle -like "*Warmed*") {
  Echo "Not starting Kubelet due to warmed state."
  & C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 –Schedule
} else {
  & $EKSBootstrapScriptFile -EKSClusterName {{ .ClusterName }} -KubeletExtraArgs '{{ .KubeletExtraArgs }}' 3>&1 4>&1 5>&1 6>&1
  {{range $post := .PostBootstrap}}{{$post}}{{end}}
}
  
# Call Start-EKSBootstrap.ps1 here

簡單解釋一下 user-data 做了哪些事:

  1. 169.254.169.254 拿到 Region, Instance-Id 以及 LifecycleState
  2. 判斷 EC2 目前跑的 LifecycleState 是 Warm pool
  3. 如果是,則刪除 config_scripts_user 這個檔案,然後 exit 0 跳出這個 user-data
  4. 在 exit 0 以下的 bootstrap.sh 則不會執行

Amazon EC2 上的 cloud-init state

在 Amazon EC2 上的 user-data 是利用 cloud-init 來實作 user-data 這項功能,而 init 的文件中有提到:

init

Generally run by OS init systems to execute cloud-init’s stages init and init-local. See Boot Stages for more info. Can be run on the commandline, but is generally gated to run only once due to semaphores in /var/lib/cloud/instance/sem/ and /var/lib/cloud/sem.

cloud-init 是依據 /var/lib/cloud/instance/sem/var/lib/cloud/sem 這兩個目錄底下的檔案作為 run-state 判斷依據,這裡面有非常多的 config_* 檔案,其中 /var/lib/cloud/instances/${INSTANCE_ID}/sem/config_scripts_user (also softlink to /var/lib/cloud/instance/sem/config_scripts_user) 這隻檔案是用於判斷 user-data 是否執行過的依據。

所以意味者我可以在 user-data 內判斷「是否在 *Warmed* 階段」後再刪除 run-state 檔案讓下一次能夠再次執行 user-data script,而為了讓這次 Warmed 階段不執行 bootstrap.sh 產生 NotReady nodes,所以 exit 0 跳出 user-data 執行。

除了在 user-data 做手腳以外,在 instance-manager#277 的內文也有人分享「Blazing fast Kubernetes scaling with ASG warm pools」用 nodeup 來阻止 kubelet 啟動 & 註冊 Kubernetes Cluster,其判斷依據也是檢查 ASG lifecycle

現在,你也能透過 Warm pool 來加速 Amazon EKS pods 的啟動速度,也能避免要 scale-out 時拿不到 Amazon EC2 機器的困擾。

給 Mr. 沙先生一點建議

彙整

分類

展開全部 | 收合全部

License

訂閱 Mr. 沙先生 的文章

輸入你的 email 用於訂閱