本文已发布一年多。旧文章可能包含过时的内容。请检查页面中的信息自发布以来是否已不正确。

client-go 版本 6 介绍

Kubernetes API 服务器 公开了一个 REST 接口,可供任何客户端使用。client-go 是 Go 编程语言的官方客户端库。它在 Kubernetes 内部(例如,在 kubectl 内部)以及 众多外部使用者 中使用:诸如 etcd-operatorprometheus-operator 之类的操作符;诸如 KubeLessOpenShift 之类的高级框架;以及更多。

client-go 的第 6 版更新增加了对 Kubernetes 1.9 的支持,从而可以访问最新的 Kubernetes 功能。虽然 变更日志 包含了所有详细信息,但此博客文章重点介绍了最突出的更改,并旨在指导如何从版本 5 升级。

此博客文章是为使第三方使用者更容易访问 client-go 而进行的众多努力之一。更容易的访问是来自众多公司的许多人共同努力的结果,他们都在 Kubernetes Slack 的 #client-go-docs 频道中见面。我们很高兴听到反馈和进一步改进的想法,当然也感谢任何想要贡献的人。

API 组变更

以下 API 组升级是 Kubernetes 1.9 的一部分

  • 工作负载对象(部署、守护程序集、副本集和有状态集)已在 Kubernetes 1.9 中 提升到 apps/v1 API 组。client-go 遵循此转换,并允许开发人员通过导入 k8s.io/api/apps/v1 包而不是 k8s.io/api/apps/v1beta1 并使用 Clientset.AppsV1() 来使用最新版本。
  • 准入 Webhook 注册已在 Kubernetes 1.9 中提升到 admissionregistration.k8s.io/v1beta1 API 组。以前的 ExternalAdmissionHookConfiguration 类型已被不兼容的 ValidatingWebhookConfiguration 和 MutatingWebhookConfiguration 类型取代。此外,admission.k8s.io 中的 webhook 准入有效负载类型 AdmissionReview 已提升为 v1beta1。请注意,版本化对象现在会传递给 Webhook。有关详细信息,请参阅准入 Webhook 文档

CustomResources 的验证

在 Kubernetes 1.8 中,我们引入了 CustomResourceDefinitions (CRD) 持久化前模式验证 作为 Alpha 功能。在 1.9 中,该功能已升级为 Beta 版,并且默认情况下将启用。作为 client-go 用户,您将在 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1 中找到 API 类型。

可以在 CRD 规范中将 OpenAPI v3 模式定义为


apiVersion: apiextensions.k8s.io/v1beta1  
kind: CustomResourceDefinition  
metadata: ...  
spec:  
  ...  
  validation:  
    openAPIV3Schema:  
      properties:  
        spec:  
          properties:  
            version:  
                type: string  
                enum:  
                - "v1.0.0"  
                - "v1.0.1"  
            replicas:  
                type: integer  
                minimum: 1  
                maximum: 10

上述 CRD 中的模式对实例应用以下验证

  1. spec.version 必须是字符串,并且必须是“v1.0.0”或“v1.0.1”。
  2. spec.replicas 必须是整数,并且最小值必须为 1,最大值必须为 10。spec.version (v1.0.2) 和 spec.replicas (15) 的值无效的 CustomResource 将被拒绝

apiVersion: mygroup.example.com/v1  
kind: App  
metadata:  
  name: example-app  
spec:  
  version: "v1.0.2"  
  replicas: 15
$ kubectl create -f app.yaml

The App "example-app" is invalid: []: Invalid value: map[string]interface {}{"apiVersion":"mygroup.example.com/v1", "kind":"App", "metadata":map[string]interface {}{"creationTimestamp":"2017-08-31T20:52:54Z", "uid":"5c674651-8e8e-11e7-86ad-f0761cb232d1", "clusterName":"", "name":"example-app", "namespace":"default", "deletionTimestamp":interface {}(nil), "deletionGracePeriodSeconds":(\*int64)(nil)}, "spec":map[string]interface {}{"replicas":15, "version":"v1.0.2"}}:
validation failure list:  
spec.replicas in body should be less than or equal to 10  
spec.version in body should be one of [v1.0.0 v1.0.1]

请注意,借助 准入 Webhook,Kubernetes 1.9 提供了另一个 Beta 功能来在创建或更新对象之前对其进行验证。从 1.9 开始,这些 Webhook 还允许更改对象(例如,设置默认值或注入值)。当然,Webhook 也适用于 CRD。此外,Webhook 可用于实现 CRD 验证不易表达的验证。请注意,Webhook 比 CRD 验证更难实现,因此对于许多用途,CRD 验证是正确的工具。

创建命名空间 Informer

通常,要在控制器中处理一个命名空间中的对象或仅带有某些标签的对象。Informer 现在允许您调整用于查询 API 服务器以列出和监视对象的 ListOptions。可以通过将 IncludeUnitialized 设置为 true 来使未初始化的对象可见(供 初始化器 使用)。所有这些都可以使用新的 NewFilteredSharedInformerFactory 构造函数为共享 Informer 完成


import “k8s.io/client-go/informers”
...  
sharedInformers := informers.NewFilteredSharedInformerFactory(  
 client,  
 30\*time.Minute,   
 “some-namespace”,  
 func(opt \*metav1.ListOptions) {  
  opt.LabelSelector = “foo=bar”  
 },  
)  

请注意,相应的 Lister 将仅知道与命名空间和给定的 ListOptions 匹配的对象。请注意,相同的限制适用于客户端上的 List 或 Watch 调用。

cert-manager 的此生产代码示例演示了如何在实际代码中使用命名空间 Informer。

多态缩放客户端

从历史上看,只有扩展 API 组中的类型才能使用自动生成的缩放客户端。此外,不同的 API 组对其 /scale 子资源使用不同的缩放类型。为了解决这些问题,k8s.io/client-go/scale 提供了一个多态缩放客户端,以一致的方式缩放不同 API 组中的不同资源


import (


apimeta "k8s.io/apimachinery/pkg/api/meta"

 discocache "k8s.io/client-go/discovery/cached"  
 "k8s.io/client-go/discovery"

"k8s.io/client-go/dynamic"

“k8s.io/client-go/scale”  
)

...

cachedDiscovery := discocache.NewMemCacheClient(client.Discovery())  
restMapper := discovery.NewDeferredDiscoveryRESTMapper(

cachedDiscovery,

apimeta.InterfacesForUnstructured,

)  
scaleKindResolver := scale.NewDiscoveryScaleKindResolver(

client.Discovery(),

)  
scaleClient, err := scale.NewForConfig(

client, restMapper,

dynamic.LegacyAPIPathResolverFunc,

scaleKindResolver,

)
scale, err := scaleClient.Scales("default").Get(groupResource, "foo")

返回的缩放对象是通用的,并且作为 autoscaling/v1.Scale 对象公开。它由内部缩放类型支持,并定义了到所有支持缩放的 API 组中的特殊缩放类型的转换。我们计划在 1.10 中将其扩展到 CustomResources

如果您正在实现对缩放子资源的支持,我们建议您公开 autoscaling/v1.Scale 对象。

类型安全的 DeepCopy

深度复制对象以前需要调用 Scheme.Copy(Object),其明显的缺点是会丢失类型安全性。client-go 版本 5 中的典型代码需要进行类型转换


newObj, err := runtime.NewScheme().Copy(node)


if err != nil {

    return fmt.Errorf("failed to copy node %v: %s”, node, err)

}


newNode, ok := newObj.(\*v1.Node)

if !ok {

    return fmt.Errorf("failed to type-assert node %v", newObj)


}

感谢 k8s.io/code-generator,Copy 现在已被驻留在每个对象上的类型安全 DeepCopy 方法替换,使您可以在数量和 API 错误方面显着简化代码

newNode := node.DeepCopy()

不需要错误处理:此调用永远不会失败。仅当 node 为 nil 时,DeepCopy() 才返回 nil。

要复制 runtime.Objects,runtime.Object 接口中还有一个额外的 DeepCopyObject() 方法。

随着旧方法的消失,客户端需要相应地更新其复制调用。

代码生成和 CustomResources

不建议使用 client-go 的动态客户端访问 CustomResources,而是使用 k8s.io/code-generator 中的生成器使用类型安全的代码来取代。查看 Open Shift 博客上的深入探讨,了解如何将代码生成与 client-go 结合使用。

注释块

现在,您可以将标记放在类型或函数正上方的注释块中,也可以放在第二个块中。这两个注释块之间不再有区别。这曾经是使用生成器时产生 细微错误 的根源

// second block above  
// +k8s:some-tag  

// first block above  
// +k8s:another-tag  
type Foo struct {}

自定义客户端方法

现在,您可以使用扩展的标记定义来创建自定义动词。这使您可以扩展到 HTTP 定义的动词之外。这为更高层次的自定义打开了大门。

例如,此代码块将导致生成方法 UpdateScale(s *autoscaling.Scale) (*autoscaling.Scale, error)

// genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/kubernetes/pkg/apis/autoscaling.Scale,result=k8s.io/kubernetes/pkg/apis/autoscaling.Scale

解决 Golang 命名冲突

在更复杂的 API 组中,Kind、组名称、Go 包名称和 Go 组别名名称可能会冲突。在 1.9 之前,未正确处理此问题。以下标记解决了命名冲突并使生成的代码更漂亮

// +groupName=example2.example.com  
// +groupGoName=SecondExample

它们通常位于 API 包的 doc.go 文件中。第一个用作使用 HTTP 以 RESTful 方式与 API 服务器通信时的 CustomResource 组名称。第二个在生成的 Golang 代码(例如,在客户端集中)中使用,以访问组版本

clientset.SecondExampleV1()

最后可以在 Go 包名称中使用点。在本节的示例中,您会将 groupName 代码段放入项目的 pkg/apis/example2.example.com 目录中。

示例项目

Kubernetes 1.9 包含许多示例项目,这些项目可以作为您自己项目的蓝图

  • k8s.io/sample-apiserver 是一个简单的用户提供的 API 服务器,它通过 API 聚合集成到集群中。
  • k8s.io/sample-controller 是一个功能齐全的 控制器 (也称为 operator),它具有共享的 Informer 和一个工作队列,用于处理创建、更改或删除的对象。它基于 CustomResourceDefinitions,并使用 k8s.io/code-generator 来生成深拷贝函数、类型化的 clientset、informer 和 lister。

依赖管理 (Vendoring)

为了从之前的 client-go 版本 5 更新到版本 6,必须更新库本身以及某些第三方依赖项。以前,由于大量代码在现有包布局中跨版本进行了重构或重新定位,这个过程很繁琐。幸运的是,在最新版本中,需要移动的代码要少得多,这应该可以简化大多数用户的升级过程。

已发布仓库的状态

过去,k8s.io/client-gok8s.io/apik8s.io/apimachinery 的更新频率不高。标签(例如 v4.0.0)是在 Kubernetes 发布后一段时间才创建的。在 1.9 版本中,我们恢复运行一个夜间机器人,该机器人会更新所有仓库以供公开使用,甚至在手动标记之前。这包括以下分支

  • master
  • release-1.8 / release-5.0
  • release-1.9 / release-6.0 Kubernetes 标签(例如 v1.9.1-beta1)也会自动应用于已发布的仓库,前缀为 kubernetes-(例如 kubernetes-1.9.1-beta1)。

这些标签的测试覆盖率有限,但可以被 client-go 和其他库的早期采用者使用。此外,它们有助于供应商化正确版本的 k8s.io/apik8s.io/apimachinery。请注意,我们只在 k8s.io/client-go 上创建一个类似 v6.0.3 的语义版本控制标签。 k8s.io/api 和 k8s.io/apimachinery 的对应标签是 kubernetes-1.9.3。

另请注意,只有这些标签对应于经过测试的 Kubernetes 版本。如果您的依赖项是发布分支,例如 release-1.9,则您的客户端正在运行未发布的 Kubernetes 代码。

client-go 的依赖管理状态

通常,要 vendor 的依赖项列表会自动生成并写入文件 Godeps/Godeps.json。只有其中列出的修订版本经过测试。这尤其意味着我们不会也不能针对依赖项的 master 分支测试代码库。这使我们处于以下情况,具体取决于所使用的依赖管理工具

  • godep 通过在您的 GOPATH 中从 k8s.io/client-go 运行 godep restore 来读取 Godeps/Godeps.json。然后使用 godep save 在您的项目中进行 vendor。 godep 将从您的 GOPATH 中选择正确的版本。
  • glide 会从其依赖项(包括 k8s.io/client-go)中自动读取 Godeps/Godeps.json,无论是在 init 还是在 update 时。因此,只要没有冲突,glide 应该大部分是自动的。
  • dep 目前没有以一致的方式遵守 Godeps/Godeps.json,尤其是在更新时。手动将 client-go 依赖项指定为约束或覆盖非常重要,对于非 k8s.io/* 依赖项也是如此。如果没有这些,dep 只会选择依赖项的 master 分支,这可能会导致问题,因为它们会频繁更新。
  • Kubernetes 和 golang/dep 社区都知道这些问题 [issue #1124, issue #1236],并且 正在共同努力寻找解决方案。在此之前,必须特别小心。有关更多详细信息,请参阅 client-go 的 INSTALL.md

更新依赖项 – golang/dep

即使 golang/dep 今天存在缺陷,dep 正在慢慢成为 Go 生态系统中的事实标准。通过必要的注意和对缺失功能的了解,dep 可以(并且正在!)成功使用。以下演示如何使用 dep 将具有 client-go 5 的项目更新到最新版本 6

(如果您仍在运行 client-go 版本 4 并且希望安全起见不跳过任何版本,那么现在是查看 这篇出色的博客文章 的好时机,它介绍了如何升级到版本 5,由我们的 Heptio 的朋友们共同撰写。)

在开始之前,重要的是要了解 client-go 依赖于其他两个 Kubernetes 项目:k8s.io/apimachineryk8s.io/api。此外,如果您正在使用 CRD,您可能还依赖于 k8s.io/apiextensions-apiserver 来获取 CRD 客户端。第一个公开了较低级别的 API 机制(例如模式、序列化和类型转换),第二个保存了 API 定义,第三个提供了与 CustomResourceDefinitions 相关的 API。为了使 client-go 正常运行,它需要将其配套库以相应的匹配版本进行 vendor。每个库存储库都提供一个名为 release-<version> 的分支,其中<version> 指的是特定的 Kubernetes 版本;对于 client-go 版本 6,必须引用每个存储库上的 release-1.9 分支。

假设通过 dep vendor 的 client-go 的最新版本 5 补丁版本,Gopkg.toml 清单文件应如下所示(可能使用分支而不是版本)






[[constraint]]


  name = "k8s.io/api"

  version = "kubernetes-1.8.1"


[[constraint]]

  name = "k8s.io/apimachinery"

  version = "kubernetes-1.8.1"


[[constraint]]

  name = "k8s.io/apiextensions-apiserver"

  version = "kubernetes-1.8.1"


[[constraint]]

  name = "k8s.io/client-go"




  version = "5.0.1"

请注意,如果客户端实际上不需要某些库,则这些库可能会缺失。

升级到 client-go 版本 6 意味着按如下方式增加版本和标签标识符(强调显示)






[constraint]]


  name = "k8s.io/api"

  version = "kubernetes-1.9.0"


[[constraint]]

  name = "k8s.io/apimachinery"

  version = "kubernetes-1.9.0"


[[constraint]]

  name = "k8s.io/apiextensions-apiserver"

  version = "kubernetes-1.9.0"


[[constraint]]

  name = "k8s.io/client-go"




  version = "6.0.0"

升级的结果可以在 这里 找到。

注意:如上所述,dep 无法以可靠和可重现的方式捕获完整的依赖项集。这意味着,对于 100% 面向未来的项目,您必须向 client-go 的 Godeps/Godeps.json 中列出的许多其他包添加约束(甚至覆盖)。如果出现问题,请准备好添加它们。我们正在与 golang/dep 社区合作,以使此过程更轻松、更顺畅。

最后,我们需要通过执行 dep ensure 来告诉 dep 升级到指定的版本。如果一切顺利,命令调用的输出应该为空,唯一的指示表明它成功的是 vendor 文件夹内一些已更新的文件。

如果您正在使用 CRD,您可能还会使用代码生成。以下 Gopkg.toml 块会将所需的代码生成包添加到您的项目中


required = [  
  "k8s.io/code-generator/cmd/client-gen",  
  "k8s.io/code-generator/cmd/conversion-gen",  
  "k8s.io/code-generator/cmd/deepcopy-gen",  
  "k8s.io/code-generator/cmd/defaulter-gen",  
  "k8s.io/code-generator/cmd/informer-gen",  
  "k8s.io/code-generator/cmd/lister-gen",  
]


[[constraint]]

  branch = "kubernetes-1.9.0"


  name = "k8s.io/code-generator"

您是否还希望通过 dep 修剪不需要的包(例如测试文件)或将更改提交到 VCS 取决于您自己 -- 但从升级的角度来看,您现在应该准备好利用 Kubernetes 1.9 通过 client-go 带来的所有精美新功能。