最近用 Jenkins 将原来公司自研的 CICD 工具给替换掉了, Jenkins 本身的文档并不是很明晰,很多问题都需要自己尝试和搜索才能解决,这里简要记录下期间遇到的一些问题的解决方法。
sudo apt update
sudo apt install openjdk-8-jdk
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt update
sudo apt install jenkins
安装完成后通过 systemctl 命令可以查看状态
$ systemctl status jenkins
● jenkins.service - LSB: Start Jenkins at boot time
Loaded: loaded (/etc/init.d/jenkins; generated)
Active: active (exited) since Wed 2018-08-22 13:03:08 PDT; 2min 16s ago
Docs: man:systemd-sysv-generator(8)
Tasks: 0 (limit: 2319)
CGroup: /system.slice/jenkins.service
通过 apt-get 安装启动后, Jenkins 工作目录如下:
/etc/default/jenkins
: 配置文件地址/var/lib/jenkins
: Jenkins 工作目录/var/cache/jenkins
: Jenkins 缓存目录/var/log/jenkins
: Jenkins 日志目录打开 /etc/default/jenkins
文件可以针对我们的要求对相应的配置进行设置,我最常改的设置有两个:
修改端口
# 默认是 8080,我这里改成了 8081
HTTP_PORT=8081
修改用户和用户组
Jenkins 启动时会创建名为 jenkins
的用户名和用户组,这是 Jenkins 默认执行命令的用户,如果采用默认的用户,执行某些操作时可能会报没有权限的问题,因此建议改成自己常用的 user,比如我最常用的用户是 ubuntu
,修改如下:
# 默认是 jenkins
JENKINS_USER = ubuntu
JENKINS_GROUP = ubuntu
启动之前将 Jenkins 的各个目录所属用户和组进行修改,如下:
chown -R ubuntu:ubuntu /var/lib/jenkins
chown -R ubuntu:ubuntu /var/cache/jenkins
chown -R ubuntu:ubuntu /var/log/jenkins
修改完重启 Jenkins 之后就可以通过 http://server_ip:8081 进行访问了,第一次访问会要求输入管理员密码,该密码会在 Jenkins 第一次启动时打印到日志中,从日志中复制出来填入即可。
初次访问一般会提示安装插件,选择安装默认插件即可。
Jenkins pipeline 允许我们定义全局或者 stage 范围的环境变量,定义好通过 env.name 访问即可,对于一些需要在整个 pipeline 中用到的信息,可以考虑用环境变量进行定义,示例如下:
pipeline {
agent any
environment {
GLOBAL_ENV = 'global'
}
stages {
stage('Build') {
environment {
STAGE_ENV = "stage"
}
steps {
echo "${env.GLOBAL_ENV}"
echo "${env.stage}"
}
}
}
}
对于一些动态的内容,可以考虑采用参数的形式,Jenkins pipeline 定义参数有两种形式:
首先可以在 Jenkins 的 Job 配置界面进行参数化构建,选择对应类型的参数,填入参数名、默认值和描述即可,如图:
Jenkins 提供了 parameters
块来定义参数,官方文档示例如下,定义了 string
类型的 PERSON
参数,并设置了默认值为 Mr Jenkins
,然后通过 ${params.PERSON}
即可访问。
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
parameters {
string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
}
stages {
stage('Example') {
steps {
echo "Hello ${params.PERSON}"
}
}
}
}
在 pipeline 中定义的参数,会自动同步到 Jenkins Job 配置界面中显示,如果一个参数同时在配置界面和 pipeline 中进行了定义,那么 pipeline 中定义的默认值会覆盖掉在 Jenkins Job 设置界面设定的值。
理想的方式是在 pipeline 中通过 parameters
定义参数并设置默认值,如果在 Jenkins Job 中没有显式配置参数,则使用默认值,如果进行了显式配置,则使用配置的值,可以通过三元运算符解决这个问题,示例如下:
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
parameters {
string(name: 'PERSON', defaultValue: params.PERSION ?:'Mr Jenkins', description: 'Who should I say hello to?')
}
stages {
stage('Example') {
steps {
echo "Hello ${params.PERSON}"
}
}
}
}
这样每次定义参数前都先判断一下是否已经传递了参数值了,如果已经传递,则参数的值就是传递过来的值,如果没有则采用默认值。
首先需要在 Github 对应的仓库中配置 webhook,地址是 https://jenkins_addr/github-webhook/,
然后选择需要发送请求的事件,我需要在代码 Push 和提交 PR 的时候触发 pipeline 执行,因此勾选了下面两个:
这样在仓库有代码 Push 和 PR 操作的时候就会向我们的 hook 地址发送请求了,每次发送都会在 webhook 下面生成记录,如图所示:
可以在这里手动重新发送便于测试。
首先在 Jenkins 中创建 pipeline,然后在 Github 项目中填入地址,用于标识该 pipeline 监听的项目,如图:
Github Push 的触发需要安装 Github plugin
插件,然后在 Job 的 Build Trigger
中勾选 GitHub hook trigger for GITScm polling
即可,如图:
为了保证触发,项目中必须已经添加了 Jenkinsfile 文件,并在 Job 中指定,如图所示,设置分支和 Jenkinsfile 路径,该 Job 就会监听对应分支的 Github Push 事件。
注意事项
PR 的触发需要使用 GitHub Integration Plugin
插件,安装后在 Job 的 Build Trigger 就会有对应的选项,如下:
可以看到有四种触发的方式,这里选择了 hook 触发,然后在下面选择要监听的事件,这里选择了 打开、关闭 PR 以及 PR 中的 commit 发生变化时执行触发。
除了在 Job 中配置 PR 触发之外,还需要在 Manage Jenkins -> Configure System -> Github Server
配置 Server,如图
Name 可以自定义,API URL 填写默认的 https://api.github.com
即可,token 填写在 Github 中生成的 token,这里有两点要求:
admin:repo_hook
选项Jenkins 可以通过接入 SonarQube 在 CI/CD 过程中执行代码检测并将结果传送到 SonarQube Server 中,具体步骤如下:
Jenkins 需要先安装 SonarQube Scanner for Jenkins
插件,在 Manage Jenkins -> Plugin Manager
下搜索安装即可:
参考官方文档 Get Started in Two Minutes Guide 即可,这里不再赘述。
安装完成后就可以在 Jenkins 中配置 SonarQube Server 的地址了,需要用到 token 进行验证,点击 SonarQube 的用户信息,在安全配置下生成对应的 token 复制即可,如图:
完成 SonarServer 的 安装和 token 生成后,在 Manage Jenkins -> Configure System ->SonarQube servers(只有安装 SonarQube Scanner for Jenkins 插件后才会有该配置项)
中设置 Server 地址,如下:
首先要下载安装 SonarScanner,Linux 下载地址是 sonar-scanner-cli-4.2.0.1873-linux.zip,下载完成解压后放到对应的目录即可,比如我放到了 /opt/sonar_scanner
下。
➜ ~ |> mv sonar-scanner-4.2.0.1873-linux /opt/sonar_scanner
下载完成后在 Manage Jenkins -> Global Tool Configuration -> SonarQube Scanner
下配置,因为我是安装在本地,直接配置工作目录,取好对应的名字即可,如下:
Jenkins 提供了 withSonarQubeEnv 块来执行代码检测。关于 Jenkins 的使用在 SonarQube 中有专门的文档SonarScanner for Jenkins 介绍,还算详细,参考文档照着做就行,这里只简单给两个示例:
对于 Maven 项目可以直接通过 mvn 命令执行,如下:
pipeline {
agent any
stages {
stage('Sonar') {
steps {
script {
# 填入 SonarQube servers 中配置的名称,检测完成后就会自动发送到 SonarQube Server 中。
withSonarQubeEnv('server name'){
sh "mvn clean verify sonar:sonar -DskipTests"
}
}
}
}
}
}
sonar scanner 指定参数执行
上面是 Maven 项目的执行方式,还有更通用的是使用 sonar-scanner
命令指定参数执行,
pipeline {
agent any
stages {
stage('Sonar') {
steps {
script {
# 填入 SonarQube servers 中配置的名称,检测完成后就会自动发送到 SonarQube Server 中。
withSonarQubeEnv('server name'){
sh "${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=${PROJECT_NAME} -Dsonar.projectName=${PROJECT_NAME} -Dsonar.projectVersion=1.0 -Dsonar.projectBaseDir=${PROJECT_DIR} -Dsonar.sources=src -Dsonar.sourceEncoding=UTF-8"
}
}
}
}
}
}
sonar-scanner
命令通过 -D 传递参数,具体如下:
除此之外还可以将配置写入到一个 myproject.properties
文件中,然后指定文件进行分析即可:
pipeline {
agent any
stages {
stage('Sonar') {
steps {
script {
withSonarQubeEnv('server name'){
sh "${scannerHome}/bin/sonar-scanner -Dproject.settings=../myproject.properties
}
}
}
}
}
}
在执行 CICD 时,有些环境、项目相关的配置我们可能会以 Yaml、Json 配置文件的形式进行管理,Jenkins 需要读取到对应的配置进行解析,可以通过 pipeline-utility-steps-plugin 插件实现,该插件提供了很多有用的功能,比如读写 Yaml、Json、Jar Manifest、Java Properties、CSV 文件、zip 解压缩文件等功能。这里仅简单介绍下读取 Yaml 、Json 文件的使用方式。
文件内容简单如下:
project:
name: test
dockerFilePath: rootfs/Dockerfile
config:
port: 8000
{
"project": {
"name": "test",
"dockerFilePath": "rootfs/Dockerfile",
"config": {
"port": 8000
}
}
}
插件提供的读取方式非常简单,直接调用插件中的 readYaml 和 readJSON 方法即可。Pipeline 中使用方式如下:
pipeline {
agent any
stages {
stage('Read Yaml') {
steps {
script {
yaml_datas = readYaml file: "/home/ubuntu/backdemo/test.yaml"
echo "${yaml_datas}"
echo "${yaml_datas.project}"
echo "${yaml_datas.project.name}"
echo "${yaml_datas.project.config}"
}
}
}
stage('Read Json') {
steps {
script {
json_datas = readJSON file: "/home/ubuntu/backdemo/test.json"
echo "${json_datas}"
echo "${json_datas.project}"
echo "${json_datas.project.name}"
echo "${json_datas.project.config}"
}
}
}
}
}
读取内容赋值给变量,其实还是一个字典对象,可以通过 key 读取到对应的值,上述 Pipeline 执行结果打印如下:
在使用 Jenkins 执行 CICD 过程中,很多步骤和操作都是重复的。对于重复的步骤,如果在每个项目的 Jenkinsfile 里面都写一遍的话,维护起来就太麻烦了。Jenkins 提供了 共享库 的方式使我们可以将 pipeline、通用代码、配置文件等抽取到一个项目中,然后配置引用即可。
Jenkins 共享库是一个用 Groovy 编写的项目,结构如下:
Project structure
// 引用自官方文档
+- src # Groovy source files
| +- org
| +- foo
| +- Bar.groovy # for org.foo.Bar class
+- vars
| +- foo.groovy # for global 'foo' variable
| +- foo.txt # help for 'foo' variable
+- resources # resource files (external libraries only)
| +- org
| +- foo
| +- bar.json # static helper data for org.foo.Bar
可以看到项目有三个目录:
src
: 和标准 Java 项目一样,src 下的定义的类将会在执行时加入到 classpath 中。vars
: 该目录用于创建一些脚本,一些通用的方法可以放在这里直接引用。resources
: 主要放置一些非 Groovy 类型的文件,比如 Json 文件。下面看一些编写共享库的示例:
创建类
// src/com.ahri/Foo.groovy
package com.ahri
class Foo {
String getName() {
return "This is Foo class";
}
}
编写脚本,封装通用方法
// vars/utils.groovy
def info(message) {
echo "INFO: ${message}"
}
封装 pipeline
//vars/deploy-pipelne.groovy
def call() {
pipeline {
agent any
}
stages {
stage ("First Stage"){
steps {
echo "This is First Stage"
}
}
stage ("Second Stage"){
steps {
echo "This is Second Stage"
}
}
}
}
封装配置文件
// resources/config.json
{
"Config": {
"host": "127.0.0.1",
"port": 8080,
"minio": {
"name": "TestProject",
"user": "Ahri",
"passwd": "1234"
}
}
}
Manager Jenkins
-> Configure System
-> Global Pipeline Libraries
@Library("JenkinsHelper")
import com.ahri.Foo
pipeline {
agent any
stages {
stage('Hello') {
steps {
echo 'Hello World'
script {
Foo foo = new Foo()
def name = foo.getName()
echo "${name}"
}
}
}
}
}
@Library("JenkinsHelper")
import com.ahri.Foo
pipeline {
agent any
stages {
stage('Hello') {
steps {
echo 'Hello World'
script {
Foo foo = new Foo()
def name = foo.getName()
echo "${name}"
utils.info("This is utils info")
}
}
}
}
}
// Jenkinsfile
@Library(value='JenkinsHelper', changelog=false) _
deploy-pipeline()
对于 resources 中的文件,Jenkins 可以通过 libraryResource
关键字直接读取,然后我们根据需要进行转换即可,下面是对 config.json 文件的解析示例:
import groovy.json.JsonSlurper
def loadJson() {
// 读取文件内容
def config_text = libraryResource 'config.json'
// 转为 json 对象
def jsonSlurper = new JsonSlurper()
def config_object = jsonSlurper.parseText(config_text)
// 操作对象,读取配置
echo "${config_object}"
echo "${config_object.config.minio}"
echo "${config_object.config.minio.name}"
}
上面就是遇到的一些使用场景的简单总结,欢迎交流。
老铁,都看到这了来一波点赞、评论、关注三连可好
我是 AhriJ邹同学,前后端、小程序、DevOps 都搞的炸栈工程师。博客持续更新,如果觉得写的不错,欢迎来一波老铁三连,不好的话也欢迎指正,互相学习,共同进步。