从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: Pod
metadata:
  name: xxx-builder
spec:
  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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章