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

介绍 client-go 第 6 版

Kubernetes API server 暴露了一个 REST 接口,任何客户端都可以使用。client-go 是 Go 编程语言的官方客户端库。它既被 Kubernetes 本身在内部使用(例如,在 kubectl 中),也被众多外部使用者使用:诸如 etcd-operatorprometheus-operator 这样的 Operator;诸如 KubeLessOpenShift 这样的更高级框架;以及更多其他使用者。

client-go 的第 6 版更新增加了对 Kubernetes 1.9 的支持,允许访问最新的 Kubernetes 功能。虽然变更日志包含了所有细节,但这篇博文重点介绍了最显著的变化,旨在指导如何从第 5 版升级。

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

API 组的变化

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

  • 工作负载对象(Deployments、DaemonSets、ReplicaSets 和 StatefulSets)在 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) 的预持久化 schema 验证作为一个 Alpha 功能。在 1.9 中,该功能已晋升到 Beta 阶段,并将默认启用。作为 client-go 用户,你将在 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1 找到 API 类型。

OpenAPI v3 schema 可以在 CRD 规范中定义为


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 中的 schema 对实例应用以下验证

  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 现在允许你调整 ListOptions,用于查询 API server 以列出和监听对象。通过将 IncludeUnitialized 设置为 true,可以使未初始化对象(供初始化器使用)可见。所有这些都可以使用共享 Informer 的新构造函数 NewFilteredSharedInformerFactory 完成


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。

多态伸缩客户端

历史上,只有 extensions API 组中的类型可以使用自动生成的 Scale 客户端。此外,不同的 API 组为其 /scale 子资源使用不同的 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")

返回的 scale 对象是泛型的,并作为 autoscaling/v1.Scale 对象暴露。它由一个内部 Scale 类型支持,并定义了与支持伸缩的 API 组中所有特殊 Scale 类型之间的相互转换。我们计划在 1.10 版本中将此功能扩展到 CustomResources

如果你正在实现对 scale 子资源的支持,我们建议你暴露 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 的 dynamic client 访问 CustomResources,它已被使用 k8s.io/code-generator 中的生成器生成的类型安全代码取代。请查看Open Shift 博客上的深度解析,了解如何在 client-go 中使用代码生成。

注释块

现在可以将标签放置在类型或函数正上方的注释块中,或在其上方的第二个块中。这两个注释块之间不再有区分。这曾经是使用生成器时出现微妙错误的来源

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

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

自定义客户端方法

现在可以使用扩展标签定义创建自定义动词(verb)。这让你能够扩展超出 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 组中,Kinds、组名、Go 包名和 Go 组别名可能存在冲突。在 1.9 版本之前,这种情况未能正确处理。以下标签可以解决命名冲突,并使生成的代码更美观(通常位于 API 包的 doc.go 文件中)

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

第一个用作 CustomResource 的组名,以便在使用 HTTP 与 API server 进行 RESTful 通信时使用。第二个用于生成的 Golang 代码中(例如,在 clientset 中),以访问组版本

clientset.SecondExampleV1()

现在 Go 包名中终于可以使用点号(.)。在本节的示例中,你会将 groupName 代码片段放到项目中的 pkg/apis/example2.example.com 目录中。

示例项目

Kubernetes 1.9 包含一些示例项目,它们可以作为你自己的项目的蓝图

Vendoring

为了从之前的第 5 版更新到第 6 版的 client-go,库本身以及某些第三方依赖都需要更新。此前,这个过程一直很繁琐,因为在不同版本中,现有包布局中的许多代码被重构或重新定位了。幸运的是,最新版本需要移动的代码少得多,这应该会简化大多数用户的升级过程。

已发布仓库的状态

过去 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 和其他库的早期采用者使用。此外,它们有助于 vendoring 正确版本的 k8s.io/apik8s.io/apimachinery。请注意,我们只在 k8s.io/client-go 上创建一个类似 v6.0.3 的语义版本控制标签。k8s.io/apik8s.io/apimachinery 对应的标签是 kubernetes-1.9.3。

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

client-go 的 Vendoring 状态

通常,要 vendoring 哪些依赖的列表是自动生成的,并写入到文件 Godeps/Godeps.json 中。只有其中列出的修订版本经过测试。这尤其意味着我们目前没有且无法针对依赖的 master 分支测试代码库。根据使用的 vendoring 工具,我们面临以下情况

  • godep 通过运行 `godep restore` 从你的 GOPATH 中的 k8s.io/client-go 读取 Godeps/Godeps.json。然后使用 `godep save` 在你的项目中进行 vendoring。godep 将从你的 GOPATH 中选择正确的版本。
  • glide 会从其依赖项(包括 k8s.io/client-go)自动读取 Godeps/Godeps.json,无论是在初始化还是更新时。因此,只要没有冲突,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 版,并希望通过不跳过版本来确保安全,那么现在是查看由 Heptio 的朋友们撰写的这篇优秀博文的好时机,该博文描述了如何升级到第 5 版。)

在开始之前,务必理解 client-go 依赖于另外两个 Kubernetes 项目:k8s.io/apimachineryk8s.io/api。此外,如果你正在使用 CRD,可能还需要依赖 k8s.io/apiextensions-apiserver 作为 CRD 客户端。第一个暴露较低级别的 API 机制(例如,scheme、序列化和类型转换),第二个包含 API 定义,第三个提供与 CustomResourceDefinitions 相关的 API。为了使 client-go 正常工作,需要 vendoring 其对应的配套库的匹配版本。每个库仓库都提供一个名为 release-<version> 的分支,其中 <version> 指代特定的 Kubernetes 版本;对于 client-go 第 6 版,必须引用每个仓库上的 release-1.9 分支。

假设通过 dep vendoring 了 client-go 最新第 5 版补丁发布版本,Gopkg.toml Manifest 文件应如下所示(可能使用分支而非版本):






[[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"

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






[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"

升级到 client-go 第 6 版意味着如下所示地提升版本和标签标识符(加粗显示):

升级结果可在此处找到。

如果你正在使用 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 带来的所有炫酷新功能了。