DockOne微信分享(一八九):有赞容器化实践


【编者的话】容器化已经成为一种趋势,它可以解决很多运维中的痛点,比如效率、成本、稳定性等问题,而接入容器的过程中往往也会碰到很多问题和不便。在有赞最开始做容器化是为了快速交付开发测试环境,在容器化的过程中,我们碰到过容器技术、运维体系适配、用户使用习惯改变等各种问题,本文主要介绍有赞容器化过程中碰到的问题以及采取的方案。

有赞容器化的初衷

在有赞同时会有很多个项目、日常在并行开发,环境的抢占问题严重影响了开发、测试和上线的效率,我们需要给每个项目提供一套开发联调(Daily)、测试环境(QA),并且随着项目、日常的生命周期项目环境也会随着创建和销毁,我们最早的容器化需求就是怎么解决环境快速交付的问题。

有赞环境:
1.png

上面是有赞大致的研发流程,在标准流程中我们有四套稳定环境,分别是Daily环境、QA环境、预发环境和测试环境。我们的开发、测试、联调工作一般并不会直接在稳定环境中进行,而是会拉一套独立的项目环境出来,随着代码经过开发、测试、预发验收最终发布到生产环境后再同步回Daily/Qa的稳定环境中。

项目环境:
2.png

我们提供了一套以最小的资源投入满足最大项目并行度的环境交付方案,在Daily/QA稳定环境的基础上,隔离出N个项目环境,在项目环境里只需要创建该项目所涉及应用的计算资源,其它缺失的服务调用由稳定环境提供,在项目环境里,我们大量使用了容器技术。

持续交付:
3.png

后面我们又在项目环境快速交付的解决方案的基础上实现了持续交付流水线,目前已经有超过600套项目/持续交付环境,加上Daily/QA稳定环境,涉及计算实例四五千个,这些计算实例无论是CPU还是内存使用率都是非常低的,容器化可以非常好的解决环境交付的效率问题,以及提高资源使用率来节省成本的投入。

有赞容器化方案

我们的容器化方案基于Kubernetes(1.7.10)和Docker(1.12.6)、Docker(1.13.1),下面介绍一下我们在各个方面遇到的问题以及解决方案。

网络

有赞后端主要是Java应用,采用定制的Dubbo服务化方案,过程中无法做到整个单元全量容器化,和原有集群在网络路由上互通也就成了刚需,由于我们无法解决公有云上Overlay网络和公有云网络的互通问题,所以一开始我们放弃了Overlay网络方案,采用了托管网络下的Macvlan方案,这样既解决了网络互通的问题也不存在网络性能问题,但是也就享受不到公有云弹性资源的优势了。随着有赞多云架构的发展以及越来越多的云厂商支持容器Overlay网络和VPC网络打通,弹性资源的问题才得到了缓解。

隔离性

容器的隔离主要利用内核的Namespace和Cgroup技术,在进程、CPU、内存、IO等资源隔离限制上有比较好的表现,但其他方面和虚拟机相比存在着很多的不足,我们在使用过程中碰到最多的问题是容器里看到的CPU数和内存大小不准确,因为/proc文件系统无法隔离,导致容器里的进程"看到"的是物理机的CPU数以及内存大小。

内存问题

我们的Java应用会根据服务器的内存大小来决定JVM参数应该怎么配置,我们是采用LXCFS方案来规避的。

4.png


CPU数的问题

因为我们有超卖的需求以及Kubernetes默认也是采用cpu share来做CPU限制,虽然我们使用了LXCFS,CPU数还是不准的。JVM以及很多Java SDK都会根据系统的CPU数来决定创建多少线程,导致Java应用在线程数和内存使用上都比虚拟机多的多,严重影响运行,其他类型的应用也有类似的问题。
我们会根据容器的规格内置一个环境变量NUM_CPUS,然后比如Nodejs应用就会按照这个变量来创建它的worker进程数。在解决Java类应用的问题时,我们索性通过LD_PRELOAD将JVM_ActiveProcessorCount函数覆盖掉,让它直接返回NUM_CPUS的值。

应用接入

在容器化之前,有赞的应用已经全部接入到发布系统,在发布系统里已经标准化了应用的打包、发布流程,所以在应用接入方面成本还是比较小的,业务方无需提供Dockerfile。


1. Nodejs、Python、php-soa等用Supervisord托管的应用,只需要在Git仓库里提供app.yaml文件定义运行需要的runtime和启动命令即可。
5.png

2. Java标准化启动的应用业务方无需改动
。
3. Java非标准化的应用需要做标准化改造 。

镜像集成

6.png


容器镜像我们分了三层,依次为stack层(os),runtime层(语言环境),应用层(业务代码和一些辅助agent),应用以及辅助agent由runit来启动。由于我们的配置还没有完全分离,在应用层目前还是每个环境独立打包,镜像里除了业务代码之外,我们还会根据业务的语言类型放一些辅助的agent。我们一开始也想将各种agent拆成多个镜像,然后每个Pod运行多个容器,后来因为解决不了Pod里容器的启动顺序(服务启动有依赖)问题,就把所有服务都扔到一个容器里去运行了。
7.png


我们的容器镜像集成过程也是通过Kubernetes来调度的(会调度到指定的打包节点上),在发布任务发起时,管控系统会在集群中创建一个打包的Pod,打包程序会根据应用类型等参数编译代码、安装依赖,并且生成Dockerifile,然后在这个Pod中使用Docker in Docker的方式来集成容器镜像并推送到仓库。
为了加速应用的打包速度,我们用PVC缓存了Python的Virtualenv,Nodejs的node_modules,Java的maven包等文件。另外就是Docker早的版本里,Dockerfile ADD指令是不支持指定文件属主和分组的,这样会带来一个问题就是需要指定文件属主时(我们的应用是以app账号运行的)需要多运行一次RUN chown,这样镜像也就多了一层数据,所以我们打包节点的docker版本采用了官方比较新的ce版本,因为新版本支持ADD --chown特性。

负载均衡(Ingress)

8.png


有赞的应用内部调用有比较完善的服务化和Service Mesh方案,集群内的访问不用过多考虑,负载均衡只需要考虑用户和系统访问的http流量,在容器化之前我们已经自研了一套统一接入系统,所以在容器化负载均衡上我们并没有完整按照Ingress的机制来实现controller,Ingress的资源配置是配在统一接入里的,配置里面转发的Upstream会和Kubernetes里的service关联,我们只是做了一个Sync程序watch kube-api,感知service的变化来实时更新统一接入系统中Upstream的服务器列表信息。

容器登录和调试

9.png


在容器化接入过程中开发会反馈是控制台比较难用,虽然我们优化了多次,和iterm2等的体验还是有所不足,最终我们还是放开了项目/持续交付环境这种需要频繁登陆调试的SSH登陆权限。
另外一个比较严重的问题是,当一个应用启动后健康检查有问题会导致Pod一直在重新调度,而在开发过程中开发肯定是希望看到失败现场的,我们提供了调试发布模式,让容器不做健康检查。

日志

10.png


有赞有专门的日志系统,我们内部叫天网,大部分日志以及业务监控数据都是通过SDK直接打到天网里去了,所以容器的标准输出日志仅仅作为一种辅助排查问题的手段。我们容器的日志收集采用的是Fluentd,经过Fluentd处理后按照天网约定的日志格式打到Kafka,最终由天网处理进入ES做存储。

灰度发布

我们涉及到灰度发布的流量主要包含三部分:
  1. 用户端的http访问流量
  2. 应用之间的http调用
  3. 应用之间的Dubbo调用

    首先,我们在入口的统一接入上统一打上灰度需要用的各种维度的标签(比如用户、店铺等),然后需要对统一接入、http client以及dubbo client做改造,目的是让这些标签能够在整个调用链上透传。我们在做容器灰度发布时,会发一个灰度的deployment,然后在统一接入以及灰度配置中心配置灰度规则,整个链路上的调用方都会感知这些灰度规则来实现灰度发布。


标准环境容器化

标准环境的出发点

  1. 和项目环境类似,标准稳定环境中的Daily、QA、Pre以及Prod中超过一半运行在低水位的服务器的资源非常浪费。
  2. 因为成本考虑Daily、QA、Pre里都是以单台虚拟机运行的,这样一旦需要发布稳定环境将会造成标准稳定环境和项目环境的短暂不可用。
  3. 虚拟机交付速度比较慢,使用虚拟机做灰度发布也比较复杂。
  4. 虚拟机往往会存在几年甚至更长的时间,运行过程中操作系统以及基础软件版本的收敛非常麻烦。


标准环境容器化推进

经过之前项目/持续交付的上线和迭代,大部分应用本身已经具备了容器化的条件。不过对于上线来说,需要整个运维体系来适配容器化,比如监控、发布、日志等等。目前我们生产环境容器化准备基本完成,生产网已经上了部分前端Nodejs应用,其他应用也在陆续推动中,希望以后可以分享更多生产环境中的容器化经验。

结束语

以上是有赞在容器化上的应用,以及在容器化过程中碰到的一些问题和解决方案,我们生产环境的容器化还处于开始阶段,后面还会碰到各种个样的问题,希望能够和大家互相学习,后面能够有更多的经验分享给大家。

Q&A

Q:你们的上线审批流程是怎样的?

A:我们主要是定义了发布窗口、发布次数等限制,如果在限制以内的,只需要走普通的发布审批流程,否则走紧急发布流程。
Q:在容器内打镜像怎么避免用户把仓库账号信息打印出来?

A:Clone代码所使用的私钥是通过Pod的环境变量下发的,这里主要是把镜像集成分成多个步骤,我们会在最开始Clone完代码后就将所有敏感信息清理掉了。
Q:网络方面可以详细讲一下,没有遇到问题吗?

A:网络方面因为我上面说的原因,我们一开始就是Macvlan的方案,ipam都是本地配置文件,这样风险确实是最低的,性能也很不错。后面我们随着有赞多云架构,也在使用QCloud的容器服务,当然这里的网络主要还是云厂商解决的。
Q:请问,你们是如何衡量应用容器发布之后的效率改进的?单纯看用户体验,还是有类似发布效率改进这类的指标?

A:容器化效率提升其实最主要的还是在开发测试环节,比如很多项目中都是push完代码就开始自动CI、部署、测试等过程的,我们在持续交付里会有很多指标的产出作为参考。
Q:应用监控方案呢?Docker内基础监控的采集、上报、存储和展示方案,能否分享下?

A:监控主要用的还是Prometheus,主要包括采集容器的基础Metrics数据、kube-state-metrics数据以及后续Java框架/Nodejs框架统一输出的Metrics数据。这些监控数据会和容器之前的所有监控数据统一到“天网”中,展示是我们自己定制的,实现在我们的基础保障平台里面。
Q:之前看过有赞SC环境的文章,里面Dubbo的路由规则是否针对环境的逻辑隔离做过改造?关键实现的点是哪些?

A:是的,我们确实改造过整个调用链里的每个环节,会全链路透传一个环境标签,以及我们灰度实现的方案也是基于这个。
以上内容根据2018年9月25日晚微信群分享内容整理。分享人王波,十年运维老兵,现担任有赞运维平台负责人,负责有赞基础保障平台的建设,面向有赞开发、测试和运维提供涵盖应用生命周期管理、项目研发生命周期管理(持续交付)等功能的DevOps一站式服务。DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesd,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

0 个评论

要回复文章请先登录注册