介绍Makisu:优步为Apache Mesos和Kubernetes提供的快速、可靠的Docker形象构建器

介绍Makisu:优步为Apache Mesos和Kubernetes提供的快速、可靠的Docker形象构建器

为了确保我们多样化技术堆栈的稳定、可扩展的增长,我们利用了一个microservices-oriented架构,让工程师在一个动态、高速的发布周期中部署数千项服务。这些服务提供了新的功能,极大地改善了乘客、司机和用餐者在我们平台上的体验。

尽管这种模式支持了规模和应用程序复杂性的高速增长,但考虑到我们业务的规模和范围,它导致了严重的成长烦恼。为了便于维护和升级微服务,我们在2015年采用了Docker来强制资源约束,并将可执行文件封装为依赖项。

作为Docker迁移的一部分,核心基础架构团队开发了一个管道,可以快速可靠地生成Dockerfiles,并为基于Apache Mesos和kubernetes的容器生态系统将应用程序代码构建到Docker映像中。回馈于不断增长的微服务技术堆栈,我们将其核心组件开源,Makisu.,使其他组织能够利用与自己的架构相同的福利。

Uber的Docker迁移

2015年初,我们将400个服务部署到我们基础设施的裸机主机。这些服务共享依赖关系和配置在主机上的配置文件,并且具有很少的资源约束。随着工程组织的增长,所以需要建立适当的依赖管理和资源隔离。为解决此问题,核心基础架构团队开始将服务迁移到Docker容器。作为这一努力的一部分,我们建立了一种自动化服务,可标准化和简化我们的Docker Image构建过程。

新的集装箱运行时为我们的服务生命周期管理操作带来了显着的速度和可靠性。然而,秘密(用于安全特性的术语,如密码、密钥和证书)被构建到映像中,用于访问服务依赖项,这可能导致安全问题。简而言之,一旦文件被创建或复制到Docker图像中,它将被持久化到一个层中。而它是可能的掩码文件使用特殊粉红色文件因此,秘密不会出现在生成的图像中,Docker不提供实际从中间层中删除文件的机制。

我们尝试的减轻这种安全风险的第一个解决方案是docker-squash,这是一个开源工具,通过删除只在中间构建步骤中使用的文件来组合Docker层。作为我们工作流程的一部分,docker-squash删除了秘密,但增加了映像构建时间。构建Docker映像已经是一个漫长的过程,而Docker -squash的加入否定了基于微服务的体系结构带来的提高开发速度的好处。

不满我们的因果挤压的解决方案,我们决定叉码头工人添加支持我们需要的:如果我们能挂载卷在构建时没有包括在生成的图像,我们可以使用这个访问机密而山建筑,不会留下任何痕迹的秘密在最终容器的形象。这种方法很有效;没有额外的延迟,所需的代码更改相对较少,从而可以快速实现。基础架构团队和服务所有者当时都很满意,并使Docker迁移顺利进行。

在刻度构建容器图像

到2017年,这种设置已经不足以满足我们的规模。随着优步的发展,我们的基础设施的规模和范围也迅速扩大。每天多次构建超过3,000个服务会导致更加耗时的映像构建过程。其中一些构建需要长达两个小时,生成的映像大小超过10GB,这消耗了大量的存储空间和带宽,并损害了开发人员的生产力。

在这一点上,我们意识到我们不得不恢复一步,然后重新思考我们如何在规模上建造容器图像。在这样做时,我们为下一代构建解决方案到达了三个主要要求:便携式建筑,分布式缓存支持和图像尺寸优化。

便携式建筑

2017年,核心基础设施团队开始努力将优步的计算工作量转移到一个统一的平台,这将提供更大的自动化、额外的可伸缩性和更弹性的资源管理。为了适应这一点,Dockerfile构建过程还需要能够在共享集群中的通用容器中运行。

不幸的是,Docker的构建逻辑依赖于写时复制文件系统来确定构建时层之间的差异。这种逻辑需要提高在构建容器内挂载和卸载目录的权限,我们希望在我们的新平台上防止这种行为,以确保最佳安全性。这意味着,为了满足我们的需求,Docker不支持全便携建筑。

分布式缓存支持

层缓存允许用户创建由与以前构建相同的构建步骤组成的构建,并重用以前生成的层,从而减少执行冗余。Docker为个别构建提供了一些分布式缓存支持,但这种支持通常不会扩展到分支或服务之间。我们主要依赖于Docker的本地层缓存,但即使是在我们的机器的一个小集群上,构建的数量也会迫使我们频繁地执行清理,导致缓存命中率较低,这在很大程度上导致了构建时间的膨胀。

过去,优步一直在努力通过在同一台机器上执行相同服务的后续构建来提高命中率。然而,这种方法是不够的,因为构建由不同的服务集合组成,而不是仅仅由少数几个服务组成。这个过程还增加了构建调度的复杂性,这是一个了不起的壮举,因为我们每天要利用数千个服务。

很明显,对于我们的新解决方案,更大的缓存支持是必需的。在进行了一些研究之后,我们确定实现跨服务的分雷竞技是骗人的布式层缓存可以解决这两个问题,方法是改进跨不同服务和机器的构建,而不对构建的位置施加任何约束。

图像尺寸优化

较小的映像可以节省存储空间,传输、解压和启动的时间也更短。

为了优化图像大小,我们研究了使用多级建造在我们的解决方案。这是Docker映像构建解决方案中的常见做法:在中间映像中执行构建步骤,然后将运行时文件复制到更瘦的最终映像中。虽然这个特性确实需要更复杂的dockerfile,但我们发现它可以极大地减少最终的图像大小,因此需要大规模地构建和部署图像。

我们探索的另一个减少图像大小的优化策略是合理地减少图像中的层数。胖图像有时是由中间层创建、删除或更新文件的结果。正如前面提到的,即使在后面的步骤中删除了一个临时文件,它仍然留在创建层中,占用宝贵的空间。在图像中拥有更少的层会减少删除或更新后的文件留在前一层的机会,从而减少图像的大小。

介绍Makisu.

为了解决上述问题,我们构建了自己的映像构建工具,Makisu.,该解决方案允许更灵活、更快速的大规模容器映像构建。具体来说,Makisu:

  • 需要没有权限提升,使构建过程可移植。
  • 用A.分布式层缓存提高构建集群的性能。
  • 提供柔性层代,防止图像中出现不必要的文件。
  • Docker兼容,支持多阶段构建和通用构建命令。

下面,我们将更详细地讨论这些特性。

没有特权的建筑

与其他部署单元(如虚拟机)相比,Docker映像是轻量级的原因之一是,它的中间层仅包含运行当前构建步骤之前和之后的文件系统差异。Docker计算步骤之间的差异,并使用写时复制文件系统(CoW)生成层,这需要权限。

为了生成没有提升权限的层,Makisu执行文件系统扫描,并保持文件系统在内存中的表示。运行构建步骤后,它将再次扫描文件系统,更新内存视图,并将每个更改的文件(即添加、修改或删除的文件)添加到新层。

马基苏还促进了高压缩速度,这是一个重要的考虑在规模工作。Docker使用Go的默认值gzip图书馆压缩层,这不能满足我们对带有大量纯文本文件的大型图像层的性能要求。尽管扫描的时间成本很高,但在很多情况下,即使没有缓存,Makisu也比Docker更快。在我们将构建过程从Docker迁移到Makisu之后,P90的构建时间减少了近50%。

分布式缓存

Makisu使用键值存储将给定Dockerfile的行映射到存储在Docker注册表中的层的摘要。键值存储可以由Redis或文件系统支持。Makisu还强制缓存TTL,以确保缓存条目不会变得太陈旧。

缓存键是在一个阶段中使用当前的构建命令和以前的构建命令的键生成的。缓存值是生成层的内容散列,它也用于标识存储在Docker注册表中的实际层。

在Dockerfile的初始构建过程中,生成的层被异步推送到Docker注册表和键值存储区。然后,共享相同构建步骤的后续构建可以获取和解包缓存层,从而防止重复执行。

柔性层代

要提供对构建过程中生成的图像图层的控制,Makisu使用新的可选提交注释,# !提交,它指定Dockerfile中哪些行可以生成一个新层。这个简单的机制允许减少容器图像的大小(因为一些图层可能会删除或修改前一层添加的文件),并解决了秘密包含问题,而不需要post-hoc层压缩。每个提交的层也被上传到一个分布式缓存中,这意味着它可以被集群中其他机器上的构建使用。

下面,我们分享一个利用Makisu的层生成注释的Dockerfile示例:

从debian:8 AS build_phase
运行apt-get安装wget# !提交
运行apt-get install go1.10# !提交
复制git-repo git-repo
执行cd git-repo && make命令

从debian:8作为run_phase
运行apt-get安装wget# !提交
标签服务名称=测试
COPY -from =build_phase git-repo/binary /binary
entrypoint /二进制

在本例中,多阶段构建安装了一些构建时需求(wgetgo1.10),在它们各自的层中提交,然后继续构建服务代码。一旦这些提交的层被构建,它们就被上传到分布式缓存中,并可用于其他构建,由于应用程序依赖中的冗余,提供了显著的加速。

在第二个阶段中,构建版本将再次安装wget,但此时间makisu重用构建阶段期间承诺的图层。最后,从构建阶段复制服务二进制文件,并生成最终图像。这种常见的范例导致苗条的最终图像,其更便宜到构造(由于分布式缓存的好处)并占用更少的空间,如图1所示,如下所示:

图1.虽然Docker为每个步骤启动新容器并生成三个图层,但Makisu在一个容器中策划所有步骤并跳过不必要的层。

码头工人的兼容性

假设一个Docker映像只是由几个按顺序解压缩的普通tar文件和两个配置文件组成,那么由Makisu生成的映像与Docker守护进程和注册表完全兼容。

Makisu还全力支持多级建造。智能用途# !提交,可以优化阶段,使那些不太可能被重用的可以避免层代,节省存储空间。

另外,由于提交注释被格式化为注释,所以Makisu使用的Dockerfiles可以被Docker构建而没有问题。

使用Makisu.

要开始使用makisu,用户可以直接将二进制文件下载到他们的笔记本电脑上,并在没有的情况下构建简单的dockerfiles运行指令。用户也可以下载Makisu,并在本地容器中运行它,以实现Docker的完全兼容性,或者实现它作为Kubernetes或Apache Mesos持续集成(CI)工作流的一部分:

开源社区中的其他解决方案

有各种其他开源项目,可促进码头图像构建,包括:

  • 巴泽尔是第一个可以构建Docker兼容的图像而不使用Docker或任何其他形式的容器的工具之一。它可以很好地与Docker构建场景的子集一起工作,给出一个Bazel构建文件。我们从它的建筑形象的方法中得到了启发,但我们发现它并不支持运行指令,意味着用户无法安装某些依赖项(例如,apt-get安装wget),难以替换大多数Docker构建工作流程。
  • Kaniko是一个图像构建工具,提供与Docker的强大兼容性,并在用户空间中执行构建命令,而无需Docker守护程序。Kaniko与Kubernetes和多个注册表实现提供了平滑的集成,使其成为Kubernetes用户的能力工具。然而,Makisu提供了一种解决方案,更好地处理大型图像,特别是具有Node_Modules的解决方案,允许缓存到期,并提供更多对缓存生成的控制,这对于处理更复杂的工作流程至关重要。
  • buildkit.,DockerFile-Agnostic Builder Toolkit取决于Runc和Containd,并支持并行阶段执行。BuildKit需要访问/ proc以启动嵌套容器,这在我们的生产环境中不可用。

这些解决方案的丰富和多样性显示了在Docker生态系统中对替代图像建设者的需求。

前进

与我们之前的构建过程相比,Makisu减少了多达90%的Docker映像构建时间,平均减少40%,节省了宝贵的开发人员时间和CPU资源。这些改进取决于给定Dockerfile的结构以及分布式缓存对其特定设置的用处。Makisu还通过删除临时文件和缓存文件,以及仅构建时依赖关系来生成小50%的映像,这节省了计算集群中的空间,并加快了容器部署的速度。

我们正在积极地改进性能,并添加更多的特性,以便更好地与容器生态系统集成。我们还计划增加对开放容器倡议(OCI)图像格式的支持,并将Makisu与现有的CI/CD解决方案集成。

我们鼓励您在您的容器CI/CD工作流程中试用Makisu,并向我们提供您的反馈!请通过我们的松弛GitHub.频道。

如果您对规模管理集装箱的挑战感兴趣,请考虑申请角色在我们的团队!

订阅我们的新闻以跟上优步工程的最新创新。

评论