Jenkins是一个开源自动化服务器,允许您构建管道以自动化构建,测试和部署应用程序的过程。在本指南中,您将实施基本工作流程,以加快持续集成和持续交付(CI / CD)过程。
sudo apt-get update && sudo apt-get upgrade
注意 本教程是为非root用户编写的。需要提升权限的命令以
sudo
为前缀。
本指南面向DevOps专业人士,因此假定:
在自动化工作流程之前,有必要了解基本的CI / CD过程。下图说明了这一点:
最基本的过程包括三个阶段:构建,测试,部署。每次在分布式版本控制系统上进行更改时,都会在Jenkins服务器上触发自动化循环。运行该流程的整套说明Jenkinsfile
位于源存储库的根目录中。该单个文件告诉服务器该做什么,何时做以及如何执行这些任务。
如前一节所述,自动化过程首先提交版本控制系统。
在GitHub中创建一个新的存储库。本指南将使用一个简单的Node.js应用程序来展示Jenkins管道的工作原理。选择.gitignore
相应的,不要忘记用以下内容初始化它README
:
将新存储库克隆到本地工作站:
git clone git@github.com:<GITHUB_USERNAME>/jenkins-guide.git
打开您喜欢的文本编辑器,并app.js
在存储库的根目录下创建该文件。添加以下内容:
~/jenkins-guide/app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 'use strict'; const express = require('express'); const app = express(); // Server connection const PORT = 9000; const HOST = '0.0.0.0'; // Application content const os = ['Windows','macOS','Linux'] // Web Server app.get('/',function(req,res) { res.json(os); }); // Console output app.listen(PORT, HOST); console.log(`Running on http://${HOST}:${PORT}`); |
---|
此应用程序使用Express Web服务器在端口9000上向浏览器提供单个JSON输出。接下来,保存test.js
到存储库根目录的相同位置。
~/jenkins-guide/ test.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | var supertest = require("supertest"); var should = require("should"); var server = supertest.agent("http://nodeapp-dev:9000"); // Unit Test describe("Webapp Status",function(){ // Test 1 - HTTP status it("Expect HTTP status 200",function(done){ server .get("/") .expect("Content-type",/text/) .expect(200) .end(function(err,res){ res.status.should.equal(200); done(); }); }); // Test 2 - Control Tests it("Mocha Control Test",function(done){ (1).should.be.exactly(1).and.be.a.Number(); done(); }); }); |
---|
这是一个使用supertest
和的简化测试套件should
。它只有两个测试:第一个检查HTTP状态,它预计为200.第二个不是真正的测试,而是一个总是通过的控件。
这个例子将使用两个Docker容器,一个用于app.js
使用Express,另一个用于使用Mocha的测试套件。每个图像都有自己的文件夹,其中包含相应的Dockerfile
和package.json
。
Dockerfile
和package.json
为express-image
。 ~/jenkins-guide/express-image/DockerfileFROM node:6-alpine
# Create a server directory
RUN mkdir -p /home/node/app
WORKDIR /home/node/app
# Install server dependencies
COPY /express-image/package.json /home/node/app
RUN npm install
# Copy node Application
COPY app.js /home/node/app
# Open port
EXPOSE 9000
CMD ["npm", "start"]
app.js
启动时默认运行此映像。您可以将其视为Web应用程序的“dockerized”版本。
package.json
将项目目录根目录中的文件复制到新映像中: ~/jenkins-guide/express-image/package.json{
"name": "express-image",
"version": "1.0.0",
"description": "Example Node Application",
"author": "Your name",
"repository": {
"type": "git",
"url": "git+https://github.com/<YOUR_USERNAME>/<REPOSITORY_NAME>.git"
},
"license": "ISC",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.13.3"
}
}
Dockerfile
为test-image
: ~/jenkins-guide/test-image/DockerfileFROM node:6-alpine
# Create feports directory
RUN mkdir -p /JUnit
# Create a server directory
RUN mkdir -p /home/node/tests
WORKDIR /home/node/tests
# Install app dependencies
COPY /test-image/package.json /home/node/tests
RUN npm install
# Copy test source
COPY test.js /home/node/tests
EXPOSE 9000
CMD ["npm", "test"]
此映像创建一个报告文件夹并从中安装依赖项package.json
。一开始,它执行Mocha测试。
此JSON文件包含所有必需的依赖项,包括mocha-junit-reporter
Jenkins将用于测试存储所需的依赖项。请注意,测试脚本配置了mochaFile
使用图像中指定的图像报告文件夹的选项Dockerfile
。 您的最终项目分发将类似于:
注意:文件夹结构的方法和两个Docker容器的实现是不寻常的,但出于教学原因用于展示Jenkins Pipeline功能。
在开始真正的自动化过程之前,首先需要了解要自动化的内容。
nodeapp-dev
容器。该标志--network
用于避免与其他容器网络冲突。请注意,端口9000已打开,并且-d
标志用于在分离模式下运行它。一旦启动,您可以打开浏览器并输入地址:http://localhost:9000
进行检查。 sudo docker run --name nodeapp-dev --network="bridge" -d -p 9000:9000 nodeapp-dev:trunk test-image
容器。--link
为了与之通信,使用相同的网络以及标志非常重要nodeapp-dev
。您会注意到容器的报告文件夹JUnit
将安装在当前的存储库根目录中。这是reports.xml
在主机上编写的必要条件。使用-it
标志以交互模式运行它以将结果输出到stdout
。 sudo docker run --name test-image -v $PWD:/JUnit --network="bridge" --link=nodeapp-dev -it -p 9001:9000 test-image:latest npm run mocha sudo -i
)并在分离模式下再次运行它以测试JUnit
输出。reports.xml
之后应保存该文件。 sudo docker rm -f test-image sudo docker run --name test-image -v $PWD:/JUnit --network="bridge" --link=nodeapp-dev -d -p 9001:9000 test-image:latestsudo -i
如有必要,停止使用两个容器。 sudo docker stop test-image nodeapp-dev您刚刚完成了这个虚构Web应用程序的整个构建,测试和部署过程。现在是时候实现自动化了。
Jenkins提供了许多安装选项:
jenkins.war
从项目的站点下载自执行文件。这是一个快速有效的解决方案,可以与Jenkins一起使用,只需要很少的先决条件,但更难以维护和更新。
使用Jenkins项目维护的包允许您使用比分发包管理器中包含的版本更新的版本。
sources.list
: sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'sudo service jenkins
与选择start
,stop
,restart
,或status
。启动您的服务以检查安装: sudo service jenkins startjenkins
用户添加到Docker组时,您在技术上授予其root
权限。
Jenkins为Jenkinsfile
语法提供了两种不同的选择:
两者都支持持续交付和Jenkins插件。脚本语法基于Groovy编程环境,因此更加完整。另一方面,声明性语法“的创建是为了提供一种更简单,更具见解性的语法来创作Jenkins管道”,因此适用于日常自动化构建。您可以在Jenkins文档中了解有关语法比较的更多信息。
本指南将使用Declarative语法来说明Jenkins进程,因为它的设计更易于实现和理解。
声明性管道语法非常直观。最基本的布局类似于下面所示的布局:
pipeline
:所有文件应从顶部的此声明开始。它表示新管道的开始。agent
:定义工作环境,通常是Docker镜像。该any
语句表明管道可以使用任何可用的代理。stages
:这个块是stage
指令的集合。stage
:组一个或多个steps
。您可以根据需要使用多个阶段,当您在需要“每个阶段”进行详细调试的复杂模型中工作时,这非常有用。steps
:在这里你定义你的行动。一个阶段可以分组许多步骤,每个步骤通常链接到一个特定的任务/命令。代码块由大括号({
和}
)分隔,不使用分号。每个陈述都必须在它自己的行中,而Jenkinsfile
你所执行的步骤的核心。一些常见的步骤是:
所有这些操作都可以在您内部执行,agent
或者您也可以指示Jenkins通过SSH远程执行任何操作。如您所见,有无尽的自动化可能性。在一个简单的场景中,只有一个顺序执行其阶段的管道足以实现所需的最终状态,但您可以定义管道以在需要时并行运行。有关Jenkins声明性流水线语法的详细信息,请参阅官方文档。
Jenkinsfile
在jenkins-guide
工作站的目录中创建第一个。这只是一个模板,但它包含启动管道所需的所有代码: pipeline { agent any stages { stage('Build') { steps { echo 'This is the Build Stage' } } stage('Test') { steps { echo 'This is the Testing Stage' } } stage('Deploy') { steps { echo 'This is the Deploy Stage' } } } }从这里,您可以获得以下有价值的信息:1)您的构建号,2)每个步骤的控制台输出,3)选择进一步分析的阶段,4)浏览选项卡,其中包含有关提交更改,测试结果和存储的工件的信息, 5)重放您的构建,6)直观地编辑管道,7)转到您的管道设置。
该Jenkinsfile
模板使用一个非常基本的管道结构,只有三个阶段。您可以根据需要自定义它以适应多个阶段。最终的管道结构由项目复杂性和您必须遵循的开发指南决定。既然您已经了解了Node.js示例,那么您就知道如何设计一个自动化每个阶段的管道。出于本指南的目的,最终的管道应该:
nodeapp-dev
图像以便于分发和手动质量测试。master
分支上执行提交并且测试阶段成功完成时才会运行。JUnit
文件并reports.xml
进行详细分析。nodeapp-prod-golden.tar.gz
压缩图像保存到持久位置。
首先编辑Jenkinsfile并粘贴以下管道。替换<DockerHub Username>
为您自己的信息。
〜/詹金斯导/ Jenkinsfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | pipeline { environment { DOCKER = credentials('docker-hub') } agent any stages { // Building your Test Images stage('BUILD') { parallel { stage('Express Image') { steps { sh 'docker build -f express-image/Dockerfile \ -t nodeapp-dev:trunk .' } } stage('Test-Unit Image') { steps { sh 'docker build -f test-image/Dockerfile \ -t test-image:latest .' } } } post { failure { echo 'This build has failed. See logs for details.' } } } // Performing Software Tests stage('TEST') { parallel { stage('Mocha Tests') { steps { sh 'docker run --name nodeapp-dev --network="bridge" -d \ -p 9000:9000 nodeapp-dev:trunk' sh 'docker run --name test-image -v $PWD:/JUnit --network="bridge" \ --link=nodeapp-dev -d -p 9001:9000 \ test-image:latest' } } stage('Quality Tests') { steps { sh 'docker login --username $DOCKER_USR --password $DOCKER_PSW' sh 'docker tag nodeapp-dev:trunk <DockerHub Username>/nodeapp-dev:latest' sh 'docker push <DockerHub Username>/nodeapp-dev:latest' } } } post { success { echo 'Build succeeded.' } unstable { echo 'This build returned an unstable status.' } failure { echo 'This build has failed. See logs for details.' } } } // Deploying your Software stage('DEPLOY') { when { branch 'master' //only run these steps on the master branch } steps { retry(3) { timeout(time:10, unit: 'MINUTES') { sh 'docker tag nodeapp-dev:trunk <DockerHub Username>/nodeapp-prod:latest' sh 'docker push <DockerHub Username>/nodeapp-prod:latest' sh 'docker save <DockerHub Username>/nodeapp-prod:latest | gzip > nodeapp-prod-golden.tar.gz' } } } post { failure { sh 'docker stop nodeapp-dev test-image' sh 'docker system prune -f' deleteDir() } } } // JUnit reports and artifacts saving stage('REPORTS') { steps { junit 'reports.xml' archiveArtifacts(artifacts: 'reports.xml', allowEmptyArchive: true) archiveArtifacts(artifacts: 'nodeapp-prod-golden.tar.gz', allowEmptyArchive: true) } } // Doing containers clean-up to avoid conflicts in future builds stage('CLEAN-UP') { steps { sh 'docker stop nodeapp-dev test-image' sh 'docker system prune -f' deleteDir() } } } } |
---|
这个完整的Jenkinsfile是使用声明性语法编写的。如果仔细阅读,您会注意到它描述了在上一节中应用程序部署期间使用的相同过程。本节将更详细地分析Jenkins文件。
第一个块定义了一个全局可用的环境变量DOCKER
。您可以告诉它全局适用,因为它位于管道块内但在stage块之外。接下来是agent
一个声明,这意味着Jenkins可以使用任何(服务器)代理。
〜/詹金斯导/ Jenkinsfile
1 2 3 4 5 | pipeline { environment { DOCKER = credentials('docker-hub') } agent any |
---|
该DOCKER
定义通过凭证功能完成。这允许您使用机密登录信息,而不将其包含在Jenkins文件中。要配置此密钥对:
docker-hub
。保存凭据后,您可以在管道中的任何位置使用它们。 在这个例子中的管道,DOCKER = credentials('docker-hub')
创建两个环境变量,DOCKER_USER
并且DOCKER_PWD
可用于登录您的泊坞枢纽帐户。
你会注意到关于parallel
代码块的第一件事是它不言自明 - 它会并行运行子阶段。这对于使用之前使用的相同shell命令构建两个Docker镜像非常有用。每个图像都在其自己的步骤中声明,这也是独立阶段的一部分。
~/jenkins-guide/Jenkinsfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // Building your Test Images stage('BUILD') { parallel { stage('Express Image') { steps { sh 'docker build -f express-image/Dockerfile \ -t nodeapp-dev:trunk .' } } stage('Test-Unit Image') { steps { sh 'docker build -f test-image/Dockerfile \ -t test-image:latest .' } } } post { failure { echo 'This build has failed. See logs for details.' } } } |
---|
关闭平行阶段后,您会遇到post
条件。Post
意味着定义适用于整个BUILD
阶段。在这种情况下,只设置failure
条件,因此只有在BUILD
阶段的任何部分失败时才会运行。配置Jenkins为通信提供的不同工具超出了本指南的范围。
测试阶段也使用并行执行:
~/jenkins-guide/Jenkinsfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // Performing Software Tests stage('TEST') { parallel { stage('Mocha Tests') { steps { sh 'docker run --name nodeapp-dev --network="bridge" -d \ -p 9000:9000 nodeapp-dev:trunk' sh 'docker run --name test-image -v $PWD:/JUnit --network="bridge" \ --link=nodeapp-dev -d -p 9001:9000 \ test-image:latest' } } stage('Quality Tests') { steps { sh 'docker login --username $DOCKER_USR --password $DOCKER_PSW' sh 'docker tag nodeapp-dev:trunk <DockerHub Username>/nodeapp-dev:latest' sh 'docker push <DockerHub Username>/nodeapp-dev:latest' } } } post { success { echo 'Build succeeded.' } unstable { echo 'This build returned an unstable status.' } failure { echo 'This build has failed. See logs for details.' } } } |
---|
的Mocha Tests
阶段开始两个图像并执行自动测试,产生了reports.xml
保存到詹金斯工作区文件。另一方面,该Quality Tests
阶段将trunk
您的应用程序版本发布到Docker Hub。它首先发出Docker登录命令(使用预定义的凭据),然后更改图像标记并推送它。
再次,你有post
代码块,但这次它有成功完成,不稳定和失败的通知。请记住,您可以在此处使用任何代码,而不仅仅是通知。
这个阶段引入了不同类型的块:when
。顾名思义,该子句仅在满足某个条件时才执行。在此示例的情况下,仅在检测到对主分支的更改时才运行代码。提交给其他分支机构不会触发此管道的这一步骤。
在步骤中,您可以选择配置retry
和timeout
参数。我们上面的示例显示了一个嵌套用法,其中图像构建过程的超时为10分钟,并且在计时器到期时总共有三次重试。
该post
块设计用于在发生故障时进行清理。没有为此阶段设置通知。
管道的最后两个阶段相对简单。该junit
语句允许Jenkins使用reports.xml
您的Mocha图像生成的文件,该archiveArtifacts
命令将报告和应用程序文件保存到持久位置。默认情况下,该位置是JENKINS_HOME/var/lib/jenkins/jobs/<REPOSITORY>/branches/master/builds/lastStableBuild
。如果需要,您可以在Jenkins的常规设置中配置自定义位置。
是时候将完整的Jenkins文件提交到Jenkins服务器并触发新管道的运行。为了测试when
前面讨论的块,更改将被推送到不同的分支。
DEPLOY
跳过了阶段,这是预期的。
您可以将Jenkins设置为定期扫描您的存储库。为此,只需再次单击“管道”视图上的齿轮图标,然后单击“ 配置”。有很多选择。查找扫描存储库触发器,如果没有运行,请定期选中此框。您可以选择任意数量的时间,对于此示例,将选择一分钟。
到目前为止,一切都应该按预期工作而不会出错。但是遇到错误会发生什么?
app.js
在本地工作站中编辑。在服务器上,更改根地址/
用/ERROR
。这将导致express
服务器上的错误404 (找不到页面),因此测试将失败。~/jenkins-guide/app.js
1 2 3 4 5 | // Web Server app.get('/ERROR',function(req,res) { res.json(os); }); |
---|
app.js
文件并保存。
现在,在BUILD
舞台上引发错误。
express-image/package.json
。将Express包名称更改express-ERROR
为模拟错误输入。~/jenkins-guide/express-image/package.json "dependencies": { "express-ERROR": "^4.13.3" }
BUILD
舞台,然后单击Shell脚本以查看控制台输出:express-image/package.json
。
将trunk
分支合并到master
。这将触发整个管道的运行,包括部署阶段:
git checkout master
git merge trunk
git push origin master
Blue Ocean界面仍处于开发阶段,这意味着Jenkins的许多方面都不受新界面的管理。以下是一些最常见的屏幕。
master
分支,您将看到更详细的仪表板: 从这个视图中,您可以查看许多有用的信息,如日志,工件,更改,测试结果的趋势等等。
本指南介绍了Jenkins和Blue Ocean的基本自动化工作流程,但您可以做很多事情。仅举几个可能性:
post
(或任何其他部分)可以从中受益,如电子邮件,松弛,或HipChat通知有用的内置功能。像往常一样,您可以决定触发通知的内容,成功构建,构建失败,更改或自定义条件。agent
的特定stages
,例如一个用于数据库任务,一个用于编译代码,一个用于webapp更新等。
有关此主题的其他信息,您可能需要参考以下资源。虽然提供这些是希望它们有用,但请注意,我们无法保证外部托管材料的准确性或及时性。
想要了解更多信息教程,请前往腾讯云+社区学习更多知识。
参考文献:《https://www.linode.com/docs/development/ci/automate-builds-with-jenkins-on-ubuntu/》