随着容器化技术的大行其道,Docker 在前端领域中也有着越来越广泛的应用。本文主要介绍了容器化技术给前端工程带来的变化,演示了如何使用 Docker 构建不同种类的前端应用。
什么是 Docker
Docker 初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,它是基于 dotCloud 公司多年云服务技术的一次革新,并于 2013 年 3 月以 Apache 2.0 授权协议开源,主要项目代码在 GitHub 上进行维护。Docker 项目后来还加入了 Linux 基金会,并成立推动开放容器联盟(OCI)。
Docker 使用 Google 公司的 Go 语言 开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它隔离的进程,因此称其为容器。初实现是基于 LXC,从 0.7 版本后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,进一步演进使用 runC 和 containerd。
Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。
前端为什么要用 Docker
这里只讨论使用 Docker 给前端带来的优势,偏运维相关的比如启动速度快,资源利用率高等略过,有兴趣的同学可以上官网看看文档。
- 提供一致的运行环境。在任何环境下使用 Docker 构建的镜像的运行环境都是确定的,Docker 给应用提供了一个从开发到上线均一致的环境。比如 Node.js 项目在不同版本下性能表现不一致,开发环境用的是 Node.js 6,UAT 环境用了 Node.js 10,那么很可能接口的压测结果不一致。
- 更轻松的迁移。由于 Docker 确保了运行环境的一致性,使得应用的迁移更加容易。可以很轻易将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行。比如接到任务说下周要加一个分区,或者客户要求部署私有云,可以很放心的说镜像拿走,而不用担心环境问题。
- 持续交付和部署。代码从开发到终在生产环境上的部署,需要经过很多中间环境,通过定制应用镜像来实现持续集成、持续交付,非常有助于降低构建持续交付流程的复杂程度。在中小型公司可以考虑直接使用 GitLab CI 搭建持续集成环境。
- 快速部署、回滚。得益于 Docker 使用的分层存储和镜像技术,使得扩展镜像变得非常简单。可以预先把程序需要的依赖,静态资源等在构建过程中添加到镜像,在需要的时候启动该容器实现快速部署、回滚、止血。比如当出现线上事故需要回滚时,传统做法是触发某些自动化工具去拉代码装依赖打包后部署,一旦某个环节出了问题,譬如网络被墙了导致依赖拉不下来,构建失败等等,小事故可能会演变为 P0 事故。
使用 Docker 构建 Web 前端项目
Web 前端项目的部署上线一般会经历 babel 编译,webpack 构建等过程,终将打包后的静态资源放在静态资源服务器上。
步,创建项目。这个过程和使用的框架并没有太多关系,这里就拿 React 来演示,使用 create-react-app
创建项目,进入目录后使用 npm run build
命令构建出部署所需要的静态资源
npx create-react-app my-app
cd my-app
npm run build
第二步,选择合适的静态资源服务器。这里选用 Nginx
,这是一款轻量级的 HTTP 服务器,具有很多非常优越的特性:轻量、高性能、并发能力强,用来部署静态页面很便捷
在根目录创建 nginx.conf
,按需进行配置
- 现代前端框架几乎都使用了 HTML5 push/pop history API 来完全控制 Web 应用程序的历史记录,在 Nginx 中需要配置 try_files 指令
- 前后端分离后,在 Nginx 中需要配置反向代理解决前端跨域问题
- ...
server {
listen 80 default_server;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html ;
try_files $uri $uri/ /index.html;
}
# location ~ /api/ {
# proxy_connect_timeout 2s;
# proxy_read_timeout 600s;
# proxy_send_timeout 600s;
# proxy_pass http://gateway:8080;
# proxy_set_header Host $host:80;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# client_max_body_size 1000m;
# }
}
第三步,定制镜像。在项目的根目录创建 Dockerfile
文件来定制我们的镜像
- 使用
FROM
指令指定基础镜像,官方已经给我们准备好了 Nginx 的镜像 - 使用
LABEL
指令为构建的镜像设置作者信息 - 使用
ADD
指令将 build 文件夹下的所有文件拷贝至 Nginx 的根目录 - 使用
ADD
指令添加上一步准备好的 Nginx 配置文件 - 使用
EXPOSE
指令声明运行时容器提供的服务端口,暴露 80 端口
第四步,构建镜像。使用 docker build
命令进行镜像构建
$ docker build -t himstone/test .
Sending build context to Docker daemon 118.1 MB
Step 1/5 : FROM nginx:latest
---> 3c5a05123222
Step 2/5 : LABEL maintainer "himstone.yang@gmail.com"
---> Using cache
---> fe13a72edfa9
Step 3/5 : ADD ./build/ /usr/share/nginx/html/
---> Using cache
---> 7725efecebbe
Step 4/5 : ADD nginx.conf /etc/nginx/
---> Using cache
---> 402dd5589c05
Step 5/5 : EXPOSE 80
---> Using cache
---> ba0ce6d40a8f
Successfully built ba0ce6d40a8f
第五步,测试并上传镜像。
使用 docker run
命令启动容器并将本地的 8080 端口 映射到容器的 80 端口
$ docker run -d -p 8080:80 himstone/test
72162395ba4241201e1b3ca08ed38f4da768051d14b3962bc2a1d77261c4df24
打开浏览器,访问 localhost:8080。出现如下页面表示工作正常,测试通过。
使用 docker push
上传镜像。这里只是演示,直接上传到了 Docker Hub 上,推荐使用 Harbor
搭建私有镜像仓库
$ docker login -u himstone -p yourPassword
Login Succeeded
$ docker push himstone/test
The push refers to a repository [docker.io/himstone/test]
e4d08e577219: Pushed
69c46c39d897: Pushed
08422f31c8c5: Pushed
4d3d2ca78cd4: Pushed
9c46f426bcb7: Pushed
latest: digest: sha256:3136cad04911e71d40afb0f481f56c6ac286ac8afe50b5578efe8e617a0f4e32 size: 1365
使用 Docker 构建 Node.js 前端项目
前端应用中还包括使用 Node.js 开发的后端服务,常用于承担一些Api 中间层、BFF层的角色,甚至是完整的核心服务。
步,创建项目。可选的框架有很多,比如 express,koa 等,在这里拿阿里开源的 Egg.js
做演示,使用 egg-init
初始化项目,再将 package.json
里 scripts-start 里的 --daemon 去掉,在 Docker 内建议前台运行
egg-init egg-example --type=simple
cd egg-example
npm i
第二步,定制镜像。在项目的根目录创建 Dockerfile
文件来定制我们的镜像
- 使用
FROM
指令指定基础镜像,这里使用官方提供的node:8
- 使用
LABEL
指令为构建的镜像设置作者信息 - 使用
COPY
指令将根目录下的所有文件拷贝至镜像内 - 使用
RUN
指令执行npm install
安装依赖 - 使用
EXPOSE
指令声明运行时容器提供的服务端口,暴露 7001 端口 - 使用
CMD
指令设置容器启动命令为npm start
第三步,构建镜像。使用 docker build
命令进行镜像构建
$ docker build -t himstone/test-egg .
Sending build context to Docker daemon 123.5 MB
Step 1/6 : FROM node:8
---> c5e9a81034a9
Step 2/6 : LABEL maintainer "himstone.yang@gmail.com"
---> Using cache
---> f68494fbcd6e
Step 3/6 : COPY . .
---> 685e9ad05928
Removing intermediate container 3c0e94e7453e
Step 4/6 : RUN npm install
---> Running in 94c5e4d37c64
npm WARN co-mocha@1.2.2 requires a peer of mocha@>=1.18 <6 but none is installed. You must install peer dependencies yourself.
up to date in 6.249s
---> 8768307d6ae9
Removing intermediate container 94c5e4d37c64
Step 5/6 : EXPOSE 3000
---> Running in b554fc4777ab
---> 55d1a6cff311
Removing intermediate container b554fc4777ab
Step 6/6 : CMD npm start
---> Running in 31378b658364
---> c7fdb400b841
Removing intermediate container 31378b658364
Successfully built c7fdb400b841
第四步,测试并上传镜像
使用 docker run
命令启动容器并将本地的 8080 端口映射到容器的 7001 端口
$ docker run -d -p 8080:7001 himstone/test-egg
72162395ba4241201e1b3ca08ed38f4da768051d14b3962bc2a1d77261c4df24
打开浏览器,访问 localhost:8080。出现如下的页面表示工作正常,测试通过。
使用 docker push
上传镜像。
$ docker push himstone/test-egg
The push refers to a repository [docker.io/himstone/test-egg]
2c4ef22a1c6b: Pushed
7e45aa7e5ac9: Pushed
d9fdc5af195e: Mounted from himstone/test-ssr
245ce6af2e7b: Mounted from himstone/test-ssr
373fc5310302: Mounted from himstone/test-ssr
25494c62cf78: Mounted from himstone/test-ssr
d714f65bc280: Mounted from himstone/test-ssr
fd6060e25706: Mounted from himstone/test-ssr
d7ed640784f1: Mounted from himstone/test-ssr
1618a71a1198: Mounted from himstone/test-ssr
latest: digest: sha256:ce0a10b07ab2f91904d60f2b35754995c177e7dd8e3d0397e84e2453d79a3384 size: 2426
使用 Docker 构建 SSR 服务端渲染的前端项目
上文有介绍如何使用 Docker 构建普通的Web项目,即使用前端渲染,在遭遇 SEO、首屏性能等问题时,往往会考虑使用服务端渲染技术,两者在构建上的差异主要体现在后者不需要静态资源服务器,直接由 Node.js 直出 HTML。因为和构建 Node.js 前端项目的过程中有许多相似处,这里只做简要介绍,完整的 demo 代码可参考文末的链接。
步,创建项目。这里就拿 Next.js 来演示,参考 官方文档 创建项目
第二步,定制镜像。在项目的根目录创建 Dockerfile 文件来定制我们的镜像
- 使用
COPY
指令将根目录下的所有文件拷贝至镜像内 - 使用
RUN
指令执行npm install
安装依赖 - 使用
RUN
指令执行npm run build
打包静态资源 - 使用
EXPOSE
指令声明运行时容器提供的服务端口,暴露 3000 端口 - 使用
CMD
指令设置容器启动命令为npm start
第三步,构建镜像。使用 docker build
命令进行镜像构建
第四步,测试并上传镜像。
出现如下的页面表示工作正常。
小结
通过上述的三个例子,演示了如何使用 Docker 构建不同类型的前端应用。Docker 为前端应用提供了一致的运行环境,带来了快速部署、回滚等特性。除此之外,还需要考虑容器化应用的部署,容器的编排、调度等问题,可以使用 Kubernetes
等容器管理平台。
当然 Docker 还可以应用到持续集成中,GitLab CI
为 runner 提供了许多执行器,其中就包括了 Docker Executor。如下图所示,Docker Executor 对比其他拥有许多优势,比如每次构建提供一致的运行环境,零配置支持并发构建,适配复杂的构建环境等。
所有的 demo 源代码已上传至 Github,需要的可以参考
himStone/build-frontend-app-with-docker