快餐型前端流水线项目 Docker 配置参考

695 阅读5分钟

前言

本司新项目周期短,大约 1-2 周就会产出一个新的前端项目,在项目的不断孵化和迭代之中,碰到有以下的问题需要解决:

  1. 多人混合开发,项目人员频繁流动从而导致的开发环境不一致的问题。比如 node 版本不一致,某项目依赖于特定的 node 版本,但因该开发人员本机 node 版本与指定的版本不一致导致安装失败。并且也需要统一构建环境和本地的 node 版本一致。
  2. 新孵化的项目假如都使用的老项目 Dockerfile 的复制,那么假如某天最原始那份需要做优化改动,那么其他全部复制了该份 Dockerfile 文件的项目就应该把这个改动全部 patch 过来,这是不理想的,理想应该是 Dockerfile 应该区分共享的和私有的。
  3. 部分同学对 docker 不是很熟悉,对 docker 的操作比较生涩,希望有更加简单的方式使其能快速上手做关于 docker 的操作。
  4. 如何对基础镜像进行选择和如何减少镜像的体积,以及 docker 的相关优化方案。

问题一

  1. 如何保证使用同一版本的 node 进行依赖安装和构建:
// package.json
"build": "docker run -v $(pwd):/workspace -w /workspace node:14.17.0-stretch bash -c \"npm run build:prod\"",
"install": "docker run -v $(pwd):/workspace -w /workspace node:14.17.0-stretch bash -c \"npm install\"",
  • package.json 文件内新增两条指令 buildinstall,分别用于使用特定版本的 node 去打包和安装依赖。
  • 通过 -v 选项,将当前前端项目根目录 $(pwd) (依赖执行路径),与 docker 容器内的 /workspace 路径形成数据卷共享关系,即在容器内可以通过在路径 /workspace 内访问到当前前端的根目录文件。
  • 通过 -w /workspace 将容器的工作空间设置为 /workspace 路径。
  • 启动 node:14.17.0-stretch 这个镜像,并在这个镜像的工作空间路径内执行 npm run build:prod 命令。

为什么使用 node:14.17.0-stretch 这个版本的镜像首先是因为 14.17.0 是目前官方推荐的稳定版本,且多项目测试都很好的兼容了,关于 stretch 是什么并且为什么选择将在下面进行说明。

问题二

  1. 如何将 Dockerfile 分为公有和私有
  • 使用多阶段构建。即思路是我们首先将公有的配置先使用一个 Dockerfile 保存起来,然后形成一个基础的镜像,其他项目再基于该镜像去做改造和传参,例如:
# 基础镜像 base-frontend-image
FROM nginx:stable-alpine
# 使用 ONBUILD 使该段命令在被其他 Dockerfile 引入且构建的时候执行
ONBUILD WORKDIR  /etc/nginx/html
# 这一步先设置 ARG 是为了方便后面阶段 Dockerfile 传参,为什么不用 ENV 而用 ARG 将在下段代码演示中说明
ONBUILD ARG ARG_PORT
ONBUILD ENV PORT $ARG_PORT
# 将 dist 构建出来的静态文件复制入镜像中
ONBUILD COPY ./dist .
# 将 nginx 配置复制入镜像中
ONBUILD COPY ./docker/nginx/* /etc/nginx/conf.d
// 将后阶段传入的 ARG_PORT 参数去替换掉 nginx 内的变量
ONBUILD RUN envsubst '\$PORT' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
# 业务镜像
# 配置该业务占用 7788 端口,其实该配置目前暂无太大意义,仅是为了表明该项目和部署时候的端口映射关系,理想是希望宿主端口和容器暴露的端口一致。

# 为什么使用 ARG 是因为 ENV 不能在 FROM 之前执行,其余 ARG 和 ENV 的差别再此不做赘述
ARG ARG_PORT=7788
FROM remote-docker-registry.com/base-frontend-image
# nginx default.conf.template 配置
server {
  # 通过
  # RUN envsubst '\$PORT' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
  # 命令,将 $PORT 注入 default.conf.template 内,并生成 default.conf
  listen  $PORT;
  # <domain> 这里只是为了后续方便区分域名,目前可以省略。当前也可以通过变量传参进来,看个人项目需求
  server_name ~^(?<domain>(.+))$;

  location / {
    root /etc/nginx/html/;
    index index.html;
  }
}
  • 使用基础镜像的 Dockerfile 构建出 base-frontend-image,然后其他的业务项目再基于该镜像去配置对应的参数即可。因为 base-frontend-image 没有配置版本默认选取的是 latest,所以只要我们远端更新了该镜像,那么本地构建的时候就会去拉新的镜像。

问题三

  1. 使不熟悉 docker 命令的同学在了解了基本的 docker 概念之后就能上手操作 docker 参考该篇文章:Docker 图形化工具 Portainer

该工具非常棒,安装和使用都十分的简单,基本解决了该问题。但是大家最好还是不要过于依赖该工具,能手动敲命令还是最快的。

问题四

使用 docker 时候的优化方案

  1. 尽量在 Dockerfile 里面使用 RUN 的时候,尽量将多个 RUN 命令整合在在一起,比如:
RUN mkdir xxx
RUN mv xx xxx

那么可以修改为:

RUN mkdir xxx && mv xx xxx
# 或者使用换行分隔符
RUN mkdir xxx \
mv xx xxx

为什么需要将多个 RUN 合并成一个是因为在 Dockerfile 里面,每执行一次 RUN 都会在该镜像上再添加多一层该镜像的复制 + 该 RUN 命令做的改动。比如一个初始镜像 20 MB,在执行了 5 个 RUN 之后,本地去看这个镜像的大小,发现超过了 100 MB 的。这也就是因为该镜像除本身之外,还有 5 层的镜像层在上面。但是其实因为每一层镜像跟上一层都是继承关系,引用是相同的,所以实际占用的硬盘大小没那么大,你可以通过 docker system df 去查看硬盘占用。但是每一层不仅是有改动的部分,还有因为这个改动 docker 所要去对相关文件的做的保存。 所以每一层虽然实际上没 20 MB 那么大,但是也应该尽量合并 RUN 以减少镜像体积,加快上传和下载的速度。

  1. 使用 .dockerignore 文件去忽略构建时传输给 docker 构建引擎的文件 在我们 docker build 的时候,会根据当前 Dockerfile 形成上下文目录,然后 docker 会将该目录下所有的文件传输到 docker 构建引擎中使用,所以当你确定不需要传输给 docker 构建引擎使用的文件时,你可以通过 .dockerignore 文件去告诉 docker 忽略掉这些目录,从而加快构建的速度,例如:
/src
/public
/node_modules
  1. 使用符合场景的基础镜像 当我们在 dockerhub 挑选镜像的时候,往往有以下几种 tag 的镜像: buster、stretch、jessie、slim、alpine
  • buster、stretch、jessie 包含比较完成的操作系统工具,比如包下载工具、完整 shell 工具、git 工具。
  • slim 非常轻量的容器环境,仅包含基础的包下载工具、shell 基础工具。
  • alpine 及其轻量的容器环境,仅包含该镜像特点要求的环境,比如 nginx-alpine,仅包含可以运行 nginx 的工具,其余无关的工具都不会安装在里面。

这些都是语义的,主要取决于镜像的作者在构建的时候都安装了哪些工具包

在我们构建的时候,使用的是 stretch ,因为在构建 node 项目的时候可能需要各种工具包,比如我项目里面有用到 git 工具包,那么我们希望在本地构建的时候,尽可能的满足复杂的 node 构建需求,体积是次要的,因为本地的 node 镜像,拉完一次之后就不用再拉了。

在我们提交业务 docker 镜像的时候,那么我们应该尽可能地减少镜像的体积,因为在日常的开发中会涉及频繁的上传和下载,所以选用了 nginx:stable-alpine 这个镜像。

但是因为 alpine / slim 太轻量了,会与平常我们使用的 Ubuntu / Centos 有很大的区别,很多的工具包是没有的,当你不确定自己可以处理这些问题的时候,并且对体积不是很敏感的时候,不建议使用。