全部產品
Search
文件中心

Auto Scaling:自建Kubernetes叢集實現節點自動調整

更新時間:Jul 26, 2024

如果您是自建的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的許可權。

    1. 建立一個RAM使用者,並開啟OpenAPI存取控制。具體操作,請參見建立RAM使用者

    2. 為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"
          }
        ]
      }
    3. 建立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

  1. 從Github下載源碼。

    mkdir -p $GOPATH/src/github.com/kubernetes
    cd $GOPATH/src/github.com/kubernetes
    git clone https://github.com/kubernetes/autoscaler.git
    cd autoscaler
  2. 構建鏡像。

    # 編譯
    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

步驟二:建立並配置伸縮組

  1. 建立伸縮組。

    1. 登入阿里雲Auto Scaling控制台

    2. 在頂部功能表列選擇可用性區域,在左側點擊伸縮組管理,點擊建立伸縮組

    3. 通過表單建立頁簽下,完成伸縮組配置,然後點擊建立按鈕。本樣本採用以下配置,更多關於伸縮組的配置說明,請參見建立伸縮組

      配置項

      說明

      樣本

      伸縮組名稱

      輸入伸縮組名稱,格式參照介面提示。

      K8s-Node-Scaling-Group

      伸縮群組類型

      選擇ECS,表示伸縮組內的執行個體類型為ECS執行個體。

      ECS

      組內執行個體配置資訊來源

      先不指定自動建立執行個體的模板。伸縮組建立完成後,您需要繼續建立伸縮配置。

      從零開始建立

      組內最小執行個體數

      代表伸縮組最少有0台ECS執行個體。

      0

      組內最大執行個體數

      代表伸縮組最大有5台ECS執行個體。

      5

      專用網路

      該伸縮組下建立的ECS執行個體會在此專用網路下。

      vpc-test****-001

      選擇交換器

      您可以配置多個可用性區域的交換器以提高擴容成功率。

      vsw-test****

      重要

      在伸縮組建立完成後,請記錄您的可用性區域伸縮組ID以供後續步驟使用。

  2. 為伸縮組建立伸縮配置。

    1. 找到剛剛建立的伸縮組,點擊查看詳情進入伸縮組詳情頁。

    2. 執行個體配置來源頁簽下,點擊伸縮配置,點擊建立伸縮配置按鈕進入建立伸縮配置頁。

    3. 本執行個體採用以下配置,更多關於建立伸縮配置的說明,請參見建立伸縮配置(ECS執行個體)

      配置項

      說明

      樣本

      伸縮配置名稱

      輸入伸縮配置名稱,格式參考介面提示。

      K8s-Scaling-Node-Config

      付費模式

      可以根據您的需求選擇。

      隨用隨付

      執行個體配置方式

      可以根據您的需求選擇。

      指定執行個體規格

      選擇執行個體規格

      可以根據您的需求選擇。

      警告

      該功能支援的執行個體規格如下:

      • 企業級x86運算規格類型系列。

      • 企業級異構運算規格類型系列。

      • 高效能運算執行個體規格類型系列群。

      • ECS Bare Metal Instance規格類型系列群。

      暫不支援企業級ARM運算規格類型系列。關於執行個體規格類型系列的說明,請參見:執行個體規格類型系列

      ecs.g6a.large

      選擇鏡像

      根據您的需求選擇合適的鏡像。

      Alibaba Cloud Linux

    4. 配置網路和安全性群組

      • 安全性群組:選擇安全性群組請確保該安全性群組可以串連到您Kubernetes叢集所在網路。

      • 分配公網IPv4地址:如果您的Kubernetes叢集的API Server地址為公網IP,則需要勾選,為執行個體配置公網訪問能力。

        警告

        如果您的Kubernetes叢集的API Server地址為公網IP,請確保您的Kubernetes叢集的API Server已放開6443連接埠。

    5. 配置進階設定 > 執行個體自訂資料,請在執行個體自訂資料中填入以下指令碼,用於初始化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,文中的指令碼已實現此功能。

    6. 點擊建立,並確保伸縮配置已生效。

  3. (可選)驗證伸縮組擴容執行個體是否可以正常加入K8s叢集。

    您可以通過手動修改伸縮組最小執行個體數為1來擴容一台ECS執行個體,並觀察擴容出來的ECS執行個體是否已經初始化並正常加入您的K8s叢集。

步驟三:在K8s叢集部署Cluster AutoScaler組件

  1. 將準備工作的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
  2. 建立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種選項:randommost-podsleast-waste

      • random:擴容時隨機播放一個伸縮組。

      • most-pods:在擁有最多Pod的伸縮組上擴容。

      • least-waste:在浪費最少CPU/記憶體的伸縮組上擴容。如果多個伸縮組判定一致,會回退到隨機模式。

    通過以下命令部署CA到K8s叢集。

    kubectl apply -f deploy-ca.yaml -n kube-system

功能驗證(可選)

當叢集中有因為資源不足而產生Pending狀態的Pod時,CA會驅動伸縮組擴容節點,當一個節點資源使用率持續低於預設的閾值時,CA會驅動伸縮組縮容節點。

  1. 部署一個簡單的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
  2. 根據叢集當前的Node資源空閑情況,通過增加replicas數量來產生因資源不足而Pending的Pod。使用以下命令增加replicas數量:

    kubectl scale deployment nginx-demo --replicas=5
  3. 等待1分鐘左右,觀察伸縮組是否發生擴容。

  4. 伸縮組執行個體擴容完成後,等待3分鐘,觀察新節點是否加入叢集中,使用以下命令查看叢集所有Node,觀察是否有新的Node節點加入K8s叢集:

    kubectl get nodes
說明

驗證縮容時,您可以通過減少nginx-demo的副本數量來降低節點的使用率使其低於閾值,並觀察伸縮組是否發縮容活動來判斷縮容是否成功。