LoginSignup
6

More than 5 years have passed since last update.

Dockerコンテナの標準入出力をリアルタイムでやりとりする

Last updated at Posted at 2017-12-04

目的

オンラインコンパイラを作る時に標準出力がストリームでヌルヌル出てくれるといいなって思った
https://editor.ugwis.net/

コンテナの標準入出力

docker attachと同じような挙動をするエンドポイントがDocker Engineの/containers/<id or name>/attachに提供されています

ContainerCreate

まず、コンテナの標準入出力を扱うにはContainerCreateのフラグを変える必要があります

main.go
resp, err := cli.ContainerCreate(ctx, &container.Config{
    Image:           lang.Language[query.Language].DockerImage,
    WorkingDir:      "/workspace",
    Cmd:             lang.Language[query.Language].RunCmd,
    NetworkDisabled: true,
    AttachStdin:     true,
    AttachStdout:    true,
    AttachStderr:    true,
    OpenStdin:       true,
    StdinOnce:       true,
    Tty:             false,
}, &container.HostConfig{
    Mounts: []mount.Mount{
        mount.Mount{
            Type:   mount.TypeBind,
            Source: "/tmp/compiler/" + runningHash,
            Target: "/workspace",
        },
    },
    AutoRemove: true,
}, nil, "")

標準入力に関するフラグのみデフォルトでfalseで指定されています。

Ttyがtrueの時は、ContainerAttachのReaderから標準入力の内容も出力されます。用途としてはターミナルのようなものを作る時に標準入力をキーボード、標準出力をコンソール画面に使う場合などが考えられます。

ContainerAttach

標準出力を受け取るにはContainerAttachを呼び出し、io.Readerから標準出力を受け取ります。

送られてくるストリームにはheaderが付いており、

header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}

のような形式になっています。
これは標準出力と標準エラーが同様に流れてくるため、呼び出し側が判別するためにヘッダーが用いられています。
この形式はcontainerLogsメソッドも同じですが公式ドキュメントには書かれていなかった為、意味不明なnull文字に悩まされました

解消するには自分でパースしても良いですが、公式が提供するcopyメソッドを使うとストリーム同士をパイプでうまく繋いでくれます
github.com/docker/docker/pkg/stdcopy.StdCopy

main.go
// Attach container
stdin, err := cli.ContainerAttach(ctx, resp.ID, types.ContainerAttachOptions{
    Stream: true,
    Stdin:  true,
})
defer stdin.Close()
if err != nil {
    fmt.Println(err.Error())
    c.String(http.StatusInternalServerError, err.Error())
    return
}

stdout, err := cli.ContainerAttach(ctx, resp.ID, types.ContainerAttachOptions{
    Stream: true,
    Stdout: true,
})
defer stdout.Close()
if err != nil {
    fmt.Println(err.Error())
    c.String(http.StatusInternalServerError, err.Error())
    return
}

ContainerAttach(WebSocket)

また、clientのライブラリには実装されていませんがdocker Engine側にWebSocketのエンドポイントが提供されており、ブラウザと接続する際はこれに繋ぐことで入出力が楽になると思います

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
6