爲Docker Swarm添加調度策略

Swarm簡介java

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

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。git

wKiom1e8bmHzBEKBAALKYW1e61A625.png

能夠在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

wKioL1e8bo2yy7K1AAGKlx6HEhg037.png

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

本文介紹如何修改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)
相關文章
相關標籤/搜索