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