Docker Swarm原理大解密

来源:互联网 发布:淘宝店铺图标大全 编辑:程序博客网 时间:2024/06/03 19:56

Docker Swarm原理大解密

Docker Swarm是docker公司2014年出品的基于docker的集群管理调度工具,官方文档地址:https://docs.docker.com/swarm/overview/。Swarm能够把多台主机构建成一个docker的集群,用户可以只和swarm api操作来管理多个主机上的docker,再结合上Overlay的网络实现简单的容器调度和互访。

Docker Swarm在设计上遵从了可插拔的设计思想,安装集群只需要启动几个docker就可以完成,安装过程可以参考这里:http://www.tuicool.com/articles/UJJJFjU。

总结下Swarm的特性:
1.工作节点的注册和发现
2.管理节点收集集群中所有的信息
3.管理节点支持HA
4.管理节点能感知集群的变化,在节点挂掉后重新调度上面的container
5.提供filter和scheduler的调度策略调度集群里的容器

下面,本文会从源码层面解密Swarm是如何实现上面的特性的。

首先上一张整体的架构图。
来自daocloud的架构图。
这里写图片描述
http://blog.daocloud.io/wp-content/uploads/2015/01/swarmarchitecture.jpg

工作节点的注册和发现

在工作节点启动时会在后端的kvstore上注册一个节点,路径是etcd://ip:2376/docker/swarm/nodeip,Worker会把当前集群的eth0
的ip注册上etcd,然后设置上一个ttl时间,比如3秒。然后启一个for循环每隔2秒(配置heartbeat)注册一次,这样,如果etcd上这个节点没了就说明这个worker已经挂了。

for {        log.WithFields(log.Fields{"addr": addr, "discovery": dflag}).Infof("Registering on the discovery service every %s...", hb)        if err := d.Register(addr); err != nil {            log.Error(err)        }        time.Sleep(hb)    }

Manager的leader会启动一个go router watch后端的kvstore上注册上来的ip,如果是新节点注册上来就把节点加入到manager的内存中,开始收集数据,如果是节点挂了就删除

discoveryCh, errCh := cluster.discovery.Watch(nil)go cluster.monitorDiscovery(discoveryCh, errCh)go cluster.monitorPendingEngines()for {        select {        case entries := <-ch:            added, removed := currentEntries.Diff(entries)            currentEntries = entries        // Remove engines first. `addEngine` will refuse to add an engine        // if there's already an engine with the same ID.  If an engine        // changes address, we have to first remove it then add it back.            for _, entry := range removed {                c.removeEngine(entry.String())            }            for _, entry := range added {                c.addEngine(entry.String())            }        case err := <-errCh:            log.Errorf("Discovery error: %v", err)        }    }

管理节点收集集群中所有的信息

管理节点会收集集群中所有主机的信息放到内存中。当一个主机加入到Swarm中时,首先会对节点上所有的信息都收集一把到内存中,然后会建立一个docker client长链接,通过event API获取这个主机上的更新。

加入主机时的代码,首先做主机的全同步,然后启动eventMonitor,监控主机上的event:

e.eventsMonitor = NewEventsMonitor(e.apiClient, e.handler)// Fetch the engine labels.if err := e.updateSpecs(); err != nil {    return err}e.StartMonitorEvents()// Force a state update before returning.if err := e.RefreshContainers(true); err != nil {    return err}if err := e.RefreshImages(); err != nil {    return err}// Do not check error as older daemon doesn't support this call.e.RefreshVolumes()e.RefreshNetworks()

Event的Handler,会根据event的类别更新对应类型的数据。这里由于考虑docker event的兼容性有点长,我就只贴一段:

switch msg.Type {case "network":    e.refreshNetwork(msg.Actor.ID)case "volume":    e.refreshVolume(msg.Actor.ID)case "image":    e.RefreshImages()case "container":    action := msg.Action    // healthcheck events are like 'health_status: unhealthy'    if strings.HasPrefix(action, "health_status") {        action = "health_status"    }    switch action {    case "commit":        // commit a container will generate a new image        e.RefreshImages()    case "die", "kill", "oom", "pause", "start", "restart", "stop", "unpause", "rename", "update", "health_status":        e.refreshContainer(msg.ID, true)    case "top", "resize", "export", "exec_create", "exec_start", "exec_detach", "attach", "detach", "extract-to-dir", "copy", "archive-path":        // no action needed    default:        e.refreshContainer(msg.ID, false)

管理节点支持HA

同其他很多的分布式的项目一样,Docker Swarm也是利用了raft里选举算法做的HA,我们来看下它的实现。

首先创建好candidata和follower,顺便说下leader election的path是docker/swarm/leader

client := kvDiscovery.Store()p := path.Join(kvDiscovery.Prefix(), leaderElectionPath)candidate := leadership.NewCandidate(client, p, addr, leaderTTL)follower := leadership.NewFollower(client, p)

然后启两个协程,一个进行选举,如果成功了则成为leader,一个监听选举成功的消息,如果监听到别的manager成为leader则把自己设置成candidate,如果API请求到candidate会proxy到真正的manager。

primary := api.NewPrimary(cluster, tlsConfig, &statusHandler{cluster, candidate, follower}, c.GlobalBool("debug"), c.Bool("cors"))replica := api.NewReplica(primary, tlsConfig)go func() {    for {        run(cluster, candidate, server, primary, replica)        time.Sleep(defaultRecoverTime)    }}()go func() {    for {        follow(follower, replica, addr)        time.Sleep(defaultRecoverTime)    }}()server.SetHandler(primary)

管理节点能感知集群的变化,在节点挂掉后重新调度上面的container

由于worker会loop往kvstore上发送消息,因此如果节点挂掉时manager能立刻感知到变化,并触发removeEngine的动作,把container重新调度到其他节点上就很容易做到了。

提供filter和scheduler的调度策略调度集群里的容器

其实有了所有集群里的所有节点的信息,调度容器就变得比较简单了。Swarm提供了Filter和scheduler来让用户定义调度的策略。

调度本质上是让用户可以定义Container分配到集群中的策略。
Filter指定了如果满足这样的条件的节点不会(会)被分配上。
Scheduler指定了满足Filter后的节点按照怎样的优先级排序,排在前面的会被有限分配上Container。

Filter和Scheduler的种类我就不赘述了,可以参考官方文档:https://docs.docker.com/swarm/scheduler/rescheduling/#rescheduling-policies (貌似最近又有了新的策略 rescheduler)

调度的代码如下:

accepted, err := filter.ApplyFilters(s.filters, config, nodes, soft)if err != nil {    return nil, err}if len(accepted) == 0 {    return nil, errNoNodeAvailable}return s.strategy.RankAndSort(config, accepted)

在使用Docker Swarm的时候大家其实可以发现,Swarm的设计还是有一些缺陷的,这会导致Swarm的一些局限性,比如:

1.worker的行为过于简单。只是往kvstore上同步状态,就启动一个Container,不做任何实际的工作,把所有活都交给Manager干,颇为浪费。
2.由于worker”啥也不干“,Manager必须保持所有节点的tcp长链接,扩展性很差。
3.没有加入副本控制。

总结下,Swarm作为一代的Docker调度工具提供了基本的调度能力,可以满足一些内部的CI/CD系统使用,但是由于扩展性较差和没有副本控制,不能直接拿来部署线上系统,这是有的遗憾的。

0 1