Macaca 集成平台的部署文档:http://macacajs.github.io/macaca/guide.html#/master-deployment

Docker 是什么

https://www.docker.com/

Docker 这个单词英文原意是码头工人,搬运工的意思,这个搬运工搬运的是各种应用的容器。

官方的说法是,Docker 是提供给开发者和系统管理员一个分布式应用的开放平台。在更多人的理解中,Docker 是一种把你的应用或者服务打包后放在容器中运行的技术。

Docker 可以说有三个关键点:构建,运输,运行。这三个词可以对应我上述提到的那句话,构建即为打包,既然是打包了,那么就需要可以方便地搬运到各个地方,为什么要搬运呢?归根到底,我们是要运行打包了的应用和服务。

Docker 存在的意图是让你把各种语言编写的程序应用以带有普适性的形式打包好,你只要拿到打包好的东西,可以忽略程序本身依赖的环境或者开发的语言,直接使用 Docker 便可以运行起来。在这个实际意义之上,最重要的是 Docker 提供了容器技术来隔离多个打包的应用或者服务之间的相互影响,同时由于打包的东西具有普适性,那么在应用规模的层面上也很便于扩展。

对于运维的管理人员,Docker 提供了一种可移植的标准化部署流程,对于开发者,Docker 提供了一种开发环境的管理方法。

为什么要使用 Docker

https://www.docker.com/enterprise

上述已经提到 Docker 的很多优点,其中,我个人感觉最重要的一点是:普适性,使用 Docker 打包好的应用或者服务,你可以忽略原本程序的依赖以及开发语言。这意味着,当你需要使用多个应用程序代码或者服务时,你不再需要对其一一去处理依赖的环境和类库工具等,直接是安装一个 Docker 就可以上手,这大大提高了部署效率。同时,由于打包好的应用或服务便于
部署的缘故,那么进行应用规模层面的扩展也变得简单的多,并且 Docker 还提供了关于扩展的工具。

对于开发者而言,自己实现了一个应用,当然是希望使用者更快更方便地上手,或者是自己使用了别人提供的程序,亦是希望能够更快更方便用上,而 Docker 恰恰就是带来了这一种便利性。你只需要安装一个 Docker,便可以轻松地跑 Redis,MongoDB,PHP,Java,Node.js 等各种应用,还不需要考虑它们之间相互影响,这是一件多么美妙的事情。

所以,综上所述,Docker 可以帮助我们做到:

Docker 中的一些概念

笔者使用 Mac 系统玩的,所以会更多阐述 Mac 上使用的一些东西。

在安装之前有些 Docker 相关的概念需要了解一下,以便我们更好了解 Docker。

简单来说,安装 Docker 之后,会在机器上跑一个守护进程,你使用 docker 客户端相关命令时,会给到守护进程,由守护进程来处理相关的东西。

在 Linux 系统中,Docker 客户端,守护进程还有你的容器都是跑在本地系统,这意味着容器的端口是直接映射到本地机器,你可以使用 localhost:8376 等来访问相关服务。

在 Mac OS X 系统中,由于 Docker 要使用到 Linux 系统内核的一些特性,所以没法直接在 Mac OS X 上跑。Docker 的守护进程其实是运行在一个称为 default 的 Linux 虚拟机中,所以我们需要 Virtual Box 来使用 Docker。大致如下图:

Docker Linux VM

所以在 Mac OS X 系统中,Docker 的主机地址是这个 Linux 虚拟机的地址。当你使用 docker-machine 来开启虚拟机时,会分配一个 IP 地址。当你开启一个容器时,容器的端口会映射到这个虚拟机的端口。

Docker Machine

Docker Machine 用于在机器或者云端上创建多个虚拟主机。它可以创建虚拟主机然后自动安装 Docker,借助 docker-machine 你可以管理相关的主机。

如果你是 Mac OS X 系统,创建新的虚拟主机很容易,使用以下命令即可:

docker-machine create --driver=virtualbox vbox-test

你也可以使用 --help 来了解命令的更多内容。

如果你要基于云端来创建或者其他驱动,可以参考官方文档:https://docs.docker.com/machine/drivers/

Docker Compose

Docker Compose 是用来定义依赖多个容器运行的应用,例如一个 Wordpress 系统,我们需要数据库,如 Mysql,需要 php 运行时等,这些可以分布在不同的容器。Docker Compose 可以用于管理和配置多个容器相关的东西,多个容器通过 Docker Compose 连接起来形成完整的应用服务。

注意,在我写这篇文章的时候,Docker Compose 还是不支持 windows 系统的,开发者还是使用 Mac 或者 Linux 的好。

Docker Swarm

Docker Swarm 是 Docker 原生的一个集群工具,它可以把多个 Docker 主机转化为单一的虚拟 Docker 主机。

Docker Swarm 遵循 Docker 标准的 API,任何可以和 Docker 守护进程交互的工具,便可以利用 Docker Swarm 来转化为操作多个 Docker 主机。

本文没有更多关于 Docker Swarm 的介绍,想要了解更多可以参考:Docker Swarm

Docker Image

Docker 镜像,便是某个应用或者服务打包好的,一个静态的包括应用或者服务相关信息的东西,在我们使用 Docker 运行某个应用时,我们需要从远程服务器上拉取相应的镜像。

一个 Docker 的镜像是可以做很多事情的,甚至复杂到来启动一个数据库,来等待用户增删改查,都是可以的。

Docker 镜像的意义在于让人们可以创建和分享应用,使用 Docker 镜像,你不用考虑这个应用或者软件能否在机器上跑,Docker 的容器总是可以运行 Docker 镜像的。

官方提供了一个获取和发布镜像的地方:https://hub.docker.com

Docker Container

Docker 容器,是某个镜像的一个运行中的实例,容器可以理解为 Linux 操作系统的一个基础版本,镜像是加载到容器的软件内容。

容器的运行情况,取决于镜像是由什么内容构建的,一个镜像可能只是运行简单的一个命令后便退出,最简单的镜像例子 hello-world 就是这样的。

我们可以通过 Docker 客户端的命令来管理容器,例如重启,停止,销毁等。

有一个需要留意的点是,Docker 的容器是一次性的,其本身并不具备持久化的东西。例如我们运行一个 MySQL 的容器,如果容器重启了,在其运行期间写入的所有数据是不会存在的,因此,Docker 提供了一种方式让系统的某个目录映射到容器的某个目录中,以便做文件或数据的持久化处理,往往许多数据库的容器都需要用到,这个后边会再详述。

Docker 安装

在 Mac OS X 系统中,首先你要下载安装包安装:Docker Toolbox

安装过程中,可以选择是否安装 Docker Machine,Docker Compose 等,默认还是全部都安装上的比较方便。

安装好了,相关的东西就都有了。你可以看到这个,可以尝试直接打开。

Docker Quickstart Terminal

然后你可以在命令行中跑一个 docker run hello-world 试试。留意下输出的内容。

如果是其他系统,参考官网文档:https://docs.docker.com/engine/installation/ ,安装过程不算特别复杂,Linux 系统留意一下内核版本是否满足 Docker 的要求。

Docker 的使用

当你执行 docker run hello-world 时,Docker 会做这些事情:

我们还可以再跑一下官方提供的简单例子:docker run docker/whalesay cowsay boo-boo

Docker 命令

Docker 提供了很多命令来管理镜像和容器,我们可以使用 --help 来查看帮助详情,这里提及几个常用的。

值得注意的是,docker run 是运行一个镜像,所以每一次跑都会创建一个新的容器,如果你并不是需要多个容器的话,使用 docker start/restart/stop/kill 来管理。

run 和 exec

如果我们需要一个 node 镜像,可以使用 docker run node 来运行,如果本地没有该镜像,会下载安装。

下载安装之后,我们可以使用 docker run -a stdin -a stdout -i -t node node 来进入运行中的 node 容器的 node REPL 环境。

docker run 命令可以携带很多参数来帮助你处理更多的需求,可以使用 docker <command> --help 来获取某个特定命令的相关帮助,例如 docker run --help

我们也可以使用 docker exec --help 来看一下 exec 命令的相关描述,run 用于运行某一个镜像,但是当你需要进入某个运行中的容器去执行某一些命令时,你需要使用 exec,例如,在 ubuntu 容器中输出 $PATH 变量:

docker exec -it ubuntu echo $PATH

你也可以使用 exec 来进入到某个容器中的 bash 来管理容器内容:

docker exec -it ubuntu bash

更多相关内容可以参考官方文档:

容器间共享文件

Docker 的容器和外部环境是相对隔离的,并且容器是一次性的,运行结束后并不会有任何的持久化的文件或者数据。所以当我们需要做应用数据的持久化,或者保留应用的日志文件时,我们需要用到 Docker 提供的一些方法来把外部系统的目录映射到容器的目录,从而达到把我们需要的持久化数据存放在特定位置的目的。

对于 docker run 命令,我们可以使用 docker run -v [host-src]:[container-dest][:<options>] 来指定目录的映射。其中,host-src 是系统目录,container-dest 是要映射到的容器里的目录,options 是可选的配置选项,通常是权限配置,例如 ro 只读,或者 rw 可读可写等。

假设我们要把 /var/tmp 映射到容器中的 /var/tmp,我们可以这么做:

docker run -v /var/tmp:/var/tmp:rw -it ubuntu bash

更多的相关内容可以参考:mount volume

所以,你可以通过把一个系统目录映射到多个容器中去,通过这种方式在系统和多个容器中共享文件和目录。

更加有趣的时,Docker 可以运行一个容器,然后使用 --link 来连接到现有运行的一个容器中,去获取某些需要的数据或者文件,例如 reliable-master 中用到数据导出脚本:mongo-dump.sh

创建一个自己的镜像

创建一个目录,然后新建一个 Dockerfile 文件,Docker 在创建镜像时要基于这个文件的配置,我们看下这个文件应该有哪些内容:

FROM: docker/whalesay:latest

RUN apt-get -y update && apt-get install -y fortune

CMD /usr/games/fortune -a | cowsay

FROM 表示即将创建的镜像基于哪一个镜像来创建,假设你要创建一个 node 应用的镜像,那么你可能是 FROM: node:latest

RUN 表示在创建镜像时,运行后边的命令来更新镜像的相关内容,很多情况下是用于安装你应用相关的依赖,所以 apt-get 用得很多。

CMD,表示容器运行镜像时默认执行的命令,当我们上边这个镜像创建后,使用 run 时,会在镜像加载好了之后在容器环境中运行 /user/games/fortune -a | cowsay。一个镜像只能有一个 CMD 配置,如果有多个,默认是使用最后一个。

写好 Dockerfile 文件之后,在该目录下运行 docker build -t docker-whale .

-t 用于指定镜像的名称,通常我们会使用 <组织名>/<应用名> 来作为镜像名称,可以参考 Docker Hub 上现有的镜像。

接下来就可以看到 Docker 开始创建镜像,结束之后,如果没报错信息,那么你可以使用 docker images 查看到你刚才创建的镜像。

现在很多的项目都会带上 Dockerfile 文件,例如 docker-node 等,所以你可以尝试去了解这些项目的 Dockerfile 文件,看看是如何构建镜像的。

关于 Dockerfile 详细的说明可以参考官网文档:Dockerfile reference

由于 Docker 特殊的文件分层以及容器创建的步骤,良好地编写 Dockerfile 可以让容器运行地更加高效,推荐这一篇文章 How to optimize your dockerfile

使用 Docker Compose

Docker Compose 的安装这里不赘述了,根据官网文档很容易便可以安装好:https://docs.docker.com/compose/install/

由于 Docker Compose 是一个很有用,并且相对常用的东西,所以在这简单提及一下,Docker Compose 是可以把多个容器连接到一起统一管理的工具,我们先来看下是怎么使用的。

可以看下官网提供的 Demo ,了解一下 Docker Compose 使用起来是怎么样的。

首先,我们需要创建一个 docker-compose.yml 文件,例如官网给出的 wordpress 例子

version: '2'
services:
  web:
    build: .
    command: php -S 0.0.0.0:8000 -t /code/wordpress/
    ports:
      - "8000:8000"
    depends_on:
      - db
    volumes:
      - .:/code
  db:
    image: orchardup/mysql
    environment:
      MYSQL_DATABASE: wordpress

一个 docker-compose.yml 大致的内容如上,一个主要的服务名称,然后便是这个这个服务对应的容器相关的配置,例如容器名称,构建的目录,端口映射,容器连接,对外的环境变量,目录映射,运行命令等。整个配置文件相对清晰易懂。

在这个文件中,可以编写多个服务的配置,然后在该目录下运行 docker-compose up 便可以运行这个文件配置的相关服务,而无须手动地一个个去启动需要的容器,同时,这个配置文件管理各个容器之间的关系也相对容易。更多详细内容请参考官方文档 compose file

官方提供了很多例子来说明如何使用 Docker Compose 来构建需要多个容器配合的应用,例如 wordpressdjango 等,通常我们要创建一个 Web 应用,是离不开数据库,缓存,web 系统本身等多个服务支撑,Docker Compose 可以帮助我们更好地使用多个容器来创建和管理复杂的系统。

Docker 在 Macaca & reliable 部署中的实践

Macaca 自动化测试的管理平台是基于 reliable 持续集成服务上运行的,reliable 提供了基于 Docker 进行服务部署的方案,我们来看一下在 Macaca & reliable 中是如何使用 Docker 的。

reliable 镜像

reliable-master 中,创建了一个基础的 reliable 的 Docker 镜像,来打包 reliable 应用服务。你可以查看整个 Dockerfile

整个镜像是基于 Node@4.2.1 版本的 Docker 镜像,首先配置 ENV ZEROMQ_VERSION 4.1.3 的环境变量用于安装和使用 zeromq,reliable 的分布式消息通信是使用 zeromq 实现的,所以需要在镜像中安装好 zeromq。

RUN apt-get update && apt-get install -y \
    libtool \
    automake \
    autoconf \
    uuid-dev \
    python-dev \
    g++ \
    python-setuptools
...

接着需要安装一系列依赖的基础库和工具,切换工作目录后,再安装 zeromq:

WORKDIR /usr/local

RUN wget http://download.zeromq.org/zeromq-$ZEROMQ_VERSION.tar.gz \
    && tar -zxvf zeromq-$ZEROMQ_VERSION.tar.gz \
    && cd ./zeromq-$ZEROMQ_VERSION \
    && ./configure && make && make install \
    && ldconfig

WORKDIR /

COPY . /reliable-master

WORKDIR /reliable-master

然后再切换到根目录下,把当前的源码放到容器根目录的 reliable-master 中去,并且切换到 reliable-master 代码目录下。接着便是 Node.js 很常见的使用 npm 安装需要的依赖,然后运行配置好的 make build 命令,来构建静态资源。

在这里使用国内的淘宝 npm 源来加速依赖的下载:npm install --registry=https://registry.npm.taobao.org

为什么这个镜像的容器运行中,会有 make 命令,因为在这之前装了 automake。为什么会有 npm 命令,因为整个镜像是基于 Node 镜像来创建的。

这里有一段配置是注释掉的,如果你需要配置 GitLab 或者 GitHub 需要,那么你需要去掉注释,把对应的内容修改好,例如 SSH Key 的文件名和应用的域名,来保证容器中可以正确地获取 SSH Key 来配置 GitLab 或者 GitHub 的接入。

最后,是暴露对应的服务端口以及运行 reliable 代码:

EXPOSE 8080

CMD ["./bin/reliable-master", "server -p 8080"]

至此,一个完整的 reliable 应用镜像配置便完成了。

由于 Docker 是从 hub.docker.com 拉取我们需要的镜像,介于国内网络的原因,可能在拉取镜像时候速度会比较慢,建议使用 阿里云 Docker 加速

完整的服务,使用 compose

整个 reliable 应用,其实除了 Node 实现的应用代码外,还依赖于 Redis 来做缓存,MongoDB 来做数据持久化,所以要将整个应用使用 Docker 跑起来,还需要其他服务的镜像,当然,这一切可以使用 Docker Compose 来处理。

reliable 中创建了一个 docker-compose.yml,里边包含了多个依赖服务的配置,我们来简单看一下是如何配置的。

reliable-master:
  container_name: "reliable_master_${RELIABLE_ENV_CONFIG}"
  restart: always
  build: .
  ports:
    - "${RELIABLE_ENV_PORT}:${RELIABLE_ENV_PORT}"
  links:
    - mongo:mongo
    - redis:redis
  environment:
    - DB_SERVICE=mongo
    - DB_PORT=27017
    - DB_NAME=reliable_dev
  volumes:
    - /etc/localtime:/etc/localtime:ro
  command: make serve env=${RELIABLE_ENV_CONFIG}

首先是 reliable 主应用的配置,其中有一些依赖于环境的变量,如 ${RELIABLE_ENV_CONFIG} ,用于控制不同环境运行不同配置的服务,例如生产环境和开发环境。

其中值得留意的是:

links 下的配置格式是 [服务名称]:[别名],这里定义了 mongoredis,用于连接其他容器,以便于使用其他容器提供的服务,配置好别名后,便可以用其作为 hostname 在容器中进行访问,links 也可以使用环境变量来获取相关容器的地址,详细可以参考:docker-compose 中的 linkslinks 的环境变量

这个用于配置目录的映射,上述的配置是把服务器的 /etc/localtime 直接映射到容器中,为了保持容器和服务器的时区和时间同步。

我们再来看下 db 的配置:

db:
  container_name: "reliable_db_${RELIABLE_ENV_CONFIG}"
  restart: always
  image: mongo
  volumes:
    - /var/lib/mongo
    - /etc/localtime:/etc/localtime:ro
  command: "true"

这个服务的配置主要的作用是提供数据持久化的功能,它会把服务器上的目录映射到容器中,作为数据存放的目录,来提供给下边的 mongo 使用,所以我们顺带看下 mongo 的配置:

mongo:
  container_name: "reliable_mongo_${RELIABLE_ENV_CONFIG}"
  restart: always
  image: mongo
  volumes_from:
    - db

其中的 volumes_from 配置便是把 db 的整个目录映射过来,这样,mongo 保存的数据就可以存放到我们系统中对应的路径。

基本上 reliable 使用的 Docker Compose 也就是这样,其他部分都是大同小异了。

现在 Docker Compose 有两个版本的配置文件,reliable-master 用的暂时是旧的,过后会进行升级,两个版本间的差异和升级可以参考文档:Versioning

reliable 提供了一个 Makefile

reliable 提供了一个 Makefile,来简化所有的 docker 相关的操作,所以你在使用 reliable-master 的过程中,你可能没使用到 Docker 或者 Docker Compose 相关的命令,我们可以看下这个 makefile,其中有多个命令是使用到 Docker Compose 的,如:

logs:
    RELIABLE_ENV_PORT=${port} RELIABLE_ENV_CONFIG=${env} docker-compose logs
status:
    RELIABLE_ENV_PORT=${port} RELIABLE_ENV_CONFIG=${env} docker-compose ps
serve:
    $(server_cmd)
deploy: stop
    RELIABLE_ENV_PORT=${port} RELIABLE_ENV_CONFIG=${env} docker-compose build
    RELIABLE_ENV_PORT=${port} RELIABLE_ENV_CONFIG=${env} docker-compose up -d
start:
    RELIABLE_ENV_PORT=${port} RELIABLE_ENV_CONFIG=${env} docker-compose start
stop:
    RELIABLE_ENV_PORT=${port} RELIABLE_ENV_CONFIG=${env} docker-compose kill

Docker Compose 的命令其实大多数都对应了 Docker 的使用命令,例如 logspsstart 等。

前边的这一部分 RELIABLE_ENV_PORT=${port} RELIABLE_ENV_CONFIG=${env} 都是用来指定环境变量的,只是为了给 docker-compose 传入对应的变量,然后就是运行 docker-compose 相应的命令,更多相关的命令可以参考 文档。如果你比较熟悉 Docker 命令了,其实 Docker Compose 使用也就不难了。

如何处理容器中的数据

在之前的 Makefile 中有这样的命令:

dump:
    RELIABLE_ENV_CONFIG=${env} ./scripts/mongo-dump.sh reliable ~/reliable.tar
restore:
    RELIABLE_ENV_CONFIG=${env} ./scripts/mongo-restore.sh ~/reliable.tar reliable

用于导出和导入数据,可以用来做数据备份。这里调用的是在 scripts 中的脚本,我们选择一个来看看:scripts/mongo-dump.sh

其中最最重要的就是这一句了,稍微有点长:

docker run -i --rm --link reliable_mongo_${RELIABLE_ENV_CONFIG}:mongo -v /tmp/mongodump:/tmp mongo bash -c 'mongodump -v --host $MONGO_PORT_27017_TCP_ADDR:$MONGO_PORT_27017_TCP_PORT --db '$1' --out=/tmp && chmod 777 /tmp/*'

这一个 Docker 命令所做的事情是:

这样子,我们便可以从 /tmp/ 目录中获得导出的数据了。

scripts/mongo-restore.sh 的实现原理大致相似,只不过把导出命令调整为导入命令。本质上就是利用了 Docker 容器间的连接通信以及目录的映射。

还有更多东西

Docker 是一个很棒的东西,在简单的使用过程中,能够很快便体会到它所带来的便利和快捷。这一篇文章仅仅只是一个入门级别的介绍,希望能够引起更多人对于 Docker,reliableMacaca 的关注。Docker 还有很多很多的东西需要我们去了解和学习,例如多个容器的网络管理,容器的资源配置和管理等,最后推荐一些学习 Docker 的好资源:


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