Thanks Driven Life

日々是感謝

Docker Hub 公式 PostgreSQL イメージを用いて、データベースクラスタ作成済みコンテナイメージを作成する

いろいろあって 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.shinitdb -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 サービスの起動も兼ねているため

  1. RUN /docker-entrypoint.sh
  2. initdb とかされて、test.sql も呼ばれる!!やったぜ!!
  3. initdb 終わったら postgresql サービス起動するぜ

     _人人人人人人人人人人人人人人_
     > docker build が終わらない <
      ̄YYYYYYYYYYYYYY ̄
    

となって、Ctrl-c とかしないと docker build から戻ってきません。そして Ctrl-c すると 正常に docker build が完了されなかった と視なされ、コンテナが作成されていない、という事態になります*1

ではどうするか。initdb まではやって欲しい。でもそのあとの exec gosu postgres "$@" は終了して欲しい。

そんな願いから生まれました。

  1. 最終的に止めたいプロセスは、exec gosu postgres "$@" の exec の効果により docker-entrypoint.sh と同じ PID になるため、まずはそれを取得
  2. /docker-entrypoint.sh の標準出力を監視し、initdb終わったと思われる出力が見つかった ら、監視(while read)をやめる
  3. 1 で取得した PID (つまり PostgreSQL サービス) に対して SIGTERM を発行
  4. 最終的に /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 を使ったテスト環境(コンテナの構成)は、上のスライドの通り

f:id:gongoZ:20150804220614g:plain

  1. test-queue で放ったテストケースを Selenium Hub が受け止める
  2. Selenium Hub に接続している Selenium Node にテストケースが割り振られる
  3. それぞれの Selenium Node が、起動時に「こいつ使え」と教えられた App に対してアクセスする
  4. PHP (Apache) + DB が動いているコンテナ上で Selenium の命令が実行される

このようにして、並列実行でよくある 違うテストケースが同じデータを触ってテストが失敗する みたいなのを防いでいました。

今のところ順調なのですが、最近こうしたいなーということをチーム内で話し合っていました。

f:id:gongoZ:20150804221126p:plain

つまり、これまで 「App+DBコンテナ」としていたものを

  • PHP(Apache) だけが起動している Web アプリケーションコンテナ
  • PostgreSQL だけが起動している テストデータリストア済みの DB コンテナ

の2つに分割したい、というものです。

  • 「テストデータだけ差し替えたいのに App インストールも含めてコンテナ作り直すのちょっとな」
  • 「DaaS (Databaee の方) に近い環境に併せたいので、やはり App とは分離しておかないと」

とかいろいろ理由はありましたが、とりあえずやってみよう、ということになりました。

そんなわけで

  • PostgreSQL の Dockerfile を一から書くのめんどくさいし Docker Hub 公式のやつあるからこれ使おう-
  • あーでも、これテストデータリストアされた状態のコンテナが作れないなーうーん

といった悩みから最初に戻ります。

参考

*1:もしかしたら none で image はできてるかも