Jenkins学习笔记

2024/7/11 Jenkins

# Jenkins 是什么?

Jenkins是一款开源 CI&CD 软件,用于自动化各种任务,包括构建、测试和部署软件。

Jenkins 支持各种运行方式,可通过系统包、Docker 或者通过一个独立的 Java 程序。

官方文档:

下载Jenkins:

下载Jenkins插件:

# 安装Jenkins

Jenkins通常作为一个独立的应用程序在其自己的流程中运行, 内置Java servlet 容器/应用程序服务器(Jetty)。

Jenkins也可以运行在不同的Java servlet容器((如Apache Tomcat 或 GlassFish))中作为servlet运行。

Jenkins安装请查阅:https://www.jenkins.io/zh/doc/book/installing/ (opens new window)

# 系统要求

最低推荐配置:

为小团队推荐的硬件配置:

  • 1GB+可用内存
  • 50 GB+ 可用磁盘空间

软件配置:

  • Java 8(无论是Java运行时环境(JRE)还是Java开发工具包(JDK)都可以。)
  • Docker(可选,将Jenkins作为Docker 容器运行)

# 使用war文件安装Jenkins

Jenkins的Web应用程序ARchive(WAR)文件版本可以安装在任何支持Java的操作系统或平台上。

要下载并运行Jenkins的WAR文件版本,请执行以下操作:

  1. 最新的稳定Jenkins WAR包 (opens new window) 下载并上传到 /opt/jenkins 目录中。

  2. 安装字体

    yum -y install fontconfig
    
    1
  3. 运行命令java -jar jenkins.war

    cd /opt/jenkins
    
    # 使用默认端口运行Jenkins,默认端口是8080
    java -jar jenkins.war
    # 使用指定端口运行Jenkins
    java -jar jenkins.war --httpPort=80
    # 使用指定端口后台运行Jenkins
    nohup java -jar jenkins.war --httpPort=80 &
    nohup java -jar jenkins.war --httpPort=80 > nohup.log 2>&1 &
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  4. 浏览http://localhost:8080并等到Unlock Jenkins页面出现。

  5. 继续使用Post-installation setup wizard (opens new window)后面步骤设置向导。

  6. 非后台运行的Jenkins,使用Ctrl+C在终端中发送中断信号。

    后台运行的Jenkins,可以通过以下命令停止Jenkins。

    # 找到进程ID
    ps aux | grep 'java -jar jenkins.war'
    # 杀死进程
    kill -9 PID
    
    1
    2
    3
    4

# 使用yum命令安装Jenkins

使用yum命令安装Jenkins请查阅:https://pkg.jenkins.io/redhat-stable/ (opens new window)

  1. 最新的稳定Jenkins rpm包 (opens new window) 下载并上传到 /opt/jenkins 目录中

  2. 安装Jenkins

    yum install epel-release # repository that provides 'daemonize'
    yum install jenkins-2.319.2-1.1.noarch.rpm -y
    
    1
    2
  3. 编辑jenkins的 /etc/init.d/jenkins 程序文件,添加java路径

    [root@jenkins2 ~]# whereis java
    java: /usr/local/jdk1.8.0_321/bin/java /usr/local/jdk1.8.0_321/jre/bin/java
    
    vim /etc/init.d/jenkins
    
    1
    2
    3
    4

  4. 编辑jenkins的 /etc/sysconfig/jenkins 配置文件,修改端口、系统运行账户

    jenkins默认情况是使用Jenkins用户启动的,但这个用户目前系统并没有赋予权限,这里我们将启动用户修改为root;另外Jenkins的默认端口是8080,这个跟tomcat的默认端口有冲突,可以做一下更改。

    vim /etc/sysconfig/jenkins
    
    1

  5. 启动和停止Jenkins

    systemctl daemon-reload                   # 重新加载配置
    systemctl start jenkins.service           # 启动jenkins
    systemctl enable jenkins.service          # 设置jenkins开机启动 
    systemctl stop jenkins.service            # 关闭jenkins 
    systemctl status jenkins.service          # 查看jenkins状态
    
    1
    2
    3
    4
    5

# 安装后设置向导

安装后设置向导请查阅:https://www.jenkins.io/zh/doc/book/installing/#setup-wizard (opens new window)

# 解锁 Jenkins

当您第一次访问新的Jenkins实例时,系统会要求您使用自动生成的密码对其进行解锁。

浏览到 http://localhost:8080(或安装时为Jenkins配置的任何端口),并等待 解锁 Jenkins 页面出现。

# 跳过安装插件

解锁 Jenkins之后,在 Customize Jenkins 页面内, 您可以安装任何数量的有用插件作为您初始步骤的一部分。

Jenkins默认使用国外地址,插件安装很慢,此处先跳过。

自定义Jenkins -> 选择插件来安装 -> 无

# 创建第一个管理员用户

最后,在customizing Jenkins with plugins之后,Jenkins要求您创建第一个管理员用户。 . 出现“ 创建第一个管理员用户 ”页面时, 请在各个字段中指定管理员用户的详细信息,然后单击 保存完成 。 当 Jenkins准备好了 出现时,单击开始使用 Jenkins。

Username: root

Password: 123456

# 修改插件下载地址

  1. Manage Jenkins > Plugins > Available plugins,把Jenkins官方的插件列表下载到本地,接着修改地址文件,替换为国内插件下载地址。

  2. 将 /root/.jenkins/updates/default.json 的国外地址替换为国内地址。

    # 进入插件文件目录
    cd /root/.jenkins/updates
    
    # 备份插件源地址配置
    cp default.json default.json.bak
    
    # 将国外官方地址替换为国内清华大学jenkins插件地址
    sed -i 's/https:\/\/updates.jenkins.io\/download\/plugins/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/plugins/g' default.json && sed -i 's/https:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json
    
    1
    2
    3
    4
    5
    6
    7
    8
  3. Manage Jenkins > Plugins > Advanced setting,把Update Site改为国内插件下载地址。

    官方插件下载地址:https://updates.jenkins.io/update-center.json

    国内插件下载地址:https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

  4. Sumbit后,在浏览器输入: http://localhost:8080/restart(或安装时为Jenkins配置的任何端口) ,重启Jenkins

# Jenkins免密登录远程服务器

在Jenkins使用ssh-keygen生成密钥对,将公钥复制到远程服务器的~/.ssh/authorized_keys中。

安装插件Publish Over SSH (opens new window)。进入Manage Jenkins -> System -> Publish over SSH配置插件。

添加凭据 (opens new window)。进入Manage Jenkins -> Credentials添加凭据。

安装插件SSH Agent (opens new window)。创建一个Pipeline项目,添加以下内容,测试远程服务器连接。

pipeline {
    agent any
    stages {
        stage('Login to Server') {
            steps {
                script {
                    // sshPrivateKey是上面添加凭据的ID
                    def sshPrivateKey = "ssh-key"
                    sshagent(credentials: [sshPrivateKey]) {
                        sh 'ssh root@192.168.1.31 "ls -al"'
                    }
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Jenkins常用插件

插件是为了适应组织或用户的需求,增强Jenkins环境的功能的主要手段。

插件以及它们的依赖能够自动地从更新中心 (opens new window)下载。更新中心是一个由Jenkins项目运营的服务,它提供了一个开源插件的清单,这些插件是由Jenkins社区的成员共同开发和维护的。

Jenkins 提供了几个不同的的方法在主机上安装插件:

  1. 在web UI使用 "插件管理器"。
  2. 使用Jenkins CLI (opens new window) install-plugin 命令。

管理插件请查阅:https://www.jenkins.io/zh/doc/book/managing/plugins/ (opens new window)

# Role-based Authorization Strategy

在Jenkins管理界面选择“Manage Jenkins” > “Plugins” > “Available Plugins”,然后搜索“Role-based Authorization Strategy (opens new window)”,找到后点击安装即可。

# Pipeline

在Jenkins管理界面选择“Manage Jenkins” > “Plugins” > “Available Plugins”,然后搜索“Pipeline ”,找到后点击安装即可。

# Pipeline Stage View

如果Pipeline项目没有阶段视图,在Jenkins管理界面选择“Manage Jenkins” > “Plugins” > “Available Plugins”,然后搜索“Pipeline Stage View (opens new window)”,找到后点击安装,安装完成后重启服务。

# SSH

  1. 搜索SSH

  2. 在 Jenkins -> Manage Jenkins -> Manage Credentials 添加凭据

  3. 在 Manage Jenkins -> Configure System 添加远程服务器

  1. 测试

# SSH Pipeline Steps

# Git

在Jenkins管理界面选择“Manage Jenkins” > “Plugins” > “Available Plugins”,然后搜索“Git”,找到后点击安装即可。

# Jenkins全局工具

  1. 在Jenkins管理界面选择“Manage Jenkins” > “Tools”

    • 配置JDK

      [root@localhost apache-maven-3.9.8]$whereis java
      java: /usr/bin/java /usr/lib/jvm/jdk-17-oracle-x64/bin/java /usr/share/man/man1/java.1
      
      1
      2

    • 配置Git

      [root@localhost jenkins]$whereis git
      git: /usr/bin/git /usr/share/man/man1/git.1.gz
      
      1
      2

    • 配置Maven

  2. 在Jenkins管理界面选择“Manage Jenkins” > “System”>"Global properties"

    • 配置JAVA_HOME,JAVA_HOME=/usr/lib/jvm/jdk-17-oracle-x64

    • 配置M2_HOME和PATH+EXTRA,解决Build时mvn: command not found

      • M2_HOME=/opt/apache-maven-3.9.8
      • PATH+EXTRA=$M2_HOME/bin

# 流水线语法

https://www.jenkins.io/zh/doc/book/pipeline/syntax/ (opens new window)

# agent 指令

agent 指令告诉Jenkins在哪里以及如何执行Pipeline或者Pipeline子集。

agent 指定流水线中的每个阶段都必须在某个地方(物理机,虚拟机或 Docker 容器)执行。

所有的Pipeline都需要 agent 指令。

agent 必须放在pipeline的顶层定义或stage中可选定义,放在stage中就是不同阶段使用不同的agent。

agent 指令更多选项和相关信息,可以查看 语法参考 (opens new window)

// agent any 告诉 Jenkins master 任意可用的agent都可以执行
agent any

// 通过标签指定 agent,比如某项目需要在JDK8中环境中构建
agent { 
    label 'jdk8' 
}
agent { 
    node { 
        label 'jdk8' 
    } 
}

// node 除了 label 选项,还支持自定义工作目录
agent {
  node {
      label 'jdk8'
      customWorkspace '/var/lib/custom'
   }
}

// label 支持过滤多标签
agent {
  label 'windows && jdk8'
}

// 指定在Docker镜像中运行
agent {
    docker {
        image 'finleyma/circleci-nodejs-browser-awscli'
    }
}

// 不分配 agent, 这样可以在具体的stages中定义
agent none
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

# Jenkinsfile 示例

https://www.jenkins.io/zh/doc/book/pipeline/jenkinsfile/ (opens new window)

# 免密登录远程服务器执行命令

pipeline {
    agent any
    parameters {
        string(name: 'user', description: '远程主机的用户名',trim: true)
        string(name: 'host', description: '远程主机的地址',trim: true)
        string(name: 'command', description: '执行远程命令',trim: true)
    }
    stages {
        stage('Execute Command In Remote Server') {
            steps {
                script {
                    echo "${params.user}"
                    echo "${params.host}"
                    echo "${params.command}"                    
                    def sshPrivateKey = "ssh-key"
                    sshagent(credentials: [sshPrivateKey]) {
                        sh "ssh ${params.user}@${params.host} \"${params.command}\""
                    }
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 为远程服务器添加公钥

pipeline {
    agent any
    parameters {
        string(name: 'user', description: '远程主机的用户名',trim: true)
        string(name: 'host', description: '远程主机的地址',trim: true)
        string(name: 'authorizedKeysPath', description: 'authorized_keys路径',trim: true)
        string(name: 'publicKey', description: '需要添加的公钥',trim: true)
    }
    stages {
        stage('Add Public Key To Remote Server') {
            steps {
                script {                  
                    def sshPrivateKey = "ssh-key"
                    def command = "echo \"${params.publicKey}\" >> ${params.authorizedKeysPath}"
                    sshagent(credentials: [sshPrivateKey]) {
                        sh "ssh ${params.user}@${params.host} \"${command}\""
                    }
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 构建-执行测试用例-部署

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                echo 'Building..'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing..'
            }
        }
        stage('Deploy') {
            steps {
                echo 'Deploying....'
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 拉取源码-构建-执行测试用例-部署

Jenkinsfile (Declarative Pipeline)
pipeline {
    agent none

    stages {
        stage('Pull source code') {
            steps {
                echo 'Pulling..'
            }
        }
        stage('Build') {
            agent {
          		label 'jdk8'
       		}
            steps {
                echo 'Building..'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing..'
            }
        }
        stage('Deploy') {
            steps {
                echo 'Deploying....'
            }
        }
    }
}
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

# 部署项目

# Jenkinsfile

pipeline {
    agent any

    environment {
        ACTION = "${action}"
        ROLLBACK_VERSION = "${rollbackVersion}"
        DEPLOY_ONLY = "deploy only"
        BACKUP_AND_DEPLOY = "backup and deploy"
        ROLLBACK = "rollback"
    }

    options {
        // Pipeline Stage View 不显示 Declarative: Checkout SCM
        skipDefaultCheckout(true)
    }

    stages {
        stage('Pull project') {
            steps {
                script {
                    if (ACTION == "${DEPLOY_ONLY}" || ACTION == "${BACKUP_AND_DEPLOY}") {
                        checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: 'e8dd0227-84f1-4158-aaac-846185848fcf', url: 'git@gitlab.example.com:uuap/uuap-api.git']]])
                    }
                }
            }
        }

        stage('Build project') {
            steps {
                script {
                    if (ACTION == "${DEPLOY_ONLY}" || ACTION == "${BACKUP_AND_DEPLOY}") {
                        sh 'mvn clean package'
                    }
                }
            }
        }

        stage('Publish project') {
            steps {
                script {
                    if (ACTION == "${DEPLOY_ONLY}" || ACTION == "${BACKUP_AND_DEPLOY}") {
                        sh 'scp target/*.jar root@192.168.1.17:/opt/webapp/uuap-api-ssh'
                    }
                }
            }
        }

        stage('Start project') {
            environment {
                WORKSPACE_PATH = '/opt/webapp'
                PROJECT_NAME = 'uuap-api-ssh'
                LOG_FILE_PATH = "${WORKSPACE_PATH}/${PROJECT_NAME}/log.log"
            }
            steps {
                script {
                    def remote = [:]
                    remote.name = 'applicationServer'
                    remote.host = '192.168.1.17'
                    remote.allowAnyHosts = true

                    withCredentials([usernamePassword(credentialsId: '1572b106-87a0-4be9-955d-afcba791d9a4', passwordVariable: 'password', usernameVariable: 'userName')]) {
                        remote.user = userName
                        remote.password = password

                        if (ACTION == "${BACKUP_AND_DEPLOY}") {
                            sshCommand remote: remote, command: """
                                cd ${WORKSPACE_PATH}/${PROJECT_NAME}/scripts
                                ./backup.sh ${BUILD_NUMBER}
                            """
                        }

                        if (ACTION == "${ROLLBACK}") {
                            sshCommand remote: remote, command: """
                                cd ${WORKSPACE_PATH}/${PROJECT_NAME}/scripts
                                ./rollback.sh ${ROLLBACK_VERSION}
                            """
                        }

                        sshCommand remote: remote, command: """
                                cd ${WORKSPACE_PATH}/${PROJECT_NAME}/scripts
                                ./stop.sh ${ACTION}
                            """

                        sshCommand remote: remote, command: """
                                cd ${WORKSPACE_PATH}/${PROJECT_NAME}/scripts
                                ./start.sh ${ACTION}
                            """
                    }
                }
            }
        }
    }
}
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
# 使脚本具有执行权限
chmod +x ./start.sh
chmod +x ./stop.sh
chmod +x ./backup.sh
chmod +x ./rollback.sh

# 执行脚本
./start.sh  
./stop.sh 
./backup.sh
./rollback.sh
1
2
3
4
5
6
7
8
9
10
11

# start.sh

#!/bin/bash
JAVA_PATH=/usr/local/jdk1.8.0_321/bin/java
PROJECT_PATH=/opt/webapp/uuap-api-ssh
LOG_FILE_PATH=${PROJECT_PATH}/log.log
file_name="uuap-api.jar"

action=$1
DEPLOY_ONLY="deploy only"
BACKUP_AND_DEPLOY="backup and deploy"
ROLLBACK="rollback"

rollbackResult=false
rollbackResult_path=${workspace_directory}/rollbackResult.txt

cat ${rollbackResult_path}/ | while read line
do
	rollbackResult=$line
done
echo "rollbackResult=${rollbackResult}"

cd ${PROJECT_PATH}
if [[ "${action}" == "${DEPLOY_ONLY}" || "${action}" == "${BACKUP_AND_DEPLOY}" || "${action}" == "${ROLLBACK}" && "${rollbackResult}" == "true" ]]; then
	nohup ${JAVA_PATH} -jar uuap-api.jar > log.log &

    tail -f ${LOG_FILE_PATH} | while read line 
    do 
        echo $line
        is_startup=`echo $line|grep "Started WebApplication"|wc -l`
        if [ $is_startup -eq 1 ] ; then  
            kill -9 `ps axu|grep "tail -f ${LOG_FILE_PATH}"|grep -v "grep"|awk '{printf $2}'`
        fi
    done
fi	
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

# stop.sh

#!/bin/bash

action=$1
DEPLOY_ONLY="deploy only"
BACKUP_AND_DEPLOY="backup and deploy"
ROLLBACK="rollback"

rollbackResult=false
rollbackResult_path=${workspace_directory}/rollbackResult.txt

cat ${rollbackResult_path}/ | while read line
do
	rollbackResult=$line
done
echo "rollbackResult=${rollbackResult}"

pid=`ps -ef | grep uuap-api.jar | grep -v grep | awk '{print $2}'`
if [[ -n "$pid" && (( "${action}" == "${DEPLOY_ONLY}" || "${action}" == "${BACKUP_AND_DEPLOY}" || "${action}" == "${ROLLBACK}" && "${rollbackResult}" == "true" )) ]]; then
	kill -9 $pid
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# backup.sh

#!/bin/bash
BUILD_NUMBER=$1
workspace_directory="/opt/webapp/uuap-api-ssh"
backup_directory="/opt/webapp_backup/uuap-api-ssh/${BUILD_NUMBER}"
file_name="uuap-api.jar"

# 如果备份目录不存在,则创建
if [ ! -d ${backup_directory} ]; then
	mkdir -p ${backup_directory}
fi

# 将当前构建文件拷贝到备份目录中
if [ -f "${workspace_directory}/${file_name}" ]; then
	cp ${workspace_directory}/${file_name} ${backup_directory}/${file_name}
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# rollback.sh

#!/bin/bash
rollbackVersion=$1
workspace_directory="/opt/webapp/uuap-api-ssh"
backup_directory="/opt/webapp_backup/uuap-api-ssh/${rollbackVersion}"
file_name="uuap-api.jar"
rollbackResult_path=${workspace_directory}/rollbackResult.txt

cd ${workspace_directory}
if [ -d ${backup_directory} ]; then
	rm -rf uuap-api.jar log.log
	cp ${backup_directory}/${file_name} ${workspace_directory}/${file_name} 
	echo "true" | tee ${rollbackResult_path}
else 
	echo "rollback version '${rollbackVersion}' is not exist."
	echo "false" | tee ${rollbackResult_path}
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 参数化构建

# 部署问题

# nohup: failed to run command ‘java’: No such file or directory

解决方案一:

source /etc/profile
nohup java -jar uuap-api.jar > log.log &
1
2

解决方案二:

JAVA_PATH=/usr/local/jdk1.8.0_321/bin/java
nohup ${JAVA_PATH} -jar uuap-api.jar > log.log &
1
2

# 参考资料

黑马程序员Java教程自动化部署Jenkins从环境配置到项目开发 (opens new window)

Jenkins持续集成从入门到精通

使用Jenkins构建、部署spring boot项目 (opens new window)