乐趣区

关于前端:前端自动化部署

前端自动化部署

本文参考自: 作者:yeyan1996 链接:https://juejin.cn/post/684516… 起源:稀土掘金 著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。

docker 简介

开发 5 分钟,打包半小时, 早已是前端的痛点, 更着, 开发者本身环境的差别会导致最终的产物也有不同

docker 能够灵便的创立 / 销毁 / 治理多个“服务器”,这些“服务器”被称为 容器 (container)

在容器中你能够做任何服务器能够做的事,例如在有 node 环境的容器中运行 npm run build 打包我的项目,在有 nginx 环境的容器中部署我的项目,在有 mysql 环境的容器中做数据存储等等

一旦服务器装置了 docker,就能够自在创立任意多的容器,上图中 docker 的 logo 形象的展现了它们之间的关系,🐳就是 docker,下面的一个个集装箱就是容器

本机装置 docker

官网下载地址: Get Started with Docker | Docker

下载时最好棘手也注册一个 dockerHub

我的电脑是 windows, 装置实现后点击 docker 图标启动 docker, 在终端输出docker, 看到如下输入则代表 docker 失常运行

  • 补充:

    在此你关上 docker, 可能会看到 docker 是红色的, 此时你能够在终端进入 docker 目录, 运行DockerCli.exe -SwitchDaemon

    1. cd “C:\Program Files\Docker\Docker”

    2. DockerCli.exe -SwitchDaemon

      // 执行失败时执行

      ./DockerCli.exe -SwitchDaemon

    如果执行上述步骤后, docker 依然是红色, 而且无奈执行 docker 其余命令(呈现 error), 例如

    那就只能按照软件提醒装置 Linux 内核

    点击进入网址后按照操作进行下载安装 https://aka.ms/wsl2kernel

    1. 下载最新的实用于 x64 计算机的更新包

      如果不确定本人计算机的类型, 关上 PowerShell 输出systeminfo | find "零碎类型", 如果你看到如下输入, 则代表计算机是 x64 类型的:

      下载安装实现后在 PowerShell 运行如下命令, 将 WSL2 设为默认版本

      wsl –set-default-version 2

      接着再次执行:

      cd “C:\Program Files\Docker\Docker”

      DockerCli.exe -SwitchDaemon

      如果还是呈现谬误, 那就重启电脑再试一次吧(我有一次就是这样, 重启之后执行上述命令就好了)

docker 概念:

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

容器能够类比一个服务器, 镜像则是创立容器的模板, 一个 docker 镜像能够创立多个容器

有两种获取镜像的形式:

  • Dockerfile 文件创建
  • 应用 dockerHub 或其余仓库上现有的镜像

创立 docker 镜像

首先创立一个 hello-docker 目录, 在目录中创立 index.htmlDockerfile文件

<!--index.html-->
<h1>Hello docker</h1>
# Dockerfile
FROM nginx
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80
  • Dockerfile 内容解析:

    • FROM nginx:基于官网 nginx 镜像
    • COPY index.html /usr/share/nginx/html/index.html:将当前目录下 index.html 替换容器中 /usr/share/nginx/html/index.html 文件,/usr/share/nginx/html 是容器中 nginx 默认寄存网页文件的目录,拜访容器 80 端口会展现该目录下 index.html 文件
    • EXPOSE 80:容器对外裸露 80 端口,次要起申明作用,实在端口映射还须要在创立容器时定义
  • 目前的文件构造:

    hello-docker
      |____index.html
      |____Dockerfile

在当前目录 (我的项目目录) 运行以下命令创立 doker 镜像

docker build . -t test-image:latest

  • 命令解析:

    • build:创立 docker 镜像
    • .:应用当前目录下的 dockerfile 文件
    • -t:应用 tag 标记版本
    • test-image:latest:创立名为 test-image 的镜像,并标记为 latest(最新)版本

创立实现后, 能够通过 docker images 命令查看所有镜像

创立 docker 容器

镜像胜利创立后, 运行以下命令能够创立一个 docker 容器

docker run -d -p 8081:80 –name test-container test-image:latest

  • 命令解析:

    • run:创立并运行 docker 容器
    • -d:后盾运行容器
    • 8081:80:将以后服务器的 8081 端口(冒号前的 8081),映射到容器的 80 端口(冒号后的 80)
    • –name:给容器命名,便于之后定位容器
    • test-image:latest:基于 test-image 最新版本的镜像创立容器

通过 docker ps -a 命令查看所有容器

当初在本地浏览器输出: localhost:8081即可拜访服务内容(即我的项目中的 index.html)

  • docker 其余命令

    • 进行、启动、杀死、重启一个容器

      docker stop Name 或者 ID
      docker start Name 或者 ID
      docker kill Name 或者 ID
      docker restart name 或者 ID

    • 删除所有进行的容器

      docker container prune

    • 查看所有 name 以 docker 结尾的 docker 容器,并只输入容器名

      docker ps -a -f “name=^docker” –format=”{{.Names}}”

    • 进行 name 为 docker-container 的容器

      docker stop docker-container

    • 删除 name 为 docker-container 的容器(进行状态的容器能力被删除)

      docker rm docker-container

dockerHub

dockerhub 是存储镜像的仓库, 开发者能够将 Dockerfile 生成的镜像上传到 dockerhub 来存储自定义镜像,也能够间接应用官网提供的镜像

  • 应用官网镜像启动一个容器

    docker pull nginx
    docker run -d -p 81:80 –name nginx-container nginx

    第一步拉取了官网的 nginx 镜像

    第二步用基于官网 nginx 镜像创立名为 nginx-container 的容器

    这里应用 81 端口映射到容器的 80 端口,拜访 localhost:81 能够看到 nginx 启动页面

docker 的益处

docker 将环境对立起来, 保障生成环境和开发环境我的项目均能失常运行

开发者将开发环境用 docker 镜像上传到 docker 仓库, 在生成环境拉取并运行雷同环境即可放弃环境始终

  • 提交名为 docker-test-image 的镜像, 镜像名前加上 dockerhub 账号作为前缀

    docker push wzc520pyfm/docker-test-image:latest

  • 服务器拉取账号 wzc520pyfm 下的 docker-test-image 镜像

    docker pull wzc520pyfm/docker-test-image:latest

docker 也有版本控制, 在创立镜像时能够应用 tag 标记版本, 如果某个版本的环境有问题, 能够疾速回滚到之前的版本

docker 将我的项目构建须要的环境放在容器中, 与服务器隔离

容器创立和销毁都非常高效

高效的前端自动化部署

没有自动化的部署, 咱们须要 npm run build生成构建产物(dist), 将 dist 文件上传到服务器, 同时还须要将代码提交到仓库(团队单干总要提交的吧).

实现自动化部署后, 咱们要做的仅仅是 git push 提交代码到仓库, 其余均由脚本主动执行.

  • 脚本须要做的事:

    • 自动更新镜像
    • 镜像中主动运行 npm run build 生成构建产物
    • 主动创立容器

登录 Linux 云服务器

参考各大云服务器厂商官网文档, 附上腾讯云 CentOS 登录指南文档: 轻量应用服务器 应用近程登录软件登录 Linux 实例 – 操作指南 – 文档核心 – 腾讯云 (tencent.com)

我的是腾讯云 CentOS 7.6 64 位 的操作系统, 学生购买云服务器有优惠, 应该是99/ 年

Linux 服务器装置必要的零碎工具

装置必要工具

sudo yum install -y yum-utils

增加软件软件源, 应用阿里云镜像

sudo yum-config-manager –add-repo http://mirrors.aliyun.com/doc…

Linux 装置 docker

装置 docker

sudo yum install docker-ce docker-ce-cli containerd.io

开启 docker 服务

sudo systemctl start docker

运行 hello-world 我的项目

sudo docker run hello-world

如果可能看到输入Hello from Docker!, 证实 Docker 装置胜利

Linux 装置 git

用于从代码仓库拉取代码

yum install git

Linux 装置 nvm

前端自动化部署, 那当然解决逻辑是用 js 来写, node 能够让 js 在服务端运行

nvm: 治理 node 版本

官网地址: nvm-sh/nvm:节点版本管理器 – 合乎 POSIX 规范的 bash 脚本,用于治理多个被动节点.js 版本 (github.com)

首先运行装置命令:

curl -o- https://raw.githubusercontent… | bash

装置时输入示例如下:

[root@VM-12-9-centos /]# curl -o- https://raw.githubusercontent… | bash
% Total % Received % Xferd Average Speed Time Time Time Current

                          Dload  Upload   Total   Spent    Left  Speed

100 13226 100 13226 0 0 7281 0 0:00:01 0:00:01 –:–:– 7283
=> Downloading nvm from git to ‘/root/.nvm’
=> Cloning into ‘/root/.nvm’…
remote: Enumerating objects: 278, done.
remote: Counting objects: 100% (278/278), done.
remote: Compressing objects: 100% (245/245), done.
remote: Total 278 (delta 31), reused 101 (delta 20), pack-reused 0
Receiving objects: 100% (278/278), 142.25 KiB | 54.00 KiB/s, done.
Resolving deltas: 100% (31/31), done.
=> Compressing and cleaning up git repository

=> nvm source string already in /root/.bashrc
=> Appending bash_completion source string to /root/.bashrc
=> Close and reopen your terminal to start using nvm or run the following to use it now:

export NVM_DIR=”$HOME/.nvm”
[-s “$NVM_DIR/nvm.sh”] && \. “$NVM_DIR/nvm.sh” # This loads nvm
[-s “$NVM_DIR/bash_completion”] && \. “$NVM_DIR/bash_completion” # This loads nvm bash_completion

程序主动地尝试将环境变量增加到正确的地位, 在此之后, 咱们须要手动运行使能命令, 让环境变量失效

source ~/.bashrc

验证是否装置胜利

command -v nvm

失常输入示例:

[root@VM-12-9-centos /]# command -v nvm
nvm

Linux 装置 node

下载、编译、装置最新版本的 node:(本次操作执行此命令)

nvm install node # “node” is an alias for the latest version

如果须要装置特定版本的 node, 请运行:

nvm install 14.7.0 # or 16.3.0, 12.22.1, etc

装置的第一个版本将成为默认版本。新 shell 将从节点的默认版本(例如)开始。

其余命令:

  • 切换 node 版本

    nvm use 14.17.3

  • 列出已装置的 node 版本

    nvm ls

  • 卸载指定版本的 node

    nvm uninstall 14.17.3

Linux 装置 pm2

pm2 能够让咱们的 js 脚本在服务器后盾运行

npm i pm2 -g

创立前端我的项目

本地创立一个简略的前端根底我的项目

vue create docker-test

创立实现后将 demo 上传到 github (倡议创立 public 共有仓库, 这样能够免去鉴权间接 clone, 如果创立了 private 公有仓库, 在运行时须要输出明码, 代码运行时当然不心愿这样, 解决办法是应用 token, 参见: (37 条音讯)【突发】解决 remote: Support for password authentication was removed on August 13, 2021. Please use a perso_与日俱增,天道酬勤 -CSDN 博客) , 接下来配置 webhook

webhook

github 仓库有一个 hook(钩子), 它会在以后仓库触发某些事件时, 发送一个 post 模式的 http 申请

当仓库有提交代码时,通过将 webhook 申请地址指向云服务器 IP 地址,云服务器就能晓得我的项目有更新,之后运行相干代码实现自动化部署

  • 配置webhook

  • 测试是否配置胜利

    1. 本地批改代码并提交到仓库

​ 参数次要波及以后仓库和本地提交的信息,这里咱们只用 repository.name 获取更新的仓库名即可

申请如何解决?

当咱们的服务器收到我的项目更新后发送的 post 申请后, 须要创立 / 更新镜像来实现自动化部署

创立 Dockerfile

在本地我的项目里新建一个 Dockerfile 文件, 用于之后创立镜像

# dockerfile
# build stage
FROM registry.cn-hangzhou.aliyuncs.com/dyjutil/node:v14.8.0 as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
  • 配置解析

    • FROM registry.cn-hangzhou.aliyuncs.com/dyjutil/node:v14.8.0 as build-stage:采纳阿里版本境像的阶段命名为 build-stage
    • WORKDIR /app:将工作区设为 /app,和其余系统文件隔离
    • COPY package*.json ./:拷贝 package.json/package-lock.json 到容器的 /app 目录
    • RUN npm install:运行 npm install 在容器中装置依赖
    • COPY . .:拷贝其余文件到容器 /app 目录,分两次拷贝是因为放弃 node_modules 统一
    • RUN npm run build:运行 npm run build 在容器中构建

    这里用到了 docker 一个技巧:多阶段构建

    将构建分为两个阶段,第一阶段基于 node 镜像,第二阶段基于 nginx 镜像

    • FROM nginx:lts-alpine as production-stage:基于 nginx stable-alpine 版本镜像,并将有 nginx 环境的阶段命名为 production-stage
    • COPY –from=build-stage /app/dist /usr/share/nginx/html:通过 –form 参数能够 援用 build-stage 阶段生成的产物,将其复制到 /usr/share/nginx/html
    • EXPOSE 80:容器对外裸露 80 端口
    • CMD [“nginx”, “-g”, “daemon off;”]:容器创立时运行 nginx -g daemon off 命令,一旦 CMD 对应的命令完结,容器就会被销毁,所以通过 daemon off 让 nginx 始终在前台运行

通过 scp 命令, 将 Dockerfile 文件复制到云服务器上

scp ./Dockerfile root@121.5.110.8:/root

创立.dockerignore

.dockerignore 能够在创立镜像复制文件时疏忽复制某些文件

在本地我的项目里新建.dockerignore

# .dockerignore
node_modules

接着将 .dockerignore 文件也复制到云服务器上

scp ./.dockerignore root@121.5.110.8:/root

创立 http 服务器并编写主动部署脚本

应用 node 来开启简略的 http 服务器解决 webhook 发送的 post 申请, 脚本须要蕴含创立 http 服务器、拉取仓库代码、创立镜像和容器。

在本地我的项目新建index.js

const http = require("http");
const {execSync} = require("child_process");
const path = require("path");
const fs = require("fs");

// 递归删除目录
function deleteFolderRecursive(path) {if (fs.existsSync(path)) {fs.readdirSync(path).forEach(function (file) {
      const curPath = path + "/" + file;
      if (fs.statSync(curPath).isDirectory()) {
        // recurse
        deleteFolderRecursive(curPath);
      } else {
        // delete file
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(path);
  }
}

const resolvePost = (req) =>
  new Promise((resolve) => {
    let chunk = "";
    req.on("data", (data) => {chunk += data;});
    req.on("end", () => {resolve(JSON.parse(chunk));
    });
  });

http
  .createServer(async (req, res) => {console.log("receive request");
    console.log(req.url);
    if (req.method === "POST" && req.url === "/") {const data = await resolvePost(req);
      const projectDir = path.resolve(`./${data.repository.name}`);
      deleteFolderRecursive(projectDir);

      // 拉取仓库最新代码  data.repository.name 即 webhook 中记录仓库名的属性
      execSync(`git clone https://github.com/wzc520pyfm/${data.repository.name}.git ${projectDir}`,
        {stdio: "inherit",}
      );
      // 复制 Dockerfile 到我的项目目录
      fs.copyFileSync(path.resolve(`./Dockerfile`),
        path.resolve(projectDir, "./Dockerfile")
      );

      // 复制 .dockerignore 到我的项目目录
      fs.copyFileSync(path.resolve(__dirname, `./.dockerignore`),
        path.resolve(projectDir, "./.dockerignore")
      );

      // 创立 docker 镜像
      execSync(`docker build . -t ${data.repository.name}-image:latest `, {
        stdio: "inherit",
        cwd: projectDir,
      });

      // 销毁 docker 容器
      execSync(`docker ps -a -f "name=^${data.repository.name}-container" --format="{{.Names}}" | xargs -r docker stop | xargs -r docker rm`,
        {stdio: "inherit",}
      );

      // 创立 docker 容器  -- 这里应用了服务器的 8888 端口
      execSync(`docker run -d -p 8888:80 --name ${data.repository.name}-container  ${data.repository.name}-image:latest`,
        {stdio: "inherit",}
      );

      console.log("deploy success");
    }
    res.end("ok");
  })
  .listen(3000, () => {console.log("server is ready");
  });

在销毁 docker 容器局部用到了 linux 的管道运算符和 xargs 命令,过滤出以 docker-test 结尾容器(用 docker-test 仓库的代码制作的镜像创立的容器),进行,删除并从新创立它们

最初, 通过 scp 将 index 上传到云服务器上

scp ./index.js root@121.5.110.8:/root

当初, 咱们的我的项目构造为:

云服务器上应用 pm2 运行 index.js

pm2 start index.js

  • pm2 命令

    • pm2 list 查看运行的 pm2 服务
    • pm2 logs 查看 pm2 日志
    • pm2 flush 革除 pm2 日志
    • pm2 start index.js 运行 index.js 文件
    • pm2 stop id 进行指定 id 的 pm2 服务

小插曲

因为咱们须要通过服务器的 8888 端口拜访部署的我的项目, 仓库须要通过服务器 3000 端口告诉服务器代码更新, 所以服务器须要放通 8888 和 3000 端口, 上面介绍腾讯云服务器放通端口的步骤:

接着就能够拜访 http:// 服务器 ip:8888 看到页面, 如果无响应, 尝试向仓库 push 一次代码, 查看 pm2 日志是否胜利拉取代码并更新镜像, 如果呈现网络问题无奈拉取代码, 最好是改用 gitee 仓库, gitee 的 webhook 配置拜访与 github 统一.

接下来对我的项目中的 App.vue 稍作更改, 并提交仓库, 测试自动化部署

从新关上 http:// 服务器 ip:8888

能够看到内容曾经更新

示例代码

wzc520pyfm/docker-test – 码云 – 开源中国 (gitee.com)

关注 Dockerfile,.dockerignore,index.js 文件

间隔实在环境仍有肯定差距

上述 demo 只创立了单个 docker 容器,当我的项目更新时,因为容器须要通过销毁和创立的过程,会存在一段时间页面无法访问状况

而理论投入生产时个别会创立多个容器,并逐渐更新每个容器,配合负载平衡将用户的申请映射到不同端口的容器上,确保线上的服务不会因为容器的更新而宕机

另外基于 github 平台也有十分成熟的 CI/CD 工具,例如

  • travis-ci
  • circleci

通过 yml 配置文件,简化上文中注册 webhook 和编写更新容器的 index.js 脚本的步骤

# .travis.yml
language: node_js
node_js:
  - 8
branchs:
  only:
    - master
cache:
  directories:
    - node_modules
install:
  - yarn install
scripts:
  - yarn test
  - yarn build

另外随着环境的增多,容器也会逐步减少,docker 也推出了更好治理多个容器的形式 docker-compose

退出移动版