前言
基礎(chǔ)平臺(tái)搭建上篇 介紹項(xiàng)目流程設(shè)計(jì)耳舅、數(shù)據(jù)庫(kù)搭建、jwt 登錄等模塊
基礎(chǔ)平臺(tái)搭建中篇 介紹分支管理設(shè)計(jì)营搅、webSocket 基礎(chǔ)模塊
基礎(chǔ)平臺(tái)搭建下篇 介紹流程管理涵防、提測(cè)相關(guān)基礎(chǔ)模塊
基礎(chǔ)篇主要介紹了項(xiàng)目管理流程的基礎(chǔ)架構(gòu)設(shè)計(jì)與一些基本的 node 開(kāi)發(fā)铜幽,本篇開(kāi)始構(gòu)建系統(tǒng)系列驴剔,簡(jiǎn)單講述一下 jenkins 與項(xiàng)目管理系統(tǒng)的配合
Jenkins
Jenkins 是什么
Jenkins 是一個(gè)開(kāi)源的、提供友好操作界面的持續(xù)集成(CI)工具跋核,起源于 Hudson(Hudson 是商用的)岖瑰,主要用于持續(xù)、自動(dòng)的構(gòu)建/測(cè)試軟件項(xiàng)目砂代、監(jiān)控外部任務(wù)的運(yùn)行蹋订。Jenkins 用 Java 語(yǔ)言編寫(xiě),可在 Tomcat 等流行的 servlet 容器中運(yùn)行刻伊,也可獨(dú)立運(yùn)行露戒。通常與版本管理工具(SCM)、構(gòu)建工具結(jié)合使用捶箱。常用的版本控制工具有 SVN智什、GIT,構(gòu)建工具有 Maven丁屎、Ant荠锭、Gradle。
在此項(xiàng)目中晨川,Jenkins 作為主要構(gòu)建工具來(lái)搭配使用
Jenkins 安裝(踩坑合集)
一般我的博客是不會(huì)介紹具體安裝過(guò)程的节沦,但是這個(gè)讓我踩了一天的坑键思,還是列一下吧(主要是 windows 踩坑多)
Windows 環(huán)境
直接下載 Jenkins 安裝包,再下載一個(gè) JAVA SDK 甫贯,直接點(diǎn)擊安裝一把梭。
安裝完之后需要密碼看蚜,64 位系統(tǒng)的同學(xué)叫搁,記得不要去 sys32 目錄找,雖然網(wǎng)頁(yè)顯示的路徑是 sys32
但是 C:\Windows\SysWOW64\config\systemprofile\AppData\Local\Jenkins.jenkins 這個(gè)才是正確的路徑
不建議用 windows 版本供炎,再不濟(jì)搞個(gè)虛擬機(jī)渴逻,體驗(yàn)效果翻倍
Mac 環(huán)境
建議用 brew 直接下載,比較容易管理
brew 安裝過(guò)慢的解決方案:點(diǎn)擊這里
插件安裝速度過(guò)慢
先進(jìn)入 http://ip:10086/pluginManager/advanced 插件管理頁(yè)音诫,將地址從 https://updates.jenkins.io/update-center.json 換成 http://mirror.xmission.com/jenkins/updates/update-center.json惨奕,可以提高下載速度。
修改 Jenkins/updates/default.json 配置
替換 updates.jenkins-ci.org/download 為 mirrors.tuna.tsinghua.edu.cn/jenkins
完了直接 http://ip:10086/restart 重啟
docer 安裝不建議竭钝,高射炮打蚊子的事情少干
pipeline 腳本
先簡(jiǎn)單介紹一下
聯(lián)系:node, agent 以及 slave 都用來(lái)指被 Jenkins master 管理的用來(lái)執(zhí)行 Jenkins jobs 的服務(wù)器梨撞。
區(qū)別:agents 用在表述性 pipeline 中,可以不僅僅是 nodes ,還可以是 docker container 等香罐。node(這個(gè)不是那個(gè) js node) 用在腳本化 pipeline 中卧波。
直接上 node pipeline 腳本,強(qiáng)擼一把(簡(jiǎn)單先跑起來(lái)庇茫,后期需要優(yōu)化的地方還是很多的)
配置參數(shù):
名稱 | 描述 |
---|---|
PROJECT_NAME | 工程名稱 |
PROJECT_VERSION | 工程版本號(hào) |
PROJECT_GIT_PATH | 工程地址 |
BRANCH_NAME | 工程分支 |
BUILD_PATH | 構(gòu)建目錄 |
CACHE | 是否緩存 |
node {
stage('Pre Git') {
echo "${params.PROJECT_NAME},${params.PROJECT_VSERSION},${params.PROJECT_GIT_PATH}"
dir("D:/jenkins/build") {
if(fileExists("${params.PROJECT_NAME}")) {
echo " git exit"
dir("D:/jenkins/build/${params.PROJECT_NAME}") {
powershell " git fetch --all && git reset --hard origin/${params.BRANCH_NAME} && git pull"
powershell " git checkout ${params.BRANCH_NAME}"
}
} else {
echo " git is not exit"
powershell " git clone ${params.PROJECT_GIT_PATH}"
}
}
}
stage('Pre Env') {
echo "check node_modules,${params.CACHE}"
dir("D:/jenkins/build/${params.PROJECT_NAME}") {
if(!fileExists("node_modules")) {
powershell "cnpm i"
}
if(!params.CACHE) {
echo "CACHE --- ${params.CACHE}"
powershell "rimraf node_modules"
powershell "cnpm i"
}
}
}
stage('build') {
echo "check node_modules"
dir("D:/jenkins/build/${params.PROJECT_NAME}") {
bat "npm run build"
}
}
stage('test') {
echo "test case"
}
stage('deploy') {
echo "deploy project"
if(!fileExists("D:/jenkins/deploy/${params.PROJECT_NAME}")) {
powershell " mkdir D:/jenkins/deploy/${params.PROJECT_NAME}"
}
if(!fileExists("D:/jenkins/deploy/${params.PROJECT_NAME}/${params.PROJECT_VERSION}")) {
powershell " mkdir D:/jenkins/deploy/${params.PROJECT_NAME}/${params.PROJECT_VERSION}"
}
powershell "cp D:/jenkins/build/${params.PROJECT_NAME}/${params.BUILD_PATH}/* D:/jenkins/deploy/${params.PROJECT_NAME}/${params.PROJECT_VERSION} -Recurse"
}
}
上述腳本創(chuàng)建了 5 個(gè) stage港粱,將構(gòu)建流程拆分為 5 個(gè)步驟:
- 拉取項(xiàng)目:判斷本地存不存在項(xiàng)目,存在就 pull 不存在直接 clone
- 安裝項(xiàng)目依賴:判斷項(xiàng)目依賴是否安裝完畢旦签,沒(méi)有安裝且強(qiáng)制清除緩存的情況下查坪,先安裝依賴
- 項(xiàng)目構(gòu)建:運(yùn)行項(xiàng)目構(gòu)建,此處后期可以把腳本抽出來(lái)
- 項(xiàng)目測(cè)試:預(yù)留宁炫,后面做項(xiàng)目流程卡點(diǎn)使用
- 項(xiàng)目發(fā)布:直接 cp 或者 ssh 上傳到你發(fā)布的地方即可(本地搭建了 Nginx 環(huán)境偿曙,所以復(fù)制到對(duì)應(yīng)的目錄即可)
當(dāng)然上述的腳本只完成了簡(jiǎn)單的構(gòu)建任務(wù),遇到復(fù)雜的系統(tǒng)會(huì)直接跪淋淀,簡(jiǎn)單列舉下大概率會(huì)遇到的問(wèn)題
- 多端構(gòu)建遥昧,比如一個(gè)項(xiàng)目需要直接構(gòu)建多端產(chǎn)物
- 區(qū)分開(kāi)發(fā)、測(cè)試朵纷、預(yù)發(fā)炭臭、線上等多環(huán)境
- 多命令構(gòu)建,復(fù)雜項(xiàng)目可能需要執(zhí)行多條命令袍辞,才能完成構(gòu)建產(chǎn)物
- 構(gòu)建產(chǎn)出目錄鞋仍,發(fā)布目錄等不確定性
- 等等…………………………
可以根據(jù)參數(shù)傳遞,多腳本等等配合解決上述問(wèn)題搅吁,具體要根據(jù)業(yè)務(wù)來(lái)設(shè)計(jì)
由于我是 windows 系統(tǒng)威创,所以文件處理比較坑爹落午,運(yùn)行速度慢而腳本還難寫(xiě),如果你想用的話肚豺,強(qiáng)烈建議上 linux溃斋。
上圖一共構(gòu)建 5 次,由于加了緩存判斷吸申,所以第一次構(gòu)建的時(shí)候梗劫,會(huì)去安裝對(duì)應(yīng)的依賴,耗時(shí)比較多截碴。
但是一般來(lái)說(shuō)梳侨,長(zhǎng)期迭代的項(xiàng)目,依賴變動(dòng)不會(huì)太頻繁日丹,所以只需要判斷是否安裝過(guò)依賴即可走哺,后續(xù)跳過(guò)依賴安裝,直接走構(gòu)建流程哲虾,節(jié)約倒杯水的時(shí)間丙躏。
看自己選擇,高興每次構(gòu)建全部重新安裝依賴也可以妒牙。做人嘛彼哼,開(kāi)心最重要!
構(gòu)建產(chǎn)物演示
請(qǐng)注意上圖的鏈接有個(gè)版本號(hào)湘今,這個(gè)需要配合腳手架一起改造敢朱,在腳手架篇會(huì)具體介紹
Nginx
Nginx 是一款輕量級(jí)的 Web 服務(wù)器/反向代理服務(wù)器及電子郵件(IMAP/POP3)代理服務(wù)器,并在一個(gè) BSD-like 協(xié)議下發(fā)行摩瞎,可以在 UNIX拴签、GNU/Linux、BSD旗们、Mac OS X蚓哩、Solaris,以及 Microsoft Windows 等操作系統(tǒng)中運(yùn)行上渴。
在實(shí)際的運(yùn)作中岸梨,可以支持二萬(wàn)至四萬(wàn)并發(fā),性價(jià)比極高稠氮。沒(méi)錢(qián)就自己搭建曹阔,有錢(qián)直接上 cos, cdn隔披,買(mǎi)云服務(wù)它不香嗎赃份? PS:有錢(qián)真好!
server { #這里是我自己配置服務(wù)端口
listen 10010;
server_name resouce;
root D:/jenkins/deploy; #訪問(wèn)文件根目錄
autoindex on; #是否瀏覽文件下的列表
location / { #是否允許跨域
add_header Access-Control-Allow-Origin *;
}
add_header Cache-Control "no-cache,must-revalidate";# 是否緩存
}
根據(jù)上述配置,可以簡(jiǎn)單的配置一個(gè)靜態(tài)服務(wù)器抓韩。把前端項(xiàng)目丟進(jìn)去纠永,直接訪問(wèn)對(duì)應(yīng)的端口即可。
上述代碼谒拴,直接 copy 到 nginx.config 里面尝江,然后重啟即可
DevOps Jenkins Coding
封裝基礎(chǔ) Jenkins Api
項(xiàng)目選擇 jenkins 庫(kù)來(lái)拓展,注意如果你使用 TS 模式的話英上,需要安裝 @types/jenkins 依賴茂装。
import * as jenkins from "jenkins";
/**
* Jenkins連接
* @param type
*/
const getJenkins = function (
type: "h5" | "node" | "nodeProduct" | "android" | "java"
) {
const jenkinsConfig = {
h5: {
baseUrl: "http://devOps:118844ffb045d994acf8bb353e8d7b34f0@localhost:9001",
crumbIssuer: true,
},
node: {
baseUrl:
"http://devOps:118844ffb045d994acf8bb353e8d7b34f0@localhost:9001",
crumbIssuer: true,
},
};
return jenkins(jenkinsConfig[type]);
};
/**
* @description: 觸發(fā)jenkins流水線
*/
const buildJenkins = async ({ type, job, params }) => {
const jenkinsCallback: any = await new Promise((resolve) => {
getJenkins(type).job.build(
{ name: job, parameters: params },
(err: any, data: any) => {
if (err) {
console.log("err: ", err);
throw err;
}
resolve({ queueId: data });
}
);
});
return { data: jenkinsCallback };
};
/**
* @description: 獲取當(dāng)前節(jié)點(diǎn)信息
*/
const getQueuedInfo = async ({ type, queueId }) => {
const jenkinsCallback: any = await new Promise((resolve) => {
getJenkins(type).queue.item(queueId, (err: any, data: any) => {
if (err) {
console.log("err---->", err);
throw err;
}
resolve(data);
});
});
return { data: jenkinsCallback };
};
/**
* @description: 獲取當(dāng)前構(gòu)建信息
*/
const getJenkinsInfo = async ({ type, job, buildNumber }) => {
console.log(type, job, buildNumber);
const jenkinsCallback: any = await new Promise((resolve) => {
getJenkins(type).build.get(job, buildNumber, (err: any, data: any) => {
console.log("data: ", data);
console.log("err: ", err);
if (err) {
console.log("err---->", err);
throw err;
}
resolve(data);
});
});
const { statusCode } = jenkinsCallback;
if (jenkinsCallback && statusCode !== 404) {
return { data: jenkinsCallback };
} else {
return { data: jenkinsCallback };
}
};
/**
* @description: 獲取jenkins console.log 信息
*/
const getJenkinsConsole = async ({ type, job, buildId }) => {
const jenkinsCallback: any = await new Promise((resolve) => {
getJenkins(type).build.log(job, buildId, (err: any, data: any) => {
if (err) {
return console.log("err---->", err);
}
resolve(data);
});
});
return { data: jenkinsCallback };
};
export default {
buildJenkins,
getQueuedInfo,
getJenkinsInfo,
getJenkinsConsole,
};
上述是對(duì) Jenkins 的基本封裝,簡(jiǎn)單的封裝了一些我們需要用到的方法善延,具體的定制化,可以結(jié)合業(yè)務(wù)自己設(shè)計(jì)城侧。
各端的業(yè)務(wù)構(gòu)建易遣,可以選擇多個(gè) Jenkins 項(xiàng)目或者不同的 job 區(qū)分,不建議一個(gè) job 直接擼到黑嫌佑,這樣腳本會(huì)很復(fù)雜豆茫。
真的一個(gè) job 擼到黑的人,敬你是條漢子
// Controller
import { Post, Prefix, Get } from "egg-shell-decorators";
import BaseController from "./base";
@Prefix("build")
export default class BuildController extends BaseController {
/**
* @description: 創(chuàng)建構(gòu)建任務(wù)
*/
@Post("/creatJob")
public async getUserToken({
request: {
body: { params },
},
}) {
const { ctx, app } = this;
const { access_token: accessToken } = this.user;
const {
projectId,
branchName,
projectVersion,
buildPath,
type,
cache,
} = params;
const project = await ctx.service.project.getProject({ projectId });
let projectGitPath = project.projectUrl.replace(
"http://",
`https://oauth2:${accessToken}@`
);
const callBack = await ctx.service.build.buildProject({
type,
projectName: project.projectGitName,
projectVersion,
projectGitPath: `${projectGitPath}.git`,
branchName,
buildPath,
cache,
});
this.success(callBack);
}
}
// Service
import { Service } from "egg";
export default class Build extends Service {
/**
* @description: 構(gòu)建項(xiàng)目
*/
public async buildProject({
type = "h5",
projectName,
projectVersion,
projectGitPath,
branchName,
buildPath,
cache,
}) {
const { ctx } = this;
const callBack = await ctx.helper.api.jenkins.index.buildJenkins({
type,
job: "fe-base-h5",
params: {
PROJECT_NAME: projectName,
PROJECT_VERSION: projectVersion,
PROJECT_GIT_PATH: projectGitPath,
BRANCH_NAME: branchName,
BUILD_PATH: buildPath,
CACHE: cache,
},
});
return callBack;
}
}
上述是業(yè)務(wù)代碼屋摇,一般獲取返回值的時(shí)候揩魂,存一下記錄 queueId, 通過(guò)調(diào)用 Jenkins api 獲取發(fā)布時(shí)間跟日志
如上圖炮温,將 Jenkins 與項(xiàng)目管理系統(tǒng)聯(lián)合起來(lái)火脉,方便用戶操作。
構(gòu)建信息推送
前端輪詢
直接用返回的 queueId 輪詢 Jenkins Api柒啤,可以直接獲取信息
優(yōu)點(diǎn):暴力倦挂、簡(jiǎn)單,開(kāi)發(fā)速度最快担巩,較為迅速
缺點(diǎn):用戶離開(kāi)頁(yè)面將無(wú)法感知方援,數(shù)據(jù)落庫(kù)會(huì)中斷,且極度消耗性能涛癌,多個(gè)用戶在操作同一個(gè)項(xiàng)目時(shí)犯戏,無(wú)法及時(shí)通知到位
后臺(tái)輪詢 + socket
Node 后臺(tái)通過(guò) queueId 直接輪詢 Jenkins Api,通過(guò) websocket 推送到前端展示
優(yōu)點(diǎn):暴力拳话,開(kāi)發(fā)速度先匪、難度適中,用戶即使離開(kāi)頁(yè)面假颇,數(shù)據(jù)依然能夠落庫(kù)胚鸯,可以同時(shí)推送到多個(gè)用戶
缺點(diǎn):Node 后臺(tái)性能消耗增加,需要前后臺(tái)一起配合開(kāi)發(fā)笨鸡,大量無(wú)用消息需要落庫(kù)姜钳,且節(jié)點(diǎn)無(wú)法感知
webhook + socket
Node 開(kāi)放 webhook 接口坦冠,Jenkins 流水線在每個(gè) stage 推送消息到 Node 后臺(tái),再通過(guò) socket 推送到前端展示
優(yōu)點(diǎn):最大程度節(jié)約資源哥桥,且可以自定義有效數(shù)據(jù)跟節(jié)點(diǎn)感知辙浑,時(shí)效性最高
缺點(diǎn):需要前端、node拟糕、腳本一起配合開(kāi)發(fā)判呕,成本較高
各位同學(xué)可以在實(shí)際開(kāi)發(fā)過(guò)程中結(jié)合業(yè)務(wù)選擇成本低,收益高的方式來(lái)配合開(kāi)發(fā)
最好的方式不一定是你最優(yōu)的選擇送滞,性價(jià)比最主要
建議
從第一篇看到目前這篇博客的同學(xué)侠草,如果團(tuán)隊(duì)缺少合適的項(xiàng)目管理或者想練習(xí) node 的情況下,可以上手試試看犁嗅,一般關(guān)鍵的代碼边涕,我有直接貼在博客上(大部分復(fù)制就能用啊)褂微。
構(gòu)建篇正式開(kāi)啟功蜓,后期會(huì)逐步推出構(gòu)建篇的博文,可能更偏向運(yùn)維開(kāi)發(fā)這塊宠蚂,前端同學(xué)如果吃力的情況下式撼,可以請(qǐng)教一下后端或者運(yùn)維同學(xué)。
不搶走所有人飯碗的前端不是一個(gè)好前端求厕,手動(dòng)狗頭
全系列博文目錄
后端模塊
- DevOps - Gitlab Api使用(已完成著隆,點(diǎn)擊跳轉(zhuǎn))
- DevOps - 搭建 DevOps 基礎(chǔ)平臺(tái) 基礎(chǔ)平臺(tái)搭建上篇 | 基礎(chǔ)平臺(tái)搭建中篇 | 基礎(chǔ)平臺(tái)搭建下篇
- DevOps - Gitlab CI 流水線構(gòu)建
- DevOps - Jenkins 流水線構(gòu)建
- DevOps - Docker 使用
- DevOps - 發(fā)布任務(wù)流程設(shè)計(jì)
- DevOps - 代碼審查卡點(diǎn)
- DevOps - Node 服務(wù)質(zhì)量監(jiān)控
前端模塊
- DevOps - H5 基礎(chǔ)腳手架
- DevOps - React 項(xiàng)目開(kāi)發(fā)
后期可能會(huì)根據(jù) DevOps 項(xiàng)目的實(shí)際開(kāi)發(fā)進(jìn)度對(duì)上述系列進(jìn)行調(diào)整
尾聲
此項(xiàng)目是從零開(kāi)發(fā),后續(xù)此系列博客會(huì)根據(jù)實(shí)際開(kāi)發(fā)進(jìn)度推出(真 TMD 累)甘改,項(xiàng)目完成之后旅东,會(huì)開(kāi)放部分源碼供各位同學(xué)參考。
為什么是開(kāi)放部分源碼十艾,因?yàn)橛行I(yè)務(wù)是需要貼合實(shí)際項(xiàng)目針對(duì)性開(kāi)發(fā)的抵代,開(kāi)放出去的公共模塊我寫(xiě)的認(rèn)真點(diǎn)
為了寫(xiě)個(gè)系列博客,居然真擼完整個(gè)系統(tǒng)(不是一般的累)忘嫉,覺(jué)得不錯(cuò)的同學(xué)麻煩順手三連(點(diǎn)贊荤牍,關(guān)注,轉(zhuǎn)發(fā))庆冕。