一種在Swarm集群中實現IP保持的方法
Docker在1.12版本中開始集成Swarmkit,本文研究在Swarmkit管理的集群中實現IP保持的方法。
Swarmkit簡介
Swarmkit是Docker公司推出的Docker集群管理和容器編排工具,自Docker1.12版本,開始集成到Docker-engine里發布。Swarmkit是從Swarm項目發展而來。作為Docker自身的編排系統,可提供服務編排,集群管理和調度功能。
Swarmkit中節點分為兩類
1. 工作節點負責通過執行器運行任務。Swarmkit的默認執行器為Docker容器執行器(Docker Container Executor);
2. 管理節點負責接收和響應用戶的請求,將集群狀態調節成最終狀態。
上圖給出了Docker中Swarm集群的組織方式,集群中可包含多個manager節點,這些 manager選舉一個節點作為leader節點。由leader節點負責整個集群的管理工作。如果該leader節點出現故障,剩余manager節點會重新選舉,新的leader節點會接管集群的管理工作,原leader節點在恢復后可重新加入集群,此時身份是普通manager節點。
Swarmkit在服務編排方面可實現服務狀態一致性,為服務按指定策略進行升級及重啟等。簡單來說,在創建服務后,Swarmkit根據服務類型確定task的數目,并生成相應task,定期檢查以保證task數目穩定,并按照調度策略將這些任務分派到各個工作節點上。如果有節點不可用,Leader節點會將該節點上的任務遷移到其它可用節點上繼續運行,還可以根據用戶命令增加或縮減任務的數目。具體使用方法參見官方文檔,下圖給出了Swarmkit的內部結構示意圖。Swarmkit中通過libnetwork實現對網絡資源的管理。
問題提出
從運維角度來看,為提高運維效率,希望容器在重啟或者遷移之后,可以保持容器IP地址不變。這樣運維人員可通過具體的靜態IP地址來對容器進行維護,升級等操作。
現在的實際情況是,在Docker.10之后的版本中,創建容器時可以通過在命令行使用–ip選項來指定其IP地址。但是目前Swarmkit的實現中,在為某個服務的task分配IP時,并不支持指定IP地址,而是采用隨機分配的方式。在任務需要重啟或遷移時,并不能保證前后兩次任務對應容器所得到的IP地址相同。要提高swarm集群的運維效率,需要一種能保持任務對應容器IP地址的方法。
實現思路
通過對Docker文檔及代碼進行閱讀,發現在為每個任務創建容器時,容器名稱由三部分組成,詳見下方代碼( daemon/cluster/executor/container/container.go name() )。
return strings.Join([]string{c.task.ServiceAnnotations.Name, fmt.Sprint(c.task.Slot), c.task.ID}, “.”)
其中服務名稱與Slot的值是確定的,任務的ID由系統自動生成,每一次重啟或遷移都會變化。可以通過容器名稱的前兩部分唯一標識容器所執行的任務,這里將其稱為任務標識 (Swarmkit具體實現中對任務的標識與此類似,不過用到的是ServiceID)。
解決方案的主要思路就是將容器的IP地址與任務標識關聯起來,在系統中保存任務標識與Swarmkit為該任務分配IP地址之間的映射關系。在任務重啟或遷移時,為新生成的容器分配之前保存在映射關系中的IP地址,這樣就可以保持一個任務對應的IP地址不變。
另一個需要考慮的問題是在leader節點不可用后,如何在新的leader節點上恢復之前所保存的映射關系。一種方案是將對應關系寫入圖1中的狀態存儲,另一種方案是在leader初始化時從task列表中重建對應關系。這里選擇的是后一種方案,因為第一種方案還要學習protobuffer的使用及Swarmkit的分布式存儲實現代碼,實現起來更復雜。
IP保持的具體實現
這里主要講一下為實現IP保持所要修改代碼的位置及功能。
- 注釋libnetwork中檢查指定IP地址合法性語句
在libnetwork代碼中注釋掉為指定IP的容器進行IP地址合法性檢查部分的語句,該語句檢查為容器分配的指定IP地址是否可用。原因是容器在停止后,會對其IP地址進行回收,但這種回收并不是實時的。如果新創建的容器在創建時,該IP地址還沒有被回收,容器創建會由于資源沖突而失敗。實際上擁有該IP地址的老容器已經停止,所以這樣做并不會影響服務的正確性。該修改位于getAddress 函數中。 - 增加保存映射關系的數據結構
映射關系的保持,在networkAllocator 中,將部分網絡信息保存在一個名字為network 的局部結構中。為保存映射關系,在該結構中增加一個map,其key為前面提到的可唯一標識一個任務的任務標識,其value值為該任務對應的IP地址。 - 增加分配IP功能函數
在為一個任務分配IP地址時,首先根據網絡名稱,查找到保存對應網絡信息的數據結構,并在該結構中新增的map里進行查找。如果命中,說明該任務之前已經被分配過IP地址,這次可能是該任務進行重啟或遷移,此時直接將已經分配過的IP地址再分配給該任務。如果沒有命中,說明該任務是首次執行,這時直接調用libnetwork的接口為其分配一個未用的IP地址。該功能通過修改AllocateTask 函數和增加allocateTaskIP 函數實現。 - 增加記錄已分配IP功能函數
在為一個任務分配了IP地址后,還需要對新增map中的數據進行更新。即將以任務標識為key,以分配的IP地址為value值的映射保存在map中。如果后續需要對任務進行重啟或遷移,就可以通過分配函數實現IP保持。該功能通過修改AllocateTask 函數和增加recordTaskIP 函數實現。 - 修改回收IP功能函數
為實現IP保持,還需要將對回收任務IP地址部分的代碼進行修改,以防止對已經分配過的IP地址進行回收。因為IP地址一旦被回收,就有可能被分配給新的任務。這樣就無法實現IP保持了。該功能對原有的releaseEndpoints 函數進行了修改。 - 增加映射關系初始化功能函數
該函數負責Leader改變后映射數據的重建。節點在被選為leader后,將負責集群后續的各項管理工作。為實現IP保持,需要在leader的初始化工作中重建映射關系。該項工作由修改doNetworkInit 函數和增加IpmapsInit 函數實現,該函數遍歷保存在一致性存儲中的所有task,對每個任務,從其保存的網絡信息中讀取已分配的IP地址,將這些IP與任務標識的映射關系保存到網絡中新增的map中,這樣就實現了對映射關系的重建。
編譯安裝方法(以 Docker1.12.1版本為例)
(1)從github上克隆Docker項目,并切換到1.12.1版本,或者直接下載Docker1.12.1的源代碼。
(2) 完成代碼修改。
(3)進入項目目錄,輸入make shell,會安裝對應1.12.1版本的開發環境,并進入開發環境所在容器。
(4)輸入hack/make.sh binary會編譯生成二進制可執行文件。在bundle/latest目錄下。
(5)安裝方法,在編譯生成二進制文件后,將文件復制到系統的/usr/bin或/usr/local/bin目錄下。宿主機系統最好是剛安裝完同版本的Docker或未安裝Docker,否則可能會出現Docker啟動不成功。
效果測試
- 在三臺VM上安裝新編譯的Docker程序,三個節點都是manager,這里省略了創建集群和服務的過程
root@vm-1476374349871:/home/ubuntu# docker service ls ID NAME REPLICAS IMAGE COMMAND 7huiwv4vgrs7 helloworld 10/10 alpine ping www.docker.com root@vm-1476374349871:/home/ubuntu# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS bn68rlizdmnchk14fd1ft9izx vm-1473648642890.vm-14736486428901473648642890 Ready Active Reachable bu5cc1nx0aj29q984kkngeftd * vm-1476374349871 Ready Active Leader cm1k85xgzxivrazge4qqteotv vm-1473648611529.vm-14736486115291473648611529 Ready Active Reachable root@vm-1476374349871:/home/ubuntu#
2. 將副本數目增加到10,在其中的一臺VM上的task如下
[root@vm-1473648611529 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 971b6cca77c4 alpine:latest "ping www.docker.com" 2 minutes ago Up 2 minutes helloworld.2.8hnyq37xrkkvoc1ddzoxxacv5 8773911a47fc alpine:latest "ping www.docker.com" 2 minutes ago Up 2 minutes helloworld.3.9egflo60nvh1i96l1y110ejg7 1315a7fb46d1 alpine:latest "ping www.docker.com" 2 minutes ago Up 2 minutes helloworld.5.e25s0snjn960zu6xlmt3a8cqq [root@vm-1473648611529 ~]#
3. 其中helloworld.3的eth0的IP為10.0.0.3
[root@vm-1473648611529 ~]# docker exec -ti 8773911a47fc sh / # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:0A:00:00:03 inet addr:10.0.0.3 Bcast:0.0.0.0 Mask:255.255.255.0 inet6 addr: fe80::42:aff:fe00:3%32520/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:29 errors:0 dropped:0 overruns:0 frame:0 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:2334 (2.2 KiB) TX bytes:648 (648.0 B) eth1 Link encap:Ethernet HWaddr 02:42:AC:12:00:03 inet addr:172.18.0.3 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::42:acff:fe12:3%32520/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:214 errors:0 dropped:0 overruns:0 frame:0 TX packets:235 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:19918 (19.4 KiB) TX bytes:22174 (21.6 KiB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1%32520/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:4 errors:0 dropped:0 overruns:0 frame:0 TX packets:4 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:334 (334.0 B) TX bytes:334 (334.0 B) / # exit [root@vm-1473648611529 ~]#
4. 將該節點設為不可用,該VM上的任務會重新分配到另外兩臺機器上
[root@vm-1473648611529 ~]# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS bn68rlizdmnchk14fd1ft9izx vm-1473648642890.vm-14736486428901473648642890 Ready Active Reachable bu5cc1nx0aj29q984kkngeftd vm-1476374349871 Ready Active Leader cm1k85xgzxivrazge4qqteotv * vm-1473648611529.vm-14736486115291473648611529 Ready Active Reachable [root@vm-1473648611529 ~]# docker node update --availability drain cm1k85xgzxivrazge4qqteotv cm1k85xgzxivrazge4qqteotv [root@vm-1473648611529 ~]# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS bn68rlizdmnchk14fd1ft9izx vm-1473648642890.vm-14736486428901473648642890 Ready Active Leader bu5cc1nx0aj29q984kkngeftd vm-1476374349871 Ready Active Reachable cm1k85xgzxivrazge4qqteotv * vm-1473648611529.vm-14736486115291473648611529 Ready Drain Reachable [root@vm-1473648611529 ~]# docker service ls ID NAME REPLICAS IMAGE COMMAND 7huiwv4vgrs7 helloworld 10/10 alpine ping www.docker.com [root@vm-1473648611529 ~]#
5. 其中helloworld.3分配到了leader節點上(helloworld.3.后面的任務ID是不一樣的)
[root@vm-1473648642890 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7308b277a77d alpine:latest "ping www.docker.com" About a minute ago Up About a minute helloworld.3.95dodap792m1l2d9of2tg6lt0 8b78496370c3 alpine:latest "ping www.docker.com" 6 minutes ago Up 6 minutes helloworld.6.auphkstyrt0yrrqpile2wjatz 243d15b67aaa alpine:latest "ping www.docker.com" 6 minutes ago Up 6 minutes helloworld.8.e9v6zdshztpvik79zt9tooei9 4d84734f60f9 alpine:latest "ping www.docker.com" 6 minutes ago Up 6 minutes helloworld.10.4rbworz9sa4mvmby6aj2ys8m6 c1a833410e3a alpine:latest "ping www.docker.com" 6 minutes ago Up 6 minutes helloworld.7.5y4urb3mnn9395hzzkxp1ffgs [root@vm-1473648642890 ~]#
6. 查看一下它的eth0的IP,還是10.0.0.3
[root@vm-1473648642890 ~]# docker exec -ti 7308b277a77d sh / # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:0A:00:00:03 inet addr:10.0.0.3 Bcast:0.0.0.0 Mask:255.255.255.0 inet6 addr: fe80::42:aff:fe00:3%32684/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:8 errors:0 dropped:0 overruns:0 frame:0 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:648 (648.0 B) TX bytes:648 (648.0 B) eth1 Link encap:Ethernet HWaddr 02:42:AC:12:00:06 inet addr:172.18.0.6 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::42:acff:fe12:6%32684/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:101 errors:0 dropped:0 overruns:0 frame:0 TX packets:125 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:9416 (9.1 KiB) TX bytes:11674 (11.4 KiB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1%32684/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:4 errors:0 dropped:0 overruns:0 frame:0 TX packets:4 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:334 (334.0 B) TX bytes:334 (334.0 B) / # exit [root@vm-1473648642890 ~]#
7. 重新將剛才的VM設為可用,并將leader重啟
[root@vm-1473648611529 ~]# docker node update --availability active cm1k85xgzxivrazge4qqteotv cm1k85xgzxivrazge4qqteotv [root@vm-1473648611529 ~]# [root@vm-1473648642890 ~]# reboot
8. 在其它VM上查看,大約半分鐘后,已經選舉出新的leader,且task的數目已經恢復到10個
[root@vm-1473648611529 ~]# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS bn68rlizdmnchk14fd1ft9izx vm-1473648642890.vm-14736486428901473648642890 Down Active Unreachable bu5cc1nx0aj29q984kkngeftd vm-1476374349871 Ready Active Leader cm1k85xgzxivrazge4qqteotv * vm-1473648611529.vm-14736486115291473648611529 Ready Active Reachable [root@vm-1473648611529 ~]# docker service ls ID NAME REPLICAS IMAGE COMMAND 7huiwv4vgrs7 helloworld 10/10 alpine ping www.docker.com [root@vm-1473648611529 ~]#
9. 此時原leader節點上的任務已經遷移到重新變為可用的節點上
[root@vm-1473648611529 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1d3a252664d8 alpine:latest "ping www.docker.com" 23 seconds ago Up 22 seconds helloworld.3.25sfbbqwqggvpdwbst8dgqmza 8ca053e810c1 alpine:latest "ping www.docker.com" 23 seconds ago Up 22 seconds helloworld.6.3z3dy1w216eu2833sdpxjz7mr a1744b36a51d alpine:latest "ping www.docker.com" 24 seconds ago Up 23 seconds helloworld.10.28ikt9ajopam1lf11c6vm0b91 981ba2a3ffec alpine:latest "ping www.docker.com" 24 seconds ago Up 23 seconds helloworld.7.7ce9f4ia66op0pi08h897vsw8 7af8cbd1e07c alpine:latest "ping www.docker.com" 30 seconds ago Up 27 seconds helloworld.8.339nbcym1ttyvnytm6ls65zuu [root@vm-1473648611529 ~]#
10. 再查看一個helloworld.3的eth0的IP, 仍然是10.0.0.3
[root@vm-1473648611529 ~]# docker exec -ti 1d3a252664d8 sh / # ifconfig eth0 Link encap:Ethernet HWaddr 02:42:0A:00:00:03 inet addr:10.0.0.3 Bcast:0.0.0.0 Mask:255.255.255.0 inet6 addr: fe80::42:aff:fe00:3%32585/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:26 errors:0 dropped:0 overruns:0 frame:0 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:2036 (1.9 KiB) TX bytes:648 (648.0 B) eth1 Link encap:Ethernet HWaddr 02:42:AC:12:00:06 inet addr:172.18.0.6 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::42:acff:fe12:6%32585/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:218 errors:0 dropped:0 overruns:0 frame:0 TX packets:224 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:20106 (19.6 KiB) TX bytes:20984 (20.4 KiB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1%32585/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:4 errors:0 dropped:0 overruns:0 frame:0 TX packets:4 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:334 (334.0 B) TX bytes:334 (334.0 B) / # exit [root@vm-1473648611529 ~]#
來自:http://dockone.io/article/1861