如何使用Go客户端将本地文件复制到我的minikube集群中的Pod容器中?

huangapple go评论88阅读模式
英文:

How do I copy a local file to a pod's container in my minikube cluster using the Go client?

问题

我的查询基本上就是标题所说的,我有一个本地文件,比如file.txt,我想将它复制到pod1的容器container1中。

如果我使用kubectl来执行,适当的命令应该是:

kubectl cp file.txt pod1:file.txt -c container1

然而,我如何使用kubectl的Go客户端来执行呢?

我尝试了两种方法,但都没有成功:

import (
	"fmt"
	"context"
	"log"
	"os"
	"path/filepath"

	g "github.com/sdslabs/katana/configs"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	//"k8s.io/kubectl/pkg/cmd/exec"
)

func CopyIntoPod(namespace string, podName string, containerName string, srcPath string, dstPath string) {
	// 创建一个Kubernetes客户端
	config, err := GetKubeConfig()
	if err != nil {
		log.Fatal(err)
	}

	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		log.Fatal(err)
	}

	// 构建要执行的命令
	cmd := []string{"cp", srcPath, dstPath}

	// 使用PodExecOptions结构体指定exec请求的选项
	options := v1.PodExecOptions{
		Container: containerName,
		Command:   cmd,
		Stdin:     false,
		Stdout:    true,
		Stderr:    true,
		TTY:       false,
	}
	log.Println("Options set!")

	// 使用CoreV1Api.Exec方法在容器内执行命令
	req := client.CoreV1().RESTClient().Post().
		Namespace(namespace).
		Name(podName).
		Resource("pods").
		SubResource("exec").
		VersionedParams(&options, metav1.ParameterCodec)
	log.Println("Request generated")
	
	exec, err := req.Stream(context.TODO())
	if err != nil {
		log.Fatal(err)
	}
	defer exec.Close()

	// 读取exec命令的响应
	var result []byte
	if _, err := exec.Read(result); err != nil {
		log.Fatal(err)
	}

	fmt.Println("File copied successfully!")
}

这给了我错误消息:

no kind is registered for the type v1.PodExecOptions in scheme "pkg/runtime/scheme.go:100"

我无法弄清楚,所以我尝试了另一种方法:

type PodExec struct {
	RestConfig *rest.Config
	*kubernetes.Clientset
}

func NewPodExec(config *rest.Config, clientset *kubernetes.Clientset) *PodExec {
	config.APIPath = "/api" // 确保我们目标是/api而不仅仅是/
	config.GroupVersion = &schema.GroupVersion{Version: "v1"} // 这将目标核心API组,因此URL路径将为/api/v1
	config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
	return &PodExec{
	  RestConfig: config,
	  Clientset:  clientset,
	}  
}

func (p *PodExec) PodCopyFile(src string, dst string, containername string, podNamespace string) (*bytes.Buffer, *bytes.Buffer, *bytes.Buffer, error) {
	ioStreams, in, out, errOut := genericclioptions.NewTestIOStreams()
	copyOptions := cp.NewCopyOptions(ioStreams)
	copyOptions.Clientset = p.Clientset
	copyOptions.ClientConfig = p.RestConfig
	copyOptions.Container = containername
	copyOptions.Namespace = podNamespace
	err := copyOptions.Run()
	if err != nil {
		return nil, nil, nil, fmt.Errorf("could not run copy operation: %v", err)
	}
	return in, out, errOut, nil
}

然而,copyOptions.Run()命令存在一些问题,它试图在copyOptions中查找o.args[0]o.args[0],但是o没有被导入,所以无法修改。

上下文:https://pkg.go.dev/k8s.io/kubectl/pkg/cmd/cp#CopyOptions.Run

所以,现在我真的迷失和困惑了。任何帮助将不胜感激。谢谢。

编辑:我想到了一种可行的方法,我们可以直接调用cmd.exec()并直接运行kubectl cp命令,但这似乎有点破解,我不确定它是否会起作用,有什么想法吗?

英文:

My query is pretty much what the title says, I have a local file say file.txt and I want to copy it into pod1's container container1.

If I was to do it using kubectl, the appropriate command would be :

kubectl cp file.txt pod1:file.txt -c container1

However, how do I do it using the Go client of kubectl?

I tried 2 ways but none of them worked :

import (
"fmt"
"context"
"log"
"os"
"path/filepath"
g "github.com/sdslabs/katana/configs"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
//"k8s.io/kubectl/pkg/cmd/exec"
)
func CopyIntoPod(namespace string, podName string, containerName string, srcPath string, dstPath string) {
// Create a Kubernetes client
config, err := GetKubeConfig()
if err != nil {
log.Fatal(err)
}
client, err := kubernetes.NewForConfig(config)
if err != nil {
log.Fatal(err)
}
// Build the command to execute
cmd := []string{"cp", srcPath, dstPath}
// Use the PodExecOptions struct to specify the options for the exec request
options := v1.PodExecOptions{
Container: containerName,
Command:   cmd,
Stdin:     false,
Stdout:    true,
Stderr:    true,
TTY:       false,
}
log.Println("Options set!")
// Use the CoreV1Api.Exec method to execute the command inside the container
req := client.CoreV1().RESTClient().Post().
Namespace(namespace).
Name(podName).
Resource("pods").
SubResource("exec").
VersionedParams(&options, metav1.ParameterCodec)
log.Println("Request generated")
exec, err := req.Stream(context.TODO())
if err != nil {
log.Fatal(err)
}
defer exec.Close()
// Read the response from the exec command
var result []byte
if _, err := exec.Read(result); err != nil {
log.Fatal(err)
}
fmt.Println("File copied successfully!")
}

This gave me the error message :

no kind is registered for the type v1.PodExecOptions in scheme "pkg/runtime/scheme.go:100"

I couldn't figure it out, so I tried another way :

type PodExec struct {
RestConfig *rest.Config
*kubernetes.Clientset
}
func NewPodExec(config *rest.Config, clientset *kubernetes.Clientset) *PodExec {
config.APIPath = "/api" // Make sure we target /api and not just /
config.GroupVersion = &schema.GroupVersion{Version: "v1"} // this targets the core api groups so the url path will be /api/v1
config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
return &PodExec{
RestConfig: config,
Clientset:  clientset,
}  
}
func (p *PodExec) PodCopyFile(src string, dst string, containername string, podNamespace string) (*bytes.Buffer, *bytes.Buffer, *bytes.Buffer, error) {
ioStreams, in, out, errOut := genericclioptions.NewTestIOStreams()
copyOptions := cp.NewCopyOptions(ioStreams)
copyOptions.Clientset = p.Clientset
copyOptions.ClientConfig = p.RestConfig
copyOptions.Container = containername
copyOptions.Namespace = podNamespace
err := copyOptions.Run()
if err != nil {
return nil, nil, nil, fmt.Errorf("could not run copy operation: %v", err)
}
return in, out, errOut, nil
}

However, there were some issues with the copyOptions.Run() command, it tried to look for o.args[0] and o.args[0] inside copyOptions but o is not imported so it couldn't be modified.

Context : https://pkg.go.dev/k8s.io/kubectl/pkg/cmd/cp#CopyOptions.Run

So, now I'm really lost and confused. Any help would be appreciated. Thanks.

Edit : I did think of a viable method where we can just call cmd.exec() and run the kubectl cp command directly but it seems kinda hacky and I'm not sure whether it would work, any thoughts?

答案1

得分: 1

这是我最终成功完成的方法:

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/remotecommand"
)

func CopyIntoPod(podName string, namespace string, containerName string, srcPath string, dstPath string) {
	// 获取默认的kubeconfig文件
	kubeConfig := filepath.Join(homedir.HomeDir(), ".kube", "config")

	// 使用kubeconfig文件创建一个配置对象
	config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
	if err != nil {
		fmt.Printf("创建配置失败:%s\n", err)
		return
	}

	// 创建一个Kubernetes客户端
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		fmt.Printf("创建客户端失败:%s\n", err)
		return
	}

	// 打开要复制的文件
	localFile, err := os.Open(srcPath)
	if err != nil {
		fmt.Printf("打开本地文件失败:%s\n", err)
		return
	}
	defer localFile.Close()

	pod, err := client.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
	if err != nil {
		fmt.Printf("获取Pod失败:%s\n", err)
		return
	}

	// 在Pod中查找容器
	var container *corev1.Container
	for _, c := range pod.Spec.Containers {
		if c.Name == containerName {
			container = &c
			break
		}
	}

	if container == nil {
		fmt.Printf("在Pod中找不到容器\n")
		return
	}

	// 创建一个到容器的流
	req := client.CoreV1().RESTClient().Post().
		Resource("pods").
		Name(podName).
		Namespace(namespace).
		SubResource("exec").
		Param("container", containerName)

	req.VersionedParams(&corev1.PodExecOptions{
		Container: containerName,
		Command:   []string{"bash", "-c", "cat > " + dstPath},
		Stdin:     true,
		Stdout:    true,
		Stderr:    true,
	}, scheme.ParameterCodec)

	exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
	if err != nil {
		fmt.Printf("创建执行器失败:%s\n", err)
		return
	}

	// 创建一个到容器的流
	err = exec.StreamWithContext(context.TODO(), remotecommand.StreamOptions{
		Stdin:  localFile,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
		Tty:    false,
	})
	if err != nil {
		fmt.Printf("流式传输失败:%s\n", err)
		return
	}

	fmt.Println("文件成功复制")
}
英文:

Here's how I finally managed to do it :

package main
import (
"context"
"fmt"
"os"
"path/filepath"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/remotecommand"
)
func CopyIntoPod(podName string, namespace string, containerName string, srcPath string, dstPath string) {
// Get the default kubeconfig file
kubeConfig := filepath.Join(homedir.HomeDir(), ".kube", "config")
// Create a config object using the kubeconfig file
config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
if err != nil {
fmt.Printf("Error creating config: %s\n", err)
return
}
// Create a Kubernetes client
client, err := kubernetes.NewForConfig(config)
if err != nil {
fmt.Printf("Error creating client: %s\n", err)
return
}
// Open the file to copy
localFile, err := os.Open(srcPath)
if err != nil {
fmt.Printf("Error opening local file: %s\n", err)
return
}
defer localFile.Close()
pod, err := client.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
fmt.Printf("Error getting pod: %s\n", err)
return
}
// Find the container in the pod
var container *corev1.Container
for _, c := range pod.Spec.Containers {
if c.Name == containerName {
container = &c
break
}
}
if container == nil {
fmt.Printf("Container not found in pod\n")
return
}
// Create a stream to the container
req := client.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
Param("container", containerName)	
req.VersionedParams(&corev1.PodExecOptions{
Container: containerName,
Command:   []string{"bash", "-c", "cat > " + dstPath},
Stdin:     true,
Stdout:    true,
Stderr:    true,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
if err != nil {
fmt.Printf("Error creating executor: %s\n", err)
return
}
// Create a stream to the container
err = exec.StreamWithContext(context.TODO(), remotecommand.StreamOptions{
Stdin:  localFile,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty:    false,
})
if err != nil {
fmt.Printf("Error streaming: %s\n", err)
return
}
fmt.Println("File copied successfully")
}

huangapple
  • 本文由 发表于 2022年12月30日 15:35:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/74959178.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定