解读与部署(二):基于 Kubernetes 的 CICD 基础设施即代码

在上一篇基于 Kubernetes 的基础设施即代码一文中,我概要地介绍了基于 Kubernetes 的 .NET Core 微服务和 CI/CD 动手实践工作坊使用的基础设施是如何使用代码描述的,以及它的自动化执行过程。

如果要查看基于 Kubernetes 的基础设施即代码架构全图,以及实现代码,请回到文章基于 Kubernetes 的基础设施即代码。

本文,我们深入探讨其中 CI/CD 软件部分的“基础设施即代码”的实现原理。

变量模板引擎

在工作坊中,由于所有与会者使用的都是同一个 Kubernetes 集群,因此我们需要一种方法来标识当前用户。Kubernetes 的命名空间提供的逻辑隔离功能可以很轻松地实现这个效果。因此,我们要为每个工作坊与会者创建他的一批命名空间:

•cicd-<suffix> 用于部署 CI/CD 软件

•dev-<suffix> 作为“开发环境”,部署微服务

•stage-<suffix> 作为“预生产环境”,部署微服务

显然,对于每一个与会者来说,这里的 suffix 会有所不同,因此它是一个变量。除了在启动安装 CI/CD 软件时需要使用,这个变量还需要以某种形式保存到 Jenkins 上,因为当 Jenkins 运行部署任务时,它也需要知道目标命名空间的名字。

为了处理变量,我们自己发明了一个小型的“模板引擎”。其作用是,使用变量文件中指定的值,替换各个文件中的变量,输出最终的内容。这个模板引擎以双美元符号 $$ 作为变量起始字符。打开 cicd-infra/jenkins.yaml 并搜索 $$ 就可以发现其中大量地引用了各个变量。

模板引擎的实现位于 ./tmpl.sh 脚本文件,它能从指定的变量文件和环境变量读入各个变量的值,并将待处理文件中的变量占位符替换为对应的值,最后向标准输出(stdout)打印最终的文件内容。借助流水线指令 |,这些内容随后被 kubectl apply -f – 命令读取,用于安装配置对应的 Kubernetes 资源。

在 kubelet 1.14 及以上的版本中,新增加了 kustomize 子命令。它提供更多编写模板化、嵌套式 Yaml 文件的方法。在工作坊中,我们需要兼容支持低一些版本的 kubelet,就不得不借助这样的模板引擎。

自动化安装Jenkins

打开 cicd-infra/jenkins.yaml 会发现接近 600 行,可以说不短了。其中包含如下几个关键的 Kubernetes 资源:
•ServiceAccount jenkins 是 Jenkins 本身,以及 Jenkins 用于生成容器镜像并部署微服务时所用的 Pod 要使用的集群账号;

•RoleBinding jenkins_edit 为上述集群账号赋予相应权限;

•Service jenkins-jnlp 供 Jenkins 构建运行器(Slave)启动期间连接 Jenkins 主机(Master)时用的集群 Service;

•Service jenkins 是供 Ingress 用于把 Jenkins Web 界面暴露给用户用的集群 Service;

•Ingress jenkins-ingress 是负责把用户请求转发到集群 Service 的流量入口处理规则;

•ConfigMap jenkins-jobs 可挂载为 Jenkins 内置任务的配置;

•ConfigMap jenkins 一系列用于初始化 Jenkins 的配置;

•Deployment jenkins 用于部署 Jenkins Web 服务。

这里需要重点介绍的是 configmap/jenkins,以及 deployment/jenkins。后者挂载前者,以文件的方式读入内容并完成 Jenkins 的初始化配置工作。具体来说,deployment/jenkins 声明了两个容器,在这两个容器上共享多个存储卷,以实现共享文件的目的:

1.在 Jenkins 启动之前运行的初始化容器 installer,它按照 plugins.txt 先将插件安装到磁盘上,并为工作坊的所有微服务创建内置 Jenkins 任务

2.在 installer 运行完成之后才启动的容器 jenkins,它就是 Jenkins Web 服务本身所在的容器

从 deployment/jenkins 的 yaml 配置中,我们不难发现,installer 运行的具体过程位于脚本文件 /var/jenkins_config/apply_config.sh 中,它的内容是从 configmap/jenkins 挂载而来的。这个脚本中还将用到很多其他文件,比如安装插件用的 plugins.txt,它们都是从这个 configmap/jenkins 挂载而来。为了加速 Jenkins 插件的安装过程,我们在 installer 容器里使用 JENKINS_UC、JENKINS_UC_DOWNLOAD 这两个环境变量来让它从国内的服务器源下载插件。

configmap/jenkins/plugins.txt 定义了工作坊中我们需要用到的插件列表:

•git

•dashboard-view

•pipeline-stage-view

•workflow-aggregator

•kubernetes:1.20.0

其中的 kubernetes 插件让我们的 Jenkins 可以与它所在的 Kubernetes 集群集成,从而实现几乎能把任何容器镜像作为构建运行器(Slave)节点来使用,并且这些节点将以独立的 Pod 的方式“按需”在 Kubernetes 集群中运行,并自动连接到 Jenkins。这大大简化了 Jenkins 的运行器节点的维护工作。如果进一步研读 configmap/jenkins/config.xml 配置内容可以发现,我们的 Jenkins 将内置支持 dotnet 和 image-builder 两种 Slave 节点。
阅读 configmap/jenkins/apply_config.sh 可以看到,它使用了 Jenkins 支持的多种自动化配置功能:

•运行 /usr/local/bin/install-plugins.sh 脚本文件可以预先安装指定的插件

•在 /var/jenkins_home/init.groovy.d 目录中创建的 groovy 脚本将在 Jenkins 启动后自动运行,我们这里用来向 Jenkins 中植入容器镜像注册表的登录信息

•通过预先定义 /var/jenkins_home/config.xml 及其他 xml 文件可以定制 Jenkins 的各类全局系统设置

•/var/jenkins_jobs 目录下的子目录将自动被视为内置任务自动被 Jenkins 加载

上面第一种自动化功能,是内置在 Jenkins 安装包中的一个实用工具,它的源代码位于 GitHub 上。第二种自动化功能是 Jenkins 的初始化脚本,它支持以 Groovy 语言为 Jenkins 开发自动运行的脚本钩子。后面两种自动化功能则是根据 Jenkins 的配置存储机制而预先写入配置来达到内置配置和任务的目的。

deployment/jenkins 还让这两个容器共享 jenkins-home 和 plugin-dir 这两个存储卷,这样就可以让 jenkins 容器从 installer 容器继承已经初始化完成的 Jenkins 配置和插件。这样就确保 Jenkins 主容器运行起来时,就已经具备了已经下载好的插件,以及正确的全局配置。

自动化安装 Gogs 和 Nexus

比起 Jenkins 自动化的过程,Gogs 和 Nexus 的自动化安装就简单得多了。虽然 Gogs 需要 Postgre 数据库的支持,我们在工作坊环境中,还是为数据库配置了 emptyDir 类型的临时存储。因此并不提供持久化存储的支持。给 Nexus 提供的存储也一样用的是 emptyDir 临时存储

值得一提的是这两个软件启动后的初始化操作。在工作坊的自动化脚本中,分别对这两个软件执行了如下自动化初始化:

•在 Gogs 中自动创建账号,从 GitHub 导入各个微服务的源代码库,并配置 WebHook

•修改 Nexus 的默认登录信息为 admin/admin

这些过程,都是借助独立的集群任务 cicd-installer 完成的。在该任务中,它首先读入当前 Kubernetes 环境给定的 Service Account 凭据,配置好 kubectl 命令行工具。接着执行以下工作:

1.等待 gogs-postgresql 和 gogs 部署完成,调用 Gogs 的 RESTful API 接口,完成用户注册和代码库导入工作

2.等待 nexus 部署完成,调用 Nexus 的 Scripting API(脚本编程)接口,完成管理员密码的修改

不难发现,虽然都是自动化配置,却使用了不同的技术。比起 RESTful API 接口,Nexus 的脚本编程接口由于是直接注入脚本,似乎功能会更灵活和强大。不过,过于强大的功能也通常会带来额外的安全风险。

总结

简单总结一下,在上面的讲解中,用到过的自动化技术有:

1.基于 Deployment 实现容器应用自动化部署(自动化部署 Jenkins、Gogs、Nexus 和 Sonarqube 等软件);

2.借助 Pod 的初始化容器(initContainer)实现提前运行自动化任务(在 Jenkins 主容器启动之前,在 initContainer 中安装插件);

3.借助 Pod 多容器共享存储卷来跨容器共享文件(在 initContainer 中安装插件后,由 Jenkins 主容器直接使用);

4.借助 Pod 环境变量向应用注入预置的配置(为 Jenkins 指定插件下载源);

5.借助 ConfigMap 向应用中直接挂载预置的配置文件(为 Jenkins 预设配置);

6.借助 Pod 就绪探针和存活探针,配合 kubectl rollout status 跟踪检测应用部署状态(等待 Gogs、Nexus 部署完成);

7.借助 Job 执行一次性任务(cicd-installer);

8.使用 Dockerfile 构建容器镜像(Jenkins 上的自定义 Slave 节点);

9.调用应用准备好的脚本自动完成配置(使用 Jenkins 提供的 install-plubins.sh 安装插件);

10.调用应用的 RESTful API 接口导入数据(为 Gogs 注册用户并自动导入代码库);

11.调用应用的 Script API 编程接口自动配置(向 Jenkins 和 Nexus 设置登录凭据);

12.使用模板引擎替换变量引用。

到目前,我们详细地解读了如何有机地结合使用各种自动化技术,让工作坊的各个 CI/CD 软件在 Kubernetes 上完成启动之后,自动地完成各项自动化配置。由于 Kubernetes 部署 Yaml 文件以及各类自动化配置脚本都是文本文件,因此我们上一篇文章基于 Kubernetes 的基础设施即代码中关于“基础设施即代码”的两个要求仍然成立。

最后,工作坊的自动化脚本还没有提供存储支持,在实际的项目中应该会有对应的需求;基本上,只要在你的 Kubernetes 集群中配置好集群的存储类和自动存储供给支持,要支持存储并不困难。

K8S中文社区微信公众号

评论 抢沙发

登录后评论

立即登录