让我们在 Kubernetes 上创建一个CI/CD(持续集成和持续部署)解决方案,使用 Jenkins 作为构建工具,并使用 Traefik 作为用于灵活应用程序部署和路由的入口。
目标
主要目标是在 Kubernetes 之上提供一种灵活的 CI/CD解决方案,并在每个环境中自动部署应用程序,定义主机和路由。为了使此过程易于理解,将详细介绍并描述以下步骤:
- 设置 Kubernetes 并了解其基本概念;
- 使用 Helm 安装 Traefik,Dashboard 和 Jenkins;
- 创建 Kotlin 应用程序以展示如何使用 CI/CD;
- 实施 Jenkins 管道以自动构建和部署应用程序。
为了完成上述步骤并验证提出的 CI/CD解决方案,提出了具有以下组件的体系结构:
- Kubernetes:用于容器管理和编排;
- Traefik:作为访问服务的代理和负载平衡器;
- Kubernetes 仪表板:通过基于Web的界面管理Kubernetes;
- Jenkins:作为自动化服务器来自动构建和部署应用程序;
- GitHub:使用Git管理源代码;
- DockerHub:作为用于使用示例应用程序管理Docker映像的注册表;
- 应用程序说明:出于开发和测试目的的示例应用程序部署;
- 应用程序生产:将在生产中使用的示例应用程序部署。
在幕后并作为支持工具,还使用了以下技术:
- Docker:用于服务和应用程序容器化;
- Helm:用于在Kubernetes上简化服务的部署和配置;
- Kotlin:开发示例应用程序,它将自动构建并部署到Kubernetes
关于CI/CD解决方案,本文将重点介绍两个主要的交互工作流程,如下图所示:
- 构建和部署应用程序:签出最新的源代码版本以构建应用程序并将其部署在Kubernetes集群上;
- 访问应用程序:使用代理对特定主机名上的已部署应用程序进行标准化访问。
Kubernetes
Kubernetes,也称为K8s,是容器编排的当前标准解决方案,可以轻松地在云中以高可伸缩性,可用性和自动化级别部署和管理大规模应用程序。Kubernetes最初是由Google开发的,受到了开源社区的广泛关注。
它是 Cloud Native Computing Foundation 的主要项目,一些最大的支持者也支持它,例如 Google,Amazon,Microsoft 和 IBM。
Kubernetes 目前是顶级的开源项目之一,也是 Linux 之前活动最多的项目。如今,已有多家公司提供了可用于生产的 Kubernetes 集群,例如亚马逊的 AWS,微软的 Azure 和 Google 的 GCE。Kubernetes文档中提供了现有云提供商的正式列表。
术语
为了理解如何部署应用程序,基本介绍一些核心概念,下面将对其进行介绍和简要介绍:
- 命名空间:一个虚拟集群,可以位于同一物理集群硬件之上,从而使各个开发团队之间的关注点分离;
- Pod:是最小的可部署单元,具有一组共享相同资源(例如内存,CPU和IP)的容器;
- 副本集:确保在任何给定时间正在运行指定数量的Pod副本;
- 部署:一组多个相同的Pod,定义如何运行应用程序的多个副本,如何自动替换任何失败或无响应的实例以及如何执行更新;
- 服务:逻辑Pod集的抽象,这是其他应用程序用来与之交互的唯一接口;
- 入口:管理如何提供对服务的外部访问;
- 持久卷:用于在Pod生命周期内持久保存数据的一块存储。
架构
在开始安装和配置Kubernetes之前,了解正确设置集群所需的软件和硬件组件非常重要。下图总结了必需的组件体系结构,并简要描述了每个组件的角色:
- Master:负责维护所需的群集状态,是管理员管理各个节点的入口点。以下软件组件在主机中运行:
- API服务器:REST API,它公开了可以在群集上执行的所有操作,例如创建,配置和删除Pod和服务;
- 调度程序:负责将任务分配给各个群集节点;
- Controller-Manager:确保集群状态按预期运行,对整个集群中控制器触发的事件做出反应;
- etcd:分布式键值存储,用于共享有关集群状态的信息,所有集群节点均可访问;
- 节点:执行给定任务并运行以下组件的物理或虚拟机:
- Docker:负责启动和管理容器的容器运行时;
- Kubelet:跟踪Pod的状态,以确保所有容器都按预期运行;
- Kube-proxy:路由从服务进入节点的流量;
- UI:用于管理集群配置和应用程序的用户界面应用程序。Kubernetes仪表板将在本文中使用;
- CLI:命令行界面,用于管理集群配置和应用程序。Kubectl将在本文中使用;
要了解有关 Kubernetes 体系结构和术语的更多信息,已经有几页提供了深入的描述,例如 Kubernetes 官方文档,Digital Ocean 的介绍和 Daniel Sanche 的术语介绍。
安装
由于安装和配置每个组件都可能是一项耗时的任务,因此有多个选项可以使安装Kubernetes 的过程更加简单。Ramit Surana 提供了此类安装程序的详尽列表。特别强调 kubeadm,kops,minikube 和 k3,它们一直受到开源社区的支持和更新。
由于我使用 MacOS 并想在单个节点上本地运行 Kubernetes,因此我决定利用 Docker Desktop,该桌面已经在单个工具中提供了 Docker 和 Kubernetes 的安装。安装后,可以检查系统托盘菜单,以确保 Kubernetes 按预期运行:
Kubectl
Kubectl是用于完全管理Kubernetes集群的官方CLI工具,可用于部署应用程序,检查和管理集群资源以及查看日志。由于Docker Desktop已经安装了kubectl,因此我们只需执行kubectl版本来检查其运行是否正常,该版本提供的输出类似于:
代码语言:javascript复制➜ ~ kubectl versionClient Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:16:51Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"darwin/amd64"}Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.5", GitCommit:"20c265fef0741dd71a66480e35bd69f18351daea", GitTreeState:"clean", BuildDate:"2019-10-15T19:07:57Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
代码语言:javascript复制为了了解可用的命令和内在逻辑,我建议对官方的kubectl备忘单进行快速概述。例如,可以通过执行kubectl get pod来获取正在运行的列表pod。
最后但并非最不重要的一点是,如果您使用ZSH Shell,请记住使用kubectl插件,以便获得适当的突出显示和自动完成功能。为此,只需添加kubectl插件即可更改ZSH〜/ .zshrc初始化脚本:
代码语言:javascript复制plugins=(git kubectl)
Helm
Helm是Kubernetes的软件包管理器,它有助于创建模板,以准确描述如何安装应用程序。此类模板可以与社区共享,并可以针对特定安装进行自定义。每个模板称为舵图。检查Helm集线器以了解是否已经有想要运行的应用程序的图表。
如果您好奇并想了解图表的实现方式,则还可以检查带有正式稳定和孵化图表源代码的GitHub存储库。此外,如果您想拥有一个掌舵图表的存储库,则可以使用Harbor和JFrog Artifactory之类的解决方案来存储和提供自己的图表。
最后,要安装 Helm 并检查其是否正确安装,只需运行:
代码语言:javascript复制brew install helm
helm version
Which should give you something like:
代码语言:javascript复制➜ ~ helm version
version.BuildInfo{Version:"v3.1.1", GitCommit:"afe70585407b420d0097d07b21c47dc511525ac8", GitTreeState:"clean", GoVersion:"go1.13.8"}
Traefik
Traefik是HTTP和TCP应用程序的一种广泛使用的代理和负载平衡器,本机兼容并针对基于云的解决方案进行了优化。总而言之,Traefik分析基础架构和服务配置,并自动发现每一项的正确配置,从而实现自动应用程序部署和路由。最重要的是,Traefik还支持收集详细的指标,日志和可追溯性。
Traefik提供了一个稳定且正式的Helm图表,可用于在Kubernetes上进行简单的安装和配置。以下配置值提供给图表,以便进行配置:
- 使用管理员作为用户名和密码,通过域“ traefik.localhost”访问Traefik仪表板;
- 对所有代理服务强制实施SSL,并自动为“ * .localhost”域生成通配符SSL证书。
dashboard: enabled: true domain: traefik.localhost auth: basic: admin: $2y$05$kpCJY2gJWlgG5CUs5tdPx.2xGJ4xyqhWtjiiM/NKfHmj3pfUPsap2ssl: enabled: true enforced: true permanentRedirect: true generateTLS: true defaultCN: "*.localhost"
将配置值保存在文件“ traefik-values.yml”中后,可以通过执行以下命令来安装Traefik:
代码语言:javascript复制helm install stable/traefik --name traefik --values traefik-values.yml
如果要删除Traefik,以下命令应有帮助:
代码语言:javascript复制Helm del --purge traefik
通过检查部署和Pod的状态来检查安装进度:
代码语言:javascript复制kubectl get deployments
kubectl get pods
当部署就绪状态为“ 1/1”(必须在1中准备1)时,访问http://traefik.localhost/以访问Traefik仪表板并使用先前定义的用户名和密码登录。在仪表板中,可以检查可用于访问已部署服务(后端)的入口点(前端)。
Kubernetes仪表板
Kubernetes Dashboard是一个开放源代码的Web界面,用于快速管理Kubernetes集群,并提供用户友好的功能来管理已部署的应用程序并进行故障排除。就个人而言,我更喜欢Portainer的界面和组织,但是它仍然不支持Kubernetes。因此,提供了以下配置以启用Traefik入口并通过http://dashboard.localhost使仪表板可用。
代码语言:javascript复制enableInsecureLogin: true
service:
externalPort: 9090
ingress:
enabled: true
hosts:
- dashboard.localhost
paths:
- /
annotations:
kubernetes.io/ingress.class: traefik
与Traefik相似,可以使用Kubernetes Dashboard Helm官方图表通过以下命令安装Dashboard:
代码语言:javascript复制helm install stable/kubernetes-dashboard --name dashboard --values dashboard-values.yml
为了登录,头盔图表已经创建了具有适当权限的服务帐户。使用这种服务帐户登录的令牌在kubernetes机密中可用。要获取可用机密列表,只需运行kubectl get secrets:
为了登录,头盔图表已经创建了具有适当权限的服务帐户。使用这种服务帐户登录的令牌在 kubernetes 机密中可用。要获取可用机密列表,只需运行kubectl get secrets
:
要获取机密值,请使用kubectl描述包含仪表板令牌的机密:
代码语言:javascript复制describe secrets console-kubernetes-dashboard-token-sk68z:
最后,转到http://dashboard.localhost,并使用先前的令牌值登录Kubernetes仪表板:
Jenkins
Jenkins 是使用最广泛的开源工具,可自动构建,测试和部署软件应用程序。因此,使用 Jenkins,我们可以指定一个处理管道,准确描述每次提交后如何自动构建和部署我们的应用程序。
要安装 Jenkins,我们将利用官方的 Jenkins Helm 图表,提供以下配置以指定登录凭据并安装插件以与 GitHub 和 Kubernetes 集成:
代码语言:javascript复制master: useSecurity: true adminUser: admin adminPassword: admin numExecutors: 1 installPlugins: - kubernetes:1.21.1 - workflow-job:2.36 - workflow-aggregator:2.6 - credentials-binding:1.20 - git:3.12.1 - command-launcher:1.3 - github-branch-source:2.5.8 - docker-workflow:1.21 - pipeline-utility-steps:2.3.1 overwritePlugins: true ingress: enabled: true hostName: jenkins.localhost annotations: kubernetes.io/ingress.class: traefik
要执行安装,请执行以下命令并检查 kubectl get 部署的进度:
代码语言:javascript复制helm install stable/jenkins --name jenkins --values jenkins-values.yml
当所需的 Pod 运行时,请访问 http://jenkins.localhost 以访问 Jenkins 并使用先前提供的凭据登录:
应用
由于所有必需的工具都已成功安装并运行,因此我们现在可以创建要自动构建和部署的示例应用程序。此类应用程序将使用 Spring Boot 框架在 Kotlin 中开发。Spring Initializr 用于通过以下配置创建初始应用程序:
核心功能将在 GreetingController 中,后者仅提供 GET REST 端点即可根据输入参数,提供的环境变量和总体计数器提供问候,以区分不同的调用。
代码语言:javascript复制@RestController
class GreetingController {
val counter = AtomicLong()
@GetMapping("/greeting")
fun greeting(@RequestParam(value = "name", defaultValue = "World") name: String): Greeting {
val envVar: String = System.getenv("EXAMPLE_VALUE") ?: "default_value"
return Greeting(counter.incrementAndGet(), "Hello, $name", envVar)
}
}
此外,请记住添加执行器依赖项以在 /actuator/health 上启用运行状况端点,该端点将用于向 Kubernetes 提供应用程序运行状况信息:
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Dockerfile
要在 Kubernetes 中运行该应用程序,需要该应用程序的 Docker 映像,该映像可通过以下 Dockerfile 描述:
代码语言:javascript复制FROM openjdk:8-jdk-alpine
EXPOSE 8090
ADD /target/k8s-jenkins-example*.jar k8s-jenkins-example.jar
ENTRYPOINT ["java", "-jar", "k8s-jenkins-example.jar"]
Helm chart
要为示例应用程序创建舵图,可以利用舵机CLI工具创建一个基准,使我们可以适应示例应用程序。可以通过在终端上运行helm create helm来创建这样的基准,helm create helm将创建必需的Kubernetes组件的模板以运行并正确配置应用程序。考虑到我们的目标,以下文件是最需要注意的文件:
- Chart.yaml:图表属性,例如名称,描述和版本;
- values.yaml:提供给图表的默认配置值;
- template / deplyment.yaml:Kubernetes部署规范的模板,用于配置应用程序pod和复制特性;
- template / service.yaml:Kubernetes服务规范的模板,用于配置其他应用程序的应用程序接口;
- templates / ingress.yaml:Kubernetes入口规范的模板,以公开服务以供外部访问。
舵图使用{{}}
用于模板,这意味着将解释内部的内容以提供输出值。官方指南中有关多个模板选项的更多详细信息。对于我们正在创建的模板,以下是最重要的示例:
{{.Values.replicaCount}}
从提供的值文件中获取配置副本计数;{{-toYaml. | nindent 8}}
:将引用的Yaml树(点指向当前结构引用)复制到带有8个空格的缩进的结果中。
定义了以下值来配置应用程序,这些值将在图表模板中使用。重要的是要参考提供的docker映像参考,服务端口和入口配置以使用Traefik:
代码语言:javascript复制image:
repository: davidcampos/k8s-jenkins-example
tag: latest
pullPolicy: Always
name: "example"
domain: "localhost"
replicaCount: 1
service:
port: 8090
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: traefik
hosts:
- host:
paths:
- /
以下模板提供了服务配置,该服务配置引用了部署中提供的端口:apiVersion: v1
kind: Service
metadata:
name: {{ .Values.name }}-service
spec:
ports:
- name: http
targetPort: {{ .Values.service.port }}
port: {{ .Values.service.port }}
selector:
app: {{ .Values.name }}
最后,入口模板配置服务公开给外部访问的方式,指定匹配的规则和TLS属性:{{- if .Values.ingress.enabled -}}
{{- $name := .Values.name -}}
{{- $hostname := printf "%s.%s" .Values.name .Values.domain -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $name }}-ingress
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ $hostname | quote}}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ $hostname | quote }}
http:
paths:
{{- range .paths }}
- path: {{ . }}
backend:
serviceName: {{ $name }}-service
servicePort: http
{{- end }}
{{- end }}
{{- end }}
为了检查 Helm 图表是否正常工作,我们可以安装它并检查几个组件是否正确部署:
代码语言:javascript复制helm install example ./helm
kubectl get deployment
kubectl get pod
kubectl get service
kubectl get ingress
Pipeline
目标是建立充分利用Kubernetes的管道,在按需执行的专用代理上构建所需的工件。这种方法为开发人员提供了高度的灵活性和独立性,他们可以完全控制他们的构建管道,并且不依赖于Jenkins主机上安装的任何软件。结果,Jenkins机器不会被许多不同的工具和版本污染。例如,如果一个团队需要Java 8,而另一个团队则需要Java 13,则Jenkins主机不需要同时安装两者,因为每个团队管道都将在自己的Jenkins代理上运行,并且每次运行都按需部署。为此,我们使用了Kubernetes Jenkins插件,该插件允许使用所需工具定义带有容器的容器。然后,我们仅需提及,我们想通过引用其名称在特定容器内运行特定步骤。
请记住,工作空间卷是自动创建的,并且在容器中的容器之间共享,这意味着工作空间上的任何更改将可用于其他容器。例如,如果我们使用maven容器创建打包的jar文件,则docker容器将可以使用它来创建docker映像。此外,为了加快构建过程,不要忘记为maven〜/ .m2
文件夹创建一个卷,以便在作业运行之间共享下载的依赖项。
由于需要maven,docker和helm工具才能正确构建和部署示例应用程序,因此build.yaml文件中提供了以下pod规范:
代码语言:javascript复制apiVersion: v1
kind: Pod
metadata:
labels:
some-label: pod
spec:
containers:
- name: maven
image: maven:3.3.9-jdk-8-alpine
command:
- cat
tty: true
volumeMounts:
- name: m2
mountPath: /root/.m2
- name: docker
image: docker:19.03
command:
- cat
tty: true
privileged: true
volumeMounts:
- name: dockersock
mountPath: /var/run/docker.sock
- name: helm
image: lachlanevenson/k8s-helm:v3.1.1
command:
- cat
tty: true
volumes:
- name: dockersock
hostPath:
path: /var/run/docker.sock
- name: m2
hostPath:
path: /root/.m2
代码语言:javascript复制
对于管道,我决定使用声明性语法而不是脚本,这更适合简单的管道,并且更易于阅读和理解。但是,如果我们要执行更高级的任务,那么限制性更强的语法可能会成为限制。对于此类情况,可以在声明性管道中定义脚本块。总而言之,示例应用程序的CI / CD声明性管道将分为以下阶段:
- 构建:使用maven构建应用程序包;
- Docker Build:使用先前创建的Dockerfile构建docker镜像;
- Docker Publish:将构建的Docker映像发布到Docker Hub;
- Kubernetes部署:通过安装或升级相应的Kubernetes组件,使用先前创建的头盔图来部署应用程序。
在这些阶段的顶部,将创建两个不同的部署环境:生产(https://example.localhost)和登台(https://example-staging.localhost),它们分别与master和develop分支相关。因此,如果分支不是master或developer,则不会构建docker映像,并且不会将应用程序部署到Kubernetes。此外,所有应用程序工件都具有相同的版本,可以使用Pipeline Utility步骤Jenkins库从POM文件加载该版本。
在示例应用程序的Jenkins声明性管道下面找到该管道,该管道还使用build.yaml文件中所述的pod设置代理,并在每次运行作业时自动从GitHub签出源代码:
代码语言:javascript复制pipeline {
environment {
DEPLOY = "${env.BRANCH_NAME == "master" || env.BRANCH_NAME == "develop" ? "true" : "false"}"
NAME = "${env.BRANCH_NAME == "master" ? "example" : "example-staging"}"
VERSION = readMavenPom().getVersion()
DOMAIN = 'localhost'
REGISTRY = 'davidcampos/k8s-jenkins-example'
REGISTRY_CREDENTIAL = 'dockerhub-davidcampos'
}
agent {
kubernetes {
defaultContainer 'jnlp'
yamlFile 'build.yaml'
}
}
stages {
stage('Build') {
steps {
container('maven') {
sh 'mvn package'
}
}
}
stage('Docker Build') {
when {
environment name: 'DEPLOY', value: 'true'
}
steps {
container('docker') {
sh "docker build -t ${REGISTRY}:${VERSION} ."
}
}
}
stage('Docker Publish') {
when {
environment name: 'DEPLOY', value: 'true'
}
steps {
container('docker') {
withDockerRegistry([credentialsId: "${REGISTRY_CREDENTIAL}", url: ""]) {
sh "docker push ${REGISTRY}:${VERSION}"
}
}
}
}
stage('Kubernetes Deploy') {
when {
environment name: 'DEPLOY', value: 'true'
}
steps {
container('helm') {
sh "helm upgrade --install --force --set name=${NAME} --set image.tag=${VERSION} --set domain=${DOMAIN} ${NAME} ./helm"
}
}
}
}
}
Job
最后,让我们创建 Jenkins 作业以使用示例应用程序源代码运行管道。为此,请转到Jenkins并使用以下配置创建一个新的 Multibranch Pipeline 作业:
保存 Jenkins 作业后,您应该能够在列表中看到它,浏览它的几个分支,并检查为每个分支执行的管道:
验证
现在所有部分都运行在一起,并且我们检查了核心功能,让我们验证该解决方案是否适合典型的GitFlow开发流程:
建立 Jenkins 的总行工作;
检查生产部署是否正在运行并提供期望的值:
代码语言:javascript复制➜ ~ curl -k -w 'n' --request GET 'https://example.localhost/greeting'
{"id":1,"content":"Hello, World","env":"default_value"}
创建开发部门并建立各自的 Jenkins 工作;
检查暂存部署是否正常运行:
代码语言:javascript复制➜ ~ curl -k -w 'n' --request GET 'https://example-staging.localhost/greeting'
{"id":1,"content":"Hello, World","env":"default_value"}
Checkout开发分支,并将问候语方法的默认名称参数值从“ World”更改为“ World!”; 提交并等待Jenkins作业完成,以更新登台部署; 检查分段部署上的默认值是否已更改:
代码语言:javascript复制➜ ~ curl -k -w 'n' --request GET 'https://example-staging.localhost/greeting'
{"id":1,"content":"Hello, World!","env":"default_value"}
代码语言:javascript复制
- 将开发分支合并为主分支;
- 等待Jenkins主工作完成并更新生产部署;
- 检查生产部署是否正确更新:
- ➜〜curl -k -w' n'-请求GET'https://example.localhost/greeting' {“ id”:1,“ content”:“ Hello,World!”,“ env”:“ default_value”}
是的,一切都在自动进行!
来源:https://hands-on-tech.github.io/2020/03/15/k8s-jenkins-example.html