Fork me on GitHub

longhorn是如何确定哪个longhorn-manager实例才是负责处理资源调谐的

以volume create 为例

volumen 资源创建后,volume Informer watch 到资源变化,volume controller 的worker 开始处理,因为longhorn-manager 实例有多个,也就意味着informer有多个,那么,究竟是哪个manager的informer才是处理某次资源变化的负责人呢?

代码探析:

==controller/volume_controller.go==

1
2
3
4
5
6
7
8
// 当前的节点 manager 是否负责运行sync逻辑
isResponsible, err := vc.isResponsibleFor(volume, defaultEngineImage)
if err != nil {
return err
}
if !isResponsible {
return nil
}

我们查看下 isResponsibleFor 方法的逻辑,代码省略了判错及一些不影响主流程阅读的内容

==controller/volume_controller.go==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// isResponsibleFor picks a running node that has the default engine image deployed.
// We need the default engine image deployed on the node to perform operation like backup operations.
// Prefer picking the node v.Spec.NodeID if it meet the above requirement. 如果满足上述要求,则优先选择节点 v.Spec.NodeID
func (vc *VolumeController) isResponsibleFor(v *longhorn.Volume, defaultEngineImage string) (bool, error) {
var err error


isResponsible := isControllerResponsibleFor(vc.controllerID, vc.ds, v.Name, v.Spec.NodeID, v.Status.OwnerID)

// No node in the system has the default engine image,
// Fall back to the default logic where we pick a running node to be the owner
if len(readyNodesWithDefaultEI) == 0 {
return isResponsible, nil
}

preferredOwnerEngineAvailable, err := vc.ds.CheckEngineImageReadiness(defaultEngineImage, v.Spec.NodeID)

currentOwnerEngineAvailable, err := vc.ds.CheckEngineImageReadiness(defaultEngineImage, v.Status.OwnerID)

currentNodeEngineAvailable, err := vc.ds.CheckEngineImageReadiness(defaultEngineImage, vc.controllerID)


// 如果当前节点engine不可用,那么就不负责处理了
// 当前节点engine可用 && (最优、继续、需要新的,都是当前节点)
isPreferredOwner := currentNodeEngineAvailable && isResponsible

// (当前那节点engine可用 && 最优节点engine 不可用 && 当前控制器就是的节点就是之前的owner节点) 继续当前节点
continueToBeOwner := currentNodeEngineAvailable && !preferredOwnerEngineAvailable && vc.controllerID == v.Status.OwnerID

// (当前那节点engine可用 && 最优节点engine 不可用 && owner节点不engine不可用) 选择当前节点
requiresNewOwner := currentNodeEngineAvailable && !preferredOwnerEngineAvailable && !currentOwnerEngineAvailable

return isPreferredOwner || continueToBeOwner || requiresNewOwner, nil
}

我们继续看下 isControllerResponsibleFor,这个函数是longhorn-manager 资源用来判断当前 manager是否负责处理 sync 逻辑的 通用函数
==controller/controller_manager.go==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 挑选指导原则:优先 Spec.NodeId,优先最优节点
func isControllerResponsibleFor(controllerID string, ds *datastore.DataStore, name, preferredOwnerID, currentOwnerID string) bool {
// we use this approach so that if there is an issue with the data store
// we don't accidentally transfer ownership
isOwnerUnavailable := func(node string) bool {
isUnavailable, err := ds.IsNodeDownOrDeletedOrMissingManager(node)
if node != "" && err != nil {
logrus.Errorf("Error while checking IsNodeDownOrDeletedOrMissingManager for object %v, node %v: %v", name, node, err)
}
return node == "" || isUnavailable
}

// 当前节点是最优节点
isPreferredOwner := controllerID == preferredOwnerID
// owner节点是当前节点,且最优节点不可用,那么还是用当前owner节点
continueToBeOwner := currentOwnerID == controllerID && isOwnerUnavailable(preferredOwnerID)
// owner节点不可用,最优节点不可用,需要新节点
requiresNewOwner := isOwnerUnavailable(currentOwnerID) && isOwnerUnavailable(preferredOwnerID)
return isPreferredOwner || continueToBeOwner || requiresNewOwner
}

总结:

通过以上代码,我们可以总结出:lognhorn-manager 实例主要是判断当前节点是否可负责处理sync逻辑,而判断能否使用当前节点:

最优先条件是当前节点是 perfer(最优节点,perfer的值一般是 xx.Spec.NodeID)

其次是当前节点就是owner节点

最次是最优节点及owner节点都不可用了,那么就使用当前节点

可以看出,节点的挑选最终的优化方向是往 “资源.Spec.NodeId 是哪个,那么就由这个期望的node上的manager来处理”

==注:voulume contoller 这一块加入了 节点engineImage 的判断,与其他资源(例:replica)有些不同,但核心的处理逻辑还是一致的==

(最优不可用,Owner不可用),两个或多个节点都获取到了负责权,这种情况下,是通过 更新 Status.OwnerID,通过 k8s 的版本冲突机制来保证只有最先更新的manager获得处理权,即这一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 这里是 让哪个 vc instance 来处理 volume,如果是volume create,那就是第一个update 的 vc 获取处理权
// 如果只是 volume (update等)其他操作,也是谁先更新谁获得处理权
if volume.Status.OwnerID != vc.controllerID {
volume.Status.OwnerID = vc.controllerID
volume, err = vc.ds.UpdateVolumeStatus(volume)
if err != nil {
// we don't mind others coming first
if apierrors.IsConflict(errors.Cause(err)) {
return nil
}
return err
}
log.Debugf("Volume got new owner %v", vc.controllerID)
}