1

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

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

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

4

3 回答 3

7

有使用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


触摸存根


于 2019-09-16T08:18:04.220 回答
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-11-05T23:04:33.070 回答
1

具有讽刺意味的是,今天有人支持我对 Java 的这个问题的回答。我还没有打开repo 看看,但如果它比 Java 库更client-go暴露命令,我会非常非常惊讶。cp

于 2018-08-05T03:58:52.723 回答