デフォルトに甘えてComposeのプロジェクト管理を意識せずにいたら、いざという時に意図通りのコンテナ操作ができずにハマったという話です。
前のコンテナを止められない
ある日、僕はとあるサービスのデプロイをしました。
プロジェクトをDocker Composeでまとめ、ssh,pull等のデプロイ作業を1行のコマンドにまとめた。
はじめは動いていましたが…
次のデプロイでコンテナ起動時に問題発生。
ERROR: {省略} Bind for 0.0.0.0:3000 failed: port is already allocated
ERROR: Encountered errors while bringing up the project.
# 本番にsshして確認
$ docker ps
{省略} days ago 0.0.0.0:3000->3000/tcp 0200122040412_1
前のコンテナが起動したままになっており、ポートが被って起動できないというエラーです。
原因はわかりませんが、とりあえずstop。
$ docker-compose stop
$ docker ps
{省略} days ago 0.0.0.0:3000->3000/tcp 0200122040412_1
残ってる…
コンテナとdocker-compose.yamlの関係はどうやって決まる?
ここでdocker-compose
ではなくdocker
コマンドでコンテナを止めるのは簡単ですが、ちょっと待ってください。
普通は同じプロジェクトのdocker-compose.yaml
を使えば、再起動時に自動で前のコンテナをシャットダウンしてくれます。くれてました。
なぜその対応関係が切れてしまったんでしょうか。
コンテナかDockerが記憶しているのかと思いましたが、そんな記述は見当たりません。
現実はもっとシンプルだった。
Docker Composeのプロジェクト名とは
Docker Composeはプロジェクト単位でコンテナを操作します。
プロジェクト(とコンテナ)にはプロジェクト名が付き、コマンド操作(up,run,stop,etc)はそれを参照して行われる。
で、このプロジェクト名は別に設定しなくても動く。
このプロジェクト名の設定はオプションです。設定をしなければ、 COMPOSE_PROJECT_NAME (Composeのプロジェクト名)は、デフォルトではプロジェクトのディレクトリを ベース名 にします。
http://docs.docker.jp/v1.12/compose/reference/envvars.html
デフォルトでディレクトリ名になる。
プロジェクト名を設定しないとどうなる?
[1] 複数のプロジェクトでディレクトリ名がダブると、意図しないコンテナを操作してしまう
(例:日付、バージョン、"app", etc…)
[2] ディレクトリ名を変えるとプロジェクト名が変わり、以前そのディレクトリ内のdocker-compose.yaml
で起動したコンテナを操作できなくなる
こういう問題が発生します!!!
実例:Capistranoでデプロイ
Capistranoはruby製のデプロイ自動化gemで、標準でバージョン管理もしてくれる優れもの。
別にRails専用というわけではないので今回はssh→pull→up -d --buildとかする用途で使っていた。
…が、今回このバージョン管理機能が仇になった。
Capistranoのバージョン管理機能とは
Capistranoのデプロイは、Datetime名のディレクトリを都度作成して行われる。
releases]$ ls
20200122033238 20200122033501 20200122040412 20200122041011 20200122041121
その上で、デプロイ先の指定ディレクトリ「標準ではcurrent
」は最新バージョンへのsymlinkになる。
app]$ ls
current # ←これが実際には20200122041121へのsymlink
まァ、それは良いのだが、この状態でdocker-compose up
すると…
current]$ docker-compose up -d
Starting 20200122033501_db_1 ... done
Starting 20200122033501_web_1 ... done
symlink先の正式なディレクトリ名(=日時のほう)が付けられて起動する。
こうなるともうだめだ
次にCapでデプロイを行うと、それはまた先の日時のディレクトリ名に置かれる。
つまり、 もう別のプロジェクト名になってしまう。
docker-compose up
しても古いコンテナを自動終了してくれず、このようにポートダブりエラーが出る。
(前略 )connectivity on endpoint 20200122041121_web_1 (中略): Bind for 0.0.0.0:3000 failed: port is already allocated
今まで何も考えずに使っていても勝手に古いコンテナを操作できていたのは、ディレクトリ名が同じだったからなんですね。
Capが日時でディレクトリを切るから、それが転記された「プロジェクト名」が固有のものって感覚が全然なかったよ…(言い訳、責任転嫁)
プロジェクト名はちゃんと設定しよう。
ちなみに、コンテナをすべて削除するコマンドはこう。
$ docker rm $(docker ps -qa) -f
-fをつけなかった場合は起動中のものは生き残る。
解決策:プロジェクト名の設定方法
というわけでプロジェクト名を設定します。 yamlファイルに直接は書けません。
.envファイル
docker-compose.yamlと同じ場所に置けばOK。
COMPOSE_PROJECT_NAME=hogefuga_project
ただ、.envファイルはそうgit pushするものではないし、docker-composeだけが使うものでもないことに注意。
コマンドライン引数
docker-composeコマンドに-p
を付けることで、プロジェクト名を指定できる。
docker-compose -p hoge_project up
ただ、毎回引数にプロジェクト名を付けるのは冗長なので、このコマンド自体を何かに登録する前提だろう。
Capistranoのようなデプロイ自動化スクリプトを使用する場合はどんどん使っていこう。
namespace :container do
desc 'Up containers'
task :up do
on roles(:docker) do
execute "cd #{release_path} && docker-compose -p hoge_project up -d"
end
end
end