kubectl 命令行工具为与Kubernetes API服务器的通信提供了一个强大的客户端机制。kubectl ,有很多细化的命令、子命令和选项。熟悉所有这些选项需要练习和时间。
尽管它有大量的选项,但你有时希望kubectl ,提供更高层次的功能或简化特定的操作。例如,你可能需要借壳进入一个正在运行的容器,在交互模式下检查环境。用kubectl exec mypod -it --namespace=abc — /bin/sh ,这很容易实现,但是,你必须记住各种选项。如果有一个简单的命令,例如kubectl iexec mypod ,不是更容易吗?这就是kubectl插件的作用。你可以编写你自己的命令和它们应该展示的行为。
在这篇博文中,我想展示如何编写一个简单的kubectl 插件,利用Go客户端与API服务器进行通信。你将学习如何建立Go项目,在Go模块的帮助下声明对Go客户端库的依赖,并编写源代码。
Go库并不是编写
kubectl插件的唯一选择。Kubernetes支持其他语言,尽管它们不是这篇博文的主题。我个人还没有尝试过其他的客户端库。我认为Go库可能是维护得最好的,因为Kubernetes本身就是用Go写的。
定义插件的功能
我们的插件的目的是在命令行上呈现Kubernetes服务器版本。我们将希望通过命令server-version 来实现这一功能。其结果应该类似于下面的控制台输出。
$ kubectl server-version
Hello from Kubernetes server with version v1.10.11!
此外,提供一个渲染插件版本的子命令是个好主意,这样终端用户就知道应该期待哪种功能集。如果你知道你的机器上已经安装了哪个版本的插件,这将对升级过程有极大的帮助。下面的控制台输出表明,使用的是版本为0.2.0 的插件。
$ kubectl server-version version
kubectl server-version v0.2.0
我们定义了我们想要公开的命令和它们的功能。让我们从设置项目结构开始。
设置项目
设置一个实现kubectl 插件的Go项目,其实没有什么特别的。你可以按照其他 Go 项目的惯例来设置。
我们将首先添加一个main.go 文件,作为应用程序的入口。接下来,我们将用go mod init github.com/bmuschko/kubectl-server-version 生成 Go 模块。最后你会得到一个go.mod 和go.sum 文件,我们将在后面填充。包cmd ,要包含所有相关的CLI命令处理。此外,我个人喜欢为项目提供一个许可证和一个解释如何使用该插件的README文件。最后,你应该得到一个如下所示的项目布局。
.
├── LICENSE
├── README.adoc
├── cmd
├── go.mod
├── go.sum
└── main.go
在下一节,我们将添加必要的依赖项。
声明依赖性
当我们运行go init 命令时,go.mod 文件的初始结构已经设置好了。记住,在添加任何依赖项之前,你必须设置环境变量GO111MODULES=on ,以启用Go模块。要开始使用Go客户端,只需运行go get k8s.io/client-go 命令即可。你可能需要Go客户端库的其他部分,例如:k8s.io/api,k8s.io/apimachinery 。你可以用同样的Go命令来获取它们。
下面的Go模块文件显示了我最终的依赖列表。正如你所看到的,我们正在用Go 1.12构建项目。我还添加了用于暴露CLI命令的依赖性github.com/spf13/cobra ,以及帮助简化测试代码的github.com/stretchr/testify 。
go.mod
module github.com/bmuschko/kubectl-server-version
go 1.12
require (
github.com/evanphx/json-patch v4.5.0+incompatible // indirect
github.com/gogo/protobuf v1.2.1 // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/gofuzz v1.0.0 // indirect
github.com/googleapis/gnostic v0.3.0 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/imdario/mergo v0.3.7 // indirect
github.com/json-iterator/go v1.1.6 // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/spf13/cobra v0.0.4
github.com/stretchr/testify v1.3.0
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b // indirect
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d // indirect
k8s.io/cli-runtime v0.0.0-20190409023024-d644b00f3b79
k8s.io/client-go v11.0.0+incompatible
k8s.io/klog v0.3.3 // indirect
k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 // indirect
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a // indirect
sigs.k8s.io/kustomize v2.0.3+incompatible // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
)
接下来,我们将实现插件的功能。
实现插件功能
不出所料,我们将首先在main.go ,用生命来填充。实现逻辑很简单。首先,我们将通过调用函数NewServerVersionCommand ,创建一个新的命令。标准输入、标准输出和标准错误已经被genericclioptions.IOStreams ,并作为参数传入。接下来,我们将执行新创建的命令并处理一个潜在的错误情况。
main.go
package main
import (
"github.com/bmuschko/kubectl-server-version/cmd"
"k8s.io/cli-runtime/pkg/genericclioptions"
"os"
)
var version = "undefined"
func main() {
cmd.SetVersion(version)
serverVersionCmd := cmd.NewServerVersionCommand(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
if err := serverVersionCmd.Execute(); err != nil {
os.Exit(1)
}
}
变量version 的值可以在构建时注入,代表插件的版本。我们将把这个值传递给包cmd ,以便以后在version 子命令中使用。
让我们来看看插件的主要功能,与API服务器通信并显示返回的Kubernetes服务器版本的命令。我把代码分割成多个可消化的部分。下面的代码块使用Cobra库来创建新的命令。除了Kubernetes包的导入外,这个实现没有什么特别之处。你可以很容易地想象类似的代码作为任何其他用Go编写的CLI工具的一部分。
cmd/server_version.go
import (
"errors"
"fmt"
"github.com/spf13/cobra"
"io"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
type serverVersionCmd struct {
out io.Writer
}
// NewServerVersionCommand creates the command for rendering the Kubernetes server version.
func NewServerVersionCommand(streams genericclioptions.IOStreams) *cobra.Command {
helloWorldCmd := &serverVersionCmd{
out: streams.Out,
}
cmd := &cobra.Command{
Use: "server-version",
Short: "Prints Kubernetes server version",
SilenceUsage: true,
RunE: func(c *cobra.Command, args []string) error {
if len(args) != 0 {
return errors.New("this command does not accept arguments")
}
return helloWorldCmd.run()
},
}
cmd.AddCommand(newVersionCmd(streams.Out))
return cmd
}
func (sv *serverVersionCmd) run() error {
serverVersion, err := getServerVersion()
if err != nil {
return err
}
_, err = fmt.Fprintf(sv.out, "Hello from Kubernetes server with version %s!\n", serverVersion)
if err != nil {
return err
}
return nil
}
这个文件中最重要的部分是对函数getServerVersion() 的调用。在这里,我们实际上是在与Go客户端进行交互。
客户端 "实例被称为clientset ,由结构体kubernetes.Clientset 表示。为了创建该结构,你必须以restclient.Config 的形式提供额外的客户端配置。一旦创建,clientset 就可以访问整个Kubernetes API和所有相关操作,以查询、创建、更新和删除Kubernetes对象。
下面的函数检索了DiscoveryClient ,它可以发现服务器支持的API组、版本和资源。例如,函数ServerVersion() ,返回Kubernetes服务器版本。
cmd/server_version.go
func getServerVersion() (string, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
configOverrides := &clientcmd.ConfigOverrides{}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
config, err := kubeConfig.ClientConfig()
if err != nil {
return "", err
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return "", err
}
sv, err := clientset.Discovery().ServerVersion()
if err != nil {
return "", err
}
return sv.String(), nil
}
这其实就是实现的全部内容。最重要的方面是熟悉Go客户端的API。其他都是普通的Go编程。
安装和使用该插件
在机器上安装kubectl 插件有几个要求。
-
插件可以以bash脚本或二进制文件的形式安装。
-
插件的名字必须有前缀
kubectl-。 -
插件名称中
kubectl-之后的部分代表命令名称。 -
命令名中的破折号必须改成下划线。
在这篇文章中,我们甚至没有谈及把插件写成bash脚本的选项。我们的插件是用Go编写的,所以我们必须先构建二进制文件。最简单的选择是使用go build 命令。这个 "安装过程 "一点都不复杂。你只需将二进制文件复制到PATH。
下面的命令建立了该插件的二进制文件,并将其复制到PATH 。在MacOSX下,PATH 被表示为/usr/local/bin 。
$ go build -o kubectl-server_version
$ cp kubectl-server_version /usr/local/bin
$ kubectl plugin list
The following kubectl-compatible plugins are available:
/usr/local/bin/kubectl-server_version
很好,插件已经安装完毕。我们现在可以按照前面的描述去执行它。正如你所看到的,开发和安装过程并不十分繁琐。但如果你想与团队或Kubernetes社区分享这个插件呢?让我们在下一节中谈谈分发该插件的问题。
在GitHub上发布该插件
分发插件的二进制文件有多种选择。在这篇文章中,我们将讨论在GitHub Releases上发布插件的问题。
另一个流行的选择是使用
kubectl插件的软件包管理器,称为krew。这是一个最好的选择,因为它易于使用,而且可以向全世界提供插件。我将把这个话题留给另一篇博文。
我选择的向GitHub Releases发布二进制文件的工具是GoReleaser。它可以帮助你定义、构建和发布你想用给定的 Git 标签发布的二进制文件。GoReleaser 在一个名为.goreleaser.yml 的文件中读取二进制文件的配置。一旦一个版本被发布,任何消费者都可以下载目标平台的二进制文件并直接将插件安装到PATH 。
总结
这篇博文教你如何开始使用Go语言编写一个kubectl 的插件。我们从零开始创建了一个新的项目,并把它从开始到发布的全过程。我希望我可以启发你编写你自己的插件。