自从我首次撰写探讨我们如何组合 Docker 容器和 Jenkins 为 Riot Games 的诸多后端软件创建短期构建环境一文以来,已经过去两年多了。截至今天,此系列共有七篇文章,并且我们也受到了许多关于如何使用容器来做各种有趣的事情的反馈、沟通、技术见解、小技巧和故事。在技术的世界中,两年时间是很长的。该系列尽管仍然有用,但已经过时。许多最新的 Docker 小工具都是缺失的。与其编写一个全新的博客系列,我选择回去并更新我的原创文章。你可以在这里找到完整的更新之后的系列文章:
Part I: Thinking Inside the Container (深入思考容器)
Part II: Putting Jenkins in a Docker Container (将 Jenkins 放入 Docker 容器中)
Part III: Docker & Jenkins: Data That Persists (Docker&Jenkins:持久化数据)
Part IV: Jenkins, Docker, Proxies, and Compose
Part V: Taking Control of Your Docker Image (控制你的 Docker 镜像)
Part VI: Building with Jenkins Inside an Ephemeral Docker Container (在临时 Docker 容器中使用 Jenkins 构建)
Part VII: Tutorial: Building with Jenkins Inside an Ephemeral Docker Container (教程:在临时 Docker 容器中使用 Jenkins 构建)
Part VIII: DockerCon Talk and the Story So Far (DockerCon 谈话及目前的故事)
今天,我想介绍下我们在使用 Docker 和 Jenkins 两年后在 Riot 中学到的经验。我还会为那些熟悉的人员详细介绍我为该系列文章做出的所有更新和改动,并介绍 Docker 容器和 Jenkins 的变迁。
早在2016年,当我第一次将笔放在纸上,安装 Docker 并在桌面上使用起来并不是很方便。当时 Docker Toolbox 是桌面设置中的最好的工具。该工具在桌面上设置 Virtualbox 并在 Linux 虚拟机中运行 Docker 。Docker 提供了一个叫做 Docker Machine 的具有创造性的客户端应用程序来创建和管理改 VM(如果你愿意的话,可以使用几个其他的)。这种设置运行良好,但是非常的重量级。
现在,Docker 为 Windows 和 OSX 提供了本地安装程序。尽管虚拟机管理程序现在更加本地化,并且不需要 Virtualbox ,但他们仍然依赖于虚拟机管理程序样式的解决方案。作为奖励,Windows 版的 Docker 可以在“本地窗口”模式下运行,这使得 Windows Docker 容器得益于微软的合作开发成果。这两种解决方案都像集成的桌面客户端,通过方便的图形用户界面驱动的设置菜单和选项,使得 Docker 的工作变得更加容易。我已经更新了一系列的博客来反映这些增加的内容,这反过来使得设置和运行更容易解释!
在2016年Docker的版本是1.10左右,Docker composer几乎不支持Windows。我们的部署也依赖于Docker Swarm版本0.3.0。Docker 1.12于当年年末时候在DockerCon上宣布,标志着一个重大的进化。在这两年中对Docker的更改太多了,无法详细介绍,但是有几个更改影响了教程。
对Docker最重要的更改可能是添加了Docker Volumes。在Docker的1.10版本中,如果不希望从主机装载数据,您仍然需要创建一个“Docker数据容器”来封装存储持久性。这在Docker中创建了一个半永久 volume ,其他容器可以从中装载和共享数据。虽然很方便,但也有一定的风险,因为如果您希望维护存储,至少有一个容器必须指向数据容器内的 volumes 。这使得在生产Docker主机上使用这样的设置不太理想,实际上我们只在Jenkins的本地开发环境中使用这种设置。
通过引入真正的 volume 支持,您可以创建、命名和管理 volumes ,这些 volumes 可以独立于容器或包含大量集成命令的映像。 volumes 一直存在,直到您有意地从Docker主机中删除它们,并且它们甚至与存储插件集成在一起,以支持跨集群的共享数据 volumes (如果您愿意的话)。我更新了博客关于消除笨重的数据 volumes 容器,并为数据存储定义和创建Docker卷。我想你会发现这种方法更加直观。事实上,你们中的许多人都写过,并提供反馈,认为这样的转换是长期需要的。
您可以在 这里olumes
及其各种存储特性的更多信息。
在2016年Docker的时代, pre Docker 1.12,让两个容器相互通信,需要手动识别他们所暴露的端口,知道Docker主机的IP地址或使用Docker链接,这在同一个主机上是有效的。在最初的教程中,我建议为启用Docker的虚拟机安装检索您的IP地址,并将其提供给几个脚本,以便您的Jenkins安装可以修改NGINX和内部configs等内容。它使得像“localhost”这样的东西作为简单的DNS引用几乎是不可能的。这也意味着我们必须使用容器连接两个容器,以便它们能够很容易地找到彼此。
如今,Docker已经引入了Docker网络。随着Docker在Mac和Windows上的出现,Docker主机现在只使用机器的本地IP地址;容器链接被认为是过时的,并已被弃用。与Volumes一样,网络可以独立地创建、命名和维护,而不需要任何容器或图像。容器可以连接到网络,即使是最简单的Docker也提供服务发现,并通过DNS公开网络中的所有容器。
这使我们能够完全重新考虑教程和本地Jenkins设置的许多方面。为了使Jenkins和NGINX能够彼此对话 我取消了使用容器链接 ,而是将它们放在Docker网络上。同样,我重新配置了Jenkins配置中的构建Docker的设置,以便它们连接到相同的网络,并且可以轻松找到Jenkins主服务器。
整个设置现在是自包含的,不再需要您提供一个IP地址。我们还可以消除在Jenkins配置中用于重命名字段的繁琐启动脚本。
新特性是广泛的,并且在整个Docker群集群中工作,允许您轻松地在主机之间桥接容器。您可以在 这里 阅读更多Docker网络特性。
在Docker Compose上 也看到了许多变化。除了对Volumes和网络的完全支持之外,还获得了对Windows的完全原生的支持。这意味着,我现在几乎只在创建多容器应用程序时使用Docker composer在我的桌面上运行。 Compose 使名称变得超级简单,并创建多个网络、Volumes和“服务”(多容器应用程序)。规范文件也进行了一些修订,现在已经在版本3上了。您将发现,我已经完全接受了Docker composer的最新版本,相关的配置文件都已更新。
您可以在 这里Compose
更改的更多信息。
随着对Docker进行如此多的更改以及切换到原生Docker安装版,一个问题出现了。为了让Jenkins安装动态创建构建从机能够和Docker容器一样,它需要在系统桌面上与Docker对话,就好像它是Docker主机一样。具有讽刺意味的是,当Docker主机在Virtualbox内的虚拟机中运行时,这相当简单,因为它有自己的本地IP地址,并可配置为不安全的监听端口2375或使用TLS证书监听端口2376。旧的教程让你通过获得这些证书并安全地与该终端通信。
现在Dockerhost因为智能安全的原因不会将自己暴露给公共互联网,而这种变化使Jenkins开发配置变得复杂,并在Windows和Mac上创造了独特的挑战。在OSX上,我选择通过使用 socat ,通过Docker网络上的端口2375(这仍然是不会暴露此文件到公开网络的)来公开Docker套接字文件来解决此问题。这使得与Jenkins的配置通信变得更加容易。这个技巧在Windows上不起作用,因为没有Docker套接字文件。因此,我选择了暴露公开端口2375的作为仅在Windows上的选项。这从技术上意味着本教程中的Windows配置比OSX更不安全。
你将在 上一篇教程 中找到有关这些更改的详细演练,其中的Jenkins安装是完全针对临时用例进行了优化。
正如你所看到的,两年来Docker发生了很多变化。在本教程系列中,您会发现许多小细节,例如Docker构建时间参数,标签以及Dockerfiles中反映的其他小调整。其中有很多强大的新功能,但它们并没有彻底改变教程的性质,所以我会把它们留给你自己去发现。
就像Docker一样,Jenkins的时间也在不断前进。当我写这篇教程时,Jenkins 2刚刚上线。 Docker Plugin正在经历非常早的迭代,代码库被分入了 另一个Docker插件 。最终Jenkins仍然像两年前一样工作,但是,Jenkins的Dockerfile设置已经发生了很多变化,配置和设置也发生了改变,它允许与Docker主机进行通信并构建从属设备。
本教程现在使用Jenkins v2.112(撰写本文时为最新版本)。大部分重大更改都在Dockerfile演练中,演示了Cloudbees如何设置Dockerfiles - 例如底层OS容器,Jenkins dockerfiles内部暴露的变量,启动以及Java选项,还有脚本。
这个空间中最大的变化来自插件,用于在构建从服务器动态地派生Docker容器。2016年,当我完成博客系列时,另一个Docker插件作为Docker插件的一个分支刚刚上线。从这个插件看到了持续的改进和新版本的诞生,而最初的Docker插件在大约一年的时间里都没有改变。Docker插件直到最近才被Cloudbees的开发人员复兴,现在这两个插件都有非常不同的配置。我已经进行了广泛的测试,并且两者都运行良好。可以说Docker插件在本教程中更容易设置,因此在整个教程中都是突出显示的。
无论您使用哪个插件,我都主张放弃我在2016年写过的SSH连接设置。相反,这两个插件都提供了一种不同的方式来使用与Jenkins的反向JNLP连接,这将大大提高速度和性能。同样,它使配置和设置临时构建变得更容易。因此,本教程已经进行了更新,以消除SSH连接方法,并坚持使用更简单的方法。
那么你最终应该选择哪个插件呢?在2016年,我开始喜欢另一个Docker插件,因为当涉及到Jenkins时,我总是喜欢正在开发中的插件。最近,随着Cloudbees对Docker插件的支持,我怀疑这可能是最好的长期使用,我们的下一个升级将集中在最新的版本上。
两年对处理Riot Jenkins部署等大型复杂系统的运营问题来说是一个相当长的时间。很可能,我会忘记我们一路上所学的所有微小的事情。这就是说,我认为这里有一些重要的经验教训值得一提。
我们的Jenkins集群自发布第一篇文章以来就一直在增长。在之前文章撰写过程中,我相信我们所描述的Docker Jenkins设置定义了大约1,000-2,000个作业,并且峰值为每小时以120-200个作业。现在我们的服务器平均每小时平均可以处理200-400个作业,峰值为600个左右,并且定义了超过7,000个作业。
这当然会给系统带来很大的压力。我们仍然在该服务器上使用0.15版本的Docker Plugin(我们将随后升级),但我们必须在容器上进行多轮实例上限设定。我们的用户开始对Jenkins非常熟练,并且会创建多个流程,同时启用5个,10个甚至20个容器,以并行处理事务。较旧版本的插件无法处理所有这些情况,特别是在使用SSH时。我们发现立即构建环境实例数量控制在5到10之间对我们用例的最佳配置。
在我们运行这个设置的两年时间里,我们的用户创建了大约240个独特的构建环境,最多可以有7000多个工作机会。经过培训、教育、交谈和反馈,这个数字已经缩减到150-180个左右。我们发现很多人在创建独特的环境时,实际上他们可以使用通用的设置,比如Java或Go软件开发。我们继续提倡合并。我们发现,在Riot的各个团队中,大多数不同的设置仍然存在于Python和NPM开发社区中,并且大部分由包管理和环境设置组成。
作为一种和平服务,我们为各种操作系统(Alpine, Ubuntu和Centos)提供基础构建服务,因此使用我们工具的团队可以与他们的包管理人员一起工作。除此之外,我们还提供Python、Java和Go从用例以及一组基本的实用程序脚本。我们得到了一个有力的教训,那就是升级这些系统实际上是非常痛苦的。当Jenkins切换到使用Java 1.8的强制性需求时,我们必须在几天内更新每个人的构建环境,在此期间许多团队的构建都无法工作。对于拥有核心服务的团队来说,这从来都不是一个好时机。
自从更新以来,我们提倡最小化基本构建环境中的内容。我们已经开始使用许多预先安装的脚本和工具,并在运行时通过从服务器的简单下载将它们安装或安装到容器中。在许多情况下,我们将实用程序转移到共享Jenkins管道的Groovy库(到目前为止,将代码放到环境中是最容易/最快的选择)。
在我们的Docker Jenkins配置的7,000个作业中,有近4400个是Jenkins管道作业。我们使用了Pipeline的Jenkins全局库功能创建了一套通用管道函数。这些自定义库中的大部分可以更容易地与各种Riot artifact商店和系统进行交互。有些只是通过最小化语法和使用常规默认值来使管道更容易使用。我将在不久之后写一篇关于我们的通用函数的博客。
Pipeline作业突飞猛进的主要原因是我们开始构建我称之为的“持续交付即服务”的部分。Riot的大部分后端软件栈已经整合到Java或Go服务中,其中大多数使用相同的内核软件库。因此,我们要求团队在代码仓库中放置了一个简单的Jenkinsfile,其中仅包含这些构建的配置数据。我们使用Jenkins共享库来使用这些文件,并使用通用管道来构建我们的软件。因此,使用这些框架构建的团队不需要构建工程师。实际上,团队不需要编写单个构建脚本来发布他们的软件,因为即使他们的部署工作是自动化的。更多关于这个介绍将在未来的博客中!