前端自动化部署

前端自动化部署 本文参考自: 作者: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文件
Hello docker

# 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
DloadUploadTotalSpentLeftSpeed

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. 本地修改代码并提交到仓库
    2. 前端自动化部署
      文章图片
? 参数主要涉及当前仓库和本地提交的信息,这里我们只用 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 = https://www.it610.com/article/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端口, 下面介绍腾讯云服务器放通端口的步骤:
  1. 前端自动化部署
    文章图片
  2. 前端自动化部署
    文章图片
接着就可以访问 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

    推荐阅读