如果您是自建的Kubernetes叢集,且期望根據實際工作負載動態調整工作節點數量,確保資源的有效利用並維持服務的穩定性,可通過Cluster Autoscaler和阿里雲Auto Scaling實現。
工作原理
Cluster AutoScaler(簡稱CA)是一個自動擴充和收縮Kubernetes叢集節點的組件。CA會定期檢測是否有因資源不足而處於Pending狀態的Pod,如果有,會驅動伸縮組進行擴容,其工作原理如下圖所示:
CA在監測到某些節點資源使用率持續低於預設的閾值,且這些節點上的Pod能夠遷移到其他節點時,會先將Pod驅逐到其他節點,之後驅動伸縮組進行縮容,其工作原理如下圖所示:
更多Cluster AutoScaler的資訊,請參見Cluster Autoscaling官方介紹。
準備工作
在操作前,請確保您已經完成以下工作。
已自建Kubernetes叢集,且叢集版本在v1.9.3及以上。
重要本文檔基於在阿里雲ECS上搭建的K8s叢集進行測試,如果涉及雲下IDC機器、跨雲供應商等混合雲情境,建議參考VPN網關或者Smart Access Gateway等產品解決網路連通性問題。
建立RAM使用者。
當CA需要訪問阿里雲ESS時,必須先通過訪問憑證來驗證身份資訊和存取權限。您需要為CA建立RAM使用者並授予訪問ESS的許可權。
建立一個RAM使用者,並開啟OpenAPI存取控制。具體操作,請參見建立RAM使用者。
為RAM使用者授權以下自訂權限原則。如何為RAM使用者授權,請參見為RAM使用者授權。
{ "Version": "1", "Statement": [ { "Action": [ "ess:Describe*", "ess:CreateScalingRule", "ess:ModifyScalingGroup", "ess:RemoveInstances", "ess:ExecuteScalingRule", "ess:ModifyScalingRule", "ess:DeleteScalingRule", "ess:DetachInstances", "ecs:DescribeInstanceTypes" ], "Resource": [ "*" ], "Effect": "Allow" } ] }
建立AccessKey並儲存AccessKey ID和AccessKey Secret,在後續步驟中會使用。如何建立AccessKey,請參見建立AccessKey。
操作步驟
(可選)步驟一:構建Cluster AutoScaler鏡像
通過源碼構建自己的Cluster AutoScaler鏡像,該鏡像用於在您的K8s叢集部署Cluster AutoScaler。
您可以直接跳過此步驟,直接使用阿里雲已構建好的cluster-autoscaler鏡像:ess-cluster-autoscaler-registry.cn-hangzhou.cr.aliyuncs.com/ess-cluster-autoscaler/cluster-autoscaler:v1.7。
從Github下載源碼。
mkdir -p $GOPATH/src/github.com/kubernetes cd $GOPATH/src/github.com/kubernetes git clone https://github.com/kubernetes/autoscaler.git cd autoscaler
構建鏡像。
# 編譯 cd cluster-autoscaler && make build-arch-amd64 # 構建鏡像 docker build -t cluster-autoscaler:v1.0 -f Dockerfile.amd64 . # 打Tag docker tag cluster-autoscaler:v1.0 您的鏡像倉庫網域名稱/cluster-autoscaler:v1.0 # 上傳鏡像 docker push 您的鏡像倉庫網域名稱/cluster-autoscaler:v1.0
步驟二:建立並配置伸縮組
建立伸縮組。
登入阿里雲Auto Scaling控制台。
在頂部功能表列選擇可用性區域,在左側點擊伸縮組管理,點擊建立伸縮組。
在通過表單建立頁簽下,完成伸縮組配置,然後點擊建立按鈕。本樣本採用以下配置,更多關於伸縮組的配置說明,請參見建立伸縮組。
配置項
說明
樣本
伸縮組名稱
輸入伸縮組名稱,格式參照介面提示。
K8s-Node-Scaling-Group
伸縮群組類型
選擇ECS,表示伸縮組內的執行個體類型為ECS執行個體。
ECS
組內執行個體配置資訊來源
先不指定自動建立執行個體的模板。伸縮組建立完成後,您需要繼續建立伸縮配置。
從零開始建立
組內最小執行個體數
代表伸縮組最少有0台ECS執行個體。
0
組內最大執行個體數
代表伸縮組最大有5台ECS執行個體。
5
專用網路
該伸縮組下建立的ECS執行個體會在此專用網路下。
vpc-test****-001
選擇交換器
您可以配置多個可用性區域的交換器以提高擴容成功率。
vsw-test****
重要在伸縮組建立完成後,請記錄您的可用性區域和伸縮組ID以供後續步驟使用。
為伸縮組建立伸縮配置。
找到剛剛建立的伸縮組,點擊查看詳情進入伸縮組詳情頁。
在執行個體配置來源頁簽下,點擊伸縮配置,點擊建立伸縮配置按鈕進入建立伸縮配置頁。
本執行個體採用以下配置,更多關於建立伸縮配置的說明,請參見建立伸縮配置(ECS執行個體)。
配置項
說明
樣本
伸縮配置名稱
輸入伸縮配置名稱,格式參考介面提示。
K8s-Scaling-Node-Config
付費模式
可以根據您的需求選擇。
隨用隨付
執行個體配置方式
可以根據您的需求選擇。
指定執行個體規格
選擇執行個體規格
可以根據您的需求選擇。
警告該功能支援的執行個體規格如下:
企業級x86運算規格類型系列。
企業級異構運算規格類型系列。
高效能運算執行個體規格類型系列群。
ECS Bare Metal Instance規格類型系列群。
暫不支援企業級ARM運算規格類型系列。關於執行個體規格類型系列的說明,請參見:執行個體規格類型系列。
ecs.g6a.large
選擇鏡像
根據您的需求選擇合適的鏡像。
Alibaba Cloud Linux
配置網路和安全性群組。
安全性群組:選擇安全性群組請確保該安全性群組可以串連到您Kubernetes叢集所在網路。
分配公網IPv4地址:如果您的Kubernetes叢集的API Server地址為公網IP,則需要勾選,為執行個體配置公網訪問能力。
警告如果您的Kubernetes叢集的API Server地址為公網IP,請確保您的Kubernetes叢集的API Server已放開6443連接埠。
配置
,請在執行個體自訂資料中填入以下指令碼,用於初始化Kubernetes Worker節點環境並將Worker節點加入Kubernetes叢集。重要將<<YOUR_MASTER_NODE_IP>>替換為您的Kubernetes的主節點IP。
#!/bin/bash #關閉防火牆 systemctl stop firewalld systemctl disable firewalld #關閉selinux sed -i 's/enforcing/disabled/' /etc/selinux/config # 永久 setenforce 0 # 臨時 #關閉swap swapoff -a # 臨時 sed -ri 's/.swap./#&/' /etc/fstab # 永久 #將橋接的IPv4流量傳遞到iptables的鏈 cat > /etc/sysctl.d/k8s.conf << EOF net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sysctl --system # 生效 #增加Kubernetes 源 cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF #通用安裝包 yum install vim bash-completion net-tools gcc -y #安裝Docker wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo yum -y install docker-ce systemctl enable docker && systemctl start docker cat > /etc/docker/daemon.json << EOF { "exec-opts": ["native.cgroupdriver=systemd"] } EOF systemctl restart docker # 安裝kubeadm、kubectl、kubelet yum install -y kubelet-1.23.0 kubeadm-1.23.0 kubectl-1.23.0 # 啟動kubelet服務 systemctl enable kubelet && systemctl start kubelet #如果kubelet起不來,通過這個命名排查:journalctl -xeu kubelet #Worker節點加入叢集 regionId=$(sed -n 's/.*"region-id": "\(.*\)".*/\1/p' /run/cloud-init/instance-data.json) instanceId=$(sed -n 's/.*"instance_id": "\(.*\)".*/\1/p' /run/cloud-init/instance-data.json) privateIpv4=$(sed -n 's/.*"private-ipv4": "\(.*\)".*/\1/p' /run/cloud-init/instance-data.json) cat > kubeadm-config.yaml << EOF apiVersion: kubeadm.k8s.io/v1beta2 kind: JoinConfiguration discovery: bootstrapToken: token: "your-bootstrap-token" apiServerEndpoint: "<<YOUR_MASTER_NODE_IP>>:6443" caCertHashes: - "sha256:your-discovery-token-ca-cert-hash" nodeRegistration: name: "$regionId-$privateIpv4" kubeletExtraArgs: provider-id: "$regionId.$instanceId" EOF kubeadm join --config=kubeadm-config.yaml
說明需要在擴容時為worker節點指定--provider-id,文中的指令碼已實現此功能。
點擊建立,並確保伸縮配置已生效。
(可選)驗證伸縮組擴容執行個體是否可以正常加入K8s叢集。
您可以通過手動修改伸縮組最小執行個體數為1來擴容一台ECS執行個體,並觀察擴容出來的ECS執行個體是否已經初始化並正常加入您的K8s叢集。
步驟三:在K8s叢集部署Cluster AutoScaler組件
將準備工作的RAM使用者的AccessKey ID和AccessKey Secret作Base64轉換。
echo $AccessKey-ID | tr -d '\n' | base64 echo $AccessKey-Secret | tr -d '\n' | base64 echo $RegionId | tr -d '\n' | base64
建立deploy-ca.yaml,內容如下,修改其中的相關欄位資訊後,部署到您K8s叢集的kube-system命名空間。
重要更新Secret的access-key-id、access-key-secret、region-id,以及在Deployment的容器啟動命令中,更新您的ESS伸縮組ID,具體操作如下:
將<<YOUR_ACCESS_KEY_ID>>替換為Base64轉換後的AccessKey ID。
將<<YOUR_ACCESS_KEY_SECRET>>替換為Base64轉換後的AccessKey Secret。
將<<YOUR_REGION_ID>>替換為Base64轉換後的RegionID,RegionID擷取請參見地區。
將<<YOUR_ESS_SCALING_GROUP_ID>>替換為您剛剛建立的伸縮組ID。
講<<KUBERNETES_SERVICE_HOST>>替換為您的K8s叢集ApiServer地址。
--- apiVersion: v1 kind: Secret metadata: name: cloud-config type: Opaque data: access-key-id: <<YOUR_ACCESS_KEY_ID>> access-key-secret: <<YOUR_ACCESS_KEY_SECRET>> region-id: <<YOUR_REGION_ID>> --- apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler name: cluster-autoscaler namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cluster-autoscaler labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler rules: - apiGroups: [""] resources: ["events","endpoints"] verbs: ["create", "patch"] - apiGroups: [""] resources: ["pods/eviction"] verbs: ["create"] - apiGroups: [""] resources: ["pods/status"] verbs: ["update"] - apiGroups: [""] resources: ["endpoints"] resourceNames: ["cluster-autoscaler"] verbs: ["get","update"] - apiGroups: [""] resources: ["nodes"] verbs: ["watch","list","get","update"] - apiGroups: [""] resources: ["namespaces","pods","services","replicationcontrollers","persistentvolumeclaims","persistentvolumes"] verbs: ["watch","list","get"] - apiGroups: ["extensions"] resources: ["replicasets","daemonsets"] verbs: ["watch","list","get"] - apiGroups: ["policy"] resources: ["poddisruptionbudgets"] verbs: ["watch","list"] - apiGroups: ["apps"] resources: ["statefulsets", "replicasets", "daemonsets"] verbs: ["watch","list","get"] - apiGroups: ["batch"] resources: ["jobs"] verbs: ["watch","list","get"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses", "csinodes", "csidrivers", "csistoragecapacities"] verbs: ["watch","list","get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: cluster-autoscaler namespace: kube-system labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["create","list","watch"] - apiGroups: [""] resources: ["configmaps"] resourceNames: ["cluster-autoscaler-status", "cluster-autoscaler-priority-expander"] verbs: ["delete","get","update","watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cluster-autoscaler labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-autoscaler subjects: - kind: ServiceAccount name: cluster-autoscaler namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: cluster-autoscaler namespace: kube-system labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: cluster-autoscaler subjects: - kind: ServiceAccount name: cluster-autoscaler namespace: kube-system --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: cluster-autoscaler name: cluster-autoscaler namespace: kube-system spec: replicas: 1 selector: matchLabels: app: cluster-autoscaler template: metadata: labels: app: cluster-autoscaler spec: dnsPolicy: "None" dnsConfig: nameservers: - 100.100.2.136 - 100.100.2.138 options: - name: timeout value: "1" - name: attempts value: "3" priorityClassName: system-cluster-critical serviceAccountName: cluster-autoscaler containers: - command: - ./cluster-autoscaler - '--v=2' - '--logtostderr=true' - '--stderrthreshold=info' - '--cloud-provider=alicloud' - '--expander=least-waste' - '--scan-interval=60s' - '--scale-down-enabled=true' - '--scale-down-delay-after-add=10m' - '--scale-down-delay-after-failure=1m' - '--scale-down-unready-time=2m' - '--ok-total-unready-count=1000' - '--max-empty-bulk-delete=50' - '--leader-elect=false' - '--max-node-provision-time=5m' - '--scale-up-from-zero=true' - '--daemonset-eviction-for-empty-nodes=false' - '--daemonset-eviction-for-occupied-nodes=false' - '--max-graceful-termination-sec=14400' - '--skip-nodes-with-system-pods=true' - '--skip-nodes-with-local-storage=false' - '--min-replica-count=0' - '--scale-down-unneeded-time=10m' - '--scale-down-utilization-threshold=0.3' - '--scale-down-gpu-utilization-threshold=0.3' - '--nodes=0:100:<<YOUR_ESS_SCALING_GROUP_ID>>' image: >- ess-cluster-autoscaler-registry.cn-hangzhou.cr.aliyuncs.com/ess-cluster-autoscaler/cluster-autoscaler:v1.7 imagePullPolicy: Always name: cluster-autoscaler resources: requests: cpu: 100m memory: 300Mi securityContext: allowPrivilegeEscalation: true capabilities: add: - SYS_ADMIN drop: - ALL env: - name: ACCESS_KEY_ID valueFrom: secretKeyRef: name: cloud-config key: access-key-id - name: ACCESS_KEY_SECRET valueFrom: secretKeyRef: name: cloud-config key: access-key-secret - name: REGION_ID valueFrom: secretKeyRef: name: cloud-config key: region-id - name: KUBERNETES_SERVICE_HOST value: "<<KUBERNETES_SERVICE_HOST>>" - name: KUBERNETES_SERVICE_PORT value: "6443" - name: KUBERNETES_SERVICE_PORT_HTTPS value: "6443"
說明通過參數
--scale-down-enabled
可以控制是否開啟縮容。如果開啟縮容,CA定期會檢測叢集狀態,判斷當前叢集狀態下,哪些節點資源使用率小於50%(通過參數--scale-down-utilization-threshold
控制)。CA預設不會終止kube-system命名空間的Pods,可以通過指定
--skip-nodes-with-system-pods=false
來覆蓋此預設設定。CA的縮容操作預設會等待10分鐘,可以通過指定
--scale-down-delay
來修改等待時間長度,例如--scale-down-delay=5m
。如果運行在多個伸縮組上,
--expander
參數支援3種選項:random
、most-pods
和least-waste
。random
:擴容時隨機播放一個伸縮組。most-pods
:在擁有最多Pod的伸縮組上擴容。least-waste
:在浪費最少CPU/記憶體的伸縮組上擴容。如果多個伸縮組判定一致,會回退到隨機模式。
通過以下命令部署CA到K8s叢集。
kubectl apply -f deploy-ca.yaml -n kube-system
功能驗證(可選)
當叢集中有因為資源不足而產生Pending狀態的Pod時,CA會驅動伸縮組擴容節點,當一個節點資源使用率持續低於預設的閾值時,CA會驅動伸縮組縮容節點。
部署一個簡單的nginx-demo.yaml,來驗證自動擴容功能,yaml檔案內容如下:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo spec: selector: matchLabels: app: nginx-demo replicas: 2 template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: ess-cluster-autoscaler-registry.cn-hangzhou.cr.aliyuncs.com/ess-cluster-autoscaler/nginx-demo:v1.0 ports: - containerPort: 80 name: http - containerPort: 443 name: https resources: requests: memory: 1Gi cpu: 1 limits: memory: 1Gi cpu: '1'
使用以下命令部署nginx-demo.yaml:
kubectl apply -f nginx-demo.yaml
根據叢集當前的Node資源空閑情況,通過增加replicas數量來產生因資源不足而Pending的Pod。使用以下命令增加replicas數量:
kubectl scale deployment nginx-demo --replicas=5
等待1分鐘左右,觀察伸縮組是否發生擴容。
伸縮組執行個體擴容完成後,等待3分鐘,觀察新節點是否加入叢集中,使用以下命令查看叢集所有Node,觀察是否有新的Node節點加入K8s叢集:
kubectl get nodes
驗證縮容時,您可以通過減少nginx-demo的副本數量來降低節點的使用率使其低於閾值,並觀察伸縮組是否發縮容活動來判斷縮容是否成功。