目录
起步
本指南会向您介绍Jenkins的基本使用以及主要功能——Jenkins管线。本指南使用“独立”Jenkins分布模式,在您的机器本地执行。
准备工作
对本教程来说,您需要:
- 一台包含如下配件的机器:
- 256MB内存,不过推荐512MB及以上内存
- 10GB存储空间(给Jenkins和Docker镜像使用)
- 需要安装如下软件:
- Java 8(JRE或Java开发工具包(JDK)都可以)
- Docker(访问Docker官网下载对应您的平台的版本)
下载和运行Jenkins
- 下载Jenkins。
- 在下载目录打开命令行终端。
- 执行
java -jar jenkins.war --httpPort=8080
。 - 浏览器访问
http://localhost:8080
。 - 根据提示完成安装。
在安装完成后,您的Jenkins就可以开始工作了!
创建您的第一个管线(Pipline)
什么是Jenkins管线?
Jenkins管线(或简称为“管线”)是一组支持通过Jenkins实现和集成持续交付管线的插件。
持续交付管线是一种自动化表达式,用来处理在用户或客户的版控系统中获取软件代码。
Jenkins管线提供了一系列可扩展的工具用于“以代码的形式”对简单到复杂的发送管线的建模。Jenkins管线的定义通常会写入文本文件中(称为Jenkinsfile
),并轮流签入一个项目的源码控制仓库。1
对于管线和Jenkinsfile
到底是什么的更多信息,请分别参考用户手册中的管线和Jenkinsfile。
使用管线的快速起步:
- 复制下述实例到您的仓库,并将其命名为
Jenkinsfile
- 点击Jenkins中的New Item(新建项)
- 给您的新项起名(如“My Pipeline”),并选择Multibranch Pipeline(多分支管线)
- 点击**Add Source(添加源)**按钮,选择您要的仓库的类型并添加详细信息。
- 点击**Save(保存)**按钮并欣赏您第一个管线的执行!
您可能需要修改示例Jenkinsfile
文件中的某写地方以使其可以在您的项目上运行。尝试修改sh
命令以在您本地机器上执行相应的命令。
在您设置了您的管线之后,Jenkins将自动检测您仓库中任何新加入的分支或拉取请求并为它们执行该管线。
快速起步实例
下面是简单的可复制粘贴的对多种语言的管线实例。Java
声明式管线:
pipeline {
agent { docker { image 'maven:3.3.3' } }
stages {
stage('build') {
steps {
sh 'mvn --version'
}
}
}
}
脚本式管线(进阶):
/* 依赖Docker Pipeline插件 */
node('docker') {
checkout scm
stage('Build') {
docker.image('maven:3.3.3').inside {
sh 'mvn --version'
}
}
}
Node.js / JavaScript
声明式管线:
pipeline {
agent { docker { image 'node:6.3' } }
stages {
stage('build') {
steps {
sh 'npm --version'
}
}
}
}
脚本式管线(进阶):
/* 依赖Docker Pipeline插件 */
node('docker') {
checkout scm
stage('Build') {
docker.image('node:6.3').inside {
sh 'npm --version'
}
}
}
Ruby
声明式管线:
pipeline {
agent { docker { image 'ruby' } }
stages {
stage('build') {
steps {
sh 'ruby --version'
}
}
}
}
脚本式管线(进阶):
/* 依赖Docker Pipeline插件 */
node('docker') {
checkout scm
stage('Build') {
docker.image('ruby').inside {
sh 'ruby --version'
}
}
}
Python
声明式管线:
pipeline {
agent { docker { image 'python:3.5.1' } }
stages {
stage('build') {
steps {
sh 'python --version'
}
}
}
}
脚本式管线(进阶):
/* 依赖Docker Pipeline插件 */
node('docker') {
checkout scm
stage('Build') {
docker.image('python:3.5.1').inside {
sh 'python --version'
}
}
}
PHP
声明式管线:
pipeline {
agent { docker { image 'php' } }
stages {
stage('build') {
steps {
sh 'php --version'
}
}
}
}
脚本式管线(进阶):
/* 依赖Docker Pipeline插件 */
node('docker') {
checkout scm
stage('Build') {
docker.image('php').inside {
sh 'php --version'
}
}
}
执行多个步骤
管线由多个步骤组成,从而使您可以构建、测试和部署应用。Jenkins管线允许您以一种简单的方式集成多个步骤,这可以帮助您对任意自动化处理组合进行建模。
可以将一个“步骤”想象成处理某个动作的一行命令。每个步骤执行成功后就会移向下一个步骤。如果某一步没有正确执行,整个管线的执行就会失败。
当一个管线的所有步骤都成功完成了,这个管线也就算是成功执行了。
Linux、BSD及Mac OS
在Linux、BSD和Mac OS等(类Unix)系统上,sh
步骤用于在管线内执行shell命令。
声明式管线:
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'echo "Hello World"'
sh '''
echo "Multiline shell steps works too"
ls -lah
'''
}
}
}
}
脚本式管线(进阶):
node {
stage('Build') {
sh 'echo "Hello World"'
sh '''
echo "Multiline shell steps works too"
ls -lah
'''
}
}
Windows
基于Windows的操作系统应该使用bat
步骤来执行批处理命令。
声明式管线:
pipeline {
agent any
stages {
stage('Build') {
steps {
bat 'set'
}
}
}
}
脚本式管线(进阶):
node {
stage('Build') {
bat 'set'
}
}
超时和重试…
有一些功能强大的步骤可以“封装”其他步骤,从而可以轻松解决像重试(retry
)步骤直至成功,或在某个步骤耗时太久(timeout
)时将其退出等问题。
声明式管线:
pipeline {
agent any
stages {
stage('Deploy') {
steps {
retry(3) {
sh './flakey-deploy.sh'
}
timeout(time: 3, unit: 'MINUTES') {
sh './health-check.sh'
}
}
}
}
}
脚本式管线(进阶):
node {
stage('Deploy') {
retry(3) {
sh './flakey-deploy.sh'
}
timeout(time: 3, unit: 'MINUTES') {
sh './health-check.sh'
}
}
}
“Deploy”阶段会尝试3次flakey-deploy.sh
脚本,并等待3分钟health-check.sh
脚本的执行。如果健康检查脚本在3分钟之后还没有执行完毕,管线就会标记“Deploy”阶段为已失败。
“封装器”步骤,如timeout
及retry
可能会包含其他步骤,包括timeout
或retry
自身。
我们可以将这些步骤组合起来。例如,假设我们的部署可以尝试5次,但总耗时不许超过3分钟,否则该阶段失败:
声明式管线:
pipeline {
agent any
stages {
stage('Deploy') {
steps {
timeout(time: 3, unit: 'MINUTES') {
retry(5) {
sh './flakey-deploy.sh'
}
}
}
}
}
}
脚本式管线(进阶):
node {
stage('Deploy') {
timeout(time: 3, unit: 'MINUTES') {
retry(5) {
sh './flakey-deploy.sh'
}
}
}
}
临结束
在管线执行完毕后,您可能需要执行清理步骤或执行一些基于管线执行结果的动作。这些动作可以在post
部分发挥作用。
声明式管线:
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'echo "失败!"; exit 1'
}
}
}
post {
always {
echo '我总是会执行'
}
success {
echo '我只会在成功时运行'
}
failure {
echo '我只会在失败时运行'
}
unstable {
echo '我只在执行被标记为不稳定时运行'
}
changed {
echo '我只会在管线的状态改变时运行'
echo '例如,管线在之前执行失败但现在成功了'
}
}
}
脚本式管线(进阶):
node {
try {
stage('Test') {
sh 'echo "失败!"; exit 1'
}
echo '我只会在成功时执行'
} catch (e) {
echo '我只会在失败时执行'
// 由于我们为了输出报告而捕获了异常,
// 为了确保构建被标记为失败,我们需要重抛这个异常
throw e
} finally {
def currentResult = currentBuild.result ?: 'SUCCESS'
if (currentResult == 'UNSTABLE') {
echo '我只在执行被标记为不稳定时运行'
}
def previousResult = currentBuild.previousBuild?.result
if (previousResult != null && previousResult != currentResult) {
echo '我只会在管线的状态改变时运行'
echo '例如,管线在之前执行失败但现在成功了'
}
echo '我总是会执行'
}
}
在学习过定义多步骤之后,让我们继续学习“定义执行环境”吧。
定义执行环境
在上一节,您可能注意到了每个示例中的agent
指令。agent
指令告诉Jenkins在何处以及如何执行管线或其自集。如你所想,agent
在所有管线中都是必填项。
在表象之下,agent
会导致一些事情的发生:
- 代码块中包含的所有步骤会形成队列用于被Jenkins执行。在一个executor(执行器)可用的时候,这些步骤就会开始执行。
- 会申请一个workspace(工作空间)用于存放自代码控制系统签出的文件及其他管线工作需要的附加工作文件。
在管线中有许多定义agent的方式,在本教程中,我们将只关注使用短暂Docker容器。
管线被设计为可以简单地使用Docker容器和镜像并在其中运行。这使得管线可以不用在agent里配置一系列系统工具和依赖的前提下定义所需的环境和工具。这种方法使得您可以使用几乎所有可以被打包进Docker容器的工具。
更多关于agent的技术选项,请查看语法参考。
声明式管线:
pipeline {
agent {
docker { image 'node:7-alpine' }
}
stages {
stage('Test') {
steps {
sh 'node --version'
}
}
}
}
脚本式管线(进阶):
node {
/* 需要已安装Docker Pipeline插件 */
docker.image('node:7-alpine').inside {
stage('Test') {
sh 'node --version'
}
}
}
在管线执行时,Jenkins将自动启动指定的容器并在其中执行所定义的步骤:
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] sh
[guided-tour] Running shell script
+ node --version
v7.4.0
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
混合或匹配不同的容器或agent,为管线的执行提供了极大的灵活性。要学习更多配置选项,请继续阅读“使用环境变量”。
使用环境变量
全局变量可以如下文示例所示全局设定,也可以分阶段设定。如你所想,按阶段定义环境变量意味着它们将只会在它们所在的阶段有效。
声明式管线:
pipeline {
agent any
environment {
DISABLE_AUTH = 'true'
DB_ENGINE = 'sqlite'
}
stages {
stage('Build') {
steps {
sh 'printenv'
}
}
}
}
脚本式管线(进阶):
node {
withEnv(['DISABLE_AUTH=true',
'DB_ENGINE=sqlite']) {
stage('Build') {
sh 'printenv'
}
}
}
在Jenkinsfile
中定义环境变量的方法会在执行脚本时非常有用,例如对于Makefile
,可以在Jenkins中使用不同的参数配置构建和测试。
另一个环境变量的常用场景是在构建或测试脚本里设置或重写“空包”证书。由于(很明显)直接给Jenkinsfile
传入证书是个坏主意,Jenkins管线允许用户又快又安全地访问Jenkinsfile
里预定义的证书而不需要了解其值。
环境中的证书
记录测试和打包
尽管测试是一个好的持续交付管线的重要组成,多数用户并不想在数以千计的控制台输出中筛选以查找有关失败测试用例的信息。为了简化这一点,只要您的测试执行器可以输出测试结果文件,Jenkins就可以记录和汇集测试结果。Jenkins通常同捆绑定junit
步骤,但如果您的测试执行器不能输出JUnit风格的XML报告,我们也有额外的插件用于处理几乎所有广泛使用的测试报告格式。
为了手机我们的打包和测试结果,我们需要使用post
片段。
声明式管线:
pipeline {
agent any
stages {
stage('Test') {
steps {
sh './gradlew check'
}
}
}
post {
always {
junit 'build/reports/**/*.xml'
}
}
}
脚本式管线(进阶):
node {
try {
stage('Test') {
sh './gradlew check'
}
} finally {
junit 'build/reports/**/*.xml'
}
}
Jenkins将总是会追踪它们,收集测试结果、计算趋势并报告。管线的测试如果没能通过则会被标记为“UNSTABLE(不稳定的)”,在web UI上以黄色标注。这和“FAILED(失败)”状态不同,失败会标记为红色。
当测试失败时,抓取当前Jenkins的打包进行本地分析和调查是常常是很有用的。Jenkins内建的对“打包结果”以及管线执行期间产生文件的保存功能就派上用场了。
使用archiveArtifacts
步骤和一个文件通配符表达式就能很简单地实现这一功能,如下文示例所示:
声明式管线:
pipeline {
agent any
stages {
stage('Build') {
steps {
sh './gradlew build'
}
}
stage('Test') {
steps {
sh './gradlew check'
}
}
}
post {
always {
archiveArtifacts artifacts: 'build/libs/**/*.jar', fingerprint: true
junit 'build/reports/**/*.xml'
}
}
}
脚本式管线(进阶):
node {
try {
stage('Test') {
sh './gradlew check'
}
} finally {
archiveArtifacts artifacts: 'build/libs/**/*.jar', fingerprint: true
junit 'build/reports/**/*.xml'
}
}
如果在archiveArtifacts
步骤里设置了多于1个的参数,那么每个参数的名字必须在该步骤里明确指定,例如,artifactss
代表打包的路径和文件名,fingerprint
用于使用该选项。如果您只需要指定打包路径和文件名,那么就可以省略参数名artifacts
了,就像这样:
archiveArtifacts 'build/libs/**/*.jar'
在Jenkins里记录测试和打包信息对简单快速地向团队成员提供信息是很有用的。在下一节我们将讨论关于如何告诉这些团队成员在我们的管线里发生过什么。
清理和通知
由于管线的post
部分会保证其在管线之行之后运行,我们可以添加一些通知或其他步骤以实现最终确定、通知或其他后管线任务。
声明式管线:
pipeline {
agent any
stages {
stage('No-op') {
steps {
sh 'ls'
}
}
}
post {
always {
echo '无论如何,我的执行完成了'
deleteDir() /* 清理工作空间 */
}
success {
echo '我成功执行了!'
}
unstable {
echo '我是不稳定的 :/'
}
failure {
echo '我失败了 :('
}
changed {
echo '事情和以前不一样了哦…'
}
}
}
脚本式管线(进阶):
node {
try {
stage('No-op') {
sh 'ls'
}
}
catch (exc) {
echo '我失败了'
}
finally {
if (currentBuild.result == 'UNSTABLE') {
echo '我是不稳定的 :/'
} else {
echo '不论如何,我的执行完成了'
}
}
}
发送通知的手段有很多种,下面是在管线中发送通知的一些示例片段,包括电子邮件、Hipchat房间和Slack频道。
post {
failure {
mail to: 'team@example.com',
subject: "管线执行失败:${currentBuild.fullDisplayName}",
body: "有些地方发生了错误:${env.BUILD_URL}"
}
}
Hipchat
post {
failure {
hipchatSend message: “注意 @here ${env.JOB_NAME} #${env.BUILD_NUMBER} 执行失败了。",
color: 'RED'
}
}
Slack
post {
success {
slackSend channel: '#ops-room',
color: 'good',
message: "管线${currentBuild.fullDisplayName} 的执行完全成功。"
}
}
现在团队就可以在失败、不稳定或甚至是成功的时候收到通知了。我们将完成我们持续交付管线的最令人激动的部分:部署!
部署
最基本的持续交付管线应该,至少,拥有三个阶段,定义在Jenkinsfile
中:Build(构建)、Test(测试)和Deploy(部署)。本节我们将主要关注部署阶段,但一定要记住稳定的构建和测试阶段对任何部署行为都是重要的前提。
声明式管线:
pipelien {
agent any
stages {
stage('Build') {
steps {
echo '正在构建'
}
}
stage('Test') {
steps {
echo '正在测试'
}
}
stage('Deploy') {
steps {
echo '正在部署'
}
}
}
}
脚本式管线(进阶):
node {
stage('Build') {
echo '正在构建'
}
stage('Test') {
echo '正在测试'
}
stage('Deploy') {
echo '正在部署'
}
}
用阶段定义部署环境
一种常用的模式是扩展阶段的数量以附加部署环境,如“演习环境”或“生产环境”,如下文所示。
stage('Deploy - Staging') {
steps {
sh './deploy staging'
sh './run-smoke-tests'
}
}
stage('Deploy - Production') {
steps {
sh './deploy production'
}
}
在本示例中,我们假定所有的“冒烟测试”都通过我们的./run-smoke-tests
脚本来执行,从而保证足够的效率并验证其可否作为生产环境的一个发行版。此类自动部署代码到生产环境的管线可以看做是对“持续部署”的一种实现。尽管这是一个不错的主意,但针对为何持续部署也许并不适合实践也有许多人提出了不错的理由,尽管如此他们仍然可以得到持续发送带来的好处。2
询问用户输入并处理
在通过阶段与阶段之间时,特别是环境阶段,您通常可能会希望用户输入然后再继续。例如,判断应用是否足够完善从而可以被“晋升”至生产环境。我们可以通过input
步骤来完成这一点。下文所示的例子中,“Sanity check”步骤会阻塞并等待输入,在用户确认之前处理不会继续。
声明式管线:
pipeline {
agent any
stages {
/* 省略了“build”和“Test”阶段 */
stage('Deploy - Staging') {
steps {
sh './deploy staging'
sh './run-smoke-tests'
}
}
stage('Sanity check') {
steps {
input "Does the staging environment look ok?"
}
}
stage('Deploy - Production') {
steps {
sh './deploy production'
}
}
}
}
脚本式管线(进阶):
node {
/* 省略了“build”和“Test”阶段 */
stage('Deploy - Staging') {
sh './deploy staging'
sh './run-smoke-tests'
}
stage('Sanity check') {
input "Does the staging environment look ok?"
}
stage('Deploy - Production') {
sh './deploy production'
}
}
结尾
本教程向您介绍了使用Jenkins和Jenkins管线的基础知识。由于Jenkins是极度可扩展的,它可以通过修改和配置以支持几乎任意的自动化相关部分。要学习更多关于Jenkins可以做什么,您可以参考用户手册,或Jenkins博客来了解最新的事件、教程和更新。