基于1.1.2版本kubelet源码阅读(1)

kubelet的主体流程部分,主要是为了理解kubelet组件的主体流程是如何运行的,是如何获取pod信息,然后如何对pod进行部署和update等操作。
(主要是根据网上一些公开的信息,再根据自己的理解来进行整合,加深自己对k8s源码的学习)

1、从cmd/kubelet/kubelet.go开始main()函数

  func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    //kubelet 的main函数入口,新建一个NewKubeletServer
    //NewKubeletServer()初始化kubelet Server的fields为默认值。然后在初始化flags和logs之后就可开始Run kubelet Server
    s := app.NewKubeletServer()
    //解析flag
    s.AddFlags(pflag.CommandLine)
    //future work:后面要了解参数是如何写到系统里面的?是InitFlags如何运作么?
    util.InitFlags()
    util.InitLogs()
    defer util.FlushLogs()
    verflag.PrintAndExitIfRequested()
    if err := s.Run(nil); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", err)
        os.Exit(1)
    }
}

main函数主要执行NewKubeletServer()、AddFlags(pflag.CommandLine)、 s.Run(nil)这三个步骤,下面具体分析这三个函数

2、NewKubeletServer()解析

定义在cmd/kubelet/server.go
NewKubeletServer()创建一个KubeletServer,主要是完成参数的初始化—with default values
return &KubeletServer{…}
下面 给出部分参数的含义:

        Address:                     net.ParseIP("0.0.0.0"),
        ContainerRuntime:            "docker",
        DockerExecHandlerName:       "native",
        HealthzBindAddress:          net.ParseIP("127.0.0.1"),
        HealthzPort:                 10248,
        ImageGCHighThresholdPercent: 90,//表示此盘利用率超过90%的时候,会一直对image资源进行回收
        ImageGCLowThresholdPercent:  80,//表示此盘利用率低于80%的时候,不会进行GC
        KubeConfig:                  util.NewStringFlag("/var/lib/kubelet/kubeconfig"),
        MaxContainerCount:           100, //每个节点最多保留100个死亡实例
        MaxPerPodContainerCount:     2,   //每个容器最多保留2个死亡实例
        RegisterNode:        true, // will be ignored if no apiserver is configured     
        ResourceContainer:   "/kubelet",    
        RootDirectory:       defaultRootDir, //默认值是/var/lib/kubelet,存放配置及VM卷等数据

3、 s.Run(nil)

定义在cmd/kubelet/server.go

/*
    Run 基于指定的KubeletConfig运行一个KubeletServer,never exist!
    KubeletConfig可能是nil的。If nil, 在KubeletServer中利用默认值完成初始化设置
    If not nil,the default values 会被忽略
*/
func (s *KubeletServer) Run(kcfg *KubeletConfig) error {
    if kcfg == nil {
        //第一次是通过s.Run(nil)来调用
        //准备一个KubeletConfig,配置一些参数
        //KubeletConfig会返回适合运行的KubeletConfig,如果服务器设置无效,则返回错误。 它不会启动任何后台进程。
        cfg, err := s.KubeletConfig()**[1]**
        if err != nil {
            return err
        }
        kcfg = cfg
        //调用CreateAPIServerClientConfig初始化clientConfig
        //CreateAPIServerClientConfig使用command line flags来生成 client.Config,including api-server-list
        //KubeClient是kubelet 和 Api server传递信息的唯一途径。
        CreateAPIServerClientConfig根据用户的传递--kubeconfig或者默认.kubeconfig文件来解析client配置。
        clientConfig, err := s.CreateAPIServerClientConfig()
        if err == nil {
            /*
                最终初始化KubeClient。
                定义在client "k8s.io/kubernetes/pkg/client/unversioned/helper.go"
                New(c *Config)为指定的配置创建一个Kubernetes client。
                该client可以对pods,replication controllers, daemons, and services这些对象执行list, get, update and delete操作。
                如果提供的配置无效,则返回错误。
            */
            kcfg.KubeClient, err = client.New(clientConfig)
        }
        if err != nil && len(s.APIServerList) > 0 {
            glog.Warningf("No API client: %v", err)
        }
        //cloudprovider 和IaaS云商有关
        cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile)
        if err != nil {
            return err
        }
        glog.V(2).Infof("Successfully initialized cloud provider: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
        kcfg.Cloud = cloud
    }
    /*
        启动cadvisor来监控本地的容器
        通过CAdvisor我们可以看到kuberlet管理的机器上container的资源统计信息
        cadvisor源码在pkg/kubelet/cadvisor/cadvisor_linux.go中
    */
    if kcfg.CAdvisorInterface == nil {
        ca, err := cadvisor.New(s.CAdvisorPort)
        if err != nil {
            return err
        }
        kcfg.CAdvisorInterface = ca
    }

    util.ReallyCrash = s.ReallyCrashForTesting
    rand.Seed(time.Now().UTC().UnixNano())
    /*
        设置preferred Dockercfg 文件path,这个是为了读取.dockercfg文件,
        该文件中的secret可用于pull/push docker registry中的docker镜像,默认的路径为:/var/lib/kubelet
    */
    credentialprovider.SetPreferredDockercfgPath(s.RootDirectory)

    glog.V(2).Infof("Using root directory: %v", s.RootDirectory)

    // TODO(vmarmol): Do this through container config.
    oomAdjuster := oom.NewOomAdjuster()
    /*
         linux的oom机制,当系统发生OOM时,oom_adj值越小,越不容易被系统kill掉    kubelet一般设置自身oom值为 -900  取值范围是[-1000,+1000]
        主要是通过设置/proc/ /oom_score_adj来控制
        设置为-1000,该进程就被排除在OOM Killer强制终止的对象外
    */
    if err := oomAdjuster.ApplyOomScoreAdj(0, s.OOMScoreAdj); err != nil {
        glog.Warning(err)
    }
    /*
         ---代码中RunKubelet是下一个入口******
        RunKubelet中会新起一个goroutine来异步运行kubelet;
        接下来的代码还会执行:if s.HealthzPort > 0提供健康检查的http服务
    */
    if err := RunKubelet(kcfg, nil); err != nil {**[2]**
        return err
    }

    if s.HealthzPort > 0 {
        /*
            这段code会启动一个http server来提供health check功能,其实目前就提供了一个/healthz/ping的endpoint,返回的http status code为200就正常。

            Kubernetes默认提供的http端口如下(在pkg/master/ports/ports.go中定义):
            KubeletStatusPort = 10248 // kubelet的health port,就是上面的HealthzPort。
            ProxyStatusPort = 10249 // kube proxy的health port。
            KubeletPort = 10250 // kubelet server的port。
            SchedulerPort = 10251 // kube scheduler的port。
            ControllerManagerPort = 10252 // kube controller manager 的port。
            KubeletReadOnlyPort = 10255 // 用于heapster监控和收集kubelet信息使用。
        */

        healthz.DefaultHealthz()
        go util.Until(func() {
            err := http.ListenAndServe(net.JoinHostPort(s.HealthzBindAddress.String(), strconv.Itoa(s.HealthzPort)), nil)
            if err != nil {
                glog.Errorf("Starting health server failed: %v", err)
            }
        }, 5*time.Second, util.NeverStop)
    }

    if s.RunOnce {
        return nil
    }

    **// run forever**
    select {}
}   

可以看到s.Run(nil)函数中有cfg, err := s.KubeletConfig()[1]
if err := RunKubelet(kcfg, nil); err != nil {[2]
三个地方需要进一步了解

4、cfg, err := s.KubeletConfig()解析

官方解析KubeletConfig returns a KubeletConfig suitable for being run, or an error if the server setup is not valid. It will not start any background processes.

func (s *KubeletServer) KubeletConfig() (*KubeletConfig, error) {
    /*
        HostNetworkSources=api,http,file   3种类型 定义取得pod的途径
        default=\"*\"   all的意思
    */
    //GetValidatedSources()--->Gets all validated sources from the specified sources. 定义在pkg/kubelet/type.go中
    hostNetworkSources, err := kubelet.GetValidatedSources(strings.Split(s.HostNetworkSources, ","))
    if err != nil {
        return nil, err
    }
    //设置HostPIDSources,HostIPCSources,也就是设置kubelet的PID和IPC的namespace
    hostPIDSources, err := kubelet.GetValidatedSources(strings.Split(s.HostPIDSources, ","))
    if err != nil {
        return nil, err
    }
    hostIPCSources, err := kubelet.GetValidatedSources(strings.Split(s.HostIPCSources, ","))
    if err != nil {
        return nil, err
    }

    //mount.New(),创建一个mounter对象,后面会用该mounter来装载pod里的volumns
    mounter := mount.New()
    var writer io.Writer = &io.StdWriter{}
    /*
        决定kubelet是否要跑在container内部
        Containerized的默认值是false,即不在container中运行kubelet
    */
    if s.Containerized {
        glog.V(2).Info("Running kubelet in containerized mode (experimental)")
        // 获得mount,findmnt,umount在机器上的实际路径
        mounter = mount.NewNsenterMounter()
        //新建一个文件writer,如果是要写入到container内部的话,必须先使用[nsenter](https://github.com/jpetazzo/nsenter)进入到对应的container的namespace里面
        writer = &io.NsenterWriter{}
    }
    //TLS 数字证书 相关配置
    tlsOptions, err := s.InitializeTLS()
    if err != nil {
        return nil, err
    }
    /*
        设置在docker container里面执行命令的方式, 默认是native(也就是docker exec),还支持nsenter
        创建dockerExecHandler,这个对象用于在docker container里执行命令

        NativeExecHandler使用github.com/fsouza/go-dockerclient的docker client来执行命令,
        NsenterExecHandler,顾名思义,就是使用nsenter命令在container的namespace里执行命令。代码在:pkg/kubelet/dockertools/exec.go。
    */
    var dockerExecHandler dockertools.ExecHandler
    switch s.DockerExecHandlerName {
    case "native":
        dockerExecHandler = &dockertools.NativeExecHandler{}
    case "nsenter":
        dockerExecHandler = &dockertools.NsenterExecHandler{}
    default:
        glog.Warningf("Unknown Docker exec handler %q; defaulting to native", s.DockerExecHandlerName)
        dockerExecHandler = &dockertools.NativeExecHandler{}
    }
    /*
        设置image回收策略,HighThresholdPercent表示磁盘使用率最高超过多大(默认90%)的时候,不停地清理image
        在后面的资源回收的manager中使用
        imageGCPolicy针对images,highThresholdPecent默认为90%,lowThresholdPercent默认为80%。
        当images所占存储低于lowThresholdPercent时不会GC images,如果大于等于highThresholdPecent就会一直做GC
    */
    imageGCPolicy := kubelet.ImageGCPolicy{
        HighThresholdPercent: s.ImageGCHighThresholdPercent,
        LowThresholdPercent:  s.ImageGCLowThresholdPercent,
    }
    // 设置磁盘绝对保留大小,用户决定是否可以调度pod进来
    //当磁盘空间低于LowDiskSpaceThresholdMB(默认为256M)就不会再接受创建新的pod
    diskSpacePolicy := kubelet.DiskSpacePolicy{
        DockerFreeDiskMB: s.LowDiskSpaceThresholdMB,
        RootFreeDiskMB:   s.LowDiskSpaceThresholdMB,
    }
    //ManifestURL:获取pod定义的url地址
    manifestURLHeader := make(http.Header)
    if s.ManifestURLHeader != "" {
        pieces := strings.Split(s.ManifestURLHeader, ":")
        if len(pieces) != 2 {
            return nil, fmt.Errorf("manifest-url-header must have a single ':' key-value separator, got %q", s.ManifestURLHeader)
        }
        manifestURLHeader.Set(pieces[0], pieces[1])
    }
    //最后返回KubeletConfig对象
    return &KubeletConfig{
        .......
    }, nil
}

5、if err := RunKubelet(kcfg, nil); err != nil ,这是个重点函数

func RunKubelet(kcfg *KubeletConfig, builder KubeletBuilder) error {
    //把前面构建好的KubeletConfig作为参数传递进来

    /*
        一开始先设置node name,如果没有在config中设置就会找host的hostname ,如果有cloudprovider,就去获取cloud instance的node name
        如果不指定的话,就是用当前机器名字(uname -n获得)
        kcfg.HostnameOverride 的值是kubelet --hostname-override xxxx中的xxxx
    */
    kcfg.Hostname = nodeutil.GetHostname(kcfg.HostnameOverride)

    if len(kcfg.NodeName) == 0 {
        // Query the cloud provider for our node name, default to Hostname
        nodeName := kcfg.Hostname
        if kcfg.Cloud != nil {
            var err error
            instances, ok := kcfg.Cloud.Instances()
            if !ok {
                return fmt.Errorf("failed to get instances from cloud provider")
            }

            nodeName, err = instances.CurrentNodeName(kcfg.Hostname)
            if err != nil {
                return fmt.Errorf("error fetching current instance name from cloud provider: %v", err)
            }

            glog.V(2).Infof("cloud provider determined current node name to be %s", nodeName)
        }

        kcfg.NodeName = nodeName
    }
    /*
        新建一个广播事件通道
        创建一个eventBroadcaster(在pkg/client/record/event.go),该对象用于向api server发送kubelet管理pods时的各种事件
    */
    eventBroadcaster := record.NewBroadcaster()
    //建eventRecord并且赋值给kubelet cfg,后面会用到,eventRecord会把event发送到eventBroadcaster中的watcher
    kcfg.Recorder = eventBroadcaster.NewRecorder(api.EventSource{Component: "kubelet", Host: kcfg.NodeName})
    eventBroadcaster.StartLogging(glog.V(3).Infof)
    if kcfg.KubeClient != nil {
        //这地方表明kubelet会把自己的事情通知 api server
        glog.V(4).Infof("Sending events to api server.")
        if kcfg.EventRecordQPS == 0.0 {
            //eventBroadcaster开始从watcher的result channel中获取event,发送给api server
            eventBroadcaster.StartRecordingToSink(kcfg.KubeClient.Events(""))
        } else {
            eventClient := *kcfg.KubeClient
            eventClient.Throttle = util.NewTokenBucketRateLimiter(kcfg.EventRecordQPS, kcfg.EventBurst)
            eventBroadcaster.StartRecordingToSink(eventClient.Events(""))
        }
    } else {
        glog.Warning("No api server defined - no events will be sent to API server.")
    }

    privilegedSources := capabilities.PrivilegedSources{
        HostNetworkSources: kcfg.HostNetworkSources,
        HostPIDSources:     kcfg.HostPIDSources,
        HostIPCSources:     kcfg.HostIPCSources,
    }
    //先设置所有containers都要遵从的capabilities,后面在run pod的时候会用到capabilities里的这些constraints
    capabilities.Setup(kcfg.AllowPrivileged, privilegedSources, 0)
    //设置PreferredDockercfgPath
    credentialprovider.SetPreferredDockercfgPath(kcfg.RootDirectory)

    if builder == nil {
        /*
            默认情况下,会创建一个CreateAndInitKubelet的builder,
            然后执行CreateAndInitKubelet,设置当前kuberlet管理的node上container的停掉之后回收时间间隔,保留个数,最大container个数。
            同时获得一个podCfg对象(pc = makePodSourceConfig(kc)),
            podCfg按照如下方式进行初始化:
                config.NewPodConfig(config.PodConfigNotificationIncremental, kc.Recorder)
        */
        builder = createAndInitKubelet**[3]**
    }
    if kcfg.OSInterface == nil {
        kcfg.OSInterface = kubecontainer.RealOS{}
    }
    /*
        *********k, podCfg, err := builder(kcfg)********
        由于run kubelet时build为nil,所以builder函数就是createAndInitKubelet。
        podCfg存放取回来的pod资源信息
    */
    k, podCfg, err := builder(kcfg)**[3]**
    if err != nil {
        return fmt.Errorf("failed to create kubelet: %v", err)
    }

    util.ApplyRLimitForSelf(kcfg.MaxOpenFiles)

    /*
        上面的createAndInitKubelet中 k.BirthCry() 向Api server发送一个"Starting kubelet"的消息
        启动起来,process pods and exit.
        最后根据是否执行一次,执行k.RunOnce(podCfg.Updates())或者startKubelet(k, podCfg, kcfg),
        startKubelet(k, podCfg, kcfg)中是执行k.Run(podCfg.Updates())

        这个Run()方法在pkg/kubelet/kubelet.go 中定义:func (kl *Kubelet) Run(updates <-chan PodUpdate)
    */
    // process pods and exit.

    if kcfg.Runonce {
        /*
            把podCfg.Updates()传递进去
            RunOnce函数从一个配置中 轮询更新,并运行关联的pod。
            定义在pkg/kubeletrunonce.go中 func (kl *Kubelet) RunOnce(updates <-chan PodUpdate) ([]RunPodResult, error)
        */
        if _, err := k.RunOnce(podCfg.Updates()); err != nil {**[4]**
            return fmt.Errorf("runonce failed: %v", err)
        }
        glog.Infof("Started kubelet as runonce")
    } else {
        //---下面进入startKubelet
        startKubelet(k, podCfg, kcfg)**[5]**
        glog.Infof("Started kubelet")
    }
    return nil
}

RunKubelet(kcfg, nil)函数中重要的点在于[3][4][5],其中
[3]builder = createAndInitKubelet
k, podCfg, err := builder(cfg)
[3]默认情况下,会创建一个CreateAndInitKubelet的builder,
由于run kubelet时build为nil,所以builder函数就是createAndInitKubelet。
即执行builder(cfg),也就是CreateAndInitKubelet(cfg)
[4]k.RunOnce(podCfg.Updates())
[5]startKubelet(k, podCfg, cfg) 利用podCfg的信息运行相关的pod
下面对这三个点进行详细解析。

6、createAndInitKubelet(kc *KubeletConfig) 解析

重点函数
func createAndInitKubelet(kc *KubeletConfig) (k KubeletBootstrap, pc *config.PodConfig, err error) {
    var kubeClient client.Interface
    if kc.KubeClient != nil {
        kubeClient = kc.KubeClient
    }
    gcPolicy := kubelet.ContainerGCPolicy{
        //k8s已经做了images所占空间和containers数量的限制,可以很大程度上降低disk被撑爆的可能性
        MinAge:             kc.MinimumGCAge,            //默认10s
        MaxPerPodContainer: kc.MaxPerPodContainerCount, //默认2,每个容器最多保留2个死亡实例
        MaxContainers:      kc.MaxContainerCount,       //默认100,每个节点最多保留100个死亡实例
    }
    daemonEndpoints := &api.NodeDaemonEndpoints{
        KubeletEndpoint: api.DaemonEndpoint{Port: int(kc.Port)},
    }
    /*
        pc = makePodSourceConfig(kc),kubelet从三个渠道来获取pods的信息。
        pod资源的生产者,通过chan来交付给消费者kubelet
        获得一个podCfg对象(pc = makePodSourceConfig(kc))
        pc存放着刚获取到的pod资源

    ************************************
    ************************************
    ************pod资源的生产者****************
    ************************************
    ************************************
    */

    pc = makePodSourceConfig(kc)**[6]**
    /*
        k, err = kubelet.NewMainKubelet(...)新建一个Kubelet实例,在NewMainKubelet函数里面进行容器管理和节点相关的初始化
        kubelet.NewMainKubelet在pkg/kubelet/kubelet.go中定义了    函数NewMainKubelet完成了真正的初始化工作
        开始真正创建kubelet对象!!
    */
    k, err = kubelet.NewMainKubelet(
        .......
    )
    if err != nil {
        return nil, nil, err
    }
    //得到一个创建好的kubelet对象
    //k.BirthCry(),向api server发送一个"Starting kubelet"的消息
    k.BirthCry()
    //触发kubelet开启垃圾回收协程以清理无用的容器和镜像,释放磁盘空间
    k.StartGarbageCollection()

    return k, pc, nil
    //回到RunKubelet,执行一条startKubelet(k KubeletBootstrap, podCfg *config.PodConfig, kc *KubeletConfig),开始启动刚才创建好的kubelet。
}

createAndInitKubelet里面重要的是pc = makePodSourceConfig(kc)[6],主要负责取得三个途径定义的pod资源,从而通过chan交付给kubelet组件

&&&&&&&&&&&&&&&&&&&&&&&&
6.1 pc = makePodSourceConfig(kc) 解析
见后面第8点解析,这是pod资源的生产者,通过chan来交付给消费者kubelet

7、下面开始解析 标注[4]和[5]

[4]k.RunOnce(podCfg.Updates())
//从本地 manifest 或者 远程URL 读取并运行pod,结束就退出。和 –api-servers 、–enable-server 参数不兼容

[5]startKubelet(k, podCfg, kcfg)

//---看到Kubelet.Run,这个才是kubelet的真正的入口,进入pkg的真正业务源码部分  参数k就是kubelet*******
func startKubelet(k KubeletBootstrap, podCfg *config.PodConfig, kc *KubeletConfig) {
    //startKubelet方法首先启动一个协程,让kubelet处理来自于Pod Source的Pod Update消息,然后启动kubelet server
    // start the kubelet

    /*
        **********重点了解************
        podCfg就是 通过上面RunKubelet()函数的createAndInitKubelet的makePodSourceConfig取得的filesource,urlsource和api server source的pod信息的源头
        Updates()方法会返回一个channel,该channel会不断pop up出从三个sources接收到的pod的change state
        Run函数定义在pkg\kubelet\kubelet.go   func (kl *Kubelet) Run(updates <-chan PodUpdate)
    */
    go util.Until(func() { k.Run(podCfg.Updates()) }, 0, util.NeverStop)**[7]**

    // start the kubelet server
    if kc.EnableServer {
        go util.Until(func() {
            //这里创建了一个http server,默认监听在0.0.0.0:10250上,可以通过http的get request获取不少信息
            k.ListenAndServe(kc.Address, kc.Port, kc.TLSOptions, kc.EnableDebuggingHandlers)
        }, 0, util.NeverStop)
    }
    if kc.ReadOnlyPort > 0 {
        go util.Until(func() {
            //这里也创建了一个http server,该server提供给heapster来收集当前kubelet的metris信息
            k.ListenAndServeReadOnly(kc.Address, kc.ReadOnlyPort)
        }, 0, util.NeverStop)
    }
}
startKubelet(k, podCfg, kcfg)函数重要的点在于go util.Until(func() { k.Run(podCfg.Updates()) }, 0, util.NeverStop)**[7]**,启动一个协程,让kubelet处理来自于Pod Source的Pod Update消息

8、pc = makePodSourceConfig(kc) 解析

pc存放着刚获取到的pod资源;

重点---pod 的管理过程:(生产过程)
apiServer负责接收server发过来的Pod管理信息,通过channel推送到PodConfig。
PodConfig的mux使用Storage的Merge方法,Merge方法又会通过Updates 这个channel将Pod数据推给Kubelet,真正的Pod处理在Kubelet包中。
Kubelet通过syncLoop监听channel,收到数据后就执行关于Pod和容器的处理,真正操作容器的方法是调用dockertools包中的方法。

生产方式有3种,定义在pkg/kubelet/config/apiserver.go    file.go  http.go 三个文件中。
生产者:通过三种方式进行pod信息的获取,也就是生产者,通过chan的方式法送给消费者。
func makePodSourceConfig(kc *KubeletConfig) *config.PodConfig {
    //这里构建了一个chan,并作为返回值返回给了上一级调用者。

    /*
        创建一个PodConfig对象,并根据启动参数中的Pod Source是否提供,来创建对应类型的Pod Source对象
        Pod Source在各种协程中运行,拉去pod信息并汇总输出到同一个Pod Channel中等待kubelet处理。

        makePodSourceConfig(kc *KubeletConfig) 负责创建PodConfig对象
    */

    /*
        cfg := config.NewPodConfig(config.PodConfigNotificationIncremental, kc.Recorder)
        定义在pkg/kubelet/config/config.go中
        cfg是一个传送管道!!!!生产者->消费者kubelet
        此函数创建PodConfig对象。它建立起了apiServer到后端kubelet处理消息之间的联系。
        创建一个pods source的storage然后封装在config里!
        完成podcfg的初始化工作
        PodConfigNotificationIncremental是传递增加,更新和删除的消息的标识,这样创建完成一个PodConfig实例。
    */
    // source of all configuration

    cfg := config.NewPodConfig(config.PodConfigNotificationIncremental, kc.Recorder)

    //下面分别定义了3中模式,HostNetworkSources=api,http,file
    // define file config source
    if kc.ConfigFile != "" {
        glog.Infof("Adding manifest file: %v", kc.ConfigFile)
        /*
            加入一个来自本地file/directory的pods源头,这个configFile默认是没有的,
            如果需要在kubelet初始化的时候创建一些static pods,就可以使用这种方式。
            这种pods可以被更新,使用FileCheckFrequency和HTTPCheckFrequency来控制更新后被重新创建的频率

             cfg.Channel(kubelet.FileSource) 把资源放进cfg中
        */
        config.NewSourceFile(kc.ConfigFile, kc.NodeName, kc.FileCheckFrequency, cfg.Channel(kubelet.FileSource))
    }
    //ManifestURL:获取pod定义的url地址
    // define url config source
    if kc.ManifestURL != "" {
        glog.Infof("Adding manifest url %q with HTTP header %v", kc.ManifestURL, kc.ManifestURLHeader)
        /*
            从一个url来获取pod,但只能有一个pod
            这种pods可以被更新,使用FileCheckFrequency和HTTPCheckFrequency来控制更新后被重新创建的频率
        */
        config.NewSourceURL(kc.ManifestURL, kc.ManifestURLHeader, kc.NodeName, kc.HTTPCheckFrequency, cfg.Channel(kubelet.HTTPSource))
    }
    if kc.KubeClient != nil {
        glog.Infof("Watching apiserver")
        /*
            !!!重头戏,所有来自API Server创建的pods都会通过这条路径被kubelet监测到并做相关操作。
        */
        config.NewSourceApiserver(kc.KubeClient, kc.NodeName, cfg.Channel(kubelet.ApiserverSource))
    }
    return cfg
}

三种获取pod方式的具体实现在后面再进行解析,定义在pkg/kubelet/config/apiserver.go file.go http.go 三个文件中。

9、解析上面[7]k.Run(podCfg.Updates())

Run函数定义在pkg\kubelet\kubelet.go func (kl *Kubelet) Run(updates <-chan PodUpdate)

/*
    重点---pod 的管理过程(消费):
    Kubelet的Run方法循环监听updates channel上的消息。当收到消息时就进行处理。
    以增加一个pod为例,HandlePodAddition的dispatchWork最终会调用到dockertools包中的方法,进行Pod内容器的操作。
    此处的调用关系比较深
*/
// Run starts the kubelet reacting to config updates
func (kl *Kubelet) Run(updates <-chan PodUpdate) {
    //真正的Run的执行体,这个是在之前的k8s.io\kubernetes\cmd\kubelet\app\中的startKubelet中执行的

    //启动一个http的log server,可以用于查看/var/log目录下的文件;
    if kl.logServer == nil {
        kl.logServer = http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/")))
    }
    if kl.kubeClient == nil {
        glog.Warning("No api server defined - no node status update will be sent.")
    }
    //把kubelet迁移到一个cgroups的容器里;
    // Move Kubelet to a container.
    if kl.resourceContainer != "" {
        // Fixme: I need to reside inside ContainerManager interface.
        err := util.RunInResourceContainer(kl.resourceContainer)
        if err != nil {
            glog.Warningf("Failed to move Kubelet to container %q: %v", kl.resourceContainer, err)
        }
        glog.Infof("Running in container %q", kl.resourceContainer)
    }
    /*
        start各种上面创建的manager
        分别启动如下组件(下面说明以docker container作为管理对象为主):
        imageManager, 同步本地缓存的image信息和内存中的缓存信息一致
        cadvisor,启动并且通过端口暴露api
        containerManager,确保container是否正常状态
        oomWatcher 通过cadvisor获得内存的使用信息,并且广播给api-server
    */
    if err := kl.imageManager.Start(); err != nil {
        kl.recorder.Eventf(kl.nodeRef, "KubeletSetupFailed", "Failed to start ImageManager %v", err)
        glog.Errorf("Failed to start ImageManager, images may not be garbage collected: %v", err)
    }

    if err := kl.cadvisor.Start(); err != nil {
        kl.recorder.Eventf(kl.nodeRef, "KubeletSetupFailed", "Failed to start CAdvisor %v", err)
        glog.Errorf("Failed to start CAdvisor, system may not be properly monitored: %v", err)
    }

    if err := kl.containerManager.Start(); err != nil {
        kl.recorder.Eventf(kl.nodeRef, "KubeletSetupFailed", "Failed to start ContainerManager %v", err)
        glog.Errorf("Failed to start ContainerManager, system may not be properly isolated: %v", err)
    }

    if err := kl.oomWatcher.Start(kl.nodeRef); err != nil {
        kl.recorder.Eventf(kl.nodeRef, "KubeletSetupFailed", "Failed to start OOM watcher %v", err)
        glog.Errorf("Failed to start OOM watching: %v", err)
    }
    /*
        The container runtime to use. Possible values: 'docker', 'rkt'
        启动对应的runtime
        开一个goroutine来更新docker daemon的启动状态:go util.Until(kl.updateRuntimeUp, 5*time.Second, util.NeverStop)
    */
    go util.Until(kl.updateRuntimeUp, 5*time.Second, util.NeverStop)

    // Start a goroutine responsible for killing pods (that are not properly
    // handled by pod workers).
    go util.Until(kl.podKiller, 1*time.Second, util.NeverStop)

    // Run the system oom watcher forever.
    /*
        kl.statusManager.Start()
        从apiserver 同步pod的信息 ;Starting to sync pod status with apiserver
        定义在pkg/kubelet/status/manager.go
    */
    kl.statusManager.Start()
    /*
        *******重点了解**********
        kl.syncLoop(updates, kl) 处理updates 也就是PodConfig产生的updates
        这个方法会使用一个forever loop  永不结束
    */
    kl.syncLoop(updates, kl)

}

Run(updates <-chan PodUpdate)函数中重要的是最后的kl.syncLoop(updates, kl)

10、kl.syncLoop(updates, kl)解析

// syncLoop is the main loop for processing changes. It watches for changes from
// three channels (file, apiserver, and http) and creates a union of them. For
// any new change seen, will run a sync against desired state and running state. If
// no changes are seen to the configuration, will synchronize the last known desired
// state every sync-frequency seconds. Never returns.

/*
    译:syncLoop是处理更改的主循环。 它监视来自三个通道(file,apiserver和http)的更改,并创建它们的并集(merge)。
       对于看到的任何新更改,将针对所需状态和运行状态运行同步。
       如果配置没有发生变化,将在每个同步频率秒同步最后一个已知的期望状态。 永远不会返回。
*/
func (kl *Kubelet) syncLoop(updates <-chan PodUpdate, handler SyncHandler) {
    //其中的update是被传递下来的pod信息,handler其实就是kubelet自身
    glog.Info("Starting kubelet main sync loop.")
    kl.resyncTicker = time.NewTicker(kl.resyncInterval)
    var housekeepingTimestamp time.Time
    for {
        //先判断docker deamon和network是否就绪,如果没有,就一直等待直到就绪
        if !kl.containerRuntimeUp() {
            time.Sleep(5 * time.Second)
            glog.Infof("Skipping pod synchronization, container runtime is not up.")
            continue
        }
        if !kl.doneNetworkConfigure() {
            time.Sleep(5 * time.Second)
            glog.Infof("Skipping pod synchronization, network is not configured")
            continue
        }

        // Make sure we sync first to receive the pods from the sources before
        // performing housekeeping.

        // before performing housekeeping,确保一开始先从三个源头同步获取到pod的数据

        //重要的函数,真正处理chan的地方
        if !kl.syncLoopIteration(updates, handler) {**[8]**
            break
        }
        // We don't want to perform housekeeping too often, so we set a minimum
        // period for it. Housekeeping would be performed at least once every
        // kl.resyncInterval, and *no* more than once every
        // housekeepingMinimumPeriod.
        // TODO (#13418): Investigate whether we can/should spawn a dedicated
        // goroutine for housekeeping
        if !kl.sourcesReady() {
            // If the sources aren't ready, skip housekeeping, as we may
            // accidentally delete pods from unready sources.
            glog.V(4).Infof("Skipping cleanup, sources aren't ready yet.")
        } else if housekeepingTimestamp.IsZero() {
            housekeepingTimestamp = time.Now()
        } else if time.Since(housekeepingTimestamp) > housekeepingMinimumPeriod {
            glog.V(4).Infof("SyncLoop (housekeeping)")
            if err := handler.HandlePodCleanups(); err != nil {
                glog.Errorf("Failed cleaning pods: %v", err)
            }
            housekeepingTimestamp = time.Now()
        }
    }
}

重要的地方在于[8]syncLoopIteration(updates, handler)

11、syncLoopIteration(updates, handler)解析

/*
    重点---pod 的管理过程:
    apiServer负责接收server发过来的Pod管理信息,通过channel推送到PodConfig。
    PodConfig的mux使用Storage的Merge方法,Merge方法又会通过Updates 这个channel将Pod数据推给Kubelet,真正的Pod处理在Kubelet包中。
    Kubelet通过syncLoop监听channel,收到数据后就执行关于Pod和容器的处理,真正操作容器的方法调用dockertools包中的方法
*/
func (kl *Kubelet) syncLoopIteration(updates <-chan PodUpdate, handler SyncHandler) bool {
    //负责消费PodUpdate,handler其实就是kubelet自身
    //从传送管道中,获取到pod信息,然后根据pod的类型,分别调用了不同的处理接口。
    kl.syncLoopMonitor.Store(time.Now())
    select {
    case u, open := <-updates:
        if !open {
            glog.Errorf("Update channel is closed. Exiting the sync loop.")
            return false
        }
        switch u.Op {
        case ADD:
            glog.V(2).Infof("SyncLoop (ADD): %q", kubeletUtil.FormatPodNames(u.Pods))
            handler.HandlePodAdditions(u.Pods)
        case UPDATE:
            glog.V(2).Infof("SyncLoop (UPDATE): %q", kubeletUtil.FormatPodNames(u.Pods))
            //定义在下面的func (kl *Kubelet) HandlePodUpdates(pods []*api.Pod)
            handler.HandlePodUpdates(u.Pods)
        case REMOVE:
            glog.V(2).Infof("SyncLoop (REMOVE): %q", kubeletUtil.FormatPodNames(u.Pods))
            handler.HandlePodDeletions(u.Pods)
        case SET:
            // TODO: Do we want to support this?
            glog.Errorf("Kubelet does not support snapshot update")
        }
    case <-kl.resyncTicker.C:
        // Periodically syncs all the pods and performs cleanup tasks.
        glog.V(4).Infof("SyncLoop (periodic sync)")
        handler.HandlePodSyncs(kl.podManager.GetPods())
    }
    kl.syncLoopMonitor.Store(time.Now())
    return true
}

这就是kubelet的主体流程部分。
后面将进一步解析pod获取的三种方式、syncLoopIteration中如何具体运行相关的pod
一个生产消费者模型(通过一个通道chan)

参考:

http://blog.csdn.net/screscent/article/details/51086684
http://www.tuicool.com/articles/J3QzeyA
http://blog.sina.com.cn/s/blog_14c9fead60102wdz0.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值