本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。
如果您是自建的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的副本数量来降低节点的使用率使其低于阈值,并观察伸缩组是否发缩容活动来判断缩容是否成功。