前端需要了解的Docker

Docker由dotCloud的内部服务演变而来,13年开源,Docker使用Go语言开发,基于Linux内核的cgroupnamespace,对进程进行封装隔离,由于隔离的进程之间以及宿主之间相互独立,因此也称其为容器。

Docker的优势

由于容器是进程级别的,与传统虚拟化解决方案相比:

  • 系统资源利用率更高
  • 启动时间更快
  • 统一运行环境
  • 持续集成和部署
  • 维护和扩展更简单

基本概念

镜像(Image)

Docker把应用程序及其依赖,打包在image文件里面。通过这个文件,才能生 Docker容器。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。

实际开发中,一个image文件往往通过继承另一个image文件,以定制自己所需的内容,构建新的镜像。

容器(Container)

容器由镜像实例化而来,可以被建、启、停、删、止。容器实质是个进程,但是与宿主独立,有自己的namespace,可以理解成”沙箱”。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样,这种特性使得容器封装的应用比直接在宿主运行更加安全。

Dockfile

Dockerfile 是一个文本文件,其内包含了一条条的指令,用来配置 image。Docker 根据 该文件生成二进制的 image 文件。特别指出,Dockerfile 中每一个指令都会建立一层,每一条指令的内容,就是描述该层应当如何构建,在写Dockfile的时候一定要注意这一点,对于优化镜像大小有重要作用。

几个必要的指令介绍

  • FROM
1
FROM node:8.9.3

定义镜像基准,如上镜像继承自node8.9.3版本,镜像首选本地,不存在从公共仓库下载(或者私有仓库)。

  • RUN
1
RUN npm install

执行命令,并commit镜像。

  • EXPOSE

告诉容器在运行时要监听的端口,端口为容器内部端口,外部访问不到,需要隐射到外部接口,在docker run的时候,使用-p选项

  • COPY
1
COPY source target

将构建上下文中source拷贝到container的文件系统对应的路径target下。另外还有个相似的命令ADD,相比COPYADD支持远程URL文件和自动解压缩。

  • WORKDIR

WORKDIR指令用于设置Dockerfile中的RUNCMDENTRYPOINT指令执行命令的工作目录(默认为/目录),该指令在Dockerfile文件中可以出现多次,如果使用相对路径则为相对于WORKDIR上一次的值。

安装

Mac

安装完成之后可以尝试

1
2
3
4
docker --version
docker-compose --version
docker-machine --version
docker info

显示正常的话,运行一个nginx容器

1
docker run -d -p 80:80 --name webserver nginx

首次执行,由于本地没有镜像,会从hub上拉取安装。

1
2
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx

服务运行后,可以访问http://localhost,如果看到了 “Welcome to nginx!”,就说明 Docker for Mac 安装成功了。

停止并删除Nginx服务器

1
2
docker stop webserver
docker rm webserver

前端中的Docker

整个前端项目研发生命周期包括需求、设计、开发、测试、上线,Docker配合CI,让工作焕然一新。

开发阶段

目标提供统一的研发环境,node版本,工程化工具等。一个简单的例子:

  • 创建Dockerfile文件
1
2
3
4
5
6
7
8
9
10
11
12
## 指定node: 8.9.3版本
FROM node:8.9.3
## 安装node相关依赖和工具
RUN npm install yarn -g &&\
yarn global add vue-cli &&\
yarn global add cross-env
## nginx
## 指定命令运行的目录
WORKDIR /app
  • 构建镜像
1
docker build -t test .
  • 项目运行
1
docker run -v "$(PWD)":/app -p 4040:4040 test npm start

具体项目创建的镜像规模大小不同,以上的方案只是使用docker的思路,实际方案还是要根据项目来定,对于中小型项目,环境版本的差异给开发带来的问题基本上可以忽略,工程化相关的依赖也不会很复杂,如果可以,可以定制一套项目通用的工具,通过发布到私服管理,减少项目维护成本。

测试

目标提供测试一个一键运行的前端环境。一个简单的例子:

  • 创建Dockerfile文件
1
2
3
4
5
6
FROM nginx:latest
ADD ./dist/ /usr/share/nginx/html/
ADD nginx.conf /etc/nginx/
EXPOSE 80

之后发布镜像,之后就可以通过该镜像一键启动前端环境。

部署

一次典型的前端部署过程,先后经过安装依赖npm install,编译打包npm run build,启动静态服务器。

  • 创建Dockerfile文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM node:8.9.3 as builder
WORKDIR /app
## 利用镜像缓存依赖,package.json变化的情况下重新构建
COPY package.json /app
## 安装依赖
RUN npm install
COPY . /app
RUN npm run build
EXPOSE 80
## nginx多阶段构建
FROM nginx:latest
COPY --from=builder /app/dist /usr/share/nginx/html

通过镜像缓存,npm install过程可以节省一半的时间。

实际开发过程配合CI,各阶段都会有调整,效率也会更高。比如我们内部有自己的一套运维系统,部署阶段Dockerfile就得调整,需要将安装依赖和build分离。

前端容器化升级没有标准的方案,任何一步都需要结合实际定制,但是方向是一致的,充分利用容器的特点,打造简单、高速,统一的前端开发环境。