云原生学习路线导航页(持续更新中)
- kubernetes学习系列快捷链接
- Kubernetes架构原则和对象设计(一)
- Kubernetes架构原则和对象设计(二)
- Kubernetes架构原则和对象设计(三)
- Kubernetes控制平面组件:etcd(一)
- Kubernetes控制平面组件:etcd(二)
- Kubernetes控制平面组件:API Server详解(一)
- Kubernetes控制平面组件:API Server详解(二)
本文主要对kubernetes的控制面组件API Server的代码阅读做一些前置准备,包括apimachinery项目的定位和核心包的介绍,API Server代码的一些基础操作,subresource概念、apiserver代码走读的关键点等
- 希望大家多多 点赞 关注 评论 收藏,作者会更有动力继续编写技术文章
1.apimachinery项目定位
- girhub:
- https://github.com/kubernetes/apimachinery
- https://github.com/kubernetes/kubernetes/tree/master/staging/src/k8s.io/apimachinery
- apimachinery 是 Kubernetes 生态中的底层基础设施库,为 Kubernetes API 的构建、操作和扩展提供基础能力。其核心定位体现在以下方面:
- 统一 API 基础设施:定义 Kubernetes API 的核心数据结构(如
TypeMeta
、ObjectMeta
)、类型系统(GVK/GVR 模型)和序列化机制(JSON/Protobuf),是客户端和服务端共享的依赖库 - 解耦核心组件:通过抽象 API 操作的通用逻辑(如版本转换、资源发现),使 Kubernetes 主仓库(k8s.io/kubernetes)、客户端库(client-go)和扩展组件(如 CRD 控制器)无需直接依赖核心代码
- 支撑扩展生态:提供自定义资源(CRD)和聚合层(Aggregated APIServer)的基础能力,是构建 Operator、Service Mesh 等云原生组件的基石
- 统一 API 基础设施:定义 Kubernetes API 的核心数据结构(如
2.apimachinery的核心能力与基本原理
2.1.核心能力
- 类型系统管理
- 定义 Kubernetes 资源的元数据(
TypeMeta
、ObjectMeta
)和通用接口(如runtime.Object
),支持资源的版本化描述。
- 定义 Kubernetes 资源的元数据(
- 序列化与反序列化
- 提供 JSON/Protobuf 编码解码能力,通过
runtime.Scheme
实现类型与数据格式的动态映射。
- 提供 JSON/Protobuf 编码解码能力,通过
- 版本转换与兼容性
- 支持多版本 API 共存(如
v1
与v1beta1
),通过Convert
接口实现不同版本资源的自动转换。
- 支持多版本 API 共存(如
- 资源发现与操作
- 提供
RESTMapper
实现 GVK 到 GVR 的映射,支持动态客户端(dynamic.Client
)操作未预定义类型的资源。
- 提供
- 通用工具链
- 包含深拷贝(
DeepCopy
)、标签选择器(labels.Selector
)、字段选择器(fields.Selector
)等高频工具
- 包含深拷贝(
2.2.基本原理
-
Scheme 机制
runtime.Scheme
是核心组件,负责注册 Golang 类型与 GVK 的映射关系,并管理版本转换函数。例如:scheme := runtime.NewScheme() corev1.AddToScheme(scheme) // 注册核心类型
- 通过 Scheme,客户端可自动反序列化 API Server 返回的 JSON 数据为对应结构体。
-
GVK/GVR 模型
- GVK (Group/Version/Kind):标识资源的逻辑类型(如
apps/v1.Deployment
)。 - GVR (Group/Version/Resource):对应 API 路径(如
/apis/apps/v1/deployments
)。 - apimachinery 提供
meta.RESTMapper
实现两者的动态映射,支撑kubectl get deployments
等操作。
- GVK (Group/Version/Kind):标识资源的逻辑类型(如
-
类型注册与转换
- 资源类型需实现
runtime.Object
接口,并通过AddKnownTypes
注册到 Scheme。 - 版本转换通过
Convert
函数链实现,例如将v1beta1.Deployment
转换为v1.Deployment
。
- 资源类型需实现
2.3.apimachinery核心代码包
-
apimachinery 的代码结构围绕 类型系统 和 API 操作 展开,核心包如下:
包路径 功能描述 k8s.io/apimachinery/pkg/apis/meta
定义元数据( TypeMeta
、ObjectMeta
)和公共接口(如ListOptions
、DeleteOptions
)k8s.io/apimachinery/pkg/runtime
实现序列化( Serializer
)、Scheme 管理、类型转换等核心逻辑k8s.io/apimachinery/pkg/api/resource
处理资源配额(如 CPU/内存的单位转换) k8s.io/apimachinery/pkg/util
提供通用工具(如 wait
重试机制、sets
集合操作)k8s.io/apimachinery/pkg/version
定义 Kubernetes 版本信息结构 -
关键数据结构示例:
// TypeMeta 定义资源的 API 版本和类型 type TypeMeta struct {APIVersion string `json:"apiVersion,omitempty"`Kind string `json:"kind,omitempty"` }// ObjectMeta 定义资源的元数据(名称、标签等) type ObjectMeta struct {Name string `json:"name,omitempty"`Namespace string `json:"namespace,omitempty"`Labels map[string]string `json:"labels,omitempty"`Annotations map[string]string `json:"annotations,omitempty"` }
3.API Server代码的一些基础操作
3.1.如何定义一个Group
- 分为三步:
- 定义一个GV:Group+Version
- 定义一个SchemeBuilder
- 将GV下具体的对象加入SchemeBuilder
3.2.如何定义一个对象
- 对象一般都是在types.go文件中
3.3.使用代码生成的Tags
3.3.1.code-generator项目
-
code-generator项目:https://github.com/kubernetes/code-generator
-
code-generator项目下包含很多代码生成的工具,包括生成 深拷贝代码、client代码、informer代码、lister代码、conversion代码等
-
代码生成器,是通过文件中的 特定tag 生效的,比如下面有一些常用的tag
标签 解释 // +k8s:deepcopy-gen=package
全局标签,为包内所有类型生成深拷贝方法。 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
生成实现 runtime.Object
接口的深拷贝方法,用于序列化与版本转换。// +genclient
生成客户端代码(CRUD 方法、Lister、Informer)。 // +genclient:nonNamespaced
标记资源为集群作用域(Cluster-scoped),生成不带命名空间参数的方法。 // +genclient:noVerbs
不生成默认客户端方法(需配合其他标签指定特定动词)。 // +genclient:onlyVerbs=create,delete
仅生成指定的客户端方法(如仅 Create
和Delete
)。// +genclient:skipVerbs=get,list,watch
跳过生成指定的客户端方法(如 Get
、List
、Watch
)。// +genclient:method=Create,verb=create,result=...
自定义客户端方法名称、HTTP 动词及返回类型(如返回 metav1.Status
)。 -
说明:
- 全局标签作用于整个包,局部标签作用于单个资源。
- 标签需严格遵循格式(如空格不可省略),否则会被代码生成器忽略。
3.3.2.代码生成使用示例
- 通过编写脚本文件,指定code-generator项目下各种工具的路径,然后使用命令生成具体的代码。如下图:
--input-dirs
:源代码目录-O
:生成的文件名称--bounding-dirs
:输出目录--go-header-file
:加什么样的header描述
3.4.如何操作etcd
- 每一个etcd的对象都会有一个storage.go,定义如何与etcd交互。比如上面就是定义了configmap的etcd操作。
- 其中strategy为具体的操作策略,比如创建、修改、删除,操作前可以进行一些校验,平时我们操作cm报的错误,就都是这个strategy报的错误了
- 比如这里CreateStrategy里的validate方法,就是对cm的校验,看apiserver代码的时候,可以看这里的处理
3.5.将 API Group 注册到API Server
- 通过install方法,将GVK的某个资源 Handler注册到apiserver中,在发生对应事件的时候才知道怎么处理
4.subresource概念
4.1.subresource是什么
- Kubernetes 中 Pod 的 subresource(子资源) 是 Pod 主资源的扩展操作接口,用于实现特定场景下的精细化操作或状态管理。
4.2.常见的subresource
Pod 的子资源主要通过 Kubernetes API 路径区分,常见的子资源如下:
4.2.1.pod的status
- 功能:用于获取或更新 Pod 的运行时状态(如容器状态、Pod 阶段等)。状态信息由 kubelet 维护,用户可通过 kubectl get pod -o yaml 查看状态字段。
- API 路径:
/api/v1/namespaces/{namespace}/pods/{name}/status
4.2.2.pod容器执行的exec
- 功能:在 Pod 的容器内执行命令(类似 docker exec)。常用于调试容器内进程或获取实时日志
- 使用示例:
kubectl exec -it <pod-name> -- /bin/sh
- API 路径:
/api/v1/namespaces/{namespace}/pods/{name}/exec
4.2.3.pod容器的log
- 功能:获取 Pod 中容器的日志输出。支持按时间范围、容器名称过滤日志。
- 使用示例:
kubectl logs <pod-name> -c <container-name> --since=5m
- API 路径:
/api/v1/namespaces/{namespace}/pods/{name}/log
4.2.4.pod的端口转发portforward
- 功能:将本地端口转发到 Pod 的容器端口,便于本地访问容器内服务(如调试数据库)。
- 使用示例:
kubectl port-forward <pod-name> 8080:80
- API 路径:
/api/v1/namespaces/{namespace}/pods/{name}/portforward
4.3.子资源的核心原理
4.3.1.API 路径设计
- Kubernetes 的 RESTful API 通过路径后缀标识子资源。例如,对 Pod 的
exec
操作对应路径/pods/{name}/exec
,而非直接操作 Pod 主资源。
4.3.2.独立的权限控制
- 子资源的操作需通过 RBAC 独立授权。例如,
exec
和portforward
需要pods/exec
和pods/portforward
权限:apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole rules: - apiGroups: [""]resources: ["pods/exec", "pods/portforward"]verbs: ["create"]
4.3.3.status状态分离
status
子资源的设计遵循 声明式Declarative API 原则,将用户定义的期望状态(spec
)与系统维护的实际状态(status
)分离,避免并发写入冲突。- 因为status写入太频繁了,如果status直接操作pod主资源,容易与spec的update操作发生写入冲突,所以将status分离出来是非常有必要的
- 如下图,status有独立的StatusStrategy,在更新status时,会把spec的内容恢复为和环境一致,防止status的修改影响到spec
5.API Server代码走读
- https://cncamp.notion.site/kube-apiserver-10d5695cbbb14387b60c6d622005583d
cmd/kube-apiserver/app/server.go:NewAPIServerCommand()-->
completedOptions, err := Complete(s)-->s.Etcd.WatchCacheSizes, err = serveroptions.WriteWatchCacheSizes(sizes)
Run(completedOptions, genericapiserver.SetupSignalHandler())-->CreateServerChain()-->CreateServerChain()-->CreateKubeAPIServerConfig-->buildGenericConfig(s.ServerRunOptions, proxyTransport)-->genericapiserver.NewConfig(legacyscheme.Codecs) // create codec factory for encoding/decodingcontrolplane.DefaultAPIResourceConfigSource() // group version: enabled/disabledstorageFactoryConfig.Complete(s.Etcd)completedStorageFactoryConfig.New()--> // register access path in etcd for all k8s objectsstorageFactory.AddCohabitatingResources(networking.Resource("networkpolicies"), extensions.Resource("networkpolicies"))s.Etcd.ApplyWithStorageFactoryTo(storageFactory, genericConfig)-->c.AddHealthChecks()c.RESTOptionsGetter = &StorageFactoryRestOptionsFactory{Options: *s, StorageFactory: factory}
// API Server启动过程中,认证、鉴权、准入逻辑都是如何加载的
// 认证s.Authentication.ApplyTo()--> // clientcert, serviceaccount, bootstrap token, authenticatorConfig.New()-->newWebhookTokenAuthenticator(config) // webhook
// 鉴权BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers)-->authorizationConfig.New()-->rbacAuthorizer := rbac.New()--> // if authorizer type is rbac
// 准入buildServiceResolver(s.EnableAggregatorRouting, genericConfig.LoopbackClientConfig.Host, versionedInformers)admissionConfig.New(proxyTransport, genericConfig.EgressSelector, serviceResolver)-->admission.PluginInitializer{webhookPluginInitializer, kubePluginInitializer}net.SplitHostPort(s.Etcd.StorageConfig.Transport.ServerList[0])utilwait.PollImmediate(etcdRetryInterval, etcdRetryLimit*etcdRetryInterval, preflight.EtcdConnection{ServerList: s.Etcd.StorageConfig.Transport.ServerList}.CheckEtcdServers)capabilities.Initialize() // allow privillage?config := &controlplane.Config{}createAPIExtensionsConfig()createAPIExtensionsServer()-->apiextensionsConfig.Complete().New(delegateAPIServer)-->s.AddHealthChecks(delegateCheck)
// 注册通用handlerinstallAPI(s, c.Config) // register generic api handler e.g. index, profiling, metrics, flow controlCreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer)kubeAPIServerConfig.Complete().New(delegateAPIServer)m.InstallLegacyAPI(&c, c.GenericConfig.RESTOptionsGetter, legacyRESTStorageProvider)-->m.GenericAPIServer.AddPostStartHookOrDie(controllerName, bootstrapController.PostStartHook)-->controlplane.controller.Start()-->async.NewRunner(c.RunKubernetesNamespaces, c.RunKubernetesService, repairClusterIPs.RunUntil, repairNodePorts.RunUntil)m.GenericAPIServer.AddPreShutdownHookOrDie(controllerName, bootstrapController.PreShutdownHook)
// 所有的API Group都是如何Install的
// 注册core group API handler
// API Server的watch cache时如何构建的,newEtcdStorage-newCache的时候,newWatchCache建立了缓存,实现了watch etcdm.GenericAPIServer.InstallLegacyAPIGroup() // register handler for /apirestStorageProviders := []RESTStorageProvider{appsrest.StorageProvider{}}m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...)-->
// 初始化对应group中对象的watch cacherestStorageBuilder.NewRESTStorage(apiResourceConfigSource, restOptionsGetter)--> // trigger appsrest.StorageProviderp.v1Storage(apiResourceConfigSource, restOptionsGetter)-->daemonsetstore.NewREST(restOptionsGetter)-->store.CompleteWithOptions(options)-->opts, err := options.RESTOptions.GetRESTOptions(e.DefaultQualifiedResource)--> // etcd.goret.Decorator = genericregistry.StorageWithCacher()-->cacherstorage.NewCacherFromConfig(cacherConfig)-->watchCache := newWatchCache()-->
// 注册API handlerm.GenericAPIServer.InstallAPIGroups(apiGroupsInfo...)--> // register handler for /apiss.installAPIResources(APIGroupPrefix, apiGroupInfo, openAPIModels)-->apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer)-->discovery.NewAPIVersionHandler(g.Serializer, g.GroupVersion, staticLister{apiResources})createAggregatorServer(aggregatorConfig, kubeAPIServer.GenericAPIServer, apiExtensionsServer.Informers)-->apiServices := apiServicesToRegister(delegateAPIServer, autoRegistrationController)server.PrepareRun()-->s.GenericAPIServer.PrepareRun()-->s.installHealthz()s.installLivez()s.installReadyz()prepared.Run(stopCh)-->s.runnable.Run(stopCh)--> // preparedGenericAPIServer.Run()s.NonBlockingRun(delayedStopCh)-->s.SecureServingInfo.Serve(s.Handler, s.ShutdownTimeout, internalStopCh)-->RunServer(secureServer, s.Listener, shutdownTimeout, stopCh)
6.注意事项
6.1.kubernetes集群版本升级 与对象兼容性
- 需要注意,对于一个对象的版本,kubernetes只承诺向下兼容3个版本
- 比如在1.0引入了一个v1alpha1的对象A版本,在kubernetes的后面3个版本发布时,都会做v1alpha1的对象A版本的兼容性处理,但是再后面的kubernetes版本就不保证了,
- 所以在做kubernetes版本升级的时候不要跨太多版本,而且也要对你的Operator Controller等做好调查,不要升级kubernetes集群后发现Operator操作的对象不支持了
- 社区建议升级kubernetes集群版本时不要跳过任何版本,要一个个升上去,但是实践下来很难能跟上kubernetes发布的版本,在一些集群比较多的厂商中,一般会2-3个版本升级一次。
- 但即便这样,升级时也要非常仔细的读changeLog,不能忽略任何一句话
- 之前就发生过版本升级后pod的hash计算方法变化的场景,可能会造成pod重建