探索client-go中的exec和cp的原理

欢迎加入本站的kubernetes技术交流群,微信添加:加Blue_L。


开发kubernetes控制器中可能涉及到进入特定的container中执行命令的操作,或者拷贝文件等操作,此时我们可能会借助client-go提供的这个工具去实现:

"k8s.io/client-go/tools/remotecommand"

// 构建请求
req := client.RESTClient().Post().
	Resource("pods").
	Name(podName).
	Namespace(namespace).
	SubResource("exec")
req.VersionedParams(&v1.PodExecOptions{
	Container: containerName,
	Command:   cmd,
	Stdin:     false,
	Stdout:    true,
	Stderr:    true,
	TTY:       true,
}, scheme.ParameterCodec)

// 创建执行器
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())

// 执行命令
exec.Stream(remotecommand.StreamOptions{
			Stdin:  nil,
			Stdout: &buf,
			Stderr: &buf,
			Tty:    true,
		})

上面代码执行时会向apiserver发起一个请求,apiserver会通过代理机制将请求转发给相应节点上的kubelet服务,kubelet会通过cri接口调用runtime的接口发起流式接口中的Exec()接口进入到container执行。针对一般化的命令调用,输入参数和输出结果多数以文本为主,并不会占用带宽和apiserver的资源,但是当设计到文件拷贝的时候则会占用较多带宽,因此在实际使用时这种方式拷贝大文件来说可能并不是最优方案。

此处顺便描述一下如kubectl工具对cp命令的原理,其实也是调用的exec接口,然后执行tar命令进行文件的拷贝动作:

# 从容器向外拷贝,通过tar命令压缩将输出重定向到标准输出
tar cf - <srcFile>
# 从外面向容器内拷贝,将标准输入数据通过tar解压写入到container内的文件目录
tar --no-same-permissions --no-same-owner -xmf -
# 或
tar -xmf -

考虑到上面所说的带宽问题,在实际使用时也可向kubelet直接发起exec请求,只要通过client-go将pod所在节点实现查询一下即可,当然还会设计一些端口,证书问题,下面是一个直接向kubelet发起请求的示例代码。

package main

import (
	"flag"
	"fmt"
	"bytes"
	"net/url"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/remotecommand"
	"k8s.io/client-go/util/homedir"
	"path/filepath"
)

func main() {
	// 此处我们直接使用了/root/.kube/config文件,因此我们有足够的权限
	// 如果使用service account token的话,还需要额外创建role和rolebinding
	// 当直接访问kubelet接口的时候,kubelet支持使用证书和token进行认证,
	// 使用rbac对请求进行鉴权操作
	var kubeConfig *string
	if home := homedir.HomeDir(); home != "" {
		kubeConfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		kubeConfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}
	flag.Parse()

	config, err := clientcmd.BuildConfigFromFlags("", *kubeConfig)
	if err != nil {
		panic(err)
	}

	// 构建请求,直接请求kubelet需要的各种参数
	params := url.Values{}
	params.Add("tty", "1")
	params.Add("input", "0")
	params.Add("output", "1")
	params.Add("error", "0")
	params.Add("command", "ls")
	params.Add("command", "/")
	url := &url.URL{
		Scheme:   "https",
		Host:     "10.0.0.1:10250",
		Path:     "/exec/default/busybox/busybox1",
		RawQuery: params.Encode(),
	}
	// 此处配置证书相关配置,由于是测试,这块直接忽略了,在实际生产使用时可从文件读取
	// 此处配置的CAFile在更新后,client-go能够定时自动刷新
	config.TLSClientConfig.CAFile = ""
	config.TLSClientConfig.CAData = nil
	config.TLSClientConfig.Insecure = true
	executor, err := remotecommand.NewSPDYExecutor(config, "POST", url)
	if err != nil {
		panic(err)
	}

	done := make(chan error)
	var buf bytes.Buffer
	wrap := func() {
		err := executor.Stream(remotecommand.StreamOptions{
			Stdout: &buf,
			Tty: true,
		})
		done <- err
	}

	go wrap()

	fmt.Println("wait for out")

	select {
	case err := <-done:
		if err != nil {
			fmt.Println("Command exit with error", err)
		}
		fmt.Println(string(buf.Bytes()))
	}
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注