# 第9章 CI/CD
现在,读者对容器的构建与存储已经比较熟悉,本章我们简要地谈一下结合Docker镜像的CI/CD系统的使用。如今很多企业已经采用了DevOps做法,并使用一个类似Jenkins、TravisCI或Teamcity的自动化构建系统来自动构建代码。当代码自动构建完毕,在Docker构建流程中将代码添加到容器里是很简单的。如果想在生产环境中运行Docker,强烈建议使用一个自动化持续集成和持续部署(CI/CD)构建系统来构建镜像。Docker构建和推送的简易性,再与CI/CD系统相结合,可能是迄今为止最强大的DevOps服务部署方式之一。
Docker本质上是一个应用程序交付框架。即便Docker网站的座右铭是:构建、交付与运行。如果能把代码交付过程中的构建和交付部分自动化,那么很可能可以更快地将产品交付给客户。开发人员都期望能迅速且频繁地交付代码,而企业交付的是产品。Docker允许将整个产品打包在一个容器中,这不仅能迅速且频繁地交付代码,也能交付整个产品。在接下来的几年时间,我们将看到越来越多的产品以容器镜像的方式进行交付,而不是一个个下载好的msi、jar或zip文件。如果你是一家需要通过SaaS更快地交付代码给客户的公司,或正在以容器方式交付产品,你就应该开始使用构建系统来构建Docker镜像。
如果你已经使用构建系统构建过代码和产品,下一步是通过构建系统来构建Docker镜像。展开想象,一个Web容器,它运行着Jetty,并且使用Java代码的jar文件运行它的应用程序。CI/CD系统将构建代码、将其打包,然后将最终的jar文件部署在像Artifactory、文件系统、AWS S3这样的产出位置上,或保存在自己的内部系统中。此时,当构建一个Docker镜像时,只需要在Dockerfile中使用`ADD code.jar /jetty/bin/code.jar`添加jar文件。在容器运行时,只需要配置Jetty使用`/jetty/bin/code.jar`这个JAR以载入应用程序中。[Rally Soft](https://www.rallydev.com/blog/engineering/deploying-java-apps-docker-and-armada)有一个的具体示例。
CI/CD系统不仅对代码的构建和打包非常有用,在构建Docker镜像时表现也非常完美。编译Docker镜像只需要Dockerfile(代码)和一个构建命令。一旦构建完成,用户只需要将包提交到仓库中。可以对CI/CD系统进行设置,在每次检测到GitHub提交时自动完成构建的全部过程。这将让用户的开发团队或基础设施运维团队可以在一个自动化部署系统中部署新组件、基础设施或应用程序。
使用CI/CD系统构建Docker镜像需要与Docker守护进程进行通信。一个简单的方法是在用户的构建代理上安装Docker,然后就可以构建并交付镜像。另外一种方法读者可能听说过,其术语叫Docker in Docker([DIND](https://github.com/jpetazzo/dind))。读者可以通过阅读一篇[文章](https://blog.docker.com/2013/09/docker-can-now-run-within-docker/)来了解更多信息。简单而言,DIND可以通过发送构建命令给另外一个Docker守护进程来完成构建。不论选择哪种方法,都需要一个自动化方法来构建Docker镜像并在构建进程中添加代码。
构建系统一般是按步骤进行的。我们把使用Docker的几种可能性构建做下分解。
构建Docker镜像。
(1)从github.com拉取最新的Dockerfile代码。
(2)运行`docker build -t repo.com/image .`来构建镜像。
(3)使用`docker push repo.com/image`将镜像推送到仓库中。
使用代码构建Docker镜像。
(1)从github.com拉取最新的Java代码。
(2)使用Maven编译并测试Java代码,输出设置为code.jar。
(3)从github.com拉取最新的Dockerfile代码(Dockerfile具有`ADD code.jar /jetty/bin/code.jar`命令)。
(4)运行`docker build -t repo.com/image .`来构建镜像。
(5)使用`docker push repo.com/image`将镜像推送到仓库中。
使用代码和集成测试构建Docker镜像。
(1)从github.com拉取最新的Java代码。
(2)使用Maven编译并测试Java代码,输出设置为code.jar。
(3)从github.com拉取最新的Dockerfile代码(Dockerfile具有`ADD code.jar /jetty/bin/code.jar`命令)。
(4)运行`docker build -t repo.com/image .`来构建镜像。
(5)启动一个测试专用Docker基础设施。
(6)运行完整的端到端集成测试。
(7)停止该测试专用Docker基础设施。
(8)使用`docker push repo.com/image`将镜像推送到仓库中。
这些示例并不复杂,不过多数时候也不需要太复杂。构建和交付Docker镜像非常简单。最难的部分在于,理解Docker并让构建系统自动运行Docker命令。实现这一过程的自动化只会提高用户的工程成果。接下来,我们讨论几个有关使用自动化CI/CD系统构建Docker镜像的话题。
CI/CD系统已经可以自动化构建镜像了,并且现在生产环境中也运行着Docker。此时,用户大概会意识到自己的基础设施几乎可以运行所有丢给它的容器。为什么不让开发人员在编写新代码后构建并推送Docker镜像到生产环境中?或者,只是将其放置在构建系统里,然后自动将容器部署到生产环境中?这是理论上的完美情况,但现实并非如此。尤其是在放任所有开发人员在Docker镜像中使用任意代码构建自己的Web服务器容器,然后将镜像交给运维团队运行的时候,企业环境很快就会失控。运维团队会甩手不干、愤而离场。他们很快就会意识到情况不受控制,而且完全不清楚容器内有什么。如果他们坚守岗位,可能会开始问类似这样的问题:是否运行了Jetty?是否运行了Nginx?是否运行了Apache?Web服务器的版本是否是最新的?是否使用了最新的安全性强化最佳实践对其进行配置?里面是否有SSH?如何记录日志?是否使用了标准的日志格式?问题会源源不断地涌来。让所有人构建并推送容器是一些团队会想到的主意,但它经常会迅速地转变为一个糟糕的做法。我们强烈建议尽早选择更高的路线并着手制定标准。
以下是我们所认识的团队在生产环境中使用Docker的一些标准。
如果运维或开发团队始终从他们的工作站推送镜像,就会忽略一些信息或最佳实践。一致性是扩展环境和传播知识的关键。应仔细考虑有关构建、添加、使用及运行Docker容器的标准。在一个提供文档的中央系统中构建所有的Docker镜像。基础设施就像代码一样,只是目的是用于构建、编译和打包镜像。它也可以启动有关容器推送的目的地、指定基础镜像、代码验证等的标准化。
随着时间推移,你的安全团队将理解你所做的事情,并希望了解更多。新的安全方法让安全团队更多地参与到构建过程中。因此,应赋予安全团队权限并让他们审核构建的内容。他们还可以利用工具进行测试,找出容易受攻击的软件包、不安全的配置,甚至是一些很糟糕的做法,如在代码中包含密钥信息。
你的Docker镜像是运行Apache、Jetty、Nginx,还是三者都有?是否一个Docker镜像运行Gwizard,而另一个运行Dropwizard?如果你还没创建围绕容器的最佳工程实践,那就应该不断地去确认这些问题。在某些时候,你或你的运维团队会被叫去调试一个遗忘已久的镜像。让镜像内所使用的服务或软件包具有一致的标准,将为工程实践的成功奠定基础。如果你的工程团队倾向于运行Nginx和Jetty,那就使用这些服务来交付Web服务。如果你的团队倾向于使用Dropwizard而非Gwizard,那就使用前者的软件包。你的团队越早实现镜像内容标准化,成功的可能性就越大。
如果你遵循了第一个标准(见9.2节)和第二个标准(见9.3节) ,你就会意识到使用一个默认的基础Docker镜像是个好主意。假定你的团队使用Python 2.6作为代码语言。如果团队里一个新的开发人员拉取了最新版的Python基础镜像,但它是3.0版本,那你注定要遇到问题。树立一个规范,让所有镜像继承于一个标准基础镜像,将大有帮助。有些团队已经开始让运维团队为他们构建基础镜像,他们只要继承即可。例如,运维团队创建了一个基础Ubuntu镜像,对日志做了适当的配置、设置了正确的安全性并安装了正确的Python 2.6版本。开发团队所要做的唯一事情就是用`FROM`来使用这个基础镜像,然后在构建时用`ADD`将代码添加到镜像中,就像这样:
```
FROM company.com/python_base:2015_02
ADD code.py /code.py
CMD [ "python", "./code.py" ]
```
这大大简化了镜像的创建过程,将大量的细节交给拥有并维护这个基础镜像的人负责。提供一个标准基础镜像将让运维团队清楚容器内运行着什么(在多数情况下),它简化了构建过程,让开发人员可以专注于代码,同时,因为它创建了一致性,有助于在用户的基础设施上扩展Docker镜像。
Docker不仅可以让用户将应用程序打包成一个镜像并进行交付,还可丰富基础设施的方方面面。假定你在生产环境构建中运行集成测试,同时具有一个携带静态数据的静态环境。很可能你会构建代码,然后在这个静态环境中运行集成测试。在使用Docker的世界里,用户可以把这个静态环境转变成一个在测试基础设施里集成Docker镜像的动态环境。Docker可以非常快速地启动与停止,加上制作优质镜像的简易性,可根据需要对测试基础设施进行支撑。在完成一次构建时,用户可以运行同一个代理或另一台宿主机系统(调用Docker API)上的集成基础设施,然后在结束后将其关闭。这甚至可以为公司省下保持静态基础设施运行的费用。
到今天为止,在结合Docker的CI/CD测试领域我们还没看到过多创新,不过已经有一些新项目开始涌现。我们见识过在镜像中运行Selenium浏览器测试,甚至为企业提供完整的端到端测试,但是我们还没看到任何标准。我们期待在这个领域很快能看到更多成果。如果想了解Docker和Selenium,可以查看名为Docker [Selenium](https://github.com/elgalu/docker-selenium)的GitHub项目。
DevOps完全就是一种“以一个团队的身份交付代码和基础设施”的文化。当你的工程团队实践DevOps,并能使用单一系统来配置和部署Docker镜像到基础设施上时,你已经开始渐入佳境了。最近在DockerCon 15大会上,我们看到Jenkins全面拥抱了Docker,甚至是微软Visual Studio团队也展示了使用Docker镜像构建和部署应用程序和基础设施的威力。我们希望看到越来越多的公司开始在他们的CI/CD环境里使用Docker,并树立更多最佳实践。
配置管理对Docker来说很重要,第10章将对其进行说明。
- 版权信息
- 版权声明
- 内容提要
- 对本书的赞誉
- 译者介绍
- 前言
- 本书面向的读者
- 谁真的在生产环境中使用Docker
- 为什么使用Docker
- 开发环境与生产环境
- 我们所说的“生产环境”
- 功能内置与组合工具
- 哪些东西不要Docker化
- 技术审稿人
- 第1章 入门
- 1.1 术语
- 1.1.1 镜像与容器
- 1.1.2 容器与虚拟机
- 1.1.3 持续集成/持续交付
- 1.1.4 宿主机管理
- 1.1.5 编排
- 1.1.6 调度
- 1.1.7 发现
- 1.1.8 配置管理
- 1.2 从开发环境到生产环境
- 1.3 使用Docker的多种方式
- 1.4 可预期的情况
- 为什么Docker在生产环境如此困难
- 第2章 技术栈
- 2.1 构建系统
- 2.2 镜像仓库
- 2.3 宿主机管理
- 2.4 配置管理
- 2.5 部署
- 2.6 编排
- 第3章 示例:极简环境
- 3.1 保持各部分的简单
- 3.2 保持流程的简单
- 3.3 系统细节
- 利用systemd
- 3.4 集群范围的配置、通用配置及本地配置
- 3.5 部署服务
- 3.6 支撑服务
- 3.7 讨论
- 3.8 未来
- 3.9 小结
- 第4章 示例:Web环境
- 4.1 编排
- 4.1.1 让服务器上的Docker进入准备运行容器的状态
- 4.1.2 让容器运行
- 4.2 连网
- 4.3 数据存储
- 4.4 日志
- 4.5 监控
- 4.6 无须担心新依赖
- 4.7 零停机时间
- 4.8 服务回滚
- 4.9 小结
- 第5章 示例:Beanstalk环境
- 5.1 构建容器的过程
- 部署/更新容器的过程
- 5.2 日志
- 5.3 监控
- 5.4 安全
- 5.5 小结
- 第6章 安全
- 6.1 威胁模型
- 6.2 容器与安全性
- 6.3 内核更新
- 6.4 容器更新
- 6.5 suid及guid二进制文件
- 6.6 容器内的root
- 6.7 权能
- 6.8 seccomp
- 6.9 内核安全框架
- 6.10 资源限制及cgroup
- 6.11 ulimit
- 6.12 用户命名空间
- 6.13 镜像验证
- 6.14 安全地运行Docker守护进程
- 6.15 监控
- 6.16 设备
- 6.17 挂载点
- 6.18 ssh
- 6.19 私钥分发
- 6.20 位置
- 第7章 构建镜像
- 7.1 此镜像非彼镜像
- 7.1.1 写时复制与高效的镜像存储与分发
- 7.1.2 Docker对写时复制的使用
- 7.2 镜像构建基本原理
- 7.2.1 分层的文件系统和空间控管
- 7.2.2 保持镜像小巧
- 7.2.3 让镜像可重用
- 7.2.4 在进程无法被配置时,通过环境变量让镜像可配置
- 7.2.5 让镜像在Docker变化时对自身进行重新配置
- 7.2.6 信任与镜像
- 7.2.7 让镜像不可变
- 7.3 小结
- 第8章 存储Docker镜像
- 8.1 启动并运行存储的Docker镜像
- 8.2 自动化构建
- 8.3 私有仓库
- 8.4 私有registry的扩展
- 8.4.1 S3
- 8.4.2 本地存储
- 8.4.3 对registry进行负载均衡
- 8.5 维护
- 8.6 对私有仓库进行加固
- 8.6.1 SSL
- 8.6.2 认证
- 8.7 保存/载入
- 8.8 最大限度地减小镜像体积
- 8.9 其他镜像仓库方案
- 第9章 CI/CD
- 9.1 让所有人都进行镜像构建与推送
- 9.2 在一个构建系统中构建所有镜像
- 9.3 不要使用或禁止使用非标准做法
- 9.4 使用标准基础镜像
- 9.5 使用Docker进行集成测试
- 9.6 小结
- 第10章 配置管理
- 10.1 配置管理与容器
- 10.2 面向容器的配置管理
- 10.2.1 Chef
- 10.2.2 Ansible
- 10.2.3 Salt Stack
- 10.2.4 Puppet
- 10.3 小结
- 第11章 Docker存储引擎
- 11.1 AUFS
- 11.2 DeviceMapper
- 11.3 BTRFS
- 11.4 OverlayFS
- 11.5 VFS
- 11.6 小结
- 第12章 Docker 网络实现
- 12.1 网络基础知识
- 12.2 IP地址的分配
- 端口的分配
- 12.3 域名解析
- 12.4 服务发现
- 12.5 Docker高级网络
- 12.6 IPv6
- 12.7 小结
- 第13章 调度
- 13.1 什么是调度
- 13.2 调度策略
- 13.3 Mesos
- 13.4 Kubernetes
- 13.5 OpenShift
- Red Hat公司首席工程师Clayton Coleman的想法
- 第14章 服务发现
- 14.1 DNS服务发现
- DNS服务器的重新发明
- 14.2 Zookeeper
- 14.3 基于Zookeeper的服务发现
- 14.4 etcd
- 基于etcd的服务发现
- 14.5 consul
- 14.5.1 基于consul的服务发现
- 14.5.2 registrator
- 14.6 Eureka
- 基于Eureka的服务发现
- 14.7 Smartstack
- 14.7.1 基于Smartstack的服务发现
- 14.7.2 Nerve
- 14.7.3 Synapse
- 14.8 nsqlookupd
- 14.9 小结
- 第15章 日志和监控
- 15.1 日志
- 15.1.1 Docker原生的日志支持
- 15.1.2 连接到Docker容器
- 15.1.3 将日志导出到宿主机
- 15.1.4 发送日志到集中式的日志平台
- 15.1.5 在其他容器一侧收集日志
- 15.2 监控
- 15.2.1 基于宿主机的监控
- 15.2.2 基于Docker守护进程的监控
- 15.2.3 基于容器的监控
- 15.3 小结
- DockOne社区简介
- 看完了