背景

最近在用operator-sdk写operator, operator-sdk有命令可以直接生成 deepcopy, 但是并没有帮我们生成对应的clientset, 而是通过controller提供 了一个client. 但是这个client与以往的client-go中的clientset相比, 用起来 有点不太顺手, 于是就想到利用k8s的code-generator来为我们的CRD生成一个 单独clientset.

生成clientset

由于operator-sdk不帮我们生成client, 其生成的代码里, 也没有指定需要生成 client的注解, 因此要在我们的CRD的定义上加上// +genclient这么一行注释.

记下来从github上clone下来code-generator 的代码, 然后进入operator的工程项 目, 执行:

../../../k8s.io/code-generator/generate-groups.sh client,informer,lister github.com/REPONAME/PROJECTNAME/pkg/client github.com/REPONAME/PROJECTNAME/pkg/apis GROUP:v1alpha1

命令的第一段是脚本路径, 需要用code-generator相对与operator工程项目的相 对路径, 第二个参数表示要生成什么, 我们这里只需要 client/informer/lister, 至于deepcopy, 由于operator-sdk已经帮我们生成了, 所以没有用code-generator生成. 第三个参数是生成的代码要放到什么路径. 注 意这里不是相对operator工程的相对路径, 而是类似golang里import的路径, 也 就是相对于GOPATH的路径. 第四个参数是apis所在的路径, 同样需要是相对于 GOPATH的路径. 最后一个参数是Group+Version, 用冒号分割. code-generator 就会去第四个参数拼上Group+Version后的路径, 也就是 pkg/apis/GROUP/VERSION这个目录下去找CRD的定义.

代码兼容性问题

code-generator假设我们的代码按照一定的规则写成, 但是operator-sdk生成的 代码并没有100%按照这个规则生成, 这就导致code-generator生成的代码有两处 由于找不到定义编译不过, 第一处位于pkg/client/clientset/versioned/scheme/register.go, 有一行:

var localSchemeBuilder = runtime.SchemeBuilder{
        v1alpha1.AddToScheme,
}

然而实际上, operator-sdk生成的代码中, AddToScheme这个函数并不在 v1alpha1这个包里, 而是位于pkg/apis这个目录, 因此需要将这一行手动改成如 下, 同时修改import的包

var localSchemeBuilder = runtime.SchemeBuilder{
        apis.AddToScheme,
}

第二处不兼容问题位于 pkg/client/clientset/versioned/typed/GROUP/v1alpha1/GROUP_client.go, 有一行:

config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()

这个Codecs的类型定义位于k8s.io/apimachinery这个包中, 然而实际上并不存 在这个WithoutConversion函数. 排查了一会发现是由于operator-sdk生成 go.mod中, 有这么一段, 相当于固定写死了依赖的k8s包的版本为1.13.4. 而 code-generator用的是最新版的代码, 新版的apichiniery中有这个 WithoutConversion的实现, 而引入的是老版, 没有这个方法.

// Pinned to kubernetes-1.13.4
replace (
        k8s.io/api => k8s.io/api v0.0.0-20190222213804-5cb15d344471
        k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190228180357-d002e88f6236
        k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628
        k8s.io/client-go => k8s.io/client-go v0.0.0-20190228174230-b40b2a5939e4
)

简单阅读了代码后, 我一开始尝试将WithoutConversion()直接去掉, 直接用 scheme.Codecs. 两者都实现了同一套接口, 虽然编译能过, 但是执行过程中会报错:

Unable to decode an event from the watch stream no kind CRD is registered for the internal version of group

而修改go.mod让operator用最新版的k8s包, 也同样有奇奇怪怪的错误.

code-generator生成老版本client

既然没有办法让operator用最新版的k8s包, 那就得想办法让code-generator用 生成老版的代码. 上github上搜release, 果然找到适用1.3.4的release分支, 然而checkout到1.3.4版本的commit重新生成代码, 发现code-generator依赖使 用了最新版的分支来生成代码, 在go.mod中指定commit id也没用. 最后分析下 generate-groups.sh这个脚本, 发现它就干了两件事, 首先编译 code-generator/cmd下的各个子命令, 然后根据参数分别调用不同的二进制来完 成代码生成. 到这里就很好办了, 首先用1.3.4分支的代码手动编译生成各个二 进制文件, 然后注释掉generate-groups.sh中的编译那一行:

(
    # To support running this script from anywhere, we have to first cd into this directory
    # so we can install the tools.
    cd $(dirname "${0}")
    # 注释掉这一行
    # go install ${GOFLAGS:-} ./cmd/{defaulter-gen,client-gen,lister-gen,informer-gen,deepcopy-gen}
)

然后重新执行这个脚本, 就能看到生成的代码不再用CodeFactory, 而是用了老 版本的代码:

config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>