实践 | 一个案例思考容器落地的山高路远坑深

[编者的话] 容器是这两年最热的一个话题,去年大家都在谈 Mesos 、 Kubernetes 、 Swarm ,究竟哪家的挖掘技术强,今年容器技术的进一步普及,更多的人更关心容器技术如何落地,下面我们就基于一个实际的案例来聊一下容器落地遇到的问题。

背景:某银行数据中心计划搭建一个基于容器的 PaaS 平台

持续集成

持续集成是容器一个绕不过去的话题,无论哪家容器厂商都一定会谈到,数人云关于持续集成,最开始用的是 drone ,一个小众的持续集成工具,将 drone 内置在平台上,通过平台的持续集成功能可以方便的实现持续集成的配置和管理。

drone 的坑

一开始我们觉得这是一个很好的工具,但是后来发现其实没有想象中的那么美好,它的问题:

  • 对 SVN 的支持不好
  • 容易出问题,因为数人云平台所有的组件都是容器化的,所以若使用 drone ,则需要使用 docker-in-docker 技术,但是该技术已经是一个不再被维护的技术了,所以继续使用的风险很大。

Jenkins 是个好工具

Jenkins 是一个好工具,功能强大且稳定,基于 Jenkins 实现的持续集成基本没有花费什么开发的时间,通过脚本将代码构建和平台连接在一起即可轻松实现 CI/CD 。

总结

往往用户需要的并不是那些看起来很酷的功能,真正需要的是能够实际解决问题的方案,即使这个方案很 low 。

配置管理

在我看来,容器是一个革命性的产物,改变了软件交付的方式,它开箱即用的特性消灭了程序员常说的一句话 “在我这里运行时正常的啊!”; 它快速部署,环境无关的特性帮助运维人员提高了工作效率,但是任何事情都有其两面性,它的开箱即用,环境无关带来好处的同时,也带来了问题——配置文件。

传统应用在容器时代面临的第一个问题

一般而言,每个程序都会有一个或多个配置文件,里边记录着 DB 地址、账号、密码、缓存地址等信息,在容器时代之前,应用程序一般的流转方式是“从开发->测试->生产”:

  • 开发同学交付一个编译之后的二进制文件,源文件(解释执行)或者代码仓库中某一个 tag ,同时附带一个 release notes ;

  • 测试同学拿到开发同学交付的内容后,就将其部署在自己的测试环境中,如果 release notes 中说明了有配置信息需要更新或依赖文件版本需要升级,会依照文件进行调整;

  • 如果测试通过,确定可以投产,那么就将其交付给运维同学进行生产部署。

此时有一个问题,开发、测试、运维每个环节都会自己维护配置文件,如果使用了容器,那么配置文件管理就是很麻烦的问题了,因为配置文件被放到了容器里,若想修改配置文件就不是那么简单的事情了,所以这就是传统应用在容器化时面临的第一个问题,当然这个问题也不是不能解决,一般而言,有以下几种解决方案:

  • 挂盘,将配置文件放到外部存储中,然后将该目录挂到容器中;

  • 生成新的镜像,基于 Docker 文件系统的特性,使用测试环境的配置文件覆盖开发环境的镜像,从而得到测试环境的镜像,同理,使用生产环境的配置文件覆盖开发环境的配置文件得到生产环境的镜像,使用该方案会导致每个环境都有一个镜像。

容器时代配置管理的正确打开方式

以上分析了传统应用容器化基本都会遇到的一个问题,而且也没有给出很好的解决方案,下面我们来谈下容器化时代配置管理的正确打开方式。

其实通过上面问题的描述,我们可以很容易的找到问题的根本原因:配置文件本身是一个本地存储状态,要对其做无状态改造,对于配置管理的无状态改造一般是通过配置中心来完成的,即应用通过配置中心获取配置信息,无需读取本地配置文件,但是这个问题更麻烦,要解决这个问题需要首先解决两个问题:

  • 要先有个配置中心

  • 要改代码,使其可以适配配置中心

随着容器的普及,未来配置中心肯定会逐渐成为程序的标配。

最终选择的解决方案

关于容器时代配置文件的问题,上边大概提到了 3 种方案,在最终落地的时候选择的是哪一种呢?答案是第二种——生成新的镜像。这是一个很 low 的方案,为什么没有选择另外两种呢? 我们来解释下原因:

  • 方案一[挂盘], 这个方案会给容器产生另外一个状态,外部文件,不符合 cloud 的思想, pass ;

  • 方案三[配置中心],成本太高,周期太长,而且需要改代码,往往之前的应用已经被维护了很多年,修改其配置接口,风险太大。

总结

虽然这个选择从技术上来看很 low ,但是个人认为,使用一个不太优雅的方案帮助一个新技术快速落地,然后推动其快速前进,比一直纠结于方案是否高大上,是否优雅等,而导致新的技术一直被悬在空中更好,就像大家一直在争论 Mesos 、 Kubernetes 、 Swarm 究竟哪个更好,现在也没有一个结论,与其争论这么多虚的,不如仔细想一下自己的问题是什么,究竟哪个技术更适合自己。

日志

目前使用 ELK 作为日志方案。

传统应用的坑

一般而言,传统的应用都是把日志写到一个指定的路径下,然后通过 Logstash 采集日志并送入 Elasticsearch 进行存储,但是这种应用如果直接容器化之后就会带来一个问题——应用的日志文件应该如何存储。

  • 方案一:放到容器里

  • 方案二:挂盘,写到外部存储上

两种方案都有一些问题:

  • 放到容器里,逻辑上最简单,不需要做任何改动,但是它的问题是,怎么从容器中把日志取出来。

  • 通过挂盘,把容器日志写到外部存储,然后沿用传统的 Logstash + ES 的方式处理日志,听起来很美好,但是如何建立容器和日志的对应关系?

容器时代日志的正确打开方式

按照之前的惯例,我们先提出在容器时代,日志的正确处理方式,如果应用使用 Docker 进行交付,不建议写日志文件,直接将日志写入标准输出即可,然后 Logstash 通过 Docker 的 log-driver 捕获日志,这样日志文件既不需要落盘,也使日志文件摆脱了状态,可以更容易的横向扩缩。

最终选择的解决方案

最终我们实现的是方案三,因为用户在我们的建议下,选择了将日志输出到标准输出。

新的问题

写日志的目的是为了看的,这是一个无比正确的废话,但是如何实时的看到需要的日志却成了我们面临的一个新问题。在容器时代之前,我们一般是通过 tail 来实时的看日志或者通过 Kibana 来分析日志。

在容器的时代,通过 Kibana 看日志的方式没什么变化,但是看实时日志就有了一些问题,在用户采取了我们的建议将日志写入标准输出后可以比较优雅的处理日志了,但是另外一个问题出现了,实时日志没有了,因为日志已经被 log-driver 收走了,怎么办?虽然依然可以从 ES 中找到日志,但是由于 ELK 本身的机制,不能通过 ELK 看到实时日志。

新的解决办法

新的问题,需要新的办法来解决,如何解决,其实说穿了也简单,实现了一个 log-agent 的功能,可以将日志送到两个地方, Logstash 和实时日志。

总结

事情就是这样,我们以为解决了一个问题的时候,往往一个新的问题又摆在前面,像容器落地这种事情,对传统应用的整体工作方式和流程都有很大的冲击,所以一定也会遇到同等规模的问题需要解决。

监控

银行对监控是非常重视的,尤其是运维部门,所以为了满足客户的需求,我们实现了:

  • 平台自身监控

  • 宿主机监控

  • 中间件监控

监控本身是我们平台很重要的一个部分,但是在实际实施过程中发现,客户其实不是很在意做的监控页面,仪表盘等监控数据,他们自身有健全的监控平台,其实我们需要做的事情就是将我们的数据吐到他的平台上即可。

总结

世事难料,你永远不知道下一块巧克力什么味道,这个我们自身投入了很大精力和时间来实现的功能在用户那里就变成了一个对接的任务。

弹性扩缩

弹性扩缩一直是容器厂家喜欢谈的一个口号,曾经有一度我们认为基于 Docker 的特性来实行弹性扩缩是一件很容易的事情,但是后来发现,这里其实有一个大坑。

扩很容易,缩很难

弹性扩容是一件比较容易的事情,我们只要对接监控数据,然后配置一些规则,即可触发应用容器个数增加,实现扩容,但是缩容就会面临几个问题:

  • 什么时候缩容?

  • 如何安全的缩容?

什么时候缩容,这个问题相对来说还不是特别麻烦,可以设定一个指标,比如 CPU ,内存, IO 等指标来触发缩容,这里只要做一些带有缓冲的规则,避免由于规则简单而导致的扩缩抖动即可。

如何安全的缩容相当麻烦,缩容在本质上是要停掉一些容器的,如何判断这个容器是可以停止的,如果一个容器没有流量了,那么应该是可以被停止的,如何让一个容器的流量停止?可以通过前端负载进行控制,不往这个容器分发流量,那么前端的负载是如何判断应该如何往后端分发流量呢?这个有多重因素:

  • 自身算法

  • 应用程序本身是有状态的,需要保持会话

从以上简单的分析中我们可以发现,缩容不是简单的条件符合了就可以做的事情,要想在不影响业务的情况下实现缩容,是一件非常麻烦的事情,它与平台架构,运行的程序的业务逻辑有很深的耦合。

总结

要实现自动的扩缩容,不是一件简单的事情,而是一个系统的工程。

以上内容是基于数人云在某银行实施过程中总结出来的一些感悟,如果能给大家一些帮助,我们非常高兴,如果有问题,请指出来,我们共同提高, Docker 到现在大概有 3 年多的历史,个人看来它距离真正落地还有很大一段路程要走,我们必须不停的摸索。

Q&A

Q :我想问一下,日志打两份的话具体是怎么实现的呢,用到了哪些技术或现有的工具呢?

A :我们自己实现了一个 log-agent, 然后 log-agent 可以实现这个功能。

Q :如果应用有自己的写的日志,如 log4j 的,输出不到标准输出,还怎么处理?

A : log4j 貌似是可通过配置输出到标准输出的,另外如果有些应用不能输出到标准输出的,可以配置日志文件路径,我们会去读文件。

Q :缩容的产生条件是否有比较好的解决方案,比如根据 CPU 、内存甚至业务规则多维度的进行考察?

A :缩容很容易,但是麻烦的是如何安全的缩容,我理解这个环节其实是跟应用的逻辑有直接关系的,如果应用是一个无状态的应用,那么缩容非常简单,只需要在前端控制流量,然后停止容器即可,但是如果是有状态的应用,那么就有可能对用户造成影响。

Q :配置管理这块,不断的覆盖会增加镜像体积,如何最大化减少镜像大小呢?

A :首先,一个镜像最多被覆盖 2 , 3 次,测试镜像一次,生产镜像一次,而且配置文件一般是很小的,几乎对镜像大小没有影响。

Q :测试环境配置文件覆盖开发环境镜像,是只用测试环境的 docket file 吗? 如果每天打版,会很麻烦吗?

A :通过覆盖测试文件来解决环境问题,只是一个思路,不一定非要使用开发测试环境的信息,这个可以具体情况具体分析。

Q : log-agent 具体实现呢,日志直接打给 log-agent 还是 log-agent 读取本地日志文件呢?或者说 log-agent 读取标准输出的内容呢?

A : log-agent 可以通过 Docker 的 log-driver 获取标准输出的日志,同时也可以直接读取日志文件的日志。

Q :配置 ENV 化完全可以由运维来实现,容器的启动交由脚本来执行,然后在脚本中来读取所有的 ENV 并修改应用,完成后再启动应用,这样就只需要来维护脚本了。

A :是个好主意,但是我们当时考虑配置文件覆盖这个方案的时候,是基于开发人员不对代码做任何修改的思路来考虑的。

Q :容器生命周期很短?如何做到动态监控?你们具体监控了哪些重要指标?谢谢。

A :我们监控用的是 Prometheus 方案,监控做了 主机,容器,中间件 几个大的范围。