从 0 到 1 node 项目管理系统:构建篇-jenkins

7,176 阅读10分钟

前言

基础平台搭建上篇 介绍项目流程设计、数据库搭建、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 安装过慢的解决方案:点击这里

插件安装速度过慢

  1. 先进入 http://ip:10086/pluginManager/advanced 插件管理页,将地址从 updates.jenkins.io/update-cent… 换成 mirror.xmission.com/jenkins/upd…

  2. 修改 Jenkins/updates/default.json 配置

替换 updates.jenkins-ci.org/download 为 mirrors.tuna.tsinghua.edu.cn/jenkins

替换 www.google.comwww.baidu.com

完了直接 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 个步骤:

  1. 拉取项目:判断本地存不存在项目,存在就 pull 不存在直接 clone
  2. 安装项目依赖:判断项目依赖是否安装完毕,没有安装且强制清除缓存的情况下,先安装依赖
  3. 项目构建:运行项目构建,此处后期可以把脚本抽出来
  4. 项目测试:预留,后面做项目流程卡点使用
  5. 项目发布:直接 cp 或者 ssh 上传到你发布的地方即可(本地搭建了 Nginx 环境,所以复制到对应的目录即可)

当然上述的脚本只完成了简单的构建任务,遇到复杂的系统会直接跪,简单列举下大概率会遇到的问题

  1. 多端构建,比如一个项目需要直接构建多端产物
  2. 区分开发、测试、预发、线上等多环境
  3. 多命令构建,复杂项目可能需要执行多条命令,才能完成构建产物
  4. 构建产出目录,发布目录等不确定性
  5. 等等…………………………

可以根据参数传递,多脚本等等配合解决上述问题,具体要根据业务来设计

由于我是 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 的情况下,可以上手试试看,一般关键的代码,我有直接贴在博客上(大部分复制就能用啊)。

构建篇正式开启,后期会逐步推出构建篇的博文,可能更偏向运维开发这块,前端同学如果吃力的情况下,可以请教一下后端或者运维同学。

不抢走所有人饭碗的前端不是一个好前端,手动狗头

全系列博文目录

后端模块

  1. DevOps - Gitlab Api使用(已完成,点击跳转)
  2. DevOps - 搭建 DevOps 基础平台 基础平台搭建上篇 | 基础平台搭建中篇 | 基础平台搭建下篇
  3. DevOps - Gitlab CI 流水线构建
  4. DevOps - Jenkins 流水线构建
  5. DevOps - Docker 使用
  6. DevOps - 发布任务流程设计
  7. DevOps - 代码审查卡点
  8. DevOps - Node 服务质量监控

前端模块

  1. DevOps - H5 基础脚手架
  2. DevOps - React 项目开发

后期可能会根据 DevOps 项目的实际开发进度对上述系列进行调整

尾声

此项目是从零开发,后续此系列博客会根据实际开发进度推出(真 TMD 累),项目完成之后,会开放部分源码供各位同学参考。

为什么是开放部分源码,因为有些业务是需要贴合实际项目针对性开发的,开放出去的公共模块我写的认真点

为了写个系列博客,居然真撸完整个系统(不是一般的累),觉得不错的同学麻烦顺手三连(点赞,关注,转发)。

如对文章内容有任何疑问请留言