$ sudo pacman -S jdk11-openjdk ttf-dejavu
使用 Jenkins 做自动化构建
在我正式转 DevOps 之后,需要经常给应用打包,而生产环境必须要通过堡垒机才能 ssh,而只有 Windows 才能调用相关的工具去使用堡垒机…… 于是,我为了更好的运维,使用 Jenkins 对产品进行了自动化的构建和部署。
部署 Jenkins 环境
以下步骤在一台拥有公网的 ArchLinux 服务器上进行,如果你是内网服务器或其他发行版的服务器,不要生搬硬套。 |
安装 JDK 和依赖
由于 Jenkins 只支持 JDK 8-11,所以下载 JDK 时记得找到对应的版本去安装。
安装 ttf-dejavu 的原因我猜测是因为 Jenkins 的前端需要,如果不安装会在 Jenkins 的命令行输出中报 "AWT is not properly configured on this server"。
在 Nginx/Caddy 中配置反向代理
这一步是为了安全所考虑,当然你也可以直接暴露端口,都没有问题。
Jenkins 启动时的默认端口是 8080,8889 是我给 Jenkins 指定的端口,你也可以给 Jenkins 指定你想要的端口,不与现有服务冲突即可。
jenkins.epliar.com {
reverse_proxy 127.0.0.1:8889 {
}
}
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
找到。
$ cat ~/.jenkins/secrets/initialAdminPassword
安装社区推荐的插件
如果你是第一次使用 Jenkins,可以选择社区推荐的插件。如果你不是第一次使用,跳过此步骤。
点击之后就会开始安装了,过程取决于你的网络质量 (代理) 和机器配置。
事实证明,即使用了代理,也会出现安装失败……
遇到部分插件安装失败可以直接点击重试,可以解决大部分因为网络波动而引起的失败。如果还是不行,可以当作我们暂时不需要这个插件来处理。
创建管理员账户
不做赘述。此步骤可以用 admin 继续,密码是那个随机生成的 32 位字符串。
配置实例地址
这个 URL 是你访问 Jenkins 的地址,如果你部署在互联网上,需要设置服务器的公网地址,这个是为了能使用 Webhook 进行自动构建。如果你是在本地部署同时使用了端口转发的话,填写从互联网能访问到的那个地址。
你也可以暂时跳过,URL 可以在 Manage Jenkins → Configure System 中再次设置。
这一步结束之后,就可以进入 Jenkins 了。
如果在这一章结束后发生了任何问题,包括但不限于密码不正确、Jenkins 卡死等,你都随时可以把 |
开始动手:初阶
我会用三个比较简单的工程去写这一章 (其中包含直接输出目录的前端以及需要重新启动的后端,还有一个只是获取最新源码并打包的简单工程),主要体现在如何获取新源码、构建并发布上。
在上一章里,我没有配置任何的 JAVA_HOME
MAVEN_HOME
GOPATH
等变量,这一点出于配置好后依旧 command not found。如果有确定问题所在我会更新此文。
第一个项目
项目地址:https://github.com/EpLiar/JenkinsDemo
登录 Jenkins 后,首页左侧会有一个新建项目 (翻译不完整可能会显示成新建 Item),填写名字,此处选择自由风格项目 (Freestyle Project)。
在新打开的页面,源码管理 → Git,填写项目的 https 地址 (clone 仓库所用的那个链接,而不是项目首页地址),同时在下方指定要构建的分支 (分支指定你说了算,默认为 master)。
如果项目是非公开的,会需要账户和密码,但是这个对于开启了 2FA 的账户没有用。因此可以在进阶里查看如何使用 ssh key 来获取仓库。 |
随后在下方的构建处,点击增加构建步骤 → Execute Shell (执行 Shell),填写构建命令
$ PATH="/usr/bin":PATH (1)
$ go build main.go
$ ./main
1 | 你也可以不写这一行,用绝对路径来执行编译 |
随后点击保存,就算完成配置了。
即使你是在 Windows 部署的 Jenkins,它依旧会使用斜杠而不是反斜杠来做路径分隔符。执行当前目录下的程序也需要加上 |
在新打开的页面左侧有一个立即构建 (build now),点击之后会在左侧下方读出一个进度条,打开后找到控制台输出,即可看到编译输出。
前端工程
项目地址:https://github.com/EpLiar/vue-demo
这个项目使用 vue-cli 照着模板新建了一个,但是生成的文件里没有 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 | 前端构建依赖获取,这个步骤如果没有新的依赖项可以只执行一次,也就是第一次构建之后删除。 |
构建,没有问题:
访问 https://epliar.com/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>
重新构建,访问查看结果:
后端工程
与前端不同,通常前端只需要更新静态页面到指定的目录下就可以了,后端需要重启应用,因此本小章在构建的基础上会增加部署的内容。
项目地址: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 | 脚本的当前工作路径是项目代码的主目录 |
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/ 查看结果。
$ curl https://epliar.com/hello/
你好,世界!
接着修改访问接口时输出的内容,检测脚本是否能够自动重新部署。
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!";
}
}
进阶部分
GitHub 推送自动触发构建
这一章在第一个项目的基础上做延伸,做到能在 push 代码够触发 Jenkins 的自动构建。
创建 Personal access token
在 GitHub Settings 里找到 Developer settings,选择 Personal access tokens,之后点击 Generate new token。
这一步可能需要密码 |
-
Note 处起一个方便自己辨认 token 用处的名字,例如 Jenkins。
-
打勾 repo 和 admin:repo_hook。
-
点击最下方的 Generate token。
图片作为选项示例
点击 Generate token 后,会生成一个 token,需要立即保存,只能看到一次。如果忘记,只能再生成一次。
Jenkins 配置 GitHub
在系统管理 → 系统配置 → GitHub,点击添加 GitHub 服务器。名称随意,凭据处点击添加,选择你的用户名。
类型选择 Secret text,把 token 填写到 Secret 处,描述自由发挥,完成后点击添加即可。
在原来的地方,凭据处可以选择刚刚填写好的凭据,右边会有一个连接测试,提示 verified 表示凭据有效。
注意到提示里有一个 rate limit,可能是使用次数限制,遇到了再更新。 |
点击最下方保存,完成 GitHub 的配置。
配置 demo 接收推送
回到首页,打开 demo 项目的配置 (也可以是点击项目后在新页面左侧进入配置)
然后补充以下两点内容:
-
在构建触发器板块,打勾 Github hook trigger for GITScm polling
-
在构建环境板块,打勾 Use secret text(s) or file(s),然后选择上一小节配置的凭据。
-
点击保存。
图片作为参考:
在 GitHub 项目中配置 Webhook
在项目首页的 Settings → Webhooks,点击 Add webhook。
填写你的 Payload URL,通常是 Jenkins 地址加上 github-webhook/
,你也可以在配置凭据的时候点击高级以自定义 URL。例如我的 Jenkins Payload URL 为 https://jenkins.epliar.com/github-webhook/
。
填写完成后点击下方 Add webhook 保存。
如果你在 URL 少写了最后的斜杠,你会在 Webhook 看到报错。如果不介意,不妨试一下 :-) |
修改代码并推送,查看结果
在查看输出的时候代码打印了一句 v1.0
,我们将它修改一下,commit 并推送。
package main;
import "fmt"
func main() {
fmt.Println("v2.0")
}
推送结束后,回到 demo 的主面板,可以看到 GitHub push 已经触发了构建,同时还附带着 commit 信息。
再查看此次构建的控制台输出,可以看到是 GitHub 触发的构建 (Started by GitHub),且代码更新成功。
GitLab 推送自动构建
经历上一节后你应该了解了自动构建的原理,其实大概就是配置好后 GitHub 会在收到 push 后往指定的 URL 发送一个事件请求,Jenkins 收到之后就会开始获取新代码并构建。此处略写 GitLab 同等场景的配置。
在 GitLab 生成 Access Token
-
Preferences → Access Tokens
-
在 Name 填写方便辨认的名字并打勾 api
-
点击 Create personal access token
-
在新打开的页面记住你的 token,只能看到一次
在 Jenkins 安装相关插件
-
系统管理 → 插件管理 → 可选插件,搜索 GitLab Plugin 和 GitLab API
-
打勾,点击 Install without restart
-
等待安装的同时打勾 Restart Jenkins when installation is complete and no jobs are running
-
等待 Jenkins 重启并跳转登录
添加 GitLab 凭据
-
系统管理 → 系统配置 → GitLab
-
Connection name 随意,如果你不是自己搭建的 GitLab,Gitlab host URL 写 https://gitlab.com
-
凭据 → 添加 → 点击你的用户名
-
类型选择 GitLab API token,在 API token 粘贴生成的 token
-
点击 添加
-
点击 测试连接
项目 Webhook
-
在 Jenkins 新建项目,在构建触发器中打勾 Build when a change is pushed to GitLab. 后面的 URL 复制下来。
-
点击 高级 (Advanced),选择 Filter branches by name 并在 Include 填写
master
。
-
在 Secret token 点击 Generate,复制生成的 token,保存
-
GitLab 中项目首页 → Settings → Webhooks
-
在 URL 填写地址
-
Secret token 粘贴第三步生成的 token
-
打勾 Push events
-
点击 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 里添加你的私钥
-
系统管理 → 管理凭据 (Manage Credentials) → 全局 → 添加凭据
-
类型选择 SSH Username with private key
-
起一个你喜欢的 ID
-
如果只用于克隆代码,Username 写 git
-
点击 Enter directly → Add,粘贴你的 ssh 私钥
-
如果你生成密钥的时候使用了密码保护,在 Passphrase 填写你的密码
-
点击确定保存
在 GitHub 添加你的 ssh 公钥
-
Settings → SSH and GPG keys → New SSH key
-
粘贴你生成的公钥 (.pub 文件),注意不是私钥
-
点击 Add SSH key 保存你的密钥 (这一步可能需要输入密码)
克隆并使用你的仓库
仓库地址:https://github.com/EpLiar/private-demo,除了我以外访问都是 404。
-
新建项目
-
填写项目的 ssh clone 地址
-
在凭据处选择你刚刚添加私钥时写的名字
这是一个为了测试私有仓库和 ssh 私钥的项目,因此仓库中只有一个 message
文件,写了一句 This is a private repo.
,因此为了测试可以在构建命令中写打开这个文件。
$ cat message
立即构建,可以看到 Jenkins 通过 SSH 密钥获取到了私有仓库的内容。
后记
写这篇文章用了很长时间,除了工作以外,也和机器配置有点关系。我的服务器本来就有一个 docker 一个 caddy 一个 qBittorrent 在跑,Jenkins 一上负载就快拉满了。同时 Jenkins 的翻译有时局部有时完全,不太可控,因此我在文章中尽可能的把每一个名称的中文和英文都写了出来。
祝大家阅读愉快。
Jenkins 的升级
有手就行,下载新版本的 war 包重启就好了。如果你是使用的包管理安装,那就更简单了,升级完重启就行。
一些不解
我大概从大二开始使用密钥而不是密码去登录自己的服务器,一直以来这个方式都没有出现过问题,之前用密码登录每一次进去都会提示有几次密码尝试错误。我倒也可以接受从堡垒机登录,但是堡垒机还需要 VPN 且在特定的 Windows 工具下登录,我非常反感。我觉得就两种方式,要么堡垒机在公网,登录堡垒机后再登录需要运维的服务器;要么就是 VPN 直接连接服务器。而且 VPN 都是实名的,完全不知道哪里会出问题,而且机子的密码又不能每个人都一样,而且登录的账户又不能用人员来新建,这样大家手里都得有那么一份密码清单来记录…… 这个清单要是丢了,问题岂不是更大?
sshd 是可以通过修改 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/