如何使用 Go 调用 Kubernetes API - 类型和常用机制
关 注 微 信 公 众 号 《 云 原 生 C T O 》 更 多 云 原 生 干 货 等 你 来 探 索
专 注 于 云原生技术
分享
提 供 优 质 云原生开发
视频 技 术 培 训
面试技巧
, 及 技 术 疑 难 问 题 解答
云 原 生 技 术 分 享 不 仅 仅 局 限 于 Go
、 Rust
、 Python
、 Istio
、 containerd
、 CoreDNS
、 Envoy
、 etcd
、 Fluentd
、 Harbor
、 Helm
、 Jaeger
、 Kubernetes
、 Open Policy Agent
、 Prometheus
、 Rook
、 TiKV
、 TUF
、 Vitess
、 Argo
、 Buildpacks
、 CloudEvents
、 CNI
、 Contour
、 Cortex
、 CRI-O
、 Falco
、 Flux
、 gRPC
、 KubeEdge
、 Linkerd
、 NATS
、 Notary
、 OpenTracing
、 Operator Framework
、 SPIFFE
、 SPIRE
和 Thanos
等
如何使用 Go 调用 Kubernetes API - 类型和常用机制
官方的 Kubernetes Go
客户端加载了高级抽象 - Clientset、Informers、Cache、Scheme、Discovery
,哦!当我尝试在不先学习运行组件的情况下使用它时,我遇到了大量的新概念。这是一次不愉快的经历,但更重要的是,它降低了我在代码中做出明智决策的能力。
因此,我决定彻底了解它的组成部分,为自己解开谜团。 client-go
但是从哪里开始呢?在剖析 client-go
自身之前,了解它的两个主要依赖项 k8s.io/api
和 k8s.io/apimachinery
模块可能是一个好主意。它会简化主要任务,但这不是唯一的好处。这两个模块被排除在外是有原因的——它们不仅可以由客户端使用,还可以在服务器端或任何其他处理 Kubernetes
对象的软件中使用。
API 资源、种类和对象
首先,快速回顾一下。熟悉以下概念对于进一步讨论的成功至关重要:
-
资源类型- 由
Kubernetes API
端点提供服务的实体:pods、deployments、configmaps
等。 -
API Group
-资源类型被组织成版本化的逻辑组:apps/v1
、batch/v1
等storage.k8s.io/v1beta1
。 -
Object
- 一个资源实例 - 每个API
端点都处理某种资源类型的对象。 -
Kind
-API
返回或接受的每个对象都必须符合对象模式- 由其种类定义的特定属性组合:Pod
、Deployment
,ConfigMap
等。
区分广义上的对象和 Kubernetes
“一流”对象也很重要——持久实体,如 Pod
、 Service
或 Secret
。虽然为了序列化和反序列化,每个 API
对象都必须具有 API
版本和种类属性,但并非每个 API
对象都是 Kubernetes
对象。
模块 k8s.io/api
Go
是一种静态类型的编程语言。那么,与 Pods
、 ConfigMaps
、 Secrets
和其他的 Kubernetes
对象对应的所有结构都在哪里?对,在 .k8s.io/api
尽管命名松散,但该 k8s.io/api
模块似乎仅用于 API
类型定义。它与我们都知道的 YAML
清单非常相似
package main import ( "fmt" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" ) func main() { deployment := appsv1.Deployment{ Spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "web", Image: "nginx:1.21" }, }, }, }, }, } fmt.Printf("%#v", &deployment) }
该模块不仅定义了像上面这样的顶级 Kubernetes
对象 Deployment
,还为它们的内部属性定义了许多辅助类型:
// PodSpec is a description of a pod. type PodSpec struct { Volumes []Volume `json:"volumes,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,1,rep,name=volumes"` InitContainers []Container `json:"initContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,20,rep,name=initContainers"` Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=containers"` EphemeralContainers []EphemeralContainer `json:"ephemeralContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,34,rep,name=ephemeralContainers"` RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" protobuf:"bytes,3,opt,name=restartPolicy,casttype=RestartPolicy"` ... }
模块中定义的所有结构 k8s.io/api
都带有注释。不过要小心: jsonprotobuf
-
支持编组为
JSON
。 -
不鼓励
Protobuf
序列化- 生成的结果可能与现有的API
服务器不兼容(请参阅README
了解更多信息)。
http://github.com/kubernetes/api/tree/35d41aaac2bf55a353ccade31b852d466b2495c2#recommended-use
总结一下,这个 k8s.io/api
模块是:
-
Huge -
1000
多个描述Kubernetes API
对象的结构。 -
简单——几乎没有算法,只有“愚蠢”的数据结构。
-
有用 - 它的数据类型被客户端、服务器、控制器等使用。
模块 k8s.io/apimachinery
与范围狭窄的 k8s.io/api
模块不同,该 k8s.io/apimachinery
模块相当多。自述文件将其目的描述为:
该库是服务器和客户端使用 Kubernetes API
基础设施的共享依赖项,无需直接类型依赖项。它的第一个消费者是 k8s.io/kubernetes
, k8s.io/client-go
和 k8s.io/apiserver
。
要在一篇文章中涵盖 apimachery
模块的所有职责是很困难的。因此,我将讨论这个模块中最常见的包、类型和功能。
有用的结构和接口
虽然该 k8s.io/api
模块专注于具体的高级类型,如 Deployments
、 Secrets
或 Pods
,但 k8s.io/apimachinery
它是低级但更通用的数据结构的家园。
例如, Kubernetes
对象的所有这些常见属性: apiVersion
、 kind
、 name
、 uid
、 ownerReferences
、 creationTimestamp
等。如果我要构建自己的 Kubernetes
自定义资源,我不需要自己为这些属性定义数据类型——这要归功于 apimachinery
模块。
该 k8s.io/apimachinery/pkg/apis/meta
包定义了两个方便的结构 - TypeMeta
可以 ObjectMeta
嵌入到用户定义的结构中,使其看起来很像任何其他 Kubernetes
对象。
此外, TypeMetaandObjectMeta
结构实现 meta.Type
和 meta.Object
接口,可用于以通用方式指向任何兼容对象。
模块中定义的另一个方便的类型 apimachinery
是 interface runtime.Object
。由于其简单的定义,它可能看起来毫无用处:
// pkg/runtime type Object interface { GetObjectKind() schema.ObjectKind DeepCopyObject() Object }
但在现实中,它被大量使用! Kubernetes
代码早在 Go
获得真正的泛型支持之前就已经编写好了。所以,这 runtime.Object
很像传统的 interface{}
解决方法——它是一个通用接口,在代码库中被广泛的类型断言和类型切换。并且可以通过检查 kind
底层对象的来获得实际类型。
:bulb: runtime.Object
实例可以指向具有该 kind
属性的任何对象——成熟的 Kubernetes
对象、不携带元数据的更简单的 API
资源,或具有明确定义的对象方案的任何其他类型的对象。
:warning: 请注意,虽然看起来相似,但由于结构偏移量非零, meta.Object
因此无法安全地向下转换为相应的 Kubernetes
对象。
更有用的 apimachinery
类型:
-
PartialObjectMetadata
结构-元数据的组合。TypeMeta
和元数据。·ObjectMeta·作为一种通用的方法来表示任何具有元数据的对象。 -
APIVersions
,APIGroupList
,APIGroup
结构体-还记得kubectl get
的API
探索练习吗-原始/API ?
这些和类似的结构用于Kubernetes API
资源的类型,但不是Kubernetes
对象(例如,它们有kind
和apiVersion
属性,但没有真正的Object
元数据)。 -
GetOptions
,ListOptions
,UpdateOptions
等等——这些结构体代表了客户端对资源的相应动作的参数。 -
GroupKind
、GroupVersionKind
、GroupResource
、GroupVersionResource
等——简单的数据传输对象——包含组、版本、类型或资源字符串的元组。
:bulb:在 Scheme
和 RESTMapperGroupVersionKind
讨论之前请记住——他们的知识会派上用场。 GroupVersionResource
非结构化结构
是的,你没听错。撇开玩笑不谈,它是另一种重要且广泛使用的数据类型。
使用具体类型处理 Kubernetes
对象 k8s.io/api
很方便,但如果:
-
您需要以通用方式使用
Kubernetes
对象吗? -
您不想或不能依赖该
api
模块? -
api
您需要使用模块中未定义的自定义资源吗?
非结构化。用于救援的非结构化结构!这个结构体允许没有注册 Go
结构体的对象被操作为通用的 json
类对象:
type Unstructured struct { // Object is a JSON compatible map with // string, float, int, bool, []interface{}, or // map[string]interface{} children. Object map[string]interface{} } // And for the list of objects you can // use the UnstructuredList struct. type UnstructuredList struct { Object map[string]interface{} Items []Unstructured }
这两个结构只是 map[string]interface{}
. 但是,它们带有一堆方便的方法来简化嵌套属性访问和 JSON
编组/解组。
如何在 Go 代码中使用非结构化 Kubernetes 对象: http://github.com/iximiuz/client-go-examples/blob/5b220c4572d65ea8bf0ad68e369e015902e7521c/crud-dynamic-simple/main.go#L36
类型转换 - 非结构化到类型化,反之亦然
自然地,可能需要将非结构化对象转换为具体 k8s.io/api
类型的结构(反之亦然)。该 runtime.UnstructuredConverter
接口及其默认实现 DefaultUnstructuredConverter
可以帮助您:
type UnstructuredConverter interface { ToUnstructured(obj interface{}) (map[string]interface{}, error) FromUnstructured(u map[string]interface{}, obj interface{}) error }
如何将非结构化对象转换为类型化对象并返回: http://github.com/iximiuz/client-go-examples/tree/main/convert-unstructured-typed
对象序列化为 JSON、YAML 或 Protobuf
使用静态类型语言的 API
时,另一个乏味的任务是将数据结构编组和解组到它们的有线表示中和从它们的有线表示中解组。
大量 apimachinery
代码专门用于此任务:
// pkg/runtime // Encoder writes objects to a serialized form type Encoder interface { Encode(obj Object, w io.Writer) error Identifier() Identifier } // Decoder attempts to load an object from data. type Decoder interface { Decode( data []byte, defaults *schema.GroupVersionKind, into Object ) (Object, *schema.GroupVersionKind, error) } type Serializer interface { Encoder Decoder }
注意到上面的代码片段中的这些对象了吗?是的,这些是运行时。对象,也就是 Kind-able
接口{}实例。
例子:
如何将类型化的 Kubernetes
对象序列化为 JSON
http://github.com/iximiuz/client-go-examples/tree/main/serialize-typed-json
如何将类型化的 Kubernetes
对象序列化为 YAML
http://github.com/iximiuz/client-go-examples/tree/main/serialize-typed-yaml
如何将非结构化 Kubernetes
对象序列化为 JSON
http://github.com/iximiuz/client-go-examples/tree/main/serialize-unstructured-json
如何将非结构化 Kubernetes
对象序列化为 YAML
http://github.com/iximiuz/client-go-examples/tree/main/serialize-unstructured-yaml
Scheme 和 RESTMapper
运行时。在使用 client-go
时,方案概念随处出现,特别是在编写处理自定义资源的控制器(或 operator
)时。
我花了一段时间才明白它的目的。然而,按照正确的顺序处理事情会有所帮助。
想想非结构化到类型化转换的潜在实现:有一个类似 JSON
的对象, k8s.io/api
需要从它创建一些具体类型的对应对象。第一步可能是弄清楚如何使用 kind string
创建类型化对象的空实例。
一种天真的方法可能看起来像是 switch
对所有可能类型(实际上是 API
组)的巨大声明:
import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" ) func New(apiVersion, kind string) runtime.Object { switch (apiVersion + "/" + kind) { case: "v1/Pod": return &corev1.Pod{} case: "apps/v1/Deployment": return &appsv1.Deployment{} } ... }
更聪明的方法是使用反射。可以为所有已注册的类型维护 switcha
,而不是: map[string]reflect.Type
type Registry struct { map[string]reflect.Type types } func (r *Registry) Register(apiVersion, kind string, typ reflect.Type) { r.types[apiVersion + "/" + kind] = typ } func (r *Registry) New(apiVersion, kind string) runtime.Object { return r.types[apiVersion + "/" + kind].New().(runtime.Object) }
这种方法的优点是它不需要代码生成,并且可以在运行时添加新的类型映射。
现在,考虑一个反序列化问题:需要将一段 YAML
或 JSON
转换为 Typed object
。第一步 - 对象创建 - 将非常相似。
事实证明,通过 API
组和种类创建空对象是一项如此频繁的任务,以至于它在模块中拥有了自己的组件 apimachinery- runtime.Scheme
:
// Scheme defines methods for serializing and deserializing API objects, a type // registry for converting group, version, and kind information to and from Go // schemas, and mappings between Go schemas of different versions. type Scheme struct { gvkToType map[schema.GroupVersionKind]reflect.Type typeToGVK map[reflect.Type][]schema.GroupVersionKind unversionedTypes map[reflect.Type]schema.GroupVersionKind unversionedKinds map[string]reflect.Type ... }
runtime.Schemestruct
就是这样一个注册表,其中包含各种 Kubernetes
对象的类型到类型和类型到类型的映射。
请记住, GroupVersionKind
它只是一个元组,即一个 DTO
结构,对吗?:wink:
该 runtime.Scheme
结构实际上非常强大 - 它有一大堆方法并实现了一些基础接口,例如:
// ObjectTyper contains methods for extracting // the APIVersion and Kind of objects. type ObjectTyper interface { ObjectKinds(runtime.Object) ([]schema.GroupVersionKind, bool, error) Recognizes(gvk schema.GroupVersionKind) bool } // ObjectCreater contains methods for instantiating // an object by kind and version. type ObjectCreater interface { New(kind schema.GroupVersionKind) (out Object, err error) }
不过, runtime.Scheme
也不是万能的。它有从 kind
到 types
的映射,但如果不是 kind
,只知道资源名称怎么办?
这就是 RESTMapper
开始的地方:
type RESTMapper interface { // KindFor takes a partial resource and returns the single match. Returns an error if there are multiple matches KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) // KindsFor takes a partial resource and returns the list of potential kinds in priority order KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) ... ResourceSingularizer(resource string) (singular string, err error) }
这 RESTMapper
也是某种注册表。但是,它维护资源到种类的映射。因此,向映射器提供一个类似的字符串 apps/v1/deployments
会给出 API Groupapps/v1
和 kind Deployment
。 RESTMapper
也可以处理资源快捷方式和单数化: po, pod,
并且 pods
可以注册为同一资源的别名。
通常情况下,会有一个全局的单例运行时。然而,似乎 apimachery
模块本身试图避免状态——它定义了 RESTMapper
和 Scheme
结构,但没有实例化它们。
与运行时。该方案被 apimachery
模块本身广泛使用, RESTMapper
在内部没有使用,至少目前没有。
字段和标签选择器
字段和标签的类型、创建和匹配逻辑也存在于 apimachinery
模块中。例如,以下是可以使用 k8s.io/apimachinery/pkg/labels
包执行的操作:
lbl := labels.Set{"foo": "bar"} sel, _ = labels.Parse("foo==bar") if sel.Matches(lbl) { fmt.Printf("Selector %v matched label set %v\n", sel, lbl) }
:book:例子:
如何使用标签集和标签选择器
http://github.com/iximiuz/client-go-examples/tree/main/label-selectors
如何使用字段集和字段选择器
http://github.com/iximiuz/client-go-examples/tree/main/field-selectors
API 错误处理
如果不正确处理其错误,就不可能在代码中使用 Kubernetes API
。 API
服务器可能完全消失,请求可能未经授权,对象可能丢失,并发更新可能发生冲突。幸运的是,该 k8s.io/apimachinery/pkg/api/errors
包定义了一些方便的实用程序函数来处理 API
错误。这是一个例子:
_, err = client. CoreV1(). ConfigMaps("default"). Get( context.Background(), "this_name_definitely_does_not_exist", metav1.GetOptions{}, ) if !errors.IsNotFound(err) { panic(err.Error()) }
:book:示例-如何处理 Kubernetes API
错误。http://github.com/iximiuz/client-go-examples/tree/main/error-handling
util实用程序
最后但并非最不重要的一点是, apimachinery/pkg/util
包装中充满了有用的东西。这里有些例子:
-
util/wait
包通过重试和适当的退避/抖动实现来简化等待资源出现或消失的任务。 -
util/yaml
有助于解组YAML
或将其转换为JSON
。
总结
k8s.io/api
和 k8s.io/apimachinery
包是学习如何在 Go
中使用 Kubernetes
对象的一个很好的起点。如果你需要编写你的第一个控制器,直接跳到 client-go
,甚至是控制器运行时或 kubebuilder
可能会使学习体验过于粗糙——可能存在太多的知识空白。但是,先看看和玩一下 api
和 apimachinery
包将帮助您在接下来的旅程中保持安心
参考地址 [1]
参考资料
参考地址: http://iximiuz.com/en/posts/kubernetes-api-go-types-and-common-machinery/
- 云原生下一步的发展方向是什么?
- 用更云原生的方式做诊断|大规模 K8s 集群诊断利器深度解析
- 多个维度分析k8s多集群管理工具,到底哪个才真正适合你
- 使用 Kube-capacity CLI 查看 Kubernetes 资源请求、限制和利用率
- 使用 Go 在 Kubernetes 中构建自己的准入控制器
- 云原生数仓如何破解大规模集群的关联查询性能问题?
- 云原生趋势下的迁移与灾备思考
- 2022 年不容错过的六大云原生趋势!
- 使用 Prometheus 监控 Golang 应用程序
- 云原生时代下的机遇与挑战 DevOps如何破局
- 如何在云原生格局中理解Kubernetes合规性和安全框架
- 设计云原生应用程序的15条基本原则
- 使用 Operator SDK 为 Pod 标签编写Controller
- Kubernetes Visitor 模式
- 为什么云原生是第二次云革命
- 构建云原生安全的六个重要能力
- 扩展云原生策略的步骤有哪些?
- 七个值得关注的开源云原生工具
- K8S - 创建一个 kube-scheduler 插件
- 如何诊断 Kubernetes 应用程序中的 OOMKilled 错误