【问题标题】:How to copy file to container with kubernetes client-go?如何使用 kubernetes client-go 将文件复制到容器?
【发布时间】:2019-01-12 04:40:36
【问题描述】:

我想使用https://github.com/kubernetes/client-go 将文件从我的文件系统复制到容器,反之亦然。

kubectl cp <file-spec-src> <file-spec-dest> -c <specific-container>

go 客户端中是否有封装调用的函数?或者我可以使用RESTClient 之类的东西吗?

【问题讨论】:

    标签: go kubernetes client-go


    【解决方案1】:

    具有讽刺意味的是,今天有人投票支持my answer to this question for Java。我还没有打开client-go repo 来查看,但如果它公开cp 命令比Java 库更多,我会非常非常惊讶。

    【讨论】:

    • 感谢您的参考 :) 我赞成这个答案,因为我设法从容器中获取了一个文件。 gist.github.com/kyroy/8453a0c4e075e91809db9749e0adcff2 但我仍然无法发送文件:/
    • 在这种情况下,应该也可以执行相反的命令(尽管我承认我从未尝试过):cat my-local-file | kubectl exec -i $pod -- sh -c 'cat &gt; /the/pod/file'(可能不得不将-t 选项改为exec,因为我不能100% 确定它是否与tty 相关)
    【解决方案2】:

    有使用client-go的代码实现了复制文件到容器,也可以从容器中复制文件。

    https://github.com/ica10888/client-go-helper/blob/master/pkg/kubectl/cp.go

    ├─kubectl
       │  client.go
       │  cp.go
       └─ stub.s
    
    

    客户

    package kubectl 
    
    import (
        corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
        "k8s.io/client-go/rest"
        "k8s.io/client-go/tools/clientcmd"
    )
    
    
    func InitRestClient() (*rest.Config, error, *corev1client.CoreV1Client) {
        // Instantiate loader for kubeconfig file.
        kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
            clientcmd.NewDefaultClientConfigLoadingRules(),
            &clientcmd.ConfigOverrides{},
        )
        // Get a rest.Config from the kubeconfig file.  This will be passed into all
        // the client objects we create.
        restconfig, err := kubeconfig.ClientConfig()
        if err != nil {
            panic(err)
        }
        // Create a Kubernetes core/v1 client.
        coreclient, err := corev1client.NewForConfig(restconfig)
        if err != nil {
            panic(err)
        }
        return restconfig, err, coreclient
    }
    

    copyToPod 和 copyFromPod

    package kubectl
    
    import (
        "archive/tar"
        "fmt"
        "io"
        corev1 "k8s.io/api/core/v1"
        "k8s.io/client-go/kubernetes/scheme"
        "k8s.io/client-go/tools/remotecommand"
        _ "k8s.io/kubectl/pkg/cmd/cp"
        cmdutil "k8s.io/kubectl/pkg/cmd/util"
        "log"
        "os"
        "path"
        "path/filepath"
        "strings"
        _ "unsafe"
    )
    
    func (i *pod) copyToPod(srcPath string, destPath string) error {
        restconfig, err, coreclient := InitRestClient()
    
        reader, writer := io.Pipe()
        if destPath != "/" && strings.HasSuffix(string(destPath[len(destPath)-1]), "/") {
            destPath = destPath[:len(destPath)-1]
        }
        if err := checkDestinationIsDir(i, destPath); err == nil {
            destPath = destPath + "/" + path.Base(srcPath)
        }
        go func() {
            defer writer.Close()
            err := cpMakeTar(srcPath, destPath, writer)
            cmdutil.CheckErr(err)
        }()
        var cmdArr []string
    
        cmdArr = []string{"tar", "-xf", "-"}
        destDir := path.Dir(destPath)
        if len(destDir) > 0 {
            cmdArr = append(cmdArr, "-C", destDir)
        }
        //remote shell.
        req := coreclient.RESTClient().
            Post().
            Namespace(i.Namespace).
            Resource("pods").
            Name(i.Name).
            SubResource("exec").
            VersionedParams(&corev1.PodExecOptions{
                Container: i.ContainerName,
                Command:   cmdArr,
                Stdin:     true,
                Stdout:    true,
                Stderr:    true,
                TTY:       false,
            }, scheme.ParameterCodec)
    
        exec, err := remotecommand.NewSPDYExecutor(restconfig, "POST", req.URL())
        if err != nil {
            log.Fatalf("error %s\n", err)
            return err
        }
        err = exec.Stream(remotecommand.StreamOptions{
            Stdin:  reader,
            Stdout: os.Stdout,
            Stderr: os.Stderr,
            Tty:    false,
        })
        if err != nil {
            log.Fatalf("error %s\n", err)
            return err
        }
        return nil
    }
    
    func checkDestinationIsDir(i *pod, destPath string) error {
        return i.Exec([]string{"test", "-d", destPath})
    }
    
    //go:linkname cpMakeTar k8s.io/kubectl/pkg/cmd/cp.makeTar
    func cpMakeTar(srcPath, destPath string, writer io.Writer) error
    
    func (i *pod) copyFromPod(srcPath string, destPath string) error {
        restconfig, err, coreclient := InitRestClient()
        reader, outStream := io.Pipe()
        //todo some containers failed : tar: Refusing to write archive contents to terminal (missing -f option?) when execute `tar cf -` in container
        cmdArr := []string{"tar", "cf", "-", srcPath}
        req := coreclient.RESTClient().
            Get().
            Namespace(i.Namespace).
            Resource("pods").
            Name(i.Name).
            SubResource("exec").
            VersionedParams(&corev1.PodExecOptions{
                Container: i.ContainerName,
                Command:   cmdArr,
                Stdin:     true,
                Stdout:    true,
                Stderr:    true,
                TTY:       false,
            }, scheme.ParameterCodec)
    
        exec, err := remotecommand.NewSPDYExecutor(restconfig, "POST", req.URL())
        if err != nil {
            log.Fatalf("error %s\n", err)
            return err
        }
        go func() {
            defer outStream.Close()
            err = exec.Stream(remotecommand.StreamOptions{
                Stdin:  os.Stdin,
                Stdout: outStream,
                Stderr: os.Stderr,
                Tty:    false,
            })
            cmdutil.CheckErr(err)
        }()
        prefix := getPrefix(srcPath)
        prefix = path.Clean(prefix)
        prefix = cpStripPathShortcuts(prefix)
        destPath = path.Join(destPath, path.Base(prefix))
        err = untarAll(reader, destPath, prefix)
        return err
    }
    
    func untarAll(reader io.Reader, destDir, prefix string) error {
        tarReader := tar.NewReader(reader)
        for {
            header, err := tarReader.Next()
            if err != nil {
                if err != io.EOF {
                    return err
                }
                break
            }
    
            if !strings.HasPrefix(header.Name, prefix) {
                return fmt.Errorf("tar contents corrupted")
            }
    
            mode := header.FileInfo().Mode()
            destFileName := filepath.Join(destDir, header.Name[len(prefix):])
    
            baseName := filepath.Dir(destFileName)
            if err := os.MkdirAll(baseName, 0755); err != nil {
                return err
            }
            if header.FileInfo().IsDir() {
                if err := os.MkdirAll(destFileName, 0755); err != nil {
                    return err
                }
                continue
            }
    
            evaledPath, err := filepath.EvalSymlinks(baseName)
            if err != nil {
                return err
            }
    
            if mode&os.ModeSymlink != 0 {
                linkname := header.Linkname
    
                if !filepath.IsAbs(linkname) {
                    _ = filepath.Join(evaledPath, linkname)
                }
    
                if err := os.Symlink(linkname, destFileName); err != nil {
                    return err
                }
            } else {
                outFile, err := os.Create(destFileName)
                if err != nil {
                    return err
                }
                defer outFile.Close()
                if _, err := io.Copy(outFile, tarReader); err != nil {
                    return err
                }
                if err := outFile.Close(); err != nil {
                    return err
                }
            }
        }
    
        return nil
    }
    
    func getPrefix(file string) string {
        return strings.TrimLeft(file, "/")
    }
    
    //go:linkname cpStripPathShortcuts k8s.io/kubectl/pkg/cmd/cp.stripPathShortcuts 
    func cpStripPathShortcuts(p string) string
    
    
    

    触摸存根.s

    
    

    【讨论】:

      【解决方案3】:

      由于这个问题的答案已经很老了,我就是这样做的:

      package main
      
      import (
        "bytes"
        "fmt"
        "io"
        "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/apimachinery/pkg/runtime/serializer"
        "k8s.io/cli-runtime/pkg/genericclioptions"
        "k8s.io/client-go/kubernetes"
        "k8s.io/client-go/kubernetes/scheme"
        "k8s.io/client-go/rest"
        "k8s.io/kubectl/pkg/cmd/cp"
        "k8s.io/kubectl/pkg/cmd/exec"
        "log"
        "os"
      )
      
      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) (*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
        err := copyOptions.Run([]string{src, dst})
        if err != nil {
          return nil, nil, nil, fmt.Errorf("Could not run copy operation: %v", err)
        }
        return in, out, errOut, nil
      }
      

      然后你可以像 kubectl cp 一样使用 PodCopyFile

      podExec := podexec.NewPodExec(*restconfig, clientset) // Here, you need to get your restconfig and clientset from either ~/.kube/config or built-in pod config.
      _, out, _, err := podExec.ExecCmd([]string{"ls", "-l", "/tmp"}, "namespace", "podname", "containername")
      if err != nil {
          fmt.Printf("%v\n", err)
      }
      fmt.Println("out:")
      fmt.Printf("%s", out.String())
      

      【讨论】:

        猜你喜欢
        • 2021-08-01
        • 2020-06-20
        • 2020-11-22
        • 2020-07-01
        • 1970-01-01
        • 2018-10-30
        • 2020-09-30
        • 2018-04-17
        相关资源
        最近更新 更多