自动扩缩容工具

HPA

Horizonal Pod Autoscaling,pod水平自动伸缩。一般是扩缩容deployment和statefulset。

根据什么指标

通过此功能,可以利用监控指标(cpu使用率、磁盘、自定义等)自动的扩容或缩容服务中Pod数量。

当业务需求增加时,系统将无缝地自动增加适量pod容器,提高系统稳定性。

指标怎么获取

基于metrics-server自动获取节点和pod的资源指标。

怎么实现扩缩容

  • K8s的HPA controller已经实现了一套简单的自动扩缩容逻辑,默认情况下,每30s检测一次指标,只要检测到了配置HPA的目标值,则会计算出预期的工作负载的副本数,再进行扩缩容操作。
  • 同时,为了避免过于频繁的扩缩容,默认在5min内没有重新扩缩容的情况下,才会触发扩缩容。
  • HPA本身的算法相对比较保守,可能并不适用于很多场景。例如,一个快速的流量突发场景,如果正处在5min内的HPA稳定期,这个时候根据HPA的策略,会导致无法扩容。

注意:针对原生HPA

  1. 必须安装metrics-server或者其他自定义metrics-server
  2. pod必须配置requests。因为HPA就是根据requests来计算使用率的

工作原理

  • HPA的实现是一个控制循环,由controller manager的--horizontal-pod-autoscaler-sync-period参数指定周期(默认值为15秒)。每个周期内,controller manager根据每个HorizontalPodAutoscaler定义中指定的指标查询资源利用率。controller manager可以从resource metrics API(pod 资源指标)和custom metrics API(自定义指标)获取指标。
  • 然后,通过现有pods的CPU使用率的平均值(计算方式是最近的pod使用量(最近一分钟的平均值,从metrics-server中获得)除以设定的每个Pod的CPU使用率限额)跟目标使用率进行比较。
  • 计算扩容后Pod的个数:sum(最近一分钟内某个Pod的CPU使用率的平均值)/CPU使用上限的整数+1。并且在扩容时,还要遵循预先设定的副本数限制:MinReplicas <= Replicas <= MaxReplicas。

VPA

  • Vertical Pod Autoscaler,垂直 Pod 自动扩缩容,VPA会基于Pod的资源使用情况自动为设置资源占用的request,从而让集群将Pod调度到有足够资源的最佳节点上。
  • VPA也会保持最初容器定义中资源request和limit的占比。它会根据容器资源使用率自动设置pod的CPU和内存的requests的值,从而允许在节点上进行适当的调度,以便为每个 Pod 提供适当的可用的节点。
  • 既可以缩小过度请求资源的容器,也可以根据其使用情况随时提升资源不足的容量。
  • 不能与HPA一起使用。

VPA优势

Pod 资源用其所需,所以集群节点使用效率高。Pod 会被安排到具有适当可用资源的节点上。

不必运行基准测试任务来确定 CPU 和内存请求的合适值。VPA 可以随时调整 CPU 和内存请求,无需人为操作,因此可以减少维护时间。

VPA是Kubernetes比较新的功能,还没有在生产环境大规模实践过,不建议在线上环境使用自动更新模式,但是使用推荐模式你可以更好了解服务的资源使用情况。

KPA

  • Knative Pod Autoscaler:基于请求数对Pod自动扩缩容,KPA 的主要限制在于它不支持基于 CPU 的自动扩缩容。
  • 支持根据并发请求数实现自动扩缩容;支持设置扩缩容上下限。可以和HPA混合使用。
  • Github:Knative Install

  • 安装参考:Knative Serving Installation

cluster-autoscaler

  • 什么是cluster autoscaler

  • Cluster Autoscaler (CA)是一个独立程序,是用来弹性伸缩kubernetes集群的。它可以自动根据部署应用所请求的资源量来动态的伸缩集群。当集群容量不足时,它会自动去 Cloud Provider (支持 GCE、GKE 和 AWS)创建新的 Node,而在 Node 长时间资源利用率很低时自动将其删除以节省开支。

  • 项目地址:kubernetes/autoscaler

  • 什么时候集群节点不会被 CA 删除?

1)节点上有pod被 PodDisruptionBudget 控制器限制。
2)节点上有命名空间是 kube-system 的pods。
3)节点上的pod不是被控制器创建,例如不是被deployment, replica set, job, stateful set创建。
4)节点上有pod使用了本地存储
5)节点上pod驱逐后无处可去,即没有其他node能调度这个pod
6)节点有注解:"cluster-autoscaler.kubernetes.io/scale-down-disabled": "true"(在CA 1.0.3或更高版本中受支持)

kubectl annotate node cluster-autoscaler.kubernetes.io/scale-down-disabled=true

HPA自动扩缩容实战

HPA基于CPU实现pod扩缩容

创建deployment

tee deploy-nginx.yaml <<'EOF' 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-hpa
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        resources:
          requests:
            cpu: 10m
            memory: 25Mi
          limits:
            cpu: 50m
            memory: 60Mi
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    app: nginx
  type: NodePort
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
EOF

注意:pod定义中必须要有对应指标的requests,HPA才能检测得到指标从而起作用

创建HPA

kubectl autoscale deployment nginx-hpa --cpu-percent=10 --min=1 --max=10

cpu-percent=10怎么计算出来的:kubectl top po能获取pod的当前cpu使用率,除以cpu的request,就得到平均使用率。

平均使用率超过10%,就直接扩容。

压测

kubectl get svc # 获取nginx svc IP
while true; do wget -q -O- http://10.109.68.255 > /dev/null; done

HPA基于内存实现pod扩缩容

注意:生产环境中尽量避免使用基于内存的HPA。

因为内存是不可压缩资源;不像cpu一样一会高一会低,一般内存使用量升高是因为程序存在bug,升上去是降不下来的。

yaml文件创建HPA

tee hpa-mem.yaml <<'EOF'
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa
spec:
    maxReplicas: 10
    minReplicas: 1
    scaleTargetRef:
      apiVersion: apps/v1
      kind: Deployment
      name: nginx-hpa
    metrics:
    - type: Resource
      resource:
        name: memory
        target:
          averageUtilization: 60
          type: Utilization
EOF
kubectl apply -f hpa-mem.yaml

压测

kubectl exec -it nginx-hpa-7f5d6fbfd9-s8wbh  -- /bin/sh
#压测
dd if=/dev/zero of=/tmp/a

查看某个版本的hpa的字段定义

kubectl get hpa.v2beta2.autoscaling -o yaml > 1.yaml

利用HPA扩缩容PHP服务

dockerfile构建PHP服务

  • 准备镜像
#使用dockerfile构建一个新的镜像,在k8s的xianchaomaster1节点构建
docker load -i php.tar.gz
mkdir php && cd php/

tee dockerfile <<'EOF' 
FROM php:5-apache
ADD index.php /var/www/html/index.php
RUN chmod a+rx index.php
EOF

tee index.php <<'EOF' 
<?php
$x = 0.0001;
for ($i = 0; $i <= 1000000;$i++) {
$x += sqrt($x);
  }
 echo "OK!";
?>
EOF
#构建镜像
docker build -t k8s.gcr.io/hpa-example:v1 .
#打包镜像
docker save -o hpa-example.tar.gz k8s.gcr.io/hpa-example:v1
  • 部署镜像
#拷贝到工作节点并解压
scp hpa-example.tar.gz node1:/root/
docker load -i hpa-example.tar.gz
tee php-apache.yaml <<'EOF' 
apiVersion: apps/v1
kind: Deployment
metadata: 
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  replicas: 1
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: k8s.gcr.io/hpa-example:v1
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
    name: php-apache
    labels:
      run: php-apache
spec:
  ports:
  - port: 80
  selector:
   run: php-apache
EOF
kubectl apply -f php-apache.yaml

注意:pod定义中必须要有CPU的requests、limits,HPA才能检测得到指标从而起作用

创建HPA

#1)让副本数维持在1-10个之间(这里副本数指的是通过deployment部署的pod的副本数)
#2)将所有Pod的平均CPU使用率维持在50%(每个pod如果requests了200毫核,那么目标就是让平均CPU利用率为100毫核)
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
k get hpa
#由于我们没有向服务器发送任何请求,因此当前CPU消耗为0%(TARGET列显示了由相应的deployment控制的所有Pod的平均值)。

压测

#busybox.tar.gz和nginx-1-9-1.tar.gz上传
docker load -i busybox.tar.gz 
docker load -i nginx-1-9-1.tar.gz
#启动一个容器,并将无限查询循环发送到php-apache服务
kubectl run stress-test -it --image=busybox --image-pull-policy=IfNotPresent /bin/sh
while true; do wget -q -O- http://php-apache.default.svc.cluster.local; done

VPA实现pod垂直扩缩容

  • Vertical Pod Autoscaler(VPA):垂直Pod自动扩缩容,用户无需为其pods中的容器设置最新的资源request。配置后,它将根据使用情况自动设置request,从而允许在节点上进行适当的调度,以便为每个pod提供适当的资源量。

工作原理:

img

安装VPA

docker load -i vpa-admission_0.10.0.tar.gz && docker load -i vpa-recommender_0.10.0.tar.gz && docker load -i vpa-updater_0.10.0.tar.gz #可选,这些镜像是后面的安装yaml文件里面定义的镜像
#master节点上
tar zxvf autoscaler-master.tar.gz
#升级openssl:
yum install gcc gcc-c++ -y
wget https://www.openssl.org/source/openssl-1.1.1k.tar.gz --no-check-certificate && tar zxf openssl-1.1.1k.tar.gz && cd openssl-1.1.1k
./config
make && make install
mv /usr/local/bin/openssl /usr/local/bin/openssl.bak && mv apps/openssl /usr/local/bin
rm -rf /usr/lib64/libssl.so.1.1 && ln -s /usr/local/lib64/libssl.so.1.1 /usr/lib64/libssl.so.1.1
rm -rf /usr/lib64/libcrypto.so.1.1 && ln -s /usr/local/lib64/libcrypto.so.1.1 /usr/lib64/libcrypto.so.1.1
cd /root/autoscaler-master/vertical-pod-autoscaler/hack
./vpa-up.sh
kubectl get pods -n kube-system | grep vpa

部署nginx

mkdir vpa && cd vpa/
kubectl create ns vpa
tee deploy-nginx.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: vpa
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        imagePullPolicy: IfNotPresent
        resources:
          requests:
            cpu: 200m
            memory: 300Mi
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: vpa
spec:
  selector:
    app: nginx
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
EOF
kubectl apply -f deploy-nginx.yaml

创建VPA

  • updateMode:”Off”模式,这种模式仅获取资源推荐值,但是不更新Pod
tee vpa-nginx.yaml <<'EOF' 
apiVersion: autoscaling.k8s.io/v1beta2
kind: VerticalPodAutoscaler
metadata:
  name: nginx-vpa
  namespace: vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: nginx
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
    - containerName: "nginx"
      minAllowed: #Specifies the minimal amount of resources that will be recommended for the container. The default is no minimum.
        cpu: "500m"
        memory: "100Mi"
      maxAllowed:
        cpu: "2000m"
        memory: "2600Mi"
EOF
kubectl apply -f vpa-nginx.yaml
  • 查看VPA
kubectl describe vpa nginx-vpa -n vpa

Status:
  Conditions:
    Last Transition Time:  2024-05-19T09:13:42Z
    Status:                True
    Type:                  RecommendationProvided
  Recommendation:
    Container Recommendations:
      Container Name:  nginx
      #VPA去调整requests字段值的上下限和推荐值
      Lower Bound: #下限值
        Cpu:     500m
        Memory:  262144k
      Target: #推荐值
        Cpu:     500m
        Memory:  262144k #256Mi
      Uncapped Target:
        Cpu:     25m
        Memory:  262144k
      Upper Bound: #上限值
        Cpu:     576m
        Memory:  602928571
Events:          <none>
  • describe的结果表示:推荐的 Pod 的 CPU 请求为 500m,推荐的内存请求为 262144k 字节(256Mi)。

压测nginx

#使用ab作为压测工具
yum -y install httpd-tools
ab -c 100 -n 10000000 http://192.168.40.180:30327/
#VPA对于requests的推荐值的变化
kubectl describe vpa nginx-vpa -n vpa
  • 修改updateMode:”Auto”
tee vpa-nginx.yaml <<'EOF' 
apiVersion: autoscaling.k8s.io/v1beta2
kind: VerticalPodAutoscaler
metadata:
  name: nginx-vpa
  namespace: vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: nginx
  updatePolicy:
    updateMode: "Auto"
  resourcePolicy:
    containerPolicies:
    - containerName: "nginx"
      minAllowed: #Specifies the minimal amount of resources that will be recommended for the container. The default is no minimum.
        cpu: "500m"
        memory: "100Mi"
      maxAllowed:
        cpu: "2000m"
        memory: "2600Mi"
EOF
kubectl apply -f vpa-nginx.yaml
tee deploy-nginx.yaml <<'EOF' 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
  namespace: vpa
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        imagePullPolicy: IfNotPresent
        name: nginx
        resources:
          requests:
            cpu: 100m
            memory: 50Mi
EOF
kubectl apply -f deploy-nginx.yaml
ab -c 100 -n 10000000 http://192.168.40.180:30327/
#VPA对于requests的推荐值的变化
kubectl describe vpa nginx-vpa -n vpa
  • 查看event的扩缩容事件
kubectl get event -n vpa
#vpa执行了EvictedByVPA,自动停掉了nginx,然后使用 VPA推荐的资源启动了新的nginx
#随着服务的负载的变化,VPA的推荐值也会不断变化。当目前运行的pod的资源达不到VPA的推荐值,就会执行pod驱逐,重新部署新的足够资源的服务。

metrics-server

Metrics-server

  • metrics-server是一个集群范围内的资源数据集和工具,同样的,metrics-server也只是显示数据,并不提供数据存储服务,主要关注的是资源度量API的实现,比如CPU、文件描述符、内存、请求延时等指标,metric-server收集数据给k8s集群内使用,如kubectl,hpa,scheduler等。

  • Github地址:

kubernetes-sigs/metrics-server

metrics-server v0.6.1

部署

可以通过top node验证是否安装了metrics-server:kubectl top node

# 可选:上传镜像registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server:v0.6.1并在所有节点解压
docker load -i registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server:v0.6.1
# master节点上修改apiserver参数,允许在不修改Kubernetes核心代码的同时扩展Kubernetes API。
vim /etc/kubernetes/manifests/kube-apiserver.yaml
#增加如下内容:
- --enable-aggregator-routing=true
# 使配置生效,在所有节点上
systemctl restart kubelet
# 这个操作重启apiserver,会影响业务,最好在装集群的时候就配置好
  • --enable-aggregator-routing=true 是 Kubernetes API 服务器的一个配置参数。当设置为 true 时,它告诉 API 服务器通过 Kubernetes 服务 IP 路由到扩展 API 服务器,而不是直接路由到扩展 API 服务器的 Pod IP。这是在 Kubernetes 集群中使用 API 聚合层(API Aggregation Layer)时的一个重要配置。

  • API 聚合层允许 Kubernetes 提供更丰富和更复杂的 API,而无需将所有这些功能添加到 Kubernetes 核心 API 中。例如,服务目录 API、自定义资源定义 API 和其他扩展 API 都可以通过 API 聚合层提供。

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    k8s-app: metrics-server
    rbac.authorization.k8s.io/aggregate-to-admin: "true"
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
    rbac.authorization.k8s.io/aggregate-to-view: "true"
  name: system:aggregated-metrics-reader
rules:
- apiGroups:
  - metrics.k8s.io
  resources:
  - pods
  - nodes
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    k8s-app: metrics-server
  name: system:metrics-server
rules:
- apiGroups:
  - ""
  resources:
  - nodes/metrics
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - pods
  - nodes
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server-auth-reader
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server:system:auth-delegator
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    k8s-app: metrics-server
  name: system:metrics-server
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:metrics-server
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
---
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server
  namespace: kube-system
spec:
  ports:
  - name: https
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    k8s-app: metrics-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: metrics-server
  name: metrics-server
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  strategy:
    rollingUpdate:
      maxUnavailable: 0
  template:
    metadata:
      labels:
        k8s-app: metrics-server
    spec:
      containers:
      - args:
        - /metrics-server
        - --cert-dir=/tmp
        - --secure-port=4443
        - --kubelet-insecure-tls
        - --kubelet-preferred-address-types=InternalIP
        - --kubelet-use-node-status-port
        - --metric-resolution=15s
        image: registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server:v0.6.1
        imagePullPolicy: IfNotPresent
        livenessProbe:
          httpGet:
            path: /livez
            port: https
            scheme: HTTPS
          periodSeconds: 10
        name: metrics-server
        ports:
        - containerPort: 4443
          name: https
          protocol: TCP
        readinessProbe:
          httpGet:
            path: /readyz
            port: https
            scheme: HTTPS
          initialDelaySeconds: 20
          periodSeconds: 10
        resources:
          requests:
            cpu: 100m
            memory: 10Mi
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          runAsUser: 1000
        volumeMounts:
        - mountPath: /tmp
          name: tmp-dir
      nodeSelector:
        kubernetes.io/os: linux
      priorityClassName: system-cluster-critical
      serviceAccountName: metrics-server
      volumes:
      - emptyDir: {} 
        name: tmp-dir
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  labels:
    k8s-app: metrics-server
  name: v1beta1.metrics.k8s.io
spec:
  group: metrics.k8s.io
  groupPriorityMinimum: 100
  insecureSkipTLSVerify: true
  service:
    name: metrics-server
    namespace: kube-system
  version: v1beta1
  versionPriority: 100
#部署metrics-server pod
kubectl apply -f components.yaml
#测试
kubectl top nodes
kubectl top pods -n kube-system