从Jenkins迁移到Jenkins X:一场持续交付之旅

2019 年 1 月 16 日

从Jenkins迁移到Jenkins X:一场持续交付之旅

这篇文章将介绍 dailymotion(一家总部位于巴黎的视频分享网站)从 Jenkins 迁移到 Jenkins X 的故事,包括我们遇到的问题以及我们如何解决它们。


背景


在 dailymotion,我们信奉 DevOps 最佳实践,并且重度使用了 Kubernetes。我们的部分产品(并非全部)已经部署在 Kubernetes 上。在迁移我们的广告技术平台时,为了赶时髦(作者你这么直白的吗?)我们希望完全采用“Kubernetes 方式”或云原生!这意味着我们需要重新定义我们的整个 CI/CD 管道,并使用按需分配的动态环境来替代永久性的静态环境。我们的目标是为我们的开发人员提供最好的支持、缩短产品上市时间并降低运营成本。


我们对新 CI/CD 平台的初始要求是:


  • 如果可能的话,尽量避免从头开始:我们的开发人员已经习惯使用Jenkins和声明性管道,目前这些东西都还好。

  • 采用公有云基础设施——谷歌云平台和Kubernetes集群。

  • 与gitops兼容——因为我们需要版本控制、评审和自动化。


CI/CD 生态系统中有不解决方案,但只有一个符合我们的要求,也就是 Jenkins X,它基于 Jenkins 和 Kubernetes,原生支持预览环境和 gitops。


Jenkins X: Kubernetes 上的 Jenkins


Jenkins X 是一个高度集成化的 CI/CD 平台,基于 Jenkins 和 Kubernetes 实现,旨在解决微服务体系架构下的云原生应用的持续交付的问题,简化整个云原生应用的开发、运行和部署过程。


你猜的没错,Jenkins X 只能在 Kubernetes 集群上运行


Jenkins X 的搭建过程非常简单,官方网站上已经提供了很好的文档。由于我们已经在使用 Google Kubernetes Engine(GKE),因此 jx 命令行工具可以自行创建所有的内容,包括 Kubernetes 集群。在几分钟内就可以获得一个完整的可运行系统真的让人印象深刻。


Jenkins X 提供了很多快速入门和模板,不过我们想重用现有代码库中的 Jenkins 管道。所以,我们决定另辟蹊径,并对我们的声明性管道进行重构,让它们与 Jenkins X 兼容。


实际上,重构工作并不是只针对 Jenkins X,而是为了能够使用Kubernetes插件在 Kubernetes 上运行 Jenkins。


如果你习惯使用“经典”的 Jenkins,并在裸机或虚拟机上运行静态从节点,那么这里的主要变化是每个构建都将在自己的短存活期自定义 pod 上执行。你可以指定管道的每个步骤应该在哪个容器中执行。插件的源代码中提供了一些管道示例


我们面临的挑战是如何定义容器的粒度,以及它们应该包含哪些工具:拥有足够多的容器让我们可以在不同的管道之间重用它们的镜像,但又不至于太多,这样容易维护——我们可不想要花太多时间重建容器镜像。


在之前,我们在 Docker 容器中运行大部分管道步骤,当我们需要自定义步骤时,就在管道中进行即时构建。


这种方式较慢,但更容易维护,因为所有内容都是在源代码中定义的。例如,升级 Go 运行时可以在单个拉取请求中完成。因此,需要预先构建容器镜像似乎是现有的设置中增加了更多的复杂性。它还具备一些优点:代码库之间的重复代码更少、构建速度更快,并且没有了因为第三方构建平台宕机而造成的构建错误。



在 Kubernetes 上构建镜像


在 Kubernetes 集群中构建容器镜像是一件很有趣的事情。


Jenkins X 提供了一组构建包,使用“Docker 中的 Docker”在容器内部构建镜像。但随着新容器运行时的出现,以及 Kubernetes 推出了 Container Runtime Interface(CRI),我们想知道其他选择是否可行。Kaniko是最成熟的解决方案,符合我们的需求。我们很激动,直到遇到以下 2 个问题。


  1. 第一个问题是阻塞性的:多阶段构建不起作用。通过使用搜索引擎,我们很快发现我们并不是唯一受到这个问题影响的人,而且当时还没有修复或解决方法。不过,Kaniko是用Go语言编写的,而我们又是Go语言开发人员,所以为什么不看一下Kaniko的源代码呢?事实证明,一旦我们找到了问题的根本原因,修复工作就非常简单。Kaniko维护人员很快就合并了修复,一天后,修复的Kaniko镜像就已经可用了。

  2. 第二个问题是我们无法使用相同的Kaniko容器构建两个不同的镜像。这是因为Jenkins并没有正确地使用Kaniko——因为我们需要先启动容器,然后再进行构建。这一次,我们在谷歌上找到了一个解决方法:声明足够多的Kaniko容器来构建镜像,但我们不喜欢这个方法。所以我们又回到了源代码,在找到了根本原因后,修复就很容易了。


我们测试了一些方案,想自己为 CI 管道构建自定义的“工具”镜像,最后,我们选择使用单个代码库,每个分支使用一个镜像,也即一个 Dockerfile。因为我们的代码托管在 Github 上,并使用 Jenkins Github 插件来构建代码库,所以它可以构建所有的分支,并基于 webhook 触发事件为新分支创建新的作业,所以管理起来十分容易。每个分支都有自己的 Jenkinsfile 声明性管道文件,使用 Kaniko 构建镜像,并将构建好的镜像推送到容器注册表。Jenkins 帮我们做了很多事情,所以可以快速地添加新镜像或编辑现有的镜像。


声明所请求的资源


我们之前的 Jenkins 平台存在的一个主要问题来自于静态从属节点或执行程序,以及有时候会在高峰时段出现的长构建队列。Kubernetes 上的 Jenkins 可以轻松地解决这个问题,特别是运行在支持集群自动缩放器的 Kubernetes 集群上时。集群将根据当前的负载添加或移除节点。不过这是基于所请求的资源,而不是基于所使用资源的情况。


这意味着我们需要在构建 pod 模板中定义所请求的资源——比如 CPU 和内存。然后,Kubernetes 调度程序将使用这些信息查找匹配的节点来运行 pod——或者它可能决定创建一个新节点。这样就不会出现长队列了。


但是,我们需要谨慎定义所需资源的数量,并在更新管道时更新它们。因为资源是在容器级别而不是 pod 级别定义的,所以处理起来会更加复杂。但我们不关心限制问题,我们只关心请求,所以我们只将对整个 pod 的资源请求分配给第一个容器(jnlp 那个)——也就是默认的那个。


以下是 Jenkinsfile 的一个示例,以及我们是如何声明所请求的资源的。


pipeline {    agent {        kubernetes {            label 'xxx-builder'            yaml """kind: Podmetadata:  name: xxx-builderspec:  containers:  - name: jnlp    resources:      requests:        cpu: 4        memory: 1G  - name: go    image: golang:1.11    imagePullPolicy: Always    command: [cat]    tty: true  - name: kaniko    image: gcr.io/kaniko-project/executor:debug    imagePullPolicy: Always    command: [cat]    tty: true"""        }    }
stages { }}
复制代码


Jenkins X 的预览环境


现在我们有了所有工具,可以为我们的应用程序构建镜像,我们已准备好进行下一步:部署到“预览环境”!


通过重用现有工具(主要是 Helm),Jenkins X 可以轻松部署预览环境,只要遵循一些约定,例如镜像标签的名称。Helm 是 Kubernetes 应用程序的包管理器。每个应用程序都被打包为一个“chart”,然后可以使用 helm 命令行工具将其部署为“release”。


可以使用 jx 命令行工具部署预览环境,这个工具负责部署 Helm 的 chart,并为 Github 的拉取请求提供注释。在我们的第一个 POC 中,我们使用了普通的 HTTP,因此这种方式奏效了。但现在没有人再用 HTTP 了,那我们使用加密的吧!


多亏了有cert-manager,在 Kubernetes 中创建摄入资源时可以自动获取新域名的 SSL 证书。我们尝试在设置中启用 tls-acme 标志——使用 cert-manager 进行绑定——但它不起作用。


于是我们阅读了 Jenkins X 的源代码——它也是使用 Go 开发的。稍后修改一下就好了,我们现在可以使用安全的预览环境,其中包含了 let’s encrypt 提供的自动证书。


预览环境的另一个问题与环境的清理有关。我们为每个拉取请求创建了一个预览环境,在合并或关闭拉取请求时需要删除相应的环境。这是由 Jenkins X 设置的 Kubernetes 作业负责处理的,它会删除预览环境使用的命名空间。问题是这些作业并不会删除 Helm 的 release——因此,如果你运行 helm list,仍然会看到旧的预览环境列表。


对于这个问题,我们决定改变使用 Helm 部署预览环境的方式。我们决定使用 helmTemplate 功能标志,只将 Helm 作为模板渲染引擎,并使用 kubectl 来处理生成的资源。这样,临时的预览环境就不会“污染”Helm release 列表。


将 gitops 应用于 Jenkins X


在初始 POC 的某个时候,我们对设置和管道非常满意,并准备将 POC 平台转变为可投入生产的平台。第一步是安装 SAML 插件进行 Okta 集成——允许内部用户登录。它运作得很好,但几天后,我发现 Okta 集成已经不在了。我在忙其他的一些事情,所以只是问了同事一下他是否做了一些更改,然后继续做其他事情。几天后再次发生这种情况,我开始调查原因。我注意到 Jenkins pod 最近重启过。但我们有一个持久的存储,而且作业也在,所以是时候仔细看看了!


事实证明,用于安装 Jenkins 的 Helm chart 有一个启动脚本通过 Kubernetes configmap 重置了 Jenkins 配置。当然,我们无法像管理在 VM 中运行的 Jenkins 那样来管理在 Kubernetes 中运行的 Jenkins!


我们没有手动编辑 configmap,而是退后一步从大局看待这个问题。configmap 是由jenkins-x-platform管理的,因此通过升级平台来重置我们的自定义更改。我们需要将“定制”内容保存在一个安全的地方,并对变化进行跟踪。


我们可以使用 Jenkins X 的方式,并使用一个 chart 来安装和配置所有内容,但这种方法有一些缺点:它不支持“加密”——我们的 git 代码库中包含了一些敏感的信息——并且它“隐藏”了所有子 chart。因此,如果我们列出所有已安装的 Helm 版本,只会看到其中一个。但是还有其他一些基于 Helm 的工具,它们更适合 gitops。Helmfile就是其中之一,它通过helm-secrets插件sops原生支持加密。


迁移


从 Jenkins 迁移到 Jenkins X 以及如何使用 2 个构建系统处理代码库也是我们整个旅程的一个很有趣的部分。


首先,我们搭建新 Jenkins 来构建“jenkinsx”分支,同时更新了旧 Jenkins 配置,用来构建除“jenkinsx”分支之外的所有内容。我们计划在“jenkinsx”分支上构建新管道,并将其合并。


对于初始 POC,这样做没有问题,但当我们开始使用预览环境时,不得不创建新的拉取请求,并且由于分支的限制,那些拉取请求不是基于新的 Jenkins 构建的。因此,我们选择在两个 Jenkins 实例上构建所有内容,只是在 Jenkins 上使用 Jenkinsfile 文件名和在新 Jenkins 上使用 Jenkinsxfile 文件名。迁移之后,我们将会更新这个配置,并重命名文件。这样做是值得的,因为它让我们能够在两个系统之间平稳过渡,并且每个项目都可以自行迁移,不会影响到其他项目。


我们的目的地


那么,Jenkins X 是否适合所有人?老实说,我不这么认为。并非所有功能和支持的平台——git 托管平台或 Kubernetes 托管平台——都足够稳定。但是,如果你有足够的时间进行深挖,并选择了适合自己用例的功能和平台,就可以改善你的管道。这将缩短发布周期,降低成本,如果你对测试也非常认真,那么对你的软件质量也应当充满信心。


我们的旅程还没有结束,因为我们的目标仍在继续:Jenkins X 仍然处于开发阶段,而且它本身正在走向 Serverless,目前正在使用 Knative build。它的目标是云原生 Jenkins。


我们的旅程也在继续,因为我们不希望它就这样结束。我们目前的完成的一些事情并不是我们的最终目的地,它只是我们不断演化的一个步骤。这就是我们喜欢 Jenkins X 的原因:与我们一样,它遵循了相同的模式。你也可以开始你自己的旅程~


英文原文:https://medium.com/dailymotion/from-jenkins-to-jenkins-x-604b6cde0ce3


2019 年 1 月 16 日 13:2914242
用户头像

发布了 731 篇内容, 共 359.6 次阅读, 收获喜欢 1824 次。

关注

评论

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

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

teslə

架构师训练营第一周 个人心得

yanghao

第 1 周作业 - 学习总结

WW

架构

李广富

本周总结

Geek_zhangjian

就餐系统

远方

第一周学习笔记

远方

食堂就餐卡系统设计

allen

枚举

小王同学

食堂就餐卡管理系统设计

eric

架构的理解-不只是技术问题

旭东(Frank)

学习 极客大学架构师训练营

从零搭建一个Electron应用

局外人

Java 前端 Electron

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

Eric

极客大学架构师训练营

第 1 周作业 - 食堂就餐卡系统设计

WW

极客大学第一周作业

方堃

极客大学架构师训练营

架构师训练营week1

devfan

食堂就餐卡系统总结

薛定谔的🐴

极客大学架构师训练营 UML

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

Eric

极客大学架构师训练营

架构师训练营 第一周 学习心得

李君

学习 极客大学架构师训练营

week01 学习总结-架构设计文档

Z冰红茶

【架构师训练营】第一周课程总结

张明森

UML示例

Geek_196d0f

架构师训练营0期第一周学习总结

小高

食堂就餐卡系统设计

Geek_zhangjian

一行代码引来的安全漏洞就让我们丢失了整个服务器的控制权

石头

Spring Boot 网络安全 后端 前后端分离

200行代码理解 RxJS 的核心概念

局外人

Java 前端

架构师训练营第一周总结

olderwei

极客大学架构师训练营

食堂就餐卡系统设计

Dennis

第一周学习笔记

方堃

学习 极客大学架构师训练营

「架构师训练营」第1周命题作业

牛牛

极客大学架构师训练营 第一周命题作业

架构师0期第一周作业

喵呜的小哥哥

从Jenkins迁移到Jenkins X:一场持续交付之旅
-InfoQ