前言
基础平台搭建上篇 介绍项目流程设计、数据库搭建、jwt 登录等模块
基础平台搭建中篇 介绍分支管理设计、webSocket 基础模块
基础平台搭建下篇 介绍流程管理、提测相关基础模块
基础篇主要介绍了项目管理流程的基础架构设计与一些基本的 node 开发,本篇开始构建系统系列,简单讲述一下 jenkins 与项目管理系统的配合
Jenkins
Jenkins 是什么
Jenkins 是一个开源的、提供友好操作界面的持续集成(CI)工具,起源于 Hudson(Hudson 是商用的),主要用于持续、自动的构建/测试软件项目、监控外部任务的运行。Jenkins 用 Java 语言编写,可在 Tomcat 等流行的 servlet 容器中运行,也可独立运行。通常与版本管理工具(SCM)、构建工具结合使用。常用的版本控制工具有 SVN、GIT,构建工具有 Maven、Ant、Gradle。
在此项目中,Jenkins 作为主要构建工具来搭配使用
Jenkins 安装(踩坑合集)
一般我的博客是不会介绍具体安装过程的,但是这个让我踩了一天的坑,还是列一下吧(主要是 windows 踩坑多)
Windows 环境
直接下载 Jenkins 安装包,再下载一个 JAVA SDK ,直接点击安装一把梭。
安装完之后需要密码,64 位系统的同学,记得不要去 sys32 目录找,虽然网页显示的路径是 sys32
但是 C:\Windows\SysWOW64\config\systemprofile\AppData\Local\Jenkins.jenkins 这个才是正确的路径
不建议用 windows 版本,再不济搞个虚拟机,体验效果翻倍
Mac 环境
建议用 brew 直接下载,比较容易管理
brew 安装过慢的解决方案:点击这里
插件安装速度过慢
先进入 http://ip:10086/pluginManager/advanced 插件管理页,将地址从 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 脚本
先简单介绍一下
联系:node, agent 以及 slave 都用来指被 Jenkins master 管理的用来执行 Jenkins jobs 的服务器。
区别:agents 用在表述性 pipeline 中,可以不仅仅是 nodes ,还可以是 docker container 等。node(这个不是那个 js node) 用在脚本化 pipeline 中。
直接上 node pipeline 脚本,强撸一把(简单先跑起来,后期需要优化的地方还是很多的)
配置参数:
名称 | 描述 |
---|---|
PROJECT_NAME | 工程名称 |
PROJECT_VERSION | 工程版本号 |
PROJECT_GIT_PATH | 工程地址 |
BRANCH_NAME | 工程分支 |
BUILD_PATH | 构建目录 |
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"
}
}
上述脚本创建了 5 个 stage,将构建流程拆分为 5 个步骤:
- 拉取项目:判断本地存不存在项目,存在就 pull 不存在直接 clone
- 安装项目依赖:判断项目依赖是否安装完毕,没有安装且强制清除缓存的情况下,先安装依赖
- 项目构建:运行项目构建,此处后期可以把脚本抽出来
- 项目测试:预留,后面做项目流程卡点使用
- 项目发布:直接 cp 或者 ssh 上传到你发布的地方即可(本地搭建了 Nginx 环境,所以复制到对应的目录即可)
当然上述的脚本只完成了简单的构建任务,遇到复杂的系统会直接跪,简单列举下大概率会遇到的问题
- 多端构建,比如一个项目需要直接构建多端产物
- 区分开发、测试、预发、线上等多环境
- 多命令构建,复杂项目可能需要执行多条命令,才能完成构建产物
- 构建产出目录,发布目录等不确定性
- 等等…………………………
可以根据参数传递,多脚本等等配合解决上述问题,具体要根据业务来设计
由于我是 windows 系统,所以文件处理比较坑爹,运行速度慢而脚本还难写,如果你想用的话,强烈建议上 linux。
上图一共构建 5 次,由于加了缓存判断,所以第一次构建的时候,会去安装对应的依赖,耗时比较多。
但是一般来说,长期迭代的项目,依赖变动不会太频繁,所以只需要判断是否安装过依赖即可,后续跳过依赖安装,直接走构建流程,节约倒杯水的时间。
看自己选择,高兴每次构建全部重新安装依赖也可以。做人嘛,开心最重要!
构建产物演示
请注意上图的链接有个版本号,这个需要配合脚手架一起改造,在脚手架篇会具体介绍
Nginx
Nginx 是一款轻量级的 Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个 BSD-like 协议下发行,可以在 UNIX、GNU/Linux、BSD、Mac OS X、Solaris,以及 Microsoft Windows 等操作系统中运行。
在实际的运作中,可以支持二万至四万并发,性价比极高。没钱就自己搭建,有钱直接上 cos, cdn,买云服务它不香吗? PS:有钱真好!
server { #这里是我自己配置服务端口
listen 10010;
server_name resouce;
root D:/jenkins/deploy; #访问文件根目录
autoindex on; #是否浏览文件下的列表
location / { #是否允许跨域
add_header Access-Control-Allow-Origin *;
}
add_header Cache-Control "no-cache,must-revalidate";# 是否缓存
}
根据上述配置,可以简单的配置一个静态服务器。把前端项目丢进去,直接访问对应的端口即可。
上述代码,直接 copy 到 nginx.config 里面,然后重启即可
DevOps Jenkins Coding
封装基础 Jenkins Api
项目选择 jenkins 库来拓展,注意如果你使用 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: 触发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: 获取当前节点信息
*/
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: 获取当前构建信息
*/
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,
};
上述是对 Jenkins 的基本封装,简单的封装了一些我们需要用到的方法,具体的定制化,可以结合业务自己设计。
各端的业务构建,可以选择多个 Jenkins 项目或者不同的 job 区分,不建议一个 job 直接撸到黑,这样脚本会很复杂。
真的一个 job 撸到黑的人,敬你是条汉子
// Controller
import { Post, Prefix, Get } from "egg-shell-decorators";
import BaseController from "./base";
@Prefix("build")
export default class BuildController extends BaseController {
/**
* @description: 创建构建任务
*/
@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: 构建项目
*/
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;
}
}
上述是业务代码,一般获取返回值的时候,存一下记录 queueId, 通过调用 Jenkins api 获取发布时间跟日志
如上图,将 Jenkins 与项目管理系统联合起来,方便用户操作。
构建信息推送
前端轮询
直接用返回的 queueId 轮询 Jenkins Api,可以直接获取信息
优点:暴力、简单,开发速度最快,较为迅速
缺点:用户离开页面将无法感知,数据落库会中断,且极度消耗性能,多个用户在操作同一个项目时,无法及时通知到位
后台轮询 + socket
Node 后台通过 queueId 直接轮询 Jenkins Api,通过 websocket 推送到前端展示
优点:暴力,开发速度、难度适中,用户即使离开页面,数据依然能够落库,可以同时推送到多个用户
缺点:Node 后台性能消耗增加,需要前后台一起配合开发,大量无用消息需要落库,且节点无法感知
webhook + socket
Node 开放 webhook 接口,Jenkins 流水线在每个 stage 推送消息到 Node 后台,再通过 socket 推送到前端展示
优点:最大程度节约资源,且可以自定义有效数据跟节点感知,时效性最高
缺点:需要前端、node、脚本一起配合开发,成本较高
各位同学可以在实际开发过程中结合业务选择成本低,收益高的方式来配合开发
最好的方式不一定是你最优的选择,性价比最主要
建议
从第一篇看到目前这篇博客的同学,如果团队缺少合适的项目管理或者想练习 node 的情况下,可以上手试试看,一般关键的代码,我有直接贴在博客上(大部分复制就能用啊)。
构建篇正式开启,后期会逐步推出构建篇的博文,可能更偏向运维开发这块,前端同学如果吃力的情况下,可以请教一下后端或者运维同学。
不抢走所有人饭碗的前端不是一个好前端,手动狗头
全系列博文目录
后端模块
- DevOps - Gitlab Api使用(已完成,点击跳转)
- DevOps - 搭建 DevOps 基础平台 基础平台搭建上篇 | 基础平台搭建中篇 | 基础平台搭建下篇
- DevOps - Gitlab CI 流水线构建
- DevOps - Jenkins 流水线构建
- DevOps - Docker 使用
- DevOps - 发布任务流程设计
- DevOps - 代码审查卡点
- DevOps - Node 服务质量监控
前端模块
- DevOps - H5 基础脚手架
- DevOps - React 项目开发
后期可能会根据 DevOps 项目的实际开发进度对上述系列进行调整
尾声
此项目是从零开发,后续此系列博客会根据实际开发进度推出(真 TMD 累),项目完成之后,会开放部分源码供各位同学参考。
为什么是开放部分源码,因为有些业务是需要贴合实际项目针对性开发的,开放出去的公共模块我写的认真点
为了写个系列博客,居然真撸完整个系统(不是一般的累),觉得不错的同学麻烦顺手三连(点赞,关注,转发)。