前端docker自动化部署

前言

前段时间学习了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实现在线壁纸网站

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270