[英]golang exec command: stream output to stdout *and* capture output in variable
[英]Stream output from remote kubernetes command with golang client
我正在使用 golang kubernetes 客戶端來創建 kubernetes pod 並在其中執行遠程命令。 但是,我發現在遠程執行完成之前我無法獲得有關遠程執行狀態的反饋,因為我無法弄清楚如何流式傳輸遠程命令的日志。 這是我當前執行遠程命令的實現:
func (k *KubernetesClient) RunCommand(ctx context.Context, args *RunCommandArgs) (string, int, error) {
req := k.clientset.CoreV1().RESTClient().Post().Resource("pods").Name(args.ContainerId).Namespace(k.namespace).SubResource("exec").Param("container", CONTAINER_NAME)
scheme := runtime.NewScheme()
if err := v1.AddToScheme(scheme); err != nil {
return "", 0, fmt.Errorf("could not add to scheme: %w", err)
}
parameterCodec := runtime.NewParameterCodec(scheme)
req.VersionedParams(&v1.PodExecOptions{
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
Container: args.ContainerId,
Command: []string{"sh", "-c", args.Command},
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(k.config, "POST", req.URL())
if err != nil {
return "", 0, fmt.Errorf("could not exec command: %w", err)
}
var stdout, stderr bytes.Buffer
var streamErr error
streamErr = exec.Stream(remotecommand.StreamOptions{
Stdin: nil,
Stdout: &stdout,
Stderr: &stderr,
Tty: false,
})
if streamErr != nil {
if strings.Contains(streamErr.Error(), "command terminated with exit code") {
return stderr.String(), 1, nil
} else {
return "", 0, fmt.Errorf("could not stream results: %w", streamErr)
}
}
return stdout.String(), 0, nil
}
在這個實現中,我不知道遠程命令的狀態,直到它完成執行,此時我會立即獲得所有輸出日志。
有沒有辦法在調用exec.Stream
寫入stdout
/ stderr
時讀取它們? 在理想的世界中,我希望能夠逐行打印遠程命令行的輸出。 我注意到bytes.Buffer
有一個接受分隔符的ReadString
方法。 這看起來是一個有用的方法,但我一直無法弄清楚如何使用它。
這只是部分答案,但如果我設置使用以下PodExecOptions
和StreamOptions
那么我會看到每個日志行都被實時打印(注意Tty
是true
,我使用的是標准輸入和標准輸出,而不是自定義緩沖區):
v1.PodExecOptions{
Stdin: true,
Stdout: true,
Stderr: false,
TTY: true,
Container: args.ContainerId,
Command: []string{"sh", "-c", args.Command},
}
和
remotecommand.StreamOptions{
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: nil,
Tty: true,
}
但是,如果我嘗試使用os.Stdin
和os.Stdout
以外的其他東西,那么我永遠不會得到任何日志行。 例如,以下用法不會打印任何內容:
var stdout, stdin bytes.Buffer
var streamErr error
go func() {
streamErr = exec.Stream(remotecommand.StreamOptions{
Stdin: &stdin,
Stdout: &stdout,
Stderr: nil,
Tty: true,
})
}()
time.Sleep(5*time.Second)
log.Info("doing raw string calls on both buffers")
log.Info(stdin.String())
log.Info(stdout.String())
log.Info("starting scan of stdin")
scanner := bufio.NewScanner(&stdin)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
m := scanner.Text()
fmt.Println(m)
}
log.Info("starting scan of stdout")
scanner = bufio.NewScanner(&stdout)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
m := scanner.Text()
fmt.Println(m)
}
log.Info("finished scanning of stdout")
我仍在嘗試弄清楚如何使用自定義緩沖區,以便我可以管理寫入日志的內容,而不是直接管道到標准輸出(我想將一些自定義字段附加到記錄的每一行)。
編輯:好的,我想出了一個有效的解決方案。 這是完整的代碼
type LogStreamer struct{
b bytes.Buffer
}
func (l *LogStreamer) String() string {
return l.b.String()
}
func (l *LogStreamer) Write(p []byte) (n int, err error) {
a := strings.TrimSpace(string(p))
l.b.WriteString(a)
log.Info(a)
return len(p), nil
}
func (k *KubernetesClient) RunCommand(ctx context.Context, args *RunCommandArgs) (string, int, error) {
req := k.clientset.CoreV1().RESTClient().Post().Resource("pods").Name(args.ContainerId).Namespace(k.namespace).SubResource("exec").Param("container", "worker")
scheme := runtime.NewScheme()
if err := v1.AddToScheme(scheme); err != nil {
return "", 0, fmt.Errorf("could not add to scheme: %w", err)
}
parameterCodec := runtime.NewParameterCodec(scheme)
req.VersionedParams(&v1.PodExecOptions{
Stdin: true,
Stdout: true,
Stderr: false,
TTY: true,
Container: args.ContainerId,
Command: []string{"sh", "-c", args.Command},
}, parameterCodec)
exec, err := remotecommand.NewSPDYExecutor(k.config, "POST", req.URL())
if err != nil {
return "", 0, fmt.Errorf("could not exec command: %w", err)
}
var streamErr error
l := &LogStreamer{}
streamErr = exec.Stream(remotecommand.StreamOptions{
Stdin: os.Stdin,
Stdout: l,
Stderr: nil,
Tty: true,
})
if streamErr != nil {
if strings.Contains(streamErr.Error(), "command terminated with exit code") {
return l.String(), 1, nil
} else {
return "", 0, fmt.Errorf("could not stream results: %w", streamErr)
}
}
return l.String(), 0, nil
}
我創建了一個實現io.Writer
接口的結構,並在StreamOptions
結構中使用它。 另請注意,我必須在StreamOptions
結構中使用os.Stdin
,否則只有一行會流回Stdout
。
另外請注意,我不得不削減傳遞給緩沖LogStreamer.Write
,因為它似乎回車或換行符原因與logrus包問題。 這個解決方案還有更多的改進要添加,但它肯定朝着正確的方向前進。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.