小团队的前端工程化体系服务搭建
这个如火如荼的大前端时代,vue、react、angular 、flutter、electron、小程序等技术框架百家争鸣,这些都是我们前端拿来干架吃饭的武器,前端工程化体系也逐渐向小公司小团队普及,做为一个小小前端团队的leader,了解如何去搭建一个基础的前端工程化体系服务环境,来帮助小团队的提高开发体验和提升工作效率,还是有必要的。
前端工程化体系工作服务环境主要有:CI / CD、npm私有库、api mocker server 等
小团队工程化服务环境
来,老板我们先来看一下我们要完成的这一套工程化服务的简介图
简介图
东西不多其实就几个
本地开发:编写 eslint 、stylint 和 prettier 规则文件,配合vscode的保存自动格式化,再严格的话加上git hook 来检测(当然如果是项目中途加入规则的话,不建议采用git hook等全代码的格式化功能,团队开发可能会宕机😂)
当我们对特定的分支做 push,或者在仓库执行分支合并更新后,就会触发我们的 CI / CD 服务:去自动构建我们的代码(当然可以加上一些单元测试、代码质量检测等),构建成功后自动帮我们部署到对应的开发环境、测试环境,预发布环境,部署线上生产环境的话一般是设置手动更新等,当然还需要快速回滚的功能,CI / CD 服务运行的状态最后都要做通知,通过:邮件、钉钉企业微信等,告诉团队或者相关开发人员构建的结果等
知识点大图
CI / CD 流水线
CI 代表持续集成(Continuous Integration),CD 代表持续交付(Continuous Delivery)和持续部署(Continuous Deployment)。也可以将它们看作是类似于软件开发生命周期的过程
简单的说,就是在本地更改代码—>git push推送到代码托管—>自动安装依赖,打包测试部署等(开发环境一般自动,生产环境一般是需要手动点击按钮上线)
说多无益看图
CI / CD 流水线工作图
老实说:test这个阶段暂时是没有做的哈哈哈,不过做小组件库的时候会需要单元测试等,大家知道就好
自动化流程的必要性:避免很多问题,少解决很多冲突,假设你的前端团队有6个人,每个人电脑的系统、node版本,npm版本都有可能不一样,就算是规定统一了,每个人拉取代码源仓库 build 出来的产物也有可能不一样,这样会产生几个问题
- 耗时:每个人都需要去构建
- 易冲突:当git做pull ,merge,push等的时候一不小心就会产生各种不必要的冲突要去解决,非常的耗时和不愉悦
- 缓存失效:构建出来的静态文件的hash不好控制,容易在客户端失去缓存,即使在webpack中设置了contenthash等等优化
例如:一个很大的commit.js,base.js,echat.js vendor.js啊等文件,本来是没有更新的,已经在客户端缓存的了,却在这次更新后hash值莫名奇妙的变化了,再次访问页面后客户端的用户还需要重新去加载它们,浪费资源啊老板
其实这些问题在大公司,人员完善的团队一般不会遇到,因为会有专人负责这方的搭建,但是你知道的嘛,初始的小团队,那种苦只有体验过的才知道 🤣,但是如果你会了,又有点话语权,那这问题就可以迎刃而解了😎
打造自动化构建部署 CI /CD 流水线服务的工具我们这里用的是Jenkins
Jenkins
<div align=center><img src="https://fn-md-1255398640.cos.ap-guangzhou.myqcloud.com/prj/icon-jenkins.png" alt="jenkins" style="width:60px;" /></div>
ci / cd 现在有不少服务,github的GitHub Actions,gitlab 的Gitlab-CI,gitee的Gitee Go,还有travis、Netlify等等,我们用Jenkins,引用官方的话就是:
Jenkins是开源CI&CD软件领导者, 提供超过1000个插件来支持构建、部署、自动化, 满足任何项目的需要。
为了方便部署和迁移等,我们要在docker上安装Jenkins
Docker
<div align=center><img src="https://fn-md-1255398640.cos.ap-guangzhou.myqcloud.com/prj/icon-docker.png" alt="docker" style="width:60px;" /></div>
Docker 是一个开源,轻量级的应用容器引擎,以前我们想在window折腾linux系统,在上面部署应用来测试什么的,我们通常会安装一个虚拟机,在虚拟机上安装操作系统啊,应用什么的,搞垮了可以重新来什么的,不影响主机的系统,不过虚拟机比较重,启动慢,现在我们用docker,相比于虚拟机装操作系统,Docker是使用容器承载应用程序,轻量,高效,方便快捷的部署
Docker 由镜像(Image)、容器(Container)、仓库(Repository) 三部分组成。
镜像(Image)
镜像Image就是相当于安装操作的系统盘,U盘等等,里面可以包含node、gitlab等等,当然也可以包含完整的centos系统,甚至是centos + jenkins的或者centos + verdaccio(npm 私有库)
容器(Container)
容器(Container)就是实际上跑应用的地方啦,可以理解为一个个互相隔离的小虚拟机,你的镜像就是安装在这里
仓库(Repository)
仓库(Repository) 用于存放镜像,有点类似的git仓库,docker hub是个公共的仓库,不过在我们这边网速慢,一般把源设置为淘宝源等
后面我们会用几个docker容器分别安装jenkins、verdaccio、yapi等组成一个单机的小小微服务,现在多多少少知道为什么要用docker了吧
Verdaccio
<div align=center><img src="https://fn-md-1255398640.cos.ap-guangzhou.myqcloud.com/prj/icon-verdaccio.png" alt="verdaccio" style="width:60px;" />
Verdaccio 是一个 Node.js创建的轻量的私有npm proxy registry ,小团队用这个够
Yapi
<div align=center><img src="https://fn-md-1255398640.cos.ap-guangzhou.myqcloud.com/prj/icon-YAPI.png" alt="yapi" style="width:60px;" /></div>
Yapi这是一个api管理平台,前后端分离的时候,api mock显得格外重要,没有api、mock,团队的朋友得在代码中做一堆测试数据,判断条件什么的,后面等接口开发好再删掉测试的数据,在对接接口重新测试,这可一点都不好玩。现在主要的mock系统有rap2、eolinker,yapi等等,我们这里用yapi,轻量简单功能好,社区活跃star高,需要mongoDB来存数据
MongoDB
<div align=center><img src="https://fn-md-1255398640.cos.ap-guangzhou.myqcloud.com/prj/icon-mongoDB.png" alt="mongo" style="width:120px;" /></div>
MongoDB使用C++语言编写的非关系型数据库。特点是高性能、易部署、易使用,存储数据十分方便。这里我们安装是提供给yapi用
Git flow 工作流
简单的先说一下我们小团队这里的 git flow,这样好明白我们接下来的 CI / CD 要为我们做什么
代码运行的环境
一般来说,小公司小团队都会有至少这几个环境:
- 本地开发环境:
开发人员自测的,可以是自己本地部署的静态服务器,当然也可类似是运行 npm server类似的环境,本地环境运行的代码可以是任何分支的
- dev开发环境:
这个环境是用开发分支dev产出的代码来部署的,唯一的公用的
- 测试&预发布环境:
这个环境是用开发分支release产出的代码来部署的,唯一的公用的
- 线上生产环境:
这个环境是用开发分支master产出的代码来部署的,唯一的公用的
git 分支模型
先看一下分支的角色功能
分支的策略
dev 是公共的开发分支,不会合并到其他分支去的,7
所有的分支都是基于master分支检出
- master :保护分支,对应的就是生产环境的分支
- release:保护分支,所有开发完成的分支会申请合并到release分支,提供给测试人员测试
- feature-*:功能分支,具体功能开发
- dev:开发分支&脏分支,对应的是大家共用的开发环境,上面的代码会部署到一个公共的开发环境,供开发人员做自测,应付一些日常、非日常的调试
- hotfix-*:bug紧急修复分支,可以直接合并到master,(假如release合并了几个feature分支,正在测试的情况下,发现需要紧急修复的buf,紧急修复测试完毕后,可以直接合并到master,如果合并到release,在由release合并到master,那些正在测试的功能或者还不准备上线的功能就会跟着直接上线了)
工作流程大概是:
- 接到需求文档,做评审后分配个每个人或小组的功能开发,相关人员从master 检出功能分支
- 开发的时候除了会在本地测试,有需要还会合并到dev分支,在公共的开发环境去自己做测试
- 因为在开发功能的期间,可能有hotfix完成合并到master,合并代码的时候习惯merge一下master,防止冲突等
- 自测完成之后,申请合并到release,合并成功后部署到测试环境后通知测试人员做测试
- 测试通过后,release申请合并到master,准备上线
- 如果测试不通过,在功能分支修改后重新merge
- 上线成功稳定后删除对应的功能分支,dev 合并最新的master分支
CI / CD 的工作
当我们本地代码分支合并到dev,push推送到远程git仓库的时候,触发任务自动build->build成功后自动部署发布到dev开发环境
当我们在gitee 申请feature分支,合并到release,成功后,触发任务自动build->build成功后自动部署发布到release测试环境
当我们申请release分支或&hotfix分支合并到master,成功后,触发任务自动build->build成功后通过手动部署发布到正式生产环境(生产环境一般是需要多加一步手动发布,点击个按钮发布等操作)
创建仓库
上面的环境清楚了,先按照上面的新建一个仓库吧
你也可以直接拉去我的这里为你准备的初始化仓库 https://gitee.com/eric-gm/ci-...,然后放到你自己仓库里
安装准备
系统要求
liunx系统,我用的是:centos8, 因为要安装docker,要求内核版本不低于 3.10
这里我是用两台阿里云服务器,一台用来部署jenkins、verdaccio、yapi等做环境部署机,一台做静态资源的服务器装nginx,环境部署机建议2核4G起步,如果有安装Gitlab等,那可得8G起步了老板,能弄个16G当然也是甚好哈哈哈
当然不管的内网服务器还是云服务器,配置SSH连接能很方便的在本地连接到服务器
创建用户
在centos系统下创建用户,其他系统的老板遇到问题自行百度吧
- 以添加用户名为 longming 的用户为例子,输入命令添加用户、添加用户目录、指定 bash 为 shell
useradd -m -s /bin/bash eric
-m
自动创建用户的家目录,并将/etc/skel
中的文件复制到家目录中
-s
指定用户登入后所使用的 shell
- 然后对该用户设置密码,输入命令后会提示输入两次密码
passwd eric
- 查看当前用户列表
cat /etc/passwd
- 为了方便操作,现在我们要给用 eric添加root权限
#开启 sudoers 文件的写的权限,默认为只读
chmod -v u+w /etc/sudoers
#修改 sudoers
vi /etc/sudoers
-----------------------------------------------
# Allow root to run any commands anywhere
root ALL=(ALL) ALL
eric ALL=(ALL) ALL (添加这一行)
-----------------------------------------------
# 记得关闭 sudoers 文件的写的权限,默认为只读
chmod -v u-w /etc/sudoers
- 用户切换
# su 用户名,- 作用是改变工作目录 ,- 是 -l的缩写,切换到root用户不需要输入用户名
su - eric
# 接下要为ssh连接做准备了(当然这里你可以用阿里云的控制台配)
- sshd_conf 控制远程连接的文件
vi /etc/ssh/sshd_config
-----------------------------------------------
PermitRootLogin no //阻止root用户登陆
AllowUsers eric //允许制定用户使用SSH登陆
//阻止用户密码SSH登陆,如果设置no,证书还没配置对,你就登陆不上了哈哈哈,别慌阿里云的VNC远程连接还能搞回来哈哈
PasswordAuthentication no
-----------------------------------------------
SSH配置
1 创建密钥
#window系统
系统盘/Users/$(yourusername)/.ssh
#mac系统
cd /Users/$(yourusername)/.ssh
#执行命令创建密钥对,一路回车不要输入密钥密码(当然要输入密码也随你)
ssh-keygen -t rsa -f $(yoursshname) -C "your_email@example.com"
#如果是复制过来的目录内容啊 .ssh/ ,那么对.ssh 目录执行就行
sudo chown -R username .ssh
解析:
- -t :指定密钥类型,默认是 rsa ,可以省略。
- -C: 设置注释文字,比如邮箱。
- -f: 指定密钥文件存储文件名,省略的话默认生成 id_rsa 和 id_rsa.pub
-f 指定密钥对的文件名这个比较重要,
因为我们要创建多个密钥对连接对应的主机,这里我创建了几个:
gitee-eric 、aliyun-env、aliyun-nginx 代表 连接gitee仓库的、连接阿里云环境机的、连接阿里云静态服务器的
执行完成后.ssh 目录下会出现 gitee-eric、gitee-eric.pub、aliyun-env、aliyun-env.pub、aliyun-nginx、aliyun-nginx.pub
后缀.pub的,是公钥,要复制到连接的主机去的
例子:看到这个就是创建成功了一个啦,记住是在自己的 ssh目录下运行创建哈 创建的时候一路回车不用输入passphrase,大佬的话随意
➜ .ssh ssh-keygen -t rsa -f aliyun-env -C "451904906@qq.com"
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in aliyun-env.
Your public key has been saved in aliyun-env.pub.
The key fingerprint is:
SHA256:ePnhkUiIHTy8zsdpX1F9MKtC+PJK2F768PjEYuoUFgc 451904906@qq.com
The key's randomart image is:
+---[RSA 2048]----+
| oE oo |
| o++ . .oo|
| . +o+ . .. .|
| .= = ... |
| o+.S.* .. |
| .o==* +. |
| oo*.B. |
| . = X. |
| .o =o+ |
+----[SHA256]-----+
2 配置公钥
创建完成后复制创建的公钥aliyun-env.pub
#cat公钥aliyun-env.pub的内容,复制它
.ssh cat aliyun-env.pub
现在去登陆到你的环境部署主机,登陆到刚刚创建的用户eric
#查看是否进入用户工作目录
pwd
/home/eric
#在 /home/eric目录下创建 .ssh目录 设置权限700
mkdir ~/.ssh
chmod 700 ~/.ssh
#创建ssh验证文件、设置权限644
vi ~/.ssh/authorized_keys
chmod 644 ~/.ssh/authorized_keys
-----------------------------------------------
#这里复制你的一开始生产的公钥
ssh-rsa askdlajsdkl是什么的反正就是copy
-----------------------------------------------
现在还要配置一下 ssh登陆的配置文件:/etc/ssh/sshd_config,允许 eric等用户远程登陆
sudo vi /etc/ssh/sshd_config
-----------------------------------------------
PermitRootLogin yes //控制root用户登陆与否
AllowUsers eric //允许制定用户使用SSH登陆,这里我们把用户eric添加进来
PasswordAuthentication yes //阻止用户密码SSH登陆⚠️⚠️⚠️如果设置no,证书还没配置对好,你就登陆不上了哈哈哈,别慌阿里的VNC远程连接还能搞回来哈哈
RSAAuthentication yes
PubkeyAuthentication yes
-----------------------------------------------
保存退出 重启sshd服务
sudo systemctl restart sshd
3 连接远程主机
现在回到本地,配置ssh的配置文件 .ssh/conf(如果没有就创建一个),配置ssh的host
Host env
HostName 47.115.11.abc
User eric
Port 22
IdentityFile ~/.ssh/aliyun-env
配置好测试连接
ssh env
#第一次连接会提示添加到 known_hosts信任,选择yes
#看到这里就连接成功啦
Last login: Sun Aug 2 03:12:27 2020 from 113.110.38.101
Welcome to Alibaba Cloud Elastic Compute Service !
[eric@jenkins-t ~]$
连接成功后 禁止root登陆和密码登录吧
sudo vi /etc/ssh/sshd_config
-----------------------------------------------
PermitRootLogin no //控制root用户登陆与否
AllowUsers eric //允许制定用户使用SSH登陆,这里我们把用户eric添加进来
PasswordAuthentication no //阻止用户密码SSH登陆⚠️⚠️⚠️如果设置no,证书还没配置对好,你就登陆不上了哈哈哈,别慌阿里的VNC远程连接还能搞回来哈哈
RSAAuthentication yes
PubkeyAuthentication yes
-----------------------------------------------
如果使用mac,可以配合alfred的ssh workflow来开始连接远程主机,配置地址可以参考这文章 alfred集成ssh+iTerm2
一般来说对于预发布和生产环境的机器一般都需要设置堡垒机,登陆之前需要获取口令,验证码什么的,这些也可以配合alfred的,我们这边先简化哈
安装使用流程
- docker :安装docker,我们的整套服务将分别承载在各个docker容器里
- jenkinsci/blueocean :负责 CI / CD任务:自动打包构建部署等一系列自动化任务 (为了流水线的可视化我们镜像拉取的是这个,其实就是jenkins安装了blueocean的插件)
- verdaccio/verdaccio :npm库
- mongo: 数据库,这里给yapi用的
- yapi:这个镜像得我们来制作
- docker-compose:上面需要安装的4个应用容器等,到时候会用docker-compose统一控制:安装删除、启动停止,容器网络连接等
Docker
yum设置
我们用yum来安装docker,先处理好yum
- 先备份你的原镜像文件,以免出错后可以恢复
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
- 下载wget、创建目录、下载阿里云yum源配置
yum install wget -y
mkdir -p /etc/yum.repos.d
#注意,这个是要对你的centos版本的,
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo
#Centos-7就对7,
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
- 更新yum缓存
#清除原有yum源缓存
yum clean all
#生成阿里云yum源缓存
yum makecache
- 升级本地yum包
yum update
- 安装 yum-utils,它提供了 yum-config-manager,可用来管理yum源
yum install -y yum-utils device-mapper-persistent-data lvm2
- 安装阿里云docker源
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
- 更新yum索引
yum makecache fast
yum clean all
Docker安装
- 安装docker
yum -y install docker-ce
#如果出现问题,运行下来两个命令
wget https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.13-3.1.el7.x86_64.rpm
yum -y install ./containerd.io-1.2.13-3.1.el7.x86_64.rpm
- 查看docker版本
docker -v
- 将你的用户添加到 docker 的用户组,添加后退出用户重新登陆才能生效,这是避免以后docker操作总是要 sudo
sudo usermod -aG docker $(yourname)
- 启动docker服务
sudo service docker start
- 开机启动docker服务
systemctl enable docker.service
Docker的基本操作命令
镜像操作
docker search images_name # 查看仓库的镜像资料
docker pull images_name # 下载镜像
docker images # 显示本地镜像
docker rmi images_name/image_id # 删除本地镜像
容器的命令
docker ps #查看当前运行的容器
docker ps -a #查看存在的所有容器
docker stop #停止容器
docker start #运行容器
docker restart #重启容器
docker rm container_id/container_name #删除容器
docker logs [options] container_id/container_name #查看容器日志,出错或者调试可用
docker exec -it container_id/container_name [/bin/bash 或者 sh] #进入容器分配一个终端/bin/bash,不存在就用sh
exit #在容器内部
docker commit container_id/container_name # 将容器
卷的操作
#查看本地volume
docker volume ls
#删除指定volume
docker volume rm volume_id/volume_name
#删除所有的volume
docker volume prune
network(这个我们暂时不用掌握,在Docker Compose 接管的总结里讲)
#查看docker中存在的网络
docker network ls
#查看网络的详细信息
docker network inspect
#自定义网络(默认是bridge类型)
docker network create front-net
#将容器web1 和 web2 加入网络,这样容器web1 和 web2 用这个来两个名字就能互相ping同,会自动进行DNS解析
docker network connect front-net web1
docker network connect front-net web2
#断开连接
docker network disconnect front-net web1
docker network disconnect front-net web2
Jenkinsci/BlueOcean
1 Jenkins安装
现在我们要使用docker 来安装jenkins了
- 我们先下载 jenkinsci/blueocean 镜像吧
#运行这个命令会查看仓库的jenkins镜像资源
docker search jenkins
#运行后你会看到类似这样的
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
jenkins Official Jenkins Docker image 4863 [OK]
jenkins/jenkins The leading open source automation server 2154
jenkinsci/blueocean https://jenkins.io/projects/blueocean 544
jenkinsci/jenkins Jenkins Continuous Integration and Delivery … 382
jenkins/jnlp-slave a Jenkins agent which can connect to Jenkins… 129 [OK]
jenkinsci/jnlp-slave A Jenkins slave using JNLP to establish conn… 126 [OK]
jenkinsci/slave Base Jenkins slave docker image 65 [OK]
jenkins/slave base image for a Jenkins Agent, which includ… 43 [OK]
jenkinsci/ssh-slave A Jenkins SSH Slave docker image 42 [OK]
···
#我们选择下载 jenkinsci/blueocean(这个是jenkins集成blueocean的插件,可视化流水线)
docker pull jenkinsci/blueocean
#下载后我们可以运行docker images 命令查看本地镜像
docker images
- 生成jenkins应用的容器
docker run \
--name jenkins \
-d \
-it \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc/localtime:/etc/localtime:ro \
jenkinsci/blueocean
我们先来看看命令行都是啥意思
- docker run: 创建一个新容器
- --name jenkins: 将这个容器命名为jenkins(这个后面方便操作)
- -d: 这个参数能让容器到后台运行
- -it: -i 以交互模式运行容器,-t 为容器重新分配一个伪输入终端
- -p: 端口映射,格式为:主机(宿主)端口:容器端口
- -v: 指定一个volume,将容器的/var/jenkins_home的数据持久化保存到名为 jenkins的volume上,这个归主机docker统一管理
- -v /var/run/docker.sock:/var/run/docker.sock: jenkins 会用docker容器来做agent,有必要的话把他们缓存下来
- -v /etc/localtime:/etc/localtime:ro : 同步容器和主机的时间,ps:这玩意在mac上不好使
- jenkinsci/blueocean: 这个是image镜像文件,docker将以此镜像文件创建一个容器,如果本地不存在会自动拉去线上最新的版本
这样我们就成功创建运行一个jenkins应用的容器
运行成功后,如果你是阿里云,你要去添加实例的安全组规则
因为上面我的jenkins是代理到主机的8080,所以设置阿里云主机
这样用你的主机的 ip:8080就能看到jenkins的启动页面
这个密码 运行查看jenkins容器的日志,拉到最后可以看到
docker logs jenkins
*************************************************************
*************************************************************
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
cb619dcc57574a6592d96asdasdasjasd
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************
*************************************************************
*************************************************************
耐心的等待安装完成
创建第一个管理用户
老板,看到jenkins了!
jenkins的入门教程必须要花时间先看一下,主要了解一下任务啊,声明式和脚本式的两种流水线写法啊,流水线步骤啊
2 Jenkins连接Gitee
- 安装插件
我们要先让jenkins跟git托管仓库连接,gitee、gitlab、github都行,这个我们要安装几个插件
- Gitee
- Multibranch Scan Webhook Trigger
- Generic Webhook Trigger
安装好后现在先全局配置一下Gitee配置,看官方的教程
- 配置SSH连接gitee
ssh env #先连接env服务器
docker ps #查看正在运行的docker 容器找到jenkins的容器name
docker exec -it jenkins /bin/bash #在运行中的jenkins容器,生成ssh密钥对(看图吧)
还没结束、因为是自定义名字,需要再 ~/.ssh目录下新建一个config配置文件
cd ~/.ssh/
vi config
#复制下面的内容
-----------------------------------------------
Host gitee.com
Preferredauthentications publickey
IdentityFile ~/.ssh/jenkins-gitee
-----------------------------------------------
这样才能成功执行ssh连接
生成后的公钥复制在gitee上,个人配置那
创建jenkins链接gitee的凭证,当然也可以在下面新建流水线的时候在添加
3 multi branch pipeline
创建流水线任务的时候最好把全局的执行器设置会3个以上,不然我们这个仓库触发多有分支构建的时候会不够资源导致失败
现在我们开始来新建任务吧
按图上设置分支源、设置webhook的token等
设置好存,第一次会会自动拉去仓库的代码去执行构建任务
点击状态,可以看到分支任务拉去的情况,我们项目的feature分支和hoxfix分支是不作为构建任务的
这个就是dev分支对应的pipeline任务步骤啦
因为我们安装的是jenkinsci / blueocean (就是jenkins安装了blueocean插件的集合),这个方便我们对任务的可视化
点击左侧导航的Blue Ocean
分别点击 dev、release、master分支,看到
可以注意到我们的分支,在dev和release启动的时候会自动持续交付,到master的时候多了一个选择按钮,因为master是生产环境,我们一般是需要手动部署上线好
这个几个流水线是基于每个分支的Jenkinsfile来生成运行的
4 webhook 设置
说道这个刚刚只是新建了仓库后自动拉去分支触发的任务,现在我们要来设置webhook触发。
webhookURL:http:你的jenkinsurl/multibran...
现在我们来试一下触发webhook,为了方便我们先不新建功能分支了,直接在dev分支行做push
我们来给jenkinsfile做点修改吧
- 添加新的代码,add,commit,push
echo “test wehook”
- 看我们的jenkins blue-ocean界面,dev分支的任务触发了
等成功后,点击步骤 pre-build 可以看到刚刚添加的echo信息
当然也可以新建分支 feature-*,推送到远仓库,请求合并到release分支,合并成功后也会触发release的任务
5 pipeline的stage 简介
上面大概流程完成了,我们现在来看一下,pipeline上的 pre-build、build-parellel:[ build-dev | build-master | build-release ]、artifacts-manage、deliver、deloy
这些是流水线pipeline的步骤stage,我们在Jenkinsfile里面自定义的,咦,好像少了test步骤,因为我这里不做自动化测试哈哈哈,需要的慢点自己加上就行
在设置pipeline的stage前,我们先来明白我们要做什么
pre-build:打包前的工作
- 设置缓存目录 和 文件
- 判断是不是最近构建过,构建过直接调用缓存的包,重新部署就行,不用重新build什么的
- 判断需不需要重新安装node_modules包依赖
build-env:打包
- 按分支运行打包的脚本
artifacts-manage:制品管理
- 构建后的资源 /dist 打包压缩,到缓存目录或者仓库,归档
deliver:交付
- 这个步骤一步给开发环境、测试环境,预发布环境用,打包后的代码自动交付到这几个环境
deploy:部署
- 这个给生产环境用,打包后到这里会设置一个按钮,手动点击再运行部署上线脚本
Jenkinsfile详解
jenkins通过读取我们项目根目录这个Jenkinsfile,来生成相应的流水线任务,这个内容比较多,做成使用目录方便阅读吧
现在我们要看看这个pipeline的配置文件Jenkinsfile,还是要啰嗦一下,对着官方教程扫几遍教程+做一次,对着官方教程扫几遍教程+做一次,对着官方教程扫几遍教程+做一次,重要的事情我说三遍了,希望老板你能做一遍
1 Jenkinsfile 预览
我们先来看看Jenkinsfile
pipeline {
agent any // agent节点,any告诉jenkins 任何可用的agent都能执行
environment {
Name = 'Eric' // 这个是自定义的顶级pipeline全局变量
}
options {
disableConcurrentBuilds() // 在多分支的流水线中限制并发
}
stages {
stage('pre-build') { // 自定义步骤 pre-build
when {
anyOf { // 多分支才有的判断,一个真就通过过
branch 'dev'
branch 'release'
branch 'master'
}
}
agent { // 代表阶段运行在一个代理的docker nodejs容器中
docker {
image 'node:10.21.0'
// 想在 docker 容器中运行代码,但是也想使用流水线所定义的相同节点或和工作空间,必须设置这个
reuseNode true
}
}
steps {
sh "printenv"
echo "pre-build"
echo "test webhook"
}
}
stage('build-env') {
when {
anyOf {
branch 'dev'
branch 'release'
branch 'master'
}
}
// 如果设置了true 那么我们有任何一个并发步骤失败那就全都都失败,
// 因为我们是用来做不同分支任务触发时候的构建选择,所以这个不需要
failFast false
parallel {
stage('build-dev') {
when {
// beforeAgent 是指在进入agent ,如果when的条件对,才进入,错则不进入
// 就是可以加快流水线的运行啦
beoreAgent true
branch 'dev'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps {
echo "build-dev"
}
}
stage('build-release') {
when {
beforeAgent true
branch 'release'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps{
echo "build-release"
}
}
stage('build-master') {
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
when {
beforeAgent true
branch 'master'
}
steps {
echo "build-master"
}
}
}
}
stage("artifacts-manage"){
steps {
echo "artifacts"
}
}
stage('deliver') {
when {
beforeAgent true
anyOf {
branch 'dev'
branch 'release'
}
}
steps {
echo "start deliver"
}
}
stage('deploy') {
when {
beforeAgent true
branch 'master'
}
steps {
// 这个就是生成一个按钮,我们用来手动发布的
input message: "Should you deploy?"
echo "start deloy"
}
}
}
//post 就是流水线的运行结果状态啦,我们慢点会在这里设置邮件通知
post {
changed{
echo 'I changed!'
}
failure{
echo 'I failed!'
}
success{
echo 'I success'
}
always{
echo 'I always'
}
unstable{
echo "unstable"
}
aborted{
echo "aborted"
}
}
}
我们这个脚本是一个声明式流水线,相比脚本式的流程更加清楚明白,也是能更适合生成Blue-Ocean这种可视化的流水线
读懂上面代码必需要知道的pipeline流水线语法知识点:
- pipeline:代表整条流水线,包含整条流水线的逻辑
- environment: 指令制定一个 键-值对序列,该序列将被定义为所有步骤的环境变量,或者是特定于阶段的步骤, 这取决于
environment
指令在流水线内的位置。 - stages:stage的容器,stages部分至少有一个stage
- stage:流水线的阶段、必须指定名字,stage(“名字”)
- steps:代表阶段中的一个或者多个步骤。一个stage有且只有一个steps
- agent : 指定流水线的执行位置。流水线中的每个stage都必须在某个地方(物理机、虚拟机、docker容器)执行。agent部分就是负责指定执行环境,pipeline下的agent指定的是所有stage的执行环境
when: 指令允许流水线根据给定的条件决定是否应该执行阶段。
- beforeAgent 是指在进入agent ,如果when的条件对才进入,错则不进入,提高构建速度
- branch 这只适用于多分支流水线。
- expression当指定的Groovy表达式评估为true时,执行这个阶段, 例如:
when { expression { return true } }
- anyof 当至少有一个嵌套条件为真时,执行这个阶段,必须包含至少一个条件,例如:
when { anyOf { branch 'master'; branch 'staging' } }
- post: 就是流水线的运行结果状态啦,我们慢点会在这里设置邮件通知
- parallel: 声明式流水线的阶段可以在他们内部声明多隔嵌套阶段, 它们将并行执行。 注意,一个阶段必须只有一个
steps
或parallel
的阶段。
更加完整的信息请在官方文档上查阅吧
2 pre-build 阶段
接下来我们要开始写pre-build步骤的脚本啦啦啦
pre-build 要实现的功能
- 判断和新建&更新缓存 目录 & 文件
- 判断是不是最近构建过,构建过设置回滚标志文件,供后面阶段构建跳过执行、直接调用缓存的包重新部署就行
- 通过package的缓存和内容的查询对比,判断需不需要删除旧node_modules,运行
npm install
重新安装包依赖
- 开始之前我们设置几个pipeline的全局变量
pieline{
environment {
cacheDir = "stage" //定义缓存的目录名字
cachePackage = "${cacheDir}/package.json" //定义缓存的package.json
cacheCommitIDFile = "${cacheDir}/.commitIDCache.txt" //把成功打包的commitID缓存到这里
artifactsDir = "${cacheDir}/artifacts" //制品缓存的目录,构建成功的制品我们放这里
resetFlagFile = "${cacheDir}/.resetFile" //回滚标志的文件
cacheCommitIDMax = 5 //缓存版本的最大值
}
...
}
- 步骤
stage('pre-build') { // 自定义步骤 pre-build
stage('pre-build') { // 自定义步骤 pre-build
when {
anyOf { // 多分支才有的判断,一个真就通过过
branch 'dev'
branch 'release'
branch 'master'
}
}
agent { // 代表阶段运行在一个代理的docker nodejs容器中
docker {
image 'node:10.21.0'
// 想在 docker 容器中运行代码,但是也想使用流水线所定义的相同节点或和工作空间,必须设置这个
reuseNode true
}
}
steps {
sh "printenv" //打印jenkins全局环境变量
sh './jenkins/script/pre-build.sh' //我们把主要的功能写在pre-build.sh脚本里面
}
}
然后新建pre-build.sh脚本 jenkins/script/pre-build.sh
新建好需要执行,给脚本执行权限
chmod +x jenkins/script/pre-build.sh
#!/bin/bash:此脚本使用/bin/sh来解释执行
#!/bin/bash
isResetID=false #回滚标志判断
cmpFlag=1 #package.json文件的对比标志
packageJsonChange=false #package.json文件变化标志
# set -x
#先设置淘宝源加快安装速度,这里我们慢点要替换成我们自己搭建的更快
npm config set registry https://registry.npm.taobao.org
#判断缓存目录存在与否
if [ ! -d $cacheDir ]
then
echo "no cache dir"
mkdir $cacheDir
cp package.json "$cacheDir/"
touch $cacheCommitIDFile
# echo $GIT_COMMIT > $cacheCommitIDFile #先不用写入
echo "npm 安装"
npm i || exit 1
#判断缓存文件存在与否
elif [ ! -f $cachePackage ] || [ ! -f $cacheCommitIDFile ]
then
if [ ! -f $cachePackage ]
then
echo "cache file package.json does no exist"
echo "cp package.json to cache dir"
cp package.json "$cacheDir/"
fi
if [ ! -f $cacheCommitIDFile ]
then
echo "cache file commitIdCache.txt does no exist"
echo "create commitIdCache.txt to cache dir"
echo "write the ID to commitIdCache.txt"
touch $cacheCommitIDFile
echo $GIT_COMMIT > $cacheCommitIDFile
fi
rm -rf node_modules
sleep 1
echo "npm 安装"
npm i || exit 1
else
echo "cache file exists"
#这里我们判断一下新提交的commitID是不是有存在于缓存
for commitId in `cat "$cacheCommitIDFile"`
do
if [ $commitId = $GIT_COMMIT ]
then
isResetID=true
fi
done
#对比新旧的package.json,看是否有变化
cmp -s package.json $cachePackage
cmpFlag=$?
#compare package.json
if [ $cmpFlag != 0 ]
then
packageJsonChange=true
fi
#如果是重复的ID
if [ $isResetID = true ]
then
echo "reset啦啦啦啦"
#我们还是需要重置一下新的package
cp -f package.json "$cacheDir/"
#这里我们要一个创建代表回滚标志的文件,提供给pipeline判断以供跳过不必要的步骤执行,提高执行的速度
touch ${resetFlagFile}
# ID不是重复的,而且package.json有更新
elif [ $packageJsonChange = true ]
then
echo "package 更新"
cp -f package.json "$cacheDir/"
rm -rf node_modules
sleep 1
npm i || exit 1
else
ehco "啥都不用干哈哈"
fi
fi
# set +x
更新push到远程仓库,去jenkins查看构建状态
构建成功后,我们进去工作空间看看,通过web界面或者docker 容器命令行
- 通过web 界面很简单,就是点点点
- 命令行
#工作空间一般命名是项目名字_分支名
ssh env
docker exec -it jenkins /bin/bash
cd var/jenkins_home/workspace
cd vue-ci-start_dev
ls
我们可以看到我们在pre-build 步骤里面,创建的 目录stage和文件stage/package.json、stage/.commitIDCache.txt
当然也有拉取的项目文件和安装的node_modules等
PS:记住这个文件.commitIDCache.txt,我们用来判断有没有重复ID的,当我们打包压缩成功后,我们才会去把本次的commitID写到文件里面去
3 build-env 阶段
- 按分支运行打包的脚本
这里主要是构建我们的应用啦,因为我们有dev、release、master环境的代码需要构建,所以用了stage的parallel,
当webhook触发SCM触发任务时,我们通过when判断是那个分支就运行哪个分支的构建步骤
stage('build-env') {
when {
anyOf {
branch 'dev'
branch 'release'
branch 'master'
}
}
// 如果设置了true 那么我们有任何一个并发步骤失败那就全都都失败,
// 因为我们是用来做不同分支任务触发时候的构建选择,所以这个不需要
failFast false
parallel {
stage('build-dev') {
when {
// beforeAgent 是指在进入agent ,如果when的条件对,才进入,错则不进入
// 就是可以加快流水线的运行啦
beoreAgent true
branch 'dev'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps {
echo "build-dev"
sh "./jenkins/script/build-dev.sh"
}
}
stage('build-release') {
when {
beforeAgent true
branch 'release'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps{
echo "build-release"
sh "./jenkins/script/build-release.sh"
}
}
stage('build-master') {
when {
beforeAgent true
branch 'master'
}
agent {
docker {
image 'node:10.21.0'
reuseNode true
}
}
steps {
echo "build-master"
sh "./jenkins/script/build-master.sh"
}
}
}
}
在jenkins/script目录下新建三个构建脚本:build-dev.sh 、build-release.sh、build-master.sh
分别对应dev开发环境的构建、release测试环境的构建、master线上生产环境的构建
#build-dev.sh
npm run build-dev || exit 1
#build-release.sh
npm run build-release || exit 1
#build-master.sh
npm run build || exit 1
PS:记得给脚本执行 chmod +x
给执行权限
运行的具体脚本是在package.json里面定义srcipt的
"scripts": {
"serve": "vue-cli-service serve",
"build-dev": "vue-cli-service build --mode dev",
"build-release": "vue-cli-service build --mode prod",
"build": "vue-cli-service build ",
"lint": "vue-cli-service lint"
},
--mode [mode]就是指加载 根目录下的.env.[mode]文件:例如 --mode dev
就是加载根目录下的 .env.dev文件
具体可以看vue-cli的官方文档
现在测试一下在各个分支的运行打包情况吧
4 artifacts-manage 阶段
- 构建后的资源 dist 打包压缩存到缓存目录或者仓库,除了可以下载查阅,也方便分支回滚后快速部署
- 我们还设定了制品储存的数量,免得任务多的时候,构建次数一多硬盘空间不够
我们小团队用的是下面这中最朴素的归档打包方法,当然要逼格你可以自己搭Nexus等平台
到了制品管理阶段啦,我们成功打包后的文件都在根目录的dist目录下,这个是webpack控制的,可以改
Jenkinsfile添加执行脚本代码
stage("artifacts-manage"){
steps {
echo "artifacts"
sh './jenkins/script/artifacts-manage.sh'
}
}
在jenkins/script目录下新建脚本:artifacts-manage.sh,chmod +x
给执行权限
#!/bin/bash
#set -x
#创建缓存目录
if [ ! -d $artifactsDir ]
then
mkdir $artifactsDir
fi
#打包压缩 dist 文件
tar -zcvf $artifactsDir/${GIT_COMMIT}_dist.tar.gz dist
FileNum=$(ls -l $artifactsDir | grep ^- | wc -l)
#更新缓存库的内容
while [ $FileNum -gt $cacheCommitIDMax ]
do
OldFile=$(ls -rt $artifactsDir/* | head -1)
echo "Delete File:$OldFile"
rm -f $OldFile
let "FileNum--"
done
#这里可以把commitID存到之前的缓存文件
CommitIDNum=`cat $cacheCommitIDFile | wc -l`
if [ $CommitIDNum -ge $cacheCommitIDMax ]
then
sed -i '1d' $cacheCommitIDFile
fi
echo
sleep 1
echo $GIT_COMMIT >> $cacheCommitIDFile
# set +x
我们再做一次push触发jenkins的流水线,构建好后我们去jenkins任务的工作空间
我们看到项目跟目录下有打包后生成的 dist 文件夹、stage 里面有我们的缓存文件
查看 .commitIDCache.txt 文件发现了我们把这次的git commit ID :16c0451ba1368ada4e29ad6ac3ffb44f0ddb52a0 写入在里面了
再看一下 stage/artifacts目录下的文件,发现有我们打包压缩后的文件啦 16c0451ba1368ada4e29ad6ac3ffb44f0ddb52a0_dist.tar.gz
因为我们设置了缓存五个版本,我们来测试一下功能:在做5次提交push,看看会不会除去一开始这个缓存的ID和文件,看上图可以看到
缓存的commit ID 和 文件都更新到了最近的版本,之前第六个版本已删掉啦啦啦😝
但是这样我们先简单的下载本次成功构建后归档的东西好像还有点麻烦啊,别慌,jenkins还提供了archiveArtifacts 步骤
stage("artifacts-manage"){
steps {
echo "artifacts"
sh './jenkins/script/artifacts-manage.sh'
archiveArtifacts artifacts:"${artifactsDir}/${GIT_COMMIT}_dist.tar.gz" // 把我们本次构建的压缩文件归档
}
}
这样我们就能很方便的jenkins的web页面下载啦
# 用archiveArtifacts归档的文件会等放在这里哈
${JENKINS_HOME}/jobs/${JOB_NAME}/branch/${BRANCH_NAME}/builds/${BUILD_NUMBER}/stage/archive
5 reset 阶段
- 这个是一个检测回滚操作的时候执行的步骤
- 在这里我们需要为 build-env、artifacts-manage等步骤增加点代码,回滚的时候不需要执行他们
还记得我们前面在pipeline的环境设置的变量 resetFlagFile 吗,还有在 pre-build 阶段 如果出现相同的commit ID的操作吗
if [ $isResetID = true ]
then
#我们还是需要重置一下新的package
cp -f package.json "$cacheDir/"
#这里我们要一个创建代表回滚标志的文件,提供给pipeline判断以供跳过不必要的步骤执行,提高执行的速度
touch ${resetFlagFile}
# ID不是重复的,而且package.json有更新
...
检测到相同的commit ID 了,创建这个文件 ${resetFlagFile}
,用来做什么?当然是做回滚判断标志呀
在 build-env、artifacts-manage步骤的when添加
when {
beforeAgent true
+ expression{
+ return !(fileExists("${resetFlagFile}"))
+ }
...
}
fileExists()
是pipelinen内置的方法,判断文件是否存在,返回布尔类型
现在我们先做一个普通的push再做一次回滚的push来看看效果
#先做两次正常的commit push
git log
commit 7f31a184256b326cb353c506125d58b0efe28481 (HEAD -> dev, origin/dev)
Author: longming <451904906@qq.com>
Date: Thu Aug 6 13:35:32 2020 +0800
正常的提交2
commit acbc3ea45dca16c82a39f2ec6e772eada57229f7
Author: longming <451904906@qq.com>
Date: Thu Aug 6 13:33:20 2020 +0800
正常的提交
在做执行回滚到版本 acbc3ea45dca16c82a39f2ec6e772eada57229f7
git reset -hard acbc3ea45dca16c82a39f2ec6e772eada57229f7
git push -f
进入工作空间,查看stage的.commitIDCache.txt、和artifacts目录下的压缩包,可以看到写入和缓存都成功了
第二次回滚后文件没改变
我们来看一Blue Ocean的流水线图
我们来看一下,回滚操作中跳过了 build-env 和 artifacts-manage 这两个阶段,因为不需要再重新去build和缓存了,这样就能做到秒级的回滚啦啦啦
reset 阶段的代码在这里
stage("reset"){
when {
beforeAgent true
expression{
return fileExists("${resetFlagFile}")
}
anyOf {
branch 'dev'
branch 'release'
branch 'master'
}
}
steps {
echo "是回滚啊啊啊啊"
//当然我们这里为了方便下载
archiveArtifacts artifacts:"${artifactsDir}/${GIT_COMMIT}_dist.tar.gz"
}
}
6 deliver阶段
这个步骤一步给开发环境、测试环境,预发布环境用,打包后的代码自动交付到这几个环境
- 安装资源服务器
我们这里安装的是Nginx,详细看 Nginx
配置SSH
这个阶段要先配置好我们的jenkins容器 到 需要资源服务器的 ssh,具体看 SSH配置
问题来了,部署机怎么来?我是在阿里云🌥梭哈了一台几个月的香港服务器哈哈哈🍖(我们大陆的话,网站要先备案好才能通过)
我们配置好了jenkins ssh 连接 资源服务器,host的名字设置为server
在jenkins应用容器执行
ssh server
就能连接到资源服务器啦ps:如果配置连接的用户名,不是要上传目录 /data的拥有者,请最好更改一下权限
#切换到root,把/data目录的拥有者给eric,eric是我们这里ssh连接到资源服务器的用户名 chmod -R eric /data
deliver 脚本
在Jenkinsfile文件添加ssh连接的host name作为环境变量
pipeline{ ... environment { ··· + sshHostName = "server" }
}
deliver stage的代码
stage('deliver') {
when {
beforeAgent true
anyOf {
branch 'dev'
branch 'release'
}
}
steps {
echo "start deliver"
sh "./jenkins/script/deliver.sh"
}
}
// 整个pipeline流程结束需要删除回滚标志文件
post{
alway{
}
}
新建**deliver.sh**脚本 jenkins/script/deliver.sh
#!/bin/bash
# set -x
#如果是回滚的操作,我们不用再把构建上传一次啦,服务器已经有啦
if [! -d ${resetFlagFile}]
then
# 将本次构建的压缩包上传到资源服务器的指定目录
scp ${artifactsDir}/${GIT_COMMIT}_dist.tar.gz ${sshHostName}:/data/vueci/${BRANCH_NAME}/ || exit 1
fi
# 远程执行服务器上面的脚本解压缩发布
ssh ${sshHostName} /data/vueci/deploy.sh ${BRANCH_NAME} ${GIT_COMMIT} || exit 1
# set +x
新建好需要执行,给脚本执行权限
chmod +x jenkins/script/deliver.sh
我们还得跑到资源服务器新建一个发布的脚本**/data/vueci/deploy.sh**
tar -zxvf /data/vueci/$1/$2_dist.tar.gz -C /data/vueci/$1/
新建好需要执行,给脚本执行权限
chmod +x /data/vueci/deploy.sh
现在来测试一下交付到dev环境吧
git checkout dev
#更改点什么
git commit -am "发布到dev环境啦"
git push

看到效果了,其实是开发环境哈哈哈随便写的哈哈哈
#### 7 deploy 阶段
到了发布阶段啦,这个是运用在生产环境分支上的,发布前的按钮
这个步骤跟deliver差不多,多一个发布的input,来看一下代码
stage('deploy') {
when {
beforeAgent true
branch 'master'
}
steps {
// 这个就是生成一个按钮,我们用来手动发布的
input message: "Should you deploy?"
echo "start deloy"
sh './jenkins/script/deploy.sh'
}
}
新建发布脚本**deploy.sh** ,jenkins/script/deploy.sh
给脚本执行权限
chmod +x /data/vueci/deploy.sh
deploy.sh 脚本跟 交付的没啥区别(当然一般是正式环境和测试环境执行的脚本的一般是不同的)
!/bin/bash
set -x
如果是回滚的操作,我们不用再把构建上传一次啦,服务器已经有啦
if [ ! -f $resetFlagFile ]
then
# 将本次构建的压缩包上传到资源服务器的指定目录
scp ${artifactsDir}/${GIT_COMMIT}_dist.tar.gz ${sshHostName}:/data/vueci/${BRANCH_NAME} || exit 1
fi
远程执行服务器上面的脚本解压缩发布
ssh ${sshHostName} /data/vueci/deploy.sh ${BRANCH_NAME} ${GIT_COMMIT} || exit 1
set +x
现在按照最开始的 [**Git flow 工作流**](#Git flow 工作流) 介绍的,我们走个git flow 流程吧
ps:🌏**注意了,我们这里是把dev 当做feature功能分支哈!!!****注意了,我们这里是把dev 当做feature功能分支哈!!!**
**注意了,我们这里是把dev 当做feature功能分支哈!!!**
先切换到dev分支
切换到本地dev
git checkout dev
更新远程dev,如果有多人协调在这个分支做开发的话
git pull
我们把之前的显示的内容改一下
- <HelloWorld msg="欢迎来到测试环境呀" />
- <HelloWorld msg="欢迎来到正式环境环境呀" />
一系列的提交操作
git status
git add .
git commit -m "修改下显示内容"
git push
我们功能分支是从远程master分支checkout出来的,开发过程中可能master上线了其他的功能,需要更新一下
git merge origin/master
如果合并发生冲突,我们解决好冲突后
git status
git add .
git commit "origin/master"
解决好冲突或者没有冲突直接push啦
git push
现在我们要去远程仓库申请功能合并到release去测试啦

我们看到合并成功后,会触发构建任务啦
### 
PS:如果dev 合并 release 有冲突的话,可以先把release 合并到 dev 解决冲突后再申请合并,这样在gitee合并那边就没冲突了
**合并release测试好**
现在我们准备上线,需要把release 合并到 master 分支啦
合并成功后,我们可以看到

合并成功后我们停留在deploy阶段,看一下正式环境的代码,还没更新
先我们点击上面**deploy**,或者那个 **继续** 按钮,就能发布啦

#### 8 Notice 通知
- 上面的部署啊什么的完成了,可是什么时候完成我们不知道,只有通过不停的去刷网页啊才知道,或者问jenkins的管理者,这样就太麻烦了我们要给任务的完成状态配通知啊
公司有钉钉或者企业微信的的可以查一下资料配置很简单的,这里我们就讲一下最通用的邮件通知哈
配置邮件的通知我们要安装几个插件
**Email Extension**:这个用于邮箱的设置
**Config File Provider:**提供文件的存储插件,我们用来配置邮箱模板
本来我是配置了qq邮箱,可是接通知比较慢我换成了网易的163邮箱,当然一般公司都用企业邮箱,配置差不多都一样,看图吧
1. 邮箱开启SMTP服务
打开我们的163邮箱开启,这两个SMTP服务,然后获取授权码,复制好待会要用
2. jenkins 配置邮箱

配置完成之后记得测试一下邮件的发送
3. 配置邮件模板
安装了这个插件之后**Config File Provider**,会在 **系统管理 **的下方看到这个,我们用来配置邮件的模板文件

点击去可以配置邮件模板文件

因为我们要在成功和失败的构建结束后都要发送邮件通知,所以我们需要两个邮箱模板,
这里我用sketch 设计了简单的邮件模板

对应邮件的模板代码:
**email-success.tpl**
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
<style>
html,
body {
font-family: "SF Pro SC", "SF Pro Text", "SF Pro Icons", "PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
}
.table.success {
border: 1px solid #ddd;
color: #4a4a4a;
}
.table.success td {
font-size: 16px;
overflow: hidden;
word-break: break-all;
}
td.info {
background-color: #dfb051;
height: 44px;
color: #fff;
}
td:first-child {
padding-left: 49px;
padding-right: 49px;
}
td.build-status {
background-color: #76ac35;
height: 44px;
color: #fff;
}
.table.success td pre {
white-space: pre-wrap;
}
.success-ico {
vertical-align: middle;
margin-left: 10px;
width: 25px;
/* height: 30px; */
/* margin-bottom: 20px; */
}
td.info-title {
color: #76ac35;
height: 36px;
font-weight: bold;
font-size: 16px;
}
.table.success ul {
list-style: none;
margin: 0;
padding: 0;
}
.table.success ul li {
list-style: none;
}
ul li {
height: 35px;
line-height: 35px;
font-size: 14px;
white-space: nowrap;
}
hr {
margin-left: 0;
}
</style>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
<table width="95%" cellpadding="0" cellspacing="0" class="table success">
<tr>
<td class="info">(大佬们好呀,本邮件是jenkins自动下发的,请勿回复!)</td>
</tr>
</table>
<br />
<table width="95%" cellpadding="0" cellspacing="0" class="table success">
<tr>
<td class="build-status">
构建成功啦
<img
class="success-ico"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAFp3oPPAAAAAXNSR0IArs4c6QAAA/ZJREFUWAm9mLtPVEEUxlkDAaKhQY1iKyWPAmNhYazUAmOMrZRYGQtii9FWwI4e/wA1JibamKixQChITEisqFgNaGI0FDasv2/unNm5r727sDLJt+f1nTNz53VvtqfHt0ajUTe9ZookgdeIN1ICwwiOSaBhDuRHp+O7JwX5wTm8MY7jlXOg9IO5EC1UYLgWglgDwTAFZ7/pJvFtOx1lInKOR/od6cQH9eOGhSxsliSiK4tcMGYIZhUIr0AdhG4DB2cywOBxY2k+CASbQy2Ba/jcjCETIoqbD6R7GrHQQ3fo4emTEgW/kOq9PjNey0Ct0TDWVdqtJzLXAjtWItaC/Njpp8YRdgj6AHBPHRcJuoJKEDE4vYJv0MdaFkh3n61SYFM0naNeYh62a7FPepEfX5KLkhyEbFYHtmrU+KmzPCPWYwf5PX5dNdL6MRKvo9jELnZQyHF97jWXh6GVcpsU2Quqmu1AXQv5FcS5DZrHo2R44ohbEk67IU4AO6Q6qNKrT0y6jFvqeRLXwV8v57OcQhtyH9gBN4oI8vt4X1FcSzcFVgqDGad44qfcOEbAg5SzwhBfeYGGsWkG+gnwzexYyq+4+dCTPJRH5pSUDdRGM/7RxJ3nK2kvJvtCT7O+Mr/L5+d+UUK7PuXrLK21m1DCW1MRe8MuUbWTtuSL1lTE1vtFSU9lbuNP6Y7Z4644LqaGUZaR9Uf3y55G4l4BnnQ3Sy6xY16SzwDizbasEbVoy1YYTsjTY6S2PfYw0EGMm+zhqEB62ysA4XAHMKp+uKvACplkZG1fSm6jWWKZpOAQMX2BzIDT4C14D3Tf6sT/ACfBWXAOXAZXwQ54BhbZEr+RB2sMQIv2GWyCwhuz3crK93VUr3mXVRWArAX/Azq6NKvqWlx1fX07uRZqSgjaLBr5StP7/zT1A1ZB+j2EYwjonEwXdY9fHwLW9lHCeSrh6xyKZy3/MUAiQXsxak8mDeeWAmZnpU9CuLbLb3+WE9uKA/GsVdXeUr4uTL3/J9ntt+KCR6kzhuf0t6Gp0ofJ7aPsPNuX+tc49JE3Br7EBAI3sSdjX5f1DVbiZVRT/Y9pME5Bfo2C59EfRna31ey14SakcM8wM3pvfgenuj0K6u2CM8zMvtWmv2TPyIGxBVI7HlvH8xfoZlO91LWArePtTpMbHIbdM6kB+YFWfQSQ3lYLHwuu02QS8veM77TlDUx3s6DTmRJ/1jqPJf7cDZx7a0O6QNI78Jh1fRIXMB2O9tQlcBHoxaep/wn0D88q+BTvCezQyNXm1d12Bc56CLRSSIrf2tOtuFUxak0Dvf1bvrVzM1NUmCJD+OfADMh+z2g2NCuaHc3Sgb9n/gFyaAu+lH9E5AAAAABJRU5ErkJggg=="
/>
</td>
</tr>
<tr>
<td class="info-title">
构建信息
<!-- <hr size="2" width="100%" align="center" /> -->
</td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${JOB_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因 : ${CAUSE}</li>
<li>构建日志 : <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>工作目录 : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
<hr size="2" width="100%" />
</td>
</tr>
<tr>
<td class="info-title">历史变更记录</td>
</tr>
<tr>
<td>
<ul>
<li>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true,format="更新作者:%c",showPaths=true,changesFormat="[%a]",pathFormat=" %p"}
</li>
<li>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true,format="更新信息:%c",showPaths=true,changesFormat="%m",pathFormat=" %p"}
</li>
</ul>
<!-- ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat=" %p"} -->
<!-- <hr size="2" width="100%" /> -->
</td>
</tr>
<!-- <tr>
<td class="info-title">构建日志(最后100行)</td>
</tr>
<tr>
<td><p><pre>${BUILD_LOG, maxLines=100}</pre></p></td>
</tr> -->
</table>
</body>
</html>
**email-fail.tpl**
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
<style>
html,
body {
font-family: "SF Pro SC", "SF Pro Text", "SF Pro Icons", "PingFang SC", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
}
.table.success {
border: 1px solid #ddd;
color: #4a4a4a;
}
.table.success td {
font-size: 16px;
overflow: hidden;
word-break: break-all;
}
td.info {
background-color: #dfb051;
height: 44px;
color: #fff;
}
td:first-child {
padding-left: 49px;
padding-right: 49px;
}
td.build-status {
background-color: #76ac35;
height: 44px;
color: #fff;
}
.table.success td pre {
white-space: pre-wrap;
}
.success-ico {
vertical-align: middle;
margin-left: 10px;
width: 25px;
/* height: 30px; */
/* margin-bottom: 20px; */
}
td.info-title {
color: #76ac35;
height: 36px;
font-weight: bold;
font-size: 16px;
}
.table.success ul {
list-style: none;
margin: 0;
padding: 0;
}
.table.success ul li {
list-style: none;
}
ul li {
height: 35px;
line-height: 35px;
font-size: 14px;
white-space: nowrap;
}
hr {
margin-left: 0;
}
</style>
</head>
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0">
<table width="95%" cellpadding="0" cellspacing="0" class="table success">
<tr>
<td class="info">(大佬们好呀,本邮件是jenkins自动下发的,请勿回复!)</td>
</tr>
</table>
<br />
<table width="95%" cellpadding="0" cellspacing="0" class="table success">
<tr>
<td class="build-status">
构建成功啦
<img
class="success-ico"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAFp3oPPAAAAAXNSR0IArs4c6QAAA/ZJREFUWAm9mLtPVEEUxlkDAaKhQY1iKyWPAmNhYazUAmOMrZRYGQtii9FWwI4e/wA1JibamKixQChITEisqFgNaGI0FDasv2/unNm5r727sDLJt+f1nTNz53VvtqfHt0ajUTe9ZookgdeIN1ICwwiOSaBhDuRHp+O7JwX5wTm8MY7jlXOg9IO5EC1UYLgWglgDwTAFZ7/pJvFtOx1lInKOR/od6cQH9eOGhSxsliSiK4tcMGYIZhUIr0AdhG4DB2cywOBxY2k+CASbQy2Ba/jcjCETIoqbD6R7GrHQQ3fo4emTEgW/kOq9PjNey0Ct0TDWVdqtJzLXAjtWItaC/Njpp8YRdgj6AHBPHRcJuoJKEDE4vYJv0MdaFkh3n61SYFM0naNeYh62a7FPepEfX5KLkhyEbFYHtmrU+KmzPCPWYwf5PX5dNdL6MRKvo9jELnZQyHF97jWXh6GVcpsU2Quqmu1AXQv5FcS5DZrHo2R44ohbEk67IU4AO6Q6qNKrT0y6jFvqeRLXwV8v57OcQhtyH9gBN4oI8vt4X1FcSzcFVgqDGad44qfcOEbAg5SzwhBfeYGGsWkG+gnwzexYyq+4+dCTPJRH5pSUDdRGM/7RxJ3nK2kvJvtCT7O+Mr/L5+d+UUK7PuXrLK21m1DCW1MRe8MuUbWTtuSL1lTE1vtFSU9lbuNP6Y7Z4644LqaGUZaR9Uf3y55G4l4BnnQ3Sy6xY16SzwDizbasEbVoy1YYTsjTY6S2PfYw0EGMm+zhqEB62ysA4XAHMKp+uKvACplkZG1fSm6jWWKZpOAQMX2BzIDT4C14D3Tf6sT/ACfBWXAOXAZXwQ54BhbZEr+RB2sMQIv2GWyCwhuz3crK93VUr3mXVRWArAX/Azq6NKvqWlx1fX07uRZqSgjaLBr5StP7/zT1A1ZB+j2EYwjonEwXdY9fHwLW9lHCeSrh6xyKZy3/MUAiQXsxak8mDeeWAmZnpU9CuLbLb3+WE9uKA/GsVdXeUr4uTL3/J9ntt+KCR6kzhuf0t6Gp0ofJ7aPsPNuX+tc49JE3Br7EBAI3sSdjX5f1DVbiZVRT/Y9pME5Bfo2C59EfRna31ey14SakcM8wM3pvfgenuj0K6u2CM8zMvtWmv2TPyIGxBVI7HlvH8xfoZlO91LWArePtTpMbHIbdM6kB+YFWfQSQ3lYLHwuu02QS8veM77TlDUx3s6DTmRJ/1jqPJf7cDZx7a0O6QNI78Jh1fRIXMB2O9tQlcBHoxaep/wn0D88q+BTvCezQyNXm1d12Bc56CLRSSIrf2tOtuFUxak0Dvf1bvrVzM1NUmCJD+OfADMh+z2g2NCuaHc3Sgb9n/gFyaAu+lH9E5AAAAABJRU5ErkJggg=="
/>
</td>
</tr>
<tr>
<td class="info-title">
构建信息
<!-- <hr size="2" width="100%" align="center" /> -->
</td>
</tr>
<tr>
<td>
<ul>
<li>项目名称 : ${JOB_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因 : ${CAUSE}</li>
<li>构建日志 : <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>工作目录 : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
<hr size="2" width="100%" />
</td>
</tr>
<tr>
<td class="info-title">历史变更记录</td>
</tr>
<tr>
<td>
<ul>
<li>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true,format="更新作者:%c",showPaths=true,changesFormat="[%a]",pathFormat=" %p"}
</li>
<li>
${CHANGES_SINCE_LAST_SUCCESS,reverse=true,format="更新信息:%c",showPaths=true,changesFormat="%m",pathFormat=" %p"}
</li>
</ul>
<!-- ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br />%m</pre>",pathFormat=" %p"} -->
<!-- <hr size="2" width="100%" /> -->
</td>
</tr>
<!-- <tr>
<td class="info-title">构建日志(最后100行)</td>
</tr>
<tr>
<td><p><pre>${BUILD_LOG, maxLines=100}</pre></p></td>
</tr> -->
</table>
</body>
</html>
我把着两个文件放在/jenkins 目录下,方便后续的更改或者复制调试啊
那模板设置好了,怎么用?接下来的代码告诉你
配置完模板,我们得再代码上设置一下
post {
changed{
echo 'I changed!'
}
failure{
echo 'I failed!'
configFileProvider([configFile(fileId: 'email-tmp-fail', targetLocation: 'email-fail.html', variable: 'content')]) {
script {
template = readFile encoding: 'UTF-8', file: "${content}"
emailext(
subject: "Job [${env.JOB_NAME}] - Status: fail",
body: """${template}""",
recipientProviders: [culprits(),requestor(),developers()],
to: "eric_longming@163.com",
)
}
}
}
success{
echo 'I success'
configFileProvider([configFile(fileId: 'email-tmp-success', targetLocation: 'email-success.html', variable: 'content')]) {
script {
template = readFile encoding: 'UTF-8', file: "${content}"
emailext(
subject: "Job [${env.JOB_NAME}] - Status: Success",
body: """${template}""",
recipientProviders: [requestor(),developers()],
to: "eric_longming@163.com",
)
}
}
}
always{
echo 'I always'
script{
if(fileExists("${resetFlagFile}")){
sh "rm -r ${resetFlagFile}" //回滚标志文件记得删
}
}
}
unstable{
echo "unstable"
}
aborted{
echo "aborted"
}
}
- **configFileProvider**:是我们安装插件的方法
- **fileId:** 是刚刚我们设置的邮箱模板文件的ID
- **targetLocation:** 是我们的模板文件编译后放的地方,这里是根目录的 emali-fail.html
- **variable:** 就是模板编译文件
- readFile 是pipeline 内置的读文件的方法
- **emailext:** 是插件 Email Extension的方法
- subject:String类型,邮件的主题。
- body:String类型,邮件的内容
- from:发件人邮箱(默认就是我们配置的)
- to:String类型,收件人
- recipientProviders:List类型,收件人列表类型
| 类型名称 | helper方法名 | 描述 |
| :--------: | :----------: | :----------------------------------------------------------: |
| Culprits | culprits() | 引发构建失败的人。最后一次构建成功和最后一次构建失败之间的变更提交者列表 |
| Developers | developers() | 此次构建所涉及的变更的所有提交者列表 |
| Requestor | requestor() | 请求构建的人,一般指手动触发构建的人 |
我来触发一个失败和成功的构建测试看看邮件通知的功能

当然也能配置钉钉和企业微信的通知,还有http自定义的通知等
## Verdaccio
作为一个积极上进的小开发团队,当然必须要有自己的npm私有库啊
这里我们选择了verdaccio,一个开源轻量的npm私服,他还有官方的docker镜像
### 1 docker 安装 Verdaccio
查看官方的verdaccio镜像源
docker search verdaccio
看到官方镜像源叫 verdaccio/verdaccio,我们下载下来
docker pull verdaccio/verdaccio
我们先在我们用户工作目录下,新建一个verdaccio目录
cd ~
mkdir verdaccio
cd verdaccio
mkdir conf
mkdir storage,
mkdir plugins
进入conf,创建verdaccio需要的配置文件
cd conf
vi config.yaml
config.yaml内容如下
This is the config file used for the docker images.
It allows all users to do anything, so don't use it on production systems.
Do not configure host and port under listen
in this file
as it will be ignored when using docker.
see https://github.com/verdaccio/...
Look here for more config file examples:
https://github.com/verdaccio/...
path to a directory with all packages
storage: /verdaccio/storage
auth:
htpasswd:
file: /verdaccio/conf/htpasswd
# Maximum amount of users allowed to register, defaults to "+inf".
# You can set this to -1 to disable registration.
#max_users: 1000
a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.npm.taobao.org
packages:
'@/':
# scoped packages
access: $all
publish: $all
proxy: npmjs
'**':
# 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: $all
# if package is not available locally, proxy requests to 'npmjs' registry
proxy: npmjs
To use npm audit
uncomment the following section
middlewares:
audit:
enabled: true
log settings
logs:
- {type: stdout, format: pretty, level: trace}
#- {type: file, path: verdaccio.log, level: info}
创建verdaccio容器应用
创建+运行verdaccio容器
docker run \
--name verdaccio \
-d \
-it \
-p 4873:4873 \
-v ~/verdaccio/storage:/verdaccio/storage \
-v ~/verdaccio/conf:/verdaccio/conf \
-v ~/verdaccio/plugins:/verdaccio/plugins \
-v /etc/localtime:/etc/localtime:ro \
verdaccio/verdaccio
- **docker run:** 创建一个新容器
- **--name verdaccio:** 将这个容器命名为jenkins(这个后面方便操作)
- **-d:**这个参数能让容器到后台运行
- **-it:** **-i** 以交互模式运行容器,**-t** 为容器重新分配一个伪输入终端
- **-p:**端口映射,格式为:**主机(宿主)端口:容器端口**
- **-v:**在这里是是挂载模式,规则是设置的宿主文件挂载到容器内部,容器内容不管有什么内容都会被替换成宿主挂载目录的文件
而且设置的挂载目录不能为空
- **-v /etc/localtime:/etc/localtime:ro :** 同步容器和主机的时间,ps:这玩意在mac上不好使
- **verdaccio/verdaccio:** 这个是**image**镜像文件,docker将以此镜像文件创建一个容器,如果本地不存在会自动拉去线上最新的版本
创建生成容器后我们打印容器创建的信息可以看到
查看容器运行日志
docker logs verdaccio
warn --- config file - /verdaccio/conf/config.yaml
warn --- Verdaccio started
...
...
...
warn --- Plugin successfully loaded: verdaccio-htpasswd
warn --- Plugin successfully loaded: verdaccio-audit
warn --- http address - http://0.0.0.0:4873/ - verdaccio/4.8.1
看到端口是4873,上面我们生成容器的时候,我们也是配置了4873的端口映射,去阿里云配置了添加4873端口的安全组规则后,我们打开网站 http://yourip:4873

### **2 配置和注册**
从刚才的log 可以看到docker的verdaccio容器的配置文件是指向 `/verdaccio/conf/config.yaml`
这个容器没有 /bin/bash,只有sh,我们进入容器看看配置文件
进入docker容器,用sh做交互
docker exec -it verdaccio /bin/sh
cd到配置目录下
cd /verdaccio/conf
查看默认的配置文件
cat config.yaml
详细的配置文件可以查看[官网的教程](https://verdaccio.org/docs/zh-CN/configuration)
1. 默认的储存目录:verdaccio默认使用内置本地文件模式存储
storage: /verdaccio/storage/data
2. 插件存放的目录:
plugins: /verdaccio/plugins
3. 认证 :默认的授权是基于`htpasswd` 并且是内置的。您可以通过[plugins](https://verdaccio.org/docs/zh-CN/plugins)来修改此行为
auth:
htpasswd:
file: /verdaccio/storage/htpasswd
#max_users : 10000
4. 上行链路`uplinks,这里我们做了几个源的添加
uplinks:
npmjs:
url: https://registry.npmjs.org/
yarn:
url: https://registry.yarnpkg.com/
cnmp:
url: http://r.cnpmjs.org/
taobao:
url: https://registry.npm.taobao.org/
5. 包的访问
packages:
'wild-fox-*':
access: $all
publish: $authenticated
unpublish: $authenticated
'**':
access: $all
publish: $authenticated
unpublish: $authenticated
proxy: taobao
- `wild-fox-*`: 这个前缀的包都能下载,只有认证的用户能发布,不设置上行链路
- `"**"`: 所有的包都能下载,只有认证的用户能发布,本地如果没有就到设置的上行链路 taobao 去获取
- `access` 表示哪一类用户可以对匹配的项目进行安装(install)
- `publish` 表示哪一类用户可以对匹配的项目进行发布(publish)
- `unpublish` 表示哪一类用户可以对匹配的项目进行下架(unpublish)
权限类型
- `$all` 表示所有人都可以执行对应的操作
- `$authenticated` 表示只有通过验证的人可以执行对应操作
- `$anonymous` 表示只有匿名者可以进行对应操作
6. 是否禁止用 npm adduser
如果是内网的话,一般就不需要,当然也随你,在认真的配置下面设置
auth:
htpasswd:
···
#禁止用户登陆
max_users: -1
7. 添加账户
- 没有禁止用户登录的话,直接用 adduser 就好
- verdaccio 的认证是基于 [verdaccio-htpasswd](https://www.npmjs.com/package/verdaccio-htpasswd),可以通过官方提供的工具来生成 [www.htaccesstools.com/htpasswd-ge…](http://www.htaccesstools.com/htpasswd-generator/),将生成的段字符串添加到 `htpasswd` 中即可。
### 3 使用
为了方便npm源的切换,我们本地安装`nrm`
npm install -g nrm
查看现在可以的源, *表示现在使用源
nrm ls
- npm -------- https://registry.npmjs.org/
yarn ------- https://registry.yarnpkg.com/
cnpm ------- http://r.cnpmjs.org/
taobao ----- https://registry.npm.taobao.org/
nj --------- https://registry.nodejitsu.com/
npmMirror -- https://skimdb.npmjs.com/regi...
edunpm ----- http://registry.enpmjs.org/
我们现在需要添加一个我们刚刚搭建的源库
nrm add <registry> <url>
nrm add verdaccio http://yourip:4873/
添加后我们要使用它
nrm use verdaccio
切换到我们的verdaccio后,我们先来登陆我们在这里注册的账号,我这里是 `eric`
在我的用户目录下新建一个文件夹
mkdir npm-vue-ci
初始化
npm init
可以添加一个README.md 和 .npmignore,.npmignore这个是可以设计忽略需要上传的文件
touch README.md
touch .npmignore
随便写点东西我们测试一下发布
npm publish
发布成功后我们可以在web界面上看到

还要测试一下退出登录后的下载和下架
退出登录
npm logout
不要在目录npm-vue-ci,去其他的目录下
npm install npm-vue-ci
没登录的状态下,这个曹操作必须是失败
npm unpublish
当然我们能用之前安装的nginx来做个反向代理,我们设置我们的verdaccio域名为 **npm.wild-fox.cn**,在步骤[pre-build](#2 pre-build 阶段)的脚本中
- npm config set registry https://registry.npm.taobao.org
- npm config set registry http://npm.wild-fox.cn
这里有个要注意的🤣,我mac本地自己搭的verdaccio没问题,可能是因为服务器的网络慢,所以代理私有服务器的下载的时候总会出现第一次安装失败,所以我们在[pre-build](#2 pre-build 阶段) 的安装阶段的npm i 都需要做点
- npm i || exit 1
- npm i || npm i || exit 1
## Mongo
### 1 docker 安装 mongo
查看源
docker search mongo
下载源
docker pull mongo
创建+运行verdaccio容器
docker run \
--name mongo \
-u mongo \
-d \
-it \
-p 27017:27017 \
-v mongodb:/data/db \
-v /etc/localtime:/etc/localtime:ro \
mongo --auth
### 2 新建用户和数据库
我们先进入数据库
进入admin
docker exec -it mongo mongo admin
创建admin用户数据库
因为新的mongo 默认没有admin数据库,需要自己造一个还有管理员用户
use admin
db.createUser({ user: 'admin', pwd: 'admin123456', roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] });
创建好后需要开启验证
db.auth("admin","admin123456");
因为这个数据库,现在安装是提供给yapi的,我们需要新建一个数据库和它的数据库管理员用户
use yapi
db.createUser({ user: 'eric', pwd: 'eric123456', roles: [ { role: "readWrite", db: "yapi" } ] });
开启验证,一次验证多个用户可能会失败,可以退出后再进来先验证admin,在重新验证普通用户
db.auth("eric","eric123456");
测试一下数据的写入
db.test.save({name:"eric"});
db.test.find();
我们mongo的镜像还有一个路径可以放这些初始化用户的脚本,后面在[docker-compose 接管](#docker-compose 接管])那会用这种方式
### 3 mongodb知识点
用户权限列表
Read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
root:只在admin数据库中可用。超级账号,超级权限
操作
查看系统用户列表.pretty() 用这个方法美化列表
db.system.users.find().pretty()
删除数据库。
db.dropDatabase()
## Yapi
这个镜像得我们来制作,这里我们只是做个最简单的套子,让大家知道怎么简单的用Dockerfile 制作镜像
这里主要是创建一个镜像,然后还需要自己进入容器运行可视化部署,这里先入个门嘛,后面在[docker-compose 接管](#docker-compose 接管]) 我们用稍微复杂点的Dockerfile 和一些脚本完成快速部署
### 用Dockerfile 制作镜像
我们先连在环境服务器的工作目录下新进一个文件存放 docker 的一些文件
连接环境服务器
ssh env
进入用户目录,创建存放我们的docker数据的目录
cd ~
mkdir docker-data
进入docker数据目录,创建yai目录,在其目录下新建Dockerfile文件
cd docker_data
mkdir yapi
cd yapi
vi Dockerfile
用 centos镜像做基础镜像当然是熟悉,容易入手,
在后面会**alpine** 做基础镜像来构建能有效减少镜像大小,不过步骤和方式会有些差异
我们创建的镜像基于centos镜像
FROM centos
镜像的作者
LABEL MAINTAINER="longming@451904906@qq.com"
设置环境变量,这里没有用到哈哈哈
ENV WELCOME="Welcome to yapi"
yapi需要安装git,和 node
RUN yum install -y git
RUN curl --silent --location https://rpm.nodesource.com/se... | bash -
RUN yum install -y nodejs
安装yapi
RUN npm install -g yapi-cli --registry https://registry.npm.taobao.org
现在我们用Dockerfile来创建我们的yapi镜像吧
要在~ /docker-data/yapi 目录哈
-t 指定镜像的名字和tag,. 表示在当前目录下需要Dockerfile
docker build -t="yapi/wild:latest" .
查看本地的镜像
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
yapi/wild latest 7198cf86d306 30 seconds ago 483MB
...
### 创建yapi的容器
docker run \
--name yapi \
-d \
-it \
-p 9090:9090 \
-p 3000:3000 \
-v yapi:/my-yapi \
-v /etc/localtime:/etc/localtime:ro \
yapi/wild
- **9090**端口是部署的界面
- **3000**端口是我们将在部署界面设置的应用启动端口
- **/my-yapi** 是我们将在部署界面设置的部署目录
我们用 服务器的 ip:9090可以看到(阿里云记得添加安全组)

等待安装部署

成功后可以看到哦这个网站默认的账号和密码,还有如何启动
> 初始化管理员账号成功,账号名:"eric-longming@163.com",密码:"ymfe.org"
进入我们的部署路径
cd /my-yapi
node vendors/server/app.js
这样我们用ip:3000访问就能看到

成功啦,接下来我们再做一下nginx的反向代理就好了
3 . 创建docker network 进行容器的通信
创建一个网络,默认是bridge类型的,这个也是docker网络中最常用的
docker network create my-net
将我们需要的mongo容器和yapi容器加入此网络
docker network connect my-net mongo
docker network connect my-net yapi
查看网络的详情
docker network inspect my-net
## Nginx
我们这里只做最简洁的安装使用教程,具体Nginx学习可以看这位大佬的文章 [**Nginx 从入门到实践,万字详解!**](https://juejin.im/post/6844904144235413512#heading-11) 好文啊,推荐
### 安装
这里我们先不用docker安装哈
- 先创建有root权限的用户 eric 。具体看 **[创建用户](#创建用户)**
- SSH配置 本地连接,方便本地连接操作嘛。具体看 [**SSH配置**](#SSH配置)
查看yum的nginx
yum list | grep nginx
yum安装
sudo yum install -y nginx
设置开机启动
sudo systemctl enable nginx
启动nginx
sudo systemctl start nginx
查看nginx启动状态
sudo systemctl status nginx
停止你的nginx服务器
sudo systemctl stop nginx
修改了nginx的配置的话需要重载一下配置
sudo systemctl reload nginx
配置文件在这个目录下
/etc/nginx/
/etc/nginx/nginx.conf
/etc/nginx/conf.d/
- 一般服务器需要修改一下防火墙,使外部能够访问,阿里的服务器则不用
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload
### 基本配置
vi /etc/nginx/nginx.conf
For more information on configuration, see:
* Official English Documentation: http://nginx.org/en/docs/
* Official Russian Documentation: http://nginx.org/ru/docs/
user nginx; # 运行用户,默认即是nginx,可以不进行设置
worker_processes auto; # Nginx 进程数,一般设置为和 CPU 核数一样
error_log /var/log/nginx/error.log; # Nginx 的错误日志存放目录
pid /run/nginx.pid; # Nginx 服务启动时的 pid 存放位置
Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
配置影响 Nginx 服务器或与用户的网络连接
events {
worker_connections 1024; # 每个进程允许最大并发数
}
配置使用最频繁的部分,代理、缓存、日志定义等绝大多数功能和第三方模块的配置都在这里设置
http {
# 设置日志模式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main; # Nginx访问日志存放位置
sendfile on; # 开启高效传输模式
tcp_nopush on; # 减少网络报文段的数量
tcp_nodelay on;
keepalive_timeout 65; # 保持连接的时间,也叫超时时间,单位秒
types_hash_max_size 2048;
include /etc/nginx/mime.types; # 文件扩展名与类型映射表
default_type application/octet-stream; # 默认文件类型
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf; # 加载子配置项,我会把很多设置都放这里
# 配置虚拟主机的相关参数,一个 http 块中可以有多个 server 块,一个server模块可以有多个location
# 我们将多个server放在/etc/nginx/conf.d/*.conf
# 下面的server注释掉是因为我将吧server单独做成配置文件 include 进来
# server {
# listen 80;
# server_name www.wild-fox.cn; # 这个是购买的域名
# root /data/vueci/master/dist;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
}
我们先就把所有环境都设置到 `/etc/nginx/conf.d/*.conf`
新建配置文件 :
- **gzip.conf:**这个是用来开启服务器 gzip 功能的配置
- **dev.wild-fox.cn.conf:** 这个是用来配置dev 开发环境的
- **release.wild-fox.cn.conf:** 这个是用来配置release 测试环境的
- **master.wild-fox.cn.conf:** 这个是用来配置master 线上生产环境的
gzip.conf
gzip on;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_static on;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_min_length 1k;
gzip_http_version 1.1;
dev.wild-fox.cn.conf
server {
listen 80;
server_name dev.wild-fox.cn;
root /data/vueci/dev/dist;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
release.wild-fox.cn.conf
server {
listen 80;
server_name release.wild-fox.cn;
root /data/vueci/release/dist;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
master.wild-fox.cn.conf
server {
listen 80;
server_name www.wild-fox.cn;
root /data/vueci/master/dist;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
配置好后我们要重新reload一次
sudo systemctl reload nginx
设置好了,我们还得去做一下域名解析才能范围

解析好了我们把构建后的代码放到相应的目录就能范围了
## Docker Compose 接管
### 简介
前面我们一共用了jenkins、verdaccio、mongo、yapi等容器,可能后面会再加几个,例如gitlab、nexus啊等等,一两个还好,如果我们要换服务器,还需要一个一个安装的话那就有点麻烦了,所以我们要借助 **docker-compose**,几个命令就能在新的服务器上快速部署之前所有应用容器
> `Compose` 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」
**我们先把之前的jenkins、verdaccio、mongo、yapi的容器全选删了,之前容器挂载的数据卷就保留着 jenkins的吧**
我们先进入我们的环境服务器,拉去docker-compose的代码,启动服务
ssh env
pwd
把env-docker-compose的项目拉去下来
git clone git@gitee.com:eric-gm/env-docker-compose.git
目录结构是这样
├── docker-compose.yml
├── mongo
│ └── initdb.js
├── verdaccio
│ ├── config.yaml
│ └── Dockerfile
└── yapi
├── config.json
├── Dockerfile
├── entrypoint.sh
└── wait-for-it.sh
进入项目,docker-compose启动服务
cd env-docker-compose
docker-compose up -d
这样就启动和部署了之前的jenkins、verdaccio、mongo 和yapi来
### 安装
1. 安装额外依赖包
sudo yum install -y epel-release
2. 安装 python-pip
centos7的话,yum的源有这个
sudo yum install -y python-pip
centos8,yum的源没有pip,我们自己安装
sudo dnf install python3
sudo pip3 install --upgrade pip
3. 安装 Docker Compose
sudo pip install docker-compose
4. 升级 python 包
sudo yum upgrade python*
5. 验证安装
docker-compose -v
6. 如果需要卸载的话
sudo pip uninstall docker-compose
### 定义和运行容器
那具体是怎样定义容器创建部署来着,我们先介绍一下
先介绍一下docker-compose.yml
> 它允许用户通过一个单独的 `docker-compose.yml` 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
`Compose` 中有两个重要的概念:
- 服务 (`service`):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
- 项目 (`project`):由一组关联的应用容器组成的一个完整业务单元,在 `docker-compose.yml` 文件中定义。
具体来看一下我们的docker-compose.yaml文件
version: "3.8"
services:
jenkins:
image: jenkinsci/blueocean
container_name: jenkins
ports:
- 8080:8080
- 50000:50000
volumes:
- jenkins:/var/jenkins_home
- /private/etc/localtime:/etc/localtime:ro
#- /private/etc/localtime:/etc/localtime:ro
networks:
- front-net
verdaccio:
build: ./verdaccio/
image: verdaccio/wild:latest
container_name: verdaccio
ports:
- 4873:4873
volumes:
- verdaccio:/verdaccio
# - /private/etc/localtime:/etc/localtime:ro
networks:
- front-net
yapi:
build: ./yapi/
image: yapi/wild:latest
container_name: yapi
restart: always
ports:
- 9090:9090
- 3000:3000
depends_on:
- mongo
volumes:
- yapi:/my-yapi
# - /private/etc/localtime:/etc/localtime:ro
entrypoint: "bash /wait-for-it.sh mongo:27017 -- entrypoint.sh"
networks:
- back-net
mongo:
image: mongo:latest
container_name: mongo
restart: always
environment:
# 在这里输入 MongoDB 的 root 用户与密码,如果使用了此项,则不需要 --auth 参数
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root123456
ports:
- 27017:27017
volumes:
- mongodb:/data/db
- ./mongo/:/docker-entrypoint-initdb.d/:ro
# - /private/etc/localtime:/etc/localtime:ro
command: mongod
networks:
- back-net
volumes:
jenkins:
name: jenkins
verdaccio:
name: verdaccio
mongodb:
name: mongodb
yapi:
name: yapi
networks:
back-net:
front-net:
- **version:** 这个是对应 **Docker Engine** 的 ,我们的docker是 `Docker version 19.03.12`,所以对应的3.8
- **services:** 这个就是对应我们的服务,这里定义了这几个服务jenkins、verdaccio、mongo、yapi等,这个是我们重点要看的
### services 配置解析
因为篇幅不想太长,这里之说配置文件上涉及到的配置哈,更具体的可以查看这一篇文章 [docker-compose 编排指南 (v3.8)](https://juejin.im/post/6854573209799360519#heading-14)
#### 1 build
该选项被用于构建,yapi 和 verdaccio 两个服务我们只做根据dockerfile 制作的镜像,就需要用这个,jenkins 和 mongo服务是采用网上的镜像就不需要
build,**指定上下文的目录目录下的 dockerfile文件来构建** 当然你也可以自己指定上下文,和 dockerfile的别名等等等
这里的yapi 和verdaccio镜像的构建跟之前的不一样哈,
- yapi镜像:是通过代码构建自动部署的(之前是可视化部署)具体可以看 **./yapi/Dockerfile**
- verdaccio是直接把配置文件拷贝到容器内部去初始创建的
#### 2 image
build的镜像指定的名字,或者仓库的镜像名字(本地没有会自动去线上拉取的)
#### 3 container_name
这个就是容器的名字啦
#### 4 ports
暴露端口到宿主机,格式 HOST:CONTAINER
#### 5 environment
添加环境变量。可以使用一个数组或者一个字典。任何布尔量:true, false, yes, no 等等都必须用引号包围为字符串字面量,也会覆盖dockerfile中的ENV
特别注意:
在mongo服务里面这样设置,就会创建一个root管理员用户,会自动开启了验证
environment:
# 在这里输入 MongoDB 的 root 用户与密码,如果使用了此项,则不需要 --auth 参数
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root123456
#### 5 volumes
挂载宿主机路径或者命名卷,在顶级 `volumes` 中定义一个命名卷,并在一个服务的 `volumes` 列表中引用它。
特别注意一下:在mongo服务的volumes这里
./mongo/:/docker-entrypoint-initdb.d/:ro
我们是吧 ./mongo的 initdb.js 挂载到 mongo容器的/docker-entrypoint-initdb.d 目录(是官方的容器提供的哈),容器初始化的时候调用里面的文件去初始化数据库,根据文件名的排序前后调用运行,支持 .sh 和 js,相比之前的进入容器去在创建用户数据库什么的方便多了吧
#### 6 command
覆盖容器内默认的 `command`
#### 7 entrypoint
覆盖 dockerfile 中定义的默认的 entrypoint 值
在yapi服务中的配置
entrypoint: "bash /wait-for-it.sh mongo:27017 -- entrypoint.sh"
wait-for-it.sh 是github上开源的,因为我们这个yapi需要连接mongodb,所以我们要检测到 端口 mongo:27017 启动能连接了,在执行
entrypoit.sh 去部署或启动yapi(初次是部署,接下来是启动)
#### 8 depends_on
表示服务之间的依赖关系。服务依赖引发如下的行为:
比如我们的yapi 依赖 mongo服务,当我们用docker-compose 启动yapi的时候会先启动mongo服务,在启动yapi,关闭的时候先关闭yapi,在关闭mongo,注意启动只是容器的启动,并不是容器内应用和数据的启动,所以我们需要用到 **wait-for-it.sh** 来辅助
#### 9 network
要加入的网络。目标网络是在 `docker-compose.yml` 顶级的 `networks` 项中定义的。
#### 10 restart
`no` 是默认的重启策略。此时无论容器怎么退出、怎么失败也不会被自动重启。
指定 `always` 时任何情况下容器都会被重启。
### volumes
这个是用来定义数据卷给 服务services用的,如果同名的volume存在,就只是使用它,不存在就创建,
但如果只是这样定义:
volumes:
jenkins:
你会发现生成的volumes名字是 env-docker-compose_jenkins,就想我们docker-compose.yaml的所在的目录名+ "_"+jenkins,
这里我们用 name 来命名它为:jenkins,和我们最开始创建jenkins容器使用的volume同名,会自动去引用这个卷,这样就不用重新去安装插件配置什么的啦,当然你想折腾的话随你
volumes:
jenkins:
name: jenkins
### network
顶级章节 `networks` 使得你可以配置想要创建和使用的网络(Compose内网)
我们默认创建的网络是bridge类型的,这个也是docker 容器最常用的网络类型
它生产的名字跟volume一样,不过我们这里不需要去自定义它的名字,当然你想也行啊老板
networks:
back-net:
front-net:
例如我们在yapi 和 mongo 两个服务都加入了通个网络 front-net,那我们yapi容器直接可以通过 ping mongo(服务名)连接到 mongo的容器,mongo容器内 ping yapi 也行
你可以注意到 在yapi服务中,我们就直接用了 **mongo:27017**去连接检测 mongo服务端口的启动,而不是 ip:27017
entrypoint: "bash /wait-for-it.sh mongo:27017 -- entrypoint.sh"
为什么不用ip呢,因为容器分配到的ip不是固定的,会变的。
### compose 基本命令
这里只做用到的一点基础命令,其他的可以看官网的说明
#### 1 up
格式:`docker-compose up -d` ,加`-d`是后台运行
会根据当前目录下的docker-compose.yaml,构建镜像,创建服务
#### 2 down
格式:docker-compose down
此命令将会停止 `up` 命令所启动的容器,并移除网络
#### 3 start
格式为 `docker-compose start [SERVICE...]`
启动已经存在的服务容器。
#### 4 stop
格式为 `docker-compose stop [options] [SERVICE...]`
停止已经处于运行状态的容器,但不删除它。通过 `docker-compose start` 可以再次启动这些容器。
选项:
- `-t, --timeout TIMEOUT` 停止容器时候的超时(默认为 10 秒)。
#### 5 restart
格式为 `docker-compose restart [options] [SERVICE...]`。
重启项目中的服务。
选项:
- `-t, --timeout TIMEOUT` 指定重启前停止容器的超时(默认为 10 秒)。
#### 6 logs
格式为 `docker-compose logs [options] [SERVICE...]`。
查看服务容器的输出。默认情况下,docker-compose 将对不同的服务输出使用不同的颜色来区分。可以通过 `--no-color` 来关闭颜色。
该命令在调试问题的时候十分有用。
## volume 备份 和 迁移
那么问题来了,我们要去其他的服务器部署这一套服务,有了docker-compose 可以快速部署应用,那之前的数据呢?怎么搞过来???
别慌,几步就能搞定
1. 我们的数据都存在docker 管理的volume对吧,我们备份和回复需要这几步
2. 把所有的volume打包好搞到新服务器
3. 在新的服务器,创建同名的volumes,把打包的数据解压后放对应的volume的目录就好了
查看docker 存储的volume都在哪
查看现存有的volume 执行 docker volume inspect yapi
docker volume inspect yapi
...
Mountpoint": "/var/lib/docker/volumes/yapi/_data",
...
我们发现docker 管理的 volumes都在 `/var/lib/volumes` 目录下,还想什么呢,打包他啊
docker run --rm -it \
-v ~/volume-backup:/backup \
-v /var/lib/docker:/docker \
busybox \
tar cfz /backup/volume.tgz -C /docker/volumes/ .
上面这个命令中,我们运行了一个轻量级的container——busybox,并挂载了两个目录:
- (host) ~/volume-backup => (container) /backup
- (host) /var/lib/docker/ => (container) /docker
这样就是把 `/var/lib/volumes`目录打包成 **volume.tgz**
新服务器下载打包后的 **volume.tgz**
cd ~
mkdir docker
cd docker
mkdir volumes
scp env:~/volume-backup/volume.tgz ~/docker/volumes
下载好解压缩
cd ~/docker/volumes
tar zxvf volume.tgz
解压后可能会有多余的东西,我们只需要 下面这几个文件夹的文件,移动我们归档到其他文件夹吧
jenkins mongodb verdaccio yapi
先运行`docker-compose up -d` ,这样在新的服务器会构建镜像和创建之前的volumes初始化数据卷
再运行 `docker-compose down` 或者 `docker-compose stop`
,在导入数据到相应的volumes中
docker run --rm -it \
-v /var/lib/docker:/docker \
-v ~/volume-backup:/volume-backup \
busybox \
cp -r /volume-backup/ /docker/volumes
这样就把数据还原啦,最后再启动`docker-compose up` 或者 `docker-compose restart`
## 附录
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。