啃 k8s 源码:kube-apiserver 启动到底干了啥?一文带你看清三服务链路 + GenericAPIServer 全流程

前言:那个让我看了三遍源码才理顺的启动流程

线上集群有次升级 K8s 版本,APIServer 启动后 readiness 探针整整 30 秒才返回 200,期间 kubelet、controller-manager 全部连不上,节点状态飘成一片红。

我以为是配置问题,查了半天 systemd unit、网络、证书,都没问题。直到打开 APIServer 日志,看到一长串 PostStartHook 注册和执行的输出,我才意识到:APIServer 的"启动"远不止 ListenAndServe 那一行——它在 Run() 和真正提供服务之间还干了一大堆事情。

那次故障让我下决心啃一遍源码。看完发现,APIServer 启动的核心其实就 3 件事

  1. 构建 三个 server 的委托链(apiExtensionsServer → kubeAPIServer → aggregatorServer)
  2. 调用 GenericAPIServer.New 给每个 server 织好 handler / hook / 健康检查
  3. 调用 PrepareRunNonBlockingRun 启动 HTTPS server

听起来简单,但每一步都有坑。这篇就把这条链路逐行讲清楚


本节重点

  • 通用的 GenericApiServer.New 函数
  • kube-apiserver 核心服务的初始化
  • 最终的 APIServer 启动流程

一、三个 server 的"委托链"是什么?

APIServer 不是一个 server,而是三个 server 串成一条委托链。每个 server 处理自己负责的 URL 路径,处理不了的就委托给下一个。

        ┌──────────────────────────────────────────────┐
        │  HTTPS 请求 (6443)                            │
        └─────────────────┬────────────────────────────┘
                          ▼
        ┌──────────────────────────────────────────────┐
        │  aggregatorServer (聚合层 / APIService)      │
        │  - metrics-server, custom.metrics.k8s.io...  │
        │  - 处理不了?delegate ↓                       │
        └─────────────────┬────────────────────────────┘
                          ▼
        ┌──────────────────────────────────────────────┐
        │  kubeAPIServer (核心)                         │
        │  - Pod / Deployment / Service / Node ...      │
        │  - 处理不了?delegate ↓                       │
        └─────────────────┬────────────────────────────┘
                          ▼
        ┌──────────────────────────────────────────────┐
        │  apiExtensionsServer (CRD)                   │
        │  - 用户自定义资源 (Custom Resources)          │
        │  - 处理不了?delegate ↓                       │
        └─────────────────┬────────────────────────────┘
                          ▼
        ┌──────────────────────────────────────────────┐
        │  NotFoundHandler (404)                       │
        └──────────────────────────────────────────────┘

1.1 委托链的代码

位置:cmd/kube-apiserver/app/server.goCreateServerChain 函数。

// 1. apiExtensionsServer (CRD)
apiExtensionsServer, err := createAPIExtensionsServer(
    apiExtensionsConfig,
    genericapiserver.NewEmptyDelegate(),  // 链尾,委托给空 handler (404)
)

// 2. kubeAPIServer (核心资源)
kubeAPIServer, err := CreateKubeAPIServer(
    kubeAPIServerConfig,
    apiExtensionsServer.GenericAPIServer,  // 委托给 apiExtensions
)

// 3. aggregatorServer (聚合 API)
aggregatorServer, err := createAggregatorServer(
    aggregatorConfig,
    kubeAPIServer.GenericAPIServer,  // 委托给 kubeAPIServer
    apiExtensionsServer.Informers,
)

💡 为什么是这个顺序? 这是面试常考点。aggregatorServer 在最外层,因为聚合 API(如 metrics-server)是用户通过 APIService 注册的扩展,必须最先匹配;中间是 kubeAPIServer 处理 K8s 内置资源;最内层是 apiExtensionsServer 处理 CRD。这个顺序保证了用户扩展 > 内置资源 > 自定义资源 的优先级。

1.2 三个 server 共享同一个 New:GenericConfig.New

三个 server 的 New 函数里都会调用同一个底层方法 c.GenericConfig.New(...)

// kubeAPIServer 的初始化
s, err := c.GenericConfig.New("kube-apiserver", delegationTarget)

// apiExtensionsServer 的初始化
genericServer, err := c.GenericConfig.New("apiextensions-apiserver", delegationTarget)

// aggregatorServer 的初始化
genericServer, err := c.GenericConfig.New("kube-aggregator", delegationTarget)

name 参数仅用于日志区分,三个 server 共用同一份 GenericConfig、同一套 hook 框架、同一个 HTTPS 监听器——只是各自的资源路由不同。

🤔 这种设计的好处:用一个 GenericAPIServer 抽象,统一管理认证、授权、审计、限流等公共逻辑。Kubernetes 自己用,CRD 扩展也用,aggregator 也用——复用最大化。这也是为什么自己写 controller / aggregated API 时,可以直接 import k8s.io/apiserver 复用所有能力。


二、GenericAPIServer.New:源码逐行解析

位置:staging/src/k8s.io/apiserver/pkg/server/config.go

这个函数大约 200 行,干了 5 件大事:构建 handler 链 → 实例化 server → 注册 hook → 注册健康检查 → 安装通用路由。

2.1 构建 HTTP handler 链

handlerChainBuilder := func(handler http.Handler) http.Handler {
    return c.BuildHandlerChainFunc(handler, c.Config)
}
apiServerHandler := NewAPIServerHandler(
    name,
    c.Serializer,
    handlerChainBuilder,
    delegationTarget.UnprotectedHandler(),  // 委托给下一个 server
)

💡 BuildHandlerChainFunc 是 APIServer 的核心:它会层层包装出认证、授权、审计、限流、超时、Panic 恢复等中间件,最终把所有 HTTP 请求都经过这些过滤器。这条链路是 APIServer 安全性和可观测性的关键,下一节会专门拆。

2.2 实例化 GenericAPIServer

s := &GenericAPIServer{
    discoveryAddresses:         c.DiscoveryAddresses,
    LoopbackClientConfig:       c.LoopbackClientConfig,
    legacyAPIGroupPrefixes:     c.LegacyAPIGroupPrefixes,
    admissionControl:           c.AdmissionControl,
    Serializer:                 c.Serializer,
    AuditBackend:               c.AuditBackend,
    Authorizer:                 c.Authorization.Authorizer,
    delegationTarget:           delegationTarget,   // 委托链下一环
    EquivalentResourceRegistry: c.EquivalentResourceRegistry,
    HandlerChainWaitGroup:      c.HandlerChainWaitGroup,

    minRequestTimeout:     time.Duration(c.MinRequestTimeout) * time.Second,
    ShutdownTimeout:       c.RequestTimeout,
    ShutdownDelayDuration: c.ShutdownDelayDuration,
    SecureServingInfo:     c.SecureServing,
    ExternalAddress:       c.ExternalAddress,

    Handler: apiServerHandler,
    listedPathProvider: apiServerHandler,

    openAPIConfig:           c.OpenAPIConfig,
    skipOpenAPIInstallation: c.SkipOpenAPIInstallation,

    postStartHooks:         map[string]postStartHookEntry{},
    preShutdownHooks:       map[string]preShutdownHookEntry{},
    disabledPostStartHooks: c.DisabledPostStartHooks,

    healthzChecks:    c.HealthzChecks,
    livezChecks:      c.LivezChecks,
    readyzChecks:     c.ReadyzChecks,
    livezGracePeriod: c.LivezGracePeriod,

    DiscoveryGroupManager: discovery.NewRootAPIsHandler(c.DiscoveryAddresses, c.Serializer),

    maxRequestBodyBytes: c.MaxRequestBodyBytes,
    livezClock:          clock.RealClock{},

    lifecycleSignals:       c.lifecycleSignals,
    ShutdownSendRetryAfter: c.ShutdownSendRetryAfter,

    APIServerID:           c.APIServerID,
    StorageVersionManager: c.StorageVersionManager,

    Version: c.Version,
}

这段代码看着长,但其实就是把 config 里的字段一对一复制到 server 实例。重点关注几个字段:

字段作用
delegationTarget委托链下一环,处理不了的请求扔给它
admissionControl准入控制器(mutating + validating webhook 在这)
Authorizer授权器(RBAC / Node / Webhook 都接到这)
lifecycleSignals生命周期信号(启动完成、shutdown 触发等)
livezGracePeriodlivez 检查的容忍期,启动期内不算失败

2.3 注册 PostStartHook(启动后钩子)

这是整个启动流程最容易被忽略的部分——开头那个让我熬夜的 30 秒延迟,就是因为某个 hook 启动慢。

PostStartHook 注册分三个来源

① 从委托的下游 server 继承:

for k, v := range delegationTarget.PostStartHooks() {
    s.postStartHooks[k] = v
}

for k, v := range delegationTarget.PreShutdownHooks() {
    s.preShutdownHooks[k] = v
}

这意味着 aggregatorServer继承 kubeAPIServerapiExtensionsServer 的所有 hook——委托链合并 hook。

② 从 completedConfig 中预配置的 hook 注册:

// add poststarthooks that were preconfigured.
// Using the add method will give us an error if the same name has already been registered.
for name, preconfiguredPostStartHook := range c.PostStartHooks {
    if err := s.AddPostStartHook(name, preconfiguredPostStartHook.hook); err != nil {
        return nil, err
    }
}

注意这里的重名检测——如果两个地方注册了同名 hook,会直接报错。这避免了 hook 被静默覆盖。

③ 经典示例:admission post-start hook

位置:cmd/kube-apiserver/app/server.go

if err := config.GenericConfig.AddPostStartHook(
    "start-kube-apiserver-admission-initializer",
    admissionPostStartHook,
); err != nil {
    return nil, nil, nil, err
}

对应的 hook 实现(pkg/kubeapiserver/admission/config.go):

admissionPostStartHook := func(context genericapiserver.PostStartHookContext) error {
    discoveryRESTMapper.Reset()
    go utilwait.Until(discoveryRESTMapper.Reset, 30*time.Second, context.StopCh)
    return nil
}

功能:APIServer 启动后立即刷新一次 RESTMapper(资源类型映射表),然后每 30 秒刷一次。这就是为什么 webhook 启动后能立即识别新注册的 CRD——不用重启 APIServer,靠这个 hook 自动同步。

2.4 关键 hook 速览

① Informer 启动 hook
genericApiServerHookName := "generic-apiserver-start-informers"
if c.SharedInformerFactory != nil {
    if !s.isPostStartHookRegistered(genericApiServerHookName) {
        err := s.AddPostStartHook(genericApiServerHookName, func(context PostStartHookContext) error {
            c.SharedInformerFactory.Start(context.StopCh)
            return nil
        })
        if err != nil {
            return nil, err
        }
    }
    err := s.AddReadyzChecks(healthz.NewInformerSyncHealthz(c.SharedInformerFactory))
    if err != nil {
        return nil, err
    }
}

🚨 30 秒卡顿的真相SharedInformerFactory.Start 后,K8s 需要等所有 informer 完成首次 list+watch 同步才会让 readyz 返回 200。集群里 CRD/资源越多,首次 list 越慢。我那次故障就是因为加了几百个 CRD,informer 同步花了 30 秒。

排查方法:grep apiserver 日志里 caches synced,能看到每个 informer 同步耗时。

② 限流相关 hook (APF: API Priority and Fairness)
const priorityAndFairnessConfigConsumerHookName = "priority-and-fairness-config-consumer"
if s.isPostStartHookRegistered(priorityAndFairnessConfigConsumerHookName) {
    // 已注册,跳过
} else if c.FlowControl != nil {
    err := s.AddPostStartHook(priorityAndFairnessConfigConsumerHookName, func(context PostStartHookContext) error {
        go c.FlowControl.MaintainObservations(context.StopCh)
        go c.FlowControl.Run(context.StopCh)
        return nil
    })
    // ...
} else {
    klog.V(3).Infof("Not requested to run hook %s", priorityAndFairnessConfigConsumerHookName)
}

// 维护限流水位的 hook
if c.FlowControl != nil {
    // APF 启用 → 用 APF 限流
    const priorityAndFairnessFilterHookName = "priority-and-fairness-filter"
    if !s.isPostStartHookRegistered(priorityAndFairnessFilterHookName) {
        err := s.AddPostStartHook(priorityAndFairnessFilterHookName, func(context PostStartHookContext) error {
            genericfilters.StartPriorityAndFairnessWatermarkMaintenance(context.StopCh)
            return nil
        })
        // ...
    }
} else {
    // 否则 fallback 到 max-in-flight 限流
    const maxInFlightFilterHookName = "max-in-flight-filter"
    if !s.isPostStartHookRegistered(maxInFlightFilterHookName) {
        err := s.AddPostStartHook(maxInFlightFilterHookName, func(context PostStartHookContext) error {
            genericfilters.StartMaxInFlightWatermarkMaintenance(context.StopCh)
            return nil
        })
        // ...
    }
}

💡 APF 和 max-in-flight 是二选一:开了 APF(feature gate APIPriorityAndFairness=true),就用 APF 做限流;否则 fallback 到老的 max-in-flight 限流。代码里用 if-else 优雅地处理了这个切换。

这部分逻辑很深,下一节有专门的"APIServer 限流"章节,这里先有个印象就行。

2.5 添加健康检查

for _, delegateCheck := range delegationTarget.HealthzChecks() {
    skip := false
    for _, existingCheck := range c.HealthzChecks {
        if existingCheck.Name() == delegateCheck.Name() {
            skip = true
            break
        }
    }
    if skip {
        continue
    }
    s.AddHealthChecks(delegateCheck)
}

逻辑:从委托的下游 server 继承健康检查项,但同名的不重复添加

AddHealthChecks 的实现有个细节(staging/src/k8s.io/apiserver/pkg/server/healthz.go):

// AddHealthChecks adds HealthCheck(s) to health endpoints (healthz, livez, readyz) but
// configures the liveness grace period to be zero, which means we expect this health check
// to immediately indicate that the apiserver is unhealthy.
func (s *GenericAPIServer) AddHealthChecks(checks ...healthz.HealthChecker) error {
    // we opt for a delay of zero here, because this entrypoint adds generic health checks
    // and not health checks which are specifically related to kube-apiserver boot-sequences.
    return s.addHealthChecks(0, checks...)
}

💡 gracePeriod=0 是什么意思? 委托下游传过来的健康检查,不给容忍期——一旦失败立即标记不健康。这是因为这些 check 是"通用检查",不涉及 APIServer 启动序列里那些"必须等待"的逻辑(比如 informer 同步),所以可以严格判断。

2.6 安装通用路由 installAPI

最后给 server 装上几个与业务资源无关的通用路由

// 1. /  和  /index.html
if c.EnableIndex {
    routes.Index{}.Install(s.listedPathProvider, s.Handler.NonGoRestfulMux)
}

// 2. /debug/pprof  性能分析
if c.EnableProfiling {
    routes.Profiling{}.Install(s.Handler.NonGoRestfulMux)
    if c.EnableContentionProfiling {
        goruntime.SetBlockProfileRate(1)
    }
    routes.DebugFlags{}.Install(s.Handler.NonGoRestfulMux, "v", routes.StringFlagPutHandler(logs.GlogSetter))
}

// 3. /metrics  Prometheus 指标
if c.EnableMetrics {
    if c.EnableProfiling {
        routes.MetricsWithReset{}.Install(s.Handler.NonGoRestfulMux)
    } else {
        routes.DefaultMetrics{}.Install(s.Handler.NonGoRestfulMux)
    }
}

// 4. /version  版本信息
routes.Version{Version: c.Version}.Install(s.Handler.GoRestfulContainer)

// 5. 服务发现 /apis 等
if c.EnableDiscovery {
    s.Handler.GoRestfulContainer.Add(s.DiscoveryGroupManager.WebService())
}

🚨 生产环境强烈建议关掉 /debug/pprof!这个端点暴露后,任何能访问 APIServer 的人都能拉取堆栈、profile,严重影响性能甚至泄露敏感信息。如果非要开(debug 阶段),务必加 RBAC 限制。


三、kubeAPIServer 核心服务的初始化

GenericAPIServer.New通用框架,但 kubeAPIServer 在它的基础上还要做一件最重要的事:注册所有 K8s 内置资源的 REST API(Pod、Deployment、Service 等)。

位置:pkg/controlplane/instance.go

3.1 用通用 server 包一层 Instance

func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*Instance, error) {
    // 1. 先用通用方法生成 GenericAPIServer
    s, err := c.GenericConfig.New("kube-apiserver", delegationTarget)
    // ...

    // 2. 用 GenericAPIServer 实例化 Instance(master 实例)
    m := &Instance{
        GenericAPIServer:          s,
        ClusterAuthenticationInfo: c.ExtraConfig.ClusterAuthenticationInfo,
    }
    // ...
}

Instance 就是 K8s 源码里我们常说的 “master 实例”——它在 GenericAPIServer 之上包了一层,加了 K8s 内置资源相关的元数据。

3.2 注册核心资源的 API(Legacy API: /api/v1)

if c.ExtraConfig.APIResourceConfigSource.VersionEnabled(apiv1.SchemeGroupVersion) {
    legacyRESTStorageProvider := corerest.LegacyRESTStorageProvider{
        StorageFactory:              c.ExtraConfig.StorageFactory,
        ProxyTransport:              c.ExtraConfig.ProxyTransport,
        KubeletClientConfig:         c.ExtraConfig.KubeletClientConfig,
        EventTTL:                    c.ExtraConfig.EventTTL,
        ServiceIPRange:              c.ExtraConfig.ServiceIPRange,
        SecondaryServiceIPRange:     c.ExtraConfig.SecondaryServiceIPRange,
        ServiceNodePortRange:        c.ExtraConfig.ServiceNodePortRange,
        LoopbackClientConfig:        c.GenericConfig.LoopbackClientConfig,
        ServiceAccountIssuer:        c.ExtraConfig.ServiceAccountIssuer,
        ExtendExpiration:            c.ExtraConfig.ExtendExpiration,
        ServiceAccountMaxExpiration: c.ExtraConfig.ServiceAccountMaxExpiration,
        APIAudiences:                c.GenericConfig.Authentication.APIAudiences,
    }
    if err := m.InstallLegacyAPI(&c, c.GenericConfig.RESTOptionsGetter, legacyRESTStorageProvider); err != nil {
        return nil, err
    }
}

💡 什么是 Legacy API? /api/v1/... 这些路径(Pod、Service、Node、Namespace 等核心资源)是 K8s 最早设计的 API,没有 group 前缀。后来加的资源都有 group(如 /apis/apps/v1/deployments)。出于历史兼容性,core group 一直保留 /api/v1 这个特殊路径,被称为 “Legacy API”。

LegacyRESTStorageProvider 这个结构体里塞了一堆和 Pod/Service 紧密相关的配置

  • KubeletClientConfig:APIServer 通过它和 kubelet 通信(exec/log/portforward)
  • ServiceIPRange:Service 的 ClusterIP 池
  • ServiceAccountIssuer:用来签发 ServiceAccount Token

3.3 注册其他 API Group

if err := m.InstallAPIs(
    c.ExtraConfig.APIResourceConfigSource,
    c.GenericConfig.RESTOptionsGetter,
    restStorageProviders...,
); err != nil {
    return nil, err
}

restStorageProviders 是个列表,里面有 apps、batch、networking、storage、policy、authentication 等所有非 core group 的 storage provider。每个 provider 负责自己 group 下的资源注册。

🤔 想自己看看都注册了哪些 group? 起一个 K8s 集群,跑 kubectl api-resources -o wide,输出里 APIGROUP 列就是所有 group。这些 group 的 storage provider 都是在 InstallAPIs 里被加载的。

下一节会专门讲 scheme 和 RESTStorage 的初始化——这块涉及 K8s 的资源序列化和持久化层,是另一个深坑。


四、最终的 APIServer 启动流程

绕了这么大一圈,终于回到顶层入口。

位置:cmd/kube-apiserver/app/server.go

4.1 Run 函数

// Run runs the specified APIServer.  This should never exit.
func Run(completeOptions completedServerRunOptions, stopCh <-chan struct{}) error {
    // To help debugging, immediately log version
    klog.Infof("Version: %+v", version.Get())

    // 1. 创建三个 server 的委托链
    server, err := CreateServerChain(completeOptions, stopCh)
    if err != nil {
        return err
    }

    // 2. PrepareRun:执行所有准备工作(OpenAPI 注册等)
    prepared, err := server.PrepareRun()
    if err != nil {
        return err
    }

    // 3. Run:阻塞运行(直到 stopCh 关闭)
    return prepared.Run(stopCh)
}

三步:Create → Prepare → Run。我之前 30 秒卡顿就发生在 Prepare 和 Run 之间——所有 hook 在这里执行。

4.2 preparedGenericAPIServer.Run

位置:staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go

stoppedCh, listenerStoppedCh, err := s.NonBlockingRun(stopHttpServerCh, shutdownTimeout)

注意函数名 NonBlockingRun——HTTP server 在 goroutine 里跑,调用者(也就是 Run)会通过等待 stopCh 来阻塞。

4.3 NonBlockingRun:真正起 HTTPS

if s.SecureServingInfo != nil && s.Handler != nil {
    var err error
    stoppedCh, listenerStoppedCh, err = s.SecureServingInfo.ServeWithListenerStopped(
        s.Handler,
        shutdownTimeout,
        internalStopCh,
    )
    if err != nil {
        close(internalStopCh)
        close(auditStopCh)
        return nil, nil, err
    }
}

最终落到 ServeWithListenerStopped,位置 staging/src/k8s.io/apiserver/pkg/server/secure_serving.go

// ServeWithListenerStopped runs the secure http server.
// It fails only if certificates cannot be loaded or the initial listen call fails.
// The actual server loop (stoppable by closing stopCh) runs in a go routine,
// i.e. ServeWithListenerStopped does not block.
// It returns a stoppedCh that is closed when all non-hijacked active requests have been processed.
// It returns a listenerStoppedCh that is closed when the underlying http Server has stopped listening.
func (s *SecureServingInfo) ServeWithListenerStopped(
    handler http.Handler,
    shutdownTimeout time.Duration,
    stopCh <-chan struct{},
) (<-chan struct{}, <-chan struct{}, error) {
    // ... 加载 TLS 证书、起 net.Listener、起 http.Server
}

这里两个 channel 设计很精妙:

  • stoppedCh:所有未 hijack 的活跃请求都处理完后才关闭(优雅退出
  • listenerStoppedCh:底层 HTTP server 停止监听时关闭

💡 为什么要两个 channel 区分? 因为 APIServer 处理的请求有 watch 这种长连接listenerStoppedCh 关闭后,新连接进不来;但已有的 watch 连接还在跑——必须等它们都收尾完,stoppedCh 才关闭。负载均衡器先在 listenerStoppedCh 后摘流量,然后等 stoppedCh 关再杀进程,实现零丢请求滚动升级。


五、整体启动流程图

                      ┌──────────────────────┐
                      │  main() / Run()      │
                      └──────────┬───────────┘
                                 ▼
                ┌────────────────────────────────────┐
                │  CreateServerChain()               │
                │  ┌──────────────────────────────┐  │
                │  │ apiExtensionsServer (CRD)    │  │
                │  └─────────┬────────────────────┘  │
                │            ▼ delegate              │
                │  ┌──────────────────────────────┐  │
                │  │ kubeAPIServer (核心资源)     │  │
                │  └─────────┬────────────────────┘  │
                │            ▼ delegate              │
                │  ┌──────────────────────────────┐  │
                │  │ aggregatorServer (聚合)      │  │
                │  └──────────────────────────────┘  │
                └──────────┬─────────────────────────┘
                           ▼
              ┌────────────────────────────┐
              │  每个 server 都执行:      │
              │  GenericConfig.New(...)    │
              │  ├── 构建 handler 链       │
              │  ├── 实例化 GenericAPIServer│
              │  ├── 注册 PostStartHook    │
              │  ├── 注册健康检查          │
              │  └── 安装通用路由          │
              │                            │
              │  kubeAPIServer 额外执行:  │
              │  ├── InstallLegacyAPI      │
              │  └── InstallAPIs           │
              └──────────┬─────────────────┘
                         ▼
              ┌────────────────────────────┐
              │  server.PrepareRun()       │
              │  - 安装 OpenAPI 路由       │
              │  - 注册 audit              │
              │  - 启动通用 hook           │
              └──────────┬─────────────────┘
                         ▼
              ┌────────────────────────────┐
              │  prepared.Run(stopCh)      │
              │  └─ NonBlockingRun()       │
              │     └─ ServeWithListener…  │
              │        启动 6443 端口      │
              └──────────┬─────────────────┘
                         ▼
              ┌────────────────────────────┐
              │  PostStartHooks 并发执行:│
              │  - 启动 informer           │
              │  - admission 初始化        │
              │  - APF / max-in-flight     │
              │  - bootstrap controller    │
              │  ……                        │
              └──────────┬─────────────────┘
                         ▼
              ┌────────────────────────────┐
              │  readyz 返回 200           │
              │  APIServer 真正可用 ✅     │
              └────────────────────────────┘

六、启动性能排查清单

如果你也遇到了 APIServer 启动慢的问题,按这个顺序排查:

#检查项命令 / 方法可能原因
1看 PostStartHook 耗时grep apiserver 日志 "post-start hook .* finished"某个 hook 阻塞
2看 Informer 同步耗时grep "caches synced"CRD 太多 / etcd 慢
3看 OpenAPI 注册耗时grep "OpenAPI .* AggregationController"aggregated server 不可达
4看 etcd 健康检查curl /healthz/etcdetcd 连接慢
5看准入控制器加载grep "admission"webhook 不可达
6看证书加载grep "Serving securely on" 出现时间证书读取慢

🚨 生产实战经验:APIServer 启动慢 90% 的原因是 etcd 慢某个 webhook 不可达。前者通过 etcd-defrag 和 SSD 解决;后者一定要把 webhook 的 failurePolicy 设成 Ignore,并且 timeoutSeconds 设小(5 秒以内),不然 APIServer 会被一个挂掉的 webhook 拖死。


七、源码阅读建议

如果你想自己跟一遍源码,推荐这个顺序:

  1. 入口cmd/kube-apiserver/app/server.goRunCreateServerChain
  2. 通用 serverstaging/src/k8s.io/apiserver/pkg/server/config.goConfig.Complete().New()
  3. 核心 serverpkg/controlplane/instance.gocompletedConfig.New
  4. 资源注册pkg/controlplane/instance.goInstallLegacyAPI / InstallAPIs
  5. HTTPS 服务staging/src/k8s.io/apiserver/pkg/server/secure_serving.go

💡 小技巧:用 GoLand 打开 K8s 源码,从 Run 函数开始 ctrl+B 跳定义。重点关注 delegationTarget 这个参数怎么一层层传——理解了它,整个委托链就通了。


八、本节小结

APIServer 启动的完整路径:

  1. Run 是入口,做了三件事:CreateServerChainPrepareRunRun
  2. CreateServerChain 构建了三个 server 的委托链:apiExtensions → kubeAPIServer → aggregator
  3. 每个 server 都通过 GenericConfig.New() 完成通用初始化(handler 链、hook、健康检查、通用路由)
  4. kubeAPIServer 额外执行 InstallLegacyAPIInstallAPIs 注册所有 K8s 内置资源
  5. PrepareRun 执行准备工作(OpenAPI 注册、generic hook 启动)
  6. NonBlockingRunServeWithListenerStopped 启动 HTTPS 6443 端口
  7. PostStartHook 在后台并发执行,全部完成且 informer 同步完后 readyz 才返回 200

整条链路最容易踩坑的地方:readyz 返回 200 ≠ APIServer 启动完成那一刻——NonBlockingRun 之后还有 hook 在跑。如果监控做得不细,会以为 APIServer "立即就绪"了,但实际上请求可能依然有 30+ 秒的失败率。

下一节会进入更深的话题:scheme 和 RESTStorage 是怎么把 Go struct 变成 /apis/apps/v1/deployments 这条 URL 的,那是 K8s 序列化和持久化的核心。


九、你踩过这些坑吗?

  1. 你们生产环境的 APIServer 启动到 readyz 返回 200 通常要多久?最长见过多少秒?
  2. 有没有遇到过某个 webhook 不可达导致 APIServer 启动卡住的情况?最后是怎么排查到根因的?
  3. 自定义 aggregated API server 时,有没有踩过委托链的坑?比如 PostStartHook 注册重名、URL 路径冲突?

欢迎在评论区分享你的排查经验。

十、延伸思考

  • 如果让你设计一个 APIServer 的"启动时长 SLI",应该用哪些指标?/healthz ok 算就绪?还是 /readyz ok?还是 /livez/poststarthook/xxx 全 ok?
  • aggregatorServer 处于委托链最上层意味着什么?如果一个用户 APIService 的 webhook 卡死,会不会影响 kubectl get pods 这种核心请求?
  • K8s 的 PreShutdownHook 在优雅退出里扮演什么角色?和 NonBlockingRun 返回的两个 channel 是怎么配合的?

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加倍巴巴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值