写点什么

使用容器进行构建

2017 年 2 月 03 日

本文要点

  • 容器化构建代理的重要性远不如容器化工具。
  • 在容器中运用构建工具可以简化准备构建代理的过程。
  • 想象工作空间在一系列容器间流转,为容器提供输入并获得输出。
  • Docker 的镜像发布功能使该方案成为可能。
  • 虚拟机也可以应用这种“传递工作空间”的模式。

使用虚拟机分割工具集

运用自动化构建和发布管道(delivery pipelines)能为项目构建带来巨大便利,但是准备其构建代理(build agent)时却需要花费大量精力。

代理中经常可能包含同一工具的多个版本,比如,多个版本的 JDK、.Net Framework 或 Node.js。最终,为了解决版本间的冲突,会需要准备大量代理用于分割各个工具集。维护这些代理也不是容易的工作。这些代理通常是手工定制的。当然,在没有扩展需求或遇到问题之前,它们还能勉强运行。

为了应对扩展的需求,就需要我们创建优质的虚拟机镜像用于组建代理。有时这些镜像是手动创建的,有时会使用到配置管理。无论如何,都会需要一系列工具用于创建、更新并发布镜像。最终我们才可以一键在镜像的基础上创建一个新的代理。

虚拟机使我们可以分割所使用到的工具集,但是在使用上这不够方便。也许将来会得到改进,但就目前看来,创建一个镜像至少需要两步:

  • 找到一个现存的镜像,通常只单单包含操作系统。
  • 安装所需要的软件,并生成一个新的镜像。

用容器来替换虚拟机

退一步想,是否存在可以替代分割工具集的方法呢?其实我们真正需要的是虚拟机提供的隔离性。最近兴起的容器化提供了许多优秀的工具可供参考,比如 Docker。这些工具不仅隔离了各环境,还简化了中间环节。

在容器中我们可以使用镜像,镜像中可以包含所有运行一个软件需要的内容。容器的镜像很大程度上缓解了手动配置自己镜像的需求。容器镜像的注册中心也提供了几乎任何我们能想到的工具,比如,微软就提供了一个最新的.NET Core 工具镜像

当然,我们必须谨慎选择所信任的镜像。这就要说道容器化的另一个好处,极其容易地创建并发布自己的镜像

但是,大多数人在使用容器进行自动化构建和发布管道时都会遇到一个巨大问题。多数人会尝试在一个单独容器内完成所有操作,常常是在容器中放入构建和其他任务所需要的所有工具,并作为代理运行。这基本上只是用容器替换了虚拟机。

容器虽然改善了使用工具的环境,但我们还是免不了需要手动创建镜像,这至少包含了项目构建流程(构建、测试、打包、部署等)所需要的所有工具。

使用多个容器

让我们转换一下思路,不是把整个代理一起容器化,而使用每个容器来运行构建过程中单独一部分。这里每个容器运行一个进程并非为了某种最佳实践,而是为了简化过程。

使用这种方法后,我们可以使用同质化的代理池!我们使用的所有代理可以拥有同样的工具集,并且只需要 Docker 或类似容器管理工具就可以运行这些代理。

但是在需要运行不一样的操作系统及(或)CPU 架构时存在例外。例如,如果我们同时使用 Windows 以及 Linux 两种系统,我们就需要同质化的 Windows 代理池和 Linux 代理池各一个。

在一个操作系统中,我们可以根据需求使用不同版本的工具。

例如,如果我们运行一个有多个步骤的构建流程,我们可以使用一连串的容器。

  • 第一个容器通过 npm 或 nuget 工具获得所有依赖。
  • 第二个容器使用 typescript、javac 或 c#编译器进行编译。
  • 第三个容器使用 tar 或 nuget 工具将编译结果打包。

还有一类构建过程可能只需要使用一个工具,如 Maven。

无论如何,我们都可以以我们实际需要的每个工具为单位来规划管道的流程。之后我们真正需要关注的就是选择硬盘上的一个工作空间,并将它传递通过整个流程,再产生最终结果。

将管道想象成一个传送带,就像安检扫描流程。你将物品放在一个盒子中,接下来物品会通过 X 射线进行扫描。如果没有查出问题,你就可以在另一头拿回你的物品。

如果发现了可疑物品,你的物品会被标记为需要进行人工检查。接下来,物品会被带到一个私人检查区域进行进一步检查。对查出的违禁物品,如一瓶水,就会被取走。

现在,将你的发布管道想象成起始于一个给定提交版本的源码,之后对应源码会被放入一个盒子,即工作空间。然后将它放上传送带。通过第一个工具,这个工具可能获得代码的依赖并将它们也放入盒子,再放回传输带。

接下来传输到下一个工具,可能编译并生成目标代码或中间语言的二进制文件。之后,输出被放入盒子中,并被放回传输带继续传递下去。

下一个工具可能取出盒子,将内部所有的构建输出文件打包成 zip 文件,再将这个 zip 文件放回盒子继续传递。

在发布管道中,这个盒子就像硬盘上的一个目录。常被称为工作空间或检出(checkout)目录。

向容器传递工作目录

将代理想象成传输带十分有用。它协调一系列工具,并让工作目录按流程通过它们。它会获取镜像,并为每个运行的软件、工具创建一个容器。并将工作目录传递给容器中的工具。Docker 中的实现方式是通过简单的绑定挂载,称为卷组(volume)。

代理在工具运行时获得输出(包含标准输出和错误输出)。工具也可以向目录中放入任何需要的文件,完成后结果会自动传递给下一个工具。一个管道只是对目录的一系列转换及投影。

我倾向于想象一系列容器放在一条装配线上。工作空间运行在装配线上,它会被传给每个容器。容器对这个工作目录进行一系列操作,之后这个工作目录会被放回传输带。

有了这个想法,我们可以使用哑代理(dumb agent),它只运行一个代理服务以及 Docker 引擎。我们可以不用关心不同工具之间的冲突,甚至同一工具不同版本间的冲突。容器镜像为指定工具提供了完整的文件系统。

我们也可以改变容器的顺序来满足我们期望的流程。

一个哑代理只需要安装 Docker。当有代码被提交到 git 仓库时,代理会创建一个空的工作空间,如 /work/1 表示版本#1。接下来,代理会协调构建中每一步使用的容器及其中用到的工具。容器的运行顺序也可以进行切换而不需要复杂的准备过程。代理会将工作空间以卷组的方式传递给容器,容器中的工具完成对应操作,并将文件写回工作空间,最后容器会被销毁,接下来工作空间继续被传递给下一个容器。

对应的挑战

这个过程中有许多应该注意的要点。

第一,在容器中运行软件会遇到不同的挑战。例如,应用缓存,像全局的打包缓存。你当然不会希望每次容器创建都重新创建缓存。可以使用卷组作为持久化缓存。

第二,你需要确保你所用的镜像是可信的,不然就要自己创建需要的镜像。幸运的是,与创建虚拟机镜像不同,创建容器镜像十分方便快捷。甚至可以将其集成在你的管道中,因此不需要额外的协调。并且多亏 Docker 构建缓存的功能,镜像的创建是幂等的,而且只会在第一次构建时创建,之后如果镜像的内容没有变动,就不需要再次创建。

第三,清理容器和镜像会比较复杂。不知不觉中可能已经使用了大量的硬盘空间,尤其是在使用自定义的镜像时。有许多方法可以解决这个问题,例如,可以参考 Docker 1.13 将要提供的清理(prune)命令,但是要确保这个问题被充分考虑到。

第四,存在一定学习曲线。幸运的是,Docker 提供了诸多运行软件的便利,用户一旦尝到其甜头后就会积极学习它们。我认为大家也应该学习容器和镜像的内部机制。在使用 Docker 这类工具时,用户虽然并不需要知道其内部如何运行,但是没有这些知识,就不可能真正完全利用其优势。本文背后思想的构思模型就得益于我对容器是一个封闭的进程的理解。

为什么使用 Docker?

现存有众多容器管理工具,但是我还是推荐使用 Docker,因为它有如下优势:

  • 为了隔离的容器化,和锦上添花的安全控制。我们不希望我们的工具间相互产生冲突,也不希望同时运行的任务间存在冲突。
  • 一个多样且包含现存可信任镜像的注册中心。
    • 这里说的可信任是指大部分镜像应由工具的作者所维护。95% 的情况下你并不需要构建镜像。你只需简单地挂载你的工作空间并使用镜像中的工具。
    • 可信同样意味着可以安全地被运行。注册中心应该对镜像常见的薄弱环节进行扫描,并提供签名的镜像。
  • 可以简单地获得现存的镜像并在容器中运行它
  • 可以简单地利用构建缓存构建一个镜像
  • 可以简单地向容器传递一个目录,例如绑定挂载一个宿主机的目录。

未来发展

容器已经有一个较长的历史。可以说容器的概念最早出现在 80 年代 Unix 中的 chroot 。容器最近才获得大家的注意,这很大程度上是得益于简化的镜像发布。

使用虚拟机的麻烦很大程度上来源于其镜像的管理。容器化不仅减少了开销,创建容器的工具也远比虚拟机镜像工具更便于使用,也更快。另一个大家倾向于使用容器的理由,也可能是最重要的,是现存可用的镜像生态系统,用户无需再自定义创建镜像。

当然我们也可以将这些改进迁移到虚拟机镜像上。虚拟机的开销(速度和内存方面)同样可以大大降低。微软就使用 Hyper-V Windows 容器做了这个工作。Intel 的 Clear Container 使 Linux 虚拟机可以在毫秒级时间内使用极小的开销完成启动。如果你查看 Vagrant 或 Packer 这类工具,你会看到有许多大型社区在为虚拟机做类似的工作。

我们运行软件的方式在接下来几年会产生改变。如果你记着本文提到的传递工作空间的模式,你可以将其应用到新的环境中。也许将来会使用虚拟机用于隔离,每个工具都运行在自己独自的虚拟机中,虚拟机的生命周期也等同于发布管道中单个工具的生命周期。

关于作者

Wes Higbee是一名咨询师,他通过技术并快速产生结果来帮助客户消除情绪化的盲点。他的职业生涯颇具色彩,他始于一名软件开发师。通过与客户近距离接触,他意识到软件本身之上还有许多没有被人关注到的需求。这些正是他如今要解决的,无论这些是否涉及技术。在这过程中 Wes 积极地分享自己的知识。他开办了 15 门课程,帮助数千人获得提升。他同时就职于 Pluralsight O’Reilly 。他也在无数当地见面会、社区组织、网络及会议上发表演讲,并从专业的角度帮助各机构提升自我。

查看英文原文: Elevating Builds Into a Container

2017 年 2 月 03 日 16:592058
用户头像

发布了 41 篇内容, 共 10.9 次阅读, 收获喜欢 1 次。

关注

评论

发布
暂无评论
发现更多内容

用 Explain 命令分析 MySQL 的 SQL 执行

程序员历小冰

MySQL explian

架构师训练营 - 作业 - 第二周

心在飞

极客大学架构师训练营

架构师训练营 第二周 学习总结

一雄

学习 极客大学架构师训练营 第二周

第02周 开发编程框架 学习总结

Jaye

程序员的晚餐 | 6 月 15 日 红烧带鱼和清蒸多宝鱼

清远

美食

第二周作业

胡江涛

极客大学架构师训练营

第三周作业三:优化 Cache 类的设计

远方

week2 学习总结

Geek_2e7dd7

架构师训练营第二周课后作业二

不谈

极客大学架构师训练营

架构师训练营 第二周 作业

Poplar

第二周 软件设计原则

WW

江帅帅:精通 Spring Boot 系列 02

奈学教育

Spring Boot

江帅帅:精通 Spring Boot 系列 02

古月木易

Sprint Boot

架构师训练营 第二周作业

大丁💸💵💴💶🚀🐟

做产品少走弯路:你必须掌握的知识

我是IT民工

产品 互联网 方法论 思维方式 知识体系

Java参数传递分析

游侠最光阴

Java

Class-only Protocols - class or AnyObject

SwiftMic

swift AnyObject

架构师训练营第二周课后作业一

不谈

极客大学架构师训练营

第二周作业二:描述熟悉的框架,是如何实现依赖倒置原则

远方

week2 作业

Geek_2e7dd7

Spring Aware 你不能不知道的事

CoderLi

Java spring 程序员 源码分析 后端

ARTS - Week 3

Khirye

ARTS 打卡计划 arts

架构师训练营 第二周 作业

一雄

极客大学架构师训练营 作业 第二周

架构师训练营作业-Week2

wyzwlj

极客大学架构师训练营

第二周作业

远方

JVM的未来——GraalVM集成入门

孤岛旭日

Java 云原生 JVM GraalVM

第二周作业

andy

命题作业—第二周

于江水

极客大学架构师训练营

学习总结—第二周

于江水

架构是训练营

服务治理之轻量级熔断框架:Resilience4j

CoderJ

游戏夜读 | 什么是全力以赴?

game1night

低代码的认知误区与落地实践

低代码的认知误区与落地实践

使用容器进行构建-InfoQ