阅读 269

我想为公司做一套Devops(UI库,Gitlab,版本管理,Jenkins,脚手架)

Devops : 是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

当进入一个新团队,前端从 0 开始,怎样从DevOps的角度去提高团队效能呢?或者说,如何更好的完善公司的基础建设?

一套简易的DevOps流程包含了协作、构建、测试、部署、运行。而前端常说的开发规范、代码管理、测试、构建部署以及工程化其实都是在这一整个体系中。

简单的来说,文章主要讲述的是基础建设。

基础建设十分重要,它会大大提升我们的工作效率以及项目维护。比如接口的调试,测试环境,生产环境的发布,版本的迭代,线上项目的监控与报警等等,这些都是我们基础建设要解决的问题。

DevOps核心思想就是:“快速交付价值,灵活响应变化”。其基本原则如下:

  • 高效的协作和沟通;

  • 自动化流程和工具;

  • 快速敏捷的开发;

  • 持续交付和部署;

  • 不断学习和创新。

DevOps

列出来了一套出来的比较完整的基建流程,每一项我都会完整的讲述如何实现以及我的想法。列举出了很多,但是基本不会都用到,一般公司只会用到其中的几项。就像我写的前端性能优化一样,我会列出很多,我们需要根据公司以及项目来做出最适合的方案,内容较多,第一部分主要讲述前五个。

  • 基础UI组件库

  • 自建Gitlab

  • 版本管理

  • 自动编译发布Jenkins

  • 统一脚手架

  • Node中间层

  • 埋点系统

  • 监控和报警系统

  • 安全管理

  • Eslint

  • Mock

  • 灰度发布

  • oidc单点登录

  • Swagger接口自动生成

  • 文档平台建设

1.基础UI组件库

假如公司里有很多项目,项目有一些组件可以复用,那为什么不将这些组件拿出来做一个公共组件库呢?这样是不是方便了很多。

我曾经搭建过React组件库,具体技术栈是dumi + lerna + father,dumi用来开发组件和写文档,lerna负责解决多包依赖管理,而father则用来构建整个系统。

我写了一个简单的Demo在我的GitHub上:Dark-YiFeng

简单来说就是搭建一个项目,里面写上我们的公共组件,然后其他项目来调用,主要可以参考我的

我们写完了UI组件看,该怎么调用呢?

首先,我们要把组件库传到公司的私有npm库上,私有npm库如何搭建?我们这边采用的是Sinopia方法。

1.1 服务端部署

安装

前置工作:配置nodejs及npm环境

npm install -g sinopia
复制代码

启动

sinopia
warn  --- config file - /home/map/.config/sinopia/config.yaml
warn  --- http address - http://localhost:4873/
复制代码

此时访问localhost:4873,可获取html文件并且服务端响应正常,表示安装成功。

$ curl localhost:4873
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<title>Sinopia</title>
<link rel="icon" type="image/png" href="http://localhost:4873/-/static/favicon.png"/>
...
复制代码

服务端响应

$ sinopia
...
http  <-- 200, user: undefined, req: 'GET /', bytes: 0/10896
复制代码

配置

运行sinopia,自动生成的工作目录如下(通过第一个warn可以看到具体路径):

$ tree /home/map/.config/sinopia/
/home/map/.config/sinopia/
|-- config.yaml //存放所有配置信息
|-- htpasswd        //存放所有账户信息
`-- storage         //存放私有npm包及缓存公有包
|-- npm_test
|   |-- npm_test-1.0.0.tgz
|   |-- npm_test-1.0.1.tgz
|   `-- package.json
`-- sinopia
       `-- package.json

3 directories, 6 files 
复制代码

config.yaml默认配置

存放路径在你运行的时候有显示:

# This is the default config file. It allows all users to do anything,
# so don't use it on production systems.
#
# Look here for more config file examples:
# https://github.com/rlidwka/sinopia/tree/master/conf

# path to a directory with all packages
storage: ./storage      //npm包存放的路径

auth:
 htpasswd:
file: ./htpasswd    //保存用户的账号密码等信息
# Maximum amount of users allowed to register, defaults to "+inf".
# You can set this to -1 to disable registration.
#max_users: 1000 //默认为1000,改为-1,禁止注册

# a list of other known repositories we can talk to
uplinks:
 npmjs:
url: https://registry.npmjs.org/    
//拉取公共包的地址源,默认为npm的官网,可以使用淘宝的npm镜像地址

packages: //配置权限管理
'@*/*':
# scoped packages
   access: $all
publish: $authenticated
'*':

# allow all users (including non-authenticated users) to read and
# publish all packages
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
   access: $all

# allow all known users to publish packages
# (anyone can register by default, remember?)
publish: $authenticated

# if package is not available locally, proxy requests to 'npmjs' registry
proxy: npmjs

# log settings
logs:
- {type: stdout, format: pretty, level: http}
#- {type: file, path: sinopia.log, level: info}
复制代码

外网访问配置

通过在config.yaml中修改服务默认的监听端口,从而可以通过外网访问 sinopia 仓库。

listen: 0.0.0.0:4873
复制代码

外网通过http://[IP | 域名]:[端口]的形式来访问。

浏览器外网访问如图:

账号配置

config.yaml 中auth部分对应账号的管理,默认可以通过客户端npm adduser添加账号。可以通过max_users:-1禁止客户端创建,而通过我们修改htpasswd文件来管理用户。

htpasswd文件示例:

lisi:{SHA}????????????????=:autocreated 2016-02-05T15:39:19.960Z
wangwu:{SHA}????????????????=:autocreated 2016-02-05T17:59:05.041Z
复制代码

密码是被加密过的,是简单的SHA1哈稀之后再转换成 Base64 。

1.2 客户端配置

配置npm registry

建议客户端使用nrm 进行npm registry地址管理和切换

安装

npm install -g nrm
复制代码

添加sinopia仓库地址

nrm add sinopia http://192.168.xx.xx:4873
复制代码

切换私有仓库

nrm use sinopia
复制代码

查看所有仓库地址(星标为当前仓库源)

nrm ls
npm ---- https://registry.npmjs.org/
cnpm --- http://r.cnpmjs.org/
taobao - https://registry.npm.taobao.org/
nj ----- https://registry.nodejitsu.com/
rednpm - http://registry.mirror.cqupt.edu.cn/
npmMirror https://skimdb.npmjs.com/registry/
edunpm - http://registry.enpmjs.org/
* sinopia http://192.168.xx.xx:4873/
复制代码

1.3 发包

切换到私有仓库之后,发包的操作跟npm发包基本无差别。
登录账号之后:

npm publish+ npm-web@1.0.1
复制代码

ps: 版本号重复的情况再次发布的包不会主动更新,并且发布不会有错误提示,更新包务必更新版本号。

发布成功后私有仓库站点会显示包情况,README.md文件会作为详情描述展开。

图5 包发布成功示例图

发包然后,我们该如何调用。我们需要在package.json里面引入并下载相关依赖。

然后在项目里引用就可以了。

这样,我们的UI组件库就搭建完成。

2.自建Gitlab

稍具规模一点的公司都会搭建属于自己的git,svn,而内部git用的最多的则是gitlab,虽然官网已经提供了非常多的功能,但内网搭建更能保证项目的私有性,只有公司内部员工才可以访问,更加安全。

2.1 安装一些依赖软件包,SSH一般系统是默认安装好的,不过也不排除一些最小安装的系统没有sshd服务。

sudo yum install -y curl policycoreutils-python openssh-server
sudo systemctl enable sshd
sudo systemctl start sshd
复制代码

2.2关闭防火墙,或者开放HTTP的端口。

//刷新防火墙的规则
iptables -F
复制代码

2.3安装邮件服务,当gitlab想要通过邮件通知,也可以另外配置其它的邮件服务器。

sudo yum install postfix
sudo systemctl enable postfix
sudo systemctl start postfix
复制代码

2.4从官网获取一件安装脚本,当然自己手动安装也是可以的gitlab下载地址,使用官网脚本会简单一些。执行这一步会如果使用CentOS系统,会添加gitlab的yum源。

//输出到文件里是为了看下下载的脚本内容
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh > rpm.sh
chmod +x rpm.sh
./rpm.sh
复制代码

2.5安装gitlab。

//使用yum安装gitlab
yum install -y gitlab-ee
//可以看下gitlab-ee包的内容,看到gitlab安装在/opt/gitlab目录下
rpm -ql gitlab-ee | less
复制代码

2.6上面已经安装好了gitlab,不过可以稍作一些配置,配置gitlab监听的地址与端口,gitlab的配置文件在/etc/gitlab/目录下,主要配置文件为gitlab.rb我修改了下gitlab.rb文件中的nginx监听地址。

external_url 'http://gitlab.ai-he.me'
nginx['listen_addresses'] = ['0.0.0.0', '[::]']
# 系统端口冲突,我把端口改为了82
nginx['listen_port'] = 82
复制代码

里面的配置项非常的多,可以对照官网文档根据需要修改。gitlab配置选项

2.7运行gitlab命名,并重启。

//重新配置gitlab
sudo gitlab-ctl reconfigure
//重启gitlab
gitlab-ctl restart 
// 查看gitlab-ctl命令的帮助信息


gitlab-ctl --help助信息gitlab-ctl --help
复制代码

2.8打开浏览器查看效果,第一次打开页面会让我们设置root用户的密码。记住自己设置的密码,再次刷新进入登录页面。

2.9以管理员身份登录,默认的用户是root,密码是刚才设置的。

3.版本管理

我们通常使用SourceTree来进行版本管理, 是 Windows 和Mac OS X 下免费的 Git 和 Hg 客户端,拥有可视化界面,容易上手操作。同时它也是Mercurial和Subversion版本控制系统工具。支持创建、提交、clone、push、pull 和merge等操作。

3.1 下载

下载地址:www.sourcetreeapp.com/

3.2 拉取代码

sourcetree是免费的Git客户端,如何利用它从gitlab上拉取下代码呢?步骤如下:

(1)、下载并安装git
(2)、运行git,生成秘钥

ssh-keygen -t rsa
复制代码

秘钥生成的目录在系统盘用户目录下的\.ssh\id_rsa.pub。

3.3 绑定git公钥

绑定操作:Settings --> SSH Keys --> Add key(打开本地公钥文件粘贴里面所有内容)

3.4 拉取代码

点击"工具-->选项-->一般",注意以下4个部分的设置,如图:

点击确定按钮之后,点击"文件-->克隆/新建",打开克隆tab,如图:

源路径:为要拉取项目的git路径;目标路径:为自己要存放该项目的本地资源路径

名字:为项目名字,一般会自动获取填充

点击克隆按钮,项目开始拉取到本地。等待项目拉取完毕后,我们就可以开始自己的本地开发。

3.5 上传

项目克隆完成之后,我们拉取的是master分支上的代码,由于master分支是主分支,项目多人开发的情况下,很容易造成冲突。
所以我们一般会在gitlab远程新建一个自己的分支,如命名为:dev。

1.新建自己的远程开发分支并在sourcetree中切换到该分支

双击origin--->dev,就可以切换到dev开发分支。
2.切换到文件状态,暂存需要提交的代码,写好描述,点击“拉取”选项(相当svn的update操作)更新代码并点击提交;
3.点击“推送”选项,选择正确的本地和远程分支,确定推送(相当于svn的commit操作)。
4.等待项目管理员将dev分支代码合并到master分支,完成从开发到上传。

3.6 扩展

这里提到Git,不知道大家是否遇到过npm版本与依赖不兼容的问题,我推荐几个工具,nvm,nrm。

nvm是node版本管理工具,为了解决node各种版本存在不兼容现象。

这是我npm的常用版本:

nrm是npm的镜像源管理工具,有时候国外资源太慢,使用这个就可以快速地在 npm 源间切换。

看一下我的nrm:

可以看出来taobao源目前速度是最快的。

nrm use taobao
复制代码

这时候切换到taobao源,就可以更快的进行下载。

4.自动编译发布Jenkins

什么是自动编译发布?

通过搭建gitlab服务器从而在gitlab上管理项目代码,在gitlab上分别建立master,release/test分支,每次开发前需要从master分支上拉取新的开发分支feature,开发完后提交开发分支并将其合并到测试分支release/test上,合并操作通过webhook触发jenkins上已写好的webpack编译脚本,从而达到自动编译并部署到测试环境。测试完成后在发布到线上环境时,我们先从master分支上拉取新的发布分支release/prod,将开发分支feature合并到发布分支release/prod上,合并操作同发布测试一样会触发自动编译与部署。

听起来有点复杂,我来说一下我的理解。我们可以分为三点: 自动,编译,发布。也就是说自动的把我们的代码文件进行编译以及发布到环境中。自动编译发布方法有很多,本次我们选用的是Jenkins。

Jenkins是一个开源的、提供友好操作界面的持续集成(CI)工具 ,主要用于持续、自动的构建/测试软件项目、监控外部任务的运行。Jenkins用Java语言编写,可在Tomcat等流行的servlet容器中运行,也可独立运行。通常与版本管理工具(SCM)、构建工具结合使用。常用的版本控制工具有SVN、GIT,构建工具有Maven、Ant、Gradle。

4.1 安装 Nginx

可以直接去官网下直接下载,建议下载1.18.0的,最新版本1.19.1使用出现了一些问题,解压缩 start nginx就可以使了,常用命令:

start nginx # 启动

nginx -s reload # 修改配置后重新加载生效

nginx -s reopen # 重新打开日志文件

nginx -t # 配置文件检测是否正确
复制代码

4.2安装Jenkins

首先,我们需要安装JDK环境,JDK版本要求【52,55】。

如果下载JDK,oracle要求登录。

账号:2696671285@qq.com
密码:Oracle123
复制代码

Jenkins我们可以直接到官网上安装:www.jenkins.io/download/#d…

Jenkins尽量安装在C盘,或者说和JDK安装在一起,因为它会自动安装在JDK文件夹中。

我安装的是2.268版本。

安装成功后会自动打开http://localhost:8080/页面,如果打不开可能是8080端口号被占用,可以到jenkins文件夹下打开cmd:

java -jar jenkins.war --ajp13Port=-1 --httpPort=8081
复制代码

解锁Jenkins,windows下进入需要管理员权限,可以手动进入文件夹,复制密码。解锁成功后进入了下载插件页面,这里我们直接安装推荐的插件就可以了,安装插件过程的时间可能略长。

安装后就是注册页面了,注册成功后就可以愉快的使用Jenkins了。

先新建一个任务。

配置这一块,我选的是github项目和Poll SCM,这里的Poll SCM表示去检测是否更新构建的频率,* * * * *表示每分钟,H * * * *表示每小时,记得每个*之间要有空格。

当我进入项目中,执行 npm run build的时候,Jenkins就会有显示。

5. 统一脚手架

使用自己搭建的脚手架可能不是经常能遇见,但是也并不是没有。比如中国电信的e框架,华为云内部也有基于angular搭建的脚手架。自己搭建的脚手架很难比Vue,React要好,但是,自己搭建的脚手架更适合自己的项目。

我们先做一个最简单的脚手架。

Github地址: github.com/Dark-YiFeng…

5.1 目录结构

5.2 入口文件

首先建立项目,在package.json里面写入依赖并执行npm install

"dependencies": {    
    "chalk": "^1.1.3",    
    "co": "^4.6.0",    
    "co-prompt": "^1.0.0",    
    "commander": "^2.9.0"  
},
复制代码

在根目录下建立\bin文件夹,在里面建立一个无后缀名的miss文件。这个bin\miss文件是整个脚手架的入口文件,所以我们首先对它进行编写。

#!/usr/bin/env node --harmony'use strict' // 定义脚手架的文件路径process.env.NODE_PATH = __dirname + '/../node_modules/'const program = require('commander') // 定义当前版本program    .version(require('../package').version )// 定义使用方法program    .usage('<command>')program    .command('add')    .description('Add a new template')  .alias('a')  .action(() => {    require('../command/add')()  })program    .command('list')    .description('List all the templates')    .alias('l')    .action(() => {        require('../command/list')()    })program    .command('init')    .description('Generate a new project')  .alias('i')  .action(() => {    require('../command/init')()  })program    .command('delete')    .description('Delete a template')    .alias('d')    .action(() => {        require('../command/delete')()    })program.parse(process.argv)if(!program.args.length){  program.help()}
复制代码

5.3 处理用户输入

在项目根目录下建立\command文件夹,专门用来存放命令处理文件。

在根目录下建立templates.json文件并写入如下内容,用来存放模版信息:

{"tpl":{}}
复制代码

5.4 添加模板

进入\command并新建add.js文件:

"use strict";const co = require("co");const prompt = require("co-prompt");const config = require("../templates");const chalk = require("chalk");const fs = require("fs");module.exports = () => {  co(function* () {    // 分步接收用户输入的参数    let tplName = yield prompt("Template name: ");    let gitUrl = yield prompt("Git https link: ");    let branch = yield prompt("Branch: ");    // 避免重复添加    if (!config.tpl[tplName]) {      config.tpl[tplName] = {};      config.tpl[tplName]["url"] = gitUrl.replace(/[\u0000-\u0019]/g, ""); // 过滤unicode字符      config.tpl[tplName]["branch"] = branch;    } else {      console.log(chalk.red("Template has already existed!"));      process.exit();    }    // 把模板信息写入templates.json    fs.writeFile(      __dirname + "/../templates.json",      JSON.stringify(config),      "utf-8",      (err) => {        if (err) console.log(err);        console.log(chalk.green("New template added!\n"));        console.log(chalk.grey("The last template list is: \n"));        console.log(config);        console.log("\n");        process.exit();      }    );  });};
复制代码

5.5 删除模板

同样的,在\command文件夹下建立delete.js文件:

"use strict";const co = require("co");const prompt = require("co-prompt");const config = require("../templates");const chalk = require("chalk");const fs = require("fs");module.exports = () => {  co(function* () {    // 接收用户输入的参数    let tplName = yield prompt("Template name: ");    // 删除对应的模板    if (config.tpl[tplName]) {      config.tpl[tplName] = undefined;    } else {      console.log(chalk.red("Template does not exist!"));      process.exit();    }    // 写入template.json    fs.writeFile(      __dirname + "/../templates.json",      JSON.stringify(config),      "utf-8",      (err) => {        if (err) console.log(err);        console.log(chalk.green("Template deleted!"));        console.log(chalk.grey("The last template list is: \n"));        console.log(config);        console.log("\n");        process.exit();      }    );  });};
复制代码

5.6 罗列模板

建立list.js文件:

"use strict";const config = require("../templates");module.exports = () => {  console.log(config.tpl);  process.exit();};
复制代码

5.7 构建项目

现在来到我们最重要的部分——构建项目。同样的,在\command目录下新建一个叫做init.js的文件:

"use strict";const exec = require("child_process").exec;const co = require("co");const prompt = require("co-prompt");const config = require("../templates");const chalk = require("chalk");module.exports = () => {  co(function* () {    // 处理用户输入    let tplName = yield prompt("Template name: ");    let projectName = yield prompt("Project name: ");    let gitUrl;    let branch;    if (!config.tpl[tplName]) {      console.log(chalk.red("\n × Template does not exit!"));      process.exit();    }    gitUrl = config.tpl[tplName].url;    branch = config.tpl[tplName].branch;    // git命令,远程拉取项目并自定义项目名    let cmdStr = `git clone ${gitUrl} ${projectName} && cd ${projectName} && git checkout ${branch}`;    console.log(chalk.white("\n Start generating..."));    exec(cmdStr, (error, stdout, stderr) => {      if (error) {        console.log(error);        process.exit();      }      console.log(chalk.green("\n √ Generation completed!"));      console.log(`\n cd ${projectName} && npm install \n`);      process.exit();    });  });};
复制代码

可以看到,这一部分代码也非常简单,关键的一句话是

let cmdStr = `git clone ${gitUrl} ${projectName} && cd ${projectName} && git checkout ${branch}`
复制代码

它的作用正是从远程仓库克隆到自定义目录,并切换到对应的分支。熟悉git命令的同学应该明白,不熟悉的同学是时候补补课啦!

5.8 全局使用

为了可以全局使用,我们需要在package.json里面设置一下:

  "bin": {    "miss-cli": "bin/miss"  },
复制代码

本地调试的时候,在根目录下执行

npm link
复制代码

即可把miss-cli命令绑定到全局,以后就可以直接以miss-cli作为命令开头而无需敲入长长的node miss-cli之类的命令了。

文章分类
前端
文章标签