白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

Docker ComposeでMySQLとnginxのコンテナを立てる

前回の続きです。

参考にさせていただきました。
zenn.dev
note.com

今回はDocker Composeを使うので構築まであれこれ試行錯誤はしたものの、記事にすると手順自体はdocker-compose.ymlをぺたりと貼るだけのところに着地します。

DockerfileとDocker Compose

Dockerのサイトによると、
Dockerfileは「イメージを自動で作成するためのコマンドを含んだテキストファイル」という感じでしょうか。

Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. This page describes the commands you can use in a Dockerfile.

Docker Composeは「複数コンテナーのDockerアプリケーションを定義して実行するためのツール」ということですかね。

Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.

というわけで文章だけ見ると複数のコンテナを使う場合だけDocker Composeを使い、単一の場合にはDockerfileを使うと良いみたいに取れますが、実際には単一のイメージを使う場合にもDocker Composeは便利ですし、DockerfileとDocker Composeを組み合わせて使うこともよくあるようです。

どちらかというとDockerfileはイメージを作る作業を自動化したもので、Docker Composeは作ったイメージをどういう設定で動かすか指定するものくらいでいいんじゃないでしょうか。
もちろんその中に複数イメージをどう連携させるかっていう話も入ってくるのですが…。
今回はイメージは基本的に配布されているものをそのままで、dockerコマンドの引数的なものを全部Docker Composeに入れるようなイメージで作っていきます。

ネットワークを作る

docker-compose.ymlにサービスを複数書くと勝手にネットワークができてコンテナ間の参照ができます。
が、今回は1つのサービスとして全部セットでdocker-compose.ymlに書く方式は取らないため、普通にネットワークを作ってそこに参加させる方式を取ります。

ひとまずDBを参照できるネットワークとnginxを経由してHTTPアクセスできるWebのネットワークを作成します。
とりあえず既存のネットワークを見てみると、何もないかと思いきや「bridge」「host」「none」の3つのネットワークが最初からあります。
これらについては詳しく解説してくれているサイトなどがあるので特には触れません。

$ docker network ls
NETWORK ID     NAME    DRIVER    SCOPE
440f88691de8   bridge  bridge    local
c9d361d10742   host    host      local
92c07b77e335   none    null      local

DB用とWeb用のネットワークを作りました。何も指定しないとbridgeネットワークになります。

$ docker network create db_network
$ docker network create web_network
$ docker network ls
NETWORK ID     NAME                       DRIVER    SCOPE
440f88691de8   bridge                     bridge    local
46521ed7ae0a   db_network                 bridge    local
c9d361d10742   host                       host      local
92c07b77e335   none                       null      local
6ea3a2e26663   web_network                bridge    local

docker-compose.ymlを書く

まず好きなところにディレクトリを作ります。
作ったディレクトリにdocker-compose.ymlを置きます。

はい。

version: '3.8'
services:
  mysql:
    image: mysql: 8.0.32
    container_name: mysql
    ports:
      - 3306:3306
    environment:
      TZ: "Asia/Tokyo"
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: db
      MYSQL_USER: docker
      MYSQL_PASSWORD: docker
    volumes:
      - ./data:/var/lib/mysql
    networks:
      - db_network
networks:
  db_network:
    external: true

ちょっと解説。
servicesの下に登録したいサービスを書きます。複数書いたら複数同時にコンテナを立ち上げられます。
imageにはDockerImageを指定します。Dockerfileを指定する場合はbuildを使います。ここでは省略。
container_nameはコンテナ名です。docker psで表示されるコンテナ名や、他のコンテナから参照するときの名前です。
portsはホスト側のポートとコンテナ内のポートを繋ぎます。この例だとホストの3306ポートへのアクセスはこのコンテナに転送されます。(アプリケーションで使うだけならホスト側にポートを繋ぐ必要はないかもしれませんが、普通に中身が見たいので設定)
environmentは環境変数です。利用したいDockerImageごとに使うものはバラバラですが、ここに初期値を与えることでコンテナ起動時に色々勝手にやってくれることが多いです。(MySQLの場合はrootのパスワードや初期作成するDBの情報が書けます。複数DB作るのはDockerfileでやってねってことかな…?)
volumesはボリュームのマウント設定です。ホスト側のディレクトリをコンテナ内に繋ぐときに使います。後ろに「:ro」をつけると読み取り専用になります。
networksは参加させるネットワーク名です。今回は既に作成してあるdb_networkを使います。(外にあるネットワークなのでexternal: trueが必要)

で、はい。

version: '3.8'
services:
  nginx:
    image: nginx:1.23.3
    container_name: nginx
    ports:
      - 80:80
    environment:
      TZ: "Asia/Tokyo"
    volumes:
      - ./config/default.conf:/etc/nginx/conf.d/default.conf
      - ./log:/var/log/nginx
      - ./html:/var/www/html
    networks:
      - web_network
networks:
  web_network:
    external: true

設定をいじったり、ログを外側に吐いたり、htmlを置いたりできるようにマウントしているくらいですかね。

default.confは一旦こんな感じでマウント元のディレクトリにおいておきます。
とりあえず./htmlにindex.htmlとか置くと見れます。他にはなんのアプリケーションもないので404。

server {
    listen 80;		# IPv4
    listen [::]:80;		# IPv6
    server_name ubuntu-server;	# 受け付けるホスト

    # なんかもろもろのヘッダ
    proxy_set_header   Host              $host;
    proxy_set_header   X-Real-IP         $remote_addr;
    proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
    proxy_set_header   X-Forwarded-Host  $host;
    proxy_set_header   X-Forwarded-Port  $server_port;

    root /var/www/html;	# 静的ページのroot
    
    # 400番台エラーのハンドリング(root上書きしてるので戻す)
    error_page 404 /404.html;
    location = /40x.html {
        root /usr/share/nginx/html;
    }
    
    # 500番台エラーのハンドリング
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}

これでそれぞれのディレクトリで以下のコマンドを叩くとコンテナが立ち上がります。

$ docker compose up

が、フォアグラウントで実行しても困るので、バックグラウンドで実行する場合はdオプションを付けます。

$ docker compose up -d

バックグラウンドだとログとか何も見えないので、ログが見たい場合には「docker logs」コマンド。

$ docker logs nginx
~()~
/docker-entrypoint.sh: Configuration complete; ready for start up

これでDBを使うときはdb_networkに、nginxの裏側でWebサービスを公開する場合にはweb_networkに繋げばOKという状態になりました。