22時頃から寝ていて2回起きて3時に起き出して、4時までネットみたりしていて、また寝て6時に起きた。生活のリズムがおかしい。

コンテナー間のデータのやり取り

昨日 モジュール分割 したことにより、いままで1つのモジュールで管理していたが、モジュールを分割したのでそれぞれのバージョンを取得できるとよいという話題が出た。コンテナー内にアプリケーションのバイナリがあり、バイナリを実行するとバージョン情報を取得できる。それぞれのモジュールは独立したコンテナで動いてるため、コンテナー間でその情報を受け渡す方法が必要になる。ググってみると次の so がヒットして named pipe がプラクティスだという。

ホスト os 上の named pipe をコンテナーの volumes でマウントして、それぞれのコンテナーが読み書きすればよい。構築時に named pipe さえ作ったらコンテナー内での読み書きでデータ通信を実現できるため、シンプルでよいんじゃないかと思えた。

mypipe という named pipe を作る。

$ mkfifo mypipe

読み込み用コンテナーのための read-Dockerfile を作る。tail コマンドで named pipe を読む。

$ vi read-Dockerfile
From bash:latest

ENTRYPOINT [ "tail", "-f", "/app/mypipe" ]
$ docker build -t mypipe-read:latest -f read-Dockerfile .

named pipe をマウントして読み込み用コンテナーを起動する。

$ docker run --rm --mount type=bind,source="$(pwd)"/mypipe,target=/app/mypipe,readonly mypipe-read

書き込み用のエントリーポイントのスクリプトはちょっと工夫がいる。おそらく Dockerfile 内で直接リダイレクトの操作ができない (やり方がわからなかった) 。シェルスクリプトを呼び出す形にして、シェルスクリプト内部でリダイレクトにより、named pipe に書き込みする。

エントリーポイントのスクリプトは次のような感じ。eval "$@" で任意のコード実行できるようにちょっと細工。

$ vi myentrypoint.sh
#!/bin/sh

cleanup() {
    echo "cleanup ..."
    exit 0
}

trap cleanup INT TERM

while true
do
    echo "$(date)"
    eval "$@"
    sleep 1
done

書き込み用コンテナーのための write-Dockerfile を作る。先の myentrypoint.sh を ENTRYPOINT として起動させる。

$ vi write-Dockerfile
From bash:latest

COPY myentrypoint.sh /app/
RUN chmod +x /app/myentrypoint.sh

ENTRYPOINT [ "/app/myentrypoint.sh" ]
$ docker build -t mypipe-write:latest -f write-Dockerfile .

適当に乱数を生成する cli を eval 実行させる。

$ docker run --rm --mount type=bind,source="$(pwd)"/mypipe,target=/app/mypipe mypipe-write "tr -dc 0-9 < /dev/urandom | fold -w 8 | head -1  > /app/mypipe"

読み込み用コンテナーで乱数を表示できるはず。

なにも難しくなく、linux の標準の機能を使ってコンテナー間のデータ通信を実現できたことにちょっと驚いた。

go で named pipe を読むときは linux ならば syscall.O_NONBLOCK を指定することで書き込みしていなくてもブロックせずに読める。値を取得できない可能性はあるけど、それが許される要件ならこれで済む。またテックブログにまとめたい。

func readNamedPipe(path string) ([]byte, error) {
	flag := os.O_RDONLY | syscall.O_NONBLOCK
	pipe, err := os.OpenFile(path, flag, os.ModeNamedPipe)
	if err != nil {
		return nil, fmt.Errorf("failed to open path: %w", err)
	}
	defer pipe.Close()
	reader := bufio.NewReader(pipe)
	b, _, err := reader.ReadLine()
	if err != nil {
		return nil, fmt.Errorf("failed to read line: %w", err)
	}
	return b, err
}