无法使用Go客户端从Docker访问stdout

我有一个小的项目,我的go服务器将通过http发送的C文件复制到Docker容器中,在那里它们被编译和运行。 但是,我无法获得任何数据发送到容器中的标准输出。

我已经确定该文件被发送到Docker容器,更重要的是,任何编译问题都会显示在错误stream中。 然而,在C程序中通过stderr发送数据也没有显示任何结果,直到我有玩Dockerfile,使用'>&2回声“”'以某种方式推送数据通过stream,我能够读取它。

现在,如上所述,我只能阅读stderr,只能感谢一个解决方法。 任何想法,为什么我不能用标准方法做到这一点?

去服务器

package main import ( "fmt" "net/http" "io" "os" "os/exec" "log" "encoding/json" "github.com/docker/docker/client" dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "golang.org/x/net/context" "time" "bytes" ) type Result struct { CompilationCode int RunCode int TestsPositive int TestsTotal int } func upload(w http.ResponseWriter, r *http.Request) { log.Println("method:", r.Method) if r.Method == "POST" { log.Println("Processing new SUBMISSION.") // https://github.com/astaxie/build-web-application-with-golang/blob/master/de/04.5.md r.ParseMultipartForm(32 << 20) file, handler, err := r.FormFile("file") if err != nil { fmt.Println(err) return } defer file.Close() baseName:= os.Args[1] f, err := os.OpenFile(baseName+handler.Filename, os.O_WRONLY|os.O_CREATE, 777) if err != nil { fmt.Println(err) return } defer f.Close() io.Copy(f, file) if err != nil { fmt.Println(err) return } compilationCode, runCode, testsPositive, testsTotal := processWithDocker(baseName + handler.Filename, handler.Filename) result := Result{ CompilationCode: compilationCode, RunCode: runCode, TestsPositive:testsPositive, TestsTotal:testsTotal, } resultMarshaled, _ := json.Marshal(result) w.Write(resultMarshaled) } else { w.Write([]byte("GO server is active. Use POST to submit your solution.")) } } // there is assumption that docker is installed where server.go is running // and the container is already pulled // TODO: handle situation when container is not pulled // TODO: somehow capture if compilation wasn't successful and // TODO: distinguish it from possible execution / time limit / memory limit error // http://stackoverflow.com/questions/18986943/in-golang-how-can-i-write-the-stdout-of-an-exec-cmd-to-a-file func processWithDocker(filenameWithDir string, filenameWithoutDir string) (int, int, int, int) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() cli, err := client.NewEnvClient() if err != nil { panic(err) } var hostVolumeString = filenameWithDir var hostConfigBindString = hostVolumeString + ":/WORKING_FOLDER/" + filenameWithoutDir var hostConfig = &container.HostConfig{ Binds: []string{hostConfigBindString}, } resp, err := cli.ContainerCreate(ctx, &container.Config{ Image: "tusty53/ubuntu_c_runner:twelfth", Env: []string{"F00=" + filenameWithoutDir}, Volumes: map[string]struct{}{ hostVolumeString: struct{}{}, }, }, hostConfig, nil, "") if err != nil { panic(err) } if err := cli.ContainerStart(ctx, resp.ID, dockertypes.ContainerStartOptions{}); err != nil { panic(err) } fmt.Println(resp.ID) var exited = false for !exited { json, err := cli.ContainerInspect(ctx, resp.ID) if err != nil { panic(err) } exited = json.State.Running fmt.Println(json.State.Status) } normalOut, err := cli.ContainerLogs(ctx, resp.ID, dockertypes.ContainerLogsOptions{ShowStdout: true, ShowStderr: false}) if err != nil { panic(err) } errorOut, err := cli.ContainerLogs(ctx, resp.ID, dockertypes.ContainerLogsOptions{ShowStdout: false, ShowStderr: true}) if err != nil { panic(err) } buf := new(bytes.Buffer) buf.ReadFrom(normalOut) sOut := buf.String() buf2 := new(bytes.Buffer) buf2.ReadFrom(errorOut) sErr := buf2.String() log.Printf("start\n") log.Printf(sOut) log.Printf("end\n") log.Printf("start error\n") log.Printf(sErr) log.Printf("end error\n") var testsPositive=0 var testsTotal=0 if(sErr!=""){ return 0,0,0,0 } if(sOut!=""){ fmt.Sscanf(sOut, "%d %d", &testsPositive, &testsTotal) return 1,1,testsPositive,testsTotal } return 1,0,0,0 } // Creates examine directory if it doesn't exist. // If examine directory already exists, then comes an error. func prepareDir() { cmdMkdir := exec.Command("mkdir", os.Args[1]) errMkdir := cmdMkdir.Run() if errMkdir != nil { log.Println(errMkdir) } } func main() { prepareDir() go http.HandleFunc("/submission", upload) http.ListenAndServe(":8123", nil) } 

Dockerfile

 FROM ubuntu ENV DEBIAN_FRONTEND noninteractive RUN apt-get update && \ apt-get -y install gcc COPY . /WORKING_FOLDER WORKDIR /WORKING_FOLDER CMD ["./chain"] 

链文件

 #!/bin/bash gcc -Wall $F00 -o hello ./hello >&2 echo "" 

您应该考虑将Docker CLI作为单独的进程运行,只需读取其stdout和stderr,例如:

 cmd := exec.Command("docker", "run", "-v=<volumes-to-mount>", "<image>") out, err := cmd.CombinedOutput() if err != nil { return err } // `out` now contains the combined stdout / stderr from Docker 

我不熟悉Go,但代码似乎是从docker日志stream中读取。 可能是因为你的Docker守护进程被configuration为使用不同的日志驱动程序。 docker logs命令不适用于除json-file和journald以外的其他驱动程序。 您可以使用以下命令进行检查:

 $ docker info |grep 'Logging Driver' 

您可以在每个容器的基础上更改日志logging驱动程序,如下所示:

 $ docker run -it --log-driver json-file <image> <command> 

你也应该可以在创build容器的时候通过api传递这个参数。

我相信下面的方法可以用来获得正在运行的容器的stdoutstderr

 import "github.com/docker/docker/pkg/stdcopy" 

从docker SDK导入这个包。

  data, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true}) if err != nil { panic(err) } 

从正在运行的容器中获取日志并将其存储到data 。现在创build两个缓冲区来存储stream。

  // Demultiplex stdout and stderror // from the container logs stdoutput := new(bytes.Buffer) stderror := new(bytes.Buffer) 

现在使用导入的stdcopy将两个stream保存到缓冲区。

  stdcopy.StdCopy(stdoutput, stderror, data) if err != nil { panic(err) }