コンテナー間のデータ通信と named pipe
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
}