0 前言
上篇文章我们学习了了cobra的基本使用和用法,正式基于他的强大之处,让kubectl在处理命令式参数能够支持各种各样的参数形式,包括基本的文本参数,以及yaml的解析的工作
kubectl是Kubernetes的命令行工具,用于与Kubernetes集群进行交互,帮助你逐步阅读和理解kubectl的源码。
1. 准备工作
1.1 克隆源码
首先,你需要克隆Kubernetes的源码仓库:
git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
然后我们直接在根目录直接make,就可以生成了所有的目录。
2. 了解项目结构
Kubernetes项目的目录结构非常庞大,以下是一些关键目录:
cmd/kubectl/:kubectl的主要代码目录。pkg/kubectl/:kubectl的核心功能实现。staging/src/k8s.io/client-go/: Kubernetes的客户端库。staging/src/k8s.io/apimachinery/: Kubernetes的通用API工具和类型。
其实上边主要是cmd为通过corbal生成的cmd为入口文件,最终,代码会进入到
这个目录下的kubectl中来实现的
但是本文主要是介绍关键流程。
3. 关键文件和目录
3.1 cmd/kubectl/
这个目录包含了kubectl的入口文件和命令实现。
cmd/kubectl/kubectl.go:kubectl的主入口文件。cmd/kubectl/app/: 包含了kubectl的命令行解析和命令注册。
3.2 pkg/kubectl/
这个目录包含了kubectl的核心功能实现。
pkg/kubectl/cmd/: 各种kubectl命令的实现。pkg/kubectl/util/: 一些实用工具和辅助函数。
4. 逐步阅读源码
4.1 入口文件
首先,从kubectl的入口文件开始:
// cmd/kubectl/kubectl.go
package main
import (
"os"
"k8s.io/kubectl/pkg/cmd"
"k8s.io/component-base/cli"
)
func main() {
command := cmd.NewDefaultKubectlCommand()
code := cli.Run(command)
os.Exit(code)
}
这个文件非常简洁,主要是创建一个kubectl命令并运行它。
其中, cmd.NewDefaultKubectlCommand 主要是实例和注册所有命令相关的数据和参数。
只有在调用code := cli.Run(command)时候,通过参数来快速查找具体的一个实例来执行和服务端的链接
4.2 命令创建
接下来,查看NewDefaultKubectlCommand的实现:
// pkg/cmd/cmd.go
package cmd
import (
"k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/cmd/version"
"k8s.io/kubectl/pkg/cmd/apply"
// 其他命令的导入
"github.com/spf13/cobra"
)
func NewDefaultKubectlCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "kubectl",
Short: "kubectl controls the Kubernetes cluster manager",
Long: "kubectl controls the Kubernetes cluster manager.",
}
f := util.NewFactory(util.NewMatchVersionFlags())
// 添加其他命令 而且通过
groups := templates.CommandGroups{
{
Message: "Basic Commands (Beginner):",
Commands: []*cobra.Command{
create.NewCmdCreate(f, o.IOStreams),
expose.NewCmdExposeService(f, o.IOStreams),
run.NewCmdRun(f, o.IOStreams),
set.NewCmdSet(f, o.IOStreams),
},
},
。。。。
cmd.AddCommand(version.NewCmdVersion(f))
cmd.AddCommand(apply.NewCmdApply(f))
return cmd
}
所以,目前通过组的方式来注册所有支持的命令实现
最后调用
templates.ActsAsRootCommand(cmds, filters, groups...)
这里使用了cobra库来创建命令行工具,并添加了各种子命令。
4.3 具体命令实现
pkg下的cmd目录,存了所有实现的命令
以apply命令为例,查看其实现:
// pkg/cmd/apply/apply.go
package apply
import (
"k8s.io/kubectl/pkg/cmd/util"
"github.com/spf13/cobra"
)
func NewCmdApply(f util.Factory) *cobra.Command {
o := NewApplyOptions(f)
cmd := &cobra.Command{
Use: "apply -f FILENAME",
Short: "Apply a configuration to a resource by filename or stdin",
Run: func(cmd *cobra.Command, args []string) {
util.CheckErr(o.Complete(cmd, args))
util.CheckErr(o.Run())
},
}
// 添加命令行标志和选项
return cmd
}
这里定义了apply命令,并在Run函数中调用了Complete和Run方法。
4.4 命令的具体逻辑
查看ApplyOptions的实现:
// pkg/cmd/apply/apply.go
package apply
import (
"k8s.io/kubectl/pkg/cmd/util"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
type ApplyOptions struct {
// 定义各种选项和参数
}
func NewApplyOptions(f util.Factory) *ApplyOptions {
return &ApplyOptions{
// 初始化选项
}
}
func (o *ApplyOptions) Complete(cmd *cobra.Command, args []string) error {
// 解析命令行参数和选项
return nil
}
func (o *ApplyOptions) Run() error {
// 实现apply命令的具体逻辑
return nil
}
5. 深入理解
5.1 工厂模式
util.Factory是一个工厂模式,用于创建各种客户端和配置。查看其实现:
util.Factory接口定义了一些方法,用于创建和配置Kubernetes客户端。以下是Factory接口的一部分实现:
// pkg/cmd/util/factory.go
package util
import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/kubernetes"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
type Factory interface {
ToRESTConfig() (*rest.Config, error)
ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
ToRESTMapper() (meta.RESTMapper, error)
// 其他方法
}
type factoryImpl struct {
clientGetter genericclioptions.RESTClientGetter
}
func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory {
return &factoryImpl{
clientGetter: clientGetter,
}
}
func (f *factoryImpl) ToRESTConfig() (*rest.Config, error) {
return f.clientGetter.ToRESTConfig()
}
func (f *factoryImpl) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
config, err := f.ToRESTConfig()
if err != nil {
return nil, err
}
return discovery.NewDiscoveryClientForConfig(config)
}
func (f *factoryImpl) ToRESTMapper() (meta.RESTMapper, error) {
config, err := f.ToRESTConfig()
if err != nil {
return nil, err
}
return restmapper.NewDeferredDiscoveryRESTMapper(discovery.NewDiscoveryClientForConfigOrDie(config)), nil
}
factoryImpl实现了Factory接口,并使用genericclioptions.RESTClientGetter来获取REST配置和客户端。
5.2 genericclioptions.RESTClientGetter
genericclioptions.RESTClientGetter是一个接口,定义了获取REST客户端配置的方法:
// staging/src/k8s.io/cli-runtime/pkg/genericclioptions/config_flags.go
package genericclioptions
import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
type RESTClientGetter interface {
ToRESTConfig() (*rest.Config, error)
ToRawKubeConfigLoader() clientcmd.ClientConfig
}
type ConfigFlags struct {
// 定义各种配置标志
}
func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) {
return f.ToRawKubeConfigLoader().ClientConfig()
}
func (f *ConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(f.KubeConfig, &clientcmd.ConfigOverrides{})
}
ConfigFlags实现了RESTClientGetter接口,并提供了从配置文件加载Kubernetes客户端配置的方法。
6. 具体命令的实现
以apply命令为例,继续深入了解其具体逻辑。
6.1 ApplyOptions的实现
ApplyOptions结构体包含了apply命令的各种选项和参数:
// pkg/cmd/apply/apply.go
package apply
import (
"k8s.io/kubectl/pkg/cmd/util"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
type ApplyOptions struct {
// 定义各种选项和参数
Factory util.Factory
Namespace string
EnforceNamespace bool
FilenameOptions resource.FilenameOptions
// 其他选项
}
func NewApplyOptions(f util.Factory) *ApplyOptions {
return &ApplyOptions{
Factory: f,
}
}
func (o *ApplyOptions) Complete(cmd *cobra.Command, args []string) error {
// 解析命令行参数和选项
return nil
}
func (o *ApplyOptions) Run() error {
// 实现apply命令的具体逻辑
return nil
}
6.2 Complete方法
Complete方法用于解析命令行参数和选项,并进行必要的初始化:
func (o *ApplyOptions) Complete(cmd *cobra.Command, args []string) error {
var err error
// 解析命名空间
o.Namespace, o.EnforceNamespace, err = o.Factory.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
// 解析文件名选项
o.FilenameOptions.Filenames = args
// 其他初始化操作
return nil
}
在Complete方法中,Namespace和EnforceNamespace是通过工厂方法ToRawKubeConfigLoader().Namespace()获取的。FilenameOptions.Filenames则是从命令行参数中解析出来的。
6.3 Run方法
Run方法是apply命令的核心逻辑,负责实际的资源应用操作:
func (o *ApplyOptions) Run() error {
// 获取REST配置
config, err := o.Factory.ToRESTConfig()
if err != nil {
return err
}
// 创建Kubernetes客户端
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
// 解析资源文件
objs, err := o.getObjectsFromFiles()
if err != nil {
return err
}
// 应用资源
for _, obj := range objs {
if err := o.applyObject(clientset, obj); err != nil {
return err
}
}
return nil
}
在Run方法中,首先通过工厂方法获取REST配置,然后创建Kubernetes客户端。接下来,解析资源文件并应用这些资源。
6.4 解析资源文件
getObjectsFromFiles方法用于解析资源文件:
func (o *ApplyOptions) getObjectsFromFiles() ([]runtime.Object, error) {
var objs []runtime.Object
for _, filename := range o.FilenameOptions.Filenames {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
// 解析YAML或JSON文件
obj, err := runtime.Decode(serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer(), data)
if err != nil {
return nil, err
}
objs = append(objs, obj)
}
return objs, nil
}
在getObjectsFromFiles方法中,首先读取文件内容,然后使用Kubernetes的runtime.Decode方法解析YAML或JSON文件,最终返回解析后的对象列表。
6.5 应用资源
applyObject方法用于将解析后的资源对象应用到Kubernetes集群:
func (o *ApplyOptions) applyObject(clientset *kubernetes.Clientset, obj runtime.Object) error {
// 获取对象的元数据
accessor := meta.NewAccessor()
namespace, err := accessor.Namespace(obj)
if err != nil {
return err
}
name, err := accessor.Name(obj)
if err != nil {
return err
}
// 获取对象的GVR(GroupVersionResource)
gvk := obj.GetObjectKind().GroupVersionKind()
mapping, err := o.Factory.ToRESTMapper().RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return err
}
// 创建或更新资源
resourceClient := clientset.Resource(mapping.Resource).Namespace(namespace)
_, err = resourceClient.Create(context.TODO(), obj, metav1.CreateOptions{})
if err != nil {
if apierrors.IsAlreadyExists(err) {
_, err = resourceClient.Update(context.TODO(), obj, metav1.UpdateOptions{})
}
}
return err
}
在applyObject方法中,首先获取对象的元数据(如命名空间和名称),然后通过工厂方法获取对象的GVR(GroupVersionResource)。接下来,使用Kubernetes客户端创建或更新资源。
7. 总结
通过以上步骤,我们详细了解了kubectl的apply命令的实现过程。以下是关键点总结:
- 入口文件:
cmd/kubectl/kubectl.go是kubectl的主入口文件。 - 命令创建:
NewDefaultKubectlCommand方法
- 入口文件:
cmd/kubectl/kubectl.go是kubectl的主入口文件。 - 命令创建:
NewDefaultKubectlCommand方法使用cobra库创建kubectl命令,并添加各种子命令。 - 具体命令实现:以
apply命令为例,NewCmdApply方法定义了apply命令,并在Run函数中调用了Complete和Run方法。 - 工厂模式:
util.Factory接口和其实现类factoryImpl用于创建和配置Kubernetes客户端。 - RESTClientGetter:
genericclioptions.RESTClientGetter接口定义了获取REST客户端配置的方法,ConfigFlags实现了该接口。 - 命令的具体逻辑:
ApplyOptions结构体包含了apply命令的各种选项和参数,Complete方法用于解析命令行参数和选项,Run方法负责实际的资源应用操作。
8. 进一步阅读和探索
8.1 其他命令
除了apply命令,kubectl还包含许多其他命令,如get、create、delete等。你可以按照类似的方式阅读和理解这些命令的实现。
例如,get命令的实现:
// pkg/cmd/get/get.go
package get
import (
"k8s.io/kubectl/pkg/cmd/util"
"github.com/spf13/cobra"
)
func NewCmdGet(f util.Factory) *cobra.Command {
o := NewGetOptions(f)
cmd := &cobra.Command{
Use: "get [(-o|--output=)json|yaml|wide|...] (TYPE [NAME | -l label] | TYPE/NAME ...) [flags]",
Short: "Display one or many resources",
Run: func(cmd *cobra.Command, args []string) {
util.CheckErr(o.Complete(cmd, args))
util.CheckErr(o.Run())
},
}
// 添加命令行标志和选项
return cmd
}
8.2 测试和调试
为了更好地理解kubectl的实现,你可以编写一些测试用例,或者使用调试工具进行调试。
例如,编写一个简单的测试用例:
// pkg/cmd/apply/apply_test.go
package apply
import (
"testing"
"k8s.io/kubectl/pkg/cmd/util"
)
func TestApply(t *testing.T) {
f := util.NewFactory(util.NewMatchVersionFlags())
cmd := NewCmdApply(f)
// 模拟命令行参数
args := []string{"-f", "test.yaml"}
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}