一、前言

STF 官方的搭建文档中推荐用 docker 来搭建整个平台,包括 rethhinkdb、nginx 都建议采用 docker 来搭建。事实证明,用 docker 真的是一个很方便的事情。

根据我的经验,用 docker 搭建至少有以下几个好处:

但是构建 STF 的 docker 镜像并不是一件容易的事,很多人拿官方的 Dockerfile 直接构建,发现不仅容易构建失败,而且构建速度很慢,甚至需要几个小时,这样不利于我们快速开发。本文根据 STF 的原理,介绍了快速构建 STF Docker 镜像的方法,可以做到每次修改后构建时间不超过 5 分钟。

二、STF 官方 docker 镜像介绍

关于 docker 的安装这里就不再详细介绍,网上有很多教程可以参考,不过还是建议去 docker 官方找一下对应系统的安装方法。

拉取官方 STF 的 docker 镜像很简单,pull 一下即可:

docker pull openstf/stf

openstf/stf 镜像中的代码全部在/app 文件夹,用下面的命令可以进入 docker 镜像中查看文件:

docker run --rm -it --user root openstf/stf /bin/bash

这样就创建了一个 STF 的 docker 容器。可以看出,/app 目录中的文件几乎和 github 上的源码一样,事实上,这些文件都是通过 npm pack 打包进来的,后面会做详细介绍。

在 docker 容器中执行 stf doctor,可以看到 stf 命令执行成功了,然后我们用 which stf 看一下 stf 命令安装在哪里了,在这里:

/app/bin/stf

如果观察了 stf 源码中的 Dockerfile,可以看到有一句

ENV PATH /app/bin:$PATH

这句话把/app/bin 目录加入了系统可执行目录,所以我们可以直接执行 stf 命令。关于 Dockerfile 后文会作详细介绍。

在 app 目录还有一个 node_modules 目录,这是 stf 项目所依赖的所有其他(非系统)模块,node_modules 里的模块可以在 package.json 里找到,这里提到 node_modules 目录是因为这个目录本身很大,有两三百 M 的样子,很多同学做镜像的时候对它处理不够完美,生成的镜像变化很大,每次 push 的时候很慢。

三、构建自己的镜像

先简单介绍一个 docker,docker 是利用 linux 内核的 namespace 机制分隔成了不同的空间,这些空间完全独立,可以拥有自己不同的环境。事实上,常见的 linux 系统如 ubuntu 和 centos 等都是由一个统一的内核加上自己软件组成,而 docker 是基于内核虚拟化的,因此,你可以在 ubuntu 系统上启动一个基于 centos 镜像的 docker 容器,也不用担心基于 ubuntu 构建的 stf 镜像不能运行在 centos 系统上。

STF docker 镜像其实就是一个完整的 linux 系统加上 stf 环境和代码,因此,很容易想像,构建镜像的过程大概是这样的:

  1. pull 一个操作系统作为 base 镜像,这里以 ubuntu 为例。
  2. 在 ubuntu 上安装 stf 基础环境,包括 nodejs、zmq、python、git 等。
  3. 把 stf 的代码塞到 docker 镜像中。

3.1 官方 Dockerfile 介绍

其实,stf 的源码中已经给出构建官方 docker 镜像的 Dockerfile,就在源码的根目录。

[Dockerfile]https://github.com/openstf/stf/blob/master/Dockerfile)

如果你的网络足够好,按照这个 Dockerfile 来构建镜像完全没问题,但实际上,这里有更好的方法。先介绍一下这个 Dockerfile 的内容。先看第一部分

FROM ubuntu:16.04
ENV PATH /app/bin:$PATH
WORKDIR /app
EXPOSE 3000

首先是基于 ubuntu 16.04 进行构建,如果 ubuntu 16.04 更新了而且 pull 下来了,下次构建也会基于最新的 ubuntu。

RUN export DEBIAN_FRONTEND=noninteractive && \
    useradd --system \
      --create-home \
      --shell /usr/sbin/nologin \
      stf-build && \
    useradd --system \
      --create-home \
      --shell /usr/sbin/nologin \
      stf && \

这一段的意思大概是设置 docker 为非交互模式,然后添加了 stf-build 和 stf 两个用户,这两个用户都不允许登录,这也许是 docker 镜像构建时的一个技巧,我也没搞太明白。

sed -i'' 's@http://archive.ubuntu.com/ubuntu/@mirror://mirrors.ubuntu.com/mirrors.txt@' /etc/apt/sources.list && \
  apt-get update && \
  apt-get -y install wget python build-essential && \
  cd /tmp && \
  wget --progress=dot:mega \
    https://nodejs.org/dist/v6.9.5/node-v6.9.5-linux-x64.tar.xz && \
  tar -xJf node-v*.tar.xz --strip-components 1 -C /usr/local && \
  rm node-v*.tar.xz && \
  su stf-build -s /bin/bash -c '/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js install' && \
  apt-get -y install libzmq3-dev libprotobuf-dev git graphicsmagick yasm && \
  apt-get clean && \
  rm -rf /var/cache/apt/* /var/lib/apt/lists/*

这一段是配置 Docker 镜像的基础环境,source.list 是给 ubuntu 加上 source,如果是自己构建的话最好换成国内的镜像,这样会快很多。下面一段是安装 wget python build-essential 和 nodejs 等基础环境,最后安装了 libzmq3-dev libprotobuf-dev git graphicsmagick yasm 这些软件或者库,libzmq3-dev 就是 zmq 的库,libprotobuf-dev 是 protobuf 的库,graphicsmagick 是处理图像的一个工具,graphicsmagick 在处理手机屏幕图像时会用到,yasm 是编译汇编语言的一个工具,在安装 jpegturbo 时会用到,我猜是图像处理工具有时候为了提高效率会采用汇编来编写。

COPY . /tmp/build/

这一句就是把所有源码 copy 到镜像中的/tmp/build/文件夹,便于后续构建。

下面加权限和切用户就不再介绍了,说实话,我也没搞清楚 build 和时候和运行的时候为什么不用同一个用户,难道是为了只读?下面是重头戏,构建的关键部分:

# Run the build.
RUN set -x && \
    cd /tmp/build && \
    export PATH=$PWD/node_modules/.bin:$PATH && \
    npm install --loglevel http && \
    npm pack && \
    tar xzf stf-*.tgz --strip-components 1 -C /app && \
    bower cache clean && \
    npm prune --production && \
    mv node_modules /app && \
    npm cache clean && \
    rm -rf ~/.node-gyp && \
    cd /app && \
    rm -rf /tmp/*

3.2 如何快速构建自己的镜像

从官方的 Dockerfile 中我们发现,它的构建是从基本的 ubuntu 镜像开始的,如果我们每次修改都按照这个步骤来,有很多步骤是多余的,会浪费大量的构建时间。根据 Docker 镜像的特点,我们分两步构建这个镜像,第一步先构建一个 ubuntu4stfbase 镜像,这个镜像包含的内容:

其实这基本上官方 Dockerfile 的前半部分,如果你比较熟悉,也可以把 node_modules 也做到这个 ubuntu4stfbase 镜像中。

每二步就是把代码放到镜像中了。

注意这里的一个大坑,如果你发现 ADD *.tgz 文件时一直提示找不到,请检查.dockerignore 文件,这个文件是隐藏的!!!
https://stackoverflow.com/questions/32997269/copying-a-file-in-a-dockerfile-no-such-file-or-directory

把下面的文本保存为 Dockerfile-base

FROM ubuntu:16.04

# Sneak the stf executable into $PATH.
ENV PATH /app/bin:$PATH

# Work in app dir by default.
WORKDIR /app

# Export default app port, not enough for all processes but it should do
# for now.
EXPOSE 3000

COPY ./sources.list  /etc/apt/sources.list

# Install app requirements. Trying to optimize push speed for dependant apps
# by reducing layers as much as possible. Note that one of the final steps
# installs development files for node-gyp so that npm install won't have to
# wait for them on the first native module installation.
RUN export DEBIAN_FRONTEND=noninteractive && \
    useradd --system \
      --create-home \
      --shell /usr/sbin/nologin \
      stf-build && \
    useradd --system \
      --create-home \
      --shell /usr/sbin/nologin \
      stf && \
    apt-get update && \
    apt-get -y install wget python build-essential && \
    cd /tmp && \
    wget --progress=dot:mega \
      https://npm.taobao.org/mirrors/node/v6.9.5/node-v6.9.5-linux-x64.tar.xz && \
    tar -xJf node-v*.tar.xz --strip-components 1 -C /usr/local && \
    rm node-v*.tar.xz && \
    npm install -g cnpm --registry=https://registry.npm.taobao.org && \
    cnpm install -g node-gyp-install && \
    export NVM_NODEJS_ORG_MIRROR=http://npm.taobao.org/mirrors/node && \
    su stf-build -s /bin/bash -c 'node-gyp-install' && \
    apt-get -y install libzmq3-dev libprotobuf-dev git graphicsmagick yasm && \
    apt-get clean && \
    rm -rf /var/cache/apt/* /var/lib/apt/lists/*

# Give permissions to our build user.
RUN mkdir -p /app && \
    chown -R stf-build:stf-build /app

然后执行

docker build -t ubuntu4stfbase -f ./Dockerfile-base ./

好了,现在一个叫做 ubuntu4stfbase 镜像构建好了,这个镜像的作用是为 STF 提供一个基础的环境。

下面创建 STF 镜像,叫做 stf-image-finally 吧,这个镜像的作用是把 node_modules 和代码给集成进去。在做这一步的时候需要注意几点:

下面开开始构建 stf-image-finally,使用 Dockerfile-build 文件,Dockerfile-build 的文件内容如下:

FROM ubuntu4stfbase

COPY ./node_modules /app/node_modules
ADD ./stf-*.tgz /app

# Give permissions to our build user.
RUN mkdir -p /app && \
    chown -R stf-build:stf-build /app

# Switch over to the build user.
USER stf-build

# Run the build.
RUN set -x && \
    export PATH=$PWD/node_modules/.bin:$PATH && \
    mv /app/package/* /app && \
    rm -rf /app/package

# Switch to the app user.
USER stf

# Show help by default.
CMD stf --help

再次提示,如果找不到 node_modules,而实际上 node_modules 真的在当前目录,请检查.dockerignore

然后再执行

docker build -t stf-image-finally -f ./Dockerfile_stfbase ./

因为 node_modules 这个文件夹非常大,在构建 docker 的时候最好分成一个单独的命令,意思是说不把 node_modules 和其他经常改动的代码放在 Dockerfile 中的一行,这样每次改动的变化比较大,docker push 的时候非常花时间,这点儿如果对 docker 不了解的话会比较难理解。

举个例子,从下面的 docker 镜像日志可以看出每条命令都会做个 commit,相同的命令会重复利用上次的结果,所以不要把 node_modules 和其他文件一块儿 ADD 到镜像中。

Sending build context to Docker daemon 440.1 MB
Step 1 : FROM ubuntu4stfbase
 ---> a4ae40f87e4e
Step 2 : COPY ./node_modules /app/node_modules
 ---> Using cache
 ---> 3f5a951c6e3a
Step 3 : ADD ./stf-2.3.0.tgz /app
 ---> Using cache
 ---> 3395342c5bba
Step 4 : RUN mv /app/package /app
 ---> Running in 6c5a51ccf15b

docker 镜像构建好了,现在可以验证一下了:

docker run --rm -it --net host stf-image-finally /bin/bash

然后执行 stf local 看看能不能正常工作。这样,以后每次对 stf 源码进行了修改,只要执行一下 npm install 和 npm pack,重新构建 stf-image-finally 就行了,整个过程不超过 5 分钟,如果你对 node_modules 的改动较少,甚至可以把 node_modules 放到 ubuntu4stfbase,这样每次构建会更快。

四、结束语

docker 真的是一个很方便的东西,它完全解决了环境不一致的问题,用 docker 部署 STF 可以用完美来形容。关于本文,有几点需要注意:

  1. 本文所介绍的东西是我亲自验证过的,可行的。
  2. 本文所介绍的东西是基于原理来讲的,就是说把文中的代码直接拷下来可能在你的环境中不能执行成功,请根据文中的思想修改你的代码。
  3. 写的时间仓促,发现任何错误请留言指正!


↙↙↙阅读原文可查看相关链接,并与作者交流