docker-composeを使ってEC2へSpring boot AppとMySQLをデプロイ

# 概要

Docker Composeでは複数のDockerコンテナを定義してデプロイをすることが可能になるので、アプリからデータベースまで必要な実行環境を簡単に作成できます。  
ローカル環境を汚さずに、必要なときにその都度用意できるのが個人的には最も良い点です。
この記事では、JavaアプリケーションとMySQLの実行環境を構築するために、Dockerfileの定義からDocker Composeでコマンド1つでデプロイをするまでの流れを記載します。  

## 環境
|-|Version|
|-|-|
|OS|Amazon Linux 2 x86_64|
|Docker|Docker version 20.10.17, build 100c701|
|Docker|Docker Compose version v2.16.0|
|Java|java 17.0.5 2022-10-18 LTS|
|Framework|Spring boot 3.0.4|
|ビルド|Gradle 7.5.1|

## アプリケーションとディレクトリ構成

今回は、Spring bootアプリケーションは以下のRest APIを使用する。
[こちら](https://github.com/Spicy-Cottoncandy/searchpostcode)

```text
./searchpostcode/
    build/
        libs/
            searchpostcode-0.0.1-SNAPSHOT.jar   #1
    mysql/
        1_CreateTable.sql                       #2
        2_ImportTable.sql
        import.csv
        my.conf
    Docker-compose.yml                          #3
```

- #1：build/libsは、Gradleでビルド済みのjar
- #2：mysql/は、Docker-composeで使用するファイル
- #3：Docker-compose.ymlは、コンテナの設定

## 構築の流れと、構築後の概要図

おおよその流れは、  
Dockerfileの作成 -> Docker-compose.ymlの作成 -> docker-composeでビルド -> docker-composeで起動

Linux上に、Javaアプリケーションの実行環境とDBの実行環境をそれぞれコンテナで作成し、コンテナ間はjdbc接続でデータアクセスする。  

![docker-compose-overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3018916/a79ecdb4-7d90-b520-70e3-8584b7a05b2a.png)

## Dockerfileの作成

ビルド済みのjarファイルをコンテナへコピーして、Javaコマンドを実行する。  

```Dockerfile:Dockerfile
FROM openjdk:17
COPY ./build/libs/ /usr/searcehpost/
ENTRYPOINT ["java","-jar","/usr/searcehpost/searchpostcode-0.0.1-SNAPSHOT.jar"]
```

## Docker-compose.ymlの作成

```yml:Docker-compose.yml
version: '3.1'
services:
  java:
    build: .                                        #カレントディレクトリのDockerfileをビルド
    image: searcehpost:1
    container_name: java-searcehpost
    tty: true
    ports: 
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}    #環境変数をホストからコンテナへ渡す
      SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}    #
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql-1:3306/demo        #ホスト名は、container_nameを指定する
  db:
    image: mysql:8.0.32
    container_name: mysql-1
    ports:
      - "13306:3306"                            #ホスト側のport:コンテナ側のport
    volumes:
      - ./mysql/my.cnf:/etc/my.cnf              #ホスト側のファイルをコンテナへコピーする
      - ./mysql/:/docker-entrypoint-initdb.d    #MySQLの起動時に自動実行したいスクリプトをコピーする
    environment:
      MYSQL_DATABASE: demo
      MYSQL_ROOT_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
      MYSQL_USER: ${SPRING_DATASOURCE_USERNAME}
      MYSQL_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
```

### 補足説明

- services.java.build
今回は、javaのコンテナイメージはDockerfileから作成する。  
もしDocker Hubからイメージをpullする場合は、imagesでイメージ名を指定すればよい。

- services.java.environment
コンテナで使いたい環境変数を定義している。ホスト側の環境変数の指定が可能。  
また、jdbc接続のホスト名にはcontainer_nameを指定する必要がある。この例では、MySQLコンテナのものを指定して接続する。

- db.ports
ホスト側で使用するポートとコンテナ側で使用するポートをマッピングする。  
ホスト側の環境でもMySQLを3306ポートで起動しているため、今回は変更した。もしホスト側で使用していないポートであれば、同じでも動作する。  

- db.volumes
ホスト側に用意したmy.cnfとコンテナ起動時に自動実行させたいファイルをコピーする。  
/docker-entrypoint-initdb.dディレクトリは、MySQLコンテナが用意する特別なディレクトリで、スクリプトを自動実行してくれる。  
対象の拡張子は、*.sql, *.sh, *.sql.gz　の３つ。
実行はファイル名のアルファベット順に行われるので、  
1.テーブル作成 -> 2.インデックス作成 -> 3.データインポート  
のように実行順序を意識するように命名する必要がある。 

:::note info
詳しくは、[こちら](https://hub.docker.com/_/mysql)の "Initializing a fresh instance" を参照。
:::

## docker-composeでビルド

`docker compose build` コマンドで、docker-compose.ymlおよびDockerfileをもとにコンテナをビルドする。  

```text:docker compose build --help
Usage:  docker compose build [OPTIONS] [SERVICE...]
Build or rebuild services
```

```bash
[16:55:16 ec2-user ~/dev/java/searchpostcode] docker compose build
[+] Building 1.2s (7/7) FINISHED                                                                                                          
 => [internal] load build definition from Dockerfile                                                                                 0.0s
 => => transferring dockerfile: 32B                                                                                                  0.0s
 => [internal] load .dockerignore                                                                                                    0.0s
 => => transferring context: 2B                                                                                                      0.0s
 => [internal] load metadata for docker.io/library/openjdk:17                                                                        1.1s
 => [internal] load build context                                                                                                    0.0s
 => => transferring context: 123B                                                                                                    0.0s
 => [1/2] FROM docker.io/library/openjdk:17@sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8                  0.0s
 => CACHED [2/2] COPY ./build/libs/ /usr/searcehpost/                                                                                0.0s
 => exporting to image                                                                                                               0.0s
 => => exporting layers                                                                                                              0.0s
 => => writing image sha256:dd7d43ab6d2565dfbbf126779be7d92151dcf516d3116c0416345104341c6ac7                                         0.0s
 => => naming to docker.io/library/searcehpost:1                                                                                     0.0s
```

コンテナのイメージが作成される。  

```bash
[16:55:28 ec2-user ~/dev/java/searchpostcode] docker images
REPOSITORY                    TAG       IMAGE ID       CREATED          SIZE
searcehpost                   1         dd7d43ab6d25   37 minutes ago   499MB
mysql                         8.0.32    21a7ac543605   7 hours ago      530MB
```

## docker-composeで起動

`docker compose up -d`コマンドで起動する。  

```text:docker compose up --help
Usage:  docker compose up [OPTIONS] [SERVICE...]
Create and start containers

Options:
  -d, --detach                    Detached mode: Run containers in the background
```

```bash
[16:55:41 ec2-user ~/dev/java/searchpostcode] docker compose up -d
[+] Running 3/3
 ⠿ Network searchpostcode_default  Created                                                                                           0.0s
 ⠿ Container java-searcehpost      Started                                                                                           1.4s
 ⠿ Container mysql-1               Started                                                                                           1.4s
```

ここまでで、javaとMySQLのコンテナが起動された状態となる。  

## 動作確認

コマンドラインからcurlで実行する。  
APIからデータベースへアクセスができており、レスポンスも問題なし。  

```bash
[18:35:04 ec2-user ~/dev/java/searchpostcode] curl localhost:8080/postcode/0600000
{"result":[{"kanaPrefecture":"ﾎｯｶｲﾄﾞｳ","kanaCity":"ｻｯﾎﾟﾛｼﾁｭｳｵｳｸ","kanaStreet":"ｲｶﾆｹｲｻｲｶﾞﾅｲﾊﾞｱｲ","kanjiPrefecture":"北海道","kanjiCity":"札幌市中央区","kanjiStreet":"以下に掲載がない場合"}]}
```

EC2のセキュリティグループさえ許可をしておけば、インターネットからのアクセスも可能。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3018916/04e71056-f3eb-2012-7ce0-b1abb51bc6c7.png)

### ログの閲覧

ログはdocker compose logsコマンドで確認できる。  
複数のコンテナのログが混在で出力されるので、見た目はわかりにくい。  
Javaアプリケーションのログ出力を標準出力にしているが、実用時はログファイルへ出力したうえで、ホスト側と共有するようにしたほうがよいと思います。  

```text:docker compose logs --help
Usage:  docker compose logs [OPTIONS] [SERVICE...]
View output from containers
```

```bash
[17:30:57 ec2-user ~/dev/java/searchpostcode] docker compose logs
java-searcehpost  | 07:56:01,781 |-INFO in ch.qos.logback.core.model.processor.DefaultProcessor@3ec300f1 - End of configuration.
java-searcehpost  | 07:56:01,782 |-INFO in org.springframework.boot.logging.logback.SpringBootJoranConfigurator@482cd91f - Registering cur
rent configuration as safe fallback point
java-searcehpost  | 
java-searcehpost  | 
java-searcehpost  |   .   ____          _            __ _ _
java-searcehpost  |  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
java-searcehpost  | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
java-searcehpost  |  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
java-searcehpost  |   '  |____| .__|_| |_|_| |_\__, | / / / /
java-searcehpost  |  =========|_|==============|___/=/_/_/_/
java-searcehpost  |  :: Spring Boot ::                (v3.0.4)
java-searcehpost  | 
java-searcehpost  | [2023-03-29 07:56:01.941+0000] [background-preinit] [INFO] [org.hibernate.validator.internal.util.Version] | HV000001:
 Hibernate Validator 8.0.0.Final
...
mysql-1           | 2023-03-29 07:56:12+00:00 [Warn] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb
.d/my.cnf
mysql-1           | 2023-03-29 07:56:12+00:00 [Note] [Entrypoint]: Stopping temporary server
mysql-1           | 2023-03-29 07:56:14+00:00 [Note] [Entrypoint]: Temporary server stopped
mysql-1           | 
mysql-1           | 2023-03-29 07:56:14+00:00 [Note] [Entrypoint]: MySQL init process done. Ready for start up.
```

## コンテナの停止

`docker compose down`コマンドで停止。  
-vオプションを付けていると、コンテナにコピーされたファイルも残らず削除される。  

```text:docker compose down --help
Usage:  docker compose down [OPTIONS]
Stop and remove containers, networks

  -v, --volumes volumes   Remove named volumes declared in the volumes section of the Compose file and anonymous volumes attached to containers.
```

```bash
[18:44:56 ec2-user ~/dev/java/searchpostcode] docker compose down -v
[+] Running 3/3
 ⠿ Container mysql-1               Removed                                                                                                                            1.3s
 ⠿ Container java-searcehpost      Removed                                                                                                                            0.6s
 ⠿ Network searchpostcode_default  Removed                                                                                                                            0.1s
```