您的位置 首页 golang

一文告诉你如何借助Kubernetes、Ansible和Jenkins创建CD管道

作者 | Magalix

翻译 | 火火酱,责编 | Carol

来源 | 架构师技术联盟

封图 | CSDN付费下载于IC photo

CI/CD要解决的是什么问题?

CI/CD (CI全名Continuous Integration,持续集成;CD全名Continuous Deployment,持续部署)这个术语常常和DevOps、Agile、Scrum以及Kanban、自动化等其他术语一起出现。

有时,人们只将它看作是工作流的一部分,而没有真正理解它是什么或采用它的意义是什么。 年轻的DevOps工程师经常将CI/CD视作理所当然的事情,他们可能没有见过“传统的”软件发布周期,因此,get不到CI/CD的好处。

CI/CD代表持续集成、持续交付和部署。 未实现CI/CD的团队在创建新软件产品时必须经历以下几个阶段:

  1. 产品经理(代表客户的利益)提供产品应该具有的特性和应该实现的行为。该环节必须尽可能全面和具体。
  2. 开发人员借助业务分析师的帮助,通过编写代码,运行单元测试并将结果提交到版本控制系统(例如 git )。
  3. 开发阶段结束后,项目将被转移到QA(Quality Assurance,质量保证)。针对产品运行几个测试,比如用户验收测试、集成测试、性能测试等等。在此期间,在QA阶段完成之前,不会对代码库进行任何更改。如果有任何bug,他们会返还给开发人员进行修复,然后修改后的产品将被再次交给QA。
  4. QA结束后,操作团队会将代码部署到生产环境中。

但上述工作流程存在很多缺点:

首先,从产品经理提出要求到产品准备投入生产之间,需要花费很长的时间。

对于开发人员来说,要想解决一个月或更久以前编写的代码中的bug可不容易。要记得,只有在开发阶段结束和QA阶段开始之后才会发现bug。

当需要紧急更改代码(比如需要热修复的严重bug)时,因为需要尽快部署,所以往往会缩短QA阶段。

由于不同的团队之间的沟通协作很少,所以当bug发生时,人们就会开始相互指责。每个人都只会关心他自己负责的那部分,而忽略了共同的目标。

CI/CD通过引入自动化来解决上述问题。每次将代码更改推送到版本控制系统后,都将进行测试,然后将其部署到stagingUAT环境,进行进一步的测试,之后才会将其部署到生产环境中供用户使用。自动化能够确保整个过程 快速 可靠、可重复,并且减少出错的概率。

既然如此,什么是CI/CD?

一些相关书籍已经介绍得很清楚了。如何、为什么以及何时在你的基础架构中实现CI/CD。然而,我们总是喜欢少一点理论,多一点实践。 既然如此,我们将简要描述在提交代码更改后应该执行的自动化步骤:

持续集成(CI):第一步中不包括QA。换句话说,它并不关注代码是否提供了客户要求的特性。相反,它关注的是要确保代码质量。 通过单元测试、集成测试,可以将任何有关代码质量的问题通知给开发人员。我们可以进一步通过代码覆盖率和静态分析来强化测试,从而进一步保证代码质量。

用户验收测试:这是CD流程中的第一部分。在此阶段,将对代码进行自动化测试,以确保其满足客户的期望。例如,一款web应用程序或许能够正常运行,不引发任何错误,但是客户希望访客能够在进入主页之前先经过一个包含推广的登陆页。当前的代码会将访客直接带到主页面,这与客户的实际要求是有偏差的,UAT测试会可以指出这类问题。 在非CD环境中,该工作需要QA测试人员人工完成

部署: 这是CD流程中的第二部分。它涉及到对将承载应用程序的服务器/容器进行更改,使其能够反映更新后的版本。这项工作应该以自动化的方式完成,最好是通过诸如 Ansible 、Chef或Puppet等配置管理工具来完成。

什么是管道(pipeline)?

管道是一个拥有简单概念的高级术语。当你为实现一个共同目标,可能需要按照一定的顺序执行很多个脚本,而这些脚本被统称为“管道”。

例如,在Jenkins中,一个管道可能包含一个或多个必须全部完成才能实现成功构建的阶段。使用多个阶段能够将整个过程可视化,了解每个阶段所花费的时间,并清楚了解构建在何处失败。

实验室:为golang app创建管道

在该实验室中,我们将构建一个持续交付(CD)管道。我们使用的是一个用Go语言编写的非常简单的应用程序。为了简单起见,我们仅对代码运行一种类型的测试。该实验的前提条件如下:

  • 正在运行的Jenkins实例。可以是云实例、虚拟机、裸机或docker容器。它必须能够从网络上进行公开访问,以便存储库可以通过webhook连接到Jenkins。
  • 镜像注册表:你可以使用 docker Registry,这是一种基于云的产品,如ECR或GCR,甚至也可以使用自定义注册表。

ECR:

GCR:

一个 GitHub 上的账户。虽然我们在本例中使用了GitHub,但是这个过程可以与其他存储库(比如Bitbucket)一样进行细微的修改。

该管道可描述如下:

步骤一:应用文件

我们的示例应用程序将对任何GET请求作出“Hello World”响应。创建一个名为main.go的新文件,并在文件中添加以下内容:

由于我们要构建一个CD管道,所以应该进行一些测试。我们的代码非常简单,只需要一个测试用例即可,确保我们在点击根URL时能够收到正确的字符串。在相同目录中创建一个名为main_test.go的新文件,并添加以下内容:

还有其他几个文件可以帮助我们部署应用程序,这些文件名为:

The Dockerfile

我们在此对应用程序进行打包:

Docker file是一个多阶段构建文件,它可以使镜像尽可能得小一些。它开始于一个以golang:alpine为基础的构建镜像。生成的 二进制文件 将被用于第二个镜像,这只是一scratch镜像,不包含依赖项或库,只包含启动应用程序的二进制文件。

scratch:

The Service

由于我们使用 Kubernetes 作为承载此应用程序的平台,因此至少需要一项服务和部署。我们的service.yml文件如下所示:

这没有什么特别之处。只是一个使用NodePort作为其类型的服务。它将监听任何集群节点的IP地址上的32000端口。传入的连接将中继到8080端口上的吊舱(pod)。内部通信方面,服务将监听80端口。

The deployment

一旦应用程序本身进行了docker化,就可以通过Deployment资源将其部署到Kubernetes。该deployment.yml文件如下所示:

这个部署定义最有趣的地方是镜像部分。我们没有硬编码图像名称和标签,而是使用了一个变量。稍后,我们将看到如何使用该定义作为Ansible的模板,并通过命令行参数替换镜像名称(以及部署的任何其他参数)。

The playbook

在该实验中,我们使用Ansible作为部署工具。部署Kubernetes资源有许多种方法,包括Helm Charts,但我认为使用Ansible更简单一些。Ansible使用playbook来组织其指令。我们的playbook.yml文件如下所示:

Ansible已经包含了用于处理与Kubernetes API服务器通信的k8s模块。因此,我们不需要安装kubectl,但是我们需要一个有效的kubeconfig文件来连接到集群(稍后会详细介绍)。我们来快速浏览一下playbook:

k8s模块:

kubectl:

playbook的主要功能是用于将服务和资源部署到集群。

因为我们需要在执行时将数据动态地注入到定义文件中,所以需要使用我们的定义文件作为模板,其中可以从外部提供变量。

为此,Ansible提供了查找功能,你可以在其中传递有效的YAML文件作为模板。Ansible支持多种将变量注入到模板的方法。在本特定实验中,我们将使用命令行方法。

步骤二:安装Jenkins、Ansible和Docker

现在,我们可以安装Ansible,并通过它来自动部署一个Jenkins服务器和Docker运行时环境。我们还需要安装openshift Python模块以启用与Kubernetes的Ansible连接。

Ansible的安装过程非常简单,只需安装Python,然后使用pip安装Ansible就可以了:

1. 登录到Jenkins实例。

2. 安装Python 3、Ansible和openshift模块:

 sudo apt update && sudo apt install -y python3 && sudo apt install -y python3-pip && sudo pip3 install ansible && sudo pip3 install openshift  

3. 默认情况下,pip会将二进制文件安装在用户主文件夹中的一个隐藏目录下。我们需要将此目录添加到$PATH变量中,以便轻松调用命令:

 echo "export PATH=$PATH:~/.local/bin" >> ~/.bashrc && . ~/.bashrc  

4. 安装部署Jenkins实例所需的Ansible角色:

 ansible-galaxy install geerlingguy.jenkins  

5. 安装Docker角色:

 ansible-galaxy install geerlingguy.docker  

6. 创建一个playbook.yaml文件并添加以下内容:

7. 通过以下命令运行该playbook文件:

 ansible-playbook playbook.yaml.  

这里要注意,我们实例使用的公共IP地址作为Jenkins使用的主机名。如果你使用的是DNS,那么可能需要替换成实例的DNS名称。另外,请注意,在运行playbook之前,必须在防火墙上启用8080端口 (如果有的话)。

8. 几分钟后,Jenkins应该就安装好了。你可以通过导航到计算机的IP地址(或者DNS名称)并指定8080端口来进行检查:

9. 单击“登陆”,填写“admin”作为用户名和密码。注意,这些是我们使用Ansible角色设置的默认凭据,在生产环境中使用Jenkins时,你可以(也应该)对其进行更改。可以通过设置角色变量来完成。详情参见角色官方页面。

官方页面:

10. 你最后要做的是安装将在实验中使用到的以下插件:git、pipeline、CloudBees Docker Build和Publish、GitHub

步骤三:配置Jenkins用户以连接到集群

如上所述,本实验假设你已经安装并运行了一个Kubernetes集群。为了使Jenkins能够连接到这个集群,我们需要添加必要的kubeconfig文件。在本特定实验中,我们将使用一个托管在Google Cloud上的Kubernetes集群,因此我们将使用gcloud命令。你的具体情况可能有所不同。但在所有情况中,我们都必须将kubeconfig文件复制到Jenkins的用户目录,如下所示:

请注意,你在此处使用的帐户必须具有创建和管理部署(Deployments)和服务(Services)所需的权限。

步骤04:创建Jenkins Pipeline 作业

创建一个新的Jenkins作业并选择管道类型。作业设置如下所示:

我们更改的设置是:

  • 我们使用Poll SCM作为构建触发器,此设置将会指导Jenkins定期检查Git存储库(按*****指示的每分钟检查一次)。如果仓库自上次poll以来已经更改过,那么将触发作业。
  • 在管道本身,我们指定了存储库URL和凭据。分支是master。
  • 在本实验中,我们将作业的所有代码添加到一个Jenkinsfile中,该文件与代码存储在相同的存储库中。本文稍后将讨论该Jenkinsfile。

步骤五:为GitHub和Docker Hub配置Jenkins凭据

转到/credentials/store/system/domain/_/newCredentials并将凭据添加到两个目标中。确保提供有效的ID和描述,因为稍后将引用它们:

/credentials/store/system/domain/_/newCredentials:

步骤六:创建Jenkinsfile

Jenkinsfile指导Jenkins如何构建、测试、docker化、发布并交付我们的应用程序。我们的Jenkinsfile如下所示:

 pipeline {   agent any   environment {       registry = "magalixcorp/k8scicd"       GOCACHE = "/tmp"   }   stages {       stage('Build') {           agent {               docker {                   image 'golang'               }           }           steps {               // Create our project directory.               sh 'cd ${GOPATH}/src'               sh 'mkdir -p ${GOPATH}/src/hello-world'               // Copy all files in our Jenkins workspace to our project directory.                              sh 'cp -r ${WORKSPACE}/* ${GOPATH}/src/hello-world'               // Build the app.               sh 'go build'                         }           }       stage('Test') {           agent {               docker {                   image 'golang'               }           }           steps {                               // Create our project directory.               sh 'cd ${GOPATH}/src'               sh 'mkdir -p ${GOPATH}/src/hello-world'               // Copy all files in our Jenkins workspace to our project directory.                              sh 'cp -r ${WORKSPACE}/* ${GOPATH}/src/hello-world'               // Remove cached test results.               sh 'go clean -cache'               // Run Unit Tests.               sh 'go test ./... -v -short'                      }       }       stage('Publish') {           environment {               registryCredential = 'dockerhub'           }           steps{               script {                   def appimage = docker.build registry + ":$BUILD_NUMBER"                   docker.withRegistry( '', registryCredential ) {                       appimage.push()                       appimage.push('latest')                   }               }           }       }       stage ('Deploy') {           steps {               script{                   def image_id = registry + ":$BUILD_NUMBER"                   sh "ansible-playbook  playbook.yml --extra-vars \"image_id=${image_id}\""               }           }       }   }}  

文件构建起来比看上去容易。管道基本上包含四个阶段:

1. 在Build阶段构建Go二进制,并且确保在构建过程中没有任何错误。

2. 在Test阶段应用简单的UAI测试来确保应用程序按预期工作。

3. 在Publish阶段构建Docker镜像,并将其推送到注册表。此后,其可供任何环境使用。

4. 在Deploy阶段调用Ansible来联系Kubernetes并应用定义文件。

现在,我们来讨论一下此Jenkinsfile的关键部分。

前两个阶段大致相似。它们都是用了golang Docker镜像来构建应用程序。让阶段在已经准备好所有必需的构建和测试工具的Docker容器中运行始终是一个好习惯。另一个选择是在主服务器上或者从服务器上安装这些工具。当你需要针对不同的工具版本进行测试时,问题就来了。例如,我们可能希望使用Go 1.9构建和测试我们的代码,因为我们的应用程序还没有准备好使用最新版本的Golang。镜像中包含所有内容,因此更改版本甚至镜像类型就变得像更改字符串一样简单。

Publish阶段(从第42行开始)首先指定一个环境变量,该变量将在后面的步骤中使用。该变量指向在先前步骤中添加到Jenkins的Docker Hub凭据的ID。

第48行:我们使用docker插件来构建镜像。它默认在注册表中使用Dockerfile,并将构建编号添加为镜像标签。当你需要确定哪个Jenkins构建是当前正在运行的容器的源代码时,这就变得非常重要。

第49-51行:镜像构建成功之后,我们使用构建编号将其推入Docker Hub。此外,我们将“最新的”标签添加到镜像中(第二个标签),这样一来,如果用户需要,就可以在不指定构建编号的情况下提取镜像。

第56-60行: 在部署阶段,我们将部署和服务定义文件应用到集群。我们使用之前讨论过的playbook来调用Ansible。需要注意,我们将image_id作为命令行变量传递。此值将自动替换部署文件中的镜像名称。

测试我们的CD管道

本文的最后一部分进行了实例测试。我们将把代码提交给GitHub,并确保我们的代码能够在管道中顺利运行到集群:

1. 添加文件:

 git add *  

2. 提交更改:

 git commit -m "Initial commit"  
 3.推送至GitHub:  
 git push4.  在Jenkins上,我们可以等待作业自动触发,也可以直接点击“立刻构建(Build Now)”。  

5. 如果作业运行成功,我们可以使用以下命令来检查部署的应用程序。

6. 获取节点IP地址:

7. 现在,让我们向应用程序发起一个HTTP请求:

可以看到,我们的应用程序运行正常。下面,让我们在代码中故意犯一个错误,并确保管道不会将错误代码发送到目标环境:

将应该显示的消息更改为“Hello World!”(我们将每个单词的第一个字母大写,并在后面加上感叹号)。因为我们的客户可能不希望消息以这种方式显示,所以管道应该在Test测试阶段停止。

首先,我们进行了更改。现在,main.go文件应如下所示:

接下来,让我们提交并推送代码:

回到Jenkins,我们可以看到最后一次构建失败了:

通过单击失败的作业,我们可以看到其失败的原因:

该错误代码永远不会到达目标环境。

TL;DR

  • CI/CD是所有遵循Agile方法的现代环境的组成部分。
  • 通过管道,你能够确保代码从版本控制系统到目标环境(testing/staging/production/etc.)的平稳过渡,同时应用所有必要的测试和质量控制实践。
  • 在本文中,我们进行了真实的实验,构建了一个持续交付管道来部署Golang应用程序。
  • 通过Jenkins,我们能够从存储库中提取代码,使用相关的Docker镜像对其进行构建和测试。
  • 接下来,我们对应用程序进行Docker化并将其推送到Docker Hub——因为它通过了我们的测试。
  • 最后,我们使用Ansible将应用程序部署到运行Kubernetes的目标环境中。
  • 使用Jenkins管道和Ansible令更改工作流变得非常简单和灵活,几乎不会产生什么摩擦问题。例如,我们可以在Test阶段中添加更多的测试,可以更改用于构建和测试代码的Go版本,还可以使用更多的变量来更改部署和服务中的其他方面。
  • 最棒的一点是我们使用了Kubernetes部署,这能够确保在更改容器镜像时,应用程序的停机时间为0。因为Deployments在默认情况下使用滚动更新方法来终止和重新创建容器。只有当新容器启动并且运行状况良好时,Deployment才会终止旧容器。

原文:

文章来源:智云一二三科技

文章标题:一文告诉你如何借助Kubernetes、Ansible和Jenkins创建CD管道

文章地址:https://www.zhihuclub.com/97879.shtml

关于作者: 智云科技

热门文章

网站地图