STF STF 的 docker 镜像介绍及快速构建方法

blueshark · July 15, 2017 · Last by 丽丽 replied at November 01, 2019 · 4282 hits

一、前言

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

根据我的经验,用docker搭建至少有以下几个好处:
  • 可以避免多台机器上环境的重复搭建。在每台机器都都搭建node、zmq等STF运行环境简直是一个恶梦,而且需要配置adb等工具,而采用docker以后可以省去这些烦恼,还可以保证环境完全一致。

  • 快速建立节点。使用docker建立节点非常快,把docker镜像拉下来以后运行一条启动命令即可。

  • 版本快速回滚。如果直接把STF环境运行在主机上,发现新版本出问题以后首先要用git回滚到老代码,然后再打包一下,在这期间难免会遇到一些问题,处理不好就会影响整个系统的可用性。而用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/*
  • 先说一下这个set -x,好像在很多地方看到过,它的意思是说把脚本内容本身打出来,这样便于确定哪一行脚本得到什么结果。
  • export这一行是把node_modules里的一些模块加入到系统路径,这样可以直接在命令行中执行。
  • npm install --loglevel http这句话的意思是npm install的时候把http的请求打印出来,详细可以查一下loglevel这个参数,其实不关心这个也无所谓的。
  • npm pack这是一个重要的地方,别看这条命令是最短的。详细的说明在这里: npm pack,这条命令可以把node项目打包成一个压缩文件,以-.tgz格式命名,其中name和version可以在package.json中找到。执行完npm pack以后就可以在项目目录下看到一个stf-2.3.0.tgz的文件。注意,npm pack是不会打包node_modules文件夹的。npm pack和java的maven打包有点像。
  • tar xzf stf-*.tgz --strip-components 1 -C /app这句话的意思是把npm pack好的压缩包解压到/app文件夹,--strip-components 1这个参数的意思是不要目录,直接解压到/app中。
  • bower cache clean清除bower的cache。有一点儿需要注意,bower是安装第三方js库的一个管理工具,在执行npm install的时候已经执行bower install了,不信可以去看package.json里的"scripts"这个字段。
  • npm prune --production的意思是删除开发中的依赖模块devDependencies,这样可以减小镜像体积。
  • mv node_modules /app把node_modules移动到app文件夹,因为此时的工作目录是在/tmp/build中,node_modules可以理解为java中的第三方jar包。
  • 后面就是清理工作了,不再多介绍。

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

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

  • 更换为国内的ubuntu源,安装软件更快。
  • 基于的nodejs、pyhton环境。
  • 将npm更换为更快的cnpm工具。
  • 基本的zmq protobuf graphicsmagick git yasm等工具。

其实这基本上官方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和代码给集成进去。在做这一步的时候需要注意几点:

  • 由于是把本机的代码直接拷贝到Docker镜像中,需要保证本机环境和Docker镜像环境一致,包括操作系统和nodejs版本等。
  • 在拉取node_modules的时候,即执行npm install的时候,可以暂时把这一段去掉 "scripts": { "test": "gulp test", "prepublish": "bower install && not-in-install && gulp build || in-install" }, 这一段的作用是使用bower安装前端的东西,当然,brower install最终肯定要在npm pack之前执行。
  • 下面Dockerfile-build中stf-*.tgz是使用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可以用完美来形容。关于本文,有几点需要注意:

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

最近在部署STF,大赞呀!

—— 来自TesterHome官方 安卓客户端

赞赞

赞,想到一起了,我们也是直接编译后连源码+node_module一起拷进镜像中。不过我们偷了个懒,直接基于官方 stf 镜像构建,省了 base 镜像。

这个构建不需要安装rethinkdb吗?

我这里构建完后,启动报错

6Floor has been deleted
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up