本文詳細介紹瞭如何使用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的底層工做機制是如何實現的。後續會進一步對這些內容進行分析。