いろいろあって Docker Hub 公式の PostgreSQL イメージ を使うことになりました。
序章: Docker Hub 公式 PostgreSQL イメージについて
FROM postgres:9.4
と書いた Dockerfile を用意して docker build
すれば、あっという間に PostgreSQL 9.4 がインストールされたコンテナが作成できます。便利!
ただしデータベースクラスタは未作成です。docker run
した時に初めて initdb
されるような感じです。
また、コンテナの /docker-entrypoint-initdb.d/
以下に *.sh
もしくは *.sql
を配置しておくと、docker run
した時に それらが実行されるようになっています。例えば
みたいな SQL ファイルを Dockerfile と同じ場所に配置して
FROM postgres:9.4 COPY test.sql /docker-entrypoint-initdb.d/
みたいな Dockerfile を使って docker build
、しかるのち docker run
をしてみると
$ docker build -t test-db . $ docker run -d --name gongodb test-db $ docker exec gongodb psql -U postgres gongo -c "SELECT * FROM users" name | age -------+----- gongo | 999 du | 666 (2 rows)
こんな感じで、起動時に必要なデータが投入されてコンテナを使い始めることができる、というわけです。
本当にやりたかったこと
さきほど述べたとおり、この PostgreSQL イメージは docker run
した時に初めて initdb
する設計になっています。
つまり、このままでは データベースクラスタが作成された状態のコンテナ は docker build
では作成できません。
「docker run
のタイミングでデータ投入されてもいいのでは」とかはとりあえず考えないでください。
ではどうするか。
PostgreSQL インストールして
initdb
までやり切る Dockerfile を自分で書き直すのか→ めんどくさい
/docker-entrypoint-initdb.d/
以下に配置したファイルを読み込んでくれるという仕組みはそのまま利用したい→ つまり docker-entrypoint.sh はそのまま使いたい
というわけで、最終的には docker run した時にしか実行されない docker-entrypoint.sh を docker build の時にも実行してあげればいいんじゃないか ということになりました。
docker-entrypoint.sh
は データベースクラスタが作成済みであれば 、何度呼ばれても initdb は実行しない (postgresql サービスを起動するだけ) という仕様になっているので、docker build
の時にやっちゃっても問題ないだろう、ぐらいです。
完成品
こうなりました
https://gist.github.com/gongo/531459a34e814b5c94bb
まずは Dockerfile
ここでは環境変数 PGDATA
をセットしています。
なぜかというと、PostgreSQL イメージ作成時の Dockerfile では、$PGDATA
が Data Volume のディレクトリとして指定されており、docker-entrypoint.sh
は initdb -D $PGDATA
を行うようになっています。
この状態では、例え docker build
時に initdb
を行っても、次に docker run
する時には $PGDATA
が空っぽになっているため、悲しいことになります。
そんなわけで、$PGDATA を Data Volume として指定されていないディレクトリ に指定し直しています。
次に restore.sh
つまり何をしているかというと initdb が終わったのを確認したら docker-entrypoint.sh
に対して SIGTERM を投げつける ようにしています。
当初の Dockerfile は
このように、とりあえず docker-entrypoint.sh
実行しとけばいいんだろ、ぐらいの考えだったのですが、
docker-entrypoint.sh
は、initdb
だけではなく PostgreSQL サービスの起動も兼ねているため
RUN /docker-entrypoint.sh
initdb
とかされて、test.sql
も呼ばれる!!やったぜ!!initdb
終わったら postgresql サービス起動するぜ_人人人人人人人人人人人人人人_ > docker build が終わらない <  ̄YYYYYYYYYYYYYY ̄
となって、Ctrl-c とかしないと docker build
から戻ってきません。そして Ctrl-c すると 正常に docker build が完了されなかった と視なされ、コンテナが作成されていない、という事態になります*1
ではどうするか。initdb
まではやって欲しい。でもそのあとの exec gosu postgres "$@"
は終了して欲しい。
そんな願いから生まれました。
- 最終的に止めたいプロセスは、
exec gosu postgres "$@"
の exec の効果によりdocker-entrypoint.sh
と同じ PID になるため、まずはそれを取得 /docker-entrypoint.sh
の標準出力を監視し、initdb
が 終わったと思われる出力が見つかった ら、監視(while read)をやめる- 1 で取得した PID (つまり PostgreSQL サービス) に対して SIGTERM を発行
- 最終的に
/docker-entrypoint.sh
は 正常に終了した と判断され、docker build
が完了
という感じになりました。
mkfifo
使ったのはまあいろいろあったんですが、結果的にこれでうまく動いてるので良しとしています。
最後の sleep 3
は、exec gosu postgres "$@"
で PostgreSQL が起動するのを待つためのものです。もしかしたら要らないかも。
まとめ
別に docker run
のタイミングでリストアしてもいいだろ。なに?「docker run
のタイミングでリストアするのに時間かかる巨大なデータがあると、コンテナが起動しても
実際にデータが作られるまで時間かかるから、すぐに使いたいと思ってもデータができてない可能性がある」だと?そんな巨大なデータは Data Volume Container に置いとけば
いいじゃないか。なに?コンテナ一つだけだと足りないだと?そんな我侭(ry
みたいな自問自答は今でもあるのですが、ひとまず数行のコードで目的が達成できたので良しとします。
restore.sh
の方では mkfifo
つかったりしてるんですが、もしかしたらもっと簡単に書けるかも。
あと、「Data Volume で指定されてるから〜」のところもちゃんと検証はしていません。セットしなおさなくても大丈夫かも
とりあえず終わり。
経緯
そもそもなんで データベースクラスタ作成済みコンテナイメージ が欲しかったのか。本題ではないので最後に持ってきました。
あなたの安心を高速に守る Container-based CI
現在職場で用いている CI を使ったテスト環境(コンテナの構成)は、上のスライドの通り
- test-queue で放ったテストケースを Selenium Hub が受け止める
- Selenium Hub に接続している Selenium Node にテストケースが割り振られる
- それぞれの Selenium Node が、起動時に「こいつ使え」と教えられた App に対してアクセスする
- PHP (Apache) + DB が動いているコンテナ上で Selenium の命令が実行される
このようにして、並列実行でよくある 違うテストケースが同じデータを触ってテストが失敗する みたいなのを防いでいました。
今のところ順調なのですが、最近こうしたいなーということをチーム内で話し合っていました。
つまり、これまで 「App+DBコンテナ」としていたものを
- PHP(Apache) だけが起動している Web アプリケーションコンテナ
- PostgreSQL だけが起動している テストデータリストア済みの DB コンテナ
の2つに分割したい、というものです。
- 「テストデータだけ差し替えたいのに App インストールも含めてコンテナ作り直すのちょっとな」
- 「DaaS (Databaee の方) に近い環境に併せたいので、やはり App とは分離しておかないと」
とかいろいろ理由はありましたが、とりあえずやってみよう、ということになりました。
そんなわけで
- PostgreSQL の Dockerfile を一から書くのめんどくさいし Docker Hub 公式のやつあるからこれ使おう-
- あーでも、これテストデータリストアされた状態のコンテナが作れないなーうーん
といった悩みから最初に戻ります。
参考
*1:もしかしたら none で image はできてるかも