导读:本文由Kubernetes在微博落地的具体工作整理而成。通过围绕业务需求和大家分享下企业内部如何使用Kubernetes来解决具体的业务问题和相应的方案。文中分享了基于Kubernetes的PaaS层弹性混合部署方案,其中有Kubernetes的优点,也有部分Kubernetes在企业落地的缺陷,欢迎大家一起讨论和建设Kubernetes。希望本文对大家有一定的借鉴意义。
作者介绍:
彭涛,主要负责微博容器平台的开发工作。Kubernetes代码贡献者。曾就职于百度基础架构部,百度公有云事业部。长期从事云计算行业。熟悉网络虚拟化,SDN,OpenStack,Kubernetes。致力于推动Kubernetes在微博的应用落地,构建高效的PaaS平台
王琨,微博平台高级产品运维工程师,主要负责微博feed、用户关系、架构业务的运维支撑及改造工作。擅长大规模分布式系统集群的管理与运维,疑难问题分析,故障定位与处理。致力于推进运维自动化,构建微博平台高效的运维平台。
一、微博容器平台
2016年微博平台实现基于混合云的弹性平台DCP,提升了Feed、手机微博、广告、搜索、话题、视频、直播等多个核心业务热点应对能力。2017年微博平台率先探索基于Kubernetes的PAAS层弹性混合部署解决方案,并且积极的和社区保持同步。2018年实现CI/CD与生产环境混合部署,2019年春晚实现部分核心业务混合部署与弹性伸缩。
本文主要介绍微博平台落地Kubernetes过程中的一些经验教训。
二、为什么选择Kubernetes
因为历史原因,微博docker容器治理是采用独占物理机(虚拟机)模式,直接使用物理机的网络协议栈,服务治理采用服务池方式。随着设备计算能力的提升,这种治理方式有几个问题急需解决:
1. 利用率问题:一个服务池内新、老设备共存,因为业务容器需要兼容老设备规格,导致服务无法充分发挥出新设备应有的计算能力。
2. 容器网络问题:因为直接采用物理机网络栈,导致业务混合部署只能采用调整业务监听端口的方式,造成接入成本、管理成本高。
3. 调度治理问题:因为默认采用独占策略,服务池之间资源相互隔离,不同类型的业务类型无法共享资源,导致资源总是相对紧缺。
Kubernetes提供标准化的容器管理,CNI网络虚拟化,自定义弹性调度策略,能够很好的解决上述问题。但是Kubernetes面向公有PAAS的实现方案在内网环境下有些方面不适用,主要有如下几个方面:
1. 网络虚拟化:在BGP的虚拟网络方案和隧道的虚拟网络方案中都会引入iptables来完成流量牵引,和防火墙的相关功能,在企业内网使用的过程中并没有很强烈的防火墙需求,引入iptables往往会造成性能下降(nf_conntrack表被打满,NAT性能太低)。所以微博平台现阶段没有使用BGP虚拟化网络方案和隧道虚拟化网络方案
2. 滚动发布:目前的Kubernetes的滚动发布(Deployment)不支持In-place rolling updates ,每次一个Pod都有可能被分配不同的IP地址,在企业内部使用容器的时候,固定IP的需求往往很强烈,所以我们抛弃了Kubernetes而选择了整合了公司内部的滚动发布系统
3. 资源隔离:原生的内存隔离策略中不支持swap的限制,容器会占用物理机的swap,我们修改了内存隔离限制。
4. 负载均衡:原生的Service模式会引入iptables来做NAT,同时Service的负载是硬负载没法调整流量权重。
我们基于Kubernetes搭建了一套PaaS平台,对Kubernetes进行了改进,提供了以下功能:
1. 网络虚拟化:基于CNI,提供了隔离内网和公有云网络差异的虚拟化网络方案
2. 调度管理:基于kube-scheduler,提供了锁定IP的调度系统,该系统支持带宽初筛,硬盘初筛,机房就近调度,返回库存状态,提前锁定IP功能等功能
3. CI/CD:一键发布代码,替代Kubernetes的Deployment进行滚动发布
4. 资源隔离:在原有的隔离策略上,扩展出计算资源隔离,网络资源隔离,存储资源隔离
5. 负载均衡:整合已有的调度系统,利用微服务快速部署+弹性调度提前锁定IP,减少服务抖动耗时。
6. 模块化运维:把已有的物理机运维工具整合到容器中,在Pod里面共享存储,共享网络
7. 弹性扩缩容:通过对DCP的整合,使其具有了容器弹性扩缩容的功能。
8. 监控:通过模块化的运维体系,整合了监控所需日志,无缝连接已有功能
三、整体方案
图一
整体方案如图一,微博容器平台划分出如下几层:
1. 服务层:平台的主要入口提供容器扩缩容、上下线、维护服务池、负载均衡,监控管理等功能
2. PaaS层:提供容器管理和调度管理等相关功能,负责将服务层的请求转化成对应容器的操作
3. IaaS层:提高机器资源、网络资源、存储资源供PaaS生成的容器使用,负责对容器使用资源进行管理
三、容器弹性化扩缩容平台建设
微博容器弹性扩缩容平台,是在Kubernetes基础上进行了改进,充分利用了微博平台已有的资源。避免重复造轮子。具体的工作如下:
3.1 基础建设之网络虚拟化
之前已经说过了,微博的容器会独占物理机的网络协议栈,虽然能够做到网络效率的最大化,但是会导致多容器部署时出现端口冲突。为了解决端口冲突需要使用虚拟化网络技术提供容器独立的IP地址。多个容器独立IP需要解决以下的三个问题:
1. 容器的IP地址分配问题;
2. 容器的IP路由问题;
3. 虚拟化网络对网络的性能损失要最小化;
第一个问题因为采用Kubernetes IP分配都是通过记录在etcd中,所以不会出现分配重复或者分配错误的问题,而第二个问题社区里面通常会采用隧道类型方案和BGP方案。以下是隧道模式和BGP模式的优缺点对比如表一, 性能测试如表二(BGP主要工作是路由交换,转发等不受影响,等同于物理机性能。)
表一
表二
在测试结果中显示vxlan因为需要封装和解封隧道导致带宽损耗过5%,所以隧道方案不适用于内网的网络环境。而BGP的方案Calico会引入iptables去做ACL,不仅在业务峰值流量的情况下会触发nf_conntrack表被打满丢包的风险。而且BGP方案在公有云和内网落地的时候也存在问题:
公有云方面:
1. 从公有云虚拟机发出的报文必须是Mac地址和IP地址匹配的,所以导致在公有云机器上BGP虚拟网络的容器根本无法通信。
内网方面:
1. 内网机器的上联交换机上做了Vlan和IP的绑定,如果在内网机器上起了一个其他网段的IP地址,报文发送不出本机。
接下来先来看看在网络方案上我们做的一些工作(见图二)。
图二
微博虚拟网络主要是四方面内容:
1. 对机房网络进行改造,修改机器的上联交换机为trunk模式,支持多Vlantag的网络通信
2. 在物理机层面通过创建网卡子接口(如图一左侧),通过对网卡子接口做MacVlan虚拟网卡插入Kubernetes的Pause容器中,把容器网络与物理网络打通。
3. 公有云方面通过创建弹性网卡,让一个机器上有多个网卡,且每块网卡带独立IP地址,然后对新加的网卡做host-device,将网卡的所属network namespace 修改为Kubernetes的Pause容器,把容器和物理网络打通。
4. 对CNI插件进行修改,能够给容器分配指定IP地址 ;
图二左侧是简化后的内网网络拓扑,容器的虚拟网卡通过MacVlan与物理网卡的网卡子接口相连,发出的报文会带上网卡子接口的Vlantag,而这部分的流量上到上联交换机之后就和物理机发出的没有任何区别,之后的都是交换机和网关去解决掉路由的问题。这个方案的设计对现有的环境依赖最小。同时改动量少。实现机房物理网络与容器网络的扁平化,解决了容器网络和物理网络互联互通的问题,由于没有隧道解封性能问题。性能基本上持平物理机性能。 本质上这是一个私有云的网络解决方案,但是很好的解决了问题。
图二右侧是简化后的公有云网络拓扑,通过把物理机上的网卡迁移到容器里面来间接的实现多IP。由于是虚拟机的弹性网卡,等同于虚拟机上的物理网卡,性能没有问题。
3.1.1 虚拟网络后续的演近
对Calico进行改进取消iptables依赖。利用Calico去解决内网网络方案中ip浪费的问题。同时可以对Calico做进一步的研究,如动态迁移容器如何保持ip漂移。
3.2 基础建设之调度管理
容器调度,其实是为了提高资源利用率,同时减少资源碎片化。Kubernetes的调度策略做的相对灵活,对Pod的调度通过三个阶段来实现,初筛阶段用于筛选出符合基本要求的物理机节点,优选阶段用于得到在初筛的节点里面根据策略来完成选择最优节点。在优选完毕之后,还有一个绑定过程,用于把Pod和物理机进行绑定,锁定机器上的资源。这三步完成之后,位于节点上的kubelet才能开始真正的创建Pod。在实际的接入过程中,Kubernetes的基础调度策略不能满足平台的业务需求,主要有如下两点:
1. 因为没有规格的概念所以无法给出库存状态
2. 初筛的纬度太少,目前只支持CPU,内存的初筛,优选不支持同机房就近调度。
3.2.1 整体方案
图三
整体的调度管理分成如下几层:
1. 接口层:
a. 用于接收请求返回特定规格,特定调度需求下的库存数量,同时返回锁定好的虚拟IP地址。
b. 用于接收请求释放虚拟IP地址。
2. 初筛层:对缓存中的节点信息进行初筛,返回能部署规格的全部物理机节点信息
3. 优选层:根据优选结果,模拟部署Pod,统计整体库存。
4. 部署策略层:按照部署策略挑选物理机,并且锁定物理机上的虚拟IP地址,返回库存信息
3.2.2 调度管理之接口层
锁定库存接口层的逻辑:
1. 把请求参数转化为Kubernetes的Pod对象 -> v1.Pod。
2. 把scheduler的Cache进行一次深拷贝,后续的动作都会在这个深拷贝的Cache中完成
3. 请求监控返回物理机的实时硬盘信息,实时带宽信息。整合到深拷贝Cache中
4. 连同请求参数都传递给初筛层
释放库存接口层的逻辑:
1. 调用Kubernetes接口,把物理机节点上虚拟IP的label改成unusing。
3.2.3 调度管理
图四
初筛层:对上述的cache部分里面的Node节点进行CPU,内存,硬盘和带宽的初步筛选,返回通过筛选的所有物理机节点。
优选层:对上述的物理机节点信息进行打分,对节点进行排序。然后根据请求所需部署的容器数量,按照物理机节点的进行模拟部署(挑选物理机按照分数从高到低排列),直到全部节点上可部署容器数量为0,统计每个节点能部署的容器个数。
部署策略层:根据请求参数的不同(目前只支持集中/平铺),锁定物理机上的IP地址(调用kubernetes的API 把物理机上虚拟IP的label置为Using状态),并且返回这些IP地址。
3.2.6 整体流程图
图五
3.2.7 后续演进
支持调度优先级,且能根据实际的资源使用情况进行调度而不是Kubernetes预分配的资源进行调度。
3.3 基础建设之资源隔离
Kubernetes支持CPU、内存的隔离。在宿主机层面支持驱逐模式。通过虚拟化网络的方案,主容器网络和混部容器的网络分割开来。整体的资源隔离目标是为了隔离开主容器和混部容器对资源的使用。以下是我们做的一些改进。
计算资源隔离
1. K8S提供了内存的限制能力,是通过OOM来限制内存的使用。在此基础上,我们还增加了限制容器使用物理机的swap。
存储资源隔离
1. K8S没有提供基于物理机的存储资源配额方案,但是提供了相关的框架接口。在此基础上,开发了有配额大小限制的物理机静态存储解决方案。
网络资源隔离
1. 针对容器的网络限速方案,已经在内部测试通过,同时帮助社区完善了相关的限速代码。
3.3.1 后续的演进
资源隔离的后续方向很多,首先要解决的是Kubernetes如何能动态设置资源阈值。其后需要能够设置内存OOM优先级,以及满足资源超卖的需求
3.4 基础建设之CI/CD
平台于2018年基于gitlab开发了CI/CD,通过CI/CD和Kubernetes的配合来完成从代码提交到上线的完整流程,其中使用Kubernetes的上线流程(如Deployment)的滚动发布存在着容器IP不固定,动态化的问题。是因为Kubernetes的设计原则中对集群的管理尤其是服务升级过程中需要保持“无损”升级(升级过程中提供服务的副本数一直符合预期)。如果对一个Deployment进行滚动升级,那么这个Deployment里面的IP地址和滚动升级之前的IP地址是不会相同的。而如果集群够大,一次滚动发布就会导致负载均衡变更 (集群副本数/滚动发布步长)次。对于微博服务来说,频繁变更会导致这个负载均衡辖下,所以后端实例的接口不稳定。
而平台内部的之前的上线系统是根据业务冗余度及业务实际需要来调整上线的步长,减少在上线过程中业务的抖动,也就是通常说的In-place rolling updates。保证在上线过程中容器的IP不变。
整体流程的核心思路为:
1. 切断容器的流量
2. 进行流量检测,确保已经没有线上流量
3. 清理旧容器
4. 部署新容器
5. 检测服务是否正常启动(端口检测,接口验证)
6. 接收线上流量,提供服务
针对该问题,容器平台没有使用Kubernetes的原生滚动发布而是做了以下几个改进和适配:
1. 首先不使用DP,RC的概念来完成滚动发布,只使用Pod的概念。
2. 集成已有的上线系统,来完成滚动发布,回滚功能
3. 流量引入/流量拒绝 利用Kubernetes容器生命周期管理的lifecycle(修改了其中的postStar的原生实现,因为原生里面只调用一次,不管成功与否都会杀掉容器。改进成了如果不成功会按照指定的次数或时间进行重试),服务检查利用 liveness probe、 readiness probe来完成
主要流程有
1. 提前划分给每个机器上划分虚拟IP段,并给机器打上虚拟IP的label
2. 在原有的上线系统中增加对Kubernetes管理容器的上线流程,上线过程中通过服务池中已有IP反查Pod Name,然后删掉旧Pod,然后用新的镜像tag 生成Pod的json字符串(其中nodeSelect=${IP}),然后提交Kubernetes去创建新tag版本的Pod
3. Kubelet接到创建请求之后,提取其中的IP地址发给CNI,创建指定IP的新tag版本Pod
上线回滚的流程变成了删除Pod,创建Pod的固定化操作(见图六)
图六
由于给机器打好了虚拟IP标签,所以Pod的创建会被分配给固定的物理机去执行,配合修改之后的CNI就能创建指定新tag+固定IP的Pod来提供服务。滚动发布和回滚变成了删除Pod,创建Pod的固定化操作。
3.5 基础建设之模块化运维
由于之前的容器是独占物理机的模式,所以对于容器的运维也是在物理机上进行的,一些功能如日志处理、域名解析、时钟同步、运维Agent管理以及定时任务等都是在物理机层面操作。如果开始多容器混合部署,以上的功能都兼容改动工作量大。再加上平台面向的业务方众多,需求各不相同。例如日志推送的方式已经出现scribe、flume、filebeat等不同的方式,业务运维需要根据自身去定制运维容器,由此可见模块化运维的必要性。我们基于Kubernetes的Pod概念做了如下的工作,整体架构见图七。
图七
1. 单独做了运维容器,把运维相关的工具集成在容器里面;
2. 运维容器和业务容器共享网络协议栈,共享日志存储;
3. 在容器里面共享存储是带配额的静态存储;
3.5.1 模块化运维之定时任务
物理机上的定时任务以来于crontab,而crontab会由systemd来启动。在容器中使用systemd启动会涉及到提权问题,在实践过程中发现用systemd如果权限控制不当会造成容器被Kill的情况,所以单独开发了兼容linux crontab语法的定时任务工具-gorun,把这个工具集成在了运维容器里面,替代了crontab来完成定时任务。
3.1.5.2 模块化运维之日志处理
日志的处理主要包括监控采集、日志推送、日志查询、日志压缩清理四方面:
1. 日志推送:通过scribe,flume等方式接收业务日志,推送给信息系统部等数据处理部门(如图八)。
图八
2. 日志查询:容器产生的日志需要能够静态存储三天左右,方便故障定位。所以日志会存储基于Kubernetes的PVC概念开发的本地带配额的静态存储里面。
3. 日志压缩清理:磁盘空间有限,打印的日志需要定期的清理和压缩
4. 监控采集:通过监听文件变化或者监听端口来采集需要的监控数据
通过上述手段,能够利用现有的日志体系,同时开发的工作量最小,通过这样的操作,以后的容器想要接入,只需要在Pod的配置文件里面多写一个container的配置即可。
3.5.3 后续的演进
后续的运维容器将会进一步的拆分,做成标准化的服务,例如域名解析容器,日志推送容器。让业务的接入变得更加的容易。
3.6 基础建设之弹性扩缩容
弹性伸缩在微博的应用很广,作为支持了全公司春晚扩容的DCP系统其主要能力就是进行IAAS层虚拟机的弹性伸缩。是对业务进行保障的重要手段之一。弹性扩缩容保证在峰值流量来临时通过扩容提高接口的可用性,在业务量级下降后回收资源节省了成本,而PaaS层的扩缩容比IaaS层的扩缩容更具有优势,一来是因为PaaS的启动更轻,没有虚拟机的初始化阶段。所以启动更快,二来是因为我们的弹性调度系统能提前锁定IP,可以做到业务容器和变更Nginx同步进行。所以在PaaS层的弹性扩缩容,我们目前做到了如下几点工作:
1. 定时扩缩容实现;
2. 自动化扩缩容的实现
定时扩缩容是复用了DCP系统的定时扩缩容功能,并做了一定的适配,目前可以选择使用原生扩容模式(IaaS层)和Kubernetes扩容模式。选择好了模式之后需要填的就是Pod的调度参数和本身Pod的标准化json文件,之后就是常规功能。
自动化扩缩容的实现,是基于CI/CD系统中的放量系统完成的,CI/CD会在上线过程中有单机放量的步骤,其中会采集放量容器的监控数据,业务数据和日志数据。横向对比当前服务池的整体接口耗时和纵向对比历史七天内单机的接口耗时。通过这套系统,把相关的阈值指标导出之后,集成到监控系统中,如果判断接口的QPS和耗时产生了恶化,会相应的触发扩容操作,不同于IaaS层的扩容,PaaS的扩容在存量机器上是免费的的。例如前端某接口的QPS过高,处理不过来了可以在机器学习服务池的机器上进行容器扩缩容去先扛住量。
3.1.7 基础建设之负载均衡
微博平台的7层负载均衡方案是使用Nginx。目前仍使用的是静态文件的进行路由信息管理,nginx变更系统实现了一套动态的解决方案,将upstream与对应的服务池进行关联,确保对应的接口由对应的服务池来提供服务,当服务池内有节点变更时,自动触发配置下发进行nginx的变更。
Kubernetes从最开始的Service上就开始了负载均衡和服务发现的尝试,例如在Service上利用Iptables进行请求的随机负载(问题在于后端某个实例的压力已经很高了,由于是iptables的硬负载不能感知到,依然把请求传递过来)。而且和网络虚拟化一样引入Iptables会有nf_conntrack打满的风险,考虑到公司内部的已经有成熟的负载均衡系统,这块进行了一个适配。
由于弹性调度的功能,我们会在创建Pod之前就可以锁定的IP地址。当IP地址锁定后,我们可以同步的变更我们的负载均衡系统+启动我们的业务容器。能够更快的响应业务请求(见图九)。
图九
3.8 基础建设之监控系统
监控和日志都是平台内部成熟的组件,通过模块化运维中日志信息的采集推送,监控数据采集推送已经兼容原有方式推送到监控平台。而物理机监控已经通过运维物理机部署Agent来采集,不需要重新引入别的工具去完成。整体的监控总共有如下的几个部分:
1. 物理机信息监控:物理机CPU、内存、IOPS、带宽、负载、网络等基础监控信息
2. 业务容器的业务监控:包括接口QPS、耗时、返回状态码占比、err/warn日志记数、链接资源耗时等(见图十)
图十
3. 容器使用物理资源监控:CPU、内存、IOPS、带宽、负载、网络等基础监控信息,由于已有一套监控体系,修改了的实现把原先需要集中处理的部分落在了计算节点本地,然后通过已有的物理机监控数据推送到远端监控上。一来避免了之前Heapster结构中的单点问题,二来可以复用现有的日志推送架构。(见图十一)
图十一
4. Kubernetes 组件监控:包括单机的kubelet接口耗时、成功率监控、日志中err、warn监控,同样包括master节点的同类数据监控
基础平台的建设不限于上述的部分,还做了鉴权,DNS优化等相关服务。限于篇幅不便展开,于此同时微博平台Kubernetes也在积极的与社区保持迭代,争取做到能回馈社区。
四、业务落地
2019年春晚期间,后端服务支持红包飞业务,如果按照传统方式需要近百台的公有云设备按需成本。我们通过把垂直业务从大容器中抽离形成微服务,利用Kubernetes PaaS整合能力,在未增加资源成本的情况下完成春晚红包飞保障。
目前有6大服务池接入Kubernetes PaaS,共管理数千核CPU,数TB内存的计算资源。通过Kubernetes整合闲散资源与合理的混合部署,可提供近整体的30%的弹性伸缩能力。
综上,是微博平台在探索Kubernetes与业务更好结合道路上的一些体会,实际业务接入过程中依然会有很多的挑战。但是在团队成员的努力下,最终实现了方案的落地并保障了春晚的线上业务的稳定。通过这次的平台建设充分体会到只有和业务结合,并且服务好业务才能更好的促进自身架构的成长,很多时候看似很完美的方案,面对真实的业务需求还是会出现纰漏。我们会吸取历史的教训,总结经验。争取利用先进的技术来为公司创造更大的价值。
五、展望未来
未来我们将长期运行混合部署的微服务架构,优化调度系统,对接更多的IAAS层提供商。更进一步提高接口可用性和资源利用率,也会对服务的稳定性和资源的利用率做进一步探索,利用技术提升研发效率也是我们后续的方向。在探索的同时我们也会对ServiceMesh、Serverless持续关注,结合着业务需求,更进一步优化容器基础平台。
作者:彭涛 王琨
来源:高可用架构