The Docker Workflow
这篇文章介绍 Docker 工作流
Revision Control 版本控制
Docker 有两种版本控制方式. 一个是用来跟踪文件系统层 layers (每个镜像的组成), 另一个是 tagging 标签系统.
Filesystem layers 文件系统层
Linux 容器由堆叠文件系统层组成, 每一层由一个唯一的哈希标记, 每次 build 都在之前的修改之上. 这意味着, 每次 build 只需要重新构建修改过的层. 这节省了时间和网络带宽.
Image Tags 镜像标签
第二种版本控制回答了一个问题: 之前部署的应用版本是? 非容器化应用的解决方案有很多种, 从 Git 发布标签到部署日志. Docker 有一个内置的处理机制: 每次 build 都有一个镜像标签. latest 经常被用来表示最新版本, 但由于这是一个浮动的标签, 因此在生产中使用并不好. 正确的做法应该是使用一个特定的版本.
Building 构建镜像
Docker 的命令行工具包含一个 build 标志, 它会读取 Dockerfile 并产生一个 Docker 镜像. Dockerfile 中的每一条指令都会在镜像中生成一个新的层, 因此仅通过查看 Dockerfile 就能比较容易的推断出构建会做什么. 这样标准化的好处是, 任何熟悉 Dockerfile 的工程师都可以直接上手并修改任何其他应用的构建. Dockerfile 通常会提交到版本控制系统, 这也简化了对构建变更的追踪, 现代的多阶段构建还运行将构建环境与最终镜像分离, 为构建环境提供了像生产容器那样强大的可配置性.
许多 Docker 构建只是一次性调用 docker image build
命令, 并生成一个镜像.
由于构建的逻辑大部分都写在 Dockerfile 里, 因此很容易为任何团队在 Jenkins 这样的构建系统中创建标准化的构建任务.
作为进一步标准化构建流程的举措, 许多公司已经统一使用 Linux 容器来从 Dockerfile 执行镜像构建, 像 Travis CI、CodeShip 这样的 SaaS 构建服务也对 Docker 构建提供原生支持.
Testing 测试
虽然 Docker 本身并不包含内建的测试框架, 但容器的构建方式为使用 Linux 容器进行测试带来了一些优势.
对生产应用的测试可以有多种形式, 从单元测试到在接近真实的环境中进行的完整集成测试. Docker 通过保证通过测试的制品就是最终投放生产的制品, 从而促进更可靠的测试. 这种保证可以通过使用容器的 Docker SHA 或自定义标签来实现, 确保始终发布的是同一版本的应用.
由于容器按设计包含了它们的所有依赖, 因此在容器上运行的测试非常可靠. 例如, 如果某个单元测试框架报告在某个容器镜像上测试成功, 你可以比较确信在部署时不会遇到底层库版本导致的问题. 大多数其他技术很难做到这一点: 即便像 Java 的 WAR(Java Web Application ARchive)文件, 也通常不包含对应用服务器本身的测试. 而将相同的 Java 应用部署到 Linux 容器中, 通常会把像 Tomcat 这样的应用服务器一并包含进去, 整个栈就可以在发往生产之前进行冒烟测试.
将应用以 Linux 容器形式交付的另一个次要好处是: 在多个应用通过类似 API 的远程方式相互通信的场景中, 一个应用的开发者可以方便地针对另一个服务中已为所需环境(例如 production 或 staging)打好标签的版本进行开发. 各团队的开发者无需成为其他服务的部署或实现专家, 便能继续开发自己的应用. 如果把场景扩展到包含无数微服务的面向服务架构, Linux 容器对那些需要深入应对微服务间大量 API 调用复杂性的开发人员或 QA 工程师来说, 能成为真正的救命稻草.
在生产中运行 Linux 容器的组织中, 一个常见做法是: 自动化的集成测试会拉取一组按版本管理的不同服务的 Linux 容器, 匹配当前已部署的版本. 然后就可以使用这些与部署环境相同的版本对新服务做集成测试. 在异构语言环境下做到这一点以前通常需要大量定制工具, 但由于 Linux 容器提供了标准化, 这项工作现在变得相对容易实现.
Packaging 打包
Docker构建过程会生成一个可视为独立构建产物的镜像, 尽管从技术层面看, 这些镜像可能由多个文件系统层组成. 无论您的应用程序采用何种编程语言编写, 或基于哪种 Linux 发行版运行, 最终都能获得一个分层结构的 Docker 镜像. 这一切都由 Docker 工具链自动构建和处理. 这种构建镜像正是Docker得名的"运输集装箱"隐喻: 它是一个统一的、可传输的单元, 通用工具链能够直接处理, 而无需关心其内部具体内容. 就像远洋货轮将所有货物装入标准钢制集装箱那样, 您的Docker工具链只需处理一种标准化包装: Docker镜像. 这种标准化机制极具价值, 它极大促进了不同应用程序间的工具复用, 意味着他人现成的容器工具也能直接处理您的构建镜像.
传统需要大量自定义配置才能部署到新主机或开发系统的应用程序, 通过Docker实现了高度的可移植性. 容器构建完成后, 可以轻松部署到任何运行Docker服务的同架构系统上.
Deploying 部署
不同企业使用的部署工具种类繁多, 难以尽数. 常见工具包括Shell脚本、Capistrano、Fabric、Ansible 以及内部定制工具, 每个团队通常有一两位掌握"部署魔法"的关键人员——当出现故障时, 整个团队都依赖他们恢复系统. Docker让这些问题迎刃而解, 其内置工具支持通过简单的一行命令即可完成构建产物在主机上的部署和启动.
标准 Docker 客户端虽然每次只能部署到单台主机, 但现有大量工具可以轻松实现向 Docker 集群或其他兼容 Linux 容器主机的批量部署. 得益于 Docker 提供的标准化机制, 开发团队能够以极低的复杂度将构建产物部署到任何这类系统中.
The Docker Eocosystem
多年来, 围绕 Docker 已形成一个由开发者和系统管理员共同推动的庞大社区. 与 DevOps 运动相似, 这个社区通过用代码解决运维问题催生了更优秀的工具. 当 Docker 原生工具链存在功能缺口时, 其他公司和个人纷纷挺身而出进行补充, 其中许多工具同样以开源形式发布. 这意味着这些工具具备可扩展性, 任何企业都能根据自身需求进行定制化修改.
Orchestration 编排
在增强 Docker 核心发行版及 Linux 容器体验的工具中, 首要类别当属编排与批量部署工具. 早期批量部署工具如 New Relic 的 Centurion、Spotify 的 Helios 以及 Ansible Docker 工具, 其工作方式仍近似传统部署工具, 但已将容器作为分发制品. 这些工具采用简单易实施的方案, 在无需引入过多复杂性的前提下即可获得 Docker 的大部分优势. 不过其中许多工具已被更强大灵活的方案(如Kubernetes)所取代.
全自动调度器如 Kubernetes 或搭载 Marathon 调度器的 Apache Mesos 属于更强大的选择, 它们能近乎完全代管主机资源池. 其他商业解决方案也广泛应用 例如 HashiCorp 的 Nomad、Mesosphere 的 DC/OS(数据中心操作系统)以及 Rancher. 无论是开源还是商业选项, 其生态系统都在持续快速发展.
Immutable atomic hosts 不可变原子主机
另一种能提升 Docker 使用体验的理念是不可变原子主机. 传统上, 服务器和虚拟机需要企业精心组装、配置和维护, 以提供支持广泛使用场景的多样化功能. 系统更新通常需要通过非原子操作完成, 主机配置可能存在多种偏差方式, 从而引发意外行为. 当今大多数运行中的系统都采用原地打补丁和更新的方式.
相反, 在软件部署领域, 大多数人会选择部署完整的应用程序副本, 而非对运行中的系统进行补丁操作. 容器的部分吸引力在于它们能比传统部署模式更彻底地实现应用原子化.
这是基于 Linux 的原子主机发行版(如 Red Hat 的 Fedora CoreOS、Bottlerocket OS等)的核心思想之一. 不仅应用程序应该能够轻松销毁和重新部署, 整个软件栈也应遵循相同理念. 这种模式有助于为整个技术栈提供高度一致性和弹性.
不可变原子主机的典型特征包括: 最小化占用空间、专注于支持 Linux 容器和 Docker 的设计、支持原子化的操作系统更新与回滚, 这些操作可通过裸机或常见虚拟化平台上的多主机编排工具轻松控制.
Additional Tools 扩展工具
Docker 并非独立的解决方案. 虽然它具备强大的功能集, 但总会存在需要超越其原生能力的场景. 目前已经形成庞大的工具生态系统, 用于增强或扩展 Docker 功能. 一些优秀的生产工具通过 Docker API 实现集成, 例如用于监控的 Prometheus 和实现简易编排的 Ansible; 另一些则利用 Docker 的插件架构, 插件作为可执行程序, 遵循特定规范与Docker进行数据交互.
还有更多优秀工具通过 API 对接或插件化运行的方式扩展 Docker 能力. 其中大量工具的出现是为了简化 Docker 在各云平台上的使用体验, 实现 Dockera 与云环境的无缝集成. 随着社区持续创新, 这个生态系统仍在不断扩张, 该领域持续涌现新的解决方案和工具.