在 dailymotion,咱们信奉 DevOps 最佳实践,而且重度使用了 Kubernetes。咱们的部分产品(并不是所有)已经部署在 Kubernetes 上。在迁移咱们的广告技术平台时,为了赶时髦(做者你这么直白的吗?)咱们但愿彻底采用“Kubernetes 方式”或云原生!这意味着咱们须要从新定义咱们的整个 CI/CD 管道,并使用按需分配的动态环境来替代永久性的静态环境。咱们的目标是为咱们的开发人员提供最好的支持、缩短产品上市时间并下降运营成本。git
咱们对新 CI/CD 平台的初始要求是:github
若是可能的话,尽可能避免从头开始:咱们的开发人员已经习惯使用 Jenkins 和声明性管道,目前这些东西都还好。golang
采用公有云基础设施——谷歌云平台和 Kubernetes 集群。web
与 gitops 兼容——由于咱们须要版本控制、评审和自动化。安全
CI/CD 生态系统中有不解决方案,但只有一个符合咱们的要求,也就是 Jenkins X,它基于 Jenkins 和 Kubernetes,原生支持预览环境和 gitops。架构
Jenkins X 是一个高度集成化的 CI/CD 平台,基于 Jenkins 和 Kubernetes 实现,旨在解决微服务体系架构下的云原生应用的持续交付的问题,简化整个云原生应用的开发、运行和部署过程。less
你猜的没错,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 集群中构建容器镜像是一件颇有趣的事情。
Jenkins X 提供了一组构建包,使用“Docker 中的 Docker”在容器内部构建镜像。但随着新容器运行时的出现,以及 Kubernetes 推出了 Container Runtime Interface(CRI),咱们想知道其余选择是否可行。 Kaniko 是最成熟的解决方案,符合咱们的需求。咱们很激动,直到遇到如下 2 个问题。
第一个问题是阻塞性的:多阶段构建不起做用。经过使用搜索引擎,咱们很快发现咱们并非惟一受到这个问题影响的人,并且当时尚未修复或解决方法。不过,Kaniko 是用 Go 语言编写的,而咱们又是 Go 语言开发人员,因此为何不看一下 Kaniko 的源代码呢?事实证实,一旦咱们找到了问题的根本缘由,修复工做就很是简单。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 的资源请求分配给第一个容器(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 { } }
如今咱们有了全部工具,能够为咱们的应用程序构建镜像,咱们已准备好进行下一步:部署到“预览环境”!
经过重用现有工具(主要是 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 列表。
在初始 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 的缘由:与咱们同样,它遵循了相同的模式。你也能够开始你本身的旅程~