当前Kubetnetes已经成为容器编排领域事实的行业标准,越来越多的公司选择使用Kubernetes来搭建其容器云平台。本次分享主要介绍滴滴弹性云在围绕Kubernetes打造企业级私有云过程中的一些实践经验和教训,为同样正走在企业云化转型道路上的朋友们提供一些启发和帮助。
当前滴滴国际化业务全部运行在弹性云平台上,国内的顺风车,金融,汽车后市场、企业安全、客服以及专车快车等业务的全部或部分模块也运行在我们的平台上。平台规模大约1K 宿主,2W 容器实例。
今天的分享我主要聚焦一些我们平台相对有特色的地方,希望能给各位带来一些启发。
网络模型
首先介绍一下平台的网络模型,我们最开始使用的是Flannel,这种网络模型的问题大家应该都有体会,这里不多说。
之后我们过度到了使用ONOS为主体集中式SDN网络,这种网络模型的功能可以说非常丰富,容器可以在overlay网络上随意漂移,且在容器名不变的情况下保证IP不变。于此同时容器IP相互独立,和物理网络双向互通,所以设置各类白名单也和物理机完全一致,不占用物理网络IP资源。利用Kubernetes的CNI可以很方便的为容器配置网络,用户使用起来和物理机无异。
但是此种模式的最大问题是:集中式的网络控制器在出现故障或是任何一点性能问题时,都会对集群的稳定性造成显著的影响。在经历了一些稳定性问题后,我们将网络模型演进到了当前的使用物理网络的模式。在此种场景下,容器只能在同一个接入交换机下的宿主上进行漂移,所以我们在部署新集群的时候每个交换机下的宿主数量尽量趋同。
同时我们预先为每个APP提供一个IP池,用户在创建容器之前可以先申请一个IP池(默认30个IP,未来创建的容器的IP不会超出这个IP池的范围),使用平台提供的白名单功能,预先将IP池内的IP自动化的加入到MySQL或Codis这样的公共服务白名单中,最后再启动容器。这样就实现了容器启动后不会因为连不上数据库而报错,或者扩容后新容器异常。当然,IP池是能够随着业务的变化而随时扩缩容的。调度系统在预分配IP池的时候也会根据各个tor IP的实际使用量均匀分配。
值得一提的是我们利用Kubernetes提供的nodeAffinity和podAntiAffinity实现同一服务、同一产品线、同一服务等级等多种维度下的容器实例平均分配到不同的接入交换机和宿主下的目的。
服务发现
接下来给大家介绍一下弹性云在服务发现方面做的一些改进。首先我们在很早的时候就抛弃了kube-proxy。在我们运维云平台的过程中我们发现当一个集群有几百、几千个service之后其iptables的规模要再乘以10(通常是service背后Endpoint的数量),如此多的iptables条目让我们比较难以维护和故障定位。
所以除了老集群,我们的新集群全部使用公司自研的服务发现:DSI & DGW。DSI就是基于NGINX做的公司内7层代理服务,其核心是借助服务树获取到节点对应的容器或物理机信息,将他们动态的、自动的挂载到NGINX的upstream中,实现高可用及负载均衡。容器变化时DSI可以通过watch服务树接口实时感知,实时调整,用户无需操心。
DGW则是四层的负载均衡,他同样实现了容器作为RealServer动态实时变更的功能。
业务上云稳定性
下面再说一下针对业务上云稳定性做的一些改进,我们认为云上稳定性主要分为大概三个方面:
- 容器相对于此前物理机模式下发布更新、线上扩容等流程异构导致的稳定性问题;
- 容器自身特性导致的稳定性隐患;
- Kubernetes本身特性带来的稳定性隐患。
首先对于业务发布流程,我们首先要做到的是尽量避免因发布导致的流量丢失或服务不可用问题,为此我们首先默认不使用Liveness探针(用户需要可以自己配),仅保留一个Readiness探针保证容器服务在创建时能正常启动。在Readiness探针探测通过后,我们会根据service的Endpoint将容器变化推送到周边系统,如之前提到的DSI、DGW等,后续的探活就由这些前端设备完成。
此外我们为每个服务强制的分为n个分组(通常是4个),每次上线过程中用户都必须逐一分组更新服务,组与组之间强制时间隔离用于检查,前一组不发布更新完成后一组无法操作。使用这种方式一是规避了Kubernetes来做服务探活的“不确定性”,二是同物理机发布更新模式趋同,用户认同度更高,从流程上规避可能的故障风险。
其次,针对容器自身特性的稳定性隐患可以归纳为容器隔离性不强的隐患。大家都知道直到1.10版本的Kubernetes才正式引入了cgroup的cpu_set功能,此前只有cpu_share,内存只能限制大小,磁盘和网络限制更是没有。当一台宿主上运行了多个在线业务容器时,经常遇到某容器性能下降的case,而同服务的其他容器无异常。检查发现,这通常是因为问题容器所在宿主上的其他容器负载突增或容器异常导致的消耗宿主资源过多,进而影响同宿主其他容器。为此我们在混部资源隔离方面做了一些拓展:首先对每个容器进行服务定级,根据容器的cpu request值为容器绑CPU核(cpu_set),利用cgroup v2的blk-throttle实现对非driect io场景下的磁盘读写速度隔离,利用Intel的CAT功能实现对系统三级缓存的资源优化分配,利用MBA和MBM技术实现对内存带宽的使用限制,利用tc实现对容器网络带宽的限制,利用xfs_quota实现对容器hostpath挂载目录的容量限制。我们的目标是:动态的变更以上容器所分配的资源配比,而更改的依据则是容器内业务指标的变化而变化。这块我们还在努力探索中,也欢迎有其他感兴趣的朋友和我们交流。
安全加固
针对Kubernetes本身的安全加固主要体现在:
- 限制容器的重建次数:我们在运维过程中发现:某些配置了探针的容器因为服务没有成功启动而不断的被Kubernetes杀掉重建,在重建超过几百或数千次之后会偶发性的导致宿主的一些异常。这个问题的原因我们尚未查明,但是容器不断的重建本身就显得没有意义,再加上我们使用了自研的服务发现,所以就对每个容器的重启次数做了限制,让负载均衡层去做健康检查;
- 调高controller驱逐Pod的容忍时间,宿主偶发的notready可能导致容器触发evict动作,对于有状态容器来说漂移并不是完全无损的,所以调高驱逐时间是完全有必要的;
- 添加判断宿主状态旁路系统,对宿主状态变化做二次确认。宿主notready带来的最大影响就是容器的强制漂移,而生杀大权完全依靠kubelet向kube-apiserver上报状态数据。很多复杂的因素都有可能导致这条通路的不稳定进而影响状态更新,造成健康宿主被误诊成故障进而触发漂移最终雪崩的结果。为此我们对kube-controller-manager做了改造,controller在决定要对宿主状态做修改后还需要通过一个旁路流程确认宿主状态,两边一致之后再做修改。
Q&A
Q:利用cgroup v2的blk-throttle实现对非driect io场景下的磁盘读写速度隔离,这个怎么做的,Kubernetes支持吗?
A:如果是3的内核,那么只支持cgroup v1,v1只支持对driect io的磁盘读写限制,而我们知道大多数情况下的写磁盘都是先写到内存里再FLUSH到磁盘上,所以要限制的是从内存到磁盘的这个环节。4的内核支持cgroup v2,这个功能已经比较成熟,你可以尝试升级内核或者将v2的功能移植到v1上。
Q:是要修改dockerd的配置,还是在Kubernetes上扩展?
A:如果是接着上面的问题的话,我们是使用了一个Agent的方式,单独的做加强的资源隔离的,没有在Kubernetes上做扩展。
Q:DGW是基于什么实现的?
A:DGW其实就是LVS,我们这边的系统组实现了通过接口的方式动态的变更LVS配置的能力。
Q:容器网络和物理网络类型一样,具体是什么网络?原有的ONOS SDN网络还有用吗?不同namespace之间隔离怎么做?
A:老的集群使用SDN网络,新集群使用物理网络。所谓物理网络就是和物理机同一个网络,只不过每个tor下划分出一段专门给容器分配使用。
Q:请问下“利用tc实现对容器网络带宽的限制”,具体怎么做的?
A:参考物理机使用tc对容器做网络流量限制,如果你用了SDN网络DPDK/OVS什么的,限速就更方便了。
Q:滴滴的卷管理是怎么设计的?有状态服务怎么设计调度,来保证数据持久化的?
A:目前我们这边有状态服务非常多,无状态非常少,用户的使用习惯还是和物理机趋同,对于这部分“老派”的用户我们提供了类似虚拟机这样的容器。使用本地卷(hostpath),容器只能本地重建不可跨宿主漂移。这也是对业务的一种妥协。另外我们也使用过ceph,但是最后评估风险更大,故暂时放弃使用,或小范围使用。
Q:通过负载均衡的健康检查测试服务可用性,健康检查的周期内,如何保证流量不丢失?
A:我们这边LVS默认有7秒的探活间隔,在7秒的间隔内如果容器故障不可用那么确实会有流量的丢失,目前不可避免。
Q:容器的优雅下线怎么实现的?
A:我们之前的容器启动都是用Supervisor托管的,后来我们自己写了一个dockerinit,用于用户业务服务的启停,他的功能比较强大能执行单独的一次性程序(类似物理机的rc.local),也能托管进程。于此同时他会保证容器所有的业务进程退出后再销毁容器,避免粗暴的停止容器。
Q:有没有使用cpu_quota来做精细的限制呢?CPU request绑定CPU,超分的情况怎么处理呢?
A:你想问的其实是超售的问题,参考业内一些走在前面的大厂,我们的容器会根据容器的服务等级做适当的超售,这样一个48核的宿主就能承载更多的容器。这里面的细节比较多,这里不太好描述。