使用 Jenkins 做自动化构建

Author Avatar
EpLiar 3月 19, 2021
license Anti-996 Last Updated: 2026-02-24 Words: 4326 Reading: 17 min

在我正式转 DevOps 之后,需要经常给应用打包,而生产环境必须要通过堡垒机才能 ssh,而只有 Windows 才能调用相关的工具去使用堡垒机…… 于是,我为了更好的运维,使用 Jenkins 对产品进行了自动化的构建和部署。

部署 Jenkins 环境

以下步骤在一台拥有公网的 ArchLinux 服务器上进行,如果你是内网服务器或其他发行版的服务器,不要生搬硬套。

安装 JDK 和依赖

由于 Jenkins 只支持 JDK 8-11,所以下载 JDK 时记得找到对应的版本去安装。

$ sudo pacman -S jdk11-openjdk ttf-dejavu

安装 ttf-dejavu 的原因我猜测是因为 Jenkins 的前端需要,如果不安装会在 Jenkins 的命令行输出中报 "AWT is not properly configured on this server"。

下载 Jenkins

LTS 版本由于部分插件无法安装,所以选择一周一个版本的 Regular releases (Weekly)。

在 Nginx/Caddy 中配置反向代理

这一步是为了安全所考虑,当然你也可以直接暴露端口,都没有问题。

Jenkins 启动时的默认端口是 8080,8889 是我给 Jenkins 指定的端口,你也可以给 Jenkins 指定你想要的端口,不与现有服务冲突即可。

Caddyfile
jenkins.epliar.com {
    reverse_proxy 127.0.0.1:8889 {
    }
}
nginx.conf
http {
    server {
        server_name jenkins.epliar.com;
        location / {
            proxy_pass http://127.0.0.1:8889;
        }
    }
}

启动 Jenkins

$ java -jar jenkins.war --httpPort=8889
# 后台启动
$ nohup java -jar jenkins.war --httpPort=8889 &

如果你在本地的服务器有更科学的上网方式,可以在启动时给 JVM 设置代理。如果你的代理监听在本机,注意不要和 Jenkins 的端口相同。

# 使用系统代理
$ java -Djava.net.useSystemProxies=true -jar jenkins.war
# 使用自定义 HTTP 代理
$ java -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=8000 -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=8000 -jar jenkins.war
# 使用自定义 SOCKS 代理
$ java -DsocksProxyHost=127.0.0.1 -DsocksProxyPort=1088 -jar jenkins.war

修改 Jenkins 插件源

如果你已经设置好了代理或是有比较好的网络环境,跳过此步骤。

这个修改也可以在一切就绪后的管理插件 → 高级中找到。

当 Jenkins 在命令行里输出初始密码后,就可以关闭 Jenkins 接着修改默认的更新中心 URL 了。

在 Jenkins 目录 (在主目录下的 .jenkins) 修改 hudson.model.UpdateCenter.xml,将默认的 https://updates.jenkins.io/update-center.json 换成 https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

$ vim ~/.jenkins/hudson.model.UpdateCenter.xml

随后重启你的 Jenkins。(其实就是 ^C 然后再启动)

输入在命令行里提示的密码

访问 Jenkins,第一次进入会要求输入一个随机生成的密码,这个密码可以在命令行的输出里找到。如果你是后台启动,可以在 Jenkins 目录下的 secrets/initialAdminPassword 找到。

console password
$ cat ~/.jenkins/secrets/initialAdminPassword

安装社区推荐的插件

如果你是第一次使用 Jenkins,可以选择社区推荐的插件。如果你不是第一次使用,跳过此步骤。

welcome

点击之后就会开始安装了,过程取决于你的网络质量 (代理) 和机器配置。

installing

事实证明,即使用了代理,也会出现安装失败……

install_fail

遇到部分插件安装失败可以直接点击重试,可以解决大部分因为网络波动而引起的失败。如果还是不行,可以当作我们暂时不需要这个插件来处理。

创建管理员账户

不做赘述。此步骤可以用 admin 继续,密码是那个随机生成的 32 位字符串。

配置实例地址

这个 URL 是你访问 Jenkins 的地址,如果你部署在互联网上,需要设置服务器的公网地址,这个是为了能使用 Webhook 进行自动构建。如果你是在本地部署同时使用了端口转发的话,填写从互联网能访问到的那个地址。

你也可以暂时跳过,URL 可以在 Manage Jenkins → Configure System 中再次设置。

这一步结束之后,就可以进入 Jenkins 了。

如果在这一章结束后发生了任何问题,包括但不限于密码不正确、Jenkins 卡死等,你都随时可以把 .jenkins 给删除并重启 Jenkins 来进行初始化。

开始动手:初阶

我会用三个比较简单的工程去写这一章 (其中包含直接输出目录的前端以及需要重新启动的后端,还有一个只是获取最新源码并打包的简单工程),主要体现在如何获取新源码、构建并发布上。

在上一章里,我没有配置任何的 JAVA_HOME MAVEN_HOME GOPATH 等变量,这一点出于配置好后依旧 command not found。如果有确定问题所在我会更新此文。

第一个项目

项目地址:https://github.com/EpLiar/JenkinsDemo

登录 Jenkins 后,首页左侧会有一个新建项目 (翻译不完整可能会显示成新建 Item),填写名字,此处选择自由风格项目 (Freestyle Project)。

freestyle

在新打开的页面,源码管理 → Git,填写项目的 https 地址 (clone 仓库所用的那个链接,而不是项目首页地址),同时在下方指定要构建的分支 (分支指定你说了算,默认为 master)。

如果项目是非公开的,会需要账户和密码,但是这个对于开启了 2FA 的账户没有用。因此可以在进阶里查看如何使用 ssh key 来获取仓库。

git_config

随后在下方的构建处,点击增加构建步骤 → Execute Shell (执行 Shell),填写构建命令

$ PATH="/usr/bin":PATH (1)
$ go build main.go
$ ./main
1 你也可以不写这一行,用绝对路径来执行编译

随后点击保存,就算完成配置了。

build_command_demo

即使你是在 Windows 部署的 Jenkins,它依旧会使用斜杠而不是反斜杠来做路径分隔符。执行当前目录下的程序也需要加上 ./

在新打开的页面左侧有一个立即构建 (build now),点击之后会在左侧下方读出一个进度条,打开后找到控制台输出,即可看到编译输出。

console output

前端工程

项目地址:https://github.com/EpLiar/vue-demo

这个项目使用 vue-cli 照着模板新建了一个,但是生成的文件里没有 vue.config.js 来指定输出的目录,我们手动创建一个:

vue.config.js
module.exports = {
    publicPath = '',
    outputDir = '/home/epi/website/public/vue' (1)
}
1 这个地址可以通过 https://epliar.com/vue/ 来访问到,路径的设置因人而异。

新建项目,填写地址,此处只重点说明构建的命令。

$ PATH=/usr/bin:$PATH
$ npm install (1)
$ npm run build
1 前端构建依赖获取,这个步骤如果没有新的依赖项可以只执行一次,也就是第一次构建之后删除。

构建,没有问题:

vue_build_output

访问 https://epliar.com/vue/ 看看:

epliar vue

现在修改一下 App.vue,看看构建后能否有网页更新:

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to vue-demo App"/>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

重新构建,访问查看结果:

second_vue_page

后端工程

与前端不同,通常前端只需要更新静态页面到指定的目录下就可以了,后端需要重启应用,因此本小章在构建的基础上会增加部署的内容。

项目地址:https://github.com/EpLiar/backend-demo

此项目需要 maven 来构建,因此先安装依赖:

$ sudo pacman -S maven

随后为了能请求到后端 GetMapping 的地址,需要修改一下现有的 caddy 配置文件,并重启 Caddy。

epliar.com {
    reverse_proxy /hello/ 127.0.0.1:8080 {
    }
}

新建项目并填写 git 地址,在此只重点讲构建脚本。

$ sh runscript.sh (1)
1 脚本的当前工作路径是项目代码的主目录
runscript.sh
PATH=/usr/bin:$PATH (1)
APP="backend_demo-0.0.1-SNAPSHOT.jar"
mvn install
PID=$(ps -ef | grep $APP | grep -v grep | awk '{print $2}')
kill -9 $PID (2)
BUILD_ID=dontKillMe (3)
nohup java -jar target/$APP & (4)
sleep 5
1 把路径手动添加到 PATH
2 结束在运行的应用进程
3 告诉 Jenkins 不要杀死该进程
4 应用后台驻守 (no hang up)

经过搜索后我查不到如何在 Jenkins 的 Shell 里杀死指定 PID 的进程,且 Jenkins 里的 kill 似乎不知道 -9 这个选项,于是我用了曲线救国的方法,用新的脚本去实现它。

保存,构建,并访问 https://epliar.com/hello/ 查看结果。

output
$ curl https://epliar.com/hello/
你好,世界!
helloworld

接着修改访问接口时输出的内容,检测脚本是否能够自动重新部署。

HelloController.java
package com.epliar.backend_demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class HelloController {
    @GetMapping("/")
    public static String sayHello() {
        return "Hello world!";
    }
}
new output

进阶部分

GitHub 推送自动触发构建

这一章在第一个项目的基础上做延伸,做到能在 push 代码够触发 Jenkins 的自动构建。

创建 Personal access token

在 GitHub Settings 里找到 Developer settings,选择 Personal access tokens,之后点击 Generate new token。

gen token

这一步可能需要密码

  1. Note 处起一个方便自己辨认 token 用处的名字,例如 Jenkins。

  2. 打勾 repo 和 admin:repo_hook。

  3. 点击最下方的 Generate token。

图片作为选项示例

token_option

点击 Generate token 后,会生成一个 token,需要立即保存,只能看到一次。如果忘记,只能再生成一次。

token

Jenkins 配置 GitHub

在系统管理 → 系统配置 → GitHub,点击添加 GitHub 服务器。名称随意,凭据处点击添加,选择你的用户名。

add_github_server

类型选择 Secret text,把 token 填写到 Secret 处,描述自由发挥,完成后点击添加即可。

secret

在原来的地方,凭据处可以选择刚刚填写好的凭据,右边会有一个连接测试,提示 verified 表示凭据有效。

test_connection

注意到提示里有一个 rate limit,可能是使用次数限制,遇到了再更新。

点击最下方保存,完成 GitHub 的配置。

配置 demo 接收推送

回到首页,打开 demo 项目的配置 (也可以是点击项目后在新页面左侧进入配置)

config

然后补充以下两点内容:

  1. 在构建触发器板块,打勾 Github hook trigger for GITScm polling

  2. 在构建环境板块,打勾 Use secret text(s) or file(s),然后选择上一小节配置的凭据。

  3. 点击保存。

图片作为参考:

advanced_demo_config

在 GitHub 项目中配置 Webhook

在项目首页的 Settings → Webhooks,点击 Add webhook。

add_webhook

填写你的 Payload URL,通常是 Jenkins 地址加上 github-webhook/,你也可以在配置凭据的时候点击高级以自定义 URL。例如我的 Jenkins Payload URL 为 https://jenkins.epliar.com/github-webhook/。

填写完成后点击下方 Add webhook 保存。

如果你在 URL 少写了最后的斜杠,你会在 Webhook 看到报错。如果不介意,不妨试一下 :-)

修改代码并推送,查看结果

在查看输出的时候代码打印了一句 v1.0,我们将它修改一下,commit 并推送。

main.go
package main;

import "fmt"

func main() {
  fmt.Println("v2.0")
}

推送结束后,回到 demo 的主面板,可以看到 GitHub push 已经触发了构建,同时还附带着 commit 信息。

build_info

再查看此次构建的控制台输出,可以看到是 GitHub 触发的构建 (Started by GitHub),且代码更新成功。

2.0_build_log

GitLab 推送自动构建

经历上一节后你应该了解了自动构建的原理,其实大概就是配置好后 GitHub 会在收到 push 后往指定的 URL 发送一个事件请求,Jenkins 收到之后就会开始获取新代码并构建。此处略写 GitLab 同等场景的配置。

在 GitLab 生成 Access Token

  1. Preferences → Access Tokens

  2. 在 Name 填写方便辨认的名字并打勾 api

  3. 点击 Create personal access token

  4. 在新打开的页面记住你的 token,只能看到一次

在 Jenkins 安装相关插件

  1. 系统管理 → 插件管理 → 可选插件,搜索 GitLab Plugin 和 GitLab API

  2. 打勾,点击 Install without restart

  3. 等待安装的同时打勾 Restart Jenkins when installation is complete and no jobs are running

  4. 等待 Jenkins 重启并跳转登录

添加 GitLab 凭据

  1. 系统管理 → 系统配置 → GitLab

  2. Connection name 随意,如果你不是自己搭建的 GitLab,Gitlab host URL 写 https://gitlab.com

  3. 凭据 → 添加 → 点击你的用户名

  4. 类型选择 GitLab API token,在 API token 粘贴生成的 token

  5. 点击 添加

  6. 点击 测试连接

项目 Webhook

  1. 在 Jenkins 新建项目,在构建触发器中打勾 Build when a change is pushed to GitLab. 后面的 URL 复制下来。

  2. 点击 高级 (Advanced),选择 Filter branches by name 并在 Include 填写 master。

advanced
  1. 在 Secret token 点击 Generate,复制生成的 token,保存

  2. GitLab 中项目首页 → Settings → Webhooks

  3. 在 URL 填写地址

  4. Secret token 粘贴第三步生成的 token

  5. 打勾 Push events

  6. 点击 Webhook

添加完成之后,可以点击 Test 发送一个 Push events,Jenkins 收到后会自动开始构建。

使用私钥 clone 代码

如果一个项目不是公开的,那么需要鉴定才可以进行读写。你可能会说配置好凭据使用用户名和密码就行了,那要是那个帐号开启了 2FA 呢?而且 GitHub 从 2021 年 8 月 13 号起将不接受命令行的密码认证[1],这就需要万能的 ssh 密钥了。

生成你的 ssh 密钥对

鉴于相同安全等级下 ed25519 的密钥更短,在这里推荐使用 ed25519 作为密钥算法。当然你也可以继续使用 rsa,把 -t 选项和参数去掉就是。

$ ssh-keygen -t ed25519

密钥执行完之后,可以在用户目录下的 .ssh 文件夹中找到你的密钥对,其中后缀不为 pub 的是私钥。

在 Jenkins 里添加你的私钥

  1. 系统管理 → 管理凭据 (Manage Credentials) → 全局 → 添加凭据

  2. 类型选择 SSH Username with private key

  3. 起一个你喜欢的 ID

  4. 如果只用于克隆代码,Username 写 git

  5. 点击 Enter directly → Add,粘贴你的 ssh 私钥

  6. 如果你生成密钥的时候使用了密码保护,在 Passphrase 填写你的密码

  7. 点击确定保存

在 GitHub 添加你的 ssh 公钥

  1. Settings → SSH and GPG keys → New SSH key

  2. 粘贴你生成的公钥 (.pub 文件),注意不是私钥

  3. 点击 Add SSH key 保存你的密钥 (这一步可能需要输入密码)

克隆并使用你的仓库

仓库地址:https://github.com/EpLiar/private-demo,除了我以外访问都是 404。

  1. 新建项目

  2. 填写项目的 ssh clone 地址

  3. 在凭据处选择你刚刚添加私钥时写的名字

这是一个为了测试私有仓库和 ssh 私钥的项目,因此仓库中只有一个 message 文件,写了一句 This is a private repo.,因此为了测试可以在构建命令中写打开这个文件。

$ cat message

立即构建,可以看到 Jenkins 通过 SSH 密钥获取到了私有仓库的内容。

private

后记

写这篇文章用了很长时间,除了工作以外,也和机器配置有点关系。我的服务器本来就有一个 docker 一个 caddy 一个 qBittorrent 在跑,Jenkins 一上负载就快拉满了。同时 Jenkins 的翻译有时局部有时完全,不太可控,因此我在文章中尽可能的把每一个名称的中文和英文都写了出来。

祝大家阅读愉快。

Jenkins 的升级

有手就行,下载新版本的 war 包重启就好了。如果你是使用的包管理安装,那就更简单了,升级完重启就行。

一些不解

我大概从大二开始使用密钥而不是密码去登录自己的服务器,一直以来这个方式都没有出现过问题,之前用密码登录每一次进去都会提示有几次密码尝试错误。我倒也可以接受从堡垒机登录,但是堡垒机还需要 VPN 且在特定的 Windows 工具下登录,我非常反感。我觉得就两种方式,要么堡垒机在公网,登录堡垒机后再登录需要运维的服务器;要么就是 VPN 直接连接服务器。而且 VPN 都是实名的,完全不知道哪里会出问题,而且机子的密码又不能每个人都一样,而且登录的账户又不能用人员来新建,这样大家手里都得有那么一份密码清单来记录…… 这个清单要是丢了,问题岂不是更大?

sshd 是可以通过修改 sshd_config 来设定一些限制的:

sshd_config
allowusers [email protected]
PasswordAuthentication no
PermitRootLogin no

这样就可以做到:

  • 禁止远程 root 登录

  • 禁止使用密码登录,密码无法被穷举

  • 只允许在 192.168.50.2 的 somebody 用户登录

越说越不理解同时还带着一点高血压… 何苦这么浪费时间…

Licensed under Anti-996 License 1.0
本文链接:https://epliar.com/articles/jenkins-guideline/