英文:
Can't reach stdout from Docker using Go client
问题
我有一个小项目,我的Go服务器会将通过HTTP发送的C文件复制到Docker容器中,然后在容器中进行编译和运行。
然而,我无法获取发送到容器标准输出(stdout)的任何数据。
我已经确定文件被发送到Docker容器中,而且任何编译问题都会显示在错误流(error stream)中。然而,即使我在C程序中通过stderr发送数据,也没有显示任何结果,直到我在Dockerfile中使用了'>&2 echo ""'的方式,这样才能通过流传递数据并且我能够读取它。
现在,正如上面提到的,我只能读取stderr,并且只能通过一个变通方法。有什么想法为什么我不能使用标准方法来做到这一点吗?
Go服务器
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"]
Chain文件
#!/bin/bash
gcc -Wall $F00 -o hello
./hello
>&2 echo ""
英文:
I have a small project in which my go server copies the C files send by http into Docker containers, where they are compiled and run.
However, I am not able to obtain any data send to stdout in the container.
I have determined that file is sent into Docker container, what's more - any problems with compilation are shown on the error stream. However, sending data through stderr in C program also didn't show any results until I have, playing with Dockerfile, used '>&2 echo ""' which somehow pushed data through the stream and I was able to read it.
Right now, as mentioned above, I can only read stderr and solely thanks to a workaround. Any idea why I can't do it using standard methods?
Go server
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"]
Chain file
#!/bin/bash
gcc -Wall $F00 -o hello
./hello
>&2 echo ""
答案1
得分: 2
我相信以下方法可以用来获取正在运行的容器的stdout
和stderr
。
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
中。现在创建两个缓冲区来存储流。
// Demultiplex stdout and stderror
// from the container logs
stdoutput := new(bytes.Buffer)
stderror := new(bytes.Buffer)
现在使用导入的stdcopy
将这两个流保存到缓冲区中。
stdcopy.StdCopy(stdoutput, stderror, data)
if err != nil {
panic(err)
}
这样就可以获取到运行容器的stdout
和stderr
了。
英文:
I believe the following method can be used to obtain the stdout
and stderr
of running containers.
import "github.com/docker/docker/pkg/stdcopy"
import this package from the docker SDK.
data, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true})
if err != nil {
panic(err)
}
get the logs from the running container and store it to data
.Now create two buffers to store the streams.
// Demultiplex stdout and stderror
// from the container logs
stdoutput := new(bytes.Buffer)
stderror := new(bytes.Buffer)
now use the imported stdcopy
to save the two streams to the buffers.
stdcopy.StdCopy(stdoutput, stderror, data)
if err != nil {
panic(err)
}
答案2
得分: 1
你应该考虑将Docker CLI作为一个独立的进程运行,并只读取其stdout和stderr,例如:
cmd := exec.Command("docker", "run", "-v=<volumes-to-mount>", "<image>")
out, err := cmd.CombinedOutput()
if err != nil {
return err
}
// `out` 现在包含了来自Docker的stdout和stderr的合并内容
英文:
You should consider running the Docker CLI as a separate process and just reading its stdout and stderr, for example:
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
答案3
得分: 0
我对Go语言不太熟悉,但是这段代码似乎是从Docker日志流中读取数据。可能是因为你的Docker守护进程配置了不同的日志驱动程序。除了json-file和journald之外,docker logs命令不适用于其他驱动程序。你可以使用以下命令进行检查:
$ docker info |grep 'Logging Driver'
你可以像下面这样在每个容器上更改日志驱动程序:
$ docker run -it --log-driver json-file <image> <command>
在创建容器时,你也可以通过API传递这个参数。
英文:
I'm not familiar with the Go but the code seems to be reading from the docker log stream. It could be that your Docker daemon is configured to use a different log driver. The docker logs command is not available for drivers other than json-file and journald. You can check with the following command:
$ docker info |grep 'Logging Driver'
You can change the logging driver on a per container basis like so:
$ docker run -it --log-driver json-file <image> <command>
You should be able to pass this parameter via the api when creating the container as well.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论