Pod 创建整体流程
通过Pod创建的时序图如下
接下来我们来具体分析每一个过程
api-server --- 用于接收客户端Pod创建请求
API 服务器是 Kubernetes 控制平面的组件, 该组件负责公开了 Kubernetes API,负责处理接受请求的工作。 API 服务器是 Kubernetes 控制平面的前端。
api-server
先初始化Config,通过Config初始化Server。在 CompletedConfig 的New方法中 为每个Server初始化REST存储,调用了NewRESTStorage。
e.g. 请求Pod资源的时候都是返回 Pod一系列Rest操作,起到一个聚合的作用
Storage的接口定义及具体实现,可以通过路经定位。e.g. /api/v1/watch/pods
scss
type RESTStorageProvider interface {
GroupName() string
NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error)
}
// 初始化具体实现
func (c *GenericConfig) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, error){}
安装 API 时首先为每个 API Resource 创建对应的后端存储 RESTStorage
go
// InstallAPIs 在给定的 Instance 的 GenericAPIServer 上安装 API 组和相应的 REST 存储处理程序。
func (m *Instance) InstallAPIs(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, restStorageProviders ...RESTStorageProvider) error {
// 根据服务器的版本初始化资源过期评估器
resourceExpirationEvaluator, err := genericapiserver.NewResourceExpirationEvaluator(*m.GenericAPIServer.Version)
if err != nil {
return err
}
// 遍历提供的 REST 存储提供程序
for _, restStorageBuilder := range restStorageProviders {
groupName := restStorageBuilder.GroupName()
// 创建 API 组信息和 REST 存储处理程序
apiGroupInfo, err := restStorageBuilder.NewRESTStorage(apiResourceConfigSource, restOptionsGetter)
if err != nil {
return fmt.Errorf("初始化 API 组 %q 时出现问题:%v", groupName, err)
}
// core API 注册到路由中,是 apiserver 初始化流程中最核心的方法之一
if len(groupName) == 0 {
if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil {
return fmt.Errorf("注册遗留 API 时发生错误:%w", err)
}
} else {
// 否则,将其添加到非遗留 API 组的切片中,将其安装到 /apis 路径下
nonLegacy = append(nonLegacy, &apiGroupInfo)
}
}
if err := m.GenericAPIServer.InstallAPIGroups(nonLegacy...); err != nil {}
return nil
}
在为资源创建完 RESTStorage
后,调用 m.GenericAPIServer.InstallLegacyAPIGroup
为 APIGroup 注册路由信息.
InstallLegacyAPIGroup
方法的调用链非常深,主要调用链如下:
go
InstallLegacyAPIGroup--> installAPIResources --> InstallREST --> Install --> registerResourceHandlers
// 真正执行安装的方法
func (a *APIInstaller) Install() ([]metav1.APIResource, []*storageversion.ResourceInfo, *restful.WebService, []error)
for path := range a.group.Storage {
paths[i] = path
i++
}
sort.Strings(paths)
for _, path := range paths {
apiResource, resourceInfo, err := a.registerResourceHandlers(path, a.group.Storage[path], ws)
if err != nil {
errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
}
if apiResource != nil {
apiResources = append(apiResources, *apiResource)
}
if resourceInfo != nil {
resourceInfos = append(resourceInfos, resourceInfo)
}
}
}
最终核心的路由构造在registerResourceHandlers
方法内,通过上一步骤构造的 REST Storage
判断该资源可以执行哪些操作(如 create、update等),将其对应的操作存入到 action 中,每一个 action 对应一个标准的 REST 操作,如 create 对应的 action 操作为 POST、update 对应的 action 操作为PUT。
最终根据 actions 数组依次遍历,对每一个操作添加一个 handler 方法,注册到 route 中去,再将 route 注册到 webservice 中去,webservice 最终会注册到 container 中,遵循 go-restful 的设计模式。
kube-scheduler 调度到合适的 Node 上进行 Pod的创建
go
func (sched *Scheduler) schedulePod(ctx context.Context, fwk framework.Framework, state *framework.CycleState, pod *v1.Pod) (result ScheduleResult, err error) {
feasibleNodes, diagnosis, err := sched.findNodesThatFitPod(ctx, fwk, state, pod)
priorityList, err := sched.prioritizeNodes(ctx, fwk, state, pod, feasibleNodes)
host, _, err := selectHost(priorityList, numberOfHighestScoredNodesToReport)
return ScheduleResult{
SuggestedHost: host,
EvaluatedNodes: len(feasibleNodes) + len(diagnosis.NodeToStatusMap),
FeasibleNodes: len(feasibleNodes),
}, err
}
- 先调用
findNodesThatFitPod
过滤出符合要求的预选节点 - 然后调用
prioritizeNodes
为预选的节点进行打分 - 最后调用
selectHost
选择最合适的节点返回
kubelet 初始化
kubelet 接收一组通过各类机制提供给它的 PodSpec,确保这些 PodSpec 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器
kubelet
启动时会通过 watch
的restful请求来获取 Pod
变更的信息:
css
$ curl -i http://{kube-api-server-ip}:{kube-api-server-port}/api/v1/watch/pods
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 02 Jan 2020 20:22:59 GMT
Transfer-Encoding: chunked
{"type":"ADDED", "object":{"kind":"Pod","apiVersion":"v1",...}}
{"type":"ADDED", "object":{"kind":"Pod","apiVersion":"v1",...}}
{"type":"MODIFIED", "object":{"kind":"Pod","apiVersion":"v1",...}}
kubelet
通过 list 和 watch 监听API server信息来源,只获取需要自己对应节点创建的Pod,将信息写入到 updates
中等待处理
go
// 构造 kubelet
func NewMainKubelet(
// ...
){
if kubeDeps.PodConfig == nil {
kubeDeps.PodConfig, err = makePodSourceConfig(kubeCfg, kubeDeps, nodeName, nodeHasSynced)
}
}
// 初始化Pod的配置
func makePodSourceConfig(kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *Dependencies, nodeName types.NodeName, nodeHasSynced func() bool) (*config.PodConfig, error) {
if kubeDeps.KubeClient != nil {
config.NewSourceApiserver(kubeDeps.KubeClient, nodeName, nodeHasSynced, cfg.Channel(ctx, kubetypes.ApiserverSource))
}
return cfg, nil
}
func NewSourceApiserver(c clientset.Interface, nodeName types.NodeName, nodeHasSynced func() bool, updates chan<- interface{}) {
// 接口监听API server,只获取跟Node节点匹配的Pod
lw := cache.NewListWatchFromClient(c.CoreV1().RESTClient(), "pods", metav1.NamespaceAll, fields.OneTermEqualSelector("spec.nodeName", string(nodeName)))
go func()
newSourceApiserverFromLW(lw, updates)
}()
}
// 从APIserver获取Pod的变更发送给updates
func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- interface{}) {
send := func(objs []interface{}) {
var pods []*v1.Pod
for _, o := range objs {
pods = append(pods, o.(*v1.Pod))
}
updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.ApiserverSource}
}
r := cache.NewReflector(lw, &v1.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0)
go r.Run(wait.NeverStop)
}
这里转换成 Channel
后会对监听到的Pod事件进行合并
go
// 返回一个包装过的 channel 结构,interface 里面一次会包含多个Pod的信息
func (c *PodConfig) Channel(ctx context.Context, source string) chan<- interface{} {
return c.mux.ChannelWithContext(ctx, source)
}
func (m *mux) ChannelWithContext(ctx context.Context, source string) chan interface{} {
// 如果channel 已经创建过了,那么直接返回原来已经有的channel极客
m.sourceLock.Lock()
defer m.sourceLock.Unlock()
channel, exists := m.sources[source]
if exists {
return channel
}
// 新建channel 用于监听
newChannel := make(chan interface{})
m.sources[source] = newChannel
go wait.Until(func() { m.listen(source, newChannel) }, 0, ctx.Done())
return newChannel
}
// 合并监听到的信号量
func (m *mux) listen(source string, listenChannel <-chan interface{}) {
for update := range listenChannel {
// 这里 merger的实现是 podStorage
m.merger.Merge(source, update)
}
}
func (s *podStorage) Merge(source string, change interface{}) error {
seenBefore := s.sourcesSeen.Has(source)
adds, updates, deletes, removes, reconciles := s.merge(source, change)
firstSet := !seenBefore && s.sourcesSeen.Has(source)
// deliver update notifications
switch s.mode {
case PodConfigNotificationIncremental:
if len(removes.Pods) > 0 {
s.updates <- *removes
}
if len(adds.Pods) > 0 {
s.updates <- *adds
}
if len(updates.Pods) > 0 {
s.updates <- *updates
}
if len(deletes.Pods) > 0 {
s.updates <- *deletes
}
if firstSet && len(adds.Pods) == 0 && len(updates.Pods) == 0 && len(deletes.Pods) == 0 {
// Send an empty update when first seeing the source and there are
// no ADD or UPDATE or DELETE pods from the source. This signals kubelet that
// the source is ready.
s.updates <- *adds
}
// Only add reconcile support here, because kubelet doesn't support Snapshot update now.
if len(reconciles.Pods) > 0 {
s.updates <- *reconciles
}
case PodConfigNotificationSnapshotAndUpdates:
if len(removes.Pods) > 0 || len(adds.Pods) > 0 || firstSet {
s.updates <- kubetypes.PodUpdate{Pods: s.mergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
}
if len(updates.Pods) > 0 {
s.updates <- *updates
}
if len(deletes.Pods) > 0 {
s.updates <- *deletes
}
case PodConfigNotificationSnapshot:
if len(updates.Pods) > 0 || len(deletes.Pods) > 0 || len(adds.Pods) > 0 || len(removes.Pods) > 0 || firstSet {
s.updates <- kubetypes.PodUpdate{Pods: s.mergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
}
case PodConfigNotificationUnknown:
fallthrough
default:
panic(fmt.Sprintf("unsupported PodConfigNotificationMode: %#v", s.mode))
}
return nil
}
Kubelet 监听 updates
的信息,并进行Pod状态的同步
go
func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencies, runOnce bool) error {
podCfg := kubeDeps.PodConfig
// 开始监听处理Pod的信息
if runOnce {
if _, err := k.RunOnce(podCfg.Updates()); err != nil {}
} else {
startKubelet(k, podCfg, &kubeServer.KubeletConfiguration, kubeDeps, kubeServer.EnableServer)
}
}
func startKubelet(k kubelet.Bootstrap, podCfg *config.PodConfig, kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *kubelet.Dependencies, enableServer bool) {
// 开始监听Pod配置的变化
go k.Run(podCfg.Updates())
}
初始化总体流程如下图:
api-server
接收来自客户端的 restful 请求创建Pod资源,这个时候 api-server
会根据客户端请求初始化Pod的字段信息,将其存入到 ETCD
中。
Kubelet
运行所有维护Pod的组件
scss
func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {
// Start the cloud provider sync manager
if kl.cloudResourceSyncManager != nil {
go kl.cloudResourceSyncManager.Run(wait.NeverStop)
}
if err := kl.initializeModules(); err != nil {}
// Start volume manager
go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)
// 启动协程进行状态更新
if kl.kubeClient != nil {
go wait.JitterUntil(kl.syncNodeStatus, kl.nodeStatusUpdateFrequency, 0.04, true, wait.NeverStop)
go kl.fastStatusUpdateOnce()
// start syncing lease
go kl.nodeLeaseController.Run(context.Background())
}
go wait.Until(kl.updateRuntimeUp, 5*time.Second, wait.NeverStop)
// Set up iptables util rules
if kl.makeIPTablesUtilChains {
kl.initNetworkUtil()
}
// Start component sync loops.
kl.statusManager.Start()
// Start syncing RuntimeClasses if enabled.
if kl.runtimeClassManager != nil {
kl.runtimeClassManager.Start(wait.NeverStop)
}
// Start the pod lifecycle event generator.
kl.pleg.Start()
// Start eventedPLEG only if EventedPLEG feature gate is enabled.
if utilfeature.DefaultFeatureGate.Enabled(features.EventedPLEG) {
kl.eventedPleg.Start()
}
// 启动循环
kl.syncLoop(ctx, updates, kl)
}
func (kl *Kubelet) initializeModules() error {
// Prometheus metrics.
metrics.Register(
collectors.NewVolumeStatsCollector(kl),
collectors.NewLogMetricsCollector(kl.StatsProvider.ListPodStats),
)
metrics.SetNodeName(kl.nodeName)
servermetrics.Register()
// Setup filesystem directories.
if err := kl.setupDataDirs(); err != nil {}
// 创建日志文件夹
if _, err := os.Stat(ContainerLogsDir); err != nil {
if err := kl.os.MkdirAll(ContainerLogsDir, 0755); err != nil {
return fmt.Errorf("failed to create directory %q: %v", ContainerLogsDir, err)
}
}
// Start the image manager.
kl.imageManager.Start()
// Start the certificate manager if it was enabled.
if kl.serverCertificateManager != nil {
kl.serverCertificateManager.Start()
}
// Start out of memory watcher.
if kl.oomWatcher != nil {
if err := kl.oomWatcher.Start(kl.nodeRef); err != nil {
return fmt.Errorf("failed to start OOM watcher: %w", err)
}
}
// Start resource analyzer
kl.resourceAnalyzer.Start()
return nil
}
可以看到主要启动了以下组件
cloudResourceSyncManager
用于定时获取节点的地址信息并进行广播。
metrics
初始化了 Prometheus
的监控指标
imageManager
启动了镜像管理,用于对镜像进行GC
serverCertificateManager
对证书进行管理
VolumeManager
观察正在运行的 pod 集合,并且负责挂载和卸载这些Pod的卷
oomWatcher
Go判断物理资源,封装一个包用于控制整体速率。
www.kernel.org/doc/Documen... 内核文件信息详解
通过 /dev/kmsg
的内核信息,监听了oom的事件,然后将事件解析后传递给 kubelet
的 EventRecorder
进行相应处理
record.EventRecorder
事件接收
oomWatcher
将事件通过 Eventf
方法写入 boardcast
的 incoming *chan
中*
record.Boradcaster
广播处理事件
css
func makeEventRecorder(ctx context.Context, kubeDeps *kubelet.Dependencies, nodeName types.NodeName) {
if kubeDeps.Recorder != nil {
return
}
eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx))
kubeDeps.Recorder = eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: componentKubelet, Host: string(nodeName)})
eventBroadcaster.StartStructuredLogging(3)
if kubeDeps.EventClient != nil {
klog.V(4).InfoS("Sending events to api server")
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeDeps.EventClient.Events("")})
} else {
klog.InfoS("No api server defined - no events will be sent to API server")
}
}
StartStructuredLogging
和 StartRecordingToSink
内部通过 loop
循环将事件读出后通过传入的 eventHandler
进行处理。
resourceAnalyzer
提供节点资源消耗统计信息。
pleg.PodLifecycleEventGenerator
主要用于观察容器运行时状态并通知 kubelet
容器状态的变化,通知 podWorkers
协调 Pod
的状态。例如容器停止了需要清理并重新启动。
statusManager
管理 kubelet
所有Pod的状态,并且通过 client-go
将状态同步给 apiserver
kubelet 真正创建及维护 Pod 的资源
syncLoop Pod 变更的循环
kubelet
将所有组件都启动后就启动 syncLoop
循环进行事件监听,真正将监听到的事件进行消费
go
func (kl *Kubelet) syncLoop(ctx context.Context, updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
// 每一秒钟唤醒一次同步,因为同步周期默认是10s
syncTicker := time.NewTicker(time.Second)
defer syncTicker.Stop()
// 全局清理任务的时间
housekeepingTicker := time.NewTicker(housekeepingPeriod)
plegCh := kl.pleg.Watch()
for {
// 真正执行同步循环
if !kl.syncLoopIteration(ctx, updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {
break
}
}
}
syncLoopInteration 每一个Pod变更事件的迭代
go
func (kl *Kubelet) syncLoopIteration(ctx context.Context, configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
select {
case u, open := <-configCh:
// 根据时间类型调用 SyncHandler 处理Pod更新的各种任务
case e := <-plegCh:
// 监听Pod的生命周期,调用 SyncHandler定期清理Pod
case <-syncCh:
// 调用 SyncHandler 同步Pod
case update := <-kl.livenessManager.Updates():
case update := <-kl.readinessManager.Updates():
case update := <-kl.startupManager.Updates():
kl.statusManager.SetContainerStartup(update.PodUID, update.ContainerID, started)
case <-housekeepingCh:
if !kl.sourcesReady.AllReady() {
// 如果没有所有资源都 Ready则跳过清理任务,避免清理了还未准备好的资源
} else {
// 进行资源清理
}
}
return true
}
syncLoopInteration 会根据监听到的 chan 信号执行各种不同的逻辑,主要包含了
- Pod 资源的创建和更新:这里会通过调用 CRI 插件来实现
- Pod 状态的维护,通过
statusManager
进行管理 - 资源的清理
SyncHandler 真正执行同步逻辑的方法
Pod同步处理的的接口定义如下:
scss
type SyncHandler interface {
HandlePodAdditions(pods []*v1.Pod)
HandlePodUpdates(pods []*v1.Pod)
HandlePodRemoves(pods []*v1.Pod)
HandlePodReconcile(pods []*v1.Pod)
HandlePodSyncs(pods []*v1.Pod)
HandlePodCleanups(ctx context.Context) error
}
这里原先具体实现是 Kubelet
,传入参数后将具体类型转化成接口类型 SyncHandler
,好处是 Kubelet
本身过于复杂, syncLoopInteration
只需要关心维护Pod的相关方法,所以内部通过接口的定义只让它看到这几个 Pod 维护状态的方法,避免信息过多导致无法确定自己要如何调用。
Kubelet.HandlePodAdditions
- 将Pod存入到
podManager
内存中 - 判断 Pod 是否能在该节点上创建,如磁盘空间是否足够,如果不行则拒绝
- 给每个 Pod创建一个podWorkLoop的协程
- 成功创建Pod之后增加探针的监听,来维护Pod的状态
kubelet
通过 SyncPod
来进行Pod资源的维护。
先将Pod加入到探测列表中,并开启协程进行探测
scss
func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (isTerminal bool, err error){
kl.probeManager.AddPod(pod)
}
可以看到 Kubelet
通过 kubeGenericRuntimeManager.createSandbox
来创建 Pod
的沙箱。
其中内部的逻辑会通过 GRPC
调用 CRI
插件进行资源的创建,如命名空间的创建、镜像资源的拉取和容器编排等。
快速回顾
到这里我们就了解了一个 Pod
在 Kubernetes 中创建的整体流程:
apiserver
接收所有创建 Pod的请求kube-scheduler
将创建Pod
的请求分配给合适的节点,将信息 NodeName 写入到apiserver
kubelet
通过client-go
的list
和watch
来监听自己对应 NodeName 的Pod变化,然后写入到channel等待处理。这里NodeName的匹配是由kube-scheduler
来进行分配。- 启动 Pod 事件的监听循环,来处理
Pod
变更事件的写入,并将同步完的Pod
状态重新写回到apiserver
中。 - 等到进入到
SyncPod
后就可以将容器的创建交给CRI
来实现,kubelet
专注于维护Pod
的可用状态以及是否能够正常提供服务。