新米エンジニアがawesome-composeにあるcomposeファイルに改善点がないか考えてみる
こんにちは。Sun* でバックエンドエンジニアをやっているうえひろです。
普段は業務効率化システムのバッチ作成に取り組んでいますが、今日は「Docker」について少しお話ししたいと思います。
最近、「awesome-compose」リポジトリを見つけて、Composeファイルの改善点を探る挑戦を始めました。初心者ならではの気づきが皆さんのお役に立てれば嬉しいです。ぜひ最後までお読みください!
※今回はローカル環境向けの設定を前提に記事を書いてます。
この記事はSun* Advent Calendar 2024の12日目の記事です。
awesome-composeとは?
awesome-compose は、コンテナ化されたアプリケーションの構築と管理を簡素化するための Docker Composeのサンプルリポジトリです。公式のDocker GitHubアカウントによって管理されているらしいです。
今回はReact、Express、MongoDBで開発されたToDoアプリのcomposeファイルを例に扱っていきたいと思います。
awesome-composeからcomposeファイルを手に入れる
今回使用するcomposeファイルはこちら
services:
frontend:
build:
context: frontend
target: development
ports:
- 3000:3000
stdin_open: true
volumes:
- ./frontend:/usr/src/app
- /usr/src/app/node_modules
restart: always
networks:
- react-express
depends_on:
- backend
backend:
restart: always
build:
context: backend
target: development
volumes:
- ./backend:/usr/src/app
- /usr/src/app/node_modules
depends_on:
- mongo
networks:
- express-mongo
- react-express
expose:
- 3000
mongo:
restart: always
image: mongo:4.2.0
volumes:
- mongo_data:/data/db
networks:
- express-mongo
expose:
- 27017
networks:
react-express:
express-mongo:
volumes:
mongo_data:
それではまずは簡単に説明していきます。
1. フロントエンド (frontend)
ビルド設定
-
context: frontend
: フロントエンドのソースコードが存在するディレクトリを指定。 -
target: development
: マルチステージビルドの開発用ターゲットを指定。
ポート設定
-
3000:3000
: ホストのポート3000をコンテナのポート3000にマッピング。これにより、ブラウザから http://localhost:3000 でフロントエンドにアクセス可能。
その他の設定
-
stdin_open: true:
コンテナの標準入力を開いたままにする設定。 -
volumes
:-
./frontend:/usr/src/app
: ホストの frontend ディレクトリをコンテナ内の /usr/src/app にマウント。コードの変更が即時に反映される。 -
/usr/src/app/node_modules
: コンテナ内の node_modules をホストと共有しないようにする設定。
-
-
restart
: always: コンテナが停止した場合に自動的に再起動する設定。 -
networks
:react-express
ネットワークに接続。フロントエンドとバックエンドの通信を可能にする。 -
depends_on
: backend サービスに依存。バックエンドが先に起動するよう管理。
2. バックエンド (backend)
ビルド設定
-
context: backend
: バックエンドのソースコードが存在するディレクトリを指定。 -
target: development
: マルチステージビルドの開発用ターゲットを指定。
ボリューム設定
-
./backend:/usr/src/app
: ホストの backend ディレクトリをコンテナ内の /usr/src/app にマウント。コードの変更が即時に反映される。 -
/usr/src/app/node_modules
: コンテナ内の node_modules をホストと共有しないようにする設定。
その他の設定
-
restart: always
: コンテナが停止した場合に自動的に再起動する設定。 -
depends_on
:mongo
サービスに依存。MongoDBが先に起動するよう管理。 -
networks
:express-mongo
とreact-express
の両方のネットワークに接続。これにより、フロントエンドとMongoDBとの通信が可能。 -
expose: 3000
: コンテナ内部のポート3000を他のサービスに公開。ホストからは直接アクセスできない。
3. MongoDB (mongo)
イメージ
-
mongo:4.2.0
: MongoDBのバージョン4.2.0を使用。
ボリューム設定
-
mongo_data:/data/db
: データ永続化のためにホスト側の mongo_data ボリュームをコンテナの /data/db にマウント。
その他の設定
-
restart
: always: コンテナが停止した場合に自動的に再起動する設定。 -
networks
:express-mongo
ネットワークに接続。バックエンドからのアクセスを可能にする。 -
expose: 27017
: コンテナ内部のポート27017を他のサービスに公開。ホストからは直接アクセスできない。
ネットワークとボリュームの設定
- ネットワーク
-
react-express
: フロントエンドとバックエンド間の通信を管理。 -
express-mongo
: バックエンドとMongoDB間の通信を管理。
-
- ボリューム
-
mongo_data
: MongoDBのデータを永続化するためのボリューム。
-
うーむ、完成系に近いcomposeファイルに見えますね。
気になった部分について考えてみる
ネットワークが二つ設定されている理由
サービス間の分離とセキュリティの向上
-
react-express ネットワーク:
-
目的
: フロントエンド(React)とバックエンド(Express)が通信するためのネットワーク。 -
利点
: フロントエンドとバックエンドのみがこのネットワーク上で通信できるため、他のサービスからの不要なアクセスを防ぎ、セキュリティを強化できる。
-
-
express-mongo ネットワーク:
-
目的
: バックエンド(Express)とデータベース(MongoDB)が通信するための専用ネットワーク。 -
利点
: データベースへのアクセスをバックエンドのみに限定することで、他のサービスや外部からの不正アクセスリスクを低減できる。
-
本番環境を意識した設計であれば、現在の構成でも問題ありません。ただし、個人開発の場合などローカルだけにおさまる開発なら、ネットワークを一つにまとめてシンプルにするのも良い選択肢だと思います。
MongoDBのポートをホストに公開されていない理由
セキュリティの強化
- 外部からのアクセスを防ぐ: MongoDBのポートをホストに公開しないことで、外部から直接アクセスされるリスクを減少させることが出来る。
環境ごとの設定の最適化
- 本番環境のセキュリティ: 本番環境では、データベースを外部に公開することは避けるのが一般的である。開発環境と同一の設定を維持することで、本番環境への移行時にセキュリティ設定を見逃すリスクを減らせる。
ただ今回の設定のようにポートがホストに公開されていないとSQLクライアントアプリ(今回だとMongoDBクライアントアプリ)を使用出来ないので不便ですよね。
そのような方は以下のように設定を変えてもいいかもしれません。
services:
mongo:
ports:
- "27017:27017" # ホストの27017ポートをコンテナの27017ポートにマッピング
environment:
MONGO_INITDB_ROOT_USERNAME: mongo
MONGO_INITDB_ROOT_PASSWORD: mongo
Backendのcontainerはホストに公開しなくて良い理由
Dockerのビルドコンテキストでコードがコンテナ内に含まれるから
backend:
build:
context: backend
target: development
- compose.yamlのbuildセクションでバックエンドのソースコードディレクトリ(backend/)を指定している。
- ソースコードがDockerイメージに取り込まれ、コンテナ内で実行されている。
Docker内部ネットワークで通信が完結するから
- フロントエンド(frontend)とバックエンド(backend)は両方ともreact-expressネットワークに接続されている。
フロントエンドがブラウザからの窓口になるから
- フロントエンドがaxiosを使ってbackendにリクエストを送信し、その結果をブラウザに返している。
改善できそうなポイントを捻り出す🤔
depends_on
のhealthcheck対応
depends_on
はサービスの起動順序を保証するのみで、依存サービスが完全に準備完了しているかは確認しません。
healthcheck を併用することで、依存先サービスが実際に正常に動作しているか(今回で言うとmongoサービス)を確認でき、依存先が「healthy」な状態になってから依存元サービスを起動するように制御できます。
mongo:
# 追加コード
healthcheck:
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 3
backend:
# mongoサービスが「ヘルスチェックで正常と判定される」までbackendの起動を待機。
depends_on:
mongo :
condition: service_healthy
以上、実力不足によりあまり改善点を見つけれず、終了(3個くらいは見つけてやる、という目標でした泣)
自分なりの最終的なcomposeファイル
services:
frontend:
build:
context: frontend
target: development
ports:
- 3000:3000
stdin_open: true
volumes:
- ./frontend:/usr/src/app
- /usr/src/app/node_modules
restart: always
networks:
- react-express
depends_on:
- backend
backend:
restart: always
build:
context: backend
target: development
volumes:
- ./backend:/usr/src/app
- /usr/src/app/node_modules
# 追加
depends_on:
mongo :
condition: service_healthy
networks:
- express-mongo
- react-express
expose:
- 3000
mongo:
image: mongo:4.2.0
volumes:
- mongo_data:/data/db
ports:
- "27017:27017" # ホストの27017ポートをコンテナの27017ポートにマッピング
environment:
MONGO_INITDB_ROOT_USERNAME: mongo
MONGO_INITDB_ROOT_PASSWORD: mongo
networks:
- express-mongo
restart: always
healthcheck:
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 3
networks:
react-express:
express-mongo:
volumes:
mongo_data:
まとめ
最後までお読みいただき、ありがとうございました。
Dockerの世界は奥深く、改善点を探ることでより効率的な開発環境を築くことができます。ご意見やご質問がありましたら、ぜひコメントでお知らせください。特にこんな改善点あるぞ!というご意見あればぜひ教えていただきたいです。
次はDockerfileの改善案を考えるのもいいかもしれません。
明日はKosuke Suzukiさんによる、データウェアハウス構築を初めてした際の話、です。
お楽しみに!
Discussion