专栏介绍
“高效运维最佳实践”是 InfoQ 在 2015 年推出的精品专栏,由触控科技运维总监萧田国撰写,InfoQ 总编辑崔康策划。
前言
关于 Docker 的文章铺天盖地,但精品文章往往翻译居多。都说 Docker 天生适合持续集成 / 持续部署,但同样,可落地、实际可操作性的文章也很罕见。
基于这些情况,虽然我们专栏定位为运维管理性文字,但本篇是个特例,实操性的案例讲解——JAVA 项目如何通过 Docker 实现持续部署(只需简单四步),即:
开发同学通过 git push 上传代码,经 Git 和 Jenkins 配合,自动完成程序部署、发布,全程无需运维人员参与。
这是一种真正的容器级的实现,这个带来的好处,不仅仅是效率的提升,更是一种变革:
开发人员第一次真正为自己的代码负责——终于可以跳过运维和测试部门,自主维护运行环境(首先是测试 / 开发环境)。
难者不会,会者不难。通过简单的 4 个配置,即可优雅地实现持续部署。本文依惯例放上目录,请享用。
- 持续部署的技术思路
- 效果展示
- 配置 Git 和 Jenkins 联动
- 配置 Jenkins 自动更新代码
- 效果图文详解
- FAQ
好吧,我们正式开始。
1. 持续部署的技术思路
在本例中,假设我们 JAVA 项目的名称为 hello。简要的技术思路如下。
本案例中假设代码托管在 git.oschina.com 上,Jenkins 和 Docker Registry(类似于 yum 源)各运行在一个 Docker 容器中。JAVA 项目自己也单独运行在一个叫 hello 的容器中。
本文采取的持续部署方案,是从私有的 Docker Registry 拉取代码。有些变通的方案,把代码放在宿主机上,让容器通过卷组映射来读取。这种方法不建议的原因是,将代码拆分出容器,这违背了 Docker 的集装箱原则:
这也导致装卸复杂度增加。从货运工人角度考虑,整体才是最经济的。这样,也才能实现真正意义的容器级迁移。
或者说,容器时代,抛弃过去文件分发的思想,才是正途。本文最后的问答环节对此有更多阐述。
容器即进程。我们采用上述方案做 Docker 持续部署的原因和意义,也在于此。容器的生命周期,应该远远短于虚拟机,容器出现问题,应该是立即杀掉,而不是试图恢复。
2. 效果展示
本文最后实现的效果,究竟有多惊艳呢?且看如下的演示。
2.1 程序代码更新前的效果
我们以时间戳来简洁、显式的表述程序更新情况。
2.2 提交程序代码更新
本例中,我们把首页的时间戳从 201506181750,修改为 201506191410(见如下)。
2.3 上传新代码到 Git
顺序执行如下操作,输入正确的 git 账号密码。
然后呢?
然后什么都不用做了。端杯茶(如果不喜欢咖啡的话),静静地等待自动部署的发生, 旁观一系列被自动触发的过程,机器人似的运转起来(请容稍候再加以描述)。
为什么需要 3~5 分钟?只是因为本案例中的 JAVA 项目,需要从国外 download Maven 程序包,以供 Jenkins 调用和编译 JAVA。正式应用环境中,可以把 Maven 源放在国内或机房。如果仅仅需要对 PHP 项目做持续部署,那就更快捷了。
2.4 查看代码更新后的效果
在静静地等待几分钟后,新的代码确实已经自动部署完毕。
那么,这一切怎么实现的呢?很复杂么?不然。只要按照如下几步,便可快速实现哦。
3. 配置 Git 和 Jenkins 联动
这个过程也是难者不会,会者不难。主要分为如下三步。
3.1 Jenkins 配置 Git 源
Jenkins 中新建项目 java-app,并配置从 Git 拉取程序代码。具体如下:
3.2 Jenkins 配置远程构建
Jenkins 中配置 token,以供 git 远程调用时使用。
3.3 Git 开启钩子
怎么让 Git 在接收到用户更新的代码后,把消息和任务传递给 Jenkins 呢?这借助于 Git 的 hook 功能,配置起来也非常简单,如下。
4. 配置 Jenkins 自动更新代码
Jenkins 的主要工作是配置“远程构建”。在接收到 Git 传递过来的消息后,触发这个远程构建(到目标服务器),按照预定义的任务列表,执行一系列的工作,重建容器等。详见如下:
我们把其中最关键的 Shell 脚本内容摘抄出来。这些 Docker 相关操作,在第 1 部分“技术思路”已经提及,不再赘述。
5. 效果图文详解
在 2.3 这个章节中,我们当时的操作如下,这个目的是向 Git 提交更新代码。
当时并没有细说后续发生的事情,既然上面已经说清楚了原理,那我们就可以接下来说说实际发生的事情啦。
5.1 上传代码到 Git
这里貌似整个过程已经完成并顺利退出。其实,后台的工作才刚刚开始哦。
这时会触发 Git 服务器向相应的 Jenkins 服务器发出一个操作请求,此工作太过迅速,也没啥好说的,我们接下来看 Jenkins 都干啥子了。
5.2 Jenkins 进行的精彩互动
如下这个自动运转的过程,让我们有些许成就感,值得端杯咖啡(如果不喜欢茶的话),静静观赏。
1)Jenkins 会自动"冒出来"一个构建任务。
2)我们点进来,看看具体操作日志。是的,正在接受来自 Git 的任务。
3)下载 Maven 相关的软件包(就是这个过程慢)。
4)下载完成后,就开始利用 maven BUILD 新的 hello 项目包。
5)然后重建 Maven 容器,构建新的 Image 并 Push 到 Docker 私有库中。
6)最后,重新把 Docker 容器拉起来。这样,又新生了。呵呵
6. FAQ
问题 1:采用这么相对复杂的办法(而不是把更新代码放在宿主机然后卷组映射),是因为项目基于 JAVA 么;是否 PHP 项目就可以采用更新代码放在宿主机然后卷组映射这种方式?
回答 1:将代码拆分出容器,违背了集装箱原则。导致装卸复杂度增加。从货运工人角度考虑,整体才是最经济的。一切版本化。抛弃过去的文件分发。这是正途。至于文件大小,大的 war 包也就 50M 或 100M,在现有网络下不成问题,性能问题最好优化。另外建议关注 docker 2 docker,p2p 传输。
问题 2:如果整体代码超过 500m 或者 1g 以上,整体集装箱是否就不太好了?如果容器与代码分离,镜像就 100m 左右(2 层,base+ 服务),然后代码的话,是放到共享存储里,每个代码有更新,比如 svn 的代码,可以直接在共享存储里进行 svn update 就可以控制版本
回答 2:如果你的代码 500M,那只能说明业务开发该打板子了。
问题 3:如果测试环境使用您提供的完整集装箱服务还行,但在生产环境,集群里运行 docker 做应用,如果每个容器都是有完整的代码,是否有点臃肿,不如每个集群节点里就运行基础服务镜像,通过卷组功能绑定共享存储里的代码,加上 Crontab、Python 和 Shell 脚本,这样每次代码更新就 1 次就行了。
回答 3:环境一致性,在过去从来没有解决好。10 年前我们做 paas 时,和这个做法类似。不是说不好,时代变了,用脚本东拼西凑,终究难有好的系统。不能只考虑现在的方便,容器技术和 vm 如果类比,我觉得会让自己下决定时很纠结。
补充 3:脚本一般是典型的运维工程师思维,quick & dirty。一般很难做成一个产品或者系统。整体考虑和扩展性考虑都比较少。现在做 docker 的难点在于到底怎么看待它。到底是拿它做调度的基本单位,还是部署的基本单位考虑清楚?再聊方案。
关于作者
萧田国 ,男,硕士毕业于北京科技大学,触控科技运维负责人。拥有十多年运维及团队管理经验。先后就职于联想集团(Oracle 数据库主管)、搜狐畅游(数据库主管)、智明星通及世纪互联等。从 1999 年开始,折腾各种数据库如 Oracle/MySQL/MS SQL Server/NoSQL 等,兼任数据库培训讲师若干年。
曾经的云计算行业从业者,现在喜欢琢磨云计算及评测、云端数据库,及新技术在运维中的应用。主张管理学科和运维体系的融合、人性化运维管理,打造高效、专业运维团队。
个人微信号:xiaotianguo。如需更多了解,请百度“萧田国”。
张春源,目前任职 cSphere。国内最早期的 Docker 实践者,在生产环境拥有一年多的 Docker 容器管理经历。深刻理解 Docker 对于开发、测试以及运维的价值。擅长利用 Docker 构建整个 DevOps 自动化平台。热爱专研 Dockerfile 这门艺术,并对 CoreOS 有深入研究。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。
评论