昨天介紹了 Docker 的基本概念,以及跟著 Get Started
教學文件編寫 Dockerfile
、建立映像檔並執行。在從容器層級進入服務層級之前,先看一下如何利用 registry 來分享建立好的映像檔。
Registry 是存放儲存庫 (repository) 的地方,而一個儲存庫可有多個映像檔(以標籤 tag 區別之),類似的概念已經在 Vagrant 中看過了。和 Vagrant 一樣,Docker 也有一個公有的 registry 叫 Docker Hub
https://hub.docker.com,可創建帳號並存放自己建置的映像檔以供自己或他人使用。
這裡使用 Docker Hub
作為存放映像檔的 registry,在企業內部也可以使用自建的私有 registry。請先到 Docker Hub
申請一個帳號,接下來要在本機使用 Docker CLI 進行登入,指令為 docker login
。預設 registry 為 Docker Hub
,所以直接輸入使用者名稱及帳號登入即可。
接下來要將映像檔給予儲存庫名稱及標籤,並加上使用者名稱,指令格式為:
$ docker tag image username/repository:tag
將昨天的映像檔 friendlyhello
儲存庫命名為 get-started
,標籤為 part2
,要加上自己的使用者名稱,例如:
$ docker tag friendlyhello odie1111/get-started:part2
以 docker image ls
查看,會看到剛才新 tag 的映像檔,它的 REPOSITORY 是使用者名稱加上儲存庫名稱,ID 會和 friendlyhello
相同,因為內容是一樣的。
接下來把這個映像檔推到已登入的 registry (Docker Hub
),指令為
$ docker push odie1111/get-started:part2
刪除本地的 get-started:part2
映像檔,並以 docker run
來執行此映像檔,證明若此映像檔不存在於本機, Docker 會到 Docker Hub
拉回。
$ docker image rm odie1111/get-started:part2
$ docker run -d -p 4000:80 odie1111/get-started:part2
以瀏覽器拜訪 http://localhost:4000 確認映像檔已被拉回本機並執行。
Get Started
實作的第二部分是服務,文件中說要先安裝 Docker Compose
這個工具,在 Docker for Mac
或 Docker for Windows
已經內建,Linux 請參考 https://github.com/docker/compose/releases 安裝。
在一個分散式的應用程式中,應用程式的各個組成部分 (different pieces of the app) 被稱為「服務」。服務就只是「生產環境中的容器」(container in production),一個服務只會運行一個映像檔,但它會規範這個映像檔要如何被執行,諸如用那些 port,為了足夠承載容量或流量,需要啟動多少個容器實體等等。擴展一個服務會改變執行該服務軟體的容器實例 (instance) 數量,以分配給該行程更多的運算資源。用來定義、執行、擴展服務的設定檔是 docker-compse.yml
。接續昨天的範例,下面是這個應用程式中用到的 docker-compose.yml
的內容:
version: "3"
services:
web:
# replace username/repo:tag with your name and image details
image: username/repo:tag
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "4000:80"
networks:
- webnet
networks:
webnet:
這個 docker-compse.yml
中定義了一個服務,命名為 web
,它使用的映像檔案定義在第 5 行,請填入昨天建好的映像檔,可以用 friendlyhello
或者是剛才的 <username>/get-started:part2
。它會跑 5 個容器實例,每個容器限制使用 CPU 10% 及 50MB 的記憶體資源,如果有容器失敗會立刻重啟。它把 web
這個服務的 80 port 綁定到 host 的 4000 port 並使用預設的 load-balanced 網路作為這個服務的網路,命名為 webnet
,它會把所有容器的 80 port 導到這個服務的 80 port。
接下來我們就可以來執行這個應用程式了,但首先要先執行 docker swarm init
讓本機成為 swarm 模式中的節點,否則下一個指令會報錯,之後會再介紹為什麼要先執行這個指令。
現在執行下列這個指令,並觀察其輸出:
$ docker stack deploy -c docker-compose.yml getstartedlab
Creating network getstartedlab_webnet
Creating service getstartedlab_web
-c
是 --compose-file
的意義,表示 compose-file 的路徑。
給這個應用程式(或服務堆疊)一個名稱 getstartedlab
。先以 docker stack ls
列出目前的服務堆疊,結果如下:
NAME SERVICES ORCHESTRATOR
getstartedlab 1 Swarm
目前這個應用程式只有一個服務,稱為 web
(在 docker-compose.yml
的第 3 行)。以 docker service ls
查看目前的服務,服務名稱會前綴應用程式的名稱,這裡 web
服務的名稱為 getstartedlab_web
。
ID NAME MODE REPLICAS IMAGE PORTS
sd64tsvcaan3 getstartedlab_web replicated 5/5 friendlyhello:latest *:4000->80/tcp
在服務中運行的單一容器稱為一個 task,以 docker service ps <service>
來查看,會看到有 5 個 task,就是剛才在 docker-compose.yml
定義的服務 replicas 數量,每個 task 有自己的 ID 及名稱。
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
h9knbu1ko6mc getstartedlab_web.1 friendlyhello:latest linuxkit-025000000001 Running Running 10 minutes ago
dyj5wl6kaahz getstartedlab_web.2 friendlyhello:latest linuxkit-025000000001 Running Running 10 minutes ago
dotr2xofz5tk getstartedlab_web.3 friendlyhello:latest linuxkit-025000000001 Running Running 10 minutes ago
rkx62zzm033p getstartedlab_web.4 friendlyhello:latest linuxkit-025000000001 Running Running 10 minutes ago
20ri7n0qv0y3 getstartedlab_web.5 friendlyhello:latest linuxkit-025000000001 Running Running 10 minutes ago
也可以用 docker container ls
查看,但這裡的 CONTAINER ID 和上面的 ID 會不一樣。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bde56f5772d1 friendlyhello:latest "python app.py" 33 minutes ago Up 33 minutes 80/tcp getstartedlab_web.3.dotr2xofz5tk361po2fxbz3m8
5c541b44fca0 friendlyhello:latest "python app.py" 35 minutes ago Up 35 minutes 80/tcp getstartedlab_web.1.h9knbu1ko6mc5sakopecdhjy2
18de0b3a3996 friendlyhello:latest "python app.py" 35 minutes ago Up 35 minutes 80/tcp getstartedlab_web.5.20ri7n0qv0y3bplbmv4yoowul
a715e44fdf05 friendlyhello:latest "python app.py" 35 minutes ago Up 35 minutes 80/tcp getstartedlab_web.4.rkx62zzm033pujz10jqxaqz1e
52ca4494c847 friendlyhello:latest "python app.py" 35 minutes ago Up 35 minutes 80/tcp getstartedlab_web.2.dyj5wl6kaahzajz0tjlbtrp9w
一樣以瀏覽器連接 http://localhost:8000,並重新整理瀏覽器若干次,會看到畫面中 Hostname 欄位改變,證實連接到不同容器中的應用程式。
現在來擴展應用程式。調整 docker-compose.yml
中 web
服務 replicas 的值,並重新執行 docker stack deploy -c docker-compose.yml getstartedlab
,接著以上述指令查看,應該會動態調整容器數量。
現在將應用程式卸除,指令為 docker stack rm getstartedlab
,並將本機脫離 swarm 模式,指令為 docker swarm leave --force
。
我們在第二部分 docker stack 用到 docker swarm init
指令,先介紹什麼是 swarm。swarm 是一組運行 Docker 並加入同一個叢集的機器,一個 swarm 會包含一台 swarm manager 及其他作為 worker 的節點。swarm manager 負責處理對這個 swarm 所進行的 docker 操作,以及授權其他機器加入這個 swarm 成為 worker。
接下來要將應用程式部署到擁有多個節點的叢集。文件中利用 Docker Machine 來建立 swarm 叢集所需的節點。Docker Machine 是一台執行 Docker 的虛擬機,因為我們要建立一個多節點的 swarm 叢集,所以需要建立多台 Docker Machine。我們先看一下文件中的叢集架構,首先利用 Docker Machine 建立兩台虛擬機,其中一台作為 swarm manager,另一台是 swarm worker,而應用程式的部署則是在本機上對 swarm manager 進行操作。
要如何在本機對 swarm manager 進行操作,這裡有兩個方式,第一個方法是利用 docker-machine ssh <machine> <command>
,透過 docker-machine
替我們以 SSH 方式連進 swarm manager 執行指令,第二個方式是設置環境變數,使得在本機執行 docker 指令就等同在 swarm manger 上執行。第二個方式其實也就是之前提過在 macOS 或 Windows 利用 Docker Toolbox 執行 docker 的作法。這兩種方式的主要差異點,在於第二個方式不需要處理將 docker-compose.yml
這類型的設定檔,由本機複製到 swarm manager 的問題。
因為應試能力目標中有提到 Docker Machine,所以我們先跟著文件使用 Docker Machine 來建立 swarm 叢集。但其實我們手上已經有了建立好的虛擬機,應該也可以嘗試直接使用這些虛擬機來建立,之後有機會再來試驗。
因為 Docker Machine 是虛擬機,會需要 hypervisor 支援,所以下面的操作都是在本機上進行,本機上的 hypervisor 一樣是 VirtualBox。和 Docker Compose 一樣,在 Linux 上必須並外安裝,若使用 Docker for Mac 或 Docker for Windows 則已內建,安裝請參考 https://docs.docker.com/machine/install-machine/。
首先建立兩台虛擬機,分別命名為 myvm1
及 myvm2
,--driver virtualbox
表示使用 VirtualBox 作為 hypervisor。
$ docker-machine create --driver virtualbox myvm1
$ docker-machine create --driver virtualbox myvm2
使用 docker-machine ls
查看目前建立的虛擬機器及其 IP。
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
myvm1 - virtualbox Running tcp://192.168.99.100:2376 v18.06.1-ce
myvm2 - virtualbox Running tcp://192.168.99.101:2376 v18.06.1-ce
現在有了兩台虛擬機,接下來要來建立 swarm 叢集。首先以 docker swarm init
來指定 myvm1
作為 swarm manager,這個指令是在 myvm1
中操作的,所以利用 docker-machine ssh
來連進 myvm1
,必須提供 myvm1
的 IP 作為參數,因為虛擬機會有若干不同的網路介面及 IP,必須指定一個 IP 讓其在 swarm 網路中使用。範例及輸出如下:
$ docker-machine ssh myvm1 "docker swarm init --advertise-addr 192.168.99.100"
Swarm initialized: current node (drc80qsjzoh7sqnwemc3k93t1) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-58lxdqep9apdprne95g0yjss7jbv4o3fjrnjrn0a4d2ypg3diz-5ixr698rxucrzp106s4471d0o 192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
接下來將 myvm2
加入這個 swarm 中,請看剛才的輸出內容,會有加入 swarm 所需的 token 訊息。
$ docker-machine ssh myvm2 "docker swarm join --token \
SWMTKN-1-58lxdqep9apdprne95g0yjss7jbv4o3fjrnjrn0a4d2ypg3diz-5ixr698rxucrzp106s4471d0o 192.168.99.100:2377"
This node joined a swarm as a worker.
在 swarm manager 中可以透過 docker node ls
來查看目前參與各 swarm 的節點,輸出如下:
$ docker-machine ssh myvm1 "docker node ls"
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
drc80qsjzoh7sqnwemc3k93t1 * myvm1 Ready Active Leader 18.06.1-ce
z7qijmsvizgxfpi1s0zvd14ts myvm2 Ready Active 18.06.1-ce
如果某一個 worker 想要離開 swarm,在該 worker 中執行 docker swarm leave
。
以上提到的是第一種方法,透過 docker-machine ssh 來執行指令。第二種方法則是設定環境變數,先使用 docker-machine env myvm1
查看關於 myvm1 的變數。
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/odie/.docker/machine/machines/myvm1"
export DOCKER_MACHINE_NAME="myvm1"
# Run this command to configure your shell:
# eval $(docker-machine env myvm1)
根據輸出,執行 eval $(docker-machine env myvm1)
來設定環境變數,即可在本機使用 docker 指令,但作用於 myvm1
。例如執行 docker node ls
,其輸出結果會和剛才執行 docker-machine ssh myvm1 "docker node ls"
的結果相同。如果要取消環境變數的設定,讓 docker 指令直接作用於本機,執行 eval $(docker-machine env -u)
,或者直接另開一個新的 shell,因環境變數的設定只會作用於當前的 shell 之中。
現在在剛建好的 swarm 叢集中部署應用程式,其實跟在單個節點的 docker swarm 是一樣的,必須透過 swarm manager 來部署,而剛才已經在本機設定過 shell,可以直接對 myvm1
,也就是這個 swarm cluster 的 manager 執行 docker 指令。在本機上執行下列指令:
$ docker stack deploy -c docker-compose.yml getstartedlab
這裡有一個地方要注意,就是 docker-compose.yml
中 web
服務所使用的映像檔,如果指定原本在本機上的映像檔,因為現在 docker 指令是在 myvm1
上執行,會找不到該映像檔,請把設定檔的映像檔指向上傳到 Docker Hub
中的位置,也就是 <username>/get-started:part-2
。因為要下載映像檔,這個指定會花比較多的時間執行。用 docker service ps getstartedlab_web
來查看這個服務的部署狀況:
$ docker service ps getstartedlab_web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
prsht6hxidw4 getstartedlab_web.1 odie1111/get-started:part2 myvm2 Running Running about a minute ago
tm1yy0ilfcwe getstartedlab_web.2 odie1111/get-started:part2 myvm1 Running Running about a minute ago
ykoascfsay79 getstartedlab_web.3 odie1111/get-started:part2 myvm2 Running Running about a minute ago
hdvbdp5i1pix getstartedlab_web.4 odie1111/get-started:part2 myvm1 Running Running about a minute ago
lx61el7zqon2 getstartedlab_web.5 odie1111/get-started:part2 myvm2 Running Running about a minute ago
請注意這個服務的 5 個 replica 會分布在兩個 swarm 節點上。要以瀏覽器訪問此服務,可選擇連接兩個節點任一的 4000 port。這個 swarm 使用的網路是 load balancing,不管連向那個節點,刷新頁面,應該會發現一共有 5 個不同的容器出現,即使該容器並不在訪問的節點上。(因為連到那個容器是隨機的(有可能會考慮 load balancing),有可能某個容器一直不會連接到。)
接下來可以試驗調整服務 replicas 的數量,或者新建一個 node 加入這個 docker swarm。試驗結束後,依剛才的順序反向回復環境,首先將服務卸除。
$ docker stack rm getstartedlab
下一步取消 shell 的環境變數。
$ eval $(docker-machine env -u)
將 myvm2 及 myvm1 脫離 docker swarm。
$ docker-machine ssh myvm2 "docker swarm leave"
$ docker-machine ssh myvm1 "docker swarm leave --force"
停止 myvm1 及 myvm2 兩台虛擬機。
$ docker-machine stop myvm1
$ docker-machine stop myvm2
若要將機器再次重啟,使用 docker-machine start <machine>
指令,或者用 docker-machine rm <machine>
來移除此虛擬機。因為教學文件還會用到這兩台機器,請先保留它們。
今天首先瞭解了 registry 的作用,將昨天建立好的映像檔傳送到 Docker Hub
,Docker 公開的 registry。接下來使用 docker-compose.yml
在單個節點的 swarm 部署應用程式。之後利用 Docker Machine 建立了一個具有兩個節點的 swarm cluster,並在其中部署了應用程式。明天會將應用程式加入其他服務,使其在功能上更為完整,成為具多個服務的服務堆疊 (service stack)。