[TOC]

0x00 知识扩展

1.Jenkins 如何根据代仓库的 webhook 自动触发拉取提交的分支代码并构建?

描述: 通常每个项目代码库都会有不同的分支,(如果你没有用多分支流水线的情况下)对于普通的流水线项目我们可以让一条流水线来支持多个分支的发布,其实有时候你会发现每个分支的集成步骤都是差不多的,对于常规的我们可以安装使用git parameter插件,其次还需配置参数化构建过程。

方式1.手动动态拉取指定分支

  • 1.配置 GitHub 或者 Gitlab 项目 URL 地址, 并指定 GitLab Connection。
  • 2.配置 参数化构建过程 , 使用 Git 参数 输入变量名称【TagBranchName】,选择参数类型【分支或标签】以及设置默认值【origin/master】。
  • 3.执行 Build with Parameters 选择查看构建部署可用的Tag或Branch名称?

此时流水线代码可以这样写:

1
2
// # pipeline 片段
checkout([$class: 'GitSCM', branches: [[name: "origin/${params.TagBranchName}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${env.GITLAB_PUB}", url: "${env.GITLAB_URL}"]]])

WeiyiGeek.手动动态拉取分支

WeiyiGeek.手动动态拉取分支


方式2.使用 GitLab Plugin 让代码提交自动触发拉取

  • 1.配置构建触发器, 此处启用Enabled GitLab triggers, 下拉到Build when a change is pushed to GitLab,获得 GitLab webhook URL,并点击高级获得 Secret Token, 注意需要安装【 GitLab Plugin 】插件以配置 GitLab 以向您的 Jenkins 实例(如 GitLab CI)发送 POST 请求。。

    1
    Build when a change is pushed to GitLab. GitLab webhook URL: http://jenkins.weiyigeek.top/project/HelloWorld
  • 2.通常企业内部代码仓库会采用Gitlab作为私有的代码仓库, 为了使项目可以自动触发我们需要针对指定项目设置Webhook, 打开方式->项目->设置->Webhook设置-> 填入 GitLab webhook URL 以及 配置的 Secret token。

  • 3.勾选【Tag push events】通常只是打Tag时触发。

此时流水线代码可以这样写:

1
2
3
4
5
6
7
8
9
// # pipeline 片段
// 判断是 gitlab 自动触发还是手动触发
if( env.gitlabSourceBranch != null ) {
checkout([$class: 'GitSCM', branches: [[name: "origin/${env.gitlabSourceBranch}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${env.GITLAB_PUB}", url: "${env.GITLAB_URL}"]]])
updateGitlabCommitStatus name: '代码拉取', state: 'success'
} else {
checkout([$class: 'GitSCM', branches: [[name: "${params.PREJECT_BRANCHTAG}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: "${env.GITLAB_PUB}", url: "${env.GITLAB_URL}"]]])
updateGitlabCommitStatus name: '代码拉取', state: 'success'
}

WeiyiGeek.webhook自动触发

WeiyiGeek.webhook自动触发

插件参考地址: https://plugins.jenkins.io/gitlab-plugin/

温馨提示: 在 groovy 中如果变量不存在其值为null而非字符串类型的null。


方式3.使用 Generic Webhook Trigger Plugin 让代码提交自动触发拉取
描述: 在 Github 或者 Gitlab 中的 webhook 触发到底给 Jenkins 发了什么, 以Gitlab为例我们从下述看到其发送的POST请求。

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
# Request headers:
Content-Type: application/json
X-Gitlab-Event: Tag Push Hook
X-Gitlab-Token: 11c503a496e0a573d278357836dd3e1c4f

# Request body:
{
"object_kind": "tag_push",
"event_name": "tag_push", # 事件名称 Tag 触发
"before": "0000000000000000000000000000000000000000",
"after": "013f157ce81016be5bce9fda641633ba28141055",
"ref": "refs/tags/Test_cet_v1.0.0", # 分支名称
"checkout_sha": "32a16de55f7d0c6bc6dcbb1b110db10016a2ba27",
"message": "Test_cet_v1.0.0", # 分支信息
"user_id": 5,
"user_name": "weiyigeek",
"user_username": "project",
"user_email": "",
"user_avatar": "http://gitlab.weiyigeek.top/uploads/-/system/user/avatar/5/avatar.png",
"project_id": 70,
"project": {
"id": 70,
"name": "xxsbpt",
"description": "信息上报管理端",
"web_url": "http://gitlab.weiyigeek.top/project/xxsbpt",
"avatar_url": null,
"git_ssh_url": "ssh://git@gitlab.weiyigeek.top/project/xxsbpt.git",
"git_http_url": "http://gitlab.weiyigeek.top/project/xxsbpt.git",
"namespace": "weiyigeek",
"visibility_level": 0,
"path_with_namespace": "project/xxsbpt",
"default_branch": "master",
"ci_config_path": "",
"homepage": "http://gitlab.weiyigeek.top/project/xxsbpt",
"url": "ssh://git@gitlab.weiyigeek.top/project/xxsbpt.git",
"ssh_url": "ssh://git@gitlab.weiyigeek.top/project/xxsbpt.git",
"http_url": "http://gitlab.weiyigeek.top/project/xxsbpt.git"
},
"commits": [
{
"id": "32a16de55f7d0c6bc6dcbb1b110db10016a2ba27",
"message": "xxsbpt cet",
"title": "xxsbpt cet",
"timestamp": "2022-05-18T09:34:39+08:00",
"url": "http://gitlab.weiyigeek.top/project/xxsbpt/-/commit/32a16de55f7d0c6bc6dcbb1b110db10016a2ba27",
"author": {
"name": "weiyigeek",
"email": "weiyigeek@weiyigeek.top"
},
"added": [],
"modified": ["KsXxsbPt/pom.xml"],
"removed": []
}
],
"total_commits_count": 1,
"push_options": {
},
"repository": {
"name": "xxsbpt",
"url": "ssh://git@gitlab.weiyigeek.top/project/xxsbpt.git",
"description": "信息上报管理端",
"homepage": "http://gitlab.weiyigeek.top/project/xxsbpt",
"git_http_url": "http://gitlab.weiyigeek.top/project/xxsbpt.git",
"git_ssh_url": "ssh://git@gitlab.weiyigeek.top/project/xxsbpt.git",
"visibility_level": 0
}
}


步骤 01.有了上面的请示例,我们可以在jenkins Job 里 Generic Webhook Trigger 构建触发器中进行设置,首先需要进行勾选,获取到触发URL (http://jenkins.weiyigeek.top/generic-webhook-trigger/invoke)。

  • Generic Webhook Trigger

步骤 02.然后在Post content parameters中进行配置,例如此处我们想要获取到ref的值可以这样配置。

1
2
3
4
5
6
7
8
9
10
11
# 自定义变量名称
Variable: ref

# 表达式采用 JSONPath 方式
Expression: $.ref # ref 值

# 表达式获取值过滤与匹配,例如 refs/tags/Test_cet_v1.0.0 , 当配置如下时则ref其值为 Test_cet_v1.0.0
Value filter: refs/tags/

# 未匹配到默认值
Default value:master

WeiyiGeek.Post content parameters

WeiyiGeek.Post content parameters

温馨提示: 除此之外我们还可以获得Header parameters以及Request parameters, 并且新增 Post content parameters 参数例如再获取 message 信息。

1
2
Variable: message 
Expression: $.message

步骤 03.为了保证webhook未授权访问, 我们可以对其设置 Token 在认证时可以采用如下方式。

1
Token:weiyigeek-secrets

  • Query parameter invoke?token=weiyigeek-secrets
  • A token header token: TOKEN_HERE
  • A Authorization: Bearer header Authorization: Bearer TOKEN_HERE

步骤 04.配置Optional filter, 此处作用是条件判断,当只有符合Expression中定义的正则的变量Text 才会触发发版(CICD)否则不会发版。Text处的变量是通过前面Post content parameters区域提取的变量 message 。

WeiyiGeek.变量正则表达式匹配

WeiyiGeek.变量正则表达式匹配

步骤 05.在Gitlab对应的项目中添加Webkook触发接口,即打开方式->项目->设置->Webhook设置-> 填入 GitLab webhook URL (http://jenkins.weiyigeek.top/generic-webhook-trigger/invoke?token=weiyigeek-secrets)

步骤 06.此时假如我们向代码仓库中打一个Tag, 例如 git tag -a Test_devops_v1.0.0 -m "Test_devops_v1.0.0" 等待触发 jenkins 拉取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
String BUILD_ENV = ""
String BUILD_NAME = ""
String BUILD_VERSION = ""

pipeline {
node(k8s) {
stage('Generic Webhook Trigger') {
dir('/home/jenkins/agent/workspace/test') {
def currentBranch = ref
git branch: currentBranch, credentialsId: 'xxxx', url: 'http://gitlab.weiyigeek.top/devops.git'
// 分隔 Tag 信息 或者 message 信息
def arr = ref.split("_") as List
// # 构建环境
BUILD_ENV = arr[0]
// # 构建名称
BUILD_NAME = arr[1]
// # 构建版本
BUILD_VERSION = arr[1]
}
}
}
}


2.如何在Jenkins pipeline中获取shell命令得返回值?

描述: 在 Pipeline 流水线中通常需要对执行的sh命令获取其值,我们可以通过如下几种方式获取其标准输出或者其执行状态。

  • 无需返回值,仅执行shell命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    pipeline{
    // 主要阶段以及子阶段流程
    stages {
    // [ 阶段.shell命令执行测试 ]
    stage ('代码拉取') {
    steps {
    // 最简单的方式
    sh 'whoami && uname -a'
    }
    }
    }
    }
  • 获取标准输出

    1
    2
    3
    4
    5
    6
    7
    8
    //第一种
    result = sh returnStdout: true ,script: "<shell command>"
    result = result.trim()
    //第二种
    result = sh(script: "<shell command>", returnStdout: true).trim()
    //第三种
    sh "<shell command> > commandResult"
    result = readFile('commandResult').trim()
  • 获取执行状态

    1
    2
    3
    4
    5
    6
    7
    8
    //第一种
    result = sh returnStatus: true ,script: "<shell command>"
    result = result.trim()
    //第二种
    result = sh(script: "<shell command>", returnStatus: true).trim()
    //第三种
    sh '<shell command>; echo $? > status'
    def r = readFile('status').trim()

温馨提示: trim() 方法用于删除字符串的头尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符等, 在 Jenkins 流水线中非常重要,因为命令执行后总是会在其末尾添加一个换行符。


3.如何在Jenkins pipeline中获取项目的commit id与commit msg并设置为环境变量?

描述: 在CICD中通常我们需要构建的消息提示, 而提示的相关信息必不可少的就是本次构建代码提交的id以及其代码提交主要修改信息等,方便运维以及开发人员进行后续测试、部署以及运维。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// # 1.获取完整 commit id, 例如:b34f93a7d4cbafb67d259edvf5a1f92dd6b4ddc5
sh label: 'commit_id',returnStatus: true ,script: 'git rev-parse HEAD'

// # 2.获取 short commit id(如:bb4f92a)
sh label: 'short_commit_id',returnStatus: true ,script: 'git rev-parse --short HEAD'
sh label: 'short_commit_id',returnStatus: true ,script: 'git rev-parse --short HEAD ${GIT_COMMIT}'
sh label: 'short_commit_id',returnStatus: true ,script: 'git log --oneline -1 | awk \'{print \$1}\''
sh label: 'git_commitid',returnStdout: true, script: """
git show --oneline --ignore-all-space --text | head -n 1 | cut -d ' ' -f 1 |tr -d '\\n'
"""


// # 3.获取提交 messges 信息,并将其设置为环境变量
stage('get_commit_msg') {
steps {
script {
env.GIT_COMMIT_MSG = sh (script: 'git log -1 --pretty=%B ${GIT_COMMIT}', returnStdout: true).trim()
env.GIT_COMMIT_MSG = sh label: 'git_commitmsg',returnStdout: true, script: """
git show --oneline --ignore-all-space --text | head -n 1 |tr -d '\\n'
"""
}
}
}



0x01 入坑出坑

问题1.在Jenkins流水线中运行dockerIndocker镜像环境测试docker命令显示 dial unix /var/run/docker.sock: connect: permission denied 错误信息,解决方法如下:

  • 错误信息:
    1
    2
    ~ $ docker ps
    Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json: dial unix /var/run/docker.sock: connect: permission denied
  • 问题原因: 由于镜像中的docker执行者权限较低或者不属于docker组,则无操作权限。
  • 解决办法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 解决方案1:在docker命令前加sudo
    sudo docker images

    # 解决方案2: 只需要操作一次
    # 1.将jenkins用户加入到docker组中
    sudo usermod -a -G docker jenkins
    # 2.将当前用户切换到docker组中
    sudo gpasswd -a $USER docker
    newgrp - docker

    # 解决方案3: 设置socket文件权限,但是此种方式只要docker服务重启, 就需要重新设置一次
    sudo chmod 666 /var/run/docker.sock


2.在Kubernetes集群中运行自定义动态slave工作节点时报hudson.remoting.Channel$CallSiteStackTrace: Remote call to JNLP4-connect connection错误

  • [] 错误信息:

    1
    2
    3
    4
    5
    6
    7
    8
    [Pipeline] End of Pipeline
    Also: hudson.remoting.Channel$CallSiteStackTrace: Remote call to JNLP4-connect connection from 192.168.12.226/192.168.12.226:36221
    at hudson.remoting.Channel.attachCallSiteStackTrace(Channel.java:1797)
    java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:717)
    at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:957)
    at java.util.concurrent.ThreadPoolExecutor.ensurePrestart(ThreadPoolExecutor.java:1603)
  • [] Jenkins pipeline 流水线测试脚本

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
// [Jenkins Pipeline 流水线 开始]
pipeline {
// 流水线运行的主机绑定,此处利用动态的K8s节点进行。
agent {
kubernetes {
cloud 'kubernetes'
namespace 'devops'
inheritFrom 'jenkins-slave'
showRawYaml 'false'
workingDir '/home/jenkins/agent'
nodeSelector 'kubernetes.io/hostname=weiyigeek-201'
// yamlFile 'KubernetesPod.yaml'
yaml """\
apiVersion:
kind: Pod
metadata:
labels:
app: 'jenkins-jnlp'
job: ${JOB_NAME_NUMBER}
spec:
serviceAccountName: 'jenkins-sa'
automountServiceAccountToken: false
securityContext:
runAsUser: 1000 # default UID of jenkins user in agent image
containers:
- name: 'jnlp'
image: 'alpine-jenkins-jnlp:v2.330'
imagePullPolicy: 'IfNotPresent'
command: ["/bin/sh"]
args: ["-c","/usr/local/bin/jenkins-agent.sh && cat"]
tty: true
env:
- name: JAVA_OPTS
value: '-Xms512m -Xmx1g -Xss1m'
resources:
limits: {}
requests:
memory: '512Mi'
cpu: '500m'
volumeMounts:
- name: 'maven-cache'
mountPath: '/home/jenkins/.m2'
- name: 'docker-socket'
mountPath: '/var/run/docker.sock'
volumes:
- name: maven-cache
hostPath:
path: /nfsdisk-31/appstorage/mavenRepo
- name: docker-socket
hostPath:
path: /var/run/docker.sock
"""
}
}

stages {
stage ('declarative Pipeline - kubernetes plugin Test') {
steps {
echo "[*] kubernetes 插件测试"
sh "mvn -version"
sh "release-cli -v"
sh "sonar-scanner -v"
sh "kubectl version"
sh "sleep 666"
sh "docker --version && sudo docker ps"
}
}
}
}
1
2
3
env:
- name: JAVA_OPTS
value: '-Xms512m -Xmx1g -Xss1m'