一、前言

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. 写的时间仓促,发现任何错误请留言指正!


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