数人云上海&深圳两地“容器之Mesos/K8S/Swarm三国演义”的嘉宾精彩实录第三更来啦。惟品会是数人云Meetup的老朋友,去年曾作过RPC服务框架和Mesos容器化的分享。本次分享中,嘉宾王成昌分享了惟品会在Kubernetes上两年的PaaS实践,干货满满诚意奉上~前端
王成昌,惟品会PaaS平台高级开发工程师
主要工做内容包括:平台DevOps方案流程优化,持续部署,平台日志收集,Docker以及Kubernetes研究。node
你们好,我是惟品会PaaS团队的王成昌,与你们分享一下PaaS在Kubernetes的实践。基于2014年末或2015年初PaaS没有推广的现状,惟品会PaaS部门目前已经作了两年的时间。数据库
PaaS 主要工做将分为三个部分进行介绍,首先,PaaS定义的标准构建流程,持续集成和持续部署的架构以及已有组建上的功能定制;第二部分,基于Kubernetes实现的网络方案,以及根据网络方案作的扩展定制;第三部分,PaaS如何作日志收集和监控方案,最后列一下惟品会目前为止所遇到的问题和总结。api
惟品会目前线上有一千多个域,每一个域之间相互的依赖比较复杂,每次的部署发布困难。线下有多套的测试环境,每套测试环境都要去维护单独的应用升级和管理。公司层面也没有统一的持续集成和部署的流程,你们各自去维护一个Jenkins或者一个Jenkins slave,看工程师的我的追求是否可以写一个完整的从源代码、到打包、最后到部署的脚本。安全
惟品会线上所有用物理机在跑,以前Openstack方式没有在线上,只是在测试环境跑,物理机的使用效率仍是比较低的。即便在7周年大促的高峰时段,60~80%的物理机利用率也均低于10%。网络
基于前面提到的现状,惟品会的PaaS定义了一个构建流程,整个流程不是一蹴而就,这是目前为止的定义,首先从源代码的角度出发,即Git,全部的7个Phase所有包括在Jenkins Pipeline里,因为是基于Kubernetes,因此Jenkins Pipeline的执行是经过Jenkins k8s Plugin去调度后台的k8s Cluster,由k8s产生的Pod去运行Pipeline。整个Pipeline的几个阶段,除了传统的编译单元测试和打包以外,加入了烘焙镜像、部署以及集成公司的集成测试(即VTP),打包和镜像完成后会正常上传到公司统一的包管理系统Cider和平台维护的Docker registry。架构
部署完成后会触发集成测试,若是经过测试的话,会把这个包或者是镜像标记为可用的状态,通常先从测试环境标记,而后经过到staging环境。目前PaaS 平台主要是维护测试环境和staging环境,线上尚未,可是已经定义了一个审批的流程,若是标记了这个包为可用的状态,须要一个审批来决定它是否能够上线。部署后经过k8s client,由另一套k8s的集群来管理部署里面全部的节点。app
这是惟品会的PaaS架构,主要包含持续集成和持续部署。首先由一个统一UI的入口Dashboard,使用Nginx和Tomcat做为服务的网关。其背后有两套系统——CPMS和API server,CPMS主要管理持续集成的各个流程,API server主要管理应用部署,在CPMS背后是使用多个Jenkins server统一连到一个Kubernetes集群上产生Pod做为Jenkins slave去运行,不一样的构建有多种语言也有不一样的模板,这里会提供各类方案让不一样的Jenkins Pipeline运行在不一样的Kubernetes node里面。框架
在部署实现一个Cloud Framework,能够接入各类cloud provider,目前使用的是k8s provider,背后的服务发现也是k8s推荐使用的Skydns。为了兼容公司基于包发布的这样一套模式,镜像管理这部分会把包管理系统Cider接入进入,平台的Docker Registry,以及应公司安全方面的要求,经过Clair对镜像的内容进行检查。dom
在日志收集方面,使用fluentd+ELK的组合,采用Prometheus作监控。在PaaS架构里,安全是经过接入公司的CAS作认证的动做,有一个Oauth组件作鉴权机制,经过Gnats作消息传输的系统。配额的问题在构建和部署中都会有所体现,包括用户对于Pipeline的个数控制或者Pipeline触发的个数,以及对应用上的物理配额或者逻辑资源配额等。
Docker Registry改造,主要在Middleware作了一些工做,作了一个接入公司的CAS和Oauth作的验证和受权。也接入了当有新的镜像Push进来的时候,会自动触发应用的部署。Docker Registry自己对全部的repository不一样的tag索引仍是比较慢的,因此会针对push进来全部的镜像信息存入数据库作一个索引,方便查找镜像。用户行为记录主要针对pull和push的动做,把它记录下来。镜像安全经过接入Clair作扫描,push镜像layer完成以后在push镜像的manifest时,会将镜像layer信息发送到Clair作镜像的安全扫描。
原来的Jenkins Kubernetes Plugin,默认把Jenkins slave调度在全部Kubernetes node上,咱们作了一个node selecter,针对不一样的Pipeline类型,须要跑在不一样的节点上。调度上加入了node selecter,此外在每一个Pipeline要去run的时候申请资源,加入了资源的request limit,防止单个的Pipeline在运行的时候占用过多的资源,致使其余的应用或者是构建任务受影响。在挂载方面,像传统的maven项目在下载过一个包以后,若是是同一个主机上会把.m2文件会挂载在主机上,不一样的Jenkins Pool在跑的时候,能够共享已经下载过的资源文件。
最后,实现了Container slave pool的策略。当要run一个Pipeline的时候,每次告诉k8s要起一个Jenkins slave Pod,当须要执行一个job的时候,等待时间比较长,这里会定一个池的策略,就是一个预先准备的过程,当有一个新的任务要run的时候,马上就能够拿到一个可用的containerslave。
这是PaaS的功能点,包含三个主要的部分,构建,部署和测试集。构建是关于用户定义Pipeline以及对Pipeline触发的record的管理,以及Pipeline各个phase的管理。部署主要对应用配置的管理,这个应用包括服务的配置如何、资源的申请如何,以及应用实例的一些管理。测试集对接公司的集成测试环境,和平台的应用进行关联。
空间管理和镜像管理,空间主要提供不一样的隔离空间,提供应用快速的复制,好比你有一个测试环境,我也有一个测试环境,为了你们环境之间相互不干扰能够提供应用的快速复制。镜像管理主要分三种,即平台提供的基础镜像,业务部门一些特殊的需求会基于基础镜像作一些定制,以及具体业务镜像。
PaaS采用的网络方案,网络方案最开始直接使用的k8s 1.0的版本加flannel的一套工做模式,后来因为业务需求,用户需求可以直接访问到实例IP,而flannel当时是封闭的子网。目前采用Contiv这套网络模式,由公司统一分配Pod的IP网段。这里作了一个kube-HAProxy,替换了节点上kube-proxy这个组件,用kube-HAProxy来作Service IP到end point的一个转发。
在kube2sky,完成域名和服务IP的注册。传统的模式下,域名是短域名,Service的名字做为短域名,还有Service自己的IP会注册到Skydns上。这里作了一些定制,由于公司的应用好比两个业务域A和B都有自己的域名——a.vip.com和b.vip.com,A若是要访问B,不能让这个访问跑到线上或者其余环境去,因而经过kube-sky去解析规则,把b.vip.com加入到里面,再加一个subdomain做为扩展的domain search,最终找到平台内部部署的B域。
goroute 主要是平台内部的应用,每一个应用都会提供一个平台的域名,这个域名主要是有一个组件叫作state aggregator,会watch k8s apiserver发出来的Service和end point的变化,最终经过Service的名字和end point的地址,把它写到gorouter的route注册表信息中,当咱们访问平台域名时就能够找到真正的end point地址。这里也有定制,采用HAproxy和KeepAlived替换了kube-proxy,以前从Service IP到end point IP的转化,经过每一个节点部署的kube-proxy,它会检测到 Service和end point的变化,去写IPtables的规则,来找到最终end point的地址的IP。
如今统一使用的HAproxy加上KeepAlived,有一个kube2HAproxy组件,功能和kube-proxy前面一部分类似,都要watch kube-apiserver的Service和 end point的event来动态的生成一个HAproxy最新的配置。KeepAlived主要为了高可用。有一个值得注意的细节,kube2HAproxy所在机器的IP,要和Service IP的网段在同一个网段里,用户在访问真正的应用的时候直接使用Service IP是公共可见的,而不是随便定义的Service IP。
对外应用访问是由平台提供的域名,后缀均为*.PaaS.vip.com,解析到以后会有公司的DNS统一转发到gorouter这台机器上,由于gorouter会监听到Service和end point的变化,route表里面会存储每一个域名对应的end point的地址,经过round robin的方式找到最终的Pod来完成http访问。
最后一个定制关于Pod的IP固定,为何要作PodIP固定?由于以前的测试环境不少应用都是部署在VM甚至在物理机上,IP都是固定的,有一些应用是须要白名单访问的,应用在这个部署机上,须要将IP提供给相应的调用方或者是公司的某个部门来告诉他加入白名单。Docker 默认状况下,每次销毁和重建的过程当中,IP都会随机申请和释放,因此IP有可能变化。IP固定主要在k8s apiserver作,加了两个对象,即Pod IP Allocator和IP recycler,Pod IP Allocator是一个大的Pod的网段,能够认为它是Pod的IP池, IP recycler主要记录一些临时回收的IP,或者叫临时暂存区,IP不是一直存在,不然是一种IP浪费,有一个TTL时效性的。
当应用从新部署的时候,本来的Pod会被删掉,删掉的过程当中会先放在IP recycler中,当一个新的Pod启动的时候,会经过namespace+RC name的规则去找是否有可用的IP,若是找到优先用这样的IP记录在Pod里,这个Pod对象最终会交由kubelet去启动,kubelet 启动的时候会读取这个Pod IP,而后告诉Docker 启动的IP是什么。最终有新的Pod启动以后,它的IP是以前已经被销毁的Pod IP,达到的效果就是Pod IP固定。在kubelet由于修改了Pod对象的结构,增长了Pod IP记录使用IP的状况,根据Pod的IP告诉Docker run的时候执行刚刚的IP来启动。kubelet 在删除Pod的时候会告诉k8s去release这个IP。
日志收集主要分三种类型:首先是平台自身的服务组件的收集,好比像jenkins、Docker 或者Kubernetes 相关组件的日志收集,另外一个是全部部署在平台里面应用的收集,最后还有一些域,由于公司一些已有系统(dragonfly)也是作日志收集和监控的,有一些特定的规则对接公司。
平台自身日志收集的规则,包含系统组件还有平台应用两种设计。系统组件比较简单,无外乎经过systemd或者是指定日志文件的路径作日志的收集,应用收集主要在k8snode上,k8s会把每个Pod日志link在一个特定的文件路径下,由于Docker会记录每个容器的日志,能够从这个地方读取应用的日志,可是只拿到namespace和Pod name这样的结构,咱们会经过fluentd里的filter反向去k8s拿Pod所对应的meta data,最终发送到kafka,经过logstash达到elastic search。
Kibana的展示作了一些定制,由于平台的展示主要基于namespace和应用名称的概念查看日志的,定制可以展示特定的namespace下的特定应用的日志,同时把自定义的告警加在了这里,由于告警是经过elastalert来作的,在Kibana上作一个自定义告警的UI入口,由用户来指定想要监听什么样的日志内容的告警,去配置监听的间隔或者出现的次数,以及最终的邮件接收人。
有一个组件是当用户建立了自定义告警的规则时会发送到后面的elastalert ruler,ruler解析前台UI的信息,生成elastalert可以识别的configure文件,动态地读取configure的加入。
对接公司的业务系统比较简单,特定的收集规则都是特定的,好比具体的目录规则,通常来说都是经过挂载容器的目录到主机目录上,在主机上统一部署一个agent去run,主要体如今k8s node上,因此仍然使用Daemonset的方式去跑agent。
监控有两种,一种是对单个Pod的实例监控,在页面上是能够直接看这样的实例的,单个Pod是一个应用实例了,而后经过node agent去包装了cAdviser,前端去统一访问,获取对应的CPU和Memory使用信息。cAdviser对Network收集到的数据是不正确的,经过node agent 读取Linux file获取Network的信息。Websocket Server 是为了提供网页上直接对容器进行网页控制台的登陆。
另外一个是看整个的应用,由于应用是有多个实例的,经过Graphana去定制,去展示namespace的应用,有多个实例,就把多个实例的监控都展示出来。此处有一个promethus plugin的定制,以前有一些Swarm的节点加入,持续部署提到过它是一个多个cloud framework均可以接入的。惟品会接入了一些Swarm的信息,针对Swarm建立的容器的话,也要可以监控到它的容器监控信息的数据。在Promethus plugin经过Docker info获取不一样的Swarm node的信息,在每一个Swarm node上部署cAdviser,获取由Swarm建立的容器的监控信息。
遇到的问题很是多,到如今为止将近两年的时间,有不少都是能够在GitHub找到的问题,以及经过升级能够解决的问题。最开始采用的是Docker 1.6以及Kubernetes 1.0,中间经历两次的升级,如今主要使用Docker 1.10和Kubernetes 1.2。
production使用direct-lvm作Devicemapper存储。
目前发现一些Pod一直处于pending的状态,使用kubectl或者Kubernetes API没有办法直接对Pod作任何操做,目前只能经过手动Docker的方式删除容器。
k8s僵死容器是之前碰到的问题,如今最新版的Kubelet是支持这样的参数,容许每个节点上最大僵死容器个数,交由Kubelet本身作清理的工做。
由于之前发现用户来告诉配额不足的时候,调整配额并不会立马的生效,升级之后就没有这种问题了。
这个是应用的问题,不是传统理解的跑一次性任务,k8s batch job要求必定要exit 0。也有一些Job的类型,目前是直接对应的背后k8s的Pod方式。
这是最新遇到的问题,上下文的话,稍微有一点复杂,有两个应用部署在惟品会的平台上,每一个应用都有本身的legacy域名,好比有一个域名叫user.vip.com,另外一个叫info.user.vip.com,这时候ping user.vip.com,有可能会拿到info.user.vip.com的IP,因为kube-sky自己在写Skydns record时候有问题,因此会添加一个group作一个惟一性的识别,这样在ping子域名(user.vip.com)的时候就不会读到info.user.vip.com。Skydns自己目录结构的问题,加上group就不会再去读到下面的子路径。
惟品会一直使用Devicemapper,中间尝试一些节点用Overlayfs的存储,可是在目录操做时会file not found,官方也有一个issue说当Overlayfs 在不一样的layer的时候,牵扯到删除的操做,会出现这个问题。能够升级解决,可是系统是固定的,升级很麻烦,因此没有作升级而是切回了Devicemapper。
因为容器占用的磁盘空间过多,致使了整个k8s node的磁盘空间被占满的问题,惟品会是在Cadviser作一些改造,能够监控到每个容器所占用的磁盘空间,对磁盘空间作一些限制。
在删除一个namespace的时候,namespace是能够动态建立和删除的,在删除的时候会看到namespace一直处于terminating的状态,这个在GitHub上有一些解决方法,由于它自己是因为namespace的finalizer会进入一个死循环,有一个work around,能够手动的置空finalizer,把这个namespace update回去,就能够正常删除了。
以上就是我要分享的主要内容,谢谢你们。