こんにちは。
Part 4はdocker-compose.ymlの解説についてです。
今回の目標
- docker-compose.ymlをなんとなく理解しよう!
- docker compose コマンドの使い方を覚えよう!
なんでdockerを使うの?
前回 docker-compose.yml を作成し、MySQLコンテナを起動しました。しかし
なんでわざわざこんなことをするのでしょうか?
理由の一つとして 「ローカル(自分のPC)からの影響を排除したい」 と言うのがあります。
例えばコンテナを起動せずにもともとPCに入っているMySQLアプリを使ったとします。
自分のPCのMySQLは日本語対応が済んでいる、とします。
開発中は日本語でも問題ありません。すでに日本語対応したMySQLを使っているからです。
しかし、実際のサーバーにMySQLを入れる時に日本語対応ができておらず、アプリを実行するとエラー。
だったら「初めから」の状態のもの、実際のサーバーとできるだけ近いものを準備し、そこから始めた方が後々の問題を回避できます。
だから「自分のPCからの影響を排除したい」ためにDockerを使います。
docker composeを使うのはなんで?
ぶっちゃけ楽だからです。
今回は3つのコンテナを使いますが、docker composeがなければ
$ docker container run --name api \
--rm \
--ごちゃごちゃごちゃごちゃ
$ docker container run --name db \
--rm \
-- ごちゃごちゃごちゃごちゃ
$ docker container run --name mail \
-- ごちゃごちゃごちゃごちゃ
とそれぞれのコンテナを起動しないといけません。
docker composeを使えばdocker-compose.ymlを元に全部いい感じにやってくれます。
docker composeは楽です。楽をするためです。
docker compose コマンドでよく使うやつ
サービスの起動
$ docker compose up -d
# -d バックグラウンドで起動
サービスのログを確認
$ docker compose logs -f
# -f ログを出力し続けてくれます。とりあえずつける、でOK
$ docker compose logs [サービス名] -f
# docker compose logs api -f でapiサービスのログだけ確認できます。
サービスの終了
$ docker compose down
サービスの再起動
$ docker compose restart
$ docker compose restart [サービス名]
# docker compose restart api でapiサービスを再起動させます。
サービスのコマンド実行
$ docker compose exec [サービス名] [コマンド]
# docker compose exec api bash でapiサービスにbashを起動し接続します。
docker-compose.ymlの説明
Part 3で作成したdocker-compose.ymlが次の通りです。
これを元に解説していきます。
version: "3.8"
services:
api:
container_name: login-example-api
build:
dockerfile: Dockerfile
context: .
volumes:
- ".:/app"
ports:
- "8000:8000"
environment:
DB_USER: login-user
DB_PASSWORD: login-pass
DB_NAME: login-db
DB_HOST: db
DB_PORT: 3306
depends_on:
db:
condition: service_healthy
db:
container_name: login-example-db
image: mysql:8.0.33
platform: linux/amd64
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: login-db
MYSQL_USER: login-user
MYSQL_PASSWORD: login-pass
ports:
- "33306:3306"
volumes:
- type: volume
source: login-example-db
target: /var/lib/mysql
- type: bind
source: ./_tools/mysql/conf.d
target: /etc/mysql/conf.d
- type: bind
source: ./_tools/mysql/init.d
target: /docker-entrypoint-initdb.d
healthcheck:
test: ["CMD", "mysqladmin", "ping"]
interval: 5s
timeout: 5s
retries: 5
mail:
container_name: login-example-mail
image: mailhog/mailhog
platform: linux/x86_64
ports:
- "8025:8025"
- "1025:1025"
volumes:
login-example-db:
サービス
services:
api:
~~~
db:
~~~
mail:
~~~
docker composeで「サービスの〜、サービスに〜」と書きましたがこれです。
単なる名前です。今回はapiサービスとdbサービスとmailサービスがあります。
コンテナ1つにつきサービスが1つ対応します。
container_name
services:
api:
container_name: login-example-api
~~~
db:
container_name: login-example-db
~~~
mail:
container_name: login-example-mail
~~~
container_nameは、そのままコンテナの名前です。
例えば今回の場合、
apiサービスはlogin-example-apiと言う名前のコンテナと、
dbサービスはlogin-example-dbと言う名前のコンテナと、
mailサービスはlogin-example-mailと言う名前のコンテナと対応しています。
「別の名前つけて意味あるの?」って思うかもしれません。
使い分けとしては
- docker composeコマンドからはサービス名を指定する
- docker containerコマンドからはコンテナの名前を指定する
そんな感じです。
build & image
services:
api:
~~~
build:
dockerfile: Dockerfile
context: .
~~~
db:
~~~
image: mysql:8.0.33
~~~
mail:
~~~
image: mailhog/mailhog
~~~
サービスの中身です。
例えばdbサービスはimage: mysql:8.0.33なので、中身はMySQLの8.0.33です。
apiサービスみたいにDockerfileで中身を作成したい場合 build を使います。
environment
services:
api:
~~~
environment:
DB_USER: login-user
DB_PASSWORD: login-pass
DB_NAME: login-db
DB_HOST: db
DB_PORT: 3306
~~~
db:
~~~
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: login-db
MYSQL_USER: login-user
MYSQL_PASSWORD: login-pass
~~~
mail:
~~~
~~~
それぞれのサービス内で環境変数を定義してます。
確認してみます。
$ docker compose up -d
# apiサービスに接続してみます。
$ docker compose exec api bash
app/ $ echo $DB_USER
login-user
# 環境変数がちゃんと定義されているのが確認できます。
app/ $ echo $MYSQL_USER
# 何もなし。apiサービスにはdbサービスの環境変数はもちろん定義されてません
app/ $ exit
$ docker compose down
また、
前回dbサービスのMySQLに接続するときに次のコマンドを実行しました。
$ docker compose exec db mysql -u login-user -plogin-pass login-db
なんでこれで接続できたか、というとMySQLのコンテナはこのenvironmentで定義された環境変数を元にMySQLのユーザーなどを事前に作成してくれたからです。
volumes
services:
api:
~~~
volumes:
- ".:/app"
# - ".:/app"は次と同じ意味です
# - type: bind
# source: .
# target: /app
~~~
db:
~~~
volumes:
- type: volume
source: login-example-db
target: /var/lib/mysql
- type: bind
source: ./_tools/mysql/conf.d
target: /etc/mysql/conf.d
- type: bind
source: ./_tools/mysql/init.d
target: /docker-entrypoint-initdb.d
# 次のように書いてもOK
# - login-example-db:/var/lib/mysql
# - ./_tools/mysql/conf.d:/etc/mysql/conf.d
# - ./_tools/mysql/init.d:/docker-entrypoint-initdb.d
~~~
mail:
~~~
~~~
volumes:
login-example-db:
サービス内のデータをいい感じに使いたい時に使います。
2つのtypeがあります。
- サービス内のデータは基本使い捨て。サービスを停止したらサービス内のデータは削除される。
- type: volume
- サービス内のデータを残しておきたいときなんかに使う。
- 例えばテスト用にデータを10万件作成したが、毎回サービスを起動するたびに10万作成するのは面倒くさい、データを使い回したい、みたいな時とかに便利
- 今回はMySQLのデータを使い回すのに使ってます
- type: bind
- コードなど自分のPC内のデータをサービス内で使いたい時に使う
- コードを修正したらコンテナ内のコードも同じように修正されてて欲しい、そんな時に使う
- 今回はGoのコードをapiサービス内に反映させたり、MySQLの設定ファイルをdbサービス内に反映させたり、に使ってます。
ports
services:
api:
~~~
ports:
- "8000:8000"
~~~
db:
~~~
ports:
- "33306:3306"
~~~
mail:
~~~
ports:
- "8025:8025"
- "1025:1025"
~~~
自分のPCのポート番号:サービス内のポート番号を対応させています。
例えばdbサービスの33306:3306というのは自分のPCの33306ポートがdbサービスの3306ポートに対応している、ということです。
depends_on
サービスの起動順序を決めています。
今回のapiサービスのGoのコードではMySQLに接続できなければエラーになるようになっています。
なのでdbサービスのMySQLが接続できるようになってからapiサービスを起動するようにしたい、そんな時に使います。
上記のように記述するとdbサービスが起動してからapiサービスが起動するようになります。
ただ、これだと
- (期待する順番)dbサービスが起動 > MySQLが起動し、接続準備OK > apiサービスが起動しMySQLに接続
- (実際の順番)dbサービスが起動 > MySQLが起動 > apiサービスが起動しMySQLに接続 > MySQLはまだ接続準備ができていないのでエラー
みたいなことになります。
なので、ちょっと変更して
services:
api:
~~~
depends_on:
db:
condition: service_healthy
~~~
db:
~~~
healthcheck:
test: ["CMD", "mysqladmin", "ping"]
interval: 5s
timeout: 5s
retries: 5
~~~
mail:
~~~
~~~
こんな感じにします。
dbサービスがどうなったらapiサービスが起動するか、という条件を加えました。
こうすればMySQLが接続準備OKになってからapiサービスが起動するようになります。
今回はdocker-compose.ymlの解説をしました。
今日は以上です。
ありがとうございました。
次のPart 5は仮登録 /auth/register/initial (1)の解説です。
よろしくお願いいたします。