STF 官方的搭建文档中推荐用 docker 来搭建整个平台,包括 rethhinkdb、nginx 都建议采用 docker 来搭建。事实证明,用 docker 真的是一个很方便的事情。
可以避免多台机器上环境的重复搭建。在每台机器都都搭建 node、zmq 等 STF 运行环境简直是一个恶梦,而且需要配置 adb 等工具,而采用 docker 以后可以省去这些烦恼,还可以保证环境完全一致。
快速建立节点。使用 docker 建立节点非常快,把 docker 镜像拉下来以后运行一条启动命令即可。
版本快速回滚。如果直接把 STF 环境运行在主机上,发现新版本出问题以后首先要用 git 回滚到老代码,然后再打包一下,在这期间难免会遇到一些问题,处理不好就会影响整个系统的可用性。而用 docker 以后直接运行不同的镜像即可,回滚时也只需要切回老的镜像,完全避免了切代码和打包可能带来的问题。
但是构建 STF 的 docker 镜像并不是一件容易的事,很多人拿官方的 Dockerfile 直接构建,发现不仅容易构建失败,而且构建速度很慢,甚至需要几个小时,这样不利于我们快速开发。本文根据 STF 的原理,介绍了快速构建 STF Docker 镜像的方法,可以做到每次修改后构建时间不超过 5 分钟。
关于 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 环境和代码,因此,很容易想像,构建镜像的过程大概是这样的:
其实,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/*
从官方的 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 和代码给集成进去。在做这一步的时候需要注意几点:
"scripts": {
"test": "gulp test",
"prepublish": "bower install && not-in-install && gulp build || in-install"
},
这一段的作用是使用 bower 安装前端的东西,当然,brower install 最终肯定要在 npm pack 之前执行。下面开开始构建 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 可以用完美来形容。关于本文,有几点需要注意: