前言:
本文由John Patterson 、 Chris Lunsford写于2016年4月4日,译者有容云张向波,转载请注明出处。(原文链接见文末)
作者John Patterson和Chris Lunsford 运营了一家提供运营和基础架构服务的公司,本文是他们给大家分享的内容:关于如何通过使用Docker、Docker-Compose和Rancher来实现容器部署落地。
我们想跟你一起从头开始体验整个过程,特别是之间遇到的一些痛点和所做的决策。目前,已经有许多的资源和工具可以与Docker一起配合来搭建持续集成和部署工作流程。搭建一个部署工作流程较简单,但是搭建一个部署系统相对比较复杂,因为很多相关的工作流程在已有的环境中已经实现,并且存在很多依赖关系,同时还要让开发和运维部门启用新的工作流程。希望我们在搭建部署系统中的经验能够帮您跳过项目中的那些坑。
在这篇文章中,我们将回顾最初只使用Docker来构建的工作流程。随后的几篇文章中,我们将进一步通过Docker-Compose和Rancher来一起构建工作流程。
以下事件发生在我们长期服务的一家SaaS服务商,接下来我们称这家公司为Acme Business Company或者ABC。这个项目始于ABC着手将大部分Java微服务从自建机房的服务器迁移到AWS的Docker环境中。项目目标主要是降低未来上线周期和拥有更高的应用部署服务可靠性。
实现应用部署的计划如下所示:
整个流程从代码更改、提交、推送到Git仓库开始,这将通知我们的持续集成(CI)系统运行单元测试,单元测试通过的话,编译代码并作为构件保存。如果这步成功通过的话,将触发新任务,通过代码构件来生成Docker镜像并推送到Docker私有镜像仓库。最后,我们将镜像部署到环境中。
必需组件如下:
乍看起来,整个流程还是挺简单的,而实际上并没那么简单。和大多数其他公司一样,ABC以前(现在也是)开发和运维是分开的。当代码准备好部署时,一个包含应用详细信息和目标环境的工单(ticket)将被创建。这个工单将指派给运维部门,并安排在当周的部署维护窗口进行上线。这种情况下,持续部署和交付的方式是不清晰的。
起初,应用部署的工单可能如下:
部署过程:
这种部署过程并不引人注目,却是持续部署中的第一步,还有很多地方需要改进,先权衡一下收益:
痛点:
鉴于此,我们着手对这个问题进行解决。
第一步,将通用的部署过程写成bash脚本,例如:
这个例子只对最简单的容器生效:用户不需要和容器进行直接交互。我们需要添加特定的应用逻辑来启用主机端口映射(host port mapping)和卷挂载(volume mount)。如下:
这个脚本放在所有的Docker主机上来优化部署,运维工程师登录主机,传递参数给脚本,然后脚本负责剩下的工作。部署时间因此缩短,但还是需要手动编写部署脚本的逻辑。此外,有一个新问题,当对通用脚本进行更新后,如何将更新的脚本应用到所有的Host上。通常来说,利用仓库来进行代码和脚本管理有以下优势:代码审查、测试、变更记录、复用。需要人为干预的越少越好。
理论上,部署应用的脚本可以与应用存储在同一个代码仓库中,但实际中很难,最重要的一点就是开发会反对将运维相关的东西放在他们的Java代码仓库中。特别是一些bash部署脚本也包括Dockerfile本身。
这可以归结为一个不同部门的文化问题,如果可能的话,还是值得去尝试的。虽然也可以把部署代码或脚本单独存放在其他仓库中进行维护,但需要额外搞定应用代码和部署代码的同步问题。我们在此将以后面这种方式作为例子。在ABC,每一个项目会单独创建一个文件夹在独立的代码仓库中来存放Dockerfile,部署脚本放在自己的目录下。
Dockerfiles仓库会迁出一份副本到Jenkins主机的公共目录下(如:”/opt/abc/Dockerfiles”)。在构建Docker镜像之前,Jenkins会检查本地“docker”文件夹下是否存在Dockerfile。如果不存在,Jenkins会搜索Dockerfiles路径,拷贝Dockerfile和脚本,再通过“docker build”构建镜像。由于Dockerfile放在Master分支,有可能Dockerfile在某些情况下会超前(或落后)于应用程序配置,实际生产中,这在大多数情况下,不会造成问题。下面是从Jenkins 构建逻辑摘录来的:
渐渐的,Dockerfile和对应的支持脚本被整合到应用的源代码仓库中。由于Jenkins优先在本地仓库中进行查找,所以在整合过程中不需要进行额外的其他配置更改。在迁移完第一个Java应用后,仓库拓扑如下:
我们在将Dockerfile和脚本存储在独立的仓库时,遇到的一个问题是,当应用源代码或者部署脚本发生变更时,都会触发Jenkins重新构建镜像。由于Dockerfiles仓库包含了多个项目的代码,所以在对某一个Dockerfile进行更新时,不希望触发所有项目重新构建。解决方法:一个比较隐蔽的Jenkins配置项 Git plugin - Included Regions。 当启用这个选项后,只有在仓库中特定的文件夹发生变化时,才会触发镜像构建。这使得我们可以将所有的Dockerfile保存在一个仓库,只有当特定的内容在代码库中更新后触发特定的Docker 镜像构建。(相比之前触发所有的项目重新构建)。
另一个问题是运维工程师必须在部署之前进行应用的镜像构建,这会导致上线周期的延长,特别是在镜像构建中出错,需要开发介入。为了减少这种情况,更好的支持持续部署,我们定义每一个提交的大分支版本都触发镜像构建。这要求每一个镜像拥有唯一的版本标识而不只是依靠应用程序的版本标识字符串。我们最终采用应用版本字符-提交次数-提交SHA值:
最终显示的版本字符串如:1.0.1-22-7e56158。
在结束我们这次Dockerfile内容的讨论之前,还有一些关键决策值得我们注意。在大规模使用容器之前,我们基本对此一无所知,这些关键点对容器集群的稳定运行有着重要的作用。
容器策略和资源配置的协同配合将会带来更高的容器集群稳定性,同时降低故障影响,缩短故障恢复的时间。实践过程中,有了这种保护机制,以往被用来故障恢复的时间,现在用在和开发团队一起进行故障根本原因排查。
小结
我们最初从源代码库构建包含标识符的容器镜像,接下来通过Docker 命令行工具写成的脚本和参数来部署容器。同时梳理了我们环境中的容器部署代码逻辑,强调了那些对有助于运维部门保持服务稳定运行的一些关键决策。
当前这个阶段,镜像构建和部署之间依然存在欠缺。部署维护工程师需要手动登录到服务器执行部署脚本。我们已经比最初的时候前进了一大步,但还有很多内容可以自动化。当前所有的部署逻辑集中在一个脚本中,当开发人员需要安装调试脚本,理清脚本逻辑变得很复杂。目前我们的脚本中也定义了许多环境参数来引入环境参数,查找某个环境变量对应的服务和添加新的环境变量是相对繁琐并容易混淆。
在 下一篇文章,我们将通过解构通用的封装脚本,对我们是如何解决这些痛处一探究竟,介绍如何使用Docker Compose让部署逻辑更贴合应用。
对Docker容器技术或容器生产实施感兴趣的朋友欢迎加群454565480讨论。我们汇集了Docker容器技术落地实施团队精英及业内技术派高人,在线为您分享Docker技术干货。我们的宗旨是为了大家拥有更专业的平台交流Docker实战技术,我们将定期邀请嘉宾做各类话题分享及回顾,共同实践研究Docker容器生态圈。