从 Jenkins 到 Jenkins X

本文首发于:Jenkins 中文社区git

这是一个关于 dailymotion 从 Jenkins 到 Jenkins X 的旅程,咱们遇到的问题,以及咱们是如何解决它们的故事。github

咱们的上下文

dailymotion ,咱们坚信 devops 最佳实践,而且在 Kubernetes 投入了大量投资。 咱们的部分产品已经部署在 Kubernetes 上,但并非所有。 所以,当迁移咱们的广告技术平台的时候,咱们想要彻底采用“ Kubernetes 式”——或者云原生,以追随技术趋势! 这意味着要从新定义整个 CI/CD 流水线,从静态/永久环境迁移,转向动态按需分配环境。 咱们的目标是受权给咱们的开发人员缩短咱们的上线时间以及下降咱们的运营成本web

对于新的 CI/CD 平台咱们的初始需求是:docker

  • 尽量避免从零开始:咱们的开发人员已经习惯使用 Jenkins 和声明式流水线,而且它们能够很好地知足咱们当前的需求。
  • 以公有云基础设施为目标——Google 云平台和 Kubernetes 集群
  • gitops 方法论兼容——由于咱们喜欢版本控制、同行评审和自动化

在 CI/CD 生态系统中有至关多的参与者,可是只有一个符合咱们的需求,Jenkins X ,它基于 Jenkins 和 Kubernetes ,原生支持预览环境和 gitops安全

Kubernetes 之上的 Jenkins

Jenkins X 的设置至关简单,而且在他们的官方网站上已经有很好的文档(译注:译者曾对 Jenkins X 文档中文本地化作了一些贡献,同时也期待更多的人参与以完善中文文档)。 因为咱们已经使用了 Google Kubernetes Engine (GKE),因此 jx 命令行工具本身建立了全部东西,包括 Kubernetes 集群。 这里有一个小小的哇哦效果,在几分钟内得到一个完整的工做系统是很是使人印象深入的。服务器

Jenkins X 提供了不少快速入门和模板来增长哇哦效果, 然而,在 dailymotion ,咱们已经有了带有 Jenkins 流水线的仓库,咱们想要重用它们。 咱们决定以"艰难的方式"作事情,并重构咱们的声明式流水线,使它们与 Jenkins X 兼容。less

实际上,这一部分并不针对 Jenkins X ,而是基于 Kubernetes 插件在 Kubernetes 上运行 Jenkins 。 若是您习惯使用“经典的” Jenkins ,即在裸金属或虚拟机上运行静态代理,那么这里的主要更改是,每次构建都将在本身的短暂的 pod 上执行。 流水线的每一个步骤均可以指定应该在 pod 的哪一个容器上执行。 在插件的源代码中有一些流水线的例子。 在这里,咱们的"挑战"是定义容器的粒度,以及它们将包含哪些工具:须要足够的容器,以便咱们能够在不一样流水线之间重用它们的镜像,但也不能太多,以控制维护量——咱们不想花时间从新构建容器镜像。工具

之前,咱们一般在 Docker 容器中运行大多数流水线步骤,当咱们须要自定义步骤时,咱们在运行中的流水线中构建它,就在运行它以前。 虽然它比较慢,可是易于维护,由于全部内容都是在源代码中定义的。 例如,升级 Go 运行时的版本能够在一个 pull-request 中完成。 所以,要预先构建容器镜像听起来像是给现有设置增长了更多的复杂性。 它还有几个优势:仓库之间的重复更少,构建速度更快,而且不会由于某些第三方托管平台宕机而出现更多构建错误。测试

在 Kubernetes 上构建镜像

这些天将给咱们带来一个有趣的话题:在 Kubernetes 集群中构建容器镜像。网站

Jenkins X 附带了一组"构建打包",使用 "Docker in Docker" 从容器内部构建镜像。 可是随着新的容器运行时的到来,Kubernetes 推出了它的容器运行时接口( CRI ),咱们想要探索其余的选择。 Kaniko 是最成熟的解决方案,符合咱们的需求/技术栈。 咱们很兴奋……

……直到咱们遇到两个问题:

  • 第一个问题对咱们来讲是一个阻塞问题:多阶段构建不起做用。 多亏了谷歌,咱们很快发现咱们不是惟一受到影响的人,并且目前尚未解决办法。 然而,Kaniko 是用 Go 开发的,而咱们是 Go 开发人员,因此……为何不看看源代码呢? 事实证实,一旦咱们理解了问题的根本缘由,修复就很容易了。 Kaniko 维护者是很愿意帮忙的,而且快速地合并了修复,因此一天以后一个被修复的 Kaniko 镜像就已经可用了。
  • 第二个问题是,咱们不能使用同一个 Kaniko 容器构建两个不一样的镜像。 这是由于 Jenkins 并无按照预期的方式使用 Kaniko ——由于咱们须要先启动容器,而后再运行构建。 这一次,咱们找到了一个针对谷歌的解决方案:声明咱们所须要的尽量多的 Kaniko 容器来构建镜像,可是咱们不喜欢它。 因此回到源代码,再一次,一旦咱们理解了根本缘由,修复就很容易了

咱们测试了一些解决方案来为 CI 流水线构建定制的"工具"镜像, 最后,咱们选择使用一个单个仓库,每一个分支对应一个 Dockerfile 和镜像。 由于咱们将源代码托管在 Github 上,而且使用 Jenkins Github 插件来构建咱们的仓库, 因此它能够构建咱们全部的分支,并为 webhook 事件上的新分支建立新的任务,这使得管理起来很容易。 每一个分支都有本身的 Jenkinsfile 声明式流水线,使用 Kaniko 构建镜像,并将其推入咱们的容器注册中心。 这对于快速添加新镜像或编辑现有的镜像很是有用,由于咱们知道 Jenkins 会处理好全部的事情。

声明所需资源的重要性

咱们在以前的 Jenkins 平台中遇到的一个主要问题来自静态的代理/执行器,在高峰时间有时构建队列很长。 Kubernetes 之上的 Jenkins 使这个问题很容易解决,主要是在 Kubernetes 集群上运行时,它能支持集群自动伸缩。 集群将根据当前负载简单地添加或删除节点。 但这是基于所请求的资源,而不是基于所观察到的使用的资源。 这意味着,做为开发人员,咱们的工做是在构建 pod 模板中定义所需的资源( CPU 和内存)。 而后 Kubernetes 调度程序将使用此信息找到一个匹配的节点来运行 pod ——或者它可能决定建立一个新的节点。 这真是太好了,由于咱们再也不有长构建队列。 但相反,咱们须要谨慎地定义咱们所需资源的恰当数量,并在更新流水线时更新它们。 因为资源是在容器级别定义的,而不是在 pod 级别定义的,因此处理起来有点复杂。 可是咱们不关心限制,只关心请求。 pod 的请求只是全部容器请求的相加。 所以,咱们只是将整个 pod 的资源请求写在第一个容器上——或者 jnlp 容器上——它是默认的容器。 下面是咱们使用的一个 Jenkinsfile 的例子, 也是咱们如何声明请求的资源的例子:

pipeline {
    agent {
        kubernetes {
            label 'xxx-builder'
            yaml """
kind: Pod
metadata:
  name: xxx-builder
spec:
  containers:
  - name: jnlp

Jenkins X 上的预览环境

如今咱们已经拥有了全部的工具,而且可以为咱们的应用程序构建一个镜像, 咱们准备下一步:将它部署到"预览环境"!

Jenkins X 经过重用现有的工具——主要是 Helm ,使得部署预览环境变得很容易, 只要你遵循一些约定,例如用于镜像标签的值的名称。 最好是从"包"中提供的 Helm charts 复制/粘贴。 若是你不熟悉 Helm ,它基本上是一个 Kubernetes 应用程序包管理器。 每一个应用程序都打包为一个 "chart" ,而后能够经过使用 helm 命令行工具做为一个 "release" 被部署。 预览环境是经过使用 jx 命令行工具进行部署的,该工具负责部署 Helm chart ,并以评论的形式,将所公开服务的 URL 添加到 Github pull-request 中。 这一切都很是好,并且对于咱们第一个使用纯 http 的 POC 来讲颇有效。 但如今是2018年(译注:做者是在2018年写的这篇文章),没有人再使用 http 了。 让咱们加密吧! 多亏了 cert-manager,当在 kubernetes 中建立 ingress 资源时,咱们能够自动为咱们的新域名得到一个 SSL 证书。 咱们试图在咱们的设置中启用 tls-acme 标志——与 cert-manager 进行绑定,可是它不起做用。 这给了咱们一个机会来看看 Jenkins X 的源代码——它也是用 Go 开发的。 稍做修复以后都好了, 如今咱们可使用 let's encrypt 提供的自动化证书来享受安全的预览环境。

咱们在预览环境中遇到的另外一个问题与上述环境的清理有关。 每一个打开一个 pull-request ,就建立一个预览环境,所以在 pull-request 被合并或关闭时应该删除预览环境。 这由 Jenkins X 设置的 Kubernetes 任务来处理,它删除了预览环境所使用的名称空间。 问题是这个任务不会删除 Helm release ——因此,好比若是您运行 helm list,您仍然会看到一个很大的旧的预览环境列表。 对于这个问题,咱们决定改变使用 Helm 部署预览环境的方式。 Jenkins X 团队已经写过关于 Helm 和 Tiller ( Helm 的服务器端组件)的这些问题, 所以,咱们决定使用 helmTemplate 特性标志,只使用 Helm 做为模板渲染引擎,并使用 kubectl 处理生成的资源。 这样,咱们就不会用临时预览环境"污染" Helm releases 列表。

Gitops 应用到 Jenkins X

在初始化 POC 的某个阶段,咱们对咱们的设置和流水线感到满意,并但愿将咱们的 POC 平台转变为准生产的平台。 第一步是安装 SAML 插件以设置 OKTA 集成——以容许内部用户登陆。 它运行得很好,几天后,我注意到咱们的 OKTA 集成已经再也不存在了。 我正忙着作其余事情,因此我只是问个人同事他是否作了一些改变,而后继续作其余事情。 但几天后再次发生时,我开始调查。 我注意到的第一件事是 Jenkins Pod 最近从新启动过。 可是咱们有一个持久化的存储,咱们的任务仍然在那里,因此是时候仔细看看了! 事实证实,用于安装 Jenkins 的 Helm chart 有一个启动脚本, 它从 Kubernetes configmap 重置了 Jenkins 配置。 固然,咱们不能像在虚拟机上管理 Jenkins 那样管理在 Kubernetes 中运行的 Jenkins !

所以,咱们没有手动编辑 configmap ,而是后退一步,查看全局。 这个 configmap 自己由 jenkins-x-platform 管理, 所以升级平台将重置咱们的自定义更改。 咱们须要将咱们的"定制"存储在安全的地方并跟踪咱们的更改。 咱们能够用 Jenkins X 的方式,用一个 umbrella chart 来安装/配置一切, 可是这种方法有一些缺点:它不支持 "secret" —— 咱们将一些敏感的值存储在咱们的 Git 仓库中—— 它"隐藏"了全部的 sub-charts 。 因此,若是咱们列出全部已安装的 Helm releases ,咱们将只看到一个。 可是还有其余基于 Helm 的工具,它们更对 Gitops 更友好。 Helmfile 就是其中之一,它经过 helm secrets 插件sops为 secrets 提供了原生支持。 我如今不会详细介绍咱们的设置,但别担忧,这将是我下一篇博客文章的主题!

迁移

咱们故事的另外一个有趣的部分是从 Jenkins 到 Jenkins X 的实际迁移。 以及咱们如何使用两个构建系统处理仓库。 首先,咱们设置新的 Jenkins 来只构建 "jenkinsx" 分支, 而且更新了旧的 Jenkins 的配置来构建除 "jenkinsx" 分支以外的全部分支。 咱们计划在 "jenkinsx" 分支中准备新的流水线,并将其合并以进行迁移。 对于咱们的初始化 POC ,它工做得很好,可是当咱们开始使用预览环境时, 咱们必须建立新的 PR ,而这些 PR 不是基于新的 Jenkins 构建的,由于分支限制。 所以,咱们选择在这两个 Jenkins 实例上构建一切, 但对于旧的 Jenkins 使用 Jenkinsfile 文件名,对于新的 Jenkins 使用 Jenkinsxfile 文件名。 迁移以后,咱们将更新此配置并重命名文件,但这是值得的, 由于它使咱们可以在两个系统之间进行平滑的转换,而且每一个项目均可以本身迁移,而不会影响其余项目。

咱们的目的地

因此,Jenkins X 为你们准备好了吗?老实说,我不这么认为。 并不是全部功能和所支持的平台—— Git 托管平台或 Kubernetes 托管平台——都足够稳定。 可是,若是您准备投入足够的时间来深刻研究,并选择适合您的使用场景的稳定特性和平台, 那么您将可以使用 CI/CD 等所需的一切来改进您的流水线。 这将缩短您的上线时间,下降您的成本,若是您对测试也很认真,那么请对您的软件质量充满信心。

一开始,咱们说这是咱们从 Jenkins 到 Jenkins X 的旅程。但咱们的旅程并未结束,咱们还在旅行中。 部分缘由是咱们的目标仍在移动:Jenkins X 仍处于大的发展阶段,并且它自己正在朝着 Serverless 的方向前进, 目前正在使用 Knative 构建 的路上。 它的目的地是云原生 Jenkins 。 它尚未准备好,可是您已经能够预览它的外观了。

咱们的旅程还将继续,由于咱们不但愿它结束。 咱们如今的目的地并非咱们的最终目的地,而是咱们不断进化的一个步骤。 这就是咱们喜欢 Jenkins X 的缘由:由于它遵循相同的模式。 那么,你在等待什么来开始你本身的旅程呢?

译注:译者曾对 Jenkins X 文档中文本地化作了一些贡献,同时也期待更多的人在 Jenkins X 旅程中, 可以参与到 Jenkins 中文社区以完善 Jenkins X 的中文文档。

译者:王冬辉