Jenkins使用笔记

摘要:由于最近在搭建内网测试服务器,由于涉及到多个人的项目,所以我需要每个人把构建完成的程序包通过拷贝的方式在服务器上部署,偶尔一两次还好,如果未来需要经常更新,那就太浪费时间,所以开始考虑使用 Jenkins 做自动化部署,在使用中遇到了很多问题,在此记录下来留作备忘。

Jenkins 服务搭建

Docker Image 的选择

细心的人应该能够发现,Jenkins 在 Docker Hub 上有两个 Organization,一个叫 jenkins, 另一个叫做 jenkinsci。通过加入时间可知,jenkinsci 创建较早,与2014年2月,jenkins与2014年4月创建。

之所以介绍这个的原因是,其最新的 image 管理略微有点混乱。首先看 jenkins image,jenkinsci 上已经被标记为 DEPRECATED,所以应该使用 jenkins 上的 image

但是最新的 blueocean 版本,在 Jenkins 上是没有任何更新的,所以又要使用 jenkinsci 上的镜像。所以这里给出总结:

jenkins 使用 jenkins 上的镜像:https://hub.docker.com/r/jenkins/jenkins/
blueocean 使用 jenkinsci 上的镜像:https://hub.docker.com/r/jenkinsci/blueocean/

Docker Compose Service

一般我们习惯使用 compose 来管理服务。

1
2
3
4
5
6
7
8
9
10
11
12
jenkins:
hostname: jenkins
# restart: always
image: jenkinsci/blueocean
ports:
- "8080:8080"
volumes:
- ./data/jenkins_home:/var/jenkins_home/:z
- /etc/timezone:/etc/timezone
- /etc/localtime:/etc/localtime
environment:
- JAVA_OPTS=-Duser.timezone=Europe/London

配置时区

Jenkins 默认使用 GMT 时区,需要自己配置时区,配置方式是在运行 java 的时候传入参数。这里我们需要通过 Docker 来传递参数。

这里搜索了很多解决方法,其中一种是让把宿主机的 timezone 和 localtime 映射到 jenkins 的 docker container,但是并没有生效,尽管宿主机的时区是正确的。所以就只能采用传入参数的方式了。

首先 Jenkins 支持环境参数 JAVA_OPTS,通过该环境变量传递参数。另外注意如果加入多个环境变量,可以直接写,不要加引号,参考该 issue。例如

1
JAVA_OPTS=-Dhudson.model.ParametersAction.keepUndefinedParameters=true -Dhudson.security.ArtifactsPermission=true

所以我们通过该参数设定时区

1
2
environment:
- JAVA_OPTS=-Duser.timezone=Europe/London

Maven 项目部署

JDK 和 Maven 的安装

JDK 在 Jenkins 上是个坑,因为默认的环境是 OpenJDK,Jenkins 提供安装 JDK 的方法,在 Global Tool Configuration 中,我们可以指定任意多个 JDK 和 Maven 的 installer。在指定 installer 的时候,需要指定安装方式,一般我们可以直接使用自动安装。JDK 需要输入 Orcale 的用户信息,因为有授权协议需要遵守。maven 完全不需要。

通过自动安装后,会在 Jenkins 的主目录(jenkins_home)下的 tools 目录下自动下载对应的工具,然后再解压。通过实验,这一切都是通的,但是这里有两个问题

JDK 至少要装2个

如果只装一个JDK,那么 Jenkins 就会默认使用该 JDK,连选择系统自带的机会都没有了,只有装两个以上,才会在项目部署时,提供选择。

找不到 JDK

目前该问题一直没有解决,如果 maven 的项目使用了安装的 JDK,总是提示找不到 java。找过很多解决方案,都不行,可以参考这里:

https://stackoverflow.com/questions/50120476/jenkins-config-jdk-for-project-and-maven-build-always-get-error?noredirect=1#comment87259185_50120476

和这里

https://stackoverflow.com/questions/49737624/jenkins-blue-ocean-maven-doesnt-see-java/50080283#50080283

Jenkins Pipeline

目前已经研究过两种类型的项目,一种是 freestyle,另外一种是 pipeline。目前感觉后一种更加专业,也和现在主流的 CI 工具类似(比如 CircleCi 和 TravisCi),他们都是在项目中附加一个配置文件,来告知服务器如何进行构建。这样开发者完成通过代码和配置文件来配置集成部署,提高效率,同时更好追踪变化,并让部署过程可复制。

Jenkinsfile 文件

该文件实际上是 groovy 语法,网上有攻略能够智能提醒,需要加入一个GDSL 文件,这里并没配置,如果经常要编辑置这个文件,倒是可以配置以下,可以参考以下博文:

https://st-g.de/2016/08/jenkins-pipeline-autocompletion-in-intellij

https://www.triology.de/en/blog-entries/jenkins-pipeline-plugin-code-completion

https://gist.github.com/arehmandev/736daba40a3e1ef1fbe939c6674d7da8

https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000140204-Setting-up-Groovy-completion-for-Jenkins-Pipelines-consumers

工具定义

Jenkinsfile 里面有一个 tool 的定义,这个定义需要自己在 configure->Global Properties 中的 Tool Location 自行定义。比如 maven 工具需要定义为其 Home 为 /var/jenkins_home/tools/hudson.tasks.Maven_MavenInstallation/maven-3.5.3

bash shell script 执行

我们经常在部署的时候需要拷贝文件等操作,所以如果能够执行 shell 脚本,那么操作就非常灵活自由,理论上可以做任何事情。一般我们把脚本单独写成 sh 脚本,然后再 Jenkinsfile 中直接执行该脚本,这里注意一定要注意路径问题。

比如要执行和 Jenkinsfile 同一个目录下的 xxx.sh,那么应该写成:

1
2
3
stage('sh') {
sh('./xxx.sh')
}

所以这里都是绝对路径或相对路径,不加 ./, 就会变成绝对路径。

区分 build 平台

该函数 isUnix() 可以区分 Windows类Unix 平台。

stage

尽可能分 stage,因为在整个部署过程中,stage 是可以可视化的区分开,同时一个 stage 失败了,之后的 stage 将不会继续执行。

pipeline-syntax

刚开始学习 pipeline 直接读文档太麻烦,所以 jenkins 提供了一个 pipeline-syntax 的工具,也就是把常用的操作进行总结,然后通过选择和配置生成代码脚本,通过该工具基本可以满足大部分需求了。

当然,当熟悉了之后,这个工具还是有用的,比如git 选择一个 credential,直接去写脚本还要查各个参数,用这个就直接可以生成对应 credential 的代码了。

用户权限控制

jenkins 的用户权限控制功能好像比较原始,在不借助插件的情况下,只能通过勾选 checkbox 来约束权限,大概有两种权限,全局权限和项目权限:

全局权限可以开启 Matrix-based security 或者 Project-based Matrix Authorization Strategy 来实现,这里需要手动添加用户到权限管理中,不添加的默认无权限。

项目权限需要 Enable 再进行配置。

Jenkins Build Trigger

jenkins 自动部署的自动化 trigger 有几种模式:

  1. 定时 polling,也就是每隔一段时间读取 git,如果有变化就 build,没有就忽略
  2. git server trigger, 比如 github 提供 trigger 的 hook
  3. git hook,也就是说在我们开发 push 的时候,使用本地的 git hook 一个 trigger 来告知 jenkins 来进行 build。

polling 无论是效率和性能都不好,所以 trigger 才是正道。而最后一种不受 git server 的限制,所以是广泛采用的。(当时个人测试 post-receive 的 hook 总是无效)

实际上对于 jenkins 来讲就是通过任意的工具调用一个请求,jenkins 收到请求就会进行 build,所以这里关键就是这个请求:

1
2
3
4
Trigger builds remotely (e.g., from scripts)
Authentication Token xXXX
Use the following URL to trigger build remotely: JENKINS_URL/job/PROJECT_NAME/build?token=TOKEN_NAME or /buildWithParameters?token=TOKEN_NAME
Optionally append &cause=Cause+Text to provide text that will be included in the recorded build cause.

所以这里有两个关键,一个是 token,另一个是项目名称匹配。

一般我们都会在 .git/hooks 目录下创建 post-receive 文件,这个文件就是通过 curl 访问一下上面的连接,然后就可以完成自动化 build 了

1
2
3
4
 #!/bin/bash
echo "post receive exec"
/usr/bin/curl --user ${USERNAME}:${USER_PWD} \
http://SERVER_DOMAIN/job/{PROJECT_NAME}/build?token=${TOKEN}&cause=Cause+update