开篇,我们先来看一下远古时代的构建部署流程。想必大家对这个都不陌生:
开发将源码经过编译、压缩打包生成打包文件
将打包生成的文件上传服务器
显然这个流程不仅繁琐,而且效率也不高,开发每次发布都要耗费很长的时间在部署构建上面。
后面为了解决这个问题,就出现了CI/CD
。
CI/CD
是 Continuous Intergration/Continuous Deploy
的简称,翻译过来就是持续集成/持续部署
。CD
也会被解释为持续交付(Continuous Delivery
)
再具体一点就是:
持续集成
:当代码仓库代码发生变更,就会自动对代码进行测试和构建,反馈运行结果。
持续交付
:持续交付是在持续集成的基础上,可以将集成后的代码依次部署到测试环境、预发布环境、生产环境中
聊了这么多,相信很多同学一定会说:
这一般不都是运维搞的吗?
和业务也不相关啊,了解它有什么用?
全是服务器相关的东西,docker
、nginx
、云服务器啥的,我该怎么学习呢?
很早之前,我也是这么想的,感觉与自己的业务也没啥关系,没有太大的必要去了解。
但是最近我在搞一个全栈项目
(做这个项目是为了突破自己的瓶颈)时,就遇到了这些问题,发现陷入了知识盲区。
没办法,只能一顿恶补。
但是当我通过学习这些知识和在项目中实践这些流程后,我在知识面上得到了很大的扩展。对操作系统,对实际的构建部署,甚至对工程化拥有了全新的认识。
这个大的项目以
low code
为核心,囊括了编辑器前端
、编辑器后端
、C端H5
、组件库
、组件平台
、后台管理系统前端
、后台管理系统后台
、统计服务
、自研CLI
九大系统。
其中的编辑器前端
在如何设计实现 H5 营销页面搭建系统文章中已经有很详细的说明。
目前整个项目做了 70%左右,过程中遇到了很多问题,也得到了很大的提升。后续会有一波文章是关于项目中的一个个小点展开的,也都是满满的干货。
回到本篇文章的主题:使用Docker Compose、Nginx、SSH和Github Actions实现前端自动化部署测试机
。本文是以后台管理系统前端
为依托详细说明了如何借助Docker
、nginx
、Github CI/CD
能力自动化发布一个纯前端项目
。选这个项目来讲解自动化发布测试机有两个出发点:
后台管理系统业务较简单,可将重心放在自动化部署流程上
纯前端项目更适用于大部分前端同学现状,拿去即用
前端代码,打包出来的是静态文件,可用nginx
做服务。思路:
构建一个Docker
容器(有nginx
)
将dist/
目录拷贝到Docker
容器中
启动nginx
服务
宿主机端口,对应到Docker
容器端口中,即可访问
核心代码变动:
nginx.conf
(给Docker
容器的nginx
使用)
Dockerfile
docker-compose.yml
⚠️ 本文将采用理论知识和实际相结合的方式,即先讲述一下对应知识点,同时会放一下与此知识点相关的项目代码或配置文件。
下面会依次讲解Docker
、docker-compose
、ssh
、github actions
等知识点。
Docker
Docker
很早之前,在公众号的一篇文章谁说前端不需要学习 docker?就有过详细说明。这里简单再阐述下。
docker
可以看成是一个高性能的虚拟机,主要用于 linux
环境的虚拟化。开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 linux
机器上。容器完全使用沙箱机制,相互之间不会有任何接口。
在容器中你可以做任何服务器可以做的事,例如在有 node
环境的容器中运行 npm run build
打包项目,在有 nginx
环境的容器中部署项目等等。
centos
上安装 docker
由于这次的云服务器是centos
的,所以这里就提一下如何在 centos
上安装 docker
:
$ sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install docker-ce docker-ce-cli containerd.io
$ sudo systemctl start docker
$ sudo docker run hello-world
dockerfile
docker
使用 Dockerfile
作为配置文件进行镜像的构建,简单看一个 node
应用构建的 dockerfile
:
FROM node:12.10.0
WORKDIR /usr/app
COPY package*.json ./
RUN npm ci -qy
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
说明一下每个关键字对应的含义。
FROM
基于这个 Image
开始
WORKDIR
设置工作目录
COPY
复制文件
RUN
新层中执行命令
EXPOSE
声明容器监听端口
CMD
容器启动时执行指令默认值
看下项目中的Dockerfile
文件:
# Dockerfile
FROM nginx
# 将 dist 文件中的内容复制到 /usr/share/nginx/html/ 这个目录下面
# 所以,之前必须执行 npm run build 来打包出 dist 目录,重要!!!
COPY dist/ /usr/share/nginx/html/
# 拷贝 nginx 配置文件
COPY nginx.conf /etc/nginx/nginx.conf
# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
# 创建 /admin-fe-access.log ,对应到 nginx.conf
CMD touch /admin-fe-access.log && nginx && tail -f /admin-fe-access.log
在这个文件里面,我们做了下面几件事:
1、我们用了 Nginx
的 Docker image
作为 base image
。
2、把打包生成的文件夹dist/
的全部内容放进 Nginx Docke
r 的默认 HTML
文件夹,也就是/usr/share/nginx/html
/里面。
3、把自定义的 Nginx
配置文件nginx.conf
放进 Nginx Docker
的配置文件夹/etc/nginx/nginx.conf
中。
4、设置时区。
5、创建 /admin-fe-access.log
,启动nginx
并使用tail -f
模拟类似pm2
的阻塞式进程。
这里提到了nginx.conf
文件:
#nginx进程数,通常设置成和cpu的数量相等
worker_processes auto;
#全局错误日志定义类型
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#进程pid文件
#pid logs/nginx.pid;
#参考事件模型
events {
#单个进程最大连接数(最大连接数=连接数+进程数)
worker_connections 1024;
}
#设定http服务器
http {
#文件扩展名与文件类型映射表
include mime.types;
#默认文件类型
default_type application/octet-stream;
#日志格式设定
#$remote_addr与 $http_x_forwarded_for用以记录客户端的ip地址;
#$remote_user:用来记录客户端用户名称;
#$time_local: 用来记录访问时间与时区;
#$request: 用来记录请求的url与http协议;
#$status: 用来记录请求状态;成功是200,
#$body_bytes_sent :记录发送给客户端文件主体内容大小;
#$http_referer:用来记录从那个页面链接访问过来的;
#$http_user_agent:记录客户浏览器的相关信息;
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 logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
#长连接超时时间,单位是秒
keepalive_timeout 65;
#gzip on;
#设定通过nginx上传文件的大小
client_max_body_size 20m;
#虚拟主机的配置
server {
#监听端口
listen 80;
#域名可以有多个,用空格隔开
server_name admin-fe;
#charset koi8-r;
#定义本虚拟主机的访问日志
access_log /admin-fe-access.log main; # 注意,在 Dockerfile 中创建 /admin-fe-access.log
#入口文件的设置
location / {
root /usr/share/nginx/html; #入口文件的所在目录
index index.html index.htm; #默认入口文件名称
try_files $uri $uri/ /index.html;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
核心点就是监听80
端口,定义日志文件为admin-fe-access.log
,入口文件根目录为/usr/share/nginx/html
,这些都是与Dockerfile
中一一对应的。
说完了Dockerfile
及其相关的配置文件,下面接着来看下docker
中几个核心的概念。
docker
中有三个非常重要的概念:
镜像(image)
容器(container)
仓库(repository)
如果把容器比作轻量的服务器,那么镜像就是创建它的模版,一个 docker镜像
可以创建多个容器
,它们的关系好比 JavaScript
中类
和实例
的关系。
下载镜像:docker pull
查看所有镜像:docker images
删除镜像:docker rmi
上传镜像:docker push
如果
docker images
出现repository
是的情况,可以运行
docker image prune
删除
启动容器:docker run -p xxx:xxx -v=hostPath:containerPath -d --name
-p 端口映射
-v 数据卷,文件映射
-d 后台运行
--name 定义容器名称
查看所有容器:docker ps
(加-a
显示隐藏的容器)
停止容器:docker stop
删除容器:docker rm
(加-f
强制删除)
查看容器信息(如 IP 地址等):docker inspect
查看容器日志:docker logs
进入容器控制台:docker exec -it
镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry
就是这样的服务。
一个 Docker Registry
中可以包含多个仓库(Repository
);每个仓库可以包含多个标签(Tag
);每个标签对应一个镜像。所以说:镜像仓库是 Docker
用来集中存放镜像文件的地方,类似于我们之前常用的代码仓库。
docker-compose
docker-compose
项目是Docker
官方的开源项目,负责实现对Docker
容器集群的快速编排。允许用户通过一个单独的docker-compose.yml
模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project
)。
使用 compose
的最大优点是你只需在一个文件中定义自己的应用程序栈(即应用程序需要用到的所有服务),然后把这个 YAML
文件放在项目的根目录下,与源码一起受版本控制。其他人只需 clone
你的项目源码之后就可以快速启动服务。
通常适用于项目所需运行环境(对应多个docker
容器)较多的场景,例如同时依赖于nodejs
、mysql
、mongodb
、redis
等。
这里放下docker-compose.yml
文件:
version: '3'
services:
admin-fe:
build:
context: .
dockerfile: Dockerfile
image: admin-fe # 引用官网 nginx 镜像
container_name: admin-fe
ports:
- 8085:80 # 宿主机可以用 127.0.0.1:8085 即可连接容器中的数据库
基于上文的Dockerfile
创建镜像,端口映射是8085:80
,这里的8085
是宿主机端口,80
对应的是nginx
暴露的 80 端口
构建容器:docker-compose build
启动所有服务器:docker-compose up -d
(后台启动)
停止所有服务:docker-compose down
查看服务:docker-compose ps
首先说下云服务器,既然要一键部署测试机,那么肯定要有台测试机,也就是云服务器,这里我用的是阿里云CentOS 8.4 64位
的操作系统。
有了服务器,那怎么登陆呢?
本地登陆云服务器的方式一般有两种,密码登陆和 ssh
登陆。但是如果采用密码登陆的话,每次都要输入密码,比较麻烦。这里采用ssh
登陆的方式。关于如何免密登录远程服务器,可以参考SSH 免密登陆配置[1]
此后每次登陆都可以通过ssh
的方式直接免密登陆了。
接着要给云服务器安装基础包,在CentOS
安装指定包一般用的是yum
,这个不同于npm
。
# Step 1: 卸载旧版本
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
# Step 2: 安装必要的一些系统工具
sudo yum install -y yum-utils
# Step 3: 添加软件源信息,使用阿里云镜像
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# Step 4: 安装 docker-ce
sudo yum install docker-ce docker-ce-cli containerd.io
# Step 5: 开启 docker服务
sudo systemctl start docker
# Step 6: 运行 hello-world 项目
sudo docker run hello-world
如果你像我一样,有Hello from Docker!
的话那么Docker
就安装成功了!
通过访问 https://github.com/docker/compose/releases/latest
得到最新的 docker-compose
版本(例如:1.27.4
),然后执行一下命令安装 docker-compose
# 下载最新版本的 docker-compose 到 /usr/bin 目录下
curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose
# 给 docker-compose 授权
chmod +x /usr/bin/docker-compose
安装完,命令行输入docker-compose version
来验证是否安装成功:
首先确保可以访问到EPEL库
,通过运行以下命令来安装:
sudo yum install epel-release
现在可以使用yum
命令安装Node.js
了:
sudo yum install nodejs
yum
安装 nginx
非常简单,输入一条命令即可:
$ sudo yum -y install nginx # 安装 nginx
同样也是使用yum
来安装:
yum install git
最后来看一下github actions
,也是串联起了上面提到的各个点。
github actions
大家知道,持续集成由很多操作组成,比如拉取代码、执行测试用例、登录远程服务器,发布到第三方服务等等。GitHub
把这些操作就称为 actions
。
我们先来了解一下一些术语
:
workflow
(工作流程):持续集成一次运行的过程,就是一个 workflow。
job
(任务):一个 workflow 由一个或多个 jobs 构成,含义是一次持续集成的运行,可以完成多个任务。
step
(步骤):每个 job 由多个 step 构成,一步步完成。
action
(动作):每个 step 可以依次执行一个或多个命令(action)。
workflow
文件GitHub Actions
的配置文件叫做 workflow
文件,存放在代码仓库的.github/workflows
目录。
workflow
文件采用YAML
格式,文件名可以任意取,但是后缀名统一为.yml
,比如deploy.yml
。一个库可以有多个workflow
文件。GitHub
只要发现.github/workflows
目录里面有.yml
文件,就会自动运行该文件。
workflow
文件的配置字段非常多,这里列举一些基本字段。
name
name
字段是 workflow
的名称。
如果省略该字段,默认为当前
workflow
的文件名。
name: deploy for feature_dev
on
on
字段指定触发 workflow
的条件,通常是push
、pull_request
。
指定触发事件时,可以限定分支或标签。
on:
push:
branches:
- master
上面代码指定,只有master
分支发生push
事件时,才会触发 workflow
。
jobs
jobs
字段,表示要执行的一项或多项任务。其中的runs-on
字段指定运行所需要的虚拟机环境。
runs-on: ubuntu-latest
steps
steps
字段指定每个 Job
的运行步骤,可以包含一个或多个步骤。每个步骤都可以指定以下三个字段。
jobs.
:步骤名称。
jobs.
:该步骤运行的命令或者 action。
jobs.
:该步骤所需的环境变量。
下面放一下项目中的.github/workflows/deploy-dev.yml
文件:
name: deploy for feature_dev
on:
push:
branches:
- 'feature_dev'
paths:
- '.github/workflows/*'
- '__test__/**'
- 'src/**'
- 'config/*'
- 'Dockerfile'
- 'docker-compose.yml'
- 'nginx.conf'
jobs:
deploy-dev:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 14
- name: lint and test # 测试
run: |
npm i
npm run lint
npm run test:local
- name: set ssh key # 临时设置 ssh key
run: |
mkdir -p ~/.ssh/
echo "${{secrets.COSEN_ID_RSA}}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan "106.xx.xx.xx" >> ~/.ssh/known_hosts
- name: deploy
run: |
ssh [email protected] "
cd /home/work/choba-lego/admin-fe;
git remote add origin https://Cosen95:${{secrets.COSEN_TOKEN}}@github.com/Choba-lego/admin-fe.git;
git checkout feature_dev;
git config pull.rebase false;
git pull origin feature_dev;
git remote remove origin;
# 构建 prd-dev
# npm i;
# npm run build-dev;
# 启动 docker
docker-compose build admin-fe; # 和 docker-compose.yml service 名字一致
docker-compose up -d;
"
- name: delete ssh key
run: rm -rf ~/.ssh/id_rsa
这里概述一下:
1️⃣ 整个流程在代码push
到feature_dev
分支时触发。
2️⃣ 只有一个job
,运行在虚拟机环境ubuntu-latest
。
3️⃣ 第一步使用的是最基础的action
,即actions/checkout@v2
,它的作用就是让我们的workflow
可以访问到我们的repo
。
4️⃣ 第二步是在执行工作流的机器中安装node
,这里使用的action
是actions/setup-node@v1
。
5️⃣ 第三步是执行lint
和test
。
6️⃣ 第四步是临时设置 ssh key
,这也是为了下一步登录服务器做准备。
7️⃣ 第五步是部署,这里面先是ssh
登录服务器,拉取了最新分支代码,然后安装依赖、打包,最后启动docker
,生成镜像。到这里测试机上就有了Docker
服务。
8️⃣ 最后一步是删除ssh key
。
洋洋洒洒写了这么多,也不知道你看明白了不
如果有任何问题,欢迎评论区留言,看到后会第一时间解答
后续会有很多关于这个项目的文章,也请大家多多关注~
[1]
SSH 免密登陆配置: https://segmentfault.com/a/1190000021000360