Introducing client-go version 6
The Kubernetes API server numerous external consumers:operators like the
The version 6 update to client-go adds support for Kubernetes 1.9, allowing access to the latest Kubernetes features. While the
This blog post is one of a number of efforts to make client-go more accessible to third party consumers. Easier access is a joint effort by a number of people from numerous companies, all meeting in the #client-go-docs channel of the
API group changes
The following API group promotions are part of Kubernetes 1.9:
- Workload objects (Deployments, DaemonSets, ReplicaSets, and StatefulSets) have been promoted to the apps/v1 API group in Kubernetes 1.9. client-go follows this transition and allows developers to use the latest version by importing the k8s.io/api/apps/v1 package instead of k8s.io/api/apps/v1beta1 and by using Clientset.AppsV1().
- Admission Webhook Registration has been promoted to the admissionregistration.k8s.io/v1beta1 API group in Kubernetes 1.9. The former ExternalAdmissionHookConfiguration type has been replaced by the incompatible ValidatingWebhookConfiguration and MutatingWebhookConfiguration types. Moreover, the webhook admission payload type AdmissionReview in admission.k8s.io has been promoted to v1beta1. Note that versioned objects are now passed to webhooks. Refer to the admission webhook documentation for details.
Validation for CustomResources
In Kubernetes 1.8 we introduced CustomResourceDefinitions (CRD) pre-persistence schema validation as an alpha feature. With 1.9, the feature got promoted to beta and will be enabled by default. As a client-go user, you will find the API types at k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1.
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
The schema in the above CRD applies following validations for the instance:
- spec.version must be a string and must be either “v1.0.0” or “v1.0.1”.
- spec.replicas must be an integer and must have a minimum value of 1 and a maximum value of 10. A CustomResource with invalid values for spec.version (v1.0.2) and spec.replicas (15) will be rejected:
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]
Note that with Admission Webhooks, Kubernetes 1.9 provides another beta feature to validate objects before they are created or updated. Starting with 1.9, these webhooks also allow mutation of objects (for example, to set defaults or to inject values). Of course, webhooks work with CRDs as well. Moreover, webhooks can be used to implement validations that are not easily expressible with CRD validation. Note that webhooks are harder to implement than CRD validation, so for many purposes, CRD validation is the right tool.
Creating namespaced informers
Often objects in one namespace or only with certain labels are to be processed in a controller. Informers
import “k8s.io/client-go/informers”
...
sharedInformers := informers.NewFilteredSharedInformerFactory(
client,
30\*time.Minute,
“some-namespace”,
func(opt \*metav1.ListOptions) {
opt.LabelSelector = “foo=bar”
},
)
Polymorphic scale client
Historically, only types in the extensions API group would work with autogenerated Scale clients. Furthermore, different API groups use different Scale types for their /scale subresources. To remedy these issues, k8s.io/client-go/scale provides a
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")
The returned scale object is generic and is exposed as the autoscaling/v1.Scale object. It is backed by an internal Scale type, with conversions defined to and from all the special Scale types in the API groups supporting scaling. We planto .
If you’re implementing support for the scale subresource, we recommend that you expose the autoscaling/v1.Scale object.
Type-safe DeepCopy
Deeply copying an object formerly required a call to Scheme.Copy(Object) with the notable disadvantage of losing type safety. A typical piece of code from client-go version 5 required type casting:
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)
}
No error handling is necessary: this call never fails. If and only if the node is nil does DeepCopy() return nil.
To copy runtime.Objects there is an additional DeepCopyObject() method in the runtime.Object interface.
With the old method gone for good, clients need to update their copy invocations accordingly.
Code generation and CustomResources
Using client-go’s dynamic client to access CustomResources is discouraged and superseded by type-safe code using the generators in
Comment Blocks
You can now place tags in the comment block just above a type or function, or in the second block above. There is no distinction anymore between these two comment blocks. This used to a be a source of :
// second block above
// +k8s:some-tag
// first block above
// +k8s:another-tag
type Foo struct {}
Custom Client Methods
You can now use extended tag definitions to create custom verbs . This lets you expand beyond the verbs defined by HTTP. This opens the door to higher levels of customization.
For example, this block leads to the generation of the method 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
Resolving Golang Naming Conflicts
In more complex API groups it’s possible for Kinds, the group name, the Go package name, and the Go group alias name to conflict. This was not handled correctly prior to 1.9. The following tags resolve naming conflicts and make the generated code prettier:
// +groupName=example2.example.com
// +groupGoName=SecondExample
It’s finally possible to have dots in Go package names. In this section’s example, you would put the groupName snippet into the pkg/apis/example2.example.com directory of your project.
Example projects
Kubernetes 1.9 includes a number of example projects which can serve as a blueprint for your own projects:
Vendoring
In order to update from the previous version 5 to version 6 of client-go, the library itself as well as certain third-party dependencies must be updated. Previously, this process had been tedious due to the fact that a lot of code got refactored or relocated within the existing package layout across releases. Fortunately, far less code had to move in the latest version, which should ease the upgrade procedure for most users.
State of the published repositories
These tags have limited test coverage, but can be used by early adopters of client-go and the other libraries. Moreover, they help to vendor the correct version of
State of vendoring of client-go
In general, the list of which dependencies to vendor is automatically generated and written to the file Godeps/Godeps.json. Only the revisions listed there are tested. This means especially that we do not and cannot test the code-base against master branches of our dependencies. This puts us in the following situation depending on the used vendoring tool:
Updating dependencies – golang/dep
Even with the deficiencies of golang/dep today, dep is slowly becoming the de-facto standard in the Go ecosystem. With the necessary care and the awareness of the missing features, dep can be (and is!) used successfully. Here’s a demonstration of how to update a project with client-go 5 to the latest version 6 using dep:
(If you are still running client-go version 4 and want to play it safe by not skipping a release, now is a good time to check out
Before starting, it is important to understand that client-go depends on two other Kubernetes projects:
[[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"
Note that some of the libraries could be missing if they are not actually needed by the client.
Upgrading to client-go version 6 means bumping the version and tag identifiers as following ( emphasis given):
[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"
The result of the upgrade can be found
Finally, we need to tell dep to upgrade to the specified versions by executing dep ensure. If everything goes well, the output of the command invocation should be empty, with the only indication that it was successful being a number of updated files inside the vendor folder.
If you are using CRDs, you probably also use code-generation. The following block for Gopkg.toml will add the required code-generation packages to your project:
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"
Whether you would also like to prune unneeded packages (such as test files) through dep or commit the changes into the VCS at this point is up to you -- but from an upgrade perspective, you should now be ready to harness all the fancy new features that Kubernetes 1.9 brings through client-go.