本文详细介绍了如何使用docker封装一个java应用(名字叫cspj,这个java应用涉及数据持久化以及RMI调用),包括:html
docker是一种容器技术,对操做系统、文件系统、网络等进行了封装,使其中的进程能够完整运行。java
docker和虚拟机是不一样的技术。虚拟机虚拟了一套硬件环境,须要在这个硬件环境之上安装完整的操做系统、jdk等相关软件,才能运行一个java应用,虚拟机和宿主机在操做系统层面就是相互隔离的。而docker则不一样,以Linux下的docker为例,它使用的依旧是宿主机中的Linux内核,只不过在宿主机的用户层上虚拟了一个容器,这个容器中的Linux只是操做系统中的一小部分,使用的依旧是宿主机的Linux内核。好比宿主机是Ubuntu,docker虚拟的操做系统能够是alpine,这只是虚拟了alpine和Ubuntu不一样的部分。node
一个docker容器中通常只运行一个应用,这和虚拟机也是不一样的。好比咱们的一个应用有java应用,有数据库mysql,那么java应用运行在一个容器里,mySql运行在另外一个容器里。他们之间能够经过docker虚拟的网络进行交互。mysql
docker中的容器就是运行中的进程。它是经过镜像进行启动的。docker中的镜像就至关于一个模板,启动一个容器就至关于经过模板建立一个可执行的应用。所以,只要镜像不变,全部经过这个镜像建立的容器都是一摸同样的。又由于docker进行了操做系统、文件系统、网络等方面的封装,因此这个镜像就能够在各类不一样的环境上运行,从而保证一致的执行效果。linux
容器运行以后,在其中会有一个可读写层,这是用来临时保存容器中应用在运行中产生的数据的。当这个容器被销毁以后,所保存的数据也就消失了。使用原有的镜像从新运行一个新的容器,就又是一个全新的应用了。git
因此,若是咱们须要对容器中的数据进行持久化,就须要用到volume或者bind mounts技术。好比咱们的java应用中有一个内置文件数据库Derby,若是须要保留对这个文件数据库的修改,同时又不想改变镜像文件,就能够把这个文件数据库使用volume或bind mounts技术保存到宿主机的文件系统中。这样,即便容器被销毁,容器中所修改的文件数据库也会被保留下来。github
还有一种方法保存容器中的临时数据,就是使用commit命令把容器可读写层中的临时数据也一块儿生成一个新的镜像。之后经过这个新镜像运行的容器,就都保留了这部分数据,这部分数据也就成了新镜像的一层,并且没法被修改。经过这个新镜像运行的容器,会生成一个新的可读写层,用来临时保存这次运行中生成的数据。若是一直使用commit保存数据,新的镜像就会愈来愈大。docker官方不推荐使用这种方法保存数据。sql
在详细说一下docker的镜像。docker的镜像是使用Dockerfile制做的。Dockerfile是一个脚本,docker build命令会读取这个脚本,按照其指令构造镜像。docker的镜像是一层一层的。每个Dockerfile指令,都会生成镜像中的一层。docker
咱们本身制做的docker镜像一般不会从最底层开始构建。好比咱们要制做一个java应用的镜像,咱们就要依赖于openjdk:8-alpine的官方镜像。在这个基础之上,再制做咱们的java应用镜像层。而官方的openjdk:8-alpine则是基于alpine操做系统制做的镜像,在这个操做系统之上,它为咱们设置好了各类环境变量,咱们在这个镜像之上就能够直接制做咱们本身的java应用镜像,而没必要关心jdk的设置了。数据库
aphine 是一个特别简洁的官方的Linux操做系统系统容器镜像,只有5M大小。从中也能够看出docker和虚拟机的区别,虚拟机中运行的操做系统必定是完整的操做系统,一般都会有几个G的大小。
实验电脑为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.
Docker分为社区版和企业版,咱们使用社区版便可。
$ sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine
# 安装依赖 $ 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命令实际上是客户端,后台还有一个服务端在运行。客户端和服务端能够不运行在同一个机器上。
新建一个空目录,把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链接
Dockerfile中内容为:
FROM openjdk:8-alpine COPY . /cspj-server/ WORKDIR /cspj-server/bin CMD ["./startServer.sh"]
在构建镜像时,docker中每条指令都会构建一层,因此若是有RUN命令时,通常把多个操做都写在一行里。
在刚才的目录中,执行 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构建镜像时的操做:
经过执行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镜像。
执行命令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
命令运行一个新容器时,这个容器中所做的修改也不会被新容器知道。
在咱们的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地址:
因为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,而后再试
经过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应用返回信息。
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
工具把他们关联到一块儿。详情请参看“容器集群”一节
若是没法链接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
更多搭建私有仓库的方法请参看私有仓库
一个container(容器)只作一件事情,一个container中只运行一个程序。若是咱们的应用由好几个部分组成,应该如何把它们组织到一块儿呢?好比一个应用能够分为server(逻辑处理)、db(数据库操做)、monitor(监控)等几部分,通常会把这几部分分别制做成镜像,启动到不一样的container中。怎么把它们组成一个完整的能够对外提供服务的应用呢?若是须要多个应用,如何进行负载均衡呢?
这里就要用到集群管理。Docker自带一个集群管理工具Swarm(蜂群),使用它能够解决咱们刚才提出的问题。
使用Swarm,先要明确与之相关的一些概念:
docker命令中的node
、service
、stack
指令,都必须在swarm集群环境下使用。
咱们前面建立了ws3495/cspj-server:tmp镜像。它对外提供一个服务,对任意发送到18000端口的请求,返回一个容器的IP地址。IDE工具能够用RMI方式链接到这个容器,对容器的数据进行操做。
如今须要实现对这个服务的负载均衡。咱们须要根据这个镜像启动多个容器,让它们共同对外提供更加稳定可靠的服务。
在任意位置建立一个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:
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
执行命令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也能够正常链接。
使用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.
本节使用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.
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建立的虚拟机。
使用docker-machine create --driver virtualbox <vm-name>
能够直接建立带有docker服务的虚拟机,没必要事先安装docker。这里咱们建立两个虚拟机,分别为myvm1
和myvm2
:
# 建立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
本文开始时咱们使用的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)访问私有仓库了。
使用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
新建一个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的状态:
经过使用命令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 myvm2
和docker-machine rm myvm1 myvm2
中止并删除这两个虚拟机。
执行docker-machine env myvm1
,按照其输出结果的提示,执行eval $(docker-machine env myvm1)
,能够把myvm1设置为active。此时能够在CentOS-7系统上直接执行docker命令即会向myvm1发送执行,而没必要经过docker-machine ssh myvm1 "<command>"
向myvm1发送指令了。有兴趣能够自行尝试。
docker为应用部署提供了极大方便,镜像设置好以后,能够在任何地方快速部署,保证同样的执行效果。docker的镜像尽可能把每一个功能拆分出来,使多个镜像组成stack共同对外提供服务。若是须要数据持久化,可使用volume功能把数据存储在宿主机上。volume功能也能够在多个service之间共享存储数据。
docker-machine提供了便捷搭建虚拟机,便捷管理虚拟机的能力,使得集群的管理更加方便。
docker中还有不少地方值得探索,好比如何使用config设置配置文件,如何搭建HTTPS方式的私有仓库,kubernetes和swarm的比较,docker的底层工做机制是如何实现的。后续会进一步对这些内容进行分析。