美文网首页
前端构建 DevOps :构建篇-jenkins

前端构建 DevOps :构建篇-jenkins

作者: Cookieboty | 来源:发表于2020-11-20 16:19 被阅读0次

    前言

    基础平台搭建上篇 介绍项目流程设计、数据库搭建、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 插件管理页,将地址从 https://updates.jenkins.io/update-center.json 换成 http://mirror.xmission.com/jenkins/updates/update-center.json,可以提高下载速度。

    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 中。

    image

    直接上 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. 等等…………………………

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

    image

    由于我是 windows 系统,所以文件处理比较坑爹,运行速度慢而脚本还难写,如果你想用的话,强烈建议上 linux

    上图一共构建 5 次,由于加了缓存判断,所以第一次构建的时候,会去安装对应的依赖,耗时比较多。

    但是一般来说,长期迭代的项目,依赖变动不会太频繁,所以只需要判断是否安装过依赖即可,后续跳过依赖安装,直接走构建流程,节约倒杯水的时间。

    看自己选择,高兴每次构建全部重新安装依赖也可以。做人嘛,开心最重要

    构建产物演示

    image

    请注意上图的链接有个版本号,这个需要配合脚手架一起改造,在脚手架篇会具体介绍

    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 撸到黑的人,敬你是条汉子

    image
    // 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 获取发布时间跟日志

    image

    如上图,将 Jenkins 与项目管理系统联合起来,方便用户操作。

    构建信息推送

    前端轮询

    直接用返回的 queueId 轮询 Jenkins Api,可以直接获取信息

    优点:暴力、简单,开发速度最快,较为迅速

    缺点:用户离开页面将无法感知,数据落库会中断,且极度消耗性能,多个用户在操作同一个项目时,无法及时通知到位

    后台轮询 + socket

    Node 后台通过 queueId 直接轮询 Jenkins Api,通过 websocket 推送到前端展示

    优点:暴力,开发速度、难度适中,用户即使离开页面,数据依然能够落库,可以同时推送到多个用户

    缺点:Node 后台性能消耗增加,需要前后台一起配合开发,大量无用消息需要落库,且节点无法感知

    webhook + socket

    Node 开放 webhook 接口,Jenkins 流水线在每个 stage 推送消息到 Node 后台,再通过 socket 推送到前端展示

    优点:最大程度节约资源,且可以自定义有效数据跟节点感知,时效性最高

    缺点:需要前端、node、脚本一起配合开发,成本较高

    各位同学可以在实际开发过程中结合业务选择成本低,收益高的方式来配合开发

    最好的方式不一定是你最优的选择,性价比最主要

    建议

    从第一篇看到目前这篇博客的同学,如果团队缺少合适的项目管理或者想练习 node 的情况下,可以上手试试看,一般关键的代码,我有直接贴在博客上(大部分复制就能用啊)。

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

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

    image

    全系列博文目录

    后端模块

    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 累),项目完成之后,会开放部分源码供各位同学参考。

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

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

    相关文章

      网友评论

          本文标题:前端构建 DevOps :构建篇-jenkins

          本文链接:https://www.haomeiwen.com/subject/ebadiktx.html