【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而本節點需要擔當時;其他節點上任務啟動失敗時。