欢迎加入本站的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()))
}
}