1、自动(弹性)扩缩容背景分析
背景:
弹性伸缩是根据用户的业务需求和策略,自动“调整”其“弹性资源”的管理服务。通过弹性伸缩功能,用户可设置定时、周期或监控策略,恰到好处地增加或减少“弹性资源”,并完成实例配置,保证业务平稳健康运行
在实际工作中,我们常常需要做一些扩容缩容操作,如:电商平台在618和双十一搞秒杀活动;由于资源紧张、工作负载降低等都需要对服务实例数进行扩缩容操作。
在k8s中扩缩容分为两种:
1、Node层面:
对K8s物理节点扩容和缩容,根据业务规模实现物理节点自动扩缩容
2、Pod层面:
我们一般会使用Deployment中的replicas参数,设置多个副本集来保证服务的高可用,但是这是一个固定的值,比如我们设置10个副本,就会启10个pod 同时running来提供服务。如果这个服务平时流量很少的时候,也是10个pod同时在running,而流量突然暴增时,又可能出现10个pod不够用的情况。针对这种情况怎么办?就需要扩容和缩容
2、k8s中自动伸缩的方案
2.1、HPA
Kubernetes HPA(Horizontal Pod Autoscaling):Pod水平自动伸缩
通过此功能,只需简单的配置,便可以利用监控指标(cpu使用率、磁盘、自定义的等)自动的扩容或缩容服务中Pod数量,当业务需求增加时,系统将无缝地自动增加适量pod容器,提高系统稳定性。
要想实现自动扩缩容,需要先考虑如下几点:
1.通过哪些指标决定扩缩容?
HPA v1版本可以根据CPU使用率来进行自动扩缩容:
但是并非所有的系统都可以仅依靠CPU或者Memory指标来扩容,对于大多数 Web 应用的后端来说,基于每秒的请求数量进行弹性伸缩来处理突发流量会更加的靠谱,所以对于一个自动扩缩容系统来说,我们不能局限于CPU、Memory基础监控数据,每秒请求数RPS等自定义指标也是十分重要。
HPA v2版本可以根据自定义的指标进行自动扩缩容
注意:hpa v1只能基于cpu做扩容所用
hpa v2可以基于内存和自定义的指标做扩容和缩容
2.如何采集资源指标?
如果我们的系统默认依赖Prometheus,自定义的Metrics指标则可以从各种数据源或者exporter中获取,基于拉模型的Prometheus会定期从数据源中拉取数据。 也可以基于metrics-server自动获取节点和pod的资源指标
3.如何实现自动扩缩容?
K8s的HPA controller已经实现了一套简单的自动扩缩容逻辑,默认情况下,每30s检测一次指标,只要检测到了配置HPA的目标值,则会计算出预期的工作负载的副本数,再进行扩缩容操作。同时,为了避免过于频繁的扩缩容,默认在5min内没有重新扩缩容的情况下,才会触发扩缩容。 HPA本身的算法相对比较保守,可能并不适用于很多场景。例如,一个快速的流量突发场景,如果正处在5min内的HPA稳定期,这个时候根据HPA的策略,会导致无法扩容。
2.2、KPA
KPA(Knative Pod Autoscaler):基于请求数对Pod自动扩缩容,KPA 的主要限制在于它不支持基于 CPU 的自动扩缩容。
1、根据并发请求数实现自动扩缩容
2、设置扩缩容边界实现自动扩缩容
扩缩容边界指应用程序提供服务的最小和最大Pod数量。通过设置应用程序提供服务的最小和最大Pod数量实现自动扩缩容。
相比HPA,KPA会考虑更多的场景,其中一个比较重要的是流量突发的时候。
2.3、VPA
kubernetes VPA(Vertical Pod Autoscaler),垂直 Pod 自动扩缩容,VPA会基于Pod的资源使用情况自动为集群设置资源占用的限制,从而让集群将Pod调度到有足够资源的最佳节点上。VPA也会保持最初容器定义中资源request和limit的占比。
它会根据容器资源使用率自动设置pod的CPU和内存的requests,从而允许在节点上进行适当的调度,以便为每个 Pod 提供适当的可用的节点。它既可以缩小过度请求资源的容器,也可以根据其使用情况随时提升资源不足的容量。
3、利用HPA基于CPU指标实现pod自动扩缩容
HPA全称是Horizontal Pod Autoscaler,翻译成中文是POD水平自动伸缩, HPA可以基于CPU利用率对deployment中的pod数量进行自动扩缩容(除了CPU也可以基于自定义的指标进行自动扩缩容)。pod自动缩放不适用于无法缩放的对象,比如DaemonSets。
HPA由Kubernetes API资源和控制器实现。控制器会周期性的获取平均CPU利用率,并与目标值相比较后调整deployment中的副本数量。
3.1 HPA工作原理
HPA是根据指标来进行自动伸缩的,目前HPA有两个版本–v1和v2beta
HPA的API有三个版本,通过kubectl api-versions | grep autoscal可看到
autoscaling/v1
autoscaling/v2beta1
autoscaling/v2beta2
autoscaling/v1只支持基于CPU指标的缩放;
autoscaling/v2beta1支持Resource Metrics(资源指标,如pod内存)和Custom Metrics(自定义指标)的缩放;
autoscaling/v2beta2支持Resource Metrics(资源指标,如pod的内存)和Custom Metrics(自定义指标)和ExternalMetrics(额外指标)的缩放,但是目前也仅仅是处于beta阶段
指标从哪里来?
K8S从1.8版本开始,CPU、内存等资源的metrics信息可以通过 Metrics API来获取,用户可以直接获取这些metrics信息(例如通过执行kubect top命令),HPA使用这些metics信息来实现动态伸缩。
Metrics server:
1、Metrics server是K8S集群资源使用情况的聚合器
2、从1.8版本开始,Metrics server可以通过yaml文件的方式进行部署
3、Metrics server收集所有node节点的metrics信息
HPA如何运作?
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使用率限额)跟目标使用率进行比较,并且在扩容时,还要遵循预先设定的副本数限制:MinReplicas <= Replicas <= MaxReplicas。
计算扩容后Pod的个数:sum(最近一分钟内某个Pod的CPU使用率的平均值)/CPU使用上限的整数+1
流程:
- 1、创建HPA资源,设定目标CPU使用率限额,以及最大、最小实例数
- 2、收集一组中(PodSelector)每个Pod最近一分钟内的CPU使用率,并计算平均值
- 3、读取HPA中设定的CPU使用限额
- 4、计算:平均值之和/限额,求出目标调整的实例个数
- 5、目标调整的实例数不能超过1中设定的最大、最小实例数,如果没有超过,则扩容;超过,则扩容至最大的实例个数
- 6、回到2,不断循环
自定义指标:
1.PA V2版本已经支持custom Metrics自定义指标。 Custom Metrics其实只是一个Kubernetes的接口,实际Metrics数据的提供,需要额外的扩展实现,可以自己写一个(参考:https://github.com/kubernetes-sigs/custom-Metrics-apiserver
)或者使用开源的Prometheus adapter。如果自己实现custom-Metrics,可以自定义各种Metrics指标,使用Prometheus adapter则可以使用Prometheus中现有的一些指标数据。
2. 如何采集Metrics数据 如果我们的系统默认依赖Prometheus,自定义的Metrics指标则可以从各种数据源或者exporter中获取,基于拉模型的Prometheus会定期从数据源中拉取数据。 假设我们优先采用RPS指标作为系统的默认Metrics数据,可以考虑从网关采集或者使用注入Envoy sidecar等方式获取工作负载的流量指标。
3. 如何自动扩缩容 K8s的HPA controller已经实现了一套简单的自动扩缩容逻辑,默认情况下,每30s检测一次指标,只要检测到了配置HPA的目标值,则会计算出预期的工作负载的副本数,再进行扩缩容操作。同时,为了避免过于频繁的扩缩容,默认在5min内没有重新扩缩容的情况下,才会触发扩缩容。 不过,HPA本身的算法相对比较保守,可能并不适用于很多场景。例如,一个快速的流量突发场景,如果正处在5min内的HPA稳定期,这个时候根据HPA的策略,会导致无法扩容。 另外,在一些Serverless场景下,有缩容到0然后冷启动的需求,但HPA默认不支持。
关于HPA支持缩容至0的讨论,可以参考issues(https://github.com/kubernetes/kubernetes/issues/69687
),该PR(https://github.com/kubernetes/kubernetes/pull/74526
)已经被merge,后面的版本可以通过featureGate设置开启,不过该功能是否应该由K8s本身去实现,社区仍然存在一些争议。
如果我们的系统要实现支持缩容至0和冷启动的功能,并在生产环境真正可用的话,则需要考虑更多的细节。例如HPA是定时拉取Metrics数据再决定是否扩容,但这个时间间隔即使改成1s的话,对于冷启动来说还是太长,需要类似推送的机制才能避免延迟。
总结一下,如果基于现有的HPA来实现一套Serverless自动扩缩容系统,并且默认使用流量作为扩缩容指标,大致需要:
- 考虑使用网关等流量入口来实现流量Metrics的指标检测,并暴露出接口,供Prometheus或者自研组件来采集。
- 使用Prometheus adapter或者自研Custom Metrics的K8s接口实现,使得HPA controller可以获取到具体的Metrics数据。
这样便可以直接使用HPA的功能,实现了一个最简单的自动扩缩容系统。但是,仍然存在一些问题,比较棘手的是:
- HPA无法缩容至0,也无法实现工作负载的冷启动。
- HPA的扩容算法不一定适用流量突发场景,存在一定的隐患。
http://dockerone.com/article/1943252 https://blog.51cto.com/u_15077560/2584796
3.2 安装数据采集组件metrics-server
metrics-server是一个集群范围内的资源数据集和工具,同样的,metrics-server也只是显示数据,并不提供数据存储服务,主要关注的是资源度量API的实现,比如CPU、文件描述符、内存、请求延时等指标,metric-server收集数据给k8s集群内使用,如kubectl,hpa,scheduler等
1.部署metrics-server组件
#通过离线方式获取镜像 需要的镜像是:k8s.gcr.io/metrics-server-amd64:v0.3.6和k8s.gcr.io/addon-resizer:1.8.4 镜像所在地址在课件,可自行下载,如果大家机器不能访问外部网络,可以把镜像上传到k8s的各个节点,按如下方法手动解压: docker load -i metrics-server-amd64-0-3-6.tar.gz docker load -i addon.tar.gz #部署metrics-server服务 #在/etc/kubernetes/manifests里面改一下apiserver的配置 注意:这个是k8s在1.7的新特性,如果是1.16版本的可以不用添加,1.17以后要添加。这个参数的作用是Aggregation允许在不修改Kubernetes核心代码的同时扩展Kubernetes API。 #二进制安装master节点需要部署kubelet服务 ,能够访问到metrics-service pod-ip。 [root@xianchaomaster1 ~]# vim /etc/kubernetes/manifests/kube-apiserver.yaml 增加如下内容: - --enable-aggregator-routing=true
重新更新apiserver配置: [root@xianchaomaster1 ~]# kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml [root@xianchaomaster1 ~]# kubectl apply -f metrics.yaml #验证metrics-server是否部署成功 [root@xianchaomaster1 ~]# kubectl get pods -n kube-system | grep metrics metrics-server-6595f875d6-ml5pc 2/2 Running 0 #测试kubectl top命令 [root@xianchaomaster1 ~]# kubectl top nodes NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% xianchaomaster1 673m 11% 2518Mi 44% xianchaonode1 286m 4% 1305Mi 22% [root@xianchaomaster1 ~]# kubectl top pods -n kube-system NAME CPU(cores) MEMORY(bytes) calico-kube-controllers-6949477b58-xbt82 3m 13Mi calico-node-4t7bs 118m 55Mi calico-node-hbx75 105m 88Mi
3.3 创建php-apache服务,利用HPA进行自动扩缩容。
#基于dockerfile构建一个PHP-apache项目 1)创建并运行一个php-apache服务 使用dockerfile构建一个新的镜像,在k8s的xianchaomaster1节点构建 [root@xianchaomaster1 ~]# mkdir php [root@xianchaomaster1 ~]# cd php/ [root@xianchaomaster1 ~]# docker load -i php.tar.gz [root@xianchaomaster1 php]# cat dockerfile FROM php:5-apache ADD index.php /var/www/html/index.php RUN chmod a+rx index.php [root@xianchaomaster1 php]# cat index.php <?php $x = 0.0001; for ($i = 0; $i <= 1000000;$i++) { $x += sqrt($x); } echo "OK!"; ?> #构建镜像 [root@xianchaomaster1 php]# docker build -t xianchao/hpa-example:v1 . #打包镜像 [root@xianchaomaster1 php]# docker save -o hpa-example.tar.gz xianchao/hpa-example:v1 [root@xianchaomaster1 php]# scp hpa-example.tar.gz xianchaonode1:/root/ #解压镜像 可以把镜像传到k8s的各个工作节点,通过docker load -i hpa-example.tar.gz进行解压: [root@xianchaonode1 ~]# docker load -i hpa-example.tar.gz
#通过deployment部署一个php-apache服务
[root@xianchaomaster1 ~]# cat php-apache.yaml 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: xianchao/hpa-example:v1 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
#更新资源清单文件 [root@xianchaomaster1 ~]# kubectl apply -f php-apache.yaml deployment.apps/php-apache created service/php-apache created #验证php是否部署成功 [root@xianchaomaster1 ~]# kubectl get pods NAME READY STATUS RESTARTS AGE php-apache-7d8fdb687c-bq8c7 1/1 Running 0 31s
3.4 创建HPA
php-apache服务正在运行,使用kubectl autoscale创建自动缩放器,实现对php-apache这个deployment创建的pod自动扩缩容,下面的命令将会创建一个HPA,HPA将会根据CPU,内存等资源指标增加或减少副本数,创建一个可以实现如下目的的hpa:
1)让副本数维持在1-10个之间(这里副本数指的是通过deployment部署的pod的副本数)
2)将所有Pod的平均CPU使用率维持在50%(通过kubectl top看到的每个pod如果是200毫核,这意味着平均CPU利用率为100毫核)
#给上面php-apache这个deployment创建HPA
[root@xianchaomaster1 ~]# kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
kubectl autoscale deployment php-apache (php-apache表示deployment的名字) --cpu-percent=50(表示cpu使用率不超过50%) --min=1(最少一个pod) --max=10(最多10个pod)
#验证HPA是否创建成功
[root@xianchaomaster1 ~]# kubectl get hpa
注:由于我们没有向服务器发送任何请求,因此当前CPU消耗为0%(TARGET列显示了由相应的deployment控制的所有Pod的平均值)。
3.5 压测php-apache服务,只是针对CPU做压测
#把busybox.tar.gz和nginx-1-9-1.tar.gz上传到xianchaonode1上,手动解压: docker load -i busybox.tar.gz docker load -i nginx-1-9-1.tar.gz 启动一个容器,并将无限查询循环发送到php-apache服务(复制k8s的master节点的终端,也就是打开一个新的终端窗口):
[root@xianchaomaster1]# kubectl run v1 -it --image=busybox --image-pull-policy=IfNotPresent /bin/sh 登录到容器之后,执行如下命令 /# while true; do wget -q -O- http://php-apache.default.svc.cluster.local; done 在一分钟左右的时间内,我们通过执行以下命令来看到更高的CPU负载 kubectl get hpa 显示如下: NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS php-apache Deployment/php-apache 231%/50% 1 10 4 上面可以看到,CPU消耗已经达到256%,每个pod的目标cpu使用率是50%,所以,php-apache这个deployment创建的pod副本数将调整为5个副本,为什么是5个副 本,因为256/50=5 kubectl get pod 显示如下: NAME READY STATUS RESTARTS AGE php-apache-5694767d56-b2kd7 1/1 Running 0 18s php-apache-5694767d56-f9vzm 1/1 Running 0 2s php-apache-5694767d56-hpgb5 1/1 Running 0 18s php-apache-5694767d56-mmr88 1/1 Running 0 4h13m php-apache-5694767d56-zljkd 1/1 Running 0 18s kubectl get deployment php-apache 显示如下: NAME READY UP-TO-DATE AVAILABLE AGE php-apache 5/5 5 5 2h1m
注意:可能需要几分钟来稳定副本数。由于不以任何方式控制负载量,因此最终副本数可能会与此示例不同。
停止对php-apache服务压测,HPA会自动对php-apache这个deployment创建的pod做缩容
停止向php-apache这个服务发送查询请求,在busybox镜像创建容器的终端中,通过<Ctrl>+ C把刚才while请求停止,然后,我们将验证结果状态(大约一分钟后):
kubectl get hpa
显示如下:
kubectl get deployment php-apache
显示如下:
php-apache 1/1 1 1 5s
通过上面可以看到,CPU利用率下降到0,因此HPA自动将副本数缩减到1。
注意:自动缩放副本可能需要几分钟。默认五分钟
4、利用HPA基于内存指标实现pod自动扩缩容
1、创建一个nginx的pod [root@xianchaomaster1 ~]# cat nginx.yaml 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: 0.01 memory: 25Mi limits: cpu: 0.05 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
[root@xianchaomaster1 ~]# kubectl apply -f nginx.yaml deployment.apps/nginx-hpa created service/nginx created 2、验证nginx是否运行 [root@xianchaomaster1 ~]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx-hpa-fb74696c-6m6st 1/1 Running 0 28s 注意: nginx的pod里需要有如下字段,否则hpa会采集不到内存指标 resources: requests: cpu: 0.01 memory: 25Mi limits: cpu: 0.05 memory: 60Mi
创建一个hpa
[root@xianchaomaster1 ~]# cat hpa-v1.yaml apiVersion: autoscaling/v2beta1 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 targetAverageUtilization: 60
更新资源清单文件
[root@xianchaomaster1 ~]# kubectl apply -f hpa-v1.yaml horizontalpodautoscaler.autoscaling/nginx-hpa created #查看创建的hpa [root@xianchaomaster1 ~]# kubectl get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS nginx-hpa Deployment/nginx-hpa 5%/60% 1 10 1
压测nginx的内存,hpa会对pod自动扩缩容
登录到上面通过pod创建的nginx,并生成一个文件,增加内存 [root@xianchaomaster1]#kubectl exec -it nginx-hpa-fb74696c-6m6st -- /bin/sh #压测 # dd if=/dev/zero of=/tmp/a #打开新的终端 [root@xianchaomaster1 ~]# kubectl get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE nginx-hpa Deployment/nginx-hpa 200%/60% 1 10 1 上面的targets列可看到200%/60%,200%表示当前内存使用率,60%表示所有pod的内存使用率维持在60%,现在内存使用率达到200%,所以pod增加到4个 [root@xianchaomaster1 ~]# kubectl get deployment 显示如下: NAME READY UP-TO-DATE AVAILABLE AGE nginx-hpa 4/4 4 4 25m [root@xianchaomaster1 ~]# kubectl get pods 显示如下: NAME READY STATUS RESTARTS AGE nginx-hpa-bb598885d-j4kcp 1/1 Running 0 25m nginx-hpa-bb598885d-rj5hk 1/1 Running 0 63s nginx-hpa-bb598885d-twv9c 1/1 Running 0 18s nginx-hpa-bb598885d-v9ft5 1/1 Running 0 63s 5、取消对nginx内存的压测,hpa会对pod自动缩容 [root@xianchaomaster1 ~]# kubectl exec -it nginx-hpa-fb74696c-6m6st -- /bin/sh 删除/tmp/a这个文件 # rm -rf /tmp/a [root@xianchaomaster1 ~]# kubectl get hpa 显示如下,可看到内存使用率已经降到5%: NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGEnginx-hpa Deployment/nginx-hpa 5%/60% 1 10 1 26m [root@xianchaomaster1 ~]# kubectl get deployment 显示如下,deployment的pod又恢复到1个了: NAME READY UP-TO-DATE AVAILABLE AGE nginx-hpa 1/1 1 1 38m
扩展:查看v2版本的hpa如何定义?
[root@xianchaomaster1 ~]# kubectl get hpa.v2beta2.autoscaling -o yaml > 1.yaml
VPA实现Pod自动扩缩容
Vertical Pod Autoscaler(VPA):垂直Pod自动扩缩容,用户无需为其pods中的容器设置最新的资源request。配置后,它将根据使用情况自动设置request,从而允许在节点上进行适当的调度,以便为每个pod提供适当的资源量。
安装vpa
[root@xianchaonode1 ~]# docker load -i vpa-admission-controller_0_8_0.tar.gz Loaded image: scofield/vpa-admission-controller:0.8.0 [root@xianchaonode1 ~]# docker load -i vpa-recommender_0_8_0.tar.gz Loaded image: scofield/vpa-recommender:0.8.0 [root@xianchaonode1 ~]# docker load -i vpa-updater_0_8_0.tar.gz Loaded image: scofield/vpa-updater:0.8.0 [root@xianchaomaster1~]#unzip autoscaler-vertical-pod-autoscaler-0.8.0.zip [root@xianchaomaster1 ~]#cd /root/autoscaler-vertical-pod-autoscaler-0.8.0/vertical-pod-autoscaler/deploy
修改admission-controller-deployment.yaml里的image:
image: scofield/vpa-admission-controller:0.8.0 imagePullPolicy: IfNotPresent 修改recommender-deployment.yaml里的image: image: scofield/vpa-recommender:0.8.0 imagePullPolicy: IfNotPresent 修改updater-deployment.yaml文件里的image: image: scofield/vpa-updater:0.8.0 imagePullPolicy: IfNotPresent [root@xianchaomaster1]# cd /root/autoscaler-vertical-pod-autoscaler-0.8.0/vertical-pod-autoscaler/hack [root@xianchaomaster1 hack]# ./vpa-up.sh
验证vpa是否部署成功
[root@xianchaomaster1]# kubectl get pods -n kube-system | grep vpa vpa-admission-controller-777694497b-6rrd4 1/1 Running 0 86s vpa-recommender-64f6765bd9-ckmvf 1/1 Running 0 86s vpa-updater-c5474f4c7-vq82f 1/1 Running 0 86s
测试VPA实现pod自动扩缩容
updateMode: "Off"
1、部署一个nginx服务,部署到namespace: vpa名称空间下: [root@xianchaomaster1 ~]# mkdir vpa [root@xianchaomaster1 ~]# cd vpa/ [root@xianchaomaster1 vpa]# kubectl create ns vpa [root@xianchaomaster1 vpa]# cat vpa-1.yaml 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 name: nginx imagePullPolicy: IfNotPresent resources: requests: cpu: 200m memory: 300Mi
[root@xianchaomaster1 vpa]# kubectl apply -f vpa-1.yaml [root@xianchaomaster1 vpa]# kubectl get pods -n vpa NAME READY STATUS RESTARTS AGE nginx-5f598bd784-fknwq 1/1 Running 0 6s nginx-5f598bd784-ljq75 1/1 Running 0 6s
在nginx管理的pod前端创建四层代理Service
[root@xianchaomaster1 vpa]# cat vpa-service-1.yaml apiVersion: v1 kind: Service metadata: name: nginx namespace: vpa spec: type: NodePort ports: - port: 80 targetPort: 80 selector: app: nginx [root@xianchaomaster1 vpa]# kubectl apply -f vpa-service-1.yaml [root@xianchaomaster1 vpa]# kubectl get svc -n vpa NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx NodePort 10.109.35.41 <none> 80:31957/TCP 3m28s [root@xianchaomaster1 vpa]# curl -I 192.168.40.180:31957 HTTP/1.1 200 OK #显示200说明代理没问题
创建VPA
先使用updateMode: "Off"模式,这种模式仅获取资源推荐值,但是不更新Pod
[root@xianchaomaster1 vpa]# cat vpa-nginx.yaml 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: cpu: "500m" memory: "100Mi" maxAllowed: cpu: "2000m" memory: "2600Mi"
[root@xianchaomaster1 vpa]# kubectl apply -f vpa-nginx.yaml [root@xianchaomaster1 vpa]# kubectl get vpa -n vpa NAME AGE nginx-vpa 6s 查看vpa详细信息: [root@xianchaomaster1 vpa]# kubectl describe vpa nginx-vpa -n vpa Name: nginx-vpa Namespace: vpa Labels: <none> Annotations: <none> API Version: autoscaling.k8s.io/v1 Kind: VerticalPodAutoscaler Metadata: Creation Timestamp: 2021-07-15T04:40:48Z Generation: 2 Managed Fields: API Version: autoscaling.k8s.io/v1beta2 Fields Type: FieldsV1 fieldsV1: f:metadata: f:annotations: .: f:kubectl.kubernetes.io/last-applied-configuration: f:spec: .: f:resourcePolicy: .: f:containerPolicies: f:targetRef: .: f:apiVersion: f:kind: f:name: f:updatePolicy: .: f:updateMode: Manager: kubectl-client-side-apply Operation: Update Time: 2021-07-15T04:40:48Z API Version: autoscaling.k8s.io/v1 Fields Type: FieldsV1 fieldsV1: f:status: .: f:conditions: f:recommendation: .: f:containerRecommendations: Manager: recommender Operation: Update Time: 2021-07-15T04:41:34Z Resource Version: 385606 Self Link: /apis/autoscaling.k8s.io/v1/namespaces/vpa/verticalpodautoscalers/nginx-vpa UID: fe00ca65-7dd3-495f-bcba-b1e602f01fb3 Spec: Resource Policy: Container Policies: Container Name: nginx Max Allowed: Cpu: 2000m Memory: 2600Mi Min Allowed: Cpu: 500m Memory: 100Mi Target Ref: API Version: apps/v1 Kind: Deployment Name: nginx Update Policy: Update Mode: Off Status: Conditions: Last Transition Time: 2021-07-15T04:41:34Z Status: True Type: RecommendationProvided Recommendation: Container Recommendations: Container Name: nginx Lower Bound: Cpu: 500m Memory: 262144k Target: Cpu: 500m Memory: 262144k Uncapped Target: Cpu: 25m Memory: 262144k Upper Bound: Cpu: 1349m Memory: 1410936619 Events: <none>
- Lower Bound: 下限值
- Target: 推荐值
- Upper Bound: 上限值
- Uncapped Target: 如果没有为VPA提供最小或最大边界,则表示目标利用率
上面结果表示,推荐的 Pod 的 CPU 请求为 500m,推荐的内存请求为 262144k 字节。
#压测nginx
[root@xianchaomaster1 vpa]# yum -y install httpd-tools ab [root@xianchaomaster1 vpa]# ab -c 100 -n 10000000 http://192.168.40.180:31957/
#过几分钟后观察VPA Recommendation变化
[root@xianchaomaster1 vpa]# kubectl describe vpa nginx-vpa -n vpa Recommendation: Container Recommendations: Container Name: nginx Lower Bound: Cpu: 500m Memory: 262144k Target: Cpu: 763m Memory: 262144k Uncapped Target: Cpu: 763m Memory: 262144k Upper Bound: Cpu: 2 Memory: 934920074 Events: <none>
从以上信息可以看出,VPA对Pod给出了推荐值:Cpu: 763m
,因为我们这里设置了updateMode: "Off",所以不会更新Pod
[root@xianchaomaster1 vpa]# kubectl get pods -n vpa NAME READY STATUS RESTARTS AGE nginx-5f598bd784-fknwq 1/1 Running 0 79m nginx-5f598bd784-ljq75 1/1 Running 0 79m
updateMode: "On"
1、现在我把updateMode: "Auto",看看VPA会有什么动作 这里我把resources改为:memory: 50Mi,cpu: 100m [root@xianchaomaster1 vpa]# cat vpa-nginx-1.yaml 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
[root@xianchaomaster1 vpa]# kubectl apply -f vpa-nginx-1.yaml [root@xianchaomaster1 vpa]# kubectl get pods -n vpa NAME READY STATUS RESTARTS AGE nginx-b75478445-jxp5d 1/1 Running 0 30s nginx-b75478445-psh2j 1/1 Running 0 30s
再次部署vpa
[root@xianchaomaster1 vpa]# cat vpa-nginx.yaml apiVersion: autoscaling.k8s.io/v1beta2 kind: VerticalPodAutoscaler metadata: name: nginx-vpa-2 namespace: vpa spec: targetRef: apiVersion: "apps/v1" kind: Deployment name: nginx updatePolicy: updateMode: "Auto" resourcePolicy: containerPolicies: - containerName: "nginx" minAllowed: cpu: "500m" memory: "100Mi" maxAllowed: cpu: "2000m" memory: "2600Mi"
[root@xianchaomaster1 vpa]# kubectl apply -f vpa-nginx.yaml verticalpodautoscaler.autoscaling.k8s.io/nginx-vpa-2 created [root@xianchaomaster1 vpa]# kubectl get vpa -n vpa NAME MODE CPU MEM PROVIDED AGE nginx-vpa Off 500m 262144k True 72m nginx-vpa-2 Auto 9s
再次压测
[root@xianchaomaster1 vpa]# ab -c 100 -n 10000000 http://192.168.40.180:31957/ 几分钟后,使用describe查看vpa详情,只关注Container Recommendations [root@xianchaomaster1 ~]# kubectl describe vpa nginx-vpa -n vpa |tail -n 20
Target变成了Cpu: 656m ,Memory: 262144k
来看下event事件
[root@xianchaomaster1 ~]# kubectl get event -n vpa
从输出信息可以看到,vpa执行了EvictedByVPA,自动停掉了nginx,然后使用 VPA推荐的资源启动了新的nginx
,我们查看下nginx的pod可以得到确认
[root@xianchaomaster1 ~]# kubectl get pods -n vpa NAME READY STATUS RESTARTS AGE nginx-b75478445-9sd46 1/1 Running 0 15h [root@xianchaomaster1 ~]# kubectl describe pods nginx-b75478445-9sd46 -n vpa Name: nginx-b75478445-9sd46 Namespace: vpa Priority: 0 Node: xianchaonode1/192.168.40.181 Start Time: Thu, 15 Jul 2021 14:13:34 +0800 Labels: app=nginx pod-template-hash=b75478445 Annotations: cni.projectcalico.org/podIP: 10.244.121.28/32 cni.projectcalico.org/podIPs: 10.244.121.28/32 Status: Running IP: 10.244.121.28 IPs: IP: 10.244.121.28 Controlled By: ReplicaSet/nginx-b75478445 Containers: nginx: Container ID: docker://f9d7ac11769acef2801c7d9a1090f4ece4ebe22e1eb4f128659617b1467a069d Image: nginx Image ID: docker-pullable://nginx@sha256:353c20f74d9b6aee359f30e8e4f69c3d7eaea2f610681c4a95849a2fd7c497f9 Port: <none> Host Port: <none> State: Running Started: Thu, 15 Jul 2021 14:13:35 +0800 Ready: True Restart Count: 0 Requests: cpu: 656m memory: 262144k 再回头看看部署文件
现在可以知道VPA做了哪些事了吧。当然,随着服务的负载的变化,VPA的推荐值也会不断变化。当目前运行的pod的资源达不到VPA的推荐值,就会执行pod驱逐,重新部署新的足够资源的服务。
VPA使用限制:
不能与HPA(Horizontal Pod Autoscaler )一起使用
Pod比如使用副本控制器,例如属于Deployment或者StatefulSet
VPA有啥好处:
Pod 资源用其所需,所以集群节点使用效率高。
Pod 会被安排到具有适当可用资源的节点上。
不必运行基准测试任务来确定 CPU 和内存请求的合适值。
VPA 可以随时调整 CPU 和内存请求,无需人为操作,因此可以减少维护时间。
VPA是Kubernetes比较新的功能,还没有在生产环境大规模实践过,不建议在线上环境使用自动更新模式,但是使用推荐模式你可以更好了解服务的资源使用情况。
kubernetes cluster-autoscaler
1、什么是 cluster-autoscaler
Cluster Autoscaler (CA)是一个独立程序,是用来弹性伸缩kubernetes集群的。它可以自动根据部署应用所请求的资源量来动态的伸缩集群。当集群容量不足时,它会自动去 Cloud Provider (支持 GCE、GKE 和 AWS)创建新的 Node,而在 Node 长时间资源利用率很低时自动将其删除以节省开支。
项目地址:https://github.com/kubernetes/autoscaler
2、Cluster Autoscaler 什么时候伸缩集群?
在以下情况下,集群自动扩容或者缩放:
扩容:由于资源不足,某些Pod无法在任何当前节点上进行调度
缩容: Node节点资源利用率较低时,且此node节点上存在的pod都能被重新调度到其他node节点上运行
3、什么时候集群节点不会被 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或更高版本中受支持)
扩展:什么是PodDisruptionBudget?
通过PodDisruptionBudget控制器可以设置应用POD集群处于运行状态最低个数,也可以设置应用POD集群处于运行状态的最低百分比,这样可以保证在主动销毁应用POD的时候,不会一次性销毁太多的应用POD,从而保证业务不中断
4、Horizontal Pod Autoscaler 如何与 Cluster Autoscaler 一起使用?
Horizontal Pod Autoscaler 会根据当前CPU负载更改部署或副本集的副本数。如果负载增加,则HPA将创建新的副本,集群中可能有足够的空间,也可能没有足够的空间。如果没有足够的资源,CA将尝试启动一些节点,以便HPA创建的Pod可以运行。如果负载减少,则HPA将停止某些副本。结果,某些节点可能变得利用率过低或完全为空,然后CA将终止这些不需要的节点。
扩展:如何防止节点被CA删除?
节点可以打上以下标签:
"cluster-autoscaler.kubernetes.io/scale-down-disabled": "true"
可以使用 kubectl 将其添加到节点(或从节点删除):
$ kubectl annotate node <nodename> cluster-autoscaler.kubernetes.io/scale-down-disabled=true
5、Cluster Autoscaler 支持那些云厂商?
GCE https://kubernetes.io/docs/concepts/cluster-administration/cluster-management/
Google云服务商
GKE https://cloud.google.com/container-engine/docs/cluster-autoscaler
Google kubernetes服务
AWS(亚马逊) https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md
Alibaba Cloud https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/alicloud/README.md
OpenStack Magnum https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/magnum/README.md
DigitalOcean https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/digitalocean/README.md
Cluster API https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md
Knative的自动扩缩容实现
Knative相关组件
这里我们只关心数据面的组件: Queue-proxy 针对每个业务容器Knative都会自动注入一个sidecar容器,本质上是一个基于Golang的反向代理服务,主要功能是检测流量,暴露出RPS和concurrency数据供Autoscaler组件采集。另外,如果用户配置containerConcurrency,还会限制单个容器的并发请求数,超过并发数的请求,会被缓存下来放入队列,这也是Queue-proxy名称的含义。
Autoscaler Autoscaler是自动扩缩容的核心控制组件,主要功能是采集Queue-proxy的Metrics数据,然后对比配置的数据,计算出预期的副本数,最后进行扩缩容操作。
Activator Activator的引入,最开始的目的是在冷启动的时候,由于服务副本数为0,需要有一个组件来保持住请求,通知Autoscaler扩容对应的服务,然后再将请求发送至运行后的服务。除此之外,还承担了当流量瞬间突增的时候,缓存请求,等扩容完成后再代理发送至后端服务的功能。
有哪些Metrics数据?
HPA的Metrics来源一般是Pod的CPU或者Memory,当然也支持自定义的Metrics数据,不过对于大部分在线业务来说,并非CPU密集型,而是IO密集型,这意味着单纯依赖CPU来自动扩缩容往往并不满足实际需求,可能工作负载接收的流量已经很大了,延迟已经很高了,但是HPA还没有帮我们扩容服务。 为了更好的满足这种情况,Knative KPA自动扩缩容则设计支持了请求并发(concurrency)和RPS(request-per-second)两种Metrics数据,相比CPU数据,这两者更贴近负载的load,更适合描述在线业务。数据源来自Activator组件和每个工作负载Pod,当然工作负载的Pod数据由Queue-proxy sidecar暴露出Metrics接口。 RPS比较好理解,在一个采集周期内(默认1s),来一次http request,计数加1即可,比如1s内检测到来了100个请求,那么RPS就是100。 Concurrency可以理解为一个采集周期内正在处理http request的数量,比如1s内有50个请求正在被处理则Concurrency为50。实际上Knative会记录request来的时刻和返回的时刻,如果1s内只有一个请求,并且处理了500ms即返回,则Concurrency为0.5。
Metrics是如何被采集的?
我们都知道Prometheus为pull拉模型,即服务本身提供Metrics接口,供Prometheus去拉取监控数据。 对于工作负载Pod而言,Queue-proxy sidecar承担了检测请求流量的职责,Autoscaler会从中拉取concurrency和RPS数据。 但是Activator则不同,Activator通过和Autoscaler组件建立websocket连接,使用push的方式将Metrics数据定时推到Autoscaler组件中。 如果Activator检测到流量是请求到一个副本数为0的负载,则会推送一个带有poke标记的Metrics数据到Autoscaler,Autoscaler会马上扩容负载,相比拉模式,这种推送push的方式会减少冷启动延迟。这也是Knative设计者在Activator这里采用push的方式的一个重要原因。 整体数据链路图如下所示:
KPA算法
虽然Knative本身也支持HPA,但这里我们还是只关心默认使用的KPA,我们可以简单的把KPA认为是区别于HPA的Knative自动扩缩容的方式。
Autoscaler的KPA算法本质上就是计算出多少个Pod可以满足现在的流量需求,计算逻辑便是在一个无限循环中,每隔2s触发一次,最核心的逻辑如下所示:
desiredPodCount = metricInSystem / targetPerPod
例如,检测到一共有100个并发请求,我们目标是每个Pod处理5个并发,那么期望的Pod数为100/5=20个。 当然实际的处理逻辑不会这么简单粗暴,还需要考虑不能超过配置的Pod最大副本数等等。 另外,KPA还设计了Stable和Panic模式,主要是为了区分和解决正常情况和流量突发的场景,避免流量突发时扩容不及时。 默认Stable为60s(可配置)的时间窗口,Panic时间窗口默认为10%即6s。 正常情况下,Autoscaler使用Stable时间窗口去聚合Metrics数据并计算期望的Pod个数。在一个60s的窗口周期内计算平均值,可以避免Pod过于频繁的扩缩容。 但是如果突发流量,6s内检测到的指标超过目前能处理的200%(可配置),则会进入Panic模式,在更短的时间内快速扩容Pod副本数。是否进入Panic模式可参考如下公式:
isPanic = metricInSystempanic / (readyPods * targetPerPod) >= panicThreshold
例如,检测到系统有100个并发请求(metricInSystempanic=100),目前有5个正在运行的Pod(readyPods=5)并且目标是每个Pod处理10个并发(targetPerPod=10),默认配置的进入Panic模式阈值为200%(panicThreshold=2),则根据如上公式:
100 / (5 * 10) >= 2
即会进入Panic模式。
突发流量
相比HPA,Knative会考虑更多的场景,其中一个比较重要的是流量突发的时候。 除了Autoscaler会进入Panic模式更快的去扩容外,上面也提到过Activator和Queue-proxy本身也带有缓存请求的功能,这个功能的目的也是为了在请求流量突发来不及处理时,进行缓存再转发。但是非冷启动情况下的请求缓存,会被视为一种迫不得已的兜底行为,同时会带来一定的请求时延。 Knative中会有很多的配置,可以调整用于突发流量的场景。下面为常见的配置项:
- container concurrency:容器并发度,如果设置为0,则视为不限制容器并发。
- target utilization:目标使用率,达到该使用率后会被视为达到容器的并发度,会触发扩容。
- target burst capacity (TBC) :可容忍的请求爆发容量,这个参数比较关键而且不太好理解。
举一个例子,假设我们的容器container concurrency被设置为50,目标使用率target utilization为80%,即每个容器目标是接收50*80%=40
的并发请求,TBC设置为100。当有180的并发请求进来后,我们很容易算出最终会被扩容为5个副本(这里会按照目标请求40来计算),但实际上5个副本的最大容量为5*50=250
个并发请求,则实际剩余可容忍的爆发为250-180=70
个并发。 当剩余可容忍的并发小于TBC时,Knative会让流量经过Activator,而不是直接发送到后端服务,那么在这个例子中,由于70<TBC=100,此时相当于Knative认为剩余的爆发请求容量不足以支撑目标的可容忍容量(TBC),所以流量全部都会走到Activator再进行负载均衡转发,因为Activator可以感知到哪些容器目前接收的请求已经达到极限,哪些容器却还能继续接收更多请求。 同时,如果在示例的场景中,再突然进来了100个请求,在扩容来不及的情况下,Activator会代理70个请求到后端服务,同时缓存30个请求,等后端服务有更多容量时再转发处理。 由此可见,Activator缓存请求并非只是在冷启动时,在突发流量场景下,Activator也会起到相同的作用,而冷启动其实只是后端服务副本为0的一种特殊场景而已。 从上面的分析可以看出,TBC参数十分重要,会影响到什么时候请求流量会经过Activator,什么时候则直接从网关到后端。但是,如果在非冷启动的时候,流量也经过Activator,增加了一层链路,对于延迟敏感的服务,有点得不偿失。 那TBC应该设置成多少呢?这对于很多人来说,也是一个头疼的问题。 这里给出一些参考:
- 对于非CPU密集型的服务,例如常规的web应用、静态资源服务等,建议直接将TBC设置为0,同时container concurrency也设置为0,即不限制容器的请求并发。 当TBC=0时,系统剩余的可爆发请求容量会永远大于TBC,也意味着除了冷启动的时候,请求流量永远不会走到Activator,同时我建议设置Knative Service的最小保留副本数为1,这样Activator组件其实都不会被用到,也减少了一层链路。 其实,我们使用Serverless的时候,很多的服务都是常规的在线业务,使用如上的配置,可以最小化请求延迟,增大RPS。
- 对于非常CPU密集型的服务、单线程等极端场景的服务,需要严格限制单个容器的请求并发,一般都需要设置container concurrency<5,此时建议设置TBC=-1,因为这种场景下往往扩容等不及请求流量的突增。 当TBC=-1,也意味着所有的请求,都会走到Activator,Activator会重新转发请求或者缓存超额的请求,这个时候的Activator组件在非冷启动情况下就有存在的价值。
- 对于除了上述两种情况,还需要限制一些并发请求的场景,此时一般container concurrency>5,建议设置TBC为一个预期的值,例如100,至于这个值具体设置多少,需要系统管理员根据实际的场景去评估。
思考
根据上述的分析,为什么Knative要抛弃HPA现有的实现,重新设计一个自动扩缩容系统呢? 大概主要有以下的原因:
- HPA本身扩缩容算法实现简单,不一定能满足所有需求,所以Knative提供了Autoscaler组件,提供了更加自定义的扩容算法。
- HPA默认不支持缩容至0和冷启动,这也是Activator的主要能力。
但是,Knative的现状就是合适和最理想的吗?这个值得我们更多的思考。 首先,KPA完全是定制化的,虽然HPA可以支持自定义指标的扩缩容,但HPA无法采用KPA的Metrics数据,二者比较割裂。如下图所示:
并且,KPA也只能支持PRS和Concurrency两种指标,无法接入用户自定义的其他指标。另外,虽然KPA可配置自动扩缩容算法的很多参数,但仍然缺乏可扩展性和可定制性。
不过,社区也意识到了这些问题,并在一步步的优化和改进。毕竟相比K8s,Knative还很年轻,相信随着时间推移,Knative会越来越完善和强大,成为Serverless领域的事实标准或许就在不远的前方。