使用Docker封装java应用

9次阅读

共计 29068 个字符,预计需要花费 73 分钟才能阅读完成。

使用 Docker 封装 java 应用

本文介绍如何使用 docker 应用封装一个 java 应用(名字叫 cspj)。这个 java 应用涉及数据持久化以及 RMI 调用。

1. docker 介绍

docker 是一种容器技术,对操作系统、文件系统、网络等进行了封装,使其中的进程可以完整运行。

docker 和虚拟机是不同的技术。虚拟机虚拟了一套硬件环境,需要在这个硬件环境之上安装完整的操作系统、jdk 等相关软件,才能运行一个 java 应用,虚拟机和宿主机在操作系统层面就是相互隔离的。以 Linux 下的 docker 为例,它使用的依旧是宿主机中的 Linux 内核,只不过在宿主机的用户层上虚拟了一个容器,这个容器中的 Linux 只是操作系统中的一小部分,使用的依旧是宿主机的 Linux 内核。比如宿主机是 Ubuntu,docker 虚拟的操作系统可以是 alpine,这只是虚拟了 alpine 和 Ubuntu 不同的部分。

一个 docker 容器中一般只运行一个应用,这和虚拟机也是不同的。比如我们的一个应用有 java 应用,有数据库 mysql,那么 java 应用运行在一个容器里,mySql 运行在另一个容器里。他们之间可以通过 docker 虚拟的网络进行交互。

docker 中的容器就是运行中的进程。它是通过镜像进行启动的。docker 中的镜像就相当于一个模板,启动一个容器就相当于通过模板创建一个可执行的应用。因此,只要镜像不变,所有通过这个镜像创建的容器都是一摸一样的。又因为 docker 进行了操作系统、文件系统、网络等方面的封装,所以这个镜像就可以在各种不同的环境上运行,从而保证一致的执行效果。

容器运行之后,在其中会有一个可读写层,这是用来临时保存容器中应用在运行中产生的数据的。当这个容器被销毁之后,所保存的数据也就消失了。使用原有的镜像重新运行一个新的容器,就又是一个全新的应用了。

所以,如果我们需要对容器中的数据进行持久化,就需要用到 volume 或者 bind mounts 技术。比如我们的 java 应用中有一个内置文件数据库 Derby,如果需要保留对这个文件数据库的修改,同时又不想改变镜像文件,就可以把这个文件数据库使用 volume 或 bind mounts 技术保存到宿主机的文件系统中。这样,即使容器被销毁,容器中所修改的文件数据库也会被保留下来。

还有一种方法保存容器中的临时数据,就是使用 commit 命令把容器可读写层中的临时数据也一起生成一个新的镜像。以后通过这个新镜像运行的容器,就都保留了这部分数据,这部分数据也就成了新镜像的一层,而且无法被修改。通过这个新镜像运行的容器,会生成一个新的可读写层,用来临时保存此次运行中生成的数据。如果一直使用 commit 保存数据,新的镜像就会越来越大。docker 官方不推荐使用这种方法保存数据。

在详细说一下 docker 的镜像。docker 的镜像是使用 Dockerfile 制作的。Dockerfile 是一个脚本,docker build 命令会读取这个脚本,按照其指令构造镜像。docker 的镜像是一层一层的。每一个 Dockerfile 指令,都会生成镜像中的一层。

我们自己制作的 docker 镜像通常不会从最底层开始构建。比如我们要制作一个 java 应用的镜像,我们就要依赖于 openjdk:8-alpine 的官方镜像。在这个基础之上,再制作我们的 java 应用镜像层。而官方的 openjdk:8-alpine 则是基于 alpine 操作系统制作的镜像,在这个操作系统之上,它为我们设置好了各种环境变量,我们在这个镜像之上就可以直接制作我们自己的 java 应用镜像,而不必关心 jdk 的设置了。

aphine 是一个特别简洁的官方的 Linux 操作系统系统容器镜像,只有 5M 大小。从中也可以看出 docker 和虚拟机的区别,虚拟机中运行的操作系统一定是完整的操作系统,通常都会有几个 G 的大小。

2. 环境准备

Intel-Core-i7 CPU, 安装 Windows10 操作系统,使用 VirtualBox 安装了 CentOS- 7 虚拟机。我们将在 CentOS- 7 虚拟机上安装 Docker。关于如何在安装设置虚拟机,请参看这里。

如果要执行 8.2 节中的实例,必须使用 VMWare 虚拟机安装 CentOS- 7 系统,因为 VMWare 支持 nested vm。还需要设置 vmware 虚拟机的处理器中,选择“虚拟化 Intel VT-x/EPT 或 AMD-V/RVI(V)。

如果使用 AMD 处理器,则可以使用 VirtualBox 安装 CentOS-7,因为最新的 VirtualBox- 6 支持在 AMD 系统上打开 netstad vm。

VirtualBox 中安装的 CentOS- 7 系统的 IP 地址是 192.168.56.104.

3. 安装 Docker

Docker 分为社区版和企业版,我们使用社区版即可。

  • 卸载旧 docker
$ sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
  • 安装 docker
# 安装依赖
$ sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2

# 设置国内镜像源
$ sudo yum-config-manager \
    --add-repo \
    https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo

# 安装最新版本
$ sudo yum install docker-ce docker-ce-cli containerd.io

# 启动
$ sudo systemctl start docker

# 验证,或从 docker 官方下载 hello-world 镜像并根据镜像运行容器。这个镜像只有不到 2K
$ sudo docker run hello-world

# 把用户添加到 docker 组中,这样执行 docker 命令时就不必使用 sudo 了
$ sudo usermod -aG docker your-user

设置镜像加速器可以加速从 Docker Hub 获取镜像的速度。在 /etc/docker/daemon.json 文件中(如不存在请新建)添加如下内容:

{
  "registry-mirrors": [
    "https://dockerhub.azk8s.cn",
    "https://reg-mirror.qiniu.com"
  ]
}

之后启动服务:

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

更详细的安装方法请参看 Get Docker Engine – Community for CentOS 和安装 Docker

docker 其实是 C / S 模式的,我们在 Linux 终端输入的 docker 命令其实是客户端,后台还有一个服务端在运行。客户端和服务端可以不运行在同一个机器上。

4. 制作镜像

4.1 准备镜像文件

新建一个空目录,把 java 应用程序放入到这个目录中,并新建 Dockerfile 文件。这里我们先不考虑临数据库持久化的问题,直接把所有应用程序进行打包:

[eric@centos7min2 cspj-server]$ ll
total 4
drwxrwxr-x. 2 eric eric 206 Sep 28 21:53 bin
drwxrwxr-x. 2 eric eric 207 Sep 27 16:23 conf
drwxrwxr-x. 4 eric eric  92 Sep 29 14:03 database
-rw-rw-r--. 1 eric eric  93 Sep 29 14:19 Dockerfile
drwxr-xr-x. 3 eric eric 278 Sep 27 16:12 lib

这个 java 应用程序的启动脚本是 bin/startServer.sh,这个脚本中启动命令最后有 & 符号,需要去掉。因为容器中运行的程序都是在前台运行的,如果加上 & 符号,这个在前台运行的 startServer.sh 脚本就执行完毕,这个容器也就立即停止了。

bin/setEnv.sh 中设定了一些 RMI 参数,为了可以进行 RMI 连接,设置其内容如下:

#!/bin/sh

export IP=`awk 'END{print $1}' /etc/hosts`
echo "$IP cspj-host" >> /etc/hosts
cat /etc/hosts

export JAVA_EXECUTE=java
export CSPJ_LIBPATH=../lib/*.jar
export CSPJ_LIBPATH_OPT=../lib/opt/*.jar
export CSPJ_CLASSPATH=../conf/
export JVM_OPTARGS="-Xmx1024m -Xms1024m"
export CSPJ_OPTARGS="-Dcspj.home=$PWD/../ -Dfile.encoding=UTF-8"
export CSPJ_JMXARGS="-Djava.rmi.server.hostname=cspj-host -Dcom.sun.management.jmxremote.port=9998 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"

echo $CSPJ_JMXARGS

其中 IP 是容器运行时动态获取容器的 IP 地址,把这个地址写入到 /etc/hosts 中时为了后续进行 RMI 连接,设置 -Djava.rmi.server.hostname=cspj-host 也是为了后续的 RMI 连接

4.2 编写 Dockerfile

Dockerfile 中内容为:

FROM openjdk:8-alpine
COPY . /cspj-server/
WORKDIR /cspj-server/bin
CMD ["./startServer.sh"]
  • FROM 指令表示我们的镜像所基于的基础镜像。这里我们使用的是 openjdk:8-alpine。官方镜像都可以从 docker hub 中搜索。比如搜索 openjdk,在 openjdk 界面点击 TAGS 页面,输入一些过滤信息,就可以找到对应版本。比如我们还可以使用 8 -jre-alpine 进行过滤,只使用 jre
  • COPY 指令表示把当前目录下的所有文件(Dockerfile 除外)复制到镜像中的 /cspj-server/ 目录。注意一定要有最后的“/”符号,表示复制到这个目录中。由于上一条指令我们基于的基础镜像 openjdk:8-alpine 就已经包含了一个基础的 Linux 文件系统,所以执行 COPY 命令时,就是在镜像中的文件系统中进行操作。COPY 命令会自动在这个文件系统中创建不存在的 /cspj-server/ 文件夹
  • WORKDIR 指令指定镜像的工作目录。相当于在镜像的文件系统中进入这个目录,并把这个目录设置为工作目录
  • CMD 指令指定通过这个镜像启动容器时需要执行什么命令。我们这里把应用程序的启动脚本作为执行命令

在构建镜像时,docker 中每条指令都会构建一层,所以如果有 RUN 命令时,一般把多个操作都写在一行里。

4.3 构建镜像

在刚才的目录中,执行 docker build -t ws3495/cspj-server:v1.0.0 . 命令,构建镜像。:v1.0.0可以省略,此时默认是:latest。注意不要丢掉最后的“.”,它以宿主机的一个文件夹作为 ”context”,Dockerfile 中的指令就是基于这个“context”进行构建的。比如这个 docker build 命令指定了当前路径(/home/eric/dockertest/forbuildimage/cspj-server)为“context”,那么在 Dockerfile 中,COPY . /cspj-server/ 指令中的“.”指的就是宿主机的 /home/eric/dockertest/forbuildimage/cspj-server 目录。关于 docker build 指令可以参考 docker build,关于上下文可以参考这里。

通过执行刚才的命令,其执行过程为:

[eric@centos7min2 cspj-server]$ docker build -t ws3495/cspj-server:v1.0.0 .
Sending build context to Docker daemon   32.8MB
Step 1/4 : FROM openjdk:8-alpine
 ---> a3562aa0b991
Step 2/4 : COPY . /cspj-server/
 ---> 27361ab40a65
Step 3/4 : WORKDIR /cspj-server/bin
 ---> Running in aef7152e561a
Removing intermediate container aef7152e561a
 ---> b0fcdabdde69
Step 4/4 : CMD ["./startServer.sh"]
 ---> Running in 7a11c32dccae
Removing intermediate container 7a11c32dccae
 ---> 4e56f3b72f1d
Successfully built 4e56f3b72f1d
Successfully tagged ws3495/cspj-server:v1.0.0

通过这个执行过程中每个 step,可以看出 docker 构建镜像时的操作:

  • Sending build context to Docker daemon: docker 客户端把“context”发送到 docker 服务端
  • Step 1/4: 根据基础镜像 openjdk:8-alpine 构建第一层。如果这是第一次执行,会从 docker hub 上拉取这个镜像,缓存在本地。以后再次执行这个指令时,就会直接从本地获取这个镜像了。
  • Step 2/4: 构建第 2 层,把相对“context”路径“.”下所有文件复制到镜像的 /cspj-server/ 文件夹
  • Step 3/4: 构建第 3 层。先启动一个容器 aef7152e561a,执行了所要求的指令,随后删除了这个临时的容器,并把执行结果进行了提交,就生成了镜像的一层。
  • Step 4/4: 设置容器启动命令。
  • 最后打上一个 tag

通过执行 docker image ls 命令,可以看到刚才构建的镜像:

[eric@centos7min2 cspj-server]$ docker image ls
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
ws3495/cspj-server        v1.0.0              4e56f3b72f1d        12 minutes ago      137MB
ws3495/cspj-server        v1.0.1              e5868fe7c123        4 hours ago         132MB
openjdk                   8-alpine            a3562aa0b991        4 months ago        105MB
registry                  latest              f32a97de94e1        6 months ago        25.8MB
hello-world               latest              fce289e99eb9        9 months ago        1.84kB
prakhar1989/static-site   latest              f01030e1dcf3        3 years ago         134MB

这个命令输出一个类似表格的结构,第一行是表头。第二行就是我们刚构建的 ws3495/cspj-server:v1.0.0,第 3 行是我们从 docker hub 上拉取的 openjdk:8-alpine 镜像。

5. 启动应用

5.1 启动容器

执行命令docker run -d -p 27449:27449 -p 27450:27450 ws3495/cspj-server:v1.0.0,依据刚才制作的镜像,启动一个容器:

[eric@centos7min2 cspj-server]$ docker run -d \
>   -p 27449:27449 -p 27450:27450 \
>   ws3495/cspj-server:v1.0.0
47ed8277b0e0bfbb90a798a8b5499a0ee693499fd2342615388248ad72e932ab

命令执行结束后,返给我们一个字符串,这个串就是这个刚刚启动的容器的 ID。由于我们使用了 -d 参数,所以这个容器在后台运行。

命令中的 -p <host port>:<container port> 参数把容器中的端口和宿主机中的端口进行了映射。外部访问 host port 的连接就会被转发到这个容器的 container port 上。

使用 docker logs id 指令可以查看容器的日志,id 仅需前几位即可:

[eric@centos7min2 cspj-server]$ docker logs 47ed827
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 47ed8277b0e0
172.17.0.2 cspj-host
-Djava.rmi.server.hostname=cspj-host -Dcom.sun.management.jmxremote.port=9998 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

Starting CSPJ Server...
2019-09-29_10:35:33.263[0000]CSPJ Server Process ID:10
2019-09-29_10:35:33.275[0000]CSPJ Server Version:CSPJ_V1.3.7.1 Build:2019-08-03 10:54:51
2019-09-29_10:35:33.276[0000]系统日志初始化成功[/cspj-server/log/syslog.trace]
2019-09-29_10:35:33.277[0000]平台主目录:/cspj-server
2019-09-29_10:35:33.277[0000]平台配置信息主目录:/cspj-server/conf
...
2019-09-29_10:35:37.587[0000]终端 [RMI Registry] 监听端口 [27449] 数据端口[27450]
2019-09-29_10:35:37.587[0000]终端守护进程启动成功
2019-09-29_10:35:37.587[0000]初始化交易主控入口……
2019-09-29_10:35:37.589[0000]交易主控入口初始化成功[DefaultTransactionInvoker]
2019-09-29_10:35:37.589[0000]启动通讯口岸……
2019-09-29_10:35:37.599[0000]启动通讯口岸:SocketPortal[NIO]
2019-09-29_10:35:37.599[0000]通讯口岸启动成功
2019-09-29_10:35:37.649[0000]CSPJ Server 启动成功

使用命令 docker ps 可以查看正在运行的容器:

[eric@centos7min2 cspj-server]$ docker ps
CONTAINER ID        IMAGE                       COMMAND              CREATED             STATUS              PORTS                                  NAMES
47ed8277b0e0        ws3495/cspj-server:v1.0.0   "./startServer.sh"   53 minutes ago      Up 53 minutes       0.0.0.0:27449-27450->27449-27450/tcp   jolly_shockley

对于这个运行中的容器,可以使用命令docker exec -it id sh,进入这个容器进行查看和修改:

[eric@centos7min2 cspj-server]$ docker exec -it 47ed827 sh
/cspj-server/bin # ls
SecInputKey.sh      derby.log           jmxremote.password  serverStatus.sh     startServer.sh      transform.sh
codeinfo.sh         ij.sh               pwdgen.sh           setEnv.sh           stopServer.sh
/cspj-server/bin # cd ../log
/cspj-server/log # ls
error.trace   root.trace    syslog.trace  trace
/cspj-server/bin #

此时就进入到了容器里,在里面对容器中的内容进行修改,就会写入到容器的可读写层(应该是最上层)。之后再执行 docker restart <id> 时,这个容器的修改不会消失。只有在使用 docker rm <id> 命令删除这个容器时,所有临时存储的文件就会消失。或者再使用 docker run ... ws3495/cspj-server:v1.0.0 命令运行一个新容器时,这个容器中所作的修改也不会被新容器知道。

5.2 从外部 RMI 连接到容器的 27449 端口

在我们的 windows 10 系统上,修改 C:\Windows\System32\drivers\etc\hosts 文件,添加一行192.168.56.104 cspj-host,其中 192.168.56.104 是 CentOS- 7 虚拟机的 IP。就可以使用 IDE(RMI 连接的客户端工具)连接了:

其原理是:

现在这个集群共有 3 个 IP 地址:

  • IP1: 是 windows 10 操作系统所在的地址,也是 RMI 客户端所在的地址
  • IP2: 192.168.56.104,是 CentOS- 7 系统(在 win10 中运行的 VirtualBox 虚拟机中)所在的地址
  • IP3: 是 docker 容器运行的地址,即 RMI 服务端所在的地址

由于 docker 启动时设置了 -p 27449:27449 -p 27450:27450 参数,所有发送到 IP2:27449 和 IP2:27450 的信息都会被转发到 IP3:27449 和 IP3:27450。这两个端口是我们设置的 RMI 提供服务的端口。

在 docker 容器中启动的 java 应用(RMI 服务端,IP3)在启动时设置了 -Djava.rmi.server.hostname=cspj-host 选项,当客户端使用 rmi 方式连接到 docker 容器中的进程时,容器中的进程会向客户端返回一个本机的 cspj-host 参数标定服务端所在的地址(已在 docker 中的 /etc/hosts 中设置了 IP3 cspj-host(参看 5.1 节中的 docker logs 指令的输出结果))。客户端会从本机的hosts 文件中查找 cspj-host 所在的地址。

客户端需要以 RMI 方式连接到 IP3 上时,需要通过 IP2 进行中转,所以在 RMI 客户端所在的机器上,需要在 hosts 文件中设置IP2 cspj-host,使客户端去 IP2:27449 获取 RMI 服务。又由于 IP2 会把所有 27449 端口的数据包都转发到 IP3:27449,所以就会最终找到真正的 RMI 服务。

当有更多的 IP 对数据包进行转发时,也是一样的,客户端需要设置 hosts 中 cspj-host 为 IP2 所在的地址。

注意:连接过程中可能会碰到 NoSuchObject 的异常,需要多试几次。或者在 IP2 上启动一个 cspj,IDE 连接上之后,再关闭 IP2 上的 cspj,然后再试

6. 修改数据文件

6.1 使用 commit 生成新的镜像

通过 5.2 节图中的界面,我们可以修改 Derby 数据库文件。主要修改内容是在容器中开放 18000 端口,所有向这个端口发送的数据,都会收到一个返回信息,信息中标明这个容器的 IP 地址。修改之后,使用 docker commit <id> ws3495/cspj-server:tmp 命令,把这个容器存为一个新镜像 ws3495/cspj-server:tmp

[eric@centos7min2 cspj-server]$ docker commit 47ed8277b0e0 ws3495/cspj-server:tmp
sha256:ae4f6b8ecf435b714c331372d93c96bbb56460469bb9dd06e4e0f93faa8659a8
[eric@centos7min2 cspj-server]$ docker image ls
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
ws3495/cspj-server        tmp                 ae4f6b8ecf43        9 seconds ago       141MB
ws3495/cspj-server        v1.0.0              4e56f3b72f1d        About an hour ago   137MB
ws3495/cspj-server        v1.0.1              e5868fe7c123        5 hours ago         132MB
openjdk                   8-alpine            a3562aa0b991        4 months ago        105MB
registry                  latest              f32a97de94e1        6 months ago        25.8MB
hello-world               latest              fce289e99eb9        9 months ago        1.84kB
prakhar1989/static-site   latest              f01030e1dcf3        3 years ago         134MB

可以看出,commit 生成的镜像 tmp 比原始镜像 v1.0.0 镜像大了不少。

使用命令 docker stop 命令停掉现在这个容器,再使用 docker ps -a 命令,可以看到其状态为 Exited(stop 的容器可以使用 docker start 命令再启动,其修改不会丢失):

[eric@centos7min2 cspj-server]$ docker stop 47ed8277b0e0
47ed8277b0e0
[eric@centos7min2 cspj-server]$ docker ps -a
CONTAINER ID        IMAGE                       COMMAND              CREATED             STATUS                        PORTS               NAMES
47ed8277b0e0        ws3495/cspj-server:v1.0.0   "./startServer.sh"   58 minutes ago      Exited (137) 31 seconds ago                       jolly_shockley

我们根据刚才 commit 的镜像,启动一个新容器,这次把 18000 端口也映射到宿主机:

[eric@centos7min2 cspj-server]$ docker run -d \
>   -p 27449:27449 -p 27450:27450 -p 18000:18000 \
>   ws3495/cspj-server:tmp
f211f0844f78dc38460260c34b7e9cb7f9ae8245c7d246e35e7be7cf4d3ba23c

新运行的容器和刚才那个容器的 ID 是不一样的。使用 IDE 连接到这个新容器上,可以看到刚才在那个容器中所作的修改,这里都存在。

在 windows 10 上,使用 telnet 连接到 CentOS- 7 的 18000 端口,发送一段数据,可以看到返回信息:

其中的 ip addr is 172.17.0.2 即容器中 java 应用返回信息。

6.2 更好的方法

v1.0.0 镜像启动了一个容器 47ed8277b0e0,可不可以在这个运行的容器上再用类似 -p 的参数映射出一个端口呢?根据 How do I assign a port mapping to an existing Docker container? 这个答案,需要修改 docker 守护进程的配置文件,不是一个很好的解决方案。

commit 会使镜像不断增大。可以使用 bind mounts 技术,在 docker run 命令中,使用 --mount type=bind,source=<host dir>,target=/cspj-server/database 参数,把宿主机上的一个文件夹挂载到容器中。我们的 java 应用所有数据库修改都是修改 /cspj-server/database 目录,这样对数据库的修改就可以保存下来,即使容器被删除了,对数据库的修改也不会消失。不过要注意,<host dir> 中因该包含我们 java 应用中 database 目录所需的一些基础文件:

[eric@centos7min2 cspj-server]$ ll database/
total 20
drwxrwxr-x. 2 eric eric   97 Sep 29 14:03 log
-rw-rw-r--. 1 eric eric  608 Sep 29 14:03 README_DO_NOT_TOUCH_FILES.txt
drwxrwxr-x. 2 eric eric 8192 Sep 29 14:03 seg0
-rw-rw-r--. 1 eric eric 1003 Sep 29 14:03 service.properties

或者使用外置的数据库,不要和 java 应用集成在一起。例如连接到外部的 oracle 数据库;或者启动一个 mySQL 的 docker container,使用 docker-compose 工具把他们关联到一起。详情请参看“容器集群”一节

7. 私有仓库 registry

如果无法连接 Docker Hub,我们可以搭建私有仓库。使用官方镜像 registry 即可搭建:

# 搭建本地的 registry,默认在 /etc/lib/registry 中
[eric@centos7min2 cspj-server]$ docker run -d -p 5000:5000 --restart=always --name registry registry
821497a1688646027389b8c3547ab3e321e8df1c1fa442987506b3b5784de52e

# 查看本地 registry 上存在的镜像
[eric@centos7min2 cspj-server]$ curl 127.0.0.1:5000/v2/_catalog
{"repositories":[]}

# 使用 docker tag 标记一个到 127.0.0.1:5000 的镜像
[eric@centos7min2 cspj-server]$ docker tag ws3495/cspj-server:tmp 127.0.0.1:5000/ws3495/cspj-server:tmp
[eric@centos7min2 cspj-server]$ docker image ls
REPOSITORY                          TAG                 IMAGE ID            CREATED             SIZE
127.0.0.1:5000/ws3495/cspj-server   tmp                 ae4f6b8ecf43        About an hour ago   141MB
ws3495/cspj-server                  tmp                 ae4f6b8ecf43        About an hour ago   141MB
ws3495/cspj-server                  v1.0.0              4e56f3b72f1d        3 hours ago         137MB
ws3495/cspj-server                  v1.0.1              e5868fe7c123        6 hours ago         132MB
openjdk                             8-alpine            a3562aa0b991        4 months ago        105MB
registry                            latest              f32a97de94e1        6 months ago        25.8MB
hello-world                         latest              fce289e99eb9        9 months ago        1.84kB
prakhar1989/static-site             latest              f01030e1dcf3        3 years ago         134MB

# 推送这个镜像
[eric@centos7min2 cspj-server]$ docker push 127.0.0.1:5000/ws3495/cspj-server:tmp
The push refers to repository [127.0.0.1:5000/ws3495/cspj-server]
e907982060bf: Pushed 
4c70c37a74c9: Pushed 
ceaf9e1ebef5: Pushed 
9b9b7f3d56a0: Pushed 
f1b5933fe4b5: Pushed 
tmp: digest: sha256:588c372c92e3909fb280311d273be512dfc5641eb66e0514b9c90c9db314dcb4 size: 1369

# 查看结果
[eric@centos7min2 cspj-server]$ curl 127.0.0.1:5000/v2/_catalog
{"repositories":["ws3495/cspj-server"]}

# 重新 pull
[eric@centos7min2 cspj-server]$ docker image rm 127.0.0.1:5000/ws3495/cspj-server:tmp
Untagged: 127.0.0.1:5000/ws3495/cspj-server:tmp
Untagged: 127.0.0.1:5000/ws3495/cspj-server@sha256:588c372c92e3909fb280311d273be512dfc5641eb66e0514b9c90c9db314dcb4

[eric@centos7min2 cspj-server]$ docker pull 127.0.0.1:5000/ws3495/cspj-server:tmp
tmp: Pulling from ws3495/cspj-server
Digest: sha256:588c372c92e3909fb280311d273be512dfc5641eb66e0514b9c90c9db314dcb4
Status: Downloaded newer image for 127.0.0.1:5000/ws3495/cspj-server:tmp
127.0.0.1:5000/ws3495/cspj-server:tmp

[eric@centos7min2 cspj-server]$ docker image ls
REPOSITORY                          TAG                 IMAGE ID            CREATED             SIZE
ws3495/cspj-server                  tmp                 ae4f6b8ecf43        About an hour ago   141MB
127.0.0.1:5000/ws3495/cspj-server   tmp                 ae4f6b8ecf43        About an hour ago   141MB
ws3495/cspj-server                  v1.0.0              4e56f3b72f1d        3 hours ago         137MB
ws3495/cspj-server                  v1.0.1              e5868fe7c123        6 hours ago         132MB
openjdk                             8-alpine            a3562aa0b991        4 months ago        105MB
registry                            latest              f32a97de94e1        6 months ago        25.8MB
hello-world                         latest              fce289e99eb9        9 months ago        1.84kB
prakhar1989/static-site             latest              f01030e1dcf3        3 years ago         134MB

更多搭建私有仓库的方法请参看私有仓库

8. 容器集群

一个 container(容器)只做一件事情,一个 container 中只运行一个程序。如果我们的应用由好几个部分组成,应该如何把它们组织到一起呢?比如一个应用可以分为 server(逻辑处理)、db(数据库操作)、monitor(监控)等几部分,一般会把这几部分分别制作成镜像,启动到不同的 container 中。怎么把它们组成一个完整的可以对外提供服务的应用呢?如果需要多个应用,如何进行负载均衡呢?

这里就要用到集群管理。Docker 自带一个集群管理工具 Swarm(蜂群),使用它可以解决我们刚才提出的问题。

使用 Swarm,先要明确与之相关的一些概念:

  • node: swarm 集群中每一个运行了 docker 服务的机器(虚拟机或物理机),都被称为 node。参看第 3 节
  • container: 这个我们已经知道了,就是容器,是通过镜像(image)运行起来的。swarm 之外运行的 container 随时可以加入到 swarm 中,成为一个 task
  • task: 简单来说,在 swarm 集群中,每个运行中的 container 就是一个 task
  • service: 一个应用的不同部分被称为 service。service 中可以有多个 task 正在运行
  • stack: 一些相互关联的 service 一起对外提供服务,就构成了一个 stack。例如页面 service 和数据库 service 组合在一起才能让用户使用,这就是一个 stack

docker 命令中的 nodeservicestack 指令,都必须在 swarm 集群环境下使用。

8.1 负载均衡

我们前面创建了 ws3495/cspj-server:tmp 镜像。它对外提供一个服务,对任意发送到 18000 端口的请求,返回一个容器的 IP 地址。IDE 工具可以用 RMI 方式连接到这个容器,对容器的数据进行操作。

现在需要实现对这个服务的负载均衡。我们需要根据这个镜像启动多个容器,让它们共同对外提供更加稳定可靠的服务。

8.1.1 创建 compose 文件

在任意位置创建一个 compose 文件 docker-compose.yml:

version: "3"
services:
  server:
    image: ws3495/cspj-server:tmp
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: "0.5"
          memory: 1024M 
      restart_policy:
        condition: on-failure
    ports:
      # <host port> : <container port>
      - "27449:27449"
      - "27450:27450"
      - "18000:18000"
    networks:
      - cspjnet
networks:
  cspjnet:
  • services: services 下的每一项都是一个 service
  • server: 我们把 cspj 程序提供的 service 叫做 server
  • image: 根据哪个镜像启动容器
  • replicas: 启动 2 个容器。启动后,每个容器都是一个 task,它们共同组成了一个 service(名字叫做 server)
  • limits: 限制每个容器的 CPU 和内存使用率
  • restart_policy: 失败后立即重启
  • ports: 端口映射,左侧是宿主机的端口,右侧是容器的端口
  • networks: 为这几个容器新建的虚拟网络(默认设置,即 load-balanced overlay network)

8.1.2 初始化 swarm

swarm 中的节点(node)分为 manager 和 worker。我们需要初始化一个 manager,然后把其它节点作为 worker 加入进来。这里我们只有一个机器(CentOS-7),所以我们只创建一个 manager 节点。

执行命令 docker swarm init --advertise-addr 192.168.56.104 创建 manager 节点:

[eric@centos7min2 swarm]$ docker swarm init --advertise-addr 192.168.56.104
Swarm initialized: current node (eby778btfq9hvpzn62gnnv5ux) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-4famuxyeqheaiiip2rp2vyk7fuq5v4csvn4432w2czbm7ctov2-6yjw4idxlrdy4vjbknd4d1gdg 192.168.56.104:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

这个命令会创建几个 network,用于集群管理。ingress 和 none 就是 swarm 在这个节点上创建的,docker_gwbridge 可能也是:

[eric@centos7min2 swarm]$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
015532cb540e        bridge              bridge              local
a4025c27d82d        docker_gwbridge     bridge              local
20ce1819213b        host                host                local
5fnz5sfhkka7        ingress             overlay             swarm
ec1e8f535e07        none                null                local

可以执行一些检查,看一看这个节点现在的状态:

# swarm 中只有一个节点,并且是 manager
[eric@centos7min2 swarm]$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
eby778btfq9hvpzn62gnnv5ux *   centos7min2         Ready               Active              Leader              19.03.2

# 还没有 stack
[eric@centos7min2 swarm]$ docker stack ls
NAME                SERVICES            ORCHESTRATOR

# 也没有 service
[eric@centos7min2 swarm]$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS

8.1.3 部署并启动应用

执行命令docker stack deploy -c docker-compose.yml cspj,swarm 就可以根据 docker-compose.yml 文件中的配置,自动部署并启动应用。我们给这个 stack 命名为 cspj。

# 部署应用。会根据 yml 文件中的设置,创建虚拟网络 cspj_cspjnet,使用 ws3495/cspj-server:tmp 镜像启动一个服务 cspj_server
[eric@centos7min2 swarm]$ docker stack deploy -c docker-compose.yml cspj
Creating network cspj_cspjnet
Creating service cspj_server

# swarm 又新建了一个 cspj_cspjnet 的虚拟网络
[eric@centos7min2 swarm]$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
015532cb540e        bridge              bridge              local
d5a7fdih7h92        cspj_cspjnet        overlay             swarm
a4025c27d82d        docker_gwbridge     bridge              local
20ce1819213b        host                host                local
5fnz5sfhkka7        ingress             overlay             swarm
ec1e8f535e07        none                null                local

# 查看所有 stack。现在只有 1 个,名字叫 cspj,里面有 1 个 service
[eric@centos7min2 swarm]$ docker stack ls
NAME                SERVICES            ORCHESTRATOR
cspj                1                   Swarm

# 查看所有 service。输出信息表明 cspj_server 这个 service 中有两个 task(REPLICAS),并且都已经启动了
[eric@centos7min2 swarm]$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                    PORTS
uq82928el14d        cspj_server         replicated          2/2                 ws3495/cspj-server:tmp   *:18000->18000/tcp, *:27449-27450->27449-27450/tcp

# 查看 cspj_server 这个 service 中的 task。swarm 自动为每个 task 进行了编号
[eric@centos7min2 swarm]$ docker service ps cspj_server
ID                  NAME                IMAGE                    NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
j7kjtj81re1o        cspj_server.1       ws3495/cspj-server:tmp   centos7min2         Running             Running 5 minutes ago                       
1elk64sfezfo        cspj_server.2       ws3495/cspj-server:tmp   centos7min2         Running             Running 5 minutes ago                       

# 按照普通方式查看 container,发现 ID 和 service ps 中显示的 ID 不一样。为什么?[eric@centos7min2 swarm]$ docker ps 
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                    NAMES
8d9f67c15cb6        ws3495/cspj-server:tmp   "./startServer.sh"       21 minutes ago      Up 21 minutes       27449-27450/tcp          cspj_server.2.1elk64sfezfoql0ex1njwjlnp
dee88b746baf        ws3495/cspj-server:tmp   "./startServer.sh"       21 minutes ago      Up 21 minutes       27449-27450/tcp          cspj_server.1.j7kjtj81re1o1ge5anwnlmzka
821497a16886        registry                 "/entrypoint.sh /etc…"   24 hours ago        Up 24 hours         0.0.0.0:5000->5000/tcp   registry

在 Windows 10 上,向 CentOS- 7 的 18000 端口发送数据,可以看到负载均衡的效果:

IDE 也可以正常连接。

8.1.4 关闭应用并恢复

使用 docker stack rm cspj 关闭并移除 cspj 这个 task

# 关闭 stack
[eric@centos7min2 swarm]$ docker stack rm cspj
Removing service cspj_server
Removing network cspj_cspjnet

# stack 已被移除
[eric@centos7min2 swarm]$ docker stack ls
NAME                SERVICES            ORCHESTRATOR

# service 已被移除
[eric@centos7min2 swarm]$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS

# cspj_cspjnet 已被移除
[eric@centos7min2 swarm]$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
015532cb540e        bridge              bridge              local
a4025c27d82d        docker_gwbridge     bridge              local
20ce1819213b        host                host                local
5fnz5sfhkka7        ingress             overlay             swarm
ec1e8f535e07        none                null                local

# task 已被关闭并移除
[eric@centos7min2 swarm]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
821497a16886        registry            "/entrypoint.sh /etc…"   24 hours ago        Up 24 hours         0.0.0.0:5000->5000/tcp   registry

使用命令 docker swarm leave --force 移除 swarm 集群(即把最后一个 node 从 swarm 集群中移除掉)。之后,docker node, docker stack, docker service这些命令就不可用了:

# 关闭 swarm
[eric@centos7min2 compose]$ docker swarm leave --force
Node left the swarm.

8.2 多个 node 多个 servcie

本节使用 docker-machine 工具创建多个虚拟 docker node,并把它们都用 swarm 组织成一个集群。同时,我们再在 docker-compose.yml 文件中添加一个 service,使多个 service 共同工作。操作平台是 CentOS-7。docker-machine 可以完全独立使用,不必同时安装 docker,docker-machine 创建的虚拟机中就有 docker 服务。

由于 docker-machine 创建虚拟机需要先安装 virtualbox,而目前版本的 virtualbox(6.0)仅能在 AMD 的 CPU 上支持嵌套的虚拟机,所以在本节中我们使用 vmware workstation pro 15(有 30 天免费试用期,或者使用 vmware workstation palyer),在这个虚拟机上安装 CentOS-7,然后再在 CentOS- 7 上安装 docker-machine,docker-machine 再创建基于 virtualbox 的虚拟机。

新搭建的 CentOS- 7 系统的 IP 地址是 192.168.154.100.

8.2.1 安装 docker-machine

docker-machine 可以快速部署带有 docker 服务的虚拟机。

在 CentOS- 7 上使用 docker-machine 需要先安装 virtual-box。新建 /etc/yum.repos.d/virtualbox.repo 文件,内容如下:

[virtualbox]
name=Oracle Linux / RHEL / CentOS-$releasever / $basearch - VirtualBox
baseurl=http://download.virtualbox.org/virtualbox/rpm/el/$releasever/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://www.virtualbox.org/download/oracle_vbox.asc

然后执行如下命令安装 virtualbox。安装成功之后,执行sudo systemctl status vboxdrv,可以查看 virtualbox 的状态:

  • sudo yum update,会更新所有软件,可以不执行。如果执行,需在执行后 重启系统
  • yum install -y kernel-devel kernel-headers gcc make perl,之后完后可能需要 重启系统
  • sudo yum install VirtualBox-6.0
[eric@vmwmin1 ~]$ sudo systemctl status vboxdrv
● vboxdrv.service - VirtualBox Linux kernel module
   Loaded: loaded (/usr/lib/virtualbox/vboxdrv.sh; enabled; vendor preset: disabled)
   Active: active (exited) since Wed 2019-10-02 00:23:22 CST; 23min ago
  Process: 822 ExecStart=/usr/lib/virtualbox/vboxdrv.sh start (code=exited, status=0/SUCCESS)

Oct 02 00:19:51 vmwmin1 systemd[1]: Starting VirtualBox Linux kernel module...
Oct 02 00:19:54 vmwmin1 vboxdrv.sh[822]: vboxdrv.sh: Starting VirtualBox services.
Oct 02 00:19:54 vmwmin1 vboxdrv.sh[855]: Starting VirtualBox services.
Oct 02 00:19:54 vmwmin1 vboxdrv.sh[822]: vboxdrv.sh: Building VirtualBox kernel modules.
Oct 02 00:19:54 vmwmin1 vboxdrv.sh[860]: Building VirtualBox kernel modules.
Oct 02 00:23:22 vmwmin1 systemd[1]: Started VirtualBox Linux kernel module.

安装 docker-machine:

$ base=https://github.com/docker/machine/releases/download/v0.16.0 &&
  curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine &&
  sudo mv /tmp/docker-machine /usr/local/bin/docker-machine &&
  chmod +x /usr/local/bin/docker-machine

安装成功后,执行docker-machine ls,可以看到还不存在由 docker-machine 创建的虚拟机。

8.2.2 创建虚拟机(两个 node)

使用 docker-machine create --driver virtualbox <vm-name> 可以直接创建带有 docker 服务的虚拟机,不必事先安装 docker。这里我们创建两个虚拟机,分别为 myvm1myvm2:

# 创建 myvm1,由于是第一次执行,会从 github 上下载一些文件
[eric@vmwmin1 ~]$ docker-machine create --driver virtualbox myvm1
Running pre-create checks...
(myvm1) Image cache directory does not exist, creating it at /home/eric/.docker/machine/cache...
(myvm1) No default Boot2Docker ISO found locally, downloading the latest release...
(myvm1) Latest release for github.com/boot2docker/boot2docker is v18.09.9
(myvm1) Downloading /home/eric/.docker/machine/cache/boot2docker.iso from https://github.com/boot2docker/boot2docker/releases/download/v18.09.9/boot2docker.iso...
(myvm1) 0%....10%....20%....30%....40%....50%....60%....70%....80%....90%....100%
Creating machine...
(myvm1) Copying /home/eric/.docker/machine/cache/boot2docker.iso to /home/eric/.docker/machine/machines/myvm1/boot2docker.iso...
(myvm1) Creating VirtualBox VM...
(myvm1) Creating SSH key...
(myvm1) Starting the VM...
(myvm1) Check network to re-create if needed...
(myvm1) Found a new host-only adapter: "vboxnet0"
(myvm1) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env myvm1

# 创建 myvm2,省略了一些输出内容
[eric@vmwmin1 ~]$ docker-machine create --driver virtualbox myvm2
...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env myvm2

# 查看这两个虚拟机
[eric@vmwmin1 ~]$ docker-machine ls
NAME    ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER     ERRORS
myvm1   -        virtualbox   Running   tcp://192.168.99.100:2376           v18.09.9   
myvm2   -        virtualbox   Running   tcp://192.168.99.101:2376           v18.09.9   

8.2.3 配置 http 方式访问私有仓库

本文开始时我们使用的 CentOS- 7 系统的地址是 192.168.56.104,我们在这个 CentOS- 7 系统上使用 docker 搭建个了一个私有仓库。现在我们又使用 vmware 新搭建了一个 CentOS- 7 系统,其地址是 192.168.154.100,在这个系统上使用 docker-machine 创建了两个虚拟机 myvm1 和 myvm2。我们需要使 myvm1 和 myvm2 可以访问这个私有仓库,所以需要对 myvm1 和 myvm2 进行一些配置,使其可以以不安全的方式访问私有仓库。

通过执行命令 docker-machine scp <filename> <your-machine-name>:<path> 会把文件拷贝到对应的虚拟机中。

通过执行命令 docker-machine ssh <your-machine-name> "<your-docker-command>" 可以直接在虚拟机中执行命令。如果省略 "" 中的内容,就可以以 ssh 方式连接到虚拟机中。

在 docker-machine 所在的 CentOS- 7 系统上,在任意位置新建一个文件daemon.json,内容为:

{
  "insecure-registries": ["192.168.56.104:5000"]
}

192.168.56.104 是私有仓库所在的地址。

把这个文件拷贝到 myvm1 和 myvm2 的 /etc/docker/ 目录下:

# 拷贝文件
$ docker-machine scp daemon.json myvm1:~
$ docker-machine scp daemon.json myvm2:~
$ docker-machine ssh myvm1 "sudo mv daemon.json /etc/docker/"
$ docker-machine ssh myvm2 "sudo mv daemon.json /etc/docker/"

# 重启
[eric@vmwmin1 ~]$ docker-machine restart myvm1 myvm2
Restarting "myvm2"...
Restarting "myvm1"...
(myvm2) Check network to re-create if needed...
(myvm2) Waiting for an IP...
Waiting for SSH to be available...
(myvm1) Check network to re-create if needed...
(myvm1) Waiting for an IP...
Waiting for SSH to be available...
Detecting the provisioner...
Detecting the provisioner...
Restarted machines may have new IP addresses. You may need to re-run the `docker-machine env` command.

# 查看重启后状态
[eric@vmwmin1 ~]$ docker-machine ls
NAME    ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER     ERRORS
myvm1   -        virtualbox   Running   tcp://192.168.99.102:2376           v18.09.9   
myvm2   -        virtualbox   Running   tcp://192.168.99.103:2376           v18.09.9   

配置好 daemon.json 之后,myvm1 和 myvm2 就可以以不安全的方式(HTTP)访问私有仓库了。

8.2.4 配置 swarm 集群

使用 swarm 命令,把这两个节点加入到 swarm 集群中:

# 初始化 myvm1,会自动设置 myvm1 为 manager
[eric@vmwmin1 ~]$ docker-machine ssh myvm1 "docker swarm init --advertise-addr 192.168.99.102"
Swarm initialized: current node (tjpk0hxlhij9v77yh30ehnzkg) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-11sad8xx5hp9tyt9oed3gdgzx9ma7lfkk2chm0l8hi3mc0we2s-0bjuixv1bpsvryjsizppfr7bz 192.168.99.102:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

# 根据上一个输出的提示,把 myvm2 加入到 swarm 中
[eric@vmwmin1 ~]$ docker-machine ssh myvm2 "docker swarm join --token SWMTKN-1-11sad8xx5hp9tyt9oed3gdgzx9ma7lfkk2chm0l8hi3mc0we2s-0bjuixv1bpsvryjsizppfr7bz 192.168.99.102:2377"
This node joined a swarm as a worker.

# 查看 swarm 中的节点,* 标记的 myvm1 是 manager
[eric@vmwmin1 ~]$ docker-machine ssh myvm1 "docker node ls"
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
tjpk0hxlhij9v77yh30ehnzkg *   myvm1               Ready               Active              Leader              18.09.9
w46api0hvap58ghhe3spdd9i7     myvm2               Ready               Active                                  18.09.9

8.2.5 两个 service

新建一个docker-compose2.yml,其内容为:

version: "3"
services:
  server:
    image: 192.168.56.104:5000/ws3495/cspj-server:tmp
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: "0.5"
          memory: 1024M 
      restart_policy:
        condition: on-failure
    ports:
      # <host port> : <container port>
      - "27449:27449"
      - "27450:27450"
      - "18000:18000"
    networks:
      - cspjnet
  visualizer:
    image: 192.168.56.104:5000/dockersamples/visualizer:stable
    ports:
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      placement:
        constraints: [node.role == manager]
    networks:
      - cspjnet
networks:
  cspjnet:

这个 compose 文件中有两个 service,一个是我们的 java 应用 server;另一个是 visualizer,这是一个可以通过浏览器观察 swarm 节点状态的镜像。我们已经提前把它们 push 到私有仓库了(visualizer 镜像也可以直接从 Docker Hub 中获取)。

把这个 compose 文件拷贝到 myvm1 上(必须是 manager 节点,不能是 worker 节点),就可以部署了:

# 把 docker-compose2.yml 拷贝到 myvm1 的~ 目录下
[eric@vmwmin1 ~]$ docker-machine scp docker-compose2.yml myvm1:~
docker-compose2.yml                             100%  693   405.5KB/s   00:00    

# 向 myvm1 虚拟机发送指令,进行 service 部署
[eric@vmwmin1 ~]$ docker-machine ssh myvm1 "docker stack deploy -c docker-compose2.yml cspj"
Creating network cspj_cspjnet
Creating service cspj_server
Creating service cspj_visualizer

# 查看新部署的 stack
[eric@vmwmin1 ~]$ docker-machine ssh myvm1 "docker stack ls"
NAME                SERVICES            ORCHESTRATOR
cspj                2                   Swarm

# 查看新部署的 service,可以看到现在是部署并启动了 2 个 service,其中 cspj_server 启动了 2 份,cspj_visualizer 启动了 1 份
[eric@vmwmin1 ~]$ docker-machine ssh myvm1 "docker service ls"
ID                  NAME                MODE                REPLICAS            IMAGE                                                 PORTS
lo86uqnj2unu        cspj_server         replicated          2/2                 192.168.56.104:5000/ws3495/cspj-server:tmp            *:18000->18000/tcp, *:27449-27450->27449-27450/tcp
89d5hype8ert        cspj_visualizer     replicated          1/1                 192.168.56.104:5000/dockersamples/visualizer:stable   *:8080->8080/tcp

# CentOS- 7 系统上 (192.168.154.100),所有发送到 8080 端口的数据包都会被转发到 myvm1(192.168.99.102) 的 8080 端口
[eric@vmwmin1 ~]$ sudo firewall-cmd --list-forward-ports
port=8080:proto=tcp:toport=:toaddr=192.168.99.102
port=18000:proto=tcp:toport=:toaddr=192.168.99.102
port=27449:proto=tcp:toport=:toaddr=192.168.99.102
port=27450:proto=tcp:toport=:toaddr=192.168.99.102

使用浏览器连接 http://192.168.154.100:8080/,可以看到 swarm 中节点和 service 的状态:

8.2.6 清理

通过使用命令 docker-machine ssh myvm1 "docker stack rm cspj" 关闭并删除 cspj 这个 stack,会同时停止并删除 server 和 visualizer 这两个 service,会同时停止并删除 cspj_server.1, cspj_server.2, cspj_visualizer 这 3 个 docker 容器。

通过使用命令 docker-machine stop myvm1 myvm2docker-machine rm myvm1 myvm2停止并删除这两个虚拟机。

8.2.7 简化指令

执行 docker-machine env myvm1,按照其输出结果的提示,执行eval $(docker-machine env myvm1),可以把 myvm1 设置为 active。此时可以在 CentOS- 7 系统上直接执行 docker 命令即会向 myvm1 发送执行,而不必通过docker-machine ssh myvm1 "<command>" 向 myvm1 发送指令了。有兴趣可以自行尝试。

9. 总结

docker 为应用部署提供了极大方便,镜像设置好之后,可以在任何地方快速部署,保证一样的执行效果。docker 的镜像尽量把每个功能拆分出来,使多个镜像组成 stack 共同对外提供服务。如果需要数据持久化,可以使用 volume 功能把数据存储在宿主机上。volume 功能也可以在多个 service 之间共享存储数据。

docker-machine 提供了便捷搭建虚拟机,便捷管理虚拟机的能力,使得集群的管理更加方便。

docker 中还有很多地方值得探索,比如如何使用 config 设置配置文件,如何搭建 HTTPS 方式的私有仓库,docker 的底层工作机制是如何实现的。后续会进一步对这些内容进行分析。

参考文档

  • docker for begineres,推荐阅读
  • Docker Documentation
  • Docker — 从入门到实践
  • Docker 入门教程
  • docker 一些官方项目的 docs
  • What is Docker? How Does it Work?
  • About Windows containers
  • Get container’s ip inside container
  • How do I assign a port mapping to an existing Docker container?
  • 私有仓库
  • Example stack.yml for zookeeper
  • Compose file version 3 reference
  • Docker Machine Overview
  • docker command reference
  • Docker Swarm 深入浅出
  • Swarm mode key concepts
  • How To Install VirtualBox 6.0 / 5.2 on CentOS 7 / RHEL 7
  • Virtualbox enable nested vtx/amd-v greyed out
正文完
 0