1. 程式人生 > >為Docker Swarm添加調度策略

為Docker Swarm添加調度策略

nts eid ont get 在操作 node paas sid load

Swarm簡介

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

技術分享圖片

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

技術分享圖片

可以在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,如下:

技術分享圖片

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

本文介紹如何修改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)

更多Docker相關教程見以下內容

Docker安裝應用(CentOS 6.5_x64) http://www.linuxidc.com/Linux/2014-07/104595.htm

Ubuntu 14.04安裝Docker http://www.linuxidc.com/linux/2014-08/105656.htm

Ubuntu使用VNC運行基於Docker的桌面系統 http://www.linuxidc.com/Linux/2015-08/121170.htm

Docker開發平臺的構建 http://www.linuxidc.com/Linux/2017-03/141592.htm

Ubuntu 15.04下安裝Docker http://www.linuxidc.com/Linux/2015-07/120444.htm

在Ubuntu Trusty 14.04 (LTS) (64-bit)安裝Docker http://www.linuxidc.com/Linux/2014-10/108184.htm

在 Ubuntu 15.04 上如何安裝Docker及基本用法 http://www.linuxidc.com/Linux/2015-09/122885.htm

Ubuntu 16.04上Docker使用手記 http://www.linuxidc.com/Linux/2016-12/138490.htm

Docker 的詳細介紹:請點這裏
Docker 的下載地址:請點這裏

本文永久更新鏈接地址:http://www.linuxidc.com/Linux/2017-03/141721.htm

技術分享圖片

為Docker Swarm添加調度策略