3

使用Docker Compose、Nginx、SSH和Github Actions实现前端自动化部署测试机

 2 years ago
source link: https://segmentfault.com/a/1190000040929112
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

使用Docker Compose、Nginx、SSH和Github Actions实现前端自动化部署测试机

开篇,我们先来看一下远古时代的构建部署流程。想必大家对这个都不陌生:

  • 开发将源码经过编译、压缩打包生成打包文件
  • 将打包生成的文件上传服务器

显然这个流程不仅繁琐,而且效率也不高,开发每次发布都要耗费很长的时间在部署构建上面。

后面为了解决这个问题,就出现了CI/CD

接下来我们来聊一下什么是CI/CD?

CI/CDContinuous Intergration/Continuous Deploy 的简称,翻译过来就是持续集成/持续部署CD 也会被解释为持续交付(Continuous Delivery

再具体一点就是:

  • 持续集成:当代码仓库代码发生变更,就会自动对代码进行测试和构建,反馈运行结果。
  • 持续交付:持续交付是在持续集成的基础上,可以将集成后的代码依次部署到测试环境、预发布环境、生产环境中

聊了这么多,相信很多同学一定会说:

  • 这一般不都是运维搞的吗?
  • 和业务也不相关啊,了解它有什么用?
  • 全是服务器相关的东西,dockernginx、云服务器啥的,我该怎么学习呢?

很早之前,我也是这么想的,感觉与自己的业务也没啥关系,没有太大的必要去了解。

但是最近我在搞一个全栈项目(做这个项目是为了突破自己的瓶颈)时,就遇到了这些问题,发现陷入了知识盲区。

没办法,只能一顿恶补。

但是当我通过学习这些知识和在项目中实践这些流程后,我在知识面上得到了很大的扩展。对操作系统,对实际的构建部署,甚至对工程化拥有了全新的认识。

这里也放下前面提到的全栈项目的架构图吧:

这个大的项目以low code为核心,囊括了编辑器前端编辑器后端C端H5组件库组件平台后台管理系统前端后台管理系统后台统计服务自研CLI九大系统。

其中的编辑器前端如何设计实现 H5 营销页面搭建系统文章中已经有很详细的说明。

目前整个项目做了 70%左右,过程中遇到了很多问题,也得到了很大的提升。后续会有一波文章是关于项目中的一个个小点展开的,也都是满满的干货。

回到本篇文章的主题:使用Docker Compose、Nginx、SSH和Github Actions实现前端自动化部署测试机。本文是以后台管理系统前端为依托详细说明了如何借助DockernginxGithub CI/CD能力自动化发布一个纯前端项目。选这个项目来讲解自动化发布测试机有两个出发点:

  • 后台管理系统业务较简单,可将重心放在自动化部署流程上
  • 纯前端项目更适用于大部分前端同学现状,拿去即用

前端代码,打包出来的是静态文件,可用nginx做服务。思路:

  • 构建一个Docker容器(有nginx
  • dist/目录拷贝到Docker容器中
  • 启动nginx服务
  • 宿主机端口,对应到Docker容器端口中,即可访问

核心代码变动:

  • nginx.conf(给Docker容器的nginx使用)
  • Dockerfile
  • docker-compose.yml

⚠️ 本文将采用理论知识和实际相结合的方式,即先讲述一下对应知识点,同时会放一下与此知识点相关的项目代码或配置文件。

下面会依次讲解Dockerdocker-composesshgithub 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、我们用了 NginxDocker image 作为 base image

2、把打包生成的文件夹dist/的全部内容放进 Nginx Docker 的默认 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 核心概念

docker中有三个非常重要的概念:

  • 镜像(image)
  • 容器(container)
  • 仓库(repository)

一张图来表明其中的关系:

如果把容器比作轻量的服务器,那么镜像就是创建它的模版,一个 docker镜像可以创建多个容器,它们的关系好比 JavaScript实例的关系。

镜像(image)常用命令:

  • 下载镜像:docker pull <image-name>:<tag>
  • 查看所有镜像:docker images
  • 删除镜像:docker rmi <image-id>
  • 上传镜像:docker push <username>/<repository>:<tag>

如果docker images出现repository<none>的情况,可以运行docker image prune删除

容器(container)常用命令

  • 启动容器:docker run -p xxx:xxx -v=hostPath:containerPath -d --name <container-name> <image-name>

    • -p 端口映射
    • -v 数据卷,文件映射
    • -d 后台运行
    • --name 定义容器名称
  • 查看所有容器:docker ps(加-a显示隐藏的容器)
  • 停止容器:docker stop <container-id>
  • 删除容器:docker rm <container-id>(加-f强制删除)
  • 查看容器信息(如 IP 地址等):docker inspect <container-id>
  • 查看容器日志:docker logs <container-id>
  • 进入容器控制台:docker exec -it <container-id> /bin/sh

镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。

一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。所以说:镜像仓库是 Docker 用来集中存放镜像文件的地方,类似于我们之前常用的代码仓库。

docker-compose

docker-compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排。允许用户通过一个单独的docker-compose.yml模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

使用 compose 的最大优点是你只需在一个文件中定义自己的应用程序栈(即应用程序需要用到的所有服务),然后把这个 YAML 文件放在项目的根目录下,与源码一起受版本控制。其他人只需 clone 你的项目源码之后就可以快速启动服务。

通常适用于项目所需运行环境(对应多个docker容器)较多的场景,例如同时依赖于nodejsmysqlmongodbredis等。

这里放下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 <service-name>
  • 启动所有服务器:docker-compose up -d(后台启动)
  • 停止所有服务:docker-compose down
  • 查看服务:docker-compose ps

ssh 及云服务器

首先说下云服务器,既然要一键部署测试机,那么肯定要有台测试机,也就是云服务器,这里我用的是阿里云CentOS 8.4 64位的操作系统。

有了服务器,那怎么登陆呢?

本地登陆云服务器的方式一般有两种,密码登陆和 ssh 登陆。但是如果采用密码登陆的话,每次都要输入密码,比较麻烦。这里采用ssh登陆的方式。关于如何免密登录远程服务器,可以参考SSH 免密登陆配置

此后每次登陆都可以通过ssh <username>@<IP>的方式直接免密登陆了。

云服务器安装指定包

接着要给云服务器安装基础包,在CentOS安装指定包一般用的是yum,这个不同于npm

docker

# 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就安装成功了!

docker-compose

通过访问 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

验证一下:

nginx

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 的条件,通常是pushpull_request

指定触发事件时,可以限定分支或标签。

on:
  push:
    branches:
      - master

上面代码指定,只有master分支发生push事件时,才会触发 workflow

jobs

jobs字段,表示要执行的一项或多项任务。其中的runs-on字段指定运行所需要的虚拟机环境。

runs-on: ubuntu-latest

steps

steps字段指定每个 Job 的运行步骤,可以包含一个或多个步骤。每个步骤都可以指定以下三个字段。

  • jobs.<job_id>.steps.name:步骤名称。
  • jobs.<job_id>.steps.run:该步骤运行的命令或者 action。
  • jobs.<job_id>.steps.env:该步骤所需的环境变量。

下面放一下项目中的.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️⃣ 整个流程在代码pushfeature_dev分支时触发。

2️⃣ 只有一个job,运行在虚拟机环境ubuntu-latest

3️⃣ 第一步使用的是最基础的action,即actions/checkout@v2,它的作用就是让我们的workflow可以访问到我们的repo

4️⃣ 第二步是在执行工作流的机器中安装node,这里使用的actionactions/setup-node@v1

5️⃣ 第三步是执行linttest

6️⃣ 第四步是临时设置 ssh key,这也是为了下一步登录服务器做准备。

7️⃣ 第五步是部署,这里面先是ssh登录服务器,拉取了最新分支代码,然后安装依赖、打包,最后启动docker,生成镜像。到这里测试机上就有了Docker服务。

8️⃣ 最后一步是删除ssh key

最后来github看一下完整的流程:

其中deploy阶段算是核心了:

洋洋洒洒写了这么多,也不知道你看明白了不 😂

如果有任何问题,欢迎评论区留言,看到后会第一时间解答 😊

后续会有很多关于这个项目的文章,也请大家多多关注~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK