1. 前言
自动缩扩容是现代化的容器调度平台带给我们的最激动人心的一项能力。在上规模的业务系统中我们无时无刻不面临着这样的难题:用户的流量往往随着时间波动,甚至偶尔出现不可预测的峰值(毛刺流量),每当流量增加时都需要手工的对应用进行扩容,峰值流量消失后又需要将扩容的机器回收,运维起来费时费力。
幸运的是,k8s这样的容器调度平台正在逐渐帮助我们解决这样的问题,它带来的AutoScaler
功能目前已经支持在多个不同维度上的弹性缩扩容,可以根据应用的负载情况进行自适应。尽管在一些较为苛刻的场景下,由于容器启动速度等原因的限制,AutoScaler的效果还不尽人意,但相信在不久的将来,自动缩扩容方案将会完全成熟,届时我们将轻松获取具有强大弹性能力的服务集群。
现在来试着使用一下k8s的Scale相关功能吧。
2. 手动调整服务规模
我们可以使用kubectl
提供的命令来手动调整某个Deployment
的规模,也就是其包含的Pod
数量,这里拿上一节里创建的HelloWorld
服务来作为例子,当前的deployment
状态如下:
DISIRED
表示配置时声明的期望副本数CURRENT
表示当前正在运行的副本数UP-TO-DATE
表示符合预期状态的副本数(比如某个副本宕机了,这里就不会计算那个副本)AVAILABLE
表示目前可用的副本数
我们可以使用kubectl scale
命令来手动调整deployment
的规模,现在尝试把helloworld
服务的副本数量扩充到4个:
执行命令后,k8s很快就为我们创建了另外3个helloworld的副本,这时候整个服务就有多个实例在运行了,那么对应Service的负载均衡功能便会生效
,可以看到Service的状态里已经侦测到多个EndPoint
:
当我们连续调用service时,可以看到每次实际调用的pod都不同(这里对服务的源码做了一些修改,打印出了hostname):
同理,我们也可以用同样的方式把服务缩容,比如把副本集的数量下调到两个:
3. 自动缩扩容:极致弹性?
刚才的例子中,我们是通过命令行的方式来手动调整服务规模的,显然在面对线上真实流量时是不能这么做的,我们希望调度平台能够根据服务的负载来智能地调控规模,进行弹性缩扩容。这时候就轮到k8s中的AutoScaler
出场了。
到目前为止,k8s一共提供了3个不同维度的AutoScaler,如下图:
k8s把弹性伸缩分为两类:
- 资源维度:保障集群资源池大小满足整体规划,当集群内的资源不足以支撑产出新的pod时,就会触发边界进行扩容
- 应用维度:保障应用的负载处在预期的容量规划内
对应两种伸缩策略:
-
水平伸缩
- 集群维度:自动调整资源池规模(新增/删除Worker节点)
- Pod维度:自动调整Pod的副本集数量
-
垂直伸缩
- 集群维度:不支持
- Pod维度:自动调整应用的资源分配(增大/减少pod的cpu、内存占用)
其中最为成熟也是最为常用的伸缩策略就是HPA(水平Pod伸缩)
,所以下面以它为例来重点分析,官方文档在此:kubernetes.io/docs/tasks/…
3.1 基本流程
任何弹性系统的缩扩容都无外乎于以下几个步骤:
- 采集监控指标
- 聚合监控指标,判断是否需要执行缩扩容
- 执行缩扩容动作
下面就按照这个顺序来分析HPA的工作方式,这里先给出一个HPA大体的架构图:
3.2 监控指标
根据官方文档的描述,HPA是使用巡检(Control Loop)的机制
来采集Pod资源使用情况的,默认采集间隔为15s
,可以通过Controller Manager
(Master节点上的一个进程)的–horizontal-pod-autoscaler-sync-period
参数来手动控制。
目前HPA默认采集指标的实现是Heapster
,它主要采集CPU的使用率;beta版本也支持自定义的监控指标采集,但尚不稳定,不推荐使用。
因此可以简单认为,HPA就是通过CPU的使用率作为监控指标的。
3.3 聚合算法
采集到CPU指标后,k8s通过下面的公式来判断需要扩容多少个pod:
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]
复制代码ceil
表示向上取整,举个实际例子,假设某个服务运行了4个Pod,当前的CPU使用率为50%,预期的CPU使用率为25%,那么满足预期的实际Pod数量就是4 * (50% / 25%) = 8
个,即需要将Pod容量扩大一倍,增加4个Pod来满足需求。
当然上述的指标并不是绝对精确的,首先,k8s会尽可能的让指标往期望值靠近,而不是完全相等,其次HPA设置了一个容忍度(tolerance)
的概念,允许指标在一定范围内偏离期望值,默认是0.1
,这就意味着如果你设置调度策略为CPU预期使用率 = 50%,实际的调度策略会是小于45%或者大于55%进行缩扩容,HPA会尽力把指标控制在这个范围内(容忍度可以通过–horizontal-pod-autoscaler-tolerance
来调整)。
另外有两点需要说明的细节:
- k8s做出决策的间隔,它不会连续地执行扩缩容动作,而是存在一定的
cd
,目前扩容动作的cd为3分钟,缩容则为5分钟。 - k8s针对扩容做了一个最大限制,每次扩容的pod数量不会大于当前副本数量的2倍。
3.4 HPA实践
最后我们来尝试实际使用一下HPA,依然是使用kubectl
命令行的方式来创建:
kubectl autoscale deployment helloworld --cpu-percent=10 --min=1 --max=5
复制代码运行上面的命令后,可以在dashboard看到HPA已经被创建,策略是以CPU使用率10%为临界值,最少副本集数量为1,最大为5:
使用kubectl get hpa
也可以看到:
但是这里出现了异常,targets
的当前CPU使用率始终显示为unknown
,网上说是因为副本集里没有配置resources
导致的。我们需要在dashboard里找到对应的副本集,然后在spec.containers.resources
里声明requests
和limits
值:
其中requests
表示pod所需要分配的资源配额,limits
表示单个pod最大能够获取到的资源配额。
配置完之后还是没有出现指标,继续百度,发现没有安装heapster
支持,那就安装,具体方法可见此文,注意需要把国外gcr的镜像通过阿里云代理一把才能安装(如果你不太会搞,可以用我转好的,见github),influxDB
,grafana
和heapster
三个组件都装好后,记得再装一把heapster rbac
(在kubeconfig/rbac
目录下),最后重启一波minikube
,打开控制台,可以看到节点页面已经有了指标图像:
kubectl top
命令也能正常返回数值了:
然而,kubectl get hpa
显示cpu的当前指标还是unknown
,郁闷,进到deployment
里看一把日志:
可以看到HPA一直在报无法从metrics API
获取请求资源的错误,github走一波,找到一个类似的issue,原因是1.9
版本以后的k8s默认的监控指标来源从heapster
变成了metrics-server
(我用的1.0,坑),不过要再安装一套metrics-server实在太麻烦了,可以通过修改kube-controller-manager
的启动参数来禁用这个新特性。
minikube ssh
进入到vm内部,然后找到/etc/kubernetes/manifests/kube-controller-manager.yaml
这个文件,在启动参数里加上–horizontal-pod-autoscaler-use-rest-clients=false
,这时候对应的容器组会自动重启更新,再执行kubectl get hpa
,可以看到cpu的指标终于Ok了:
留下了感动的泪水,终于可以开始压测试水了,赶紧搞个buzybox
压起来:
kubectl run -i --tty load-generator --image=busybox /bin/sh
写个循环去访问我们的helloworld
服务(为了更快的出效果,我把服务的scale缩减到只有一个副本集,直接压这个pod的ip)
/ # while true; do wget -q -O- http://172.17.0.5:8080; done
大概2,3分钟后就能看到结果,此时查看hpa的状态,可以看到已经触发水平扩容,副本集的数量由1个追加到4个:
dashboard上也能看到效果:
关掉压测脚本,静置一会儿,再看HPA的状态:
自动缩容到了1个副本集,那么HPA的实践使用到这里就算是结束了。
4. 更新、发布、回滚
服务代码必然是会经常修改的,当代码发生变动时,就需要重新打包生成镜像,然后进行发布和部署,来看看在k8s中是如何对这些步骤进行处理的。
现在我们对代码做了一些改动,加上了hostname的输出,然后打包形成了一个2.0
版本的新镜像,下面需要把这个新版本的镜像部署到k8s的容器集群上,使用kubectl set image
在deployment
上指定新的镜像:
kubectl set image deployments/helloworld helloworld=registry.cn-qingdao.aliyuncs.com/gold-faas/gold-demo-service:2.0
执行该命令后,可以从kubectl
和dashboard
中看到,新的容器组正在被创建,同时旧的容器组也在被回收:
最终所有的旧版本pod都会被回收,只留下新发布版本的容器组副本集:
来测试一下服务是否更新:
假设我们的发布到线上的版本出现了致命的问题,需要紧急将服务回退到上一个版本,该怎么做呢?使用kubectl rollout undo
即可,来看看效果:
可以看到执行回滚命令后,新版本的pod开始被回收,同时上一个版本镜像所对应的pod被唤醒(这里速度非常快,我感觉k8s在发布完新服务后可能并不会立刻销毁历史版本的pod实例,而是会缓存一段时间,这样非常快速的回滚,只要把service的负载均衡指回历史版本的pod就可以了)
回滚完成后再度请求服务,可以发现服务已经变回了上一个版本的内容:
上面这些发布和回滚的特性在k8s中被称为滚动更新
,它允许我们按照一定的比例逐步(可以手工设置百分比)更新pod,从而实现平滑
的更新和回滚
,也可以借此实现类似蓝绿发布
和金丝雀发布
的功能。
5. 小结
通过实际操作体验了k8s的缩扩容以及发布机制,虽然遇到了几个坑,但整体上用起来还是非常丝滑的,到这里k8s的基本功能就探索的差不多了,从下一篇开始将会继续深入分析k8s的原理,敬请期待!