美文网首页Docker 前端
前端docker自动化部署

前端docker自动化部署

作者: 打酱油12138 | 来源:发表于2020-08-11 14:50 被阅读0次

    前言

    前段时间学习了docker相关的内容,docker可以实现数据隔离、跨平台等,加快项目后续部署。

    在了解docker部署流程后,学习了CI/CD的概念,通过对gitlabdocker的 简单配置即可实现持续集成,大幅提高生产效率。

    本人基于gitlabdocker配置前端部署大致流程:

    服务器监听远端git分支是否出现新提交 --> 服务器拉取对应分支代码 --> 基于node镜像实现依赖下载和源码编译 --> 基于nginx镜像及编译文件build前端镜像 --> 基于docker-compose.yml运行相应容器

    联系之前实现的前端自动化部署工具
    (详情请见:从零开始 Node实现前端自动化部署),
    准备对其升级并支持docker部署。

    构思

    考虑兼容传统部署方式基础上,支持本地编译后代码的上传部署和源码上传、远端编译部署两种方式。

    考虑部分简单项目可能无需使用docker-compose进行容器编排,直接使用docker run指令即可。

    因此考虑支持以下方式进行项目部署:

    部署方式 legacy docker docker-compose
    本地打包编译dist
    源码远端打包编译

    由此考虑用户使用流程如下:

    选择项目-->选择部署方式-->选择上传源码or编译后代码-->远端自动部署

    分为以下两种实现思路:

    1. 源码上传至云端,云端借助Dockerfile基于node镜像实现依赖下载、打包编译,基于nginx镜像及编译文件build前端镜像,最后运行容器。
    2. 项目本地编译后,打包上传至云端,直接基于nginx镜像及编译文件build前端镜像,最后运行容器。

    升级前的准备

    为更好理解该项目实现,可提前准备以下内容:

    1. 熟悉docker部署流程及Dockerfiledocker-compose.yml语法规则
    2. 成功安装docker、docker-compose等基础环境的远端服务器
    3. node基础知识

    升级

    逻辑梳理 模块划分

    首先对原项目进行逻辑梳理与模块划分,明确各模块的功能,最终文件目录如图:

    image

    文件压缩 升级支持过滤列表

    由于此次需要实现对源码的打包上传,需要实现对node_modeles相关文件的过滤。考虑根据配置文件中exclude字段配置过滤列表实现打包过滤功能。

    在进行压缩前,读取目标文件目录targetDir的子文件名称,排除存在过滤列表excludeFiles中的文件,返回过滤后的文件名数组。

    在压缩过程中,旧版本使用archive.directory()实现对整个文件目录的压缩,现改为基于文件数组依次进行打包,使用fs.statSync(filePath).isDirectory()判断子文件是否为文件夹,使用archive.file()archive.directory()进行对应打包处理。

    compress.js 源码:

    const fs = require('fs')
    const archiver = require('archiver')
    const join = require('path').join
    
    function compress (targetDir, localFile, excludeFiles, homeDirName = 'web/') {
      return new Promise((resolve, reject)=>{
        // filter exclude files
        const filterDir = filterExcludeFiles(targetDir, excludeFiles)
        console.log('正在压缩文件...')
        let output = fs.createWriteStream(localFile) // create file stream write
        const archive = archiver('zip', {
          zlib: { level: 9 } // set compress level
        })
        output.on('close', () => {
          console.log('压缩完成!共计 ' + (archive.pointer() / 1024 /1024).toFixed(3) + 'MB')
          resolve('Compression complete')
        }).on('error', (err) => {
          console.error('压缩失败', err)
          reject('Compression failed')
        })
        archive.on('error', (err) => {
          throw err
        })
        archive.pipe(output) // save file by pipe
        // append file and dir
        filterDir.forEach(file => {
          const filePath = join(targetDir, file)
          const stat = fs.statSync(filePath)
          if (stat.isDirectory()) {
            archive.directory(filePath, homeDirName + file)
          } else {
            archive.file(filePath, { name: file, prefix: homeDirName })
          }
        })
        archive.finalize() // make sure file stream write completely
      })
    }
    
    // filter exclude files
    function filterExcludeFiles (targetDir, excludeFiles = []) {
      return fs.readdirSync(targetDir).filter(file => {
        return (!excludeFiles.includes(file))
      })
    }
    
    module.exports = compress
    

    升级终端颜色

    基于colors实现终端命令的颜色区分显示,使用如下:

    const colors = require('colors')
    
    colors.setTheme({
      silly: 'rainbow',
      input: 'grey',
      verbose: 'cyan',
      prompt: 'grey',
      data: 'grey',
      help: 'cyan',
      debug: 'blue',
      info: 'blue',
      error: 'red',
      warn: 'yellow',
      success: 'green'
    })
    
    console.log('粗体文字'.bold)
    console.log('出现错误'.error)
    

    tip: 部署方式等选择依然基于inquirer实现。

    新增Dockerfile docker-compose.yml

    这里以支持源码编译的Dockerfile为例,介绍前端镜像构建过程:

    1. 引用node:lts-alpine3.12(基础版node镜像,文件较小)
    1. 切换为阿里源(若依赖下载较慢,可切换为阿里源)
    2. 指定工作目录/tmp/cache(容器内)
    3. 添加当前同级目录中package.json(package-lock.json存在时请添加)
    4. 执行依赖安装
    5. 拷贝当前目录文件至工作目录
    6. 执行编译指令
    7. 引用socialengine/nginx-spa:latest(包含SPA相关配置的nginx镜像,监听地址为/app/index.html)
    8. 拷贝 node镜像 /tmp/cache/dist 至 nginx镜像 /app目录下(编译后文件夹可自定义)

    Dockerfile

    FROM node:lts-alpine3.12 as build
    
    # 若依赖下载较慢,可切换为阿里源
    RUN npm config set registry https://registry.npm.taobao.org
    
    WORKDIR /tmp/cache
    
    ADD package.json .
    # 存在package-lock.json时启用
    ADD package-lock.json .
    RUN npm install
    
    ADD . .
    # 编译指令可自定义
    RUN npm run build
    
    FROM socialengine/nginx-spa:latest as nginx
    # 编译后文件夹可自定义
    COPY --from=build /tmp/cache/dist /app
    

    docker-compose.yml较为简单,指定容器名、镜像名、重启方式、映射端口(宿主机端口:容器端口)等信息。

    ps: 远端安装最新版本docker-compose,这里使用3.8的语法,低版本可使用2.x语法

    docker-compose.yml

    version: "3.8"
    
    services: 
      web:
        # 容器名、镜像名请保持与配置文件一致
        container_name: spa_web
        restart: always
        image: spa/web:dev
        ports: 
          - 8900:80
    

    docker部署流程

    在主程序中,根据用户选择进行部署判断,主要分为以下流程

    1. docker、docker-compose安装检查
    1. 上传Dockerfiledocker-compose.yml
    2. 构建docker镜像
    3. 启动容器前检查是否存在同名容器,存在则停止并删除同名容器
    4. 根据配置使用docker rundocker-compose.yml启动容器
    5. 展示当前运行中容器状态
    6. 提示完成部署

    tips:

    • 若服务器网速较慢,可提前安装nodenginx的docker镜像,以便加快后续部署速度
      • docker pull node:lts-alpine3.12
      • docker pull socialengine/nginx-spa:latest

    spp.js docker部分代码:

    // docker流程
    // docker 部署流程 docker env check --> upload Dockerfile --> build image
    const dockerFilePath = deployDir + releaseDir
    await runCommand(ssh, `docker -v`, '/')
    await uploadFile(ssh, getAbsolutePath(BUILD__MODE === 'dist' ? docker_file : docker_file__build), dockerFilePath + '/Dockerfile') // upload Dockerfile
    console.log('5- 开始构建docker镜像...请耐心等待'.bold)
    await runCommand(ssh, `docker build -t ${ image } .`, dockerFilePath)
    console.log('6- 准备启动docker容器...请耐心等待')
    if (DEPLOY__MODE === 'docker') {
      if ((await runCommand(ssh, `docker ps -f name=${ container_name }`)).indexOf('\n') !== -1) {
        console.log('存在同名容器,正在删除同名容器...')
        await runCommand(ssh, `docker stop ${ container_name }`, '')
        await runCommand(ssh, `docker rm ${ container_name }`, '')
      }
      await runCommand(ssh, `docker run --name ${container_name} -p ${ports} -d ${image}`, dockerFilePath)
    } else {
      // docker-compose 部署流程 upload docker-compose --> run docker-compose --> show container
      await runCommand(ssh, `docker-compose -v`, '/')
      await uploadFile(ssh, getAbsolutePath(docker_compose), dockerFilePath + '/docker-compose.yml') // upload docker-compose
      if ((await runCommand(ssh, `docker ps -f name=${ container_name }`)).indexOf('\n') !== -1) {
        console.log('存在同名容器,正在删除同名容器...')
        await runCommand(ssh, `docker stop ${ container_name }`, '')
        await runCommand(ssh, `docker rm ${ container_name }`, '')
      }
      await runCommand(ssh, 'docker-compose up -d', dockerFilePath)
    }
    // 显示当前运行中容器
    console.log('7- 当前运行中的容器...'.bold)
    await runCommand(ssh, 'docker ps', dockerFilePath)
    console.log(`恭喜!${ name }部署成功`.success)
    

    至此完成该项目的主要升级,更多细节请参考my-auto-deploy

    使用

    这里选取两种情况进行展示:

    • docker + source build
    • docker-compose + dist

    这里以react在线壁纸为例,服务器已开放8800、8900端口。

    1. 拉取代码至本地,安装依赖,执行本地构建,目录如图:
    image
    1. 该项目构建后产生build文件夹且不存在package-lock.json文件,因此修改配置文件Dockerfile,如图

    配置文件

    image

    Dockerfile

    image
    1. 运行部署程序,选择相应信息开始部署,直至部署完成,如图

    选择部署

    image

    部署成功(容器信息和预期结果一致)

    image
    1. 访问对应地址,验证部署成功,如图
    image
    1. 修改 docker-compose.yml 端口为8900后,再次进行部署,如图

    docker-compose.yml

    image

    选择部署

    image
    1. 由于存在同名容器,部署过程中会停止并删除同名容器,之后使用最新的镜像启动容器,如图
    image
    1. 访问对应地址,验证部署成功,如图
    image

    最后

    🎉该项目已开源至 github 欢迎下载使用 后续会完善更多功能 🎉
    源码及项目说明

    Tip: 喜欢的话别忘记 star 哦😘,有疑问🧐欢迎提出 prissues ,积极交流。

    其他文章:

    从零开始 Node实现前端自动化部署

    从零开始 React Hook实现在线壁纸网站

    相关文章

      网友评论

        本文标题:前端docker自动化部署

        本文链接:https://www.haomeiwen.com/subject/rzlsrktx.html