Swarm簡介java
Swarm是Docker的一個集羣管理工具,最開始僅僅是用做集羣管理以及簡單的調度,就像下面的圖所示的,爲Docker Client提供與Docker Engine同樣的Docker API,客戶端操做Swarm就好像在操做一臺機器,實際上後面連了好多Docker Engine,容器都跑在後面的Docker Engine上。Swarm負責將客戶端的請求分配到後端的Docker Engine,好比客戶端要求啓動一個容器,Swarm找一個目前資源使用最少的Docker Engine。 node
所以早期的Swarm比較底層,有點像虛擬機管理器,抽象程度低。而Kubernetes(Google開源的容器管理工具)抽象了不少概念,好比Service,Pod等,更上層,稍微一封裝就能夠作成一個PaaS了。爲了對抗Kubernetes,Docker也對Swarm作了升級改造,先是SwarmKit,抽象了Service,Task等,而後又把Swarmkit集成到了Docker Engine中,可使用docker命令把一個結點設爲swarm mode,而後這個結點就成了swarm集羣的一個結點。所以最新的Swarm再也不是一個工具,而是一個集羣,咱們把集羣稱做Swarm,而後這個集羣裏面有manager和worker兩種角色,manager中有一個leader,經過Raft算法實現數據一致性。總之不少方面都抄了Kubernetes。git
能夠在Swarm集羣中建立Service,而一個Service有多個實例,好比我建立一個tomcat的Service,名字是tomcat_service,使用的鏡像是tomcat,而後建立3個實例,也就是啓動3個容器,用下面的命令:github
docker service create --name tomcat_service --replicas 3 tomcat算法
在manager上執行這個命令,manager會把3個容器按調度策略分配到不一樣的worker上(manager也能夠當作worker)。docker
swarm的調度策略是:在知足constraint的worker中找一個task(也就是容器數)最少的結點,這種策略叫作spread策略,就是儘量的把task平均分佈到不一樣結點。constraint是指一些必須知足的條件,好比某個task要求分配2G內存。後端
spread策略是沒問題的,可是swarm在調度的時候沒有把一項很重要的內容加進去,那就是實例的容災。api
我搭了一個3個結點的Swarm集羣,manager1,worker1和worker2,建立了一個hello服務,有4個replica,一個world服務,有2個replica,以下:tomcat
看上去3個結點每一個結點兩個replica,挺好的,可是有一個嚴重的問題,world服務的兩個replica被調度到同一臺主機上,那麼這臺主機掛了,整個服務就掛了。其實replica的概念就是要在多個地方存放,以防止單主機出現問題致使服務不可用。好比HDFS的3個replica通常要放到不一樣機器上,甚至還要考慮不一樣機櫃,不一樣機房。 Kubernetes在調度的時候也考慮了多個replica放到多臺主機上的策略。 docker的開發人員目前只忙着出產品,沒功夫去精雕細琢細節到地方,不過多個replica放到不一樣主機以後確定是會加進去的。app
本文介紹如何修改Docker 1.12的代碼把replica容災的策略加到Swarm調度策略中。
Swarm調度算法介紹
老的Swarm調度算法能夠用下面的圖表示:
1.一個調度請求過來,裏面包含不少constraint,好比須要分配4G內存,或者要求必須調度上含有production標籤的結點上,或者要求某個結點沒有被佔用。
2.全部的結點做爲一個List傳到一個filter鏈裏,這個filter會過濾掉不符合條件的結點,好比內存不夠,輸出一個符合條件的結點List
3.按照策略進行排序,排名最高的就是要調度的結點
策略有三個:
spread: 默認策略,儘可能均勻分佈,找容器數少的結點調度
binpack: 和spread相反,儘可能把一個結點佔滿再用其餘結點
random: 隨機
老的Swarm沒有replica的概念,每一個實例都是獨立的個體,因此不須要在調度的時候考慮多副本部署到不一樣主機。 新的Swarm調度算法和老Swarm差很少,不過再也不提供策略選擇,只提供了spread策略。
新的Swarm把結點信息放到一個堆裏(堆排序的堆),以當前結點上的容器數爲建堆的標準建一個最小堆,這樣查找起來就特別快了。
代碼改造
改造策略
最優解:對於一個task,找到的結點應該不含與這個task屬於同一個service的task,同時這個結點在符合這個條件的結點中task數最少。
次優解:全部知足硬性constraint的結點都啓動了與這個task屬於同一個service的task,只能在這其中找一個task數最少的了。
代碼修改
修改兩個源文件就能夠
修改代碼docker/vendor/src/github.com/docker/swarmkit/manager/scheduler/indexed_node_heap.go
1.添加一個函數,定義一個constraint稱爲multihostConstraint,意思是同一個service的不一樣副本要落到不一樣主機上,與其它強制性的constraint不同,這個是儘可能知足的constraint
//檢查某個結點是否已經存在屬於同一個service的task func meetMultihosConstraint(nodeInfo *NodeInfo, serviceID string) bool { for _, task := range nodeInfo.Tasks { sID = task.ServiceID if sID == serviceID { return false } } return true }
2.修改搜索nodeHeap的函數searchHeapToFindMin,加一個參數serviceID
func (nh *nodeHeap) searchHeapToFindMin( meetsConstraints func(*NodeInfo) bool, serviceID string) (*api.Node, int) { var bestNode *api.Node //知足multihostConstraint同時task最少的結點 var secondBestNode *api.Node //沒有知足multihostConstraint的,只能選一個task最少的結點 minTasks := int(^uint(0) >> 1) // max int secondMinTasks := minTasks if nh == nil || len(nh.heap) == 0 { return bestNode, minTasks } // push root to stack for search stack := []int{0} for len(stack) != 0 { // pop an element idx := stack[len(stack)-1] stack = stack[0 : len(stack)-1] heapEntry := &nh.heap[idx] if len(heapEntry.Tasks) >= minTasks { continue } if meetsConstraints(heapEntry) { //知足強制性constraint,再檢查是否知足multihostConstraint if meetMultihosConstraint(heapEntry, serviceID) == true { bestNode = heapEntry.Node minTasks = len(heapEntry.Tasks) } else { if(len(heapEntry.Tasks) < secondMinTasks) { secondBestNode = heapEntry.Node secondMinTasks = len(heapEntry.Tasks) } } } else { // otherwise, push 2 children to stack for further search if 2*idx+1 < len(nh.heap) { stack = append(stack, 2*idx+1) } if 2*idx+2 < len(nh.heap) { stack = append(stack, 2*idx+2) } } } if bestNode == nil { bestNode = secondBestNode minTasks = secondMinTasks } return bestNode, minTasks }
修改代碼docker/vendor/src/github.com/docker/swarmkit/manager/scheduler/scheduler.go裏的scheduleTask函數
// scheduleTask schedules a single task. func (s *Scheduler) scheduleTask(ctx context.Context, t *api.Task) *api.Task { s.pipeline.SetTask(t) //這個函數直接改爲searchHeapToFindMin //s.scanAllNodes是是否掃描所有結點的標誌,直接改爲false //n, _ := s.nodeHeap.findMin(s.pipeline.Process, s.scanAllNodes) n,_ := s.nodeHeap.searchHeapToFindMin(s.pipeline.Process, false, t.ServiceID)