Docker Composeでは他コンテナ内のアプリケーションが起動し終わるのを待って次のコンテナを起動するというのはそのままではできないです。

コンテナ自体の起動順制御


起動順序自体はdepends_onで制御することができます。この起動順序はあくまで コンテナの起動順序 であって 他のコンテナ内のアプリケーションが起動するまで待つことはできないです。

例えば下記のようなnginxとgitbucketを連携させるようなdocker-compose.ymlがあったとします。

1
version: '2'
services:
  nginx:
    build: ./nginx
    container_name: nginx      
    hostname: nginx    
    networks:
      - app_net
    ports:
      - "8080:8080"
    command: ["nginx", "-g", "daemon off;"]
  gitbucket:
    build: ./gitbucket
    container_name: gitbucket    
    hostname: gitbucket    
    networks:
      - app_net
    expose:
      - "8080"
      - "29418"
networks:
  app_net:
    driver: bridge

で、nginx.confには下記のようにgitbucketというホスト名でリバースプロキシ設定が書いてあるとします。

1
2
3
4
5
6
7
8
9
10
server {
listen 8080;
server_name gitbucket;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

location / {
proxy_pass http://gitbucket;
}
}

上記のような場合、nginxコンテナはgitbucketというホスト名のコンテナが先に起動していないと「nginx host not found in xxxx」みたいな感じで起動に失敗して落ちます。正確には毎回落ちるわけではなくて、先にnginxコンテナが起動した時だけ発生します。Docker composeでは特に指定しない限りではこのコンテナの起動順序はランダム(?)のようです。

この場合の対応は簡単でnginxコンテナにdepends_onというキーを足してgitbucketと記述すればよいです。
具体的には下記のような感じ。

1
nginx:
  build: ./nginx
  container_name: nginx      
  hostname: nginx    
  networks:
    - app_net
  ports:
    - "8080:8080"
  depends_on:      # このキーを足す
    - gitbucket    # 依存するコンテナを書く
  command: ["nginx", "-g", "daemon off;"]

これで、nginxコンテナはgitbucketコンテナの後に起動するようになるので、ホスト名が見つからないというエラーで落ちることはなくなります。ただし、前述の通りdepends_onはあくまで 他のコンテナの起動を待つだけ であって コンテナ内のアプリケーションを起動するまで待つことはできないです。

コンテナ内のアプリケーションの起動を待つ


じゃあ、前述の問題にどう対応するか…ですが…

例えば、他コンテナ内のアプリケーションの起動を待つ必要があるものの代表格といえばたぶんデータベースだと思います。というより、自分が作ってるものだとそれ以外思いつかない…

指定した秒だけ待つ

これに対応するには一番手っ取り早いのがコンテナの起動時にx秒待つみたいなやり方ではないかと思います。待機させたいコンテナのDockerfileのENTRYPOINTでスクリプトを指定してそのスクリプトで指定した時間だけ待機させます。

下記のようなものをENTRYPOINTで呼び出すスクリプトに記述します。

1
2
3
sleep 10

# この後にアプリケーション起動の処理入れたり、データベースに接続するような処理を書いたりする

上記のx秒はdocker-compose.ymlから変数で指定できるようにしたほうが良いと思います。
ただ、これの問題点は指定した秒数経過するとその後の処理が実行されてしまうので、正確に起動を待つことができないですし、長い秒数を設定するとそれだけコンテナの起動に時間が掛かるのでイライラします。

起動するまで待機するには…

これに対応するには、たぶん、ループで他のコンテナのアプリケーションが起動するまで待機するようなシェルスクリプトを書くしかないような…気がする…例えばデータベースの場合だとmysqladmin pingみたいなので確認するとかです。

以下は実際にdocker-redmine-orchestrationというdocker-composeを使用した自作のRedmineコンテナ内のENTRYPOINTのシェルスクリプトで使用しているものです。

1
2
3
4
5
6
7
8
9
10
11
if [ -n "$DB_PING_USER" ]; then
echo `date '+%Y/%m/%d %H:%M:%S'` $0 "[INFO] Connection confriming..."
while :
do
result=`/usr/bin/mysqladmin ping -h mariadb -u${DB_PING_USER} -p${DB_PING_USER_PASSWORD}`
if echo $result|grep 'alive'; then
break
fi
sleep 3;
done
fi

docker-compose.yml内でデータベースに対してpingを飛ばすユーザーとそのユーザーのパスワードを指定しておいて、コンテナ起動時にデータベース(上記の場合はmariadbというホスト名のコンテナ)に対して3秒ごとにmysqladmin pingを実行しています。で、帰ってきた文字列に「aliveが含まれる = DBが起動する」までループでグルグル回してます。

この方法はデータベース以外にも応用が利くのではないかと思います。

まとめ


なんか「結局シェルスクリプトでやるんかい!」みたいな地味な方法しかなくて「めんどくせ~な~」という感想しか出てこないのですが、もっと簡単な方法ないんでしょうかねぇ…