超いまさらだけどバックエンド開発環境をDockerコンテナ化したよ
こんにちは、エンジニアのすずきです。
ローカル開発環境のコンテナ化…どの企業様でもされていると思うのですが、限られたエンジニア数で新機能開発に重きをおいた結果、弊社ではずっと先送りになっておりました…
先送りにした結果、dev環境とローカル環境で同じDB(RDS)を参照する状況となり、チーム開発に大きな支障をきたしてしまいました。
私は主にフロントエンドとAIまわりを担当しているため、Dockerについてはあまり詳しくなかったのですが、チーム開発の崩壊を防ぐために、以下の目的から開発環境へのDocker導入を決意しました。
開発環境でRDSに負荷をかけない(RDSを使用しない)
メンバー間でのマイグレーション影響をなくす(共有DB問題の回避)
開発環境立ち上げの速度改善(環境構築の迅速化)
アイドリングタイムアウトによるサーバ再立ち上げの手間改善
既存のDBをコンテナDBに移行し、バックエンドとDBをdocker-composeで立ち上げるまでの手順について、以下に詳しくまとめました。
1. Docker関連ファイルの作成
開発しているサービスは、複数のフロントエンド(./frontend/)とバックエンド(./backend/)をモノレポで管理しています(フロントエンドはReact、バックエンドはNestJS)。
docker-composeを用いることで、これらの各サービスを一括して起動することも可能ですが、フロントエンドの中には開発中に使用しないものも存在するため、今回は必要なバックエンドとDBだけをDockerコンテナで立ち上げることにしました。
Dockerfileの作成
まず、バックエンドのDockerfile(Dockerfile.dev)を以下のように作成します。
FROM node:18.15.0
# タイムゾーンを東京(JST)に設定
ENV TZ=Asia/Tokyo
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /app
# パッケージの依存関係をコピー
COPY package*.json ./
# huskyインストールのエラーを回避するためにprepareスクリプトを無視
RUN yarn install --ignore-scripts
EXPOSE 8000
CMD ["yarn", "start:dev"]
既存DB(RDS)の設定がJSTだったため、タイムゾーンの設定を加えています。
また、huskyのインストールステップが失敗したので、Yarnで依存関係をインストールする際に--ignore-scriptsでスクリプトを無視するようにしています。
docker-compose.ymlの作成
次に、docker-compose.ymlファイルを以下のように作成します。
このファイルはバックエンドとDB(MySQL)のコンテナを管理します。
version: "3.9"
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
ports:
- "3010:3010" # ローカルとコンテナのポートをマッピング
depends_on:
- db # DBサービスの起動を待つ
env_file: ./backend/.env # 環境変数を指定
volumes:
- ./backend:/app # ホストとコンテナのディレクトリをマッピング
db:
image: mysql:8.0.28
platform: linux/x86_64
container_name: db
env_file: ./backend/.env # 環境変数を指定
volumes:
- db-data:/var/lib/mysql # データを永続化
ports:
- "3306:3306" # ローカルとコンテナのポートをマッピング
command: --default-authentication-plugin=mysql_native_password --sql_mode=NO_ENGINE_SUBSTITUTION
volumes:
db-data: # DBのデータを永続化するためのボリュームを定義
ここで作成したDockerfile.devとdocker-compose.ymlを利用して、バックエンドとDBを立ち上げることが可能になります。
フロントエンドが必要になった場合は、./frontend/に同様の手順でDockerファイルを作成し、docker-compose.ymlにサービスを追加することで対応できます。
補足1
MySQLでは、DBの設定により、デフォルト値が設定されていないDATETIME型のフィールドに0000-00-00 00:00:00が自動で入るようにすることができるのですが、コンテナDBではそのような設定になっておらず、コンテナ起動後にINSERTエラーが発生してしまいました。
以下でMySQLの設定を確認しました。
SHOW VARIABLES;
sql_modeを確認したところ、コンテナDBと既存DB(RDS)で設定が異なっていたため、dbのcommandに--sql_mode=NO_ENGINE_SUBSTITUTIONを追加しました。
# コンテナDB
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
# 既存DB(RDS)
NO_ENGINE_SUBSTITUTION
補足2
コンテナ起動後にバックエンドの修正を行ってもホットリロードされませんでした。
そのため、backendのvolumesに./backend:/appを追加し、ホストとコンテナのディレクトリをマッピングしました。
補足3
モノレポのサービスであること、フロントエンドについてはコンテナ化しないことを前述しましたが、将来的にフロントエンドもコンテナ化する可能性もあるので、docker-compose.ymlをルートディレクトリに配置しました。
./backend/Dockerfile.devを読み込むように、以下の記述を行いました。
build:
context: ./backend
dockerfile: Dockerfile.dev
MySQLの接続設定については、env_file: ./backend/.envで読み込むようにしました。
2. 環境変数の追加
DBの設定情報を./backend/.envに環境変数として追加します(任意の値)。
MYSQL_ROOT_PASSWORD=
MYSQL_DATABASE=
MYSQL_USER=
MYSQL_PASSWORD=
記述例
MYSQL_DATABASE=db_local
MYSQL_USER=admin
3. コンテナの起動
ルートディレクトリで以下のコマンドを実行し、複数コンテナ(backend, DB)を起動します。
$ docker-compose up
4. MySQL Workbenchの設定
MySQL Workbenchを使用してDocker上のMySQL DBに接続します。
MySQL Workbenchを開く
画面左側のMySQL接続一覧で[+]ボタンをクリックする
接続設定を以下のように設定する
Connection Name: 任意の接続名(例:db-local-docker)
Hostname: localhost(またはDockerが稼働しているホストのIPアドレス)
Port: 3306(docker-compose.ymlで設定したポート番号)
Username: ./backend/.envのMYSQL_USER
Password: ./backend/.envのMYSQL_PASSWORD
[Test Connection]をクリックして接続テストを行い、問題がなければ[OK]をクリックして接続を保存する。
5. 既存DBのダンプエクスポート
dev環境で使用しているRDS for MySQLのDBから、ダンプ(テーブル情報とデータ)をエクスポートします。
ローカルマシンのポート(<local_port>)から、踏み台サーバー(<ssh_user>@<remote_server_ip>)経由でRDSのポート(<remote_database_port>)へ接続します(SSHトンネル)。
ローカルマシン上で既にMySQLが実行されている場合などでポートの競合を避けるために、今回はlocal_portとremote_database_portは別のポートを使用します。
$ ssh -f -N -L <local_port>:<remote_database_url>:<remote_database_port> -i <path_to_your_ssh_key> <ssh_user>@<remote_server_ip>
local_port: ローカルマシンで開放したいポート(例:3307)
remote_database_url: リモートのデータベースのURL(例:database.db.coedadadas22.ap-northeast-1.rds.amazonaws.com)
remote_database_port: リモートのデータベースが使用しているポート(例:3306)
path_to_your_ssh_key: SSHキーへのパス(例:~/.ssh/db-pem-key.pem)
ssh_user: リモートサーバーへの接続に使用するユーザ名(例:ec2-user)
remote_server_ip: リモートサーバーのIPアドレス(例:54.168.32.10)
ローカルホストのポート(<local_port>)に接続して、RDSの該当DB(<database_name>)のすべてのテーブル名をテキストファイル(<output_file>)に書き込みます。
$ mysql -h <localhost_ip> -P <local_port> -u <username> -p <database_name> -e 'show tables' | tail -n +2 > <output_file>
localhost_ip: MySQLクライアントが実行されているホストのIPアドレス(例:127.0.0.1)
local_port: SSHポートフォワーディングを通じてリモートデータベースにアクセスするためのローカルマシンのポート(例:3307)
username: データベースへの接続に使用するユーザ名(例:admin)
database_name: 接続するデータベースの名前(例:db-prod)
output_file: データベースから取得したテーブル名を保存するためのファイル名(例:all_tables.txt)
エクスポートしたいテーブルのリストを元にDBをダンプします。
$ mysqldump -h <localhost_ip> -P <local_port> -u <username> -p <database_name> $(cat <tables_list_file>) > <output_file>
localhost_ip: データベースホストのIPアドレス(例:127.0.0.1)
local_port: データベースがリッスンしているポート(例:3307)
username: データベースに接続するユーザー名(例:admin)
database_name: ダンプを作成するデータベースの名前(例:db-prod)
tables_list_file: ダンプを作成するテーブルのリストが含まれるファイル(例:all_tables.txt)
output_file: 出力(ダンプファイル)を保存するファイル名(例:database_export.sql)
6. コンテナDBへのダンプインポート
MySQL WorkbenchからコンテナDBにダンプをインポートします。
MySQL Workbenchを開く
メイン画面から接続したいデータベースの接続をクリックする
データベースの接続が開かれたら、メニューバーから「Server」->「Data Import」を選択する
「Data Import」画面が表示されたら、「Import from Self-Contained File」を選択し、その右側の「...」ボタンをクリックして、インポートしたいSQLファイルを選択する
「Default Target Schema」ドロップダウンメニューからインポート先のデータベースを選択する
「Import Progress」セクションの「Start Import」ボタンをクリックする
採用情報
絶賛エンジニア募集中です。
バックエンドやインフラまわりに詳しい方と働きたいです。
YOUTRUSTでカジュアル面談もやっております。