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