使用 Jenkins 做自动化构建

Author Avatar
EpLiar 3月 19, 2021
license Anti%20996 blueLast Updated: 21-03-30Words: 4834

在我正式转 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 JenkinsConfigure 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. 打勾 repoadmin: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

在项目首页的 SettingsWebhooks,点击 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. PreferencesAccess Tokens

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

  3. 点击 Create personal access token

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

在 Jenkins 安装相关插件

  1. 系统管理插件管理可选插件,搜索 GitLab PluginGitLab 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 URLhttps://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 中项目首页 → SettingsWebhooks

  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 directlyAdd,粘贴你的 ssh 私钥

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

  7. 点击确定保存

在 GitHub 添加你的 ssh 公钥

  1. SettingsSSH and GPG keysNew 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 用户登录

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

CC BY 4.0
本文链接:https://epliar.com/articles/jenkins-guideline/