# 前言
Docker是基础设施的新成员。很少有新兴技术能像它这样,在DevOps和基础设施领域中快速风靡起来。在不到两年的时间内,Google、亚马逊、微软、IBM以及几乎所有云供应商都宣布支持运行Docker容器。大量与Docker相关的创业公司在2014年和2015年年初都获得了风险资本的投资。Docker开源技术背后的同名公司——Docker公司,在2015年第一季度的D轮融资中估值为10亿美元左右。
大大小小的公司都在转换其应用,使之运行于容器内,以此实现面向服务架构(SOA)和微服务。不论是参加从旧金山到柏林的任何DevOps聚会,还是阅读最热门的公司工程博客,都可以看出全世界的运维领导者们如今都在云上运行Docker。
毫无疑问,容器已经成为应用程序打包和基础设施自动化的重要组成部分。但有一个棘手的问题,促使本书作者和同僚们创作了另一本Docker图书。
具有中高级DevOps和运维背景的读者将从本书获益最多。因而,强烈建议读者应具备在生产环境中运行服务器以及创建和管理容器这两方面的基本经验。
很多图书和博客文章已经涵盖了与Docker安装及运行相关的话题,但能把在生产环境中运行Docker时产生的大量甚至是令人挠头的关注点结合在一起的材料则少之又少。不用担心,如果你很喜欢《盗梦空间》(Inception)这部电影,在云服务器的虚拟机中运行容器会让你感觉很自然。
本书将带读者深入理解生产环境中架构的组成部分、关注点,以及如何运行基于Docker的基础设施。
换个更深刻的说法,对于在真实生产环境中使用Docker遇到的问题,如何找到解决之道?本书综合了访谈、真实公司端到端的生产环境实例,以及来自DevOps杰出专家的参考文献,以此来解答这些问题。虽然本书包含了一些有用的示例,但它并不是一本复制粘贴的“教程式”参考书。相反,本书侧重于生产环境中对前沿技术进行评估、风险抵御及运维所需的实践理论和经验。
作为作者,我们希望这本书所包含的内容能够为那些正在评估如何及何时将Docker相关技术引入其DevOps栈的团队提供一个可靠的决策指南,这远比代码片段要来得长久。
生产环境中运行的Docker为企业提供了多个新的运行和管理服务器端软件的方式。很多现成的用例讲解了如何使用Docker,但很少有公司公开分享过他们的全栈生产环境经验。本书汇集了作者在生产环境中运行Docker的多个实例和一组选定的友好公司分享的使用经验。
Docker所使用的底层容器技术已经存在了很多年,甚至早于[dotCloud](https://www.dotcloud.com/)这家平台即服务(PaaS)创业公司,即后来我们所熟知的Docker。在dotCloud之前,许多知名的公司(如[Heroku](https://www.heroku.com/)和[Iron.io](http://www.iron.io/))已经在生产环境中运行大型容器集群,以获取额外的超越虚拟机的性能优势。与虚拟机相比,在容器中运行软件赋予了这些公司秒级而非分钟级的实例启动与停止的能力,同时能使用更少的机器运行更多实例。
既然这项技术并不新鲜,为什么Docker能获得如此巨大的成功呢?主要是因为它的易用性。Docker创造了一种统一的方式,通过简便的命令行及HTTP API工具来打包、运行和维护容器。这种简化降低了将应用程序及其运行时环境打包成一个自包含镜像的入门门槛,使之变得可行且有趣,而不需要类似Chef、Puppet及Capistrano之类的配置管理和发布系统。
Docker提供了一种统一手段,将应用程序及其运行时环境打包到一个简单的Dockerfile里,这从根本上改变了开发人员与DevOps团队之间的交互界面。从而极大简化了开发团队与DevOps之间的沟通需求与责任边界。
在Docker出现之前,各个公司的开发与运维团队之间经常会爆发史诗般的战争。开发团队想要快速前进,整合最新版的软件及依赖,以及持续部署。运维团队则以保证稳定为己任,他们负责把关可以运行于生产环境中的内容。如果运维团队对新的依赖或需求感到不适,他们通常会站在保守的立场上,要求开发人员使用旧版软件以确保糟糕的代码不会搞垮整台服务器。
Docker一下子改变了DevOps的决策思维,从“基本上说不”变成了“好的,只要运行在Docker中就可以”,因为糟糕的代码只会让容器崩溃,而不会影响到同一服务器上的其他服务。在这种泛型中,DevOps有效地负责为开发人员提供PaaS,而开发人员负责保证其代码能正常运行。如今,很多团队将开发人员加入到PagerDuty中,以监控他们在生产环境中的代码,让DevOps和运维人员专注于平台的稳定运行及安全。
对大多数团队而言,采用Docker是受开发人员更快的迭代和发布周期需求推动的。这对于开发环境是非常有益的,但对于生产环境,在单台宿主机上运行多个Docker容器可能会导致安全漏洞,这一点我们将在第6章“安全”中讲述。事实上,几乎所有关于在生产环境中运行Docker的话题都是围绕着将开发环境与生产环境区分开的两个关注点进行的:一是编排,二是安全。
有些团队试图让开发环境和生产环境尽可能保持一致。这种方法看起来很好,但是限于开发环境这样做所需定制工具的数量又或者说模拟云服务(如AWS)的复杂度,这种方法并不实际。
为了简化这本书的范畴,我们将介绍一些部署代码的用例,但判定最佳开发环境设置的实践机会将留给读者。作为基本原则之一,尽量保持生产环境和开发环境的相似性,并使用一个持续集成/持续交付(CI/CD)系统以获取最佳结果。
对于不同的团队,生产环境意味着不同的东西。在本书中,我们所说的生产环境是指真实客户用于运行代码的环境。这是相对于开发环境、预演环境及测试环境而言的,后者的停机时间不会被客户感知到。
在生产环境中,Docker有时是用于接收公共网络流量的容器,有时则是用于处理来自队列负荷的异步的后台作业。不管哪种用途,在生产环境中运行Docker与在其他环境中运行相比,最主要的差异都是需要在其安全性与稳定性上投入较多的注意力。
编写本书的动力之一是,与Docker相关的文档和博客文章中缺乏对实际生产环境与其他环境的明确区分。我们认为,80%的Docker博客文章中的建议在尝试在生产环境中运行6个月之后会被放弃(或至少修改)。为什么?因为大多数博客文章中举的都是理想化的例子,使用了最新、最好用的工具,一旦某个极端的情况变成了致命缺陷,这些工具将被遗弃(或延期),被更简单的方法所取代。这是Docker技术生态系统现状的一个反映,而非技术博客的缺陷。
总的来说,生产环境很难管理。Docker简化了从开发到生产的工作流程,但同时增加了安全和编排的复杂度(更多关于编排的内容参见第4章)。
为了节省时间,下面给出本书的重点综述。
所有在生产环境中运行Docker的团队,都会在传统的安全最佳实践上做出一项或多项妥协。如果无法完全信任容器内运行的代码,那么就只得选用容器与虚拟机一对一的拓扑方式。对于很多团队而言,在生产环境中运行Docker的优势远远大于其带来的安全与编排问题。如果遇到工具方面的问题,请等待一到两个月,以便Docker社区对其进行修复,不要浪费时间去修补其他人的工具。保持Docker设置最小化。让一切自动化。最后,对成熟的编排工具(如Mesos、Kubernates等)的需求远比想象的要少得多。
Docker社区一个常见的口头禅是“电池内置但可移除”,指的是将很多功能捆绑在一起的单体二进制文件,这有别于传统Unix哲学下相对较小、功能单一、管道化的二进制文件。
这种单体式的做法是由两个主要因素决定的:(1)使Docker易于开箱即用;(2)Golang缺少动态链接。Docker及多数相关工具都是用Google的[Go编程语言](https://golang.org/)编写的,该语言可以简化高并发代码的编写与部署。虽然Go是一门出色的编程语言,但用它来构建的Docker生态系统中也因此迟迟无法实现一个可插拔的架构,在这种架构中可以很容易用替代品对工具进行更换。
如果读者有Unix系统背景,最好是编译自己的精简版Docker守护进程,以符合生产环境的需求。如果读者有开发背景,预计到2015年下半年,Docker插件将成为现实[\[1\]](part0006.xhtml#anchor001)。在此期间,估计Docker生态系统中的工具将会出现明显的重叠现象,某些情况下甚至是相互排斥的。
换句话说,要让Docker运行于生产环境中,用户的一半工作将是决定哪些工具对自己的技术栈最有意义。与DevOps所有事情一样,先从最简单的解决方案入手,然后在必要时增加其复杂性。
2015年5月,Docker公司发布了[Compose](https://docs.docker.com/compose/)、[Machine](https://docs.docker.com/machine/)及[Swarm](https://docs.docker.com/swarm/),与Docker生态系统内的同类工具进行竞争。所有这些工具都是可选的,请根据实际情况对其进行评估,而不要认为Docker公司提供的工具就一定是最佳解决方案。
探索Docker生态系统时的另一项关键建议是:评估每个开源工具的资金来源及其商业目标。目前,Docker公司和CoreOS经常发布工具,以争夺关注度和市场份额。一个新工具发布后,最好等上几个月,看看社区的反应,不要因为它看起来很酷就切换到最新、最好用的工具上。
最后一个关键点是,不要期望能在Docker容器中运行所有东西。Heroku风格的“十二要素”([12 factor](http://12factor.net/))应用是最容易Docker化的,因为它们不维护状态。在理想的微服务环境中,容器能在几毫秒内启动、停止而不影响集群的健康或应用程序的状态。
类似[ClusterHQ](https://clusterhq.com/)这样的创业公司正着手实现Docker化数据库和有状态的应用程序,但眼下,由于编排和性能方面的原因,可能需要继续直接在虚拟机或裸机上运行数据库。
Docker还不适用于任何需要动态调整CPU和内存要求的应用[\[2\]](part0006.xhtml#anchor002)。允许动态调整的代码已经完成,但尚不清楚何时才能在一般的生产环境中投入使用。目前,若对容器的CPU和内存的限制进行调整,需要停止并重新启动容器。
另外,对网络吞吐量有高要求的应用进行最佳优化时不要使用Docker,因为Docker使用iptables来完成宿主机IP到容器IP的NAT转换。通过禁用Docker的NAT来提升网络性能是可行的,但这是一个高级的使用场景,很少有团队会在生产环境中这么做。
衷心感谢以下技术审稿人提供的早期反馈及细致的评论:Mika Turunen、Xavier Bruhiere和Felix Rabe。
- - - - - -
[\[1\]](part0006.xhtml#ac001) Docker 1.7版中正式引入了插件系统。——译者注
[\[2\]](part0006.xhtml#ac002) Docker 1.10版中新增的docker update命令可实现CPU和内存的动态调整。——译者注
- 版权信息
- 版权声明
- 内容提要
- 对本书的赞誉
- 译者介绍
- 前言
- 本书面向的读者
- 谁真的在生产环境中使用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社区简介
- 看完了