[k8s源码分析][controller-manager] ReplicaSetController(ReplicaSet)分析(2)

接上文 [k8s源码分析][controller-manager] ReplicaSetController(ReplicaSet)分析(1)

4.3 消费者

分析完了生产者, 接下来看一下消费者是如何处理队列中的key的.

syncReplicaSet
func (rsc *ReplicaSetController) syncReplicaSet(key string) error {
    startTime := time.Now()
    defer func() {
        klog.V(4).Infof("Finished syncing %v %q (%v)", rsc.Kind, key, time.Since(startTime))
    }()
    // 解析key 得到replcaset的namespace和name
    namespace, name, err := cache.SplitMetaNamespaceKey(key)
    if err != nil {
        return err
    }
    // 本地缓存
    rs, err := rsc.rsLister.ReplicaSets(namespace).Get(name)
    if errors.IsNotFound(err) {
        klog.V(4).Infof("%v %v has been deleted", rsc.Kind, key)
        // 如果该replicaset不在了 直接删除
        rsc.expectations.DeleteExpectations(key)
        return nil
    }
    if err != nil {
        return err
    }
    // 判断是否需要去计算replicas数量与当前实际存在的数量是否相等
    rsNeedsSync := rsc.expectations.SatisfiedExpectations(key)
    // 转化成selector类型
    selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
    if err != nil {
        utilruntime.HandleError(fmt.Errorf("Error converting pod selector to selector: %v", err))
        return nil
    }
    // 取出该namespace下所有的pods
    allPods, err := rsc.podLister.Pods(rs.Namespace).List(labels.Everything())
    if err != nil {
        return err
    }
    // Ignore inactive pods.
    // 过滤出那些没有active的pods
    var filteredPods []*v1.Pod
    for _, pod := range allPods {
        if controller.IsPodActive(pod) {
            filteredPods = append(filteredPods, pod)
        }
    }
    // 过滤出所有属于该replicaset的pods存到了filteredPods中
    filteredPods, err = rsc.claimPods(rs, selector, filteredPods)
    if err != nil {
        return err
    }
    var manageReplicasErr error
    if rsNeedsSync && rs.DeletionTimestamp == nil {
        // 计算从而达到replica的要求
        manageReplicasErr = rsc.manageReplicas(filteredPods, rs)
    }
    rs = rs.DeepCopy()
    newStatus := calculateStatus(rs, filteredPods, manageReplicasErr)

    // Always updates status as pods come up or die.
    updatedRS, err := updateReplicaSetStatus(rsc.kubeClient.AppsV1().ReplicaSets(rs.Namespace), rs, newStatus)
    if err != nil {
        // Multiple things could lead to this update failing. Requeuing the replica set ensures
        // Returning an error causes a requeue without forcing a hotloop
        return err
    }
    // Resync the ReplicaSet after MinReadySeconds as a last line of defense to guard against clock-skew.
    if manageReplicasErr == nil && updatedRS.Spec.MinReadySeconds > 0 &&
        updatedRS.Status.ReadyReplicas == *(updatedRS.Spec.Replicas) &&
        updatedRS.Status.AvailableReplicas != *(updatedRS.Spec.Replicas) {
        rsc.enqueueReplicaSetAfter(updatedRS, time.Duration(updatedRS.Spec.MinReadySeconds)*time.Second)
    }
    return manageReplicasErr
}

syncReplicaSet内容较多, 简单的就滤过.
1. 解析key, 得到replcasetnamespacename.
2. 从本地缓存中根据namespacename获得需要处理的replicaset. 如果已经不存在, 则从expectations中删除对应的key.
3. 根据SatisfiedExpectations方法判断是否需要进行同步.
4. 找到属于该replicasetpods, 并存到filteredPods中.
5. 如果需要同步, 调用manageReplicas来同步replicaset.replicas数量是否匹配.
6. 更新replicaset的状态.

claimPods
func (rsc *ReplicaSetController) claimPods(rs *apps.ReplicaSet, selector labels.Selector, filteredPods []*v1.Pod) ([]*v1.Pod, error) {
    canAdoptFunc := controller.RecheckDeletionTimestamp(func() (metav1.Object, error) {
        fresh, err := rsc.kubeClient.AppsV1().ReplicaSets(rs.Namespace).Get(rs.Name, metav1.GetOptions{})
        if err != nil {
            return nil, err
        }
        // 再次确认一下replicaset是否有变化
        if fresh.UID != rs.UID {
            return nil, fmt.Errorf("original %v %v/%v is gone: got uid %v, wanted %v", rsc.Kind, rs.Namespace, rs.Name, fresh.UID, rs.UID)
        }
        return fresh, nil
    })
    cm := controller.NewPodControllerRefManager(rsc.podControl, rs, selector, rsc.GroupVersionKind, canAdoptFunc)
    return cm.ClaimPods(filteredPods)
}

1. 实现定义了canAdoptFunc方法, 然后根据可以操作podpodControlselector规则等等参数生成一个PodControllerRefManager对象.
2. 调用PodControllerRefManager对象的ClaimPod方法为该rs过滤出属于它的pod.

关于PodControllerRefManager已经在 [k8s源码分析][controller-manager] controller_ref_manager分析 中已经详细介绍过了.

manageReplicas
func (rsc *ReplicaSetController) manageReplicas(filteredPods []*v1.Pod, rs *apps.ReplicaSet) error {
    // 查看当前已经拥有的pod与replicas之间的差值  看看是生成多了还是少了
    diff := len(filteredPods) - int(*(rs.Spec.Replicas))
    rsKey, err := controller.KeyFunc(rs)
    if err != nil {
        utilruntime.HandleError(fmt.Errorf("Couldn't get key for %v %#v: %v", rsc.Kind, rs, err))
        return nil
    }
    if diff < 0 {
        // 表明生成的pod还不够 
        diff *= -1
        if diff > rsc.burstReplicas {
            // 一次最多生成这么多
            diff = rsc.burstReplicas
        }
        rsc.expectations.ExpectCreations(rsKey, diff)
        klog.V(2).Infof("Too few replicas for %v %s/%s, need %d, creating %d", rsc.Kind, rs.Namespace, rs.Name, *(rs.Spec.Replicas), diff)
        // 批量生成
        successfulCreations, err := slowStartBatch(diff, controller.SlowStartInitialBatchSize, func() error {
            boolPtr := func(b bool) *bool { return &b }
            controllerRef := &metav1.OwnerReference{
                APIVersion:         rsc.GroupVersion().String(),
                Kind:               rsc.Kind,
                Name:               rs.Name,
                UID:                rs.UID,
                BlockOwnerDeletion: boolPtr(true),
                Controller:         boolPtr(true),
            }
            err := rsc.podControl.CreatePodsWithControllerRef(rs.Namespace, &rs.Spec.Template, rs, controllerRef)
            if err != nil && errors.IsTimeout(err) {
                return nil
            }
            return err
        })
        // 还有skippedPods个生成失败的
        if skippedPods := diff - successfulCreations; skippedPods > 0 {
            klog.V(2).Infof("Slow-start failure. Skipping creation of %d pods, decrementing expectations for %v %v/%v", skippedPods, rsc.Kind, rs.Namespace, rs.Name)
            for i := 0; i < skippedPods; i++ {
                rsc.expectations.CreationObserved(rsKey)
            }
        }
        return err
    } else if diff > 0 {
        if diff > rsc.burstReplicas {
            diff = rsc.burstReplicas
        }
        klog.V(2).Infof("Too many replicas for %v %s/%s, need %d, deleting %d", rsc.Kind, rs.Namespace, rs.Name, *(rs.Spec.Replicas), diff)
        podsToDelete := getPodsToDelete(filteredPods, diff)
        rsc.expectations.ExpectDeletions(rsKey, getPodKeys(podsToDelete))
        errCh := make(chan error, diff)
        var wg sync.WaitGroup
        wg.Add(diff)
        for _, pod := range podsToDelete {
            go func(targetPod *v1.Pod) {
                defer wg.Done()
                if err := rsc.podControl.DeletePod(rs.Namespace, targetPod.Name, rs); err != nil {
                    // Decrement the expected number of deletes because the informer won't observe this deletion
                    podKey := controller.PodKey(targetPod)
                    klog.V(2).Infof("Failed to delete %v, decrementing expectations for %v %s/%s", podKey, rsc.Kind, rs.Namespace, rs.Name)
                    rsc.expectations.DeletionObserved(rsKey, podKey)
                    errCh <- err
                }
            }(pod)
        }
        wg.Wait()
        select {
        case err := <-errCh:
            if err != nil {
                return err
            }
        default:
        }
    }
    return nil
}

该方法主要作用是:
1. 计算出目前该replicaset已经拥有的podsreplicas数量之间的差值.
2. 如果还没有到达replicas, 则继续生成diffpods.
3. 如果超过了replicas, 则删除diffpods.
4. 关于expectations在后面统一分析.

calculateStatus

根据filteredPods计算当前replicaset的状态.

4.4 关于expectations的理解

一般expectations要么就是期望增加, 要么就是期望减少. 一般不会说既期望增加多少, 同时又希望减少多少.

以期望增加为例

1.manageReplicas方法中设置了期望值. 此时diff < 0, 期望增加diffpod. 所以调用了rsc.expectations.ExpectCreations(rsKey, diff)设置了期望增加的数量.
2. 设置完了之后就利用podControl生成pod的操作, 此时又两种情况:

2.1 假设podController调用生成pod的操作没有错误, 此时就会进入到podInformer中的AddPod方法中, 而AddPod中检查到podowner的时候, 会调用rsc.expectations.CreationObserved(rsKey), 此时就会在对应的期望中减少一个, 因为已经成功了.
2.2 假设podController调用生成pod的操作有错误, 那在manageReplicas方法中可以看到会把这些没有成功调用的每个都调用rsc.expectations.CreationObserved(rsKey)减少一个. (因为这个时候不减, 没有别的地方可以减的了, 因为podInformer无法监控到).
3. 理解了2之后, 可以看到syncReplicaSet方法中调用SatisfiedExpectations来判断是否需要调用manageReplicas方法, 说白一点就是等待manageReplicas一轮做完, 什么叫一轮呢?所以让它生成的pod全部进入了podInformerAddPod方法, 就叫一轮. 可以看看SatisfiedExpectations的具体实现.

func (e *ControlleeExpectations) Fulfilled() bool {
    return atomic.LoadInt64(&e.add) <= 0 && atomic.LoadInt64(&e.del) <= 0
}
func (r *ControllerExpectations) SatisfiedExpectations(controllerKey string) bool {
    if exp, exists, err := r.GetExpectations(controllerKey); exists {
        // 是否满足
        if exp.Fulfilled() {
            klog.V(4).Infof("Controller expectations fulfilled %#v", exp)
            return true
        } else if exp.isExpired() {
            // 已经过期了 等待了太长时间
            klog.V(4).Infof("Controller expectations expired %#v", exp)
            return true
        } else {
            // 此时并不是所有的pod都已经进入到了podInformer的AddPod方法中
            klog.V(4).Infof("Controller still waiting on expectations %#v", exp)
            return false
        }
    } else if err != nil {
        klog.V(2).Infof("Error encountered while checking expectations %#v, forcing sync", err)
    } else {
        klog.V(4).Infof("Controller %v either never recorded expectations, or the ttl expired.", controllerKey)
    }
    // 不存在当然去同步 或者出现错误
    return true
}

相信理解了上面说的, 理解SatisfiedExpectations方法就不是很难了.

5. 例子的问题

在本文开始的例子中先创建的pod由于是一个孤儿pod, 因此在claimPods方法中由于match到了后面创建的replicaset, 所以该replicaset就会尝试接收这个pod, 并且向api-server发送patch请求将ownerReference这个字段更新到该pod(test)上.

test这个pod不同的是, replicatest-rdjv6这个pod在创建的时候就已经有ownerRerference字段了. 在manageReplicas方法中可以看到直接加入了OwnerReference字段.

6. 留一个问题

创建一个replicaset, 然后突然kube-controller-manager由于某种原因挂了, 此时删除这个replicaset, 然后观察会发生什么, 之后再恢复kube-controller-manager, 再看看会发生什么

[root@master kubectl]# ./kubectl apply -f replicaset.yaml 
replicaset.apps/replicatest created
[root@master kubectl]# ./kubectl get pods
NAME                READY   STATUS    RESTARTS   AGE
replicatest-9q9sm   1/1     Running   0          4s
replicatest-rdc8c   1/1     Running   0          4s
[root@master kubectl]# ./kubectl get rs
NAME          DESIRED   CURRENT   READY   AGE
replicatest   2         2         2       7s

此时关闭kube-controller-manager组件, 删除了replicaset, 但是对应的pods并没有删除.

[root@master kubectl]# ./kubectl delete rs replicatest
replicaset.extensions "replicatest" deleted
[root@master kubectl]# ./kubectl get rs
No resources found.
[root@master kubectl]# ./kubectl get pods
NAME                READY   STATUS    RESTARTS   AGE
replicatest-9q9sm   1/1     Running   0          38s
replicatest-rdc8c   1/1     Running   0          38s
[root@master kubectl]# 

此时重新打开kube-controller-manager组件, 可以看到这两个pods被删除了.

[root@master kubectl]# ./kubectl get pods
NAME                READY   STATUS    RESTARTS   AGE
replicatest-9q9sm   1/1     Running   0          2m48s
replicatest-rdc8c   1/1     Running   0          2m48s
[root@master kubectl]# ./kubectl get pods
NAME                READY   STATUS        RESTARTS   AGE
replicatest-9q9sm   0/1     Terminating   0          2m49s
replicatest-rdc8c   0/1     Terminating   0          2m49s
[root@master kubectl]# ./kubectl get pods
No resources found.
[root@master kubectl]# 

打开kube-controller-manager后删除pods的操作是由garbagecollector这个controller来做的.

7. 总结

1. 每当有replicaset的增加/更新/删除都会将replicaset进入队列来进行判断是否需要重新同步.
2. 每当有pods的增加/更新/删除也会根据情况找到相应的replicaset进入队列来进行判断是否需要重新同步.
3. 同步replicas的过程其实就是通过增加或者删除pods保持当前该replicaset拥有的pods的个数正好等于replicas. 但是是否需要同步是通过该replicasetexpectations来控制的.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容