LoginSignup
1
1

【Go】ログイン機能でウェブアプリを作ってみる(4)

Posted at

こんにちは。

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)の解説です。
よろしくお願いいたします。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1