🐳

新米エンジニアがawesome-composeにあるcomposeファイルに改善点がないか考えてみる

2024/12/12に公開

こんにちは。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-mongoreact-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さんによる、データウェアハウス構築を初めてした際の話、です。
お楽しみに!

Sun* Developers

Discussion