爲Docker Swarm添加調度策略

Swarm簡介

Swarm是Docker的一個集羣管理工具,最開始僅僅是用作集羣管理以及簡單的調度,就像下面的圖所示的,爲Docker Client提供與Docker Engine一樣的Docker API,客戶端操作Swarm就好像在操作一臺機器,實際上後面連了好多Docker Engine,容器都跑在後面的Docker Engine上。Swarm負責將客戶端的請求分配到後端的Docker Engine,比如客戶端要求啓動一個容器,Swarm找一個目前資源使用最少的Docker Engine。 

wKioL1e8b-PB_xpFAACOIUexc1w125.png

因此早期的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。

wKiom1e8bmHzBEKBAALKYW1e61A625.png

可以在Swarm集羣中創建Service,而一個Service有多個實例,比如我創建一個tomcat的Service,名字是tomcat_service,使用的鏡像是tomcat,然後創建3個實例,也就是啓動3個容器,用下面的命令:

docker service create --name tomcat_service  --replicas 3 tomcat

在manager上執行這個命令,manager會把3個容器按調度策略分配到不同的worker上(manager也可以當做worker)。


swarm的調度策略是:在滿足constraint的worker中找一個task(也就是容器數)最少的結點,這種策略叫做spread策略,就是儘可能的把task平均分佈到不同結點。constraint是指一些必須滿足的條件,比如某個task要求分配2G內存。


spread策略是沒問題的,但是swarm在調度的時候沒有把一項很重要的內容加進去,那就是實例的容災。

我搭了一個3個結點的Swarm集羣,manager1,worker1和worker2,創建了一個hello服務,有4個replica,一個world服務,有2個replica,如下:

wKioL1e8bo2yy7K1AAGKlx6HEhg037.png

看上去3個結點每個結點兩個replica,挺好的,但是有一個嚴重的問題,world服務的兩個replica被調度到同一臺主機上,那麼這臺主機掛了,整個服務就掛了。其實replica的概念就是要在多個地方存放,以防止單主機出現問題導致服務不可用。比如HDFS的3個replica一般要放到不同機器上,甚至還要考慮不同機櫃,不同機房。 Kubernetes在調度的時候也考慮了多個replica放到多臺主機上的策略。 docker的開發人員目前只忙着出產品,沒功夫去精雕細琢細節到地方,不過多個replica放到不同主機之後肯定是會加進去的。

本文介紹如何修改Docker 1.12的代碼把replica容災的策略加到Swarm調度策略中。


Swarm調度算法介紹


老的Swarm調度算法可以用下面的圖表示:

wKioL1e8brmhi1VbAAC08terMFE919.png

1.一個調度請求過來,裏面包含很多constraint,比如需要分配4G內存,或者要求必須調度上含有production標籤的結點上,或者要求某個結點沒有被佔用。 

2.所有的結點作爲一個List傳到一個filter鏈裏,這個filter會過濾掉不符合條件的結點,比如內存不夠,輸出一個符合條件的結點List

3.按照策略進行排序,排名最高的就是要調度的結點

策略有三個:

spread: 默認策略,儘量均勻分佈,找容器數少的結點調度

binpack: 和spread相反,儘量把一個結點佔滿再用其他結點

random: 隨機


老的Swarm沒有replica的概念,每個實例都是獨立的個體,所以不需要在調度的時候考慮多副本部署到不同主機。 新的Swarm調度算法和老Swarm差不多,不過不再提供策略選擇,只提供了spread策略。

新的Swarm把結點信息放到一個堆裏(堆排序的堆),以當前結點上的容器數爲建堆的標準建一個最小堆,這樣查找起來就特別快了。

wKiom1e8bt7Bvw1tAADwXBCtMd4939.png




代碼改造


改造策略

最優解:對於一個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)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章