概述
一次在跟同事討論中忽然萌生了自己手動搭建一套上線系統(tǒng)的想法,第一次上手,所以選用了業(yè)內(nèi)比較成熟的方案-jenkins。之前只是用過jenkins進(jìn)行過一些操作,并未自己從0到1完成搭建,本文記錄下自己整個過程中的遇到的一些問題與解決方案。
準(zhǔn)備知識
linux安裝軟件的方式(知道的可以直接跳過本節(jié)):
一般有三種方式:Linux系統(tǒng)中安裝軟件的幾種方式
源碼包安裝:
下載源碼 -> 解壓 -> 運行configure配置等 -> make 編譯 -> make install 安裝rpm包安裝:
RedHat Package Manager,由紅帽公司提出,建議統(tǒng)一的數(shù)據(jù)庫文件,詳細(xì)記錄軟件包的安裝、卸載等變化信息,能夠自動分析軟件包依賴關(guān)系。用RPM工具可以將二進(jìn)制程序進(jìn)行打包,包被稱為RPM包。RPM包并不是跨平臺的。yum源安裝:
Yellow dog Updater, Modified, 是一個在Fedora和RedHat以及CentOS中的Shell前端軟件包管理器?;赗PM包管理,能夠從指定的服務(wù)器自動下載RPM包并且安裝,可以自動處理依賴性關(guān)系,并且一次安裝所有依賴的軟件包,無須繁瑣地一次次下載、安裝
linux啟動服務(wù)管理兩種方式service和systemctl:
service作為啟動init進(jìn)程的主命令存在一些歷史缺陷,Systemd就是他的升級版,他為系統(tǒng)的啟動和管理提供一套完整的解決方案。Systemd 并不是一個命令,而是一組命令,涉及到系統(tǒng)管理的方方面面:systemctl是 Systemd 的主命令,用于管理系統(tǒng)。
方案一: docker安裝
docker作為目前比較火的一個名詞,自己一直沒機會使用,了解到j(luò)enkins可以通過docker來安裝,于是,從docker開始,進(jìn)入了采坑之旅。
centos上安裝docker
自己的服務(wù)器為阿里云,版本如下(以下所有操作均是基于此臺機器):
uname -a
Linux iZ2zeb34hcp1ui0lowu4atZ 3.10.0-957.5.1.el7.x86_64 #1 SMP Fri Feb 1 14:54:57 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
docker的安裝還是比較簡單的,參照阿里云官方文檔幾分鐘搞定:
添加yum源
# yum install epel-release –y // 安裝并啟用 EPEL 源。
# yum clean all
# yum list
安裝并運行Docker
# yum install docker-io –y
# systemctl start docker // 啟動docker服務(wù)
檢查安裝結(jié)果。
# docker info
關(guān)于 docker
"Docker" 的本質(zhì)其實是解決了應(yīng)用服務(wù)的 "隱私" 問題,實現(xiàn)進(jìn)程、內(nèi)存、文件、網(wǎng)絡(luò)之間相互隔離。也可以簡單把 Docker 理解成一種虛擬機,很多應(yīng)用服務(wù)可以像桌面軟件那樣一鍵安裝,免部署和環(huán)境配置。
前端為什么需要使用 Docker?
- 對于 Full Stack 工程師。Docker 可以提供一種簡單輕便的服務(wù)器編程環(huán)境,而且可以隨用隨刪、降低環(huán)境配置成本。
- 很多 FE 日常工作中需要跟 Nginx、MongoDB、MySQL 等服務(wù)器應(yīng)用打交道。用 Docker 可以很容易部署一個測試環(huán)境,學(xué)習(xí)和倒騰.
Docker 中的三個概念
Container - 容器
Image - 鏡像
Registry - 倉庫
可以像下面這張圖來類比:

docker 安裝jenkins
使用下面的 docker run 命令運行 jenkinsci/blueocean 鏡像作為Docker中的一個容器(如果本地沒有鏡像,這個命令會自動下載):
docker run \
--rm \
-u root \
-p 8080:8080 \
-v jenkins-data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$HOME":/home \
jenkinsci/blueocean
此時就進(jìn)入jenkins安裝流程,具體參照此處jenkins官網(wǎng)安裝示例:
啟動服務(wù),輸入密碼繼續(xù)(安裝過程忘記截圖了,以下圖收集于網(wǎng)絡(luò));

啟動后安裝推薦插件:


安裝插件完成后,進(jìn)入設(shè)置初始賬號密碼:

設(shè)置完畢即可進(jìn)入jenkins主頁:

可能遇到的問題:
1.訪問接口出現(xiàn):Error:403 No valid crumb was included in the request
解決:關(guān)閉安全設(shè)置里面的-防止款站點請求偽造選項,具體參照此處。
jenkins 配置nodejs
進(jìn)入插件管理,安裝Nodejs Plugin:

進(jìn)入全局工具配置,配置項目中會用到的nodejs版本,可以配置多個

jenkins + github 配置項目
開始創(chuàng)建項目,選擇自由風(fēng)格:

開始配置項目(以github私有項目為例)

此處我們填寫完git地址后需要添加憑證,添加其他個人私有密鑰不知道為啥一直在下拉菜單中選不到,添加用戶名密碼則可以,暫時還不知道啥原因~


觸發(fā)器中我們選擇hook,push之后,自動觸發(fā)構(gòu)建

此處在github中也要做對應(yīng)配置才可出發(fā)hook功能:

也可以自定義配置,譬如,push 與 merge時觸發(fā),選擇Let me select individual events,勾選以下選項:
Deployments
Deployment statuses
Pull requests
Pushes
接下來我們配置拉去完代碼后需要執(zhí)行的腳本,centos上我們選擇shell腳本:

至此,一個簡單的配置已經(jīng)完畢,可以進(jìn)行自動構(gòu)建測試了!!
doker 模式下遇到的問題
我們隨便改一點東西,向master分支push代碼,觸發(fā)構(gòu)建;構(gòu)建任務(wù)正常觸發(fā),但執(zhí)行到shell腳本時卻出現(xiàn)了異常:
[test-project] $ /bin/sh -xe /tmp/jenkins6958996694563138608.sh
+ node -v
/tmp/jenkins6958996694563138608.sh: line 2: node: not found
Build step 'Execute shell' marked build as failure
Finished: FAILURE
通過docker exec -it 4c0fd5e5f2c5 bash 命令進(jìn)入容器內(nèi)部bash訪問:
cd /var/jenkins_home/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/nodejs_10.15.3/
nodejs文件夾存在,但執(zhí)行 bin/node -v 報錯,提示 : No such file or directory
解決:終于在stackoverflow 翻到一個大佬的回答,原因如下:
This happens because the image doesn't contain libstdc++.so.6 as needed by nodejs
In other words, node: not found does not mean node is not installed (it is, it is executable and found in the $PATH).
It means one of node dependencies is not found.
我們通過,手動安裝:
apk add --no-cache --update nodejs nodejs-npm
拉取的是Node.js Alpine 鏡像,這個鏡像做個優(yōu)化,并沒有內(nèi)置npm包(本人驗證 10.14.2是沒有的)需要手動再安裝npm;
再次構(gòu)建
/var/jenkins_home/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/nodejs_10.15.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/jvm/java-1.8-openjdk/jre/bin:/usr/lib/jvm/java-1.8-openjdk/bin
+ node -v
v10.14.2
+ npm -v
6.4.1
Finished: SUCCESS
執(zhí)行成功,考慮到調(diào)試?yán)щy和后續(xù)還需要全局安裝vue-cli3.0 于是決定放棄docker方式,改用RPM方式再次安裝jenkins
RPM方式安裝jenkins
因為Jenkins是基于java的,所以需要先安裝jdk
安裝java環(huán)境
sudo yum install java
下載rpm包:
通過RedHat Linux RPM packages for Jenkins網(wǎng)站下載所需的rpm包,我安裝的是jenkins-2.195-1.1.noarch.rpm
安裝jenkins
sudo rpm -ih jenkins-2.73.2-1.1.noarch.rpm
自動安裝完成之后:
/usr/lib/jenkins/jenkins.war WAR包
/etc/sysconfig/jenkins 配置文件
/var/lib/jenkins/ 默認(rèn)的JENKINS_HOME目錄
/var/log/jenkins/jenkins.log Jenkins日志文件
啟動jenkins
sudo systemctl start jenkins.service
之后的操作與上一步docker安裝方式基本一摸一樣,唯一區(qū)別的就是器nodejs安裝方式
node調(diào)用centos全局配置
取消構(gòu)建環(huán)境配置下的:Provide Node & npm bin/ folder to PATH 即可
完善整個構(gòu)建流程
其實主要就是shell腳本的編寫,項目中配置如下(新建了另外一個項目vue-build-project):
node -v
echo "開始安裝依賴..."
npm install
echo "開始打包..."
npm run build
base="/home/upload-upyun"
dest="vue-build-project"
cp -r ./dist ${base}/${dest}
echo "開始上傳..."
node ${base}/index.js ${dest}
rm -rf ${base}/${dest}
/home/upload-upyun/index.js 內(nèi)容如下:
const argPath=(process.argv.splice(2));//通過透傳參數(shù)獲取文件夾目錄
const uploadFolder=(argPath[0]?argPath[0]:'').replace(/^--/g,'');
const rootPath=(argPath[1]?argPath[1]:'').replace(/^--/g,''); //靜態(tài)資源服務(wù)器的上傳根路徑
const fs=require("fs");
const path=require("path");
const upyun = require("upyun/dist/upyun.common");
const localFileArr=[];
function readPathSync (p) {
if(!fs.existsSync(p)) return;
const stat = fs.statSync(p)
if (stat.isDirectory()) {
const ls = fs.readdirSync(p).map(file => path.join(p, file))
for (let i = 0; i < ls.length; i++) {
readPathSync(ls[i])
}
} else {
localFileArr.push(p)
}
}
const ypyConf={
"serviceName" : "xxx",
"operatorName" : "xxx",
"password" :"xxx",
"remotePath" rootPath,
};
// 需要填寫本地路徑,云存儲路徑
const remoteRoot = ypyConf.remotePath;
const upService = new upyun.Service(ypyConf.serviceName, ypyConf.operatorName, ypyConf.password);
const upClient = new upyun.Client(upService);
const prefix=(path.join(__dirname))
// 上傳參數(shù)
// console.log(upClient)
function uploadFile(localFile){
const remoteFile= remoteRoot+(localFile.replace(prefix,'')).split(path.sep).join("/")
upClient.putFile(remoteFile, fs.createReadStream(localFile), {
'Date': new Date(),
'Content-Length': fs.statSync(localFile).size,
}).then(res => {
if (res) {
console.log(remoteFile+":上傳成功")
} else {
console.log(remoteFile+":上傳失敗")
}
}).catch(err => {
console.log("上傳出現(xiàn)異常!")
})
}
readPathSync(path.join(__dirname,uploadFolder));
localFileArr.map((item)=>{
uploadFile(item)
})
最終構(gòu)建效果
16:21:48 [vue-build-project] $ /bin/sh -xe /tmp/jenkins8111626610056806132.sh
16:21:48 + node -v
16:21:48 v10.15.3
16:21:48 + echo 開始安裝依賴...
16:21:48 開始安裝依賴...
16:21:48 + echo 開始打包...
16:21:48 開始打包...
16:21:48 [vue-build-project] $ /bin/sh -xe /tmp/jenkins6507915611034625552.sh
16:21:48 + cd /var/lib/jenkins/workspace
16:21:48 + cd vue-build-project
16:21:48 + cp -r ./dist /home/upload-upyun/vue-build-project
16:21:48 + echo 開始上傳...
16:21:48 開始上傳...
16:21:48 + node /home/upload-upyun/index.js vue-build-project
16:21:48 /test/vue-build-project/favicon.ico:上傳成功
16:21:48 /test/vue-build-project/index.html:上傳成功
16:21:48 /test/vue-build-project/static/js/app.6e3674b9.js:上傳成功
16:21:48 /test/vue-build-project/static/css/app.e2713bb0.css:上傳成功
16:21:48 /test/vue-build-project/static/img/logo.82b9c7a5.png:上傳成功
16:21:48 /test/vue-build-project/static/js/chunk-vendors.a1771d7d.js:上傳成功
16:21:48 + rm -rf /home/upload-upyun/vue-build-project
16:21:48 Finished: SUCCESS
遇到的問題
在npm install 這一步出現(xiàn)了一個問題困擾了我兩天,執(zhí)行過程中jenkins進(jìn)程會忽然掛掉,一直以為是配置的問題,后來忽然意識到可能是被centos給干掉了,查看日志:
grep "Out of memory" /var/log/messages
得到日志如下:

時間節(jié)點與我構(gòu)建時完全相同?。。?!,坑?。。。〔殚嗁Y料明白是觸發(fā)了 linux 的 OOM killer 機制:
于是乎重啟服務(wù)器,只開啟Jenkins,重新構(gòu)建。我的阿里云服務(wù)器可憐的1G內(nèi)存在構(gòu)建過程中的內(nèi)存變化如下:

所以,都是貧窮惹的禍~
根據(jù)git diff 選擇性上傳文件
有些情況下,我們只需要上傳變動的文件,并不需要上傳所有文件,此時shell部分可以這樣編寫:
diff=`git diff --name-only HEAD~1 HEAD~0`
base="/home/upload-upyun"
dest="static-demo"
rm -rf ${base}/${dest}
# 創(chuàng)建目標(biāo)文件夾
mkdir -p ${base}/${dest}
# 循環(huán)復(fù)制變動文件(被刪除文件忽略,只做新增與覆蓋)
for line in $diff
do
if [ -f $line ];then
cp --parents -afv $line ${base}/${dest}
fi
done
echo "開始上傳..."
node ${base}/index.js --${dest}
rm -rf ${base}/${dest}
jenkins的其他安裝方式
也可以通過以下方式安裝Jenkins,本文不再嘗試,效果與rpm方式理論應(yīng)該等同,具體可參照:
- yum進(jìn)行安裝:jenkins yum 安裝
- war包安裝:WAR包方式安裝Jenkins
Generic Webhook Trigger Plugin
上述的webhook是通過github-plugin 進(jìn)行觸發(fā),但對于其他git托管,如騰訊云,阿里云,碼云等其他第三方git服務(wù)提供平臺,并沒有對應(yīng)的插件,此時,就輪到 Generic Webhook Trigger Plugin 插件登場:
首先,進(jìn)行安裝,過程省略。安裝完畢后在項目配置頁面可以看到多出一個配置:

我們需要拿到webhook的地址,根據(jù)官方文檔可以知道,我們需要拿到Jenkins管理員的API token, 進(jìn)入管理用戶頁面,選擇admin生成一個:

最終得到的webhook地址為:http://xxx:8080/generic-webhook-trigger/invoke?token=11d85c3af55e018axxxxxx, 這里我們以騰訊云為例,配置下webhook:

我們再回到項目配置頁面,需要對該插件做一些配置, 默認(rèn)我們配置好webhook后,所有配置過該webhook的頁面,所有項目與分支的任意一個變動都可以觸發(fā)所有項目的構(gòu)建,這顯然不是我想要的,我們需要做一些區(qū)分,參照此文章:
- 區(qū)分分支

- 區(qū)分項目(不同服務(wù)提供商字段會有差異,騰訊云是在repository下)

- 配置token

- 過濾字段匹配項目(此處為每個項目特有配置,區(qū)分好項目與分支)

我們手動構(gòu)建一次可以看到webhook返回結(jié)果如下:
{
"ref": "refs/heads/dev",
"before": "6b53d96f0ee3dc5b1b60d389105d330641ac1612",
"after": "9370030232017ff649a070ae6bf1bdd7522a037c",
"created": false,
"deleted": false,
"compare": "\u003ca href\u003d\u0027https://coding.net/u/wangxx/p/jenkins-autoupload\u0027 target\u003d\u0027_blank\u0027\u003ejenkins-autoupload\u003c/a\u003e/git/compare/6b53d96f0ee3d...9370030232017",
"commits": [{
"id": "9370030232017ff649a070ae6bf1bdd7522a037c",
"distinct": false,
"message": "fix:添加文件\n",
"timestamp": 1569831379000,
"url": "\u003ca href\u003d\u0027https://coding.net/u/wangxx/p/jenkins-autoupload\u0027 target\u003d\u0027_blank\u0027\u003ejenkins-autoupload\u003c/a\u003e/git/commit/9370030232017ff649a070ae6bf1bdd7522a037c",
"author": {
"name": "wangxx",
"email": "m.h.wang@foxmail.com",
"username": "wangxx"
},
"committer": {
"name": "wangxx",
"email": "m.h.wang@foxmail.com",
"username": "wangxx"
},
"added": ["dofun.png"],
"removed": [],
"modified": []
}],
"head_commit": {
"id": "9370030232017ff649a070ae6bf1bdd7522a037c",
"distinct": false,
"message": "fix:添加文件\n",
"timestamp": 1569831379000,
"url": "\u003ca href\u003d\u0027https://coding.net/u/wangxx/p/jenkins-autoupload\u0027 target\u003d\u0027_blank\u0027\u003ejenkins-autoupload\u003c/a\u003e/git/commit/9370030232017ff649a070ae6bf1bdd7522a037c",
"author": {
"name": "wangxx",
"email": "m.h.wang@foxmail.com",
"username": "wangxx"
},
"committer": {
"name": "wangxx",
"email": "m.h.wang@foxmail.com",
"username": "wangxx"
},
"added": ["dofun.png"],
"removed": [],
"modified": []
},
"pusher": {
"name": "王xx",
"email": "m.h.wang@foxmail.com",
"username": "wangxx"
},
"sender": {
"id": 2222972,
"login": "wangxx",
"avatar_url": "https://coding-net-production-static-ci.codehub.cn/e4ed6f51-7033-4c66-bb1c-d567795c88a9.jpg?imageMogr2/auto-orient/format/jpeg/cut/!640x640x0x0",
"url": "https://dev.tencent.com/api/user/key/wangxx",
"html_url": "https://dev.tencent.com/u/wangxx",
"name": "王xx",
"name_pinyin": "|wmh|wangxx"
},
"repository": {
"id": 4733490,
"name": "jenkins-autoupload",
"full_name": "wangxx/jenkins-autoupload",
"owner": {
"id": 2222972,
"login": "wangxx",
"avatar_url": "https://coding-net-production-static-ci.codehub.cn/e4ed6f51-7033-4c66-bb1c-d567795c88a9.jpg?imageMogr2/auto-orient/format/jpeg/cut/!640x640x0x0",
"url": "https://dev.tencent.com/api/user/key/wangxx",
"html_url": "https://dev.tencent.com/u/wangxx",
"name": "王xx",
"name_pinyin": "|wmh|wangxx"
},
"private": true,
"html_url": "\u003ca href\u003d\u0027https://dev.tencent.com/u/wangxx/p/jenkins-autoupload\u0027 target\u003d\u0027_blank\u0027\u003ejenkins-autoupload\u003c/a\u003e",
"description": "測試jenkins自動化部署上傳",
"fork": false,
"url": "https://dev.tencent.com/api/user/wangxx/project/jenkins-autoupload",
"created_at": 1568866930000,
"updated_at": 1568866930000,
"clone_url": "https://git.dev.tencent.com/wangxx/jenkins-autoupload.git",
"ssh_url": "git@git.dev.tencent.com:wangxx/jenkins-autoupload.git",
"default_branch": "master"
}
}
至此,配置基本完畢。
權(quán)限部分配置
通過插件 Role-based Authorization Strategy 配置完成,安裝完插件,重啟Jenkins,會發(fā)現(xiàn)配置頁面多一個選項:

首先需要在全局安全配置頁面將授權(quán)策略改為:Role-Based Strategy

進(jìn)入 Manage Role 選項,配置用戶角色與所在組角色權(quán)限

進(jìn)入 Assign Roles 選項,為用戶分配角色(首先需要在用戶管理板塊創(chuàng)建用戶)

具體可參考jenkins配置用戶角色權(quán)限,根據(jù)不同權(quán)限顯示視圖、Job
總結(jié)
docker安裝方式比較傻瓜,而且整個插件安裝的過程都比較快,但是因為docker就像一個封閉的黑盒,很多東西與系統(tǒng)是隔離開的(廢話,設(shè)計初衷就是這樣),導(dǎo)致我這個菜鳥遇到問題想不到好的解決方案。嗯,還是RPM用起來更順手一些,遇到問題基本還能應(yīng)對。最后,不差錢的,請上大內(nèi)存服務(wù)器~
可能用到的linux命令
# 啟動docker
systemctl start docker
# 停止docker
systemctl stop docker
# 列出docker內(nèi)正在運行的容器
docker ps
# 列出docker內(nèi)的容器
docker ps -a
# 啟動docker內(nèi)容器
docker start <id>
docker restart <id>
docker stop <id>
# 進(jìn)入docker容器內(nèi)部執(zhí)行命令
docker exec -it <id> bash
# 查看jenkins啟動狀態(tài)
systemctl status jenkins.service
# 查看jenkins日志
sudo tail -f /var/log/jenkins/jenkins.log 查看日志
補充:增加swap分區(qū)解決阿里云內(nèi)存不足
發(fā)現(xiàn)可以擴展Swap分區(qū),即交換區(qū),系統(tǒng)在物理內(nèi)存(這里應(yīng)該是運行內(nèi)存)不夠時,與Swap進(jìn)行交換,來解決內(nèi)存不足的問題。設(shè)置步奏如下:
-
首先創(chuàng)建用于交換分區(qū)的文件,并設(shè)置交換分區(qū)文件
dd if=/dev/zero of=/var/swap bs=1024 count=4096000 -
創(chuàng)建 swap 文件
mkswap /var/swap -
加載這個文件
swapon /var/swap
執(zhí)行以上命令可能會出現(xiàn):“不安全的權(quán)限 0644,建議使用 0600”提示,其實已經(jīng)激活了,可以忽略,修改權(quán)限:
chmod -R 0600 /var/swap
-
設(shè)置系統(tǒng)啟動時自動掛載分區(qū)
echo "/var/swap swap swap defaults 0 0" >> /etc/fstab -
確定系統(tǒng)對SWAP分區(qū)的使用原則,當(dāng)swappiness內(nèi)容的值為0時,表示最大限度地使用物理內(nèi)存,物理內(nèi)存使用完畢后,才會使用SWAP分區(qū)。當(dāng)swappiness內(nèi)容的值為100時,表示積極地使用SWAP分區(qū),并且把內(nèi)存中的數(shù)據(jù)及時地置換到SWAP分區(qū)。liunx默認(rèn)為60,此處我們設(shè)置為默認(rèn)大小60
echo 60 > /proc/sys/vm/swappiness
再次運用構(gòu)建命令,查看內(nèi)存變化,會發(fā)現(xiàn)swap區(qū)已經(jīng)得到了利用:
