本文中含有需要您注意的重要提示信息,忽略該信息可能對您的業務造成影響,請務必仔細閱讀。
如果您是自建的Kubernetes集群,且期望根據實際工作負載動態調整工作節點數量,確保資源的有效利用并維持服務的穩定性,可通過Cluster Autoscaler和阿里云彈性伸縮實現。
本文采用手動部署Cluster Autocaler的方式實現K8s節點彈性伸縮。除了此方式之外,您可以將您的自建Kubernetes集群接入到ACK One注冊集群中,通過ACK One的自動彈性伸縮能力實現節點的自動伸縮。您可以參考以下步驟實現此功能。
更多ACK One的信息,請參見ACK One概述。
工作原理
Cluster AutoScaler(簡稱CA)是一個自動擴展和收縮Kubernetes集群節點的組件。CA會定期檢測是否有因資源不足而處于Pending狀態的Pod,如果有,會驅動伸縮組進行擴容,其工作原理如下圖所示:
CA在監測到某些節點資源使用率持續低于預設的閾值,且這些節點上的Pod能夠遷移到其他節點時,會先將Pod驅逐到其他節點,之后驅動伸縮組進行縮容,其工作原理如下圖所示:
更多Cluster AutoScaler的信息,請參見Cluster Autoscaling官方介紹。
準備工作
在操作前,請確保您已經完成以下工作。
已自建Kubernetes集群,且集群版本在v1.9.3及以上。
重要本文檔基于在阿里云ECS上搭建的K8s集群進行測試,如果涉及云下IDC機器、跨云供應商等混合云場景,建議參考VPN網關或者智能接入網關等產品解決網絡連通性問題。
創建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
步驟二:創建并配置伸縮組
創建伸縮組。
登錄阿里云彈性伸縮控制臺。
在頂部菜單欄選擇可用區,在左側點擊伸縮組管理,點擊創建伸縮組。
在通過表單創建頁簽下,完成伸縮組配置,然后點擊創建按鈕。本示例采用以下配置,更多關于伸縮組的配置說明,請參見創建伸縮組。
配置項
說明
示例
伸縮組名稱
輸入伸縮組名稱,格式參照界面提示。
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計算規格族群。
企業級異構計算規格族群。
高性能計算實例規格族群。
彈性裸金屬服務器規格族群。
暫不支持企業級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的副本數量來降低節點的使用率使其低于閾值,并觀察伸縮組是否發縮容活動來判斷縮容是否成功。