DockOne微信分享(二四九):国内某知名婚恋网站的Kubernetes落地实践

【编者的话】随着Kubernetes的遍地开花,Kubernetes的优势可以说是深入人心,所以,我们也掀起了一场改革,目的就是改变我们以往的运维模式,利用Kubernetes,来实现更高效的交付和更好地提高我们的资源使用率,推动标准化,适应云原生。本次内容主要是:基于团队实践的Kubernetes落地实战经验分享。

基于Kubernetes持续交付方案:如何一键实现容器编排

对接Kubernetes集群的部署,最麻烦的地方是一堆yaml文件的编写和管理。在这件事情的处理上我们当时有两个方案,一个是自己开发一个Operator来简化配置,并做一个配置管理中心做版本管理;另一个是寻找外部的开源方案。最终,我们选择了开源的Helm。因为Helm已经做好了yaml配置的简化和版本管理,是一个极好的包管理工具。

这里大概介绍一下我们改造后的持续交付流程,首先是GitLab收到一个push event,自动触发Jenkins Job的Webhook进行构建。Jenkins Job中,集成了我们的应用构建和部署相关的脚本,包括前后端代码的build——>单元测试、代码扫描等——>基于Dockerfile的镜像构建——>从chart museum中拉取已写好的helm chart template到本地进行修改,然后再推送至chartmuseum——>使用Kubeconfig与目标集群进行交互,通过Helm Upgrade将应用部署至Kubernetes集群。

在这里尤其要提到,持续交付不止于提升业务交付的效率。因为曾经管理过一个应用一个Jenkins Job,深知这里边的痛苦。所以我们对整个持续交付流程,做了进一步的抽象化。把构建脚本、镜像构建、Helm Chart配置这几个地方进行模块化后,把所有应用的部署交付统一成一致的脚本进行部署,实现通过几个简单配置就可以把一个应用对接到我们的持续交付平台中。这在效率提升上,相比以前真的实现了一次质的飞越。

最后提一下如何对接多集群的部署。最直接的方式,肯定是通过命令行切换Context,但这种方式会相对原始一些,而且在Jenkins Slave动态生成的情况下,还要做一些定制化配置。我们选择了Jenkins的Kubernetes Cli Plugin,结合Jenkins的凭证管理,实现在任何Slave上都可以一键进行容器编排。

关于Kubernetes落地的一些细节

关于集群网络方案的选择

最早我们其实是尝试过自建Kubernetes集群的,基于开源的方案基本上能满足绝大部分需求,但是如果涉及到集群外、跨集群的服务间调用,我们就必须重新开发CNI插件并且在私有网络中添加到Pod的相关路由,来打通Pod与集群外的网络。基于人力等方面的考虑,我们最终选择了TKE,Pod已实现与Node节点在同一网络层面,促进了我们后面的快速落地。

Kubernetes服务暴露方式

Kubernetes服务暴露的方式有三种,分别是Service的NodePort、LoadBalancer,和Ingress。我们用的是Ingress。这个主要是因为Ingress更符合我们利用Nginx做路由的现状。

Kubernetes监控

监控主要有三大块,分别是事件监控、资源监控和调用情况监控。前两者我们目前还没有自己做,暂时用的是腾讯云的监控,资源监控上加了metrics-server做增强。调用情况监控方面沿用了以前的监控方案,在容器中集成了pinpoint-agent做APM监控。

日志收集

因为原本我们的应用日志就有固定的目录,当然,这得益于我们的标准化。所以我们的方案是使用Pod反亲和性来让同一应用部署在不同的Node节点上,并通过日志落盘,在集群中部署Filebeat DaemonSet,给日志打上相关fields,利用Logstash切割后收集至ES集群中,通过Kibana进行展示。

Kubernetes为我们带来了什么变化

变化还是挺多的,包括资源利用率的提高,部署上效率的提升,多环境一致性,秒级扩缩容、Jenkins Slave动态生成等等。另外,利用Kubernetes在部署上的优势,我们在持续交付上做了相对深入的挖掘,实现了泳道环境、平滑发布和灰度发布。下面分别简单介绍下实现方式。

泳道环境——实现一个需求一个环境,在开发和测试中使用,互不影响

泳道环境,其实也叫需求环境,主要是服务于开发和测试同学,允许他们为某个需求创建一个相对独立的环境。它的原理是,我们会有一个相对稳定的环境,里面包含了所有的服务,这个称为主泳道。当某个需求提出时,改动只涉及到服务A和服务B,这个时候,我们会基于这个需求分支创建服务A和服务B,作为泳道-01,然后结合主泳道其他泳道-01没有的服务,联动起来行成一个独立环境。

做泳道环境主要两个难点,一个是如何实现快速部署一堆的服务,另一个是因为我们的业务是基于Dubbo框架的,所以需要另外实现底层Dubbo服务按需路由。第一个点在有了Kubernetes之后,快速部署已经不是难事了,结合持续交付平台可以快速把我们需要的所有服务一次性deploy到集群中。Dubbo服务路由方面,我们利用了Dubbo的分组来实现的。最终实现后的效果如下:

平滑发布——对随时发布线上非常有用,可以大大提升迭代效率

平滑发布我们利用了deployment的的滚动更新配合Pod的Liveness和Readness探针实现的。这里除了滚动更新的控制还需要注意两个点,一个是旧版本Pod必须确保收到SIGTERM信号后优先关闭监听端口从Endpoint列表中删除,另一个是新版本Pod加入Endpoints列表前需要经过Liveness和Readness的检测。

灰度发布

以前要实现灰度发布,一般都要手动在发布的时候控制发布的节点,或者通过写一堆脚本来实现固定比例的灰度。这里,因为我们的服务暴露方式用的是Ingress,所以直接利用Kubernetes的Service服务发现的能力来实现灰度发布。

使用Kubernetes过程中遇到的一些问题

CPUset的使用,实现CPU独占性

总有部分应用在高峰期对CPU的要求比较高,但是CPU频繁的上下文切换会使当前应用的CPU使用无法得到保障,无法及时处理外部请求,导致客户端出现超时。而如果集群内服务调用没有加熔断机制的话,就有可能引发大量客户端线程池打满,进而引发雪崩效应。

这个时候,可以对应用进行横向扩容来缓解这种现象,但更好的方式,是使用CPUset。具体的使用方法就请大家自行查看Kubernetes相关文档啦。

注意:内存和CPU的limits和requests都必须相等。

Ingress Controller节点上的短连接问题

因为Ingress Controller是整个集群的流量入口,使用nodeSelector把Ingress Controller进行独立调度,只对外暴露这几台服务器作为流量入口。这里要特别注意客户端的短连接问题,因为短连接到达service所在服务器后,会再次向后端发起一次流量转发,无论是使用iptables实现的还是IPVS的都一样,向后传递短连接后,外部一个短连接,到集群里就变成两个短连接了。所以要么增大nf_conntrack相关参数的容量,要么把短连接变成长连接,避免出现链接跟踪表溢出的问题。

使用podAntiAffinity的必要性

使用podAntiAffinity主要是为了避免单点故障。因为即使在集群中部署了多个实例,如果所有实例都在一台服务器上,因为服务器故障或者某些原因触发驱逐策略的时候,依旧会引发短期的服务不可用。

未来的方向

两个方向吧,一个是平台化,另一个是资源进行精细化调配。

先说平台化,因为目前绝大部分业务都已经容器化了,内部已经有了多个线上集群,急需一个集中的平台来把多个集群管起来、各个流程串起来。平台化的内容主要是监控、日志、发布和集群管理这些。资源的精细化调配方面,是因为目前我们的调度策略主要还是利用原生的调度策略,小部分利用CPUset做绑核操作。但是整个集群的内存利用率和CPU使用率并不是在一个水平线上,这方面还有比较大的可压缩空间。

Q&A

Q:请问LNMP网站架构容器化上Kubernetes、Nginx和PHP,是各设Pod吧?MySQL采用一主多从架构?用什么做存储?

A:LNMP建议是Nginx和PHP放在同一个Pod中,然后外层再加一个Nginx,做Upstream。MySQL应用上Kubernetes要用StatefulSet。不过我们目前内部这一块是还没有迁移到Kubernetes集群中的。

Q:Pod的滚动更新(优雅重启)怎么做的?

A:滚动更新Kubernetes的deployment中有相关的rollingUpdate策略,优雅停机有两个注意的点,一个是我们要确保应用发的SIGTERM信号,另一个是代码要确保收到SIGTERM信号后优先关闭端口或者取消服务注册。

Q:Harbor的使用版本?镜像的后端存储是什么?Harbor的部署形态?

A:Harbor使用版本这里不方便说。不好意思哈。镜像后端存储我们目前用的是腾讯云的CFS,性能上目前够用。Harbor部署形态,我大概描述一下:ng/lb反向代理到后端Harbor,Harbor存储用腾讯云的CFS。

Q:除开Kubernetes自身的监控,容器内部服务监控是如何做的?pinpoint-agent嵌入到Container里面有没有需要优化的地方?

A:容器内部服务监控我们是用Pinpoint做APM监控。pp-agent嵌入到Container里边有没有需要优化的地方,这个还好,因为我们目前这一块都是做在基础镜像里边的,启动的时候加启动参数就好,步骤也挺简单的。

Q:请问前端是怎么暴露在外面的,SSL证书怎么才能做到自动签发?

A:前端静态资源暴露我们有两种吧。一种是直接放在ng静态资源目录里边然后加上CDN,另外一种是放在Kubernetes集群中,前面Nginx做反向代理,配上静态资源相关的缓存策略。SSL证书自动签发,Kubernetes集群内我们目前用的是托管类型的,所以这一块未涉及。集群外其他SSL证书未做自动签发的证书。

Q:GitLab接收一个push event触发构建,这个是监控所有的分支吗,分支模型是怎么样的?

A:不是的,按需。我们内部分支模型大概有四种,dev——>test——>release——>master。master以外的为了效率都会做自动触发。

Q:请问规模是?大概多少Node,会跨很多集群吗?

A:我们内部的集群规模,生产集群数量是4个,不过是有大有小,是按业务来划分的。最大的一个集群是2700G内存,Node节点将近50个。Node节点数量要根据每个节点配置来看,配置高,节点数量就少。

Q:为什么不直接用GitLab-Runner而接Jenkins?

A:GitLab-Runner需要每个仓库都配置构建信息,当需要统一修改构建的时候很麻烦。

Q:“Pod已实现与Node节点在同一网络层面,促进了我们后面的快速落地。”这个是什么需求?

A:原生的Kubernetes集群跨集群调用网络上默认是不通的。

Q:请问Dubbo在转向Kubernetes或Istio网格化改造过程中,传统环境与容器化环境共存互相调用时,是否遇到什么坑?如何化解的?

A:网段冲突吧。我们曾经做过一段时间的Docker单机,但是当时没有配置Docker的网段,导致后面冲突的时候出现网络不通。解决方案:指定Docker单机的网段。

Q:在CD的过程中 ,版本管理是怎么做的 ?是基于镜像的tag还是Helm的版本?包括回滚的策略与方式?

A:用的是Helm的版本管理,回滚策略和方式都是用Helm的。

Q:HPA具体是咋做的,根据什么指标扩缩容的?集群升级咋做的?

A:HPA是根据CPU做的。集群升级是先升级Master再升级Node节点,这个还是按照TKE的来做。

Q:Ingress Controller节点上的短连接问题,短连接变长连接这个是怎么处理的?能提示一下吗?

A:这个不是指通Ingress Controller去做短连接变长连接,是说客户端发起的连接要用长连接的意思。

以上内容根据2020年3月17日晚微信群分享内容整理。 分享人 林小洁,国内某知名婚恋网站应用运维工程师 。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesf,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。