1. 程式人生 > >【Docker】 Swarm簡單介紹

【Docker】 Swarm簡單介紹

Swarm是Docker官方提供的一款叢集管理工具,其主要作用是把若干臺Docker主機抽象為一個整體,並且通過一個入口統一管理這些Docker主機上的各種Docker資源。Swarm和Kubernetes比較類似,但是更加輕,具有的功能也較kubernetes更少一些。

  Swarm的基本架構如下圖所示,

  這個圖作為一個整體實際上都處於一個所謂的叢集中,它可能對應了一到多臺的實際伺服器。每臺伺服器上都裝有Docker並且開啟了基於HTTP的DockerAPI。這個叢集中有一個SwarmManager的管理者,用來管理叢集中的容器資源。管理者的管理物件不是伺服器層面而是叢集層面的,也就是說通過Manager,我們只能籠統地向叢集發出指令而不能具體到某臺具體的伺服器上要幹什麼(這也是Swarm的根本所在)。至於具體的管理實現方式,Manager向外暴露了一個HTTP介面,外部使用者通過這個HTTP介面來實現對叢集的管理。對於稍微大一點的叢集,最好是拿出一臺實際的伺服器作為專門的管理者,作為學習而言,也可以把管理者和被管理者放在一臺伺服器上。

  下面就來講一下如何簡單地通過swarm搭建一個叢集

■  安裝與簡單叢集建立

  ●  開啟帶有HTTPAPI的Docker服務

  我的虛擬機器環境是CentOS7的,Docker則是通過yum來安裝的。如要使用swarm,則必須讓Docker開放其HTTP的API。預設情況下這個API沒有開啟,而開啟此API需要在啟動時加入-H引數。

  網上有的人說執行/usr/bin/docker的時候直接加,有的又說修改/etc/sysconfig/docker之類的檔案,都不適用於我,可能是由於系統以及docker本身的版本的緣故。而我的正確姿勢是修改/lib/systemd/system/docker.service這個檔案中的引數,並且用systemctl來管理啟動docker服務。

  上述這個檔案的ExecStart很明顯是指出了docker的啟動引數,在第一行的後面直接加上:

-H tcp://0.0.0.0:2375

   (有些文章也指出對於CentOS6還需要加上-H unix:///var/run/docker.sock)修改完成之後別忘了執行一下systemctl daemon-reload重新整理配置

  然後再重啟/啟動Docker服務,此時通過netstat -ntlp可以看到一個新開的2375埠,此乃預設的DockerHTTPAPI的埠。如果是一個叢集則需要注意叢集中所有相關的主機都記得要啟動帶這個埠的Docker服務。

  ●  沒有正確退出swarm叢集時引發的問題

  退出swarm叢集用的命令是docker swarm leave,然而存在這樣一種情況:沒有完全退出swarm叢集時就關掉了Docker服務。隨後網路環境變化了(主機的IP變了)。此時若再systemctl start docker將會報錯,通過systemctl status docker -l可以檢視完整的報錯資訊,提示找不到老IP地址云云。其實這是啟動swarm時報的錯誤。

  google了一下之後,發現比較方便的解決辦法是手動修改/var/lib/docker/swarm下面的docker-state.json和state.json兩個檔案。把這兩個json檔案中原來的老地址都改成現在的新地址。應該就可以順利啟動了。

  ●  建立小叢集

  前面提到的Swarm Manager,其本身其實是一個容器,其他一些swarm的角色基本上也通過容器的方式來實現。Swarm源於Docker而基於Docker。但是Docker在剛安裝的時候是沒有swarm支援的,需要我們docker pull swarm去DockerHub裡下載swarm的映象。這個映象本身不大,最新版的大概15MB左右,一會兒就下載完了。

  當然,如果是叢集的話那麼需要在所有相關的主機上都pull到這個映象。

  第一步,建立一個叢集並且取得叢集標誌。Swarm支援自動發現功能,如果在一個網路中存在多個叢集,那麼就需要每個叢集都有一個區別於其他叢集的標誌來防止混淆。這個標誌就是所謂的叢集token,在叢集創立之初就被指定且無法更改。在被選為管理者角色的伺服器上執行命令:

docker run --rm swarm create

   這個命令返回中帶有一串token字串(以dfb4fb3a8767835d799ce429fb4d7c4d為例),這個資訊需要記錄下來,之後所有操作中都需要用到它來指出我們對哪個叢集操作。而且目前還沒找到如何檢視一個既存叢集的token,所以一定要記錄一下。。

  建立叢集過後實際上並沒有真的增加什麼Docker資源,仍然是一個空的叢集。

  第二步,建立叢集獲取到叢集token之後,目前我們手上拿著的還是一個空叢集,接下來就往裡面加入節點主機吧。加入節點主機的方法是再各個節點主機上執行這條命令:

docker run -d swarm join --addr=192.168.1.102:2375 token://dfb4fb3a8767835d799ce429fb4d7c4d

  其中--addr引數指出的是本主機的Docker服務的socket,自然,IP要和各個主機自身的IP一致。另外我嘗試了一下,若在本機指定addr為localhost或者0.0.0.0之類的IP,最終是無法正常工作的。所以即便是在管理者本機,也要老老實實寫出IP。完成後可以在當前主機上docker ps看一下,應該可以看到一個在執行中的swarm容器。這個容器扮演的角色就是向上和管理者容器通訊,向下管理所在本機的docker資源,所謂被管理主機的代理。

  建立完後,可以通過

docker run -d swarm list token://dfb4fb3a8767835d799ce429fb4d7c4d

  來檢視這個指定的叢集(由token指出)中存在哪些join進來的節點。需要注意的是,swarm join命令只是簡單的加入叢集的宣告,swarm並不會去驗證給出的地址和埠是否真的可以訪問到一個Docker服務。對於沒有發現正常Docker服務的節點,將置狀態為Pending而不是Healthy,這個狀態以及其他節點相關資訊怎麼看下面會說。

   第三步,開啟管理者容器。上面建立了叢集框架,並且往叢集中加入了join節點(即被管理主機的代理),但是還沒有出現管理者容器(管理主機的代理)。建立管理者容器的方法是在管理者主機上:

docker run -d -p 8888:2375 swarm manage token://dfb4fb3a8767835d799ce429fb4d7c4d

  同樣,docker ps之後可以看到管理容器。-p表名管理者容器做了一個埠對映。因為2375埠在本機上已經被Docker程序佔用(當然啟動時指定的埠不是預設的2375就另當別論了),而管理容器暴露的這個埠要提供出來,實現外界對叢集的管理,所以做了一個埠對映。這個8888,也可以換成其他任何合理的埠號。

  有了管理容器,並且管理容器給出了8888埠作為管理的入口,我們就可以執行以下這些命令了:

docker -H 192.168.1.101:8888 info
docker -H 127.0.0.1:8888 ps
docker -H 192.168.1.101:8888 images

  由於這個8888埠就可以看做是一個網路中的普通埠,在本機上訪問的話自然IP寫127.0.0.1也是可以的。但是注意不能寫成localhost,不然會報錯。。

  這三條命令,去掉-H引數的話就是一般的docker用來檢視資訊的命令。加上-H之後,比如-H 192.168.1.101:8888之後,其意義就變成了,檢視一個叢集的相關資訊。這個叢集是192.168.1.101的8888埠對應的管理容器所對應的那個叢集。

  通過這幾條命令呈現出來的docker資源如容器和映象是不強調具體處於那臺主機上的,這就使得叢集的概念得以發揚光大。另外通過這個socket得到的叢集的資訊會把swarm本身除外。比如目前這個狀態通過-H xxx ps看到的容器列表應該是空的。因為我們還沒有讓叢集執行任何容器。但是docker ps會有swarm的容器顯示出來。如果想在-H的時候(所謂叢集檢視)也看到swarm容器資訊可以用ps -a。

  順便,info命令的結果和普通的docker info命令結果不太一樣,最主要的是有了nodes這個欄位。這個欄位包含了各個節點的資訊,包括前面提到的節點狀態等資訊。

■  叢集簡單使用

  下面正式使用叢集來跑個容器試試看。其實和原生docker命令相比,就是多了個-H引數來指明一個叢集管理入口而已:

docker -H 127.0.0.1:8888 run -d -p 10022:22 --name swarmtest tomcatssh:v1

  tomcatssh是我本地一個自定義的映象,和docker run類似的,其他的很多命令如docker start/stop/rm等等也都可以通過叢集管理的入口來對叢集做出。

  如果我們的叢集中有多型機器用於跑容器,即有多個被管理主機的話,那麼通過這樣的方式啟動起來的容器會通過一定的策略選擇一臺合適的主機作為真實的跑容器的平臺來執行容器。策略分成好多種,預設是spread(這個欄位在docker -H xxx info中的Strategy中有顯示),具體是指當叢集要執行一個新的容器,將會根據演算法和收集到的各個被管理主機CPU,記憶體等資訊進行智慧的選擇,使得各個執行容器的主機儘量均衡。

  既然能夠做到自動選擇一臺主機作為容器執行的寄託,那麼自然也可以恢復手動模式。這在swarm中就是所謂的filter功能。filter可以分成多種,

  ●  約束過濾器(Constraint Filter)

  約束過濾器通過啟動Docker守護程序時指定的標籤label來查詢合適的被管理主機。label是通過啟動引數的方式在啟動時被固定的:

--label datacenter=us-east1

  這個引數加入到之前說過的docker.service,或者手動加在啟動docker的命令後面等等。

  而在啟動容器時通過這樣的方式來指定過濾器:

docker -H 127.0.0.1:8888 run -e constraint:datacenter==us-east1 -d --name www-use1 nginx

  -e後面跟過濾器,constraint指出了約束過濾器,後面的約束標籤支援==,!=兩種判斷,後面可以寫字串和正則表示式如us-east*。

  ●  親和過濾器(Affinity Filter)

  親和過濾器以現有的某個容器為基準,讓新容器執行在/不在已經運行了某個現有容器的主機上執行。

docker run -d -e --name db affinity:container!=www-use1 mysql

  比如上面這條命令說的就是要根據mysql映象啟動一個名為db的容器,但是這個容器不能在已經預www-use1容器執行的主機上執行。

  ●  埠過濾器

  埠過濾器嚴格來說並不是一個真的過濾器。。它只是在啟動容器時通過-p引數來申請對一個主機埠的使用權。如果一臺主機上這個埠正在被使用那麼自然是不能把容器放到這個主機上執行的。

  除了上述三種,過濾器還有很多,可以通過swarm manage --help或者去官網查。總的來說,過濾器是一種主動指定主機的手段,配合swarm自身的自動分配機制,可以靈活地確定一臺主機來執行容器。

  如果當前叢集中swarm找不到一臺符合條件的主機來執行容器,那麼swarm會明確指出哪個過濾器條件得不到滿足,從而啟動容器失敗。

========================================================================================================

  意識到,上面的swarm介紹居然是老版本的!orz

  現在的Docker(1.12版本以後的)都是帶了原生的swarm命令,也就是說不需要不需要進行復雜的swarm create之類的操作,僅需要簡單幾條命令即可。

  【http://blog.csdn.net/candcplusplus/article/details/53836703】

  ■  新版本上構建swarm叢集和節點

  啟動一個swarm叢集十分簡單,只需要執行

docker swarm init --listen-addr 192.168.1.112:8888 --advertise-addr 192.168.1.112

   兩個引數也很好懂,--listen-addr指出的是這個叢集暴露給外界呼叫的HTTPAPI的socket地址。這個就是類似於上面老版本中swarm manage時-p指定的埠。新增--advertise-addr引數的原因是大多數情況下我們的主機都不只有一張網絡卡。而一個swarm叢集需要辨明叢集所在的子網路是哪張網絡卡的。

  另外需要注意,在新版本的swarm下,manage節點自身也作為被worker節點的一個,自動加入建立起來的swarm叢集中。

  命令執行成功的話會提示一串類似於這樣的資訊:

    docker swarm join \
    --token SWMTKN-1-2vndbzp43eff6vaiornhbafew242arz29qngrql0slqg4zmi4j-1hpha7vnelkbg4gg1d293qus4 \
    192.168.1.112:8888

  這是在說明,通網路下的被管理主機上,只要執行這串命令就可以將該主機加入叢集。如果不小心忘了這個命令那麼可以在manager上執行docker swarm join-token manager命令,隨時調取出這部分資訊來看。我特意多建了兩臺虛擬機器,裝上docker作為被管理機器。為方便下面稱管理者角色的機器為A,另兩臺為B、C。

  在B、C上分別執行上面這個命令(有時可以在命令後加上--listen-addr引數,倒不是說被管理主機也需要監聽,而是存在一些將被管理主機升級為管理主機的場景)後,在A上執行命令:

docker node ls

  可以檢視swarm各個節點的情況。同樣的docker info 中也會多出Swarm: True以及一系列相關欄位。docker node ls的返回類似於:

ID                           HOSTNAME               STATUS  AVAILABILITY  MANAGER STATUS
2hzmnrb0vddow7jlr7zdx86s0    localhost.localdomain  Ready   Active
444w5u9i9tf8h1dmvp404tluy *  localhost.localdomain  Ready   Active        Leader
89z0l64mitjyhwijj6o0ps3m3    localhost.localdomain  Ready   Active        

  節點id後的星號據說表示的是你當前連線著的節點。

  於是,我們就得到了一個由三個節點組成的swarm小叢集。在這個叢集中有一個manager節點和三個worker節點(別忘了manager本身自動作為worker一員加入叢集)。

  相比較於之前還需要手動pull映象,然後敲好多docker run命令,新版本下整合到docker內部的swarm明顯就要好多了。然而我們現在也只是搭建了一個小叢集,並沒有實質內容在其中執行。

  ■  構建服務

  說到實質內容,由於swarm會自動地做一些如負載均衡,保持容器副本數量等工作,所以swarm對外提供的和k8s類似也是屬於一個“服務”的概念。

docker service create --replicas 1 --name swarmtest tomcatssh:v1

  通過上面這個命令可以建立一個服務(tomcatssh是我自己的映象)。--replicas引數指出希望保持這個服務始終有多少容器在執行,name引數指定的是服務的名字而非容器的名字,雖然兩者最終會很像。

  建立完成的服務可以在manage節點上通過docker service ls命令檢視,可能replicas是0/1,這表示服務仍在建立過程中。稍等一會兒就會變成1/1了。更加詳細的資訊,則可以通過docker service inspect --pretty swarmtest來檢視。pretty引數使輸出更加友好,不加此引數的輸出是JSON格式的。

  同時也可以順便到各臺worker上去docker ps看下容器的執行情況。一般情況下再manager上會出現一個正在執行的容器,如果你停掉或者刪掉這個容器,那麼swarm會自動重啟它。

  進一步的docker service ps swarmtest,可以檢視swarmtest這個Service的各個容器到底在哪個節點上執行且執行狀態如何。比如我剛才建立了一個replicas為3的swarmtest服務,docker service ps swarmtest後的結果是:

ID                         NAME             IMAGE         NODE                   DESIRED STATE  CURRENT STATE             ERROR
a6ubuiush8l821rncxkd231le   \_ swarmtest.1  tomcatssh:v1  localhost.localdomain  Shutdown       Rejected 8 minutes ago    "No such image: tomcatssh:v1"
3sk5cowp4bve0zklvydwdgwba  swarmtest.2      tomcatssh:v1  localhost.localdomain  Running        Running 6 minutes ago
8tku51hecc6pza21urs5oz5zk  swarmtest.3      tomcatssh:v1  localhost.localdomain  Running        Preparing 32 seconds ago  

  可以看到,在要求啟動3個容器作為swarmtest服務的支撐時,swarm分別試圖在叢集(僵硬的是我叢集中三個主機的主機名都是localhost.localdomain。。。意思一下吧,總之知道這裡雖然寫的一樣但是裡面是三臺不同的機子)中去啟動swarmtest.1,swarmtest.2以及swarmtest.3三個容器,可以看到swarm.1檢視執行的主機上沒有tomcatssh:v1映象因此啟動失敗,swarm.2啟動成功,開始執行;swarm.3仍在準備中。可以想到的是,因為swarm.1已經啟動失敗,所以swarm會繼續尋找機會啟動它,儘量保證啟動服務時replicas為3的要求。

  另外服務還有一個重要的功能就是伸縮。通過命令:

docker service scale swarmtest=5

  可以將服務現有的replica為3的狀態擴充套件到5,期間已經啟動的容器不受影響。

  對於不需要的服務,可以docker service rm swarmtest來刪除。刪除後所有節點上的相關容器都會被刪除。

  對於一個服務來說,常會遇到的一件事是滾動更新,swarm為我們封裝了命令docker service update。只要給這個命令加上--image引數指定一個新映象,那麼該服務中的所有容器都會被更新成這個新映象的內容。但為了保證可用性,必然不能同時更新所有容器。swarm就內建了一個滾動更新的機制,可以讓我們依次更新各個容器從而避免更新期間的不可用。在docker service create 的時候可以指出--upgrade-delay引數,表示更新服務對應的任務或一組任務之間的時間間隔。時間間隔用數字和時間單位表示,m 表示分,h 表示時,所以 10m30s 表示 10 分 30 秒的延時。另外--update-parallelism引數標誌配置排程器每次同時更新的最大任務數量,預設情況下此引數值為1,即一個一個容器地更新。

  在有了滾動更新的保障之後,再來執行docker service update,比如docker service update --image tomcatssh:v2 swarmtest,則swarm會自動地去按照滾動更新的策略更新各個容器(實際上就是把舊容器關停並啟動新容器)。在更新過程中docker service ps swarmtest可以檢視更新的實時情況,最終更新完成後這條命令看到的結果應該是類似於這樣子的:

複製程式碼

ID                         NAME             IMAGE         NODE                   DESIRED STATE  CURRENT STATE            ERROR
5v3purlp28bg93ngkmp9x1dy9  swarmtest.1      tomcatssh:v2  worker1                Running        Running 45 seconds ago
055xxourdnlsylmnjgwvs718t   \_ swarmtest.1  tomcatssh:v1  worker1                Shutdown       Shutdown 49 seconds ago
4b1am22wx1w0abo3ylxt7qcfe  swarmtest.2      tomcatssh:v2  localhost.localdomain  Running        Running 11 seconds ago
euyu700dikpqmgzq8hyoijvdq   \_ swarmtest.2  tomcatssh:v1  worker2                Shutdown       Shutdown 7 minutes ago
efgfvp2wd0x655dy136qrt47y  swarmtest.3      tomcatssh:v2  worker2                Running        Running 7 minutes ago
1m7muogeuitfsllhcyu942ac1   \_ swarmtest.3  tomcatssh:v1  localhost.localdomain  Shutdown       Shutdown 32 seconds ago  

複製程式碼

  過程中,swarm先Shutdown了一臺節點上的老容器並且啟動新容器,如果新容器啟動成功後就再等10秒(建立service時指定的引數),然後開始操作下一臺。另外,如果操作一臺的過程中發生錯誤導致新容器沒有正確執行起來,那麼更新任務會到此暫停,不會繼續往下。docker service update後面的--update-failure-action引數可以指定是否要跳過錯誤。

  ■  節點管理

  之前所有的演示中,三個節點始終都保持著Active的可用性。Swarm管理器會自動根據演算法將任務(啟停容器等)分配給Active的節點。

  除了Active,其他常見的可用性狀態還有Drain,處於Drain的節點不會被分配新任務,而且當前執行著的容器也都會被停止,swarm管理器則會在其他節點上新建這些任務。

  執行命令

docker node update --availability drain worker2

  可以手動將某個節點的可用性設定為Drain,比如這裡將名為worker2的節點設定為drain了之後,可以看到在docker service ps swarmtest中出現的新資訊:

anrqum9q6zg0jw12ds5jnloyb  swarmtest.3      tomcatssh:v2  localhost.localdomain  Running        Running 9 seconds ago
efgfvp2wd0x655dy136qrt47y   \_ swarmtest.3  tomcatssh:v2  worker2                Shutdown       Shutdown 7 minutes ago   

  由於worker2不再接受任務並關停現有任務,所以swarmtest.3這個容器被轉移到了localhost(即manage節點所在)。docker node ls或者docker node inspect --pretty worker2就可以看到起可用性的變更了。

  如果使用docker node update再次將worker2的可用性設定為active,那麼worker2節點就可以再次獲取任務了(剛才被轉移到localhost上的任務是不會有再轉回來的,所以worker2的任務只有在接下來的分配中獲得)。總的來說,一個處於Active狀態的節點在這些情況下可能收到新任務:當服務規模擴大時;滾動更新時;其他節點被設定為Drain而本節點需要擔當時;其他節點上任務啟動失敗時。