先讲思路,通过client-go RestClient模拟kubectl exec 的手法,结合容器镜像里的tar命令和golang原生tar包,作为管道的输入输出,实现文件的数据流拷贝,以下是具体做法。
首先是一段将文件内容输出到标准输出的代码
package main
import (
"context"
"github.com/octoboy233/client-go-usage/config"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/remotecommand"
"os"
)
func main() {
cli := config.InitRestClient()
req := cli.CoreV1().RESTClient().Post().
Namespace("default").
Resource("pods").
Name("nginx-pod").
SubResource("exec").VersionedParams(
&corev1.PodExecOptions{
Container: "nginx-container",
Command: []string{"cat", "/usr/share/nginx/html/index.html"},
Stdout: true,
Stderr: true,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(config.InitRestCfg(), "POST", req.URL())
if err != nil {
panic(err)
}
if err := exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
Stdout: os.Stdout,
Stderr: os.Stderr,
}); err != nil {
panic(err)
}
}
我们在此基础上修改实现文件拷贝。
修改Command为
Command: []string{
"/bin/bash",
"-c",
"cd /usr/share/nginx/html/ && tar cf - index.html"},
tar cf - index.html这条命令的含义是创建一个tar归档文件,并将名为"index.html"的文件加入到归档中。
具体解释如下:
tar: tar命令用于创建、查看和提取tar归档文件。
cf: c表示创建新的归档文件,f表示指定归档文件的名称。
-: -(连字符)表示将归档文件的输出发送到stdout(标准输出),而不是保存到文件中。
index.html: 这是要添加到归档中的文件或目录的名称。在这种情况下,它是一个文件名。
因此,运行这条命令将创建一个tar归档文件,并将名为"index.html"的文件添加到归档中。归档文件的输出将被发送到stdout(你也可以使用重定向符号将其保存到文件中)
值得一提的是 tar cd - xxxx无法指定文件的绝对路径,因此需要先定位到目标文件夹。
新建一个管道,用于接收远程执行的结果
pipReader, pipWriter := io.Pipe()
defer pipWriter.Close()
执行远程命令,将exec命令的标准输出指定向刚创建的PipeWriter
go func() {
if err := exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
Stdout: pipWriter,
Stderr: os.Stderr,
}); err != nil {
panic(err)
}
}()
值得一提的是,这里需要使用协程。否则会阻塞直到PipeWriter关闭。
从管道中读取tar文件
reader := tar.NewReader(pipReader)
for {
header, err := reader.Next()
if err != nil {
if err == io.EOF {
break //文件读取完毕
}
}
fmt.Println("读取到文件:", header.FileInfo().Name())
//外部创建新文件
file, err := os.Create(header.FileInfo().Name())
if err != nil {
panic(err)
}
//如果是文件,复制到新建的文件中
if header.Typeflag == tar.TypeReg {
io.Copy(file, reader)
}
}
循环读取,并把归档输出写入新创建的文件,当出现End of File(文件结束符)错误,代表输入已完成,没有可读取的输出。退出循环。
附上完整代码
package main
import (
"archive/tar"
"context"
"fmt"
"github.com/octoboy233/client-go-usage/config"
"io"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/remotecommand"
"os"
)
func main() {
cli := config.InitRestClient()
req := cli.CoreV1().RESTClient().Post().
Namespace("default").
Resource("pods").
Name("nginx-pod").
SubResource("exec").VersionedParams(
&corev1.PodExecOptions{
Container: "nginx-container",
// tar文件输出到标准输出
Command: []string{
"/bin/bash",
"-c",
"cd /usr/share/nginx/html/ && tar cf - index.html"},
//Command: []string{"tar", "cf", "-", "test.txt"},
Stdout: true,
Stderr: true,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(config.InitRestCfg(), "POST", req.URL())
if err != nil {
panic(err)
}
//新建一个管道,用于接收远程执行的结果
pipReader, pipWriter := io.Pipe()
defer pipWriter.Close()
//执行远程命令
go func() {
if err := exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
Stdout: pipWriter,
Stderr: os.Stderr,
}); err != nil {
panic(err)
}
}()
//从管道中读取tar文件
reader := tar.NewReader(pipReader)
for {
header, err := reader.Next()
if err != nil {
if err == io.EOF {
fmt.Println("文件读取完毕")
break
}
}
fmt.Println("读取到文件:", header.FileInfo().Name())
//外部创建新文件
file, err := os.Create(header.FileInfo().Name())
if err != nil {
panic(err)
}
//如果是文件,复制到新建的文件中
if header.Typeflag == tar.TypeReg {
io.Copy(file, reader)
}
}
}