kubernetes源码分析 kubelet
简介
从官方的架构图中很容易就能找到 kubelet
执行 kubelet -h
看到 kubelet 的功能介绍:
-
kubelet 是每个 Node 节点上都运行的主要“节点代理”。使用如下的一个向 apiserver 注册 Node 节点:主机的
hostname
;覆盖host
的参数;或者云提供商指定的逻辑。 -
kubelet 基于
PodSpec
工作。PodSpec
是用YAML
或者JSON
对象来描述 Pod。Kubelet 接受通过各种机制(主要是 apiserver)提供的一组PodSpec
,并确保里面描述的容器良好运行。
除了由 apiserver 提供 PodSpec
,还可以通过以下方式提供:
-
文件
-
HTTP 端点
-
HTTP 服务器
kubelet 功能归纳一下就是上报 Node 节点信息,和管理(创建、销毁)Pod。 功能看似简单,实际不然。每一个点拿出来都需要很大的篇幅来讲,比如 Node 节点的计算资源,除了传统的 CPU、内存、硬盘,还提供扩展来支持类似 GPU 等资源;Pod 不仅仅有容器,还有相关的网络、安全策略等。
架构
kubelet 的架构由 N 多的组件组成,下面简单介绍下比较重要的几个:
PLEG
即 Pod Lifecycle Event Generator,字面意思 Pod 生命周期事件(ContainerStarted
、ContainerDied
、ContainerRemoved
、ContainerChanged
)生成器。
其维护着 Pod 缓存;定期通过 ContainerRuntime
获取 Pod 的信息,与缓存中的信息比较,生成如上的事件;将事件写入其维护的通道(channel)中。
PodWorkers
处理事件中 Pod 的同步。核心方法 managePodLoop()
间接调用 kubelet.syncPod()
完成 Pod 的同步:
-
如果 Pod 正在被创建,记录其延迟
-
生成 Pod 的 API Status,即
v1.PodStatus
:从运行时的 status 转换成 api status -
记录 Pod 从
pending
到running
的耗时 -
在
StatusManager
中更新 pod 的状态 -
杀掉不应该运行的 Pod
-
如果网络插件未就绪,只启动使用了主机网络(host network)的 Pod
-
如果 static pod 不存在,为其创建镜像(Mirror)Pod
-
为 Pod 创建文件系统目录:Pod 目录、卷目录、插件目录
-
使用
VolumeManager
为 Pod 挂载卷 -
获取 image pull secrets
-
调用容器运行时(container runtime)的
#SyncPod()
方法
PodManager
存储 Pod 的期望状态,kubelet 服务的不同渠道的 Pod
StatusProvider
提供节点和容器的统计信息,有 cAdvisor
和 CRI
两种实现。
ContainerRuntime
顾名思义,容器运行时。与遵循 CRI 规范的高级容器运行时进行交互。
生命周期事件生成器PLEG
对于 Pod来说,Kubelet 会从多个数据来源(api、file以及http) watch Pod spec 中的变化。对于容器来说,Kubelet 会定期轮询容器运行时,以获取所有容器的最新状态。随着 Pod 和容器数量的增加,轮询会产生较大开销,带来的周期性大量并发请求会导致较高的 CPU 使用率峰值,降低节点性能,从而降低节点的可靠性。为了降低 Pod 的管理开销,提升 Kubelet 的性能和可扩展性,引入了 PLEG(Pod Lifecycle Event Generator)。改进了之前的工作方式:
-
减少空闲期间的不必要工作(例如 Pod 的定义和容器的状态没有发生更改)。
-
减少获取容器状态的并发请求数量。
为了进一步降低损耗,社区推出了基于event实现的PLEG,当然也需要CRI运行时支持。
它定期检查节点上 Pod 运行情况,如果发现感兴趣的变化,PLEG 就会把这种变化包 装成 Event 发送给 Kubelet 的主同步机制 syncLoop 去处理。
syncLoop
Kubelet启动后通过syncLoop进入到主循环处理Node上Pod Changes事件,监听来自file,apiserver,http三类的事件并汇聚到kubetypes.PodUpdate Channel(Config Channel)中,由syncLoopIteration不断从kubetypes.PodUpdate Channel中消费。
源码分析
源码版本
origin/release-1.30
启动流程
入口函数
入口位于 cmd/kubelet/app/server.go 。
其中 RunKubelet() 接口下的 startKubelet 即是启动 kubelet 的启动函数。
startKubelet
func startKubelet(k kubelet.Bootstrap, podCfg *config.PodConfig, kubeCfg *kubeletconfiginternal.KubeletConfiguration, kubeDeps *kubelet.Dependencies, enableServer bool) {// start the kubeletgo k.Run(podCfg.Updates())// start the kubelet serverif enableServer {go k.ListenAndServe(kubeCfg, kubeDeps.TLSOptions, kubeDeps.Auth, kubeDeps.TracerProvider)}if kubeCfg.ReadOnlyPort > 0 {go k.ListenAndServeReadOnly(netutils.ParseIPSloppy(kubeCfg.Address), uint(kubeCfg.ReadOnlyPort))}go k.ListenAndServePodResources()
}
跳转到 pkg/kubelet/kubelet.go ,这里就是 kubelet 的启动接口代码所在。
kubelet.Run
// Run starts the kubelet reacting to config updates
func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) {// ...// Start the cloud provider sync managerif kl.cloudResourceSyncManager != nil {go kl.cloudResourceSyncManager.Run(wait.NeverStop)}// 加载部分模块,如镜像管理, 观察oom, if err := kl.initializeModules(); err != nil {kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.KubeletSetupFailed, err.Error())klog.ErrorS(err, "Failed to initialize internal modules")os.Exit(1)}// Start volume manager// 开启volume 管理go kl.volumeManager.Run(kl.sourcesReady, wait.NeverStop)//...// 开启pleg, 即watch方式获取 runtime的event// 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.syncLoopIteration()
// 监听来自 file/apiserver/http 事件源发送过来的事件,并对事件做出对应的同步处理。kl.syncLoop(ctx, updates, kl)
}
事件watch
syncLoop
// 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.
func (kl *Kubelet) syncLoop(ctx context.Context, updates <-chan kubetypes.PodUpdate, handler SyncHandler) {//...for {if err := kl.runtimeState.runtimeErrors(); err != nil {klog.ErrorS(err, "Skipping pod synchronization")// exponential backofftime.Sleep(duration)duration = time.Duration(math.Min(float64(max), factor*float64(duration)))continue}// reset backoff if we have a successduration = basekl.syncLoopMonitor.Store(kl.clock.Now())// updatedates为配置聚合通道,handler 为kubelet对象的接口集合// plegch 是来自runtime变化事件的通道if !kl.syncLoopIteration(ctx, updates, handler, syncTicker.C, \housekeepingTicker.C, plegCh) {break}kl.syncLoopMonitor.Store(kl.clock.Now())}
}
syncLoop参数中的updates 是来自下面文件的配置聚合通道
// PodConfig is a configuration mux that merges many sources of pod configuration into a single
// consistent structure, and then delivers incremental change notifications to listeners
// in order.
type PodConfig struct {pods *podStoragemux *mux// the channel of denormalized changes passed to listeners// updates通道updates chan kubetypes.PodUpdate// contains the list of all configured sourcessourcesLock sync.Mutexsources sets.String
}
handler interface 传入的是实现了下面方法的kubelet对象
syncLoopIteration
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:// Update from a config source; dispatch it to the right handler// callback.if !open {klog.ErrorS(nil, "Update channel is closed, exiting the sync loop")return false}// 下面为对收到pod配置后各个操作的处理switch u.Op {// 关注ADD操作case kubetypes.ADD:klog.V(2).InfoS("SyncLoop ADD", "source", u.Source, "pods", klog.KObjSlice(u.Pods))// After restarting, kubelet will get all existing pods through// ADD as if they are new pods. These pods will then go through the// admission process and *may* be rejected. This can be resolved// once we have checkpointing.handler.HandlePodAdditions(u.Pods)case kubetypes.UPDATE:klog.V(2).InfoS("SyncLoop UPDATE", "source", u.Source, "pods", klog.KObjSlice(u.Pods))handler.HandlePodUpdates(u.Pods)case kubetypes.REMOVE:klog.V(2).InfoS("SyncLoop REMOVE", "source", u.Source, "pods", klog.KObjSlice(u.Pods))handler.HandlePodRemoves(u.Pods)case kubetypes.RECONCILE:klog.V(4).InfoS("SyncLoop RECONCILE", "source", u.Source, "pods", klog.KObjSlice(u.Pods))handler.HandlePodReconcile(u.Pods)case kubetypes.DELETE:klog.V(2).InfoS("SyncLoop DELETE", "source", u.Source, "pods", klog.KObjSlice(u.Pods))// DELETE is treated as a UPDATE because of graceful deletion.handler.HandlePodUpdates(u.Pods)case kubetypes.SET:// TODO: Do we want to support this?klog.ErrorS(nil, "Kubelet does not support snapshot update")default:klog.ErrorS(nil, "Invalid operation type received", "operation", u.Op)}kl.sourcesReady.AddSource(u.Source)// 获取来自pleg通道的事件case e := <-plegCh:if isSyncPodWorthy(e) {// PLEG event for a pod; sync it.if pod, ok := kl.podManager.GetPodByUID(e.ID); ok {klog.V(2).InfoS("SyncLoop (PLEG): event for pod", "pod", klog.KObj(pod), "event", e)// 处理 同步自容器运行时的容器最新信息handler.HandlePodSyncs([]*v1.Pod{pod})} else {// If the pod no longer exists, ignore the event.klog.V(4).InfoS("SyncLoop (PLEG): pod does not exist, ignore irrelevant event", "event", e)}}if e.Type == pleg.ContainerDied {if containerID, ok := e.Data.(string); ok {kl.cleanUpContainersInPod(e.ID, containerID)}}//...}return true
}
Pod的创建
容器的创建
HandlePodAdditions
重点关注ADD操作
kubetypes.ADD
kubetypes.ADD 既是 Pod 的创建,它调用了 handler.HandlePodAdditions(u.Pods) 接口来响应 Pod 的创建操作,我们先看看 Pod 具体是怎么样被创建的。
-
HandlePodAdditions() 接口接收的是 pods 切片,意味着可能会出现多个 pod 同时创建。
-
pods 按创建时间排序,以此对排好序的 pods 进行循环遍历。
-
获取 kubelet 所在节点上现有的所有 pods 。
-
kl.podManager.AddPod(pod) 把当前 pod 信息缓存进 podManager 的 podByUID 这个 map 里面。
-
检查 pod 是否 MirrorPod ,如果是 MirrorPod ,则调用 kl.handleMirrorPod(pod, start) 对 Pod 进行处理 。
-
无论是 MirrorPod 还是正常 Pod ,最终都是调用 kl.dispatchWork() 接口进行处理,他们的区别在于传进去的参数的不同。
-
MirrorPod 是 静态 Pod 在 apiserver 上的一个镜像,kubelet 并不需要在节点上给他创建真实的容器,它的 ADD/UPDATE/DELETE 操作类型都被当做 UPDATE 来处理,更新它在 apiserver 上的状态。
// HandlePodAdditions is the callback in SyncHandler for pods being added from
// a config source.
func (kl *Kubelet) HandlePodAdditions(pods []*v1.Pod) {start := kl.clock.Now()// 按照创建事件排序sort.Sort(sliceutils.PodsByCreationTime(pods))if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {kl.podResizeMutex.Lock()defer kl.podResizeMutex.Unlock()}// 收到的chungking事件通知是一个pods数组,pods可能有多个for _, pod := range pods {existingPods := kl.podManager.GetPods()// Always add the pod to the pod manager. Kubelet relies on the pod// manager as the source of truth for the desired state. If a pod does// not exist in the pod manager, it means that it has been deleted in// the apiserver and no action (other than cleanup) is required.//把当前 pod 信息缓存进 podManager 的 podByUID 这个 map 里面kl.podManager.AddPod(pod)pod, mirrorPod, wasMirror := kl.podManager.GetPodAndMirrorPod(pod)if wasMirror {if pod == nil {klog.V(2).InfoS("Unable to find pod for mirror pod, skipping", "mirrorPod", klog.KObj(mirrorPod), "mirrorPodUID", mirrorPod.UID)continue}kl.podWorkers.UpdatePod(UpdatePodOptions{Pod: pod,MirrorPod: mirrorPod,UpdateType: kubetypes.SyncPodUpdate,StartTime: start,})continue}//...// 转移到podworkers的Update继续处理pod的创建kl.podWorkers.UpdatePod(UpdatePodOptions{Pod: pod,MirrorPod: mirrorPod,UpdateType: kubetypes.SyncPodCreate,StartTime: start,})}
}
podWorkers.UpdatePod
// UpdatePod carries a configuration change or termination state to a pod. A pod is either runnable,
// terminating, or terminated, and will transition to terminating if: deleted on the apiserver,
// discovered to have a terminal phase (Succeeded or Failed), or evicted by the kubelet.
func (p *podWorkers) UpdatePod(options UpdatePodOptions) {//...// start the pod worker goroutine if it doesn't existpodUpdates, exists := p.podUpdates[uid]// 当没有该pod时将会执行创建流程, 是以goroutine 方式启动运行if !exists {// buffer the channel to avoid blocking this methodpodUpdates = make(chan struct{}, 1)p.podUpdates[uid] = podUpdates// ensure that static pods start in the order they are received by UpdatePodif kubetypes.IsStaticPod(pod) {p.waitingToStartStaticPodsByFullname[status.fullname] =append(p.waitingToStartStaticPodsByFullname[status.fullname], uid)}// allow testing of delays in the pod update channelvar outCh <-chan struct{}if p.workerChannelFn != nil {outCh = p.workerChannelFn(uid, podUpdates)} else {outCh = podUpdates}// 在协程中启动pod// spawn a pod workergo func() {// TODO: this should be a wait.Until with backoff to handle panics, and// accept a context for shutdowndefer runtime.HandleCrash()defer klog.V(3).InfoS("Pod worker has stopped", "podUID", uid)// 启动pod逻辑p.podWorkerLoop(uid, outCh)}()}//...
}
podWorkerLoop
func (p *podWorkers) podWorkerLoop(podUID types.UID, podUpdates <-chan struct{}) {var lastSyncTime time.Timefor range podUpdates {ctx, update, canStart, canEverStart, ok := p.startPodSync(podUID)//...// Take the appropriate action (illegal phases are prevented by UpdatePod)switch {case update.WorkType == TerminatedPod:err = p.podSyncer.SyncTerminatedPod(ctx, update.Options.Pod, status)case update.WorkType == TerminatingPod:var gracePeriod *int64if opt := update.Options.KillPodOptions; opt != nil {gracePeriod = opt.PodTerminationGracePeriodSecondsOverride}podStatusFn := p.acknowledgeTerminating(podUID)// if we only have a running pod, terminate it directlyif update.Options.RunningPod != nil {err = p.podSyncer.SyncTerminatingRuntimePod(ctx, update.Options.RunningPod)} else {err = p.podSyncer.SyncTerminatingPod(ctx, update.Options.Pod, status, gracePeriod, podStatusFn)}default:// 继续进入pod的创建isTerminal, err = p.podSyncer.SyncPod(ctx, update.Options.UpdateType, update.Options.Pod, update.Options.MirrorPod, status)}lastSyncTime = p.clock.Now()return err}()}
}
Kubelet.SyncPod
// This operation writes all events that are dispatched in order to provide
// the most accurate information possible about an error situation to aid debugging.
// Callers should not write an event if this operation returns an error.
func (kl *Kubelet) SyncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (isTerminal bool, err error) {// ...sctx := context.WithoutCancel(ctx)// 创建podresult := kl.containerRuntime.SyncPod(sctx, pod, podStatus, pullSecrets, kl.backOff)kl.reasonCache.Update(pod.UID, result)// ...return false, nil
}
kubeGenericRuntimeManager.SyncPod
这里就是调用容器运行时 SyncPod() 接口的源码所在,很长,我们先总结一下他的步骤。
-
计算 sandbox 容器和普通容器的状态有没有变更。
-
如果必要,kill 掉 sandbox 容器重建。
-
如果容器不该被运行,则 kill 掉 pod 下的所有容器。
-
如果需要,创建 sandbox 容器。
-
创建临时容器。
-
创建初始化容器 initContainers 。
-
创建普通容器。
我们接下来,在根据上面的每一个步骤,来分析这些步骤的具体内容。
注意
sanbox 容器到底是什么东西?kubelet 创建 pod 的时候,你通过 docker ps 去看,会发现它使用 pause 镜像也跟着创建一个同名容器。
57f7fc7cf97b 605c77e624dd "/docker-entrypoint.…" 14 hours ago Up 14 hours k8s_nginx_octopus-deployment-549545bf46-x9cqm_default_c799bb7f-d5d9-41bd-ba60-9a968f0fac54_0 4e0c7fb21e49 registry.aliyuncs.com/google_containers/pause:3.2 "/pause" 14 hours ago Up 14 hours k8s_POD_octopus-deployment-549545bf46-x9cqm_default_c799bb7f-d5d9-41bd-ba60-9a968f0fac54_0
它是用来为 pod 下的一组容器创建各种 namespace 环境和资源隔离用的, pod 下的各个容器就可以在这个隔离的
环境里面共享各种资源。使得容器无需像 kvm 一样,需要创建一个操作系统实例来对资源进行隔离,这样就可以很好地利用到宿主机的各种资源,这也就是 kubernetes 的精髓所在。
// SyncPod syncs the running pod into the desired pod by executing following steps:
//
// 1. Compute sandbox and container changes.
// 2. Kill pod sandbox if necessary.
// 3. Kill any containers that should not be running.
// 4. Create sandbox if necessary.
// 5. Create ephemeral containers.
// 6. Create init containers.
// 7. Resize running containers (if InPlacePodVerticalScaling==true)
// 8. Create normal containers.
func (m *kubeGenericRuntimeManager) SyncPod(ctx context.Context, pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {// Step 1: Compute sandbox and container changes.// Step 2: Kill the pod if the sandbox has changed.// Step 3: kill any running containers in this pod which are not to keep.// Step 4: Create a sandbox for the pod if necessary.podSandboxID := podContainerChanges.SandboxIDif podContainerChanges.CreateSandbox {// ...podSandboxID, msg, err = m.createPodSandbox(ctx, pod, podContainerChanges.Attempt)}//...// pod 中container 启动方法// Helper containing boilerplate common to starting all types of containers.// typeName is a description used to describe this type of container in log messages,// currently: "container", "init container" or "ephemeral container"// metricLabel is the label used to describe this type of container in monitoring metrics.// currently: "container", "init_container" or "ephemeral_container"start := func(ctx context.Context, typeName, metricLabel string, spec *startSpec) error {startContainerResult := kubecontainer.NewSyncResult(kubecontainer.StartContainer, spec.container.Name)result.AddSyncResult(startContainerResult)isInBackOff, msg, err := m.doBackOff(pod, spec.container, podStatus, backOff)if isInBackOff {startContainerResult.Fail(err, msg)klog.V(4).InfoS("Backing Off restarting container in pod", "containerType", typeName, "container", spec.container, "pod", klog.KObj(pod))return err}metrics.StartedContainersTotal.WithLabelValues(metricLabel).Inc()if sc.HasWindowsHostProcessRequest(pod, spec.container) {metrics.StartedHostProcessContainersTotal.WithLabelValues(metricLabel).Inc()}klog.V(4).InfoS("Creating container in pod", "containerType", typeName, "container", spec.container, "pod", klog.KObj(pod))// NOTE (aramase) podIPs are populated for single stack and dual stack clusters. Send only podIPs.if msg, err := m.startContainer(ctx, podSandboxID, podSandboxConfig, spec, pod, podStatus, pullSecrets, podIP, podIPs); err != nil {// startContainer() returns well-defined error codes that have reasonable cardinality for metrics and are// useful to cluster administrators to distinguish "server errors" from "user errors".metrics.StartedContainersErrorsTotal.WithLabelValues(metricLabel, err.Error()).Inc()if sc.HasWindowsHostProcessRequest(pod, spec.container) {metrics.StartedHostProcessContainersErrorsTotal.WithLabelValues(metricLabel, err.Error()).Inc()}startContainerResult.Fail(err, msg)// known errors that are logged in other places are logged at higher levels here to avoid// repetitive log spamswitch {case err == images.ErrImagePullBackOff:klog.V(3).InfoS("Container start failed in pod", "containerType", typeName, "container", spec.container, "pod", klog.KObj(pod), "containerMessage", msg, "err", err)default:utilruntime.HandleError(fmt.Errorf("%v %+v start failed in pod %v: %v: %s", typeName, spec.container, format.Pod(pod), err, msg))}return err}return nil}// Step 5: start ephemeral containers// These are started "prior" to init containers to allow running ephemeral containers even when there// are errors starting an init container. In practice init containers will start first since ephemeral// containers cannot be specified on pod creation.// 创建临时容器, 主要用于需要在运行的 Pod 内执行一些临时任务,// 例如调试、日志收集、文件系统浏览等。for _, idx := range podContainerChanges.EphemeralContainersToStart {start(ctx, "ephemeral container", metrics.EphemeralContainer, ephemeralContainerStartSpec(&pod.Spec.EphemeralContainers[idx]))}if !utilfeature.DefaultFeatureGate.Enabled(features.SidecarContainers) {// Step 6: start the init container.if container := podContainerChanges.NextInitContainerToStart; container != nil {// Start the next init container.if err := start(ctx, "init container", metrics.InitContainer, containerStartSpec(container)); err != nil {return}// Successfully started the container; clear the entry in the failureklog.V(4).InfoS("Completed init container for pod", "containerName", container.Name, "pod", klog.KObj(pod))}} else {// 启动init container // 可以帮助确保在主应用容器启动之前完成必要的预处理任务,以确保应用程序的正确运行和可靠性。// Step 6: start init containers.for _, idx := range podContainerChanges.InitContainersToStart {container := &pod.Spec.InitContainers[idx]// Start the next init container.if err := start(ctx, "init container", metrics.InitContainer, containerStartSpec(container)); err != nil {if types.IsRestartableInitContainer(container) {klog.V(4).InfoS("Failed to start the restartable init container for the pod, skipping", "initContainerName", container.Name, "pod", klog.KObj(pod))continue}klog.V(4).InfoS("Failed to initialize the pod, as the init container failed to start, aborting", "initContainerName", container.Name, "pod", klog.KObj(pod))return}// Successfully started the container; clear the entry in the failureklog.V(4).InfoS("Completed init container for pod", "containerName", container.Name, "pod", klog.KObj(pod))}}// Step 7: For containers in podContainerChanges.ContainersToUpdate[CPU,Memory] list, invoke UpdateContainerResourcesif isInPlacePodVerticalScalingAllowed(pod) {if len(podContainerChanges.ContainersToUpdate) > 0 || podContainerChanges.UpdatePodResources {m.doPodResizeAction(pod, podStatus, podContainerChanges, result)}}// 启动业务container// Step 8: start containers in podContainerChanges.ContainersToStart.for _, idx := range podContainerChanges.ContainersToStart {start(ctx, "container", metrics.Container, containerStartSpec(&pod.Spec.Containers[idx]))}return
}
Init containers(初始化容器)是 Kubernetes 中的一个概念,它是一种特殊类型的容器,用于在 Pod 中运行在主应用容器启动之前执行的初始化任务。
当创建一个 Pod 时,除了主应用容器之外,你可以定义一个或多个 init containers。这些 init containers 会按照定义的顺序依次执行,并且每个 init container 必须成功完成(即退出状态码为 0)才能继续下一个 init container 的执行,以及主应用容器的启动。
Init containers 可以用于在主应用容器启动之前执行各种初始化任务,例如:
数据库初始化:在主应用容器启动之前,可以使用 init container 创建数据库并执行必要的初始化脚本。
依赖检查:在主应用容器启动之前,可以使用 init container 检查依赖的服务或资源是否可用。
文件下载:在主应用容器启动之前,可以使用 init container 下载配置文件或其他必要的资源。
安全设置:在主应用容器启动之前,可以使用 init container 配置安全相关的设置。
每个 init container 都可以有自己的镜像和配置,它们共享 Pod 的网络命名空间和存储卷。一旦所有的 init containers 成功完成,主应用容器将启动并加入到 Pod 中。
使用 init containers 可以帮助确保在主应用容器启动之前完成必要的预处理任务,以确保应用程序的正确运行和可靠性。
请注意,init containers 是顺序执行的,因此如果某个 init container 失败,则整个 Pod 的启动过程将被阻塞,直到该 init container 成功或手动重启 Pod。
pause容器的注入
containerd.RunPodSandbox
使用的pasue 是在containerd创建sandbox 时。在配置中指定的
// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
// the sandbox is in ready state.
func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr error) {...// Ensure sandbox container image snapshot.// 使用的sandbox镜像,一般就是pauseimage, err := c.ensureImageExists(ctx, c.config.SandboxImage, config)if err != nil {return nil, fmt.Errorf("failed to get sandbox image %q: %w", c.config.SandboxImage, err)}containerdImage, err := c.toContainerdImage(ctx, *image)if err != nil {return nil, fmt.Errorf("failed to get image from containerd %q: %w", image.ID, err)}ociRuntime, err := c.getSandboxRuntime(config, r.GetRuntimeHandler())if err != nil {return nil, fmt.Errorf("failed to get sandbox runtime: %w", err)}log.G(ctx).WithField("podsandboxid", id).Debugf("use OCI runtime %+v", ociRuntime)runtimeStart := time.Now()// Create sandbox container.// NOTE: sandboxContainerSpec SHOULD NOT have side// effect, e.g. accessing/creating files, so that we can test// it safely.// NOTE: the network namespace path will be created later and update through updateNetNamespacePath functionspec, err := c.sandboxContainerSpec(id, config, &image.ImageSpec.Config, "", ociRuntime.PodAnnotations)if err != nil {return nil, fmt.Errorf("failed to generate sandbox container spec: %w", err)}log.G(ctx).WithField("podsandboxid", id).Debugf("sandbox container spec: %#+v", spew.NewFormatter(spec))sandbox.ProcessLabel = spec.Process.SelinuxLabeldefer func() {if retErr != nil {selinux.ReleaseLabel(sandbox.ProcessLabel)}}()// ...container, err := c.client.NewContainer(ctx, id, opts...)if err != nil {return nil, fmt.Errorf("failed to create containerd container: %w", err)}// Add container into sandbox store in INIT state.sandbox.Container = containerdefer func() {// Put the sandbox into sandbox store when the some resource fails to be cleaned.if retErr != nil && cleanupErr != nil {log.G(ctx).WithError(cleanupErr).Errorf("encountered an error cleaning up failed sandbox %q, marking sandbox state as SANDBOX_UNKNOWN", id)if err := c.sandboxStore.Add(sandbox); err != nil {log.G(ctx).WithError(err).Errorf("failed to add sandbox %+v into store", sandbox)}}}()...
Pause 容器
Pause容器的功能可参考 pause.c文件
pause功能只是截获 SIGINT, SIGTERM, SIGCHLD 信号,其中SIGCHLD只是收集退出的子进程,避免成为孤儿进程。
puase容器主要是为了:
-
保持有一个容器在sandbox中运行,维持sandbox环境。
-
其他容器停止时需要在同空间下有主进程进行回收,避免成为孤儿进程。
/*
Copyright 2016 The Kubernetes Authors.Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License athttp://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>#define STRINGIFY(x) #x
#define VERSION_STRING(x) STRINGIFY(x)#ifndef VERSION
#define VERSION HEAD
#endifstatic void sigdown(int signo) {psignal(signo, "Shutting down, got signal");exit(0);
}static void sigreap(int signo) {while (waitpid(-1, NULL, WNOHANG) > 0);
}int main(int argc, char **argv) {int i;for (i = 1; i < argc; ++i) {if (!strcasecmp(argv[i], "-v")) {printf("pause.c %s\n", VERSION_STRING(VERSION));return 0;}}if (getpid() != 1)/* Not an error because pause sees use outside of infra containers. */fprintf(stderr, "Warning: pause should be the first process\n");// 收集退出的子进程if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)return 1;if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)return 2;if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,.sa_flags = SA_NOCLDSTOP},NULL) < 0)return 3;for (;;)pause();fprintf(stderr, "Error: infinite loop terminated\n");return 42;
}
相关文章:
kubernetes源码分析 kubelet
简介 从官方的架构图中很容易就能找到 kubelet 执行 kubelet -h 看到 kubelet 的功能介绍: kubelet 是每个 Node 节点上都运行的主要“节点代理”。使用如下的一个向 apiserver 注册 Node 节点:主机的 hostname;覆盖 host 的参数࿱…...
PostgreSQL的学习心得和知识总结(一百六十八)|深入理解PostgreSQL数据库之PostgreSQL 规划器开发与调试(翻译)
目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库…...
React创建项目实用教程
✍请将整篇文章阅读完再开始使用create-react-app react-project创建项目 检查node版本 node -v // node版本:v22.10.0使用nvm降版本修改到了node V20.11.1之后再进行一系列操作的 react脚手架安装: npm install -g create-react-app// node版本&…...
STM32 外部中断和NVIC嵌套中断向量控制器
目录 背景 外部中断/事件控制器(EXTI) 主要特性 功能说明 外部中断线 嵌套向量中断控制器 特性 中断线(Interrupt Line) 中断线的定义和作用 STM32中断线的分类和数量 优先级分组 抢占优先级(Preemption Priority) …...
讯飞唤醒+VOSK语音识别+DEEPSEEK大模型+讯飞离线合成实现纯离线大模型智能语音问答。
在信息爆炸的时代,智能语音问答系统正以前所未有的速度融入我们的日常生活。然而,随着数据泄露事件的频发,用户对于隐私保护的需求日益增强。想象一下,一个无需联网、即可响应你所有问题的智能助手——这就是纯离线大模型智能语音…...
【MediaTek】 T750 openwrt-23.05编 cannot find dependency libexpat for libmesode
MediaTek T750 T750 采用先进的 7nm 制程,高度集成 5G 调制解调器和四核 Arm CPU,提供较强的功能和配置,设备制造商得以打造精巧的高性能 CPE 产品,如固定无线接入(FWA)路由器和移动热点。 MediaTek T750 平台是一款综合的芯片组,集成了 5G SoC MT6890、12nm 制程…...
如何通过 prometheus-operator 实现服务发现
在之前的章节中,我们讲解了如何编写一个自定义的 Exporter,以便将指标暴露给 Prometheus 进行抓取。现在,我们将进一步扩展这个内容,介绍如何使用 prometheus-operator 在 Kubernetes 集群中自动发现并监控这些暴露的指标。 部署应用 在 Kubernetes 集群中部署我们的自定…...
VBA学习:将文本中的\n替换为换行符
目录 一、问题描述 二、解决方法 三、代码 四、注意事项 五、总结 一、问题描述 一个字符串,包含多个\n,现在利用VBA写一段程序,把\n替换为换行符。 二、解决方法 1、Replace函数:直接替换换行符 在Word 中 使用vbCrLf或者…...
(8/100)每日小游戏平台系列
项目地址位于:小游戏导航 新增一个打地鼠游戏! 打地鼠(Whack-a-Mole)是一款经典的休闲游戏,玩家需要点击随机出现的地鼠,以获取分数。游戏时间有限,玩家需要在规定时间内尽可能多地击中地鼠&am…...
【设计模式】 建造者模式和原型模式
建造者模式(Builder Pattern) 概述 建造者模式是一种创建型设计模式,它允许逐步构建复杂对象。通过将构造过程与表示分离,使得同样的构建过程可以创建不同的表示。这种模式非常适合用于创建那些具有很多属性的对象,尤…...
【Python 学习 / 4】基本数据结构之 字符串 与 集合
文章目录 1. 字符串概念1.1 字符串的创建1.2 字符串的访问和操作1.2.1 下标访问1.2.2 切片操作1.2.3 字符串的拼接和重复1.2.4 字符串的长度 1.3 字符串的方法1.4 字符串的查找和替换1.5 字符串格式化1.5.1 使用 % 运算符1.5.2 使用 str.format()1.5.3 使用 f-string࿰…...
Spring框架中都用到了哪些设计模式?
大家好,我是锋哥。今天分享关于【Spring框架中都用到了哪些设计模式?】面试题。希望对大家有帮助; Spring框架中都用到了哪些设计模式? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring框架中使用了大量的设计模…...
HBuilderX中uni-app打开页面时,如何用URL传递参数,Query参数传递
HBuilderX中uni-app打开页面时,如何用URL传递参数,Query参数传递? URL是一种描述文件在计算机网络中位置的方式。在web开发中,可以通过query string来传递参数。使用uniapp进行开发,打开不同的页面时,本文介绍给所要打…...
【开源向量数据库】Milvus简介
Milvus 是一个开源、高性能、可扩展的向量数据库,专门用于存储和检索高维向量数据。它支持近似最近邻搜索(ANN),适用于图像检索、自然语言处理(NLP)、推荐系统、异常检测等 AI 应用场景。 官网:…...
c/c++蓝桥杯经典编程题100道(19)汉诺塔问题
汉诺塔问题 ->返回c/c蓝桥杯经典编程题100道-目录 目录 汉诺塔问题 一、题型解释 二、例题问题描述 三、C语言实现 解法1:递归法(难度★) 解法2:迭代法(难度★★★) 四、C实现 解法1࿱…...
CSS盒模
CSS盒模型就像一个快递包裹,网页上的每个元素都可以看成是这样一个包裹,它主要由以下几个部分组成: 内容(content):就像包裹里真正装的东西,比如文字、图片等。在CSS里,可用width&a…...
【go语言规范】关于接口设计
抽象应该被发现,而不是被创造。为了避免不必要的复杂性,需要时才创建接口,而不是预见到需要它,或者至少可以证明这种抽象是有价值的。 “The bigger the interface, the weaker the abstraction. 不要用接口进行设计,要…...
计算机视觉+Numpy和OpenCV入门
Day 1:Python基础Numpy和OpenCV入门 Python基础 变量与数据类型、函数与类的定义、列表与字典操作文件读写操作(读写图像和数据文件) 练习任务:写一个Python脚本,读取一个图像并保存灰度图像。 import cv2 img cv2.im…...
计算机网络之网络层(网络层的功能,异构网络互联,路由与转发,SDN基本概念,拥塞控制)
计算机网络之网络层 网络层(Network Layer)是计算机网络体系结构中至关重要的一层,它位于数据链路层(Data Link Layer)和传输层(Transport Layer)之间,主要负责数据包从源主机到目的…...
利用雪花算法+Redis 自增 ID,生成订单号
在我们的项目中,我们需要定义一些全局唯一的 ID,比如订单号,支付单号等等。 这些ID有以下几个基本要求: 1、不能重复 2、不可被预测 3、能适应分库分表 为了生成一个这样一个全局的订单号,自定义了一个分布式 ID …...
第35次CCF计算机软件能力认证 python 参考代码
题目列表1. 密码2. 字符串变换3. 补丁应用4. 通讯延迟5. 木板切割题目列表 第35次CCF计算机软件能力认证 1. 密码 n = int(input()) for _ in range(n):s =...
【探商宝】:大数据与AI赋能,助力中小企业精准拓客引
引言:在数据洪流中,如何精准锁定商机? 在竞争激烈的商业环境中,中小企业如何从海量信息中快速筛选出高价值客户?如何避免无效沟通,精准触达目标企业? 探商宝——一款基于大数据与AI技术的企业信…...
npm 私服使用介绍
一、导读 本文主要介绍 npm 私服的使用,至于 npm 私服搭建的过程,可以看本人之前的文章《Docker 部署 verdaccio 搭建 npm 私服》 二、前置条件 npm私服地址:http://xxx.xxx.xxx.xxx:port/ 三、本地 npm 源切换 使用nrm,可以方…...
【Python 打造高效文件分类工具】
【Python】 打造高效文件分类工具 一、代码整体结构二、关键代码解析(一)初始化部分(二)界面创建部分(三)核心功能部分(四)其他辅助功能部分 三、运行与使用四、示图五、作者有话说 …...
水务+AI应用探索(一)| FastGPT+DeepSeek 本地部署
在当下的科技浪潮中,AI 无疑是最炙手可热的焦点之一,其强大的能力催生出了丰富多样的应用场景,广泛渗透到各个行业领域。对于水务行业而言,AI 的潜力同样不可估量。为了深入探究 AI 在水务领域的实际应用成效,切实掌握…...
基于若依开发的工程项目管理系统开源免费,用于工程项目投标、进度及成本管理的OA 办公开源系统,非常出色!
一、简介 今天给大家推荐一个基于 RuoYi-Flowable-Plus 框架二次开发的开源工程项目管理系统,专为工程项目的投标管理、项目进度控制、成本管理以及 OA 办公需求设计。 该项目结合了 Spring Boot、Mybatis、Vue 和 ElementUI 等技术栈,提供了丰富的功能…...
最新Apache Hudi 1.0.1源码编译详细教程以及常见问题处理
1.最新Apache Hudi 1.0.1源码编译 2.Flink、Spark、Hive集成Hudi 1.0.1 3.flink streaming写入hudi 目录 1. 版本介绍 2. 安装maven 2.1. 下载maven 2.2. 设置环境变量 2.3. 添加Maven镜像 3. 编译hudi 3.1. 下载hudi源码 3.2. 修改hudi源码 3.3. 修改hudi-1.0.1/po…...
设计模式-工厂模式
设计模式 - 工厂模式 工厂模式(Factory Pattern)是一种创建型设计模式,它通过定义一个用于创建对象的接口,让子类决定实例化哪一个类,从而使得一个类的实例化延迟到子类。工厂模式通过将对象创建的逻辑抽象化…...
基于opencv的HOG+角点匹配教程
1. 引言 在计算机视觉任务中,特征匹配是目标识别、图像配准和物体跟踪的重要组成部分。本文介绍如何使用 HOG(Histogram of Oriented Gradients,方向梯度直方图) 和 角点检测(Corner Detection) 进行特征匹…...
《深度学习》——调整学习率和保存使用最优模型
调整学习率 在使用 PyTorch 进行深度学习训练时,调整学习率是一个重要的技巧,合适的学习率调整策略可以帮助模型更好地收敛。 PyTorch 提供了多种调整学习率的方法,下面将详细介绍几种常见的学习率调整策略及实例代码: torch.opt…...
Ubuntu22.04配置cuda/cudnn/pytorch
Ubuntu22.04配置cuda/cudnn/pytorch 安装cuda官网下载.run文件并且安装/etc/profile中配置cuda环境变量 cudnn安装官网找cuda版本对应的cudnn版本下载复制相应文件到系统文件中 安装pytorch官网找cuda对应版本的pytorchpython代码测试pytorch-GPU版本安装情况 安装cuda 官网下…...
通俗理解-L、-rpath和-rpath-link编译链接动态库
一、参考资料 链接选项 rpath 的应用和原理 | BewareMyPower的博客 使用 rpath 和 rpath-link 确保 samba-util 库正确链接-CSDN博客 编译参数-Wl和rpath的理解_-wl,-rpath-CSDN博客 Using LD, the GNU linker - Options Directory Options (Using the GNU Compiler Colle…...
【Golang】GC探秘/写屏障是什么?
之前写了 一篇【Golang】内存管理 ,有了很多的阅读量,那么我就接着分享一下Golang的GC相关的学习。 由于Golang的GC机制一直在持续迭代,本文叙述的主要是Go1.9版本及以后的GC机制,该版本中Golang引入了 混合写屏障大幅度地优化了S…...
Linux(Centos 7.6)命令详解:head
1.命令作用 将每个文件的前10行打印到标准输出(Print the first 10 lines of each FILE to standard output) 2.命令语法 Usage: head [OPTION]... [FILE]... 3.参数详解 OPTION: -c, --bytes[-]K,打印每个文件的前K字节-n, --lines[-],打印前K行而…...
【工具变量】上市公司网络安全治理数据(2007-2023年)
网络安全是国家安全体系的重要组成部分,是确保金融体系稳定运行、维护市场信心的关键。网络安全治理是指通过一系列手段和措施,维护网络安全,保护网络环境的稳定和安全,旨在确保公司网络系统免受未经授权的访问、攻击和破坏&#…...
watermark解释
在 Apache Flink 中,Watermark 是处理事件时间(Event Time)的核心机制,用于解决流处理中因数据乱序或延迟到达而导致的窗口计算不准确问题。理解 Watermark 的关键在于以下几点: 1. 事件时间 vs. 处理时间 事件时间&…...
Transformer 模型介绍(四)——编码器 Encoder 和解码器 Decoder
上篇中讲完了自注意力机制 Self-Attention 和多头注意力机制 Multi-Head Attention,这是 Transformer 核心组成部分之一,在此基础上,进一步展开讲一下编码器-解码器结构(Encoder-Decoder Architecture) Transformer 模…...
Linux安装Minio
1、下载rpm包 2、rpm 安装 rpm -ivh xx.rpm3、通过查看minion状态,查看其配置文件位置 systemctl start minio可以根据情况自定义修改配置文件内容,这里暂时不做修改 4、创建数据文件和日志文件,一般在/usr/local/ 5、编写启动脚本 #!/bi…...
JAVA EE初阶 - 预备知识(三)
一、中间件 中间件是一种处于操作系统和应用程序之间的软件,它能够为分布式应用提供交互、资源共享、数据处理等功能,是现代软件架构中不可或缺的一部分。下面从多个方面为你详细介绍中间件: 定义与作用 定义:中间件是连接两个或…...
百度热力图数据获取,原理,处理及论文应用6
目录 0、数据简介0、示例数据1、百度热力图数据日期如何选择1.1、其他实验数据的时间1.2、看日历1.3、看天气 2、百度热力图几天够研究?部分文章统计3、数据原理3.1.1 ** 这个比较重要,后面还会再次出现。核密度的值怎么理解?**3.1.2 Csv->…...
tcp连接的11种状态及常见问题。
tcp链接整体流程,如下图所示: TCP协议在连接的建立和终止过程中涉及11种状态,这些状态反映了连接的不同阶段。以下是每种状态的详细说明: 1. CLOSED 初始状态,表示没有活动的连接或连接已完全关闭。当应用程序关闭连…...
宝塔面板开始ssl后,使用域名访问不了后台管理
宝塔面板后台开启ssl访问后,用的证书是其他第三方颁发的证书 再使用 域名/xxx 的形式:https://域名:xxx/xxx 访问后台,结果出现如下,不管使用 http 还是 https 的路径访问都进不后台管理 这个时候可以使用 https://ip/xxx 的方式来…...
5.日常英语笔记
sprouted tater 发芽的土豆 fluid 液体,流体 The doctor recommended drinking plenty of fluids 医生建议多喝流质 适应新环境 adapt to the new environment adjust to the new surroundings get used to the new setting accommodate oneself to the new circu…...
利用工业相机及多光谱图像技术助力医疗美容皮肤AI检测方案
随着年轻人对皮肤的管理要求越来越高,“科技护肤”已成为线下医疗美业护肤行业转型升级之趋势。如何利用先进的图像识别技术和AI大数据分析,为医生和消费者提供个性化的皮肤诊断分析和护肤建议,利用工业相机及多光谱成像技术完美实现这一可能…...
qt QOpenGLTexture详解
1. 概述 QOpenGLTexture 是 Qt5 提供的一个类,用于表示和管理 OpenGL 纹理。它封装了 OpenGL 纹理的创建、分配存储、绑定和设置像素数据等操作,简化了 OpenGL 纹理的使用。 2. 重要函数 构造函数: QOpenGLTexture(const QImage &image,…...
【Zookeeper如何实现分布式锁?】
Zookeeper如何实现分布式锁? 一、ZooKeeper分布式锁的实现原理二、ZooKeeper分布式锁的实现流程三、示例代码四、总结一、ZooKeeper分布式锁的实现原理 ZooKeeper是一个开源的分布式协调服务,它提供了一个分布式文件系统的接口,可以用来存储和管理分布式系统的配置信息。 …...
mysql 学习16 视图,存储过程,存储函数,触发器
视图, 存储过程, 存储函数 触发器...
(前端基础)HTML(一)
前提 W3C:World Wide Web Consortium(万维网联盟) Web技术领域最权威和具有影响力的国际中立性技术标准机构 其中标准包括:机构化标准语言(HTML、XML) 表现标准语言(CSS) 行为标准…...
go设置镜像代理
前言 在 Go 开发中,如果直接从官方源(https://proxy.golang.org)下载依赖包速度较慢,可以通过设置 镜像代理 来加速依赖包的下载。以下是增加 Go 镜像代理的详细方法: 一、设置 Go 镜像代理 1. 使用环境变量设置代理…...
matlab数据处理:创建网络数据
% 创建网格数据 [X, Y] meshgrid(x_data, y_data); 如 x_data [1 2 3 4] X 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 XY_data [X(:), Y(:)]; % 将X和Y合并成一个向量 X(:)表示将矩阵排成一列 XY_data 1 …...