docker-composeでNode.jsコンテナとMySQLコンテナを立ち上げる

# 目的
`docker-compose`を使用して、アプリコンテナとDBコンテナを起動する。
コンテナ起動のみを目的とするため、`docker-compose`の動作を確認することをメインに進める。

## 目標
- DBコンテナ作成
- Nodeコンテナ作成とHello, World.
- DBにテーブル作成・初期データ投入

# 起動方法
以下コマンドで`docker-compose.yml`をもとに起動される
```shell
docker-compose up -d
```

# 実装手順
## DBコンテナを作成して、DBが作成されているか確認
`docker-compose.yml`でMySQLコンテナの作成
```yml docker-compose.yml
version: '2.1'
services:
  db:
    # イメージの指定（オフィシャルMySQLイメージ）
    image: mysql:8.4.0

    # コンテナ名
    container_name: mysql-container

    # ポートの紐づけ
    ports: 
      - "3306:3306"

    # 環境変数の設定
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: basic_compose
      TZ: "Asia/Tokyo"

    # MySQLの設定をmy.cnfで指定。
    # my.cnfを以下でインスタンス内`/etc/mysql/conf.d/my.cnf`に配置
    volumes:
      - ./SQL/my.cnf:/etc/mysql/conf.d/my.cnf
```

`my.cnf`でMySQLの設定を指定。
```cnf ./SQL/my.cnf
[mysqld]
character_set_server = utf8mb4
collation_server = utf8mb4_ja_0900_as_cs # 照合順序

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4
```

この段階でターミナルで`docker-compose up -d`を実行することでMySQLコンテナの作成と、DBの作成がされる。 <br>
`docker-compose.yml`で指定した`MYSQL_DATABASE`が作成されている。
```sql
$ docker exec -it e360ca59b925 bash
bash-5.1# mysql -u root -p
mysql: [Warning] World-writable config file '/etc/mysql/conf.d/my.cnf' is ignored.
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.4.0 MySQL Community Server - GPL

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| basic_compose      |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql>
```

DB追加されているが、なんかWarningが出てる。<br>
> mysql: [Warning] World-writable config file '/etc/mysql/conf.d/my.cnf' is ignored.

よくわからないけど、追加した`my.cnf`が無視されているっぽい。<br>
調べてみると、パーミッションで全員が書き込み可能になっているから無視されているらしいので、`chmod 644`などに変えてみると解決しそう。

コンテナ起動後にコマンド実行が必要になるので、めんどくさいけどMySQL用のDockerfileを作成して`chmod`する。<br>
MySQL用のDockerfile
```Dockerfile ./SQL/Dockerfile
FROM mysql:8.4.0

# my.cnfを以下でインスタンス内`/etc/mysql/conf.d/my.cnf`に配置 
COPY ./my.cnf /etc/mysql/conf.d/my.cnf
# my.cnfの権限を変更
RUN chmod 644 /etc/mysql/conf.d/my.cnf

EXPOSE 3306
```

これにより変更を加えた`docker-compose.yml`
```yml docker-compose.yml
version: '2.1'
services:   
  db:
    # ./SQL配下にあるDockerfileからビルド
    build: ./SQL

    # コンテナ名
    container_name: basic-mysql-container

    # ポートの紐づけ
    ports: 
      - "3306:3306"

    # 環境変数の設定
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: basic_compose
      TZ: "Asia/Tokyo"
```

こんな感じで、再度`docker-compose up -d --build`する。 <br>
`--build`はオプションで、ビルドの強制をする。

以下変更後のMySQLコンテナ。Warningが消えてる。
```sql
$ docker exec -it 1d077fb30dec bash
bash-5.1# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.4.0 MySQL Community Server - GPL

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| basic_compose      |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)
```

## nodeでhello, worldして、docker-composeで動かす
`npm init`でプロジェクトを初期化 <br>
`npm install express ejs --save`で`express`と`ejs`をインストール <br>
バックエンド用の`app.js`を`./backend-src`配下に作成する。
```js ./backend-src/app.js
// expressモジュールのロード、インスタンス化してappに代入
var express = require("express")
var app = express()

// View EngineにEJSを指定。
app.set('view engine', 'ejs');

// viewsディレクトリを変更
// デフォルトは`./views`
app.set('views', './resources/views')

// listen()メソッドでリッスンポートの指定
var server = app.listen(3000, function() {
  console.log("Node.js is listening to PORT:" + server.address().port)
})

// 移行、アプリケーション処理
app.get("/", function(req, res, next) {
  res.render("index.ejs")
})
```
`./resources/views`配下に静的ファイル`index.ejs`を配置<br>
今回はHello, Worldのみ
```ejs ./resources/views/index.ejs
Hello, world.
```

アプリケーションコンテナビルド用のDockerfileをアプリケーションディレクトリ直下に作成する。
```Dockerfile
FROM node:18.18.0

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY ./backend-src ./backend-src
COPY ./resources ./resources


EXPOSE 3000
CMD ["node", "./backend-src/app.js"]
```

`docker-compose.yml`にNodeコンテナを追加する。
```yml docker-compose.yml
version: '2.1'
services:
  app:
    # アプリケーションディレクトリ直下のDockerfileからビルド
    build: .

    # コンテナ名
    container_name: basic-node-container

    # ポートの紐づけ
    ports:
      - 3000:3000

    # 依存関係の設定
    depends_on:
      - db

    # 環境変数
    #URLはlocalhostではなく、docker-composeで指定しているservicesのdbを指定
    environment:
      DATABASE_URL: mysql://root:root@db:3306/basic_compose
      
  db:
    # ./SQL配下にあるDockerfileからビルド
    build: ./SQL

    # コンテナ名
    container_name: basic-mysql-container

    # ポートの紐づけ
    ports: 
      - "3306:3306"

    # 環境変数の設定
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: basic_compose
      TZ: "Asia/Tokyo"
```

アプリコンテナの環境変数部分はよくわかっていない。
今回はなくても問題ないため、調査は後回し。

これで`docker-compose up`を実行することでHello, World.できた。

### 注意点
`docker-compose up`で一度ビルドしたイメージを変更しビルドし直したい場合、既にあるイメージを削除してから、もしくは`--build`オプションを付ける必要がある。<br>
`views`ディレクトリをアプリケーションディレクトリ直下に配置していない場合、`app.set`で指定しなおす必要がある。デフォルトではアプリケーションディレクトリ直下の`views`ディレクトリを参照する。<br>
当たり前だが、`package*.json`を配置した後、`npm install`を実行しないと、依存パッケージインストールがされないため、buildでコケる。

## DBにテーブル作成と初期データを投入する
やり方として、初期化sqlを実行させる。 <br>
`./SQL/init`配下に`sql.sql`というファイルを作成し、SQLを記述する。
```sql sql.sql
-- DBの指定
USE basic_compose;

-- 既にテーブルが存在する場合、削除する
DROP TABLE IF EXISTS user;

-- テーブルの作成
CREATE TABLE user (
  id int(10) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,
  name VARCHAR(20) NOT NULL,
  PRIMARY KEY (id)
);

-- 初期データの投入
INSERT INTO user (name) VALUE ("satou");
INSERT INTO user (name) VALUE ("tanaka");
INSERT INTO user (name) VALUE ("ito");
```
`docker-entrypoint-initdb.d`にSQLファイルを配置すると、起動時に実行してくれるらしい。
`Dockerfile`で上記ファイル`sql.sql`を`docker-entrypoint-initdb.d`に配置する。
```Dockerfile
FROM mysql:8.4.0

# my.cnfを以下でインスタンス内`/etc/mysql/conf.d/my.cnf`に配置 
COPY ./my.cnf /etc/mysql/conf.d/my.cnf
# my.cnfの権限を変更
RUN chmod 644 /etc/mysql/conf.d/my.cnf

# sql.sqlをインスタンス内の`docker-entrypoint-initdb.d`に配置
COPY ./init/sql.sql /docker-entrypoint-initdb.d/sql.sql

EXPOSE 3306
```

この状態で`docker-compose up -d`すると初期データが投入されたmysqlコンテナが作成される。

### 注意点
エラーが発生するとコンテナが立ち上がるが数秒後に落ちることがある。
エラーが発生した際はコンテナIDから`docker logs`でログを確認するか、どこでエラーが発生しているか予測して、該当部分をコメントアウト等で実行してみる。
今回の場合、SQLのシンタックスエラーなどで落ちた。

上記`sql.sql`だと、毎回テーブルが削除されるため既存データまで破棄されることになる。
後日どうするべきか調査する。

# まとめ
曖昧だったdocker-composeについて、複数コンテナの立ち上げのみに絞って簡単にやってみた。
最終目標はコンテナを複数立ち上げることなので、１つ１つコンテナが立ち上がるか見ながら進めたら案外うまくできた。
細かい部分は調査しきれていないので、今後も調査する必要がある。

疑問点、ご指摘ありましたらコメントお願いいたします。