[k8s源码分析][kube-scheduler]scheduler之高可用及原理

1. 前言

转载请说明原文出处, 尊重他人劳动成果!

源码位置: https://github.com/nicktming/kubernetes
分支: tming-v1.13 (基于v1.13版本)

本文将分析kube-scheduler如何实现高可用.

k8skube-scheuler的高可用是通过leaderElection实现的, 关于leaderElection可以参考 [k8s源码分析][client-go] k8s选举leaderelection (分布式资源锁实现). 对于同一个schedulerNamescheduler, 无论启动了多少个实例, 只能有一个leader, 并且只有该leader在提供服务, 其余的竞争者只能一直在等待.

2. 例子

关于k8s环境安装可以参考 k8s源码编译以及二进制安装(用于源码开发调试版).

2.1 初始状态

因为master(172.21.0.16)上的scheduler是先起来的, 所以它是leader, 虽然两台机器上都安装了scheduler, 但是只有leader提供服务, 另外一个(也就是worker(172.21.0.12)上面的scheduler是处于等待状态, 并没有真正运行自己的逻辑).

另外这两个scheduler竞争的是同一个资源kube-system/kube-scheduler, 也就是后面看到的kube-system这个namespace中的名字为kube-schedulerendpoints.

example1.png
[root@master kubectl]# ./kubectl get endpoints -n kube-system
NAME                      ENDPOINTS   AGE
kube-controller-manager   <none>      42h
kube-scheduler            <none>      42h
[root@master kubectl]# 
[root@master kubectl]# ./kubectl get endpoints kube-scheduler -o yaml -n kube-system
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"master_74cc3de4-f0be-11e9-9232-525400d54f7e","leaseDurationSeconds":15,"acquireTime":"2019-10-17T09:14:19Z","renewTime":"2019-10-17T09:41:41Z","leaderTransitions":5}'
  creationTimestamp: "2019-10-15T14:56:55Z"
  name: kube-scheduler
  namespace: kube-system
  resourceVersion: "59633"
  selfLink: /api/v1/namespaces/kube-system/endpoints/kube-scheduler
  uid: 0786d7b7-ef5c-11e9-af01-525400d54f7e

2.2 关闭leader

此时关闭master(172.21.0.16)kube-scheduler, 只剩下一个scheduler, 所以worker(172.21.0.12)会成为新的leader并提供服务.

example2.png

查看k8sendpoints的变化, holderIdentity已经由master_74cc3de4-f0be-11e9-9232-525400d54f7e变成worker_f6134651-f0bf-11e9-a387-5254009b5271了.

[root@master kubectl]# ./kubectl get endpoints kube-scheduler -o yaml -n kube-system
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"worker_f6134651-f0bf-11e9-a387-5254009b5271","leaseDurationSeconds":15,"acquireTime":"2019-10-17T09:42:11Z","renewTime":"2019-10-17T09:42:13Z","leaderTransitions":6}'
  creationTimestamp: "2019-10-15T14:56:55Z"
  name: kube-scheduler
  namespace: kube-system
  resourceVersion: "59667"
  selfLink: /api/v1/namespaces/kube-system/endpoints/kube-scheduler
  uid: 0786d7b7-ef5c-11e9-af01-525400d54f7e
[root@master kubectl]# 

查看处于worker(172.21.0.16)上的scheduler的日志有successfully acquired lease kube-system/kube-scheduler.

[root@worker scheduler]# cat config.txt 
./kube-scheduler --master=http://172.21.0.16:8080

[root@worker scheduler]# ./kube-scheduler --master=http://172.21.0.16:8080
...
I1017 17:24:47.941202   32277 leaderelection.go:205] attempting to acquire leader lease  kube-system/kube-scheduler...


I1017 17:42:11.815383   32277 leaderelection.go:214] successfully acquired lease kube-system/kube-scheduler

2.3 启动一个自定义调度器

此时我在master()节点上启动一个my-scheduler. 关于如果启动自定义调度器可以参考 [k8s源码分析][kube-scheduler]scheduler之自定义调度器(1)

example3.png
[root@master kubectl]# ./kubectl get endpoints -n kube-system
NAME                      ENDPOINTS   AGE
kube-controller-manager   <none>      42h
kube-scheduler            <none>      42h
my-scheduler              <none>      7s
[root@master kubectl]# ./kubectl get endpoints my-scheduler -o yaml -n kube-system
apiVersion: v1
kind: Endpoints
metadata:
  annotations:
    control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"master_1dd3cdbe-f0c3-11e9-985f-525400d54f7e","leaseDurationSeconds":15,"acquireTime":"2019-10-17T09:47:23Z","renewTime":"2019-10-17T09:47:45Z","leaderTransitions":0}'
  creationTimestamp: "2019-10-17T09:47:23Z"
  name: my-scheduler
  namespace: kube-system
  resourceVersion: "60119"
  selfLink: /api/v1/namespaces/kube-system/endpoints/my-scheduler
  uid: 1e6d5569-f0c3-11e9-b23b-525400d54f7e
[root@master kubectl]# 

可以看到在对应的endpoints上多了一个新的my-scheduler. 然而因为这两个scheduler竞争的资源不同, 所以各自都是其对应资源的leader并且都会提供服务. my-scheduler这个会为schedulerName=my-scheduler这样的pods分配节点, 而default-scheduler会为使用默认调度器的pods分配节点.

[root@master kubectl]# cat pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
  - name: podtest
    image: nginx
    ports:
    - containerPort: 80
[root@master kubectl]# cat pod-scheduler.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: test-schduler
spec:
  schedulerName: my-scheduler
  containers:
  - name: podtest-scheduler
    image: nginx
    ports:
    - containerPort: 80
[root@master kubectl]# ./kubectl get pods
No resources found.
[root@master kubectl]# ./kubectl apply -f pod.yaml 
pod/test created
[root@master kubectl]# ./kubectl apply -f pod-scheduler.yaml 
pod/test-schduler created
[root@master kubectl]# ./kubectl get pods
NAME            READY   STATUS    RESTARTS   AGE
test            1/1     Running   0          3m3s
test-schduler   1/1     Running   0          2m55s
[root@master kubectl]# ./kubectl get pod test-schduler -o yaml | grep schedulerName
      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"test-schduler","namespace":"default"},"spec":{"containers":[{"image":"nginx","name":"podtest-scheduler","ports":[{"containerPort":80}]}],"schedulerName":"my-scheduler"}}
  schedulerName: my-scheduler
[root@master kubectl]# ./kubectl get pod test -o yaml | grep schedulerName
  schedulerName: default-scheduler
[root@master kubectl]# 

如果需要在一台机器上起多个scheduler, 需要改一下healthmetric的端口号. 这里就不测试了, 因为上面的结果已经很清晰了, default-schedulermy-scheduler各自调度属于自己的pod.

3. 源码分析

3.1 结构体与默认值

这里是与leaderElection相关的配置了, 其中需要注意的

LockObjectNamespace: 代表的就是namespace.
LockObjectName: 代表的就是name.
ResourceLock: 代表的是什么类型的资源, leaderElection目前就支持三种资源endpoints, configmaplease.
LeaderElect: 代表是否启用高可用.

// pkg/scheduler/apis/config/types.go
type KubeSchedulerConfiguration struct {
...
LeaderElection KubeSchedulerLeaderElectionConfiguration
...
}

type KubeSchedulerLeaderElectionConfiguration struct {
    apiserverconfig.LeaderElectionConfiguration
    // LockObjectNamespace defines the namespace of the lock object
    LockObjectNamespace string
    // LockObjectName defines the lock object name
    LockObjectName string
}

type LeaderElectionConfiguration struct {
    LeaderElect bool
    LeaseDuration metav1.Duration
    RenewDeadline metav1.Duration
    RetryPeriod metav1.Duration
    ResourceLock string
}

当前通过配置文件可以直接配置, 在 [k8s源码分析][kube-scheduler]scheduler之自定义调度器(2) 中就已经体现过了. 但是没有配置这些参数的时候发现还是启用了高可用, 并且从上面的例子中也可以看到默认调度器中也生成kube-system/kube-scheduler这样的endpoints.

所以来看一下这些配置的系统默认值.

// pkg/scheduler/apis/config/v1alpha1/defaults.go

func SetDefaults_KubeSchedulerConfiguration(obj *kubescedulerconfigv1alpha1.KubeSchedulerConfiguration) {
...
    if len(obj.LeaderElection.LockObjectNamespace) == 0 {
        // obj.LeaderElection.LockObjectNamespace = kube-system
        obj.LeaderElection.LockObjectNamespace = kubescedulerconfigv1alpha1.SchedulerDefaultLockObjectNamespace
    }
    if len(obj.LeaderElection.LockObjectName) == 0 {
        // obj.LeaderElection.LockObjectName = kube-scheduler
        obj.LeaderElection.LockObjectName = kubescedulerconfigv1alpha1.SchedulerDefaultLockObjectName
    }
...
}

// k8s.io/apiserver/pkg/apis/config/v1alpha1/defaults.go
func RecommendedDefaultLeaderElectionConfiguration(obj *LeaderElectionConfiguration) {
    zero := metav1.Duration{}
    if obj.LeaseDuration == zero {
        obj.LeaseDuration = metav1.Duration{Duration: 15 * time.Second}
    }
    if obj.RenewDeadline == zero {
        obj.RenewDeadline = metav1.Duration{Duration: 10 * time.Second}
    }
    if obj.RetryPeriod == zero {
        obj.RetryPeriod = metav1.Duration{Duration: 2 * time.Second}
    }
    if obj.ResourceLock == "" {
        obj.ResourceLock = EndpointsResourceLock
    }
    if obj.LeaderElect == nil {
        obj.LeaderElect = utilpointer.BoolPtr(true)
    }
}

所以默认设置的为

LockObjectNamespace = "kube-system"
LockObjectName = "kube-scheduler"
ResourceLock = "endpoints"
LeaderElect = true

3.2 流程

关于启动流程在 [k8s源码分析][kube-scheduler]scheduler之启动run(1) 已经分析过了, 这里就只关注跟leaderElection相关的部分.

// cmd/kube-scheduler/app/options/options.go

func (o *Options) Config() (*schedulerappconfig.Config, error) {
    ...
// Set up leader election if enabled.
    var leaderElectionConfig *leaderelection.LeaderElectionConfig
    // 默认值就是true 只要用户不设置为false 这一步就会执行
    // 也就是说kube-scheduler 默认就是支持高可用
    if c.ComponentConfig.LeaderElection.LeaderElect {
        leaderElectionConfig, err = makeLeaderElectionConfig(c.ComponentConfig.LeaderElection, leaderElectionClient, recorder)
        if err != nil {
            return nil, err
        }
    }
    ...
    c.LeaderElection = leaderElectionConfig
    ...
}

func makeLeaderElectionConfig(config kubeschedulerconfig.KubeSchedulerLeaderElectionConfiguration, client clientset.Interface, recorder record.EventRecorder) (*leaderelection.LeaderElectionConfig, error) {
    hostname, err := os.Hostname()
    if err != nil {
        return nil, fmt.Errorf("unable to get hostname: %v", err)
    }
    // add a uniquifier so that two processes on the same host don't accidentally both become active
    id := hostname + "_" + string(uuid.NewUUID())

    rl, err := resourcelock.New(config.ResourceLock,
        config.LockObjectNamespace,
        config.LockObjectName,
        client.CoreV1(),
        resourcelock.ResourceLockConfig{
            Identity:      id,
            EventRecorder: recorder,
        })
    if err != nil {
        return nil, fmt.Errorf("couldn't create resource lock: %v", err)
    }

    return &leaderelection.LeaderElectionConfig{
        Lock:          rl,
        LeaseDuration: config.LeaseDuration.Duration,
        RenewDeadline: config.RenewDeadline.Duration,
        RetryPeriod:   config.RetryPeriod.Duration,
        WatchDog:      leaderelection.NewLeaderHealthzAdaptor(time.Second * 20),
        Name:          "kube-scheduler",
    }, nil
}

这里可以看到id是由主机名与一个uuid合并的字符串. 然后生成一个LeaderElectionConfig对象. 这些在 [k8s源码分析][client-go] k8s选举leaderelection (分布式资源锁实现) 已经详细分析过了.

最后看一下运行

// cmd/kube-scheduler/app/server.go

func Run(cc schedulerserverconfig.CompletedConfig, stopCh <-chan struct{}) error {
...
// Prepare a reusable runCommand function.
    run := func(ctx context.Context) {
        sched.Run()
        <-ctx.Done()
    }

    ctx, cancel := context.WithCancel(context.TODO()) // TODO once Run() accepts a context, it should be used here
    defer cancel()

    go func() {
        select {
        case <-stopCh:
            cancel()
        case <-ctx.Done():
        }
    }()

    // If leader election is enabled, runCommand via LeaderElector until done and exit.
    // 启动高可用
    if cc.LeaderElection != nil {
        cc.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{
            // 调用run方法
            OnStartedLeading: run,
            OnStoppedLeading: func() {
                utilruntime.HandleError(fmt.Errorf("lost master"))
            },
        }
        leaderElector, err := leaderelection.NewLeaderElector(*cc.LeaderElection)
        if err != nil {
            return fmt.Errorf("couldn't create leader elector: %v", err)
        }

        leaderElector.Run(ctx)

        return fmt.Errorf("lost lease")
    }

    // Leader election is disabled, so runCommand inline until done.
    run(ctx)
    return fmt.Errorf("finished without leader elect")
}

如果启动了高可用, 实现配置一下该client在获得leader之后需要回调的函数run, 然后生成一个leaderElector实例, 调用其Run去竞争leadership.

这些都已经在 [k8s源码分析][client-go] k8s选举leaderelection (分布式资源锁实现) 已经详细分析过了, 就不多说了.

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