学習プログラム: モノリスを GKE アプリに変換する - モノリスをモジュール化する


これは、モノリシック アプリのモジュール化とコンテナ化の方法に関する学習プログラムに含まれる 2 番目のチュートリアルです。

この学習プログラムは、次のチュートリアルで構成されています。

  1. 概要
  2. モノリスを理解する
  3. モノリスをモジュール化する(このチュートリアル)
  4. モジュラー型のアプリをコンテナ化できるように準備する
  5. モジュラー型のアプリをコンテナ化する
  6. アプリを GKE クラスタにデプロイする

前のチュートリアル(モノリスを理解する)では、Cymbal Books というモノリシック アプリについて説明しました。このモノリスをローカルマシンで実行し、そのさまざまな部分がエンドポイントを介して相互に通信していることを確認しました。

このチュートリアルでは、モノリスをコンテナ化できるようにモジュールに分割する方法を説明します。コードはすでに更新されているため、モジュール化の手順を自分で実行する必要はありません。チュートリアルに沿って、リポジトリ内にあるアプリのモジュラー バージョンを調べ、元のモノリスとの違いを確認しましょう。

費用

このチュートリアルでは費用は発生しません。ただし、このシリーズの最後のチュートリアルの手順に沿って操作すると、Trusted Cloud by S3NS アカウントに料金が発生します。費用が発生するのは、GKE を有効にして Cymbal Books アプリを GKE クラスタにデプロイしたときです。費用には、料金ページで説明されている GKE のクラスタごとの料金と、Compute Engine VM の実行料金が含まれます。

不要な料金が発生しないように、このチュートリアルの完了後に GKE を無効にするか、プロジェクトを削除してください。

始める前に

このチュートリアルを始める前に、最初のチュートリアル(モノリスを理解する)を必ず完了してください。このチュートリアルでは、Cymbal Books のモジュラー バージョンをローカルマシンで実行します。そのため、環境の設定が完了している必要があります。最初のチュートリアルを完了していれば、GitHub リポジトリをクローン済みです。Cymbal Books アプリの 3 つのバージョンはすべて、リポジトリの次のフォルダ内にあります。

  • monolith/
  • modular/
  • containerized/

続行する前に、この 3 つのフォルダがマシンにあることを確認してください。また、仮想環境 book-review-env が有効であることを確認します。有効化の方法については、最初のチュートリアルで仮想環境を作成して有効にするをご覧ください。環境を有効にすることで、アプリのモジュラー バージョンの実行に必要なものがすべて整います。

モジュール化とは

このチュートリアルでは、コンテナ化できるようにモノリシック アプリをモジュール化する方法について説明します。モジュール化とは、モノリスをモジュラー型のアプリに変えるプロセスのことです。前のチュートリアルで説明したように、モノリスの特徴は、コンポーネントの実行やスケーリングを個別に行えないことです。一方、モジュラー型のアプリでは機能がモジュールに分割され、その実行やスケーリングを個別に行えます。

モジュール化とコンテナ化はまとめて行われることが多いですが、各コンセプトを明確に理解できるよう、このチュートリアル シリーズでは別々のステップとして扱われています。このチュートリアルではモノリスをモジュール化する方法について説明し、後のチュートリアルで、モジュラー式のアプリをコンテナ化する方法を説明します。

段階的なモジュール化

通常、本番環境ではコンポーネントを 1 つずつモジュール化します。あるコンポーネントをモジュール化してモノリスに統合し、すべてが機能することを確認してから、次のコンポーネントに取り組みます。一部のコンポーネントがモジュール化され、他のコンポーネントがモノリスの一部として残っているこのハイブリッド状態を、マイクロリスと呼びます。ただし、このチュートリアルでは、アプリを完全にモジュール化する方法の例を示すため、アプリのすべてのコンポーネントを同時にモジュール化します。

モノリスをモジュール化する方法

このセクションでは、Cymbal Books モノリスが個別のモジュールに分割された方法を説明します。モジュール化のプロセスを理解して独自のアプリに適用できるよう、次のような手順を紹介します。ただし、クローンしたリポジトリにアプリのモジュラー バージョンがすでに含まれているため、このチュートリアルでこれらの手順を実行する必要はありません

  1. アプリに含まれる個別の機能を特定する
  2. モジュールを作成する
  3. モジュール間の通信を有効にする
  4. 各モジュールに、必要なデータのみへのアクセス権を付与する

アプリに含まれる個別の機能を特定する

Cymbal Books モノリスをモジュール化する最初のステップは、その主な機能を特定することです。Cymbal Books サンプル アプリケーションのモノリスには、次の 4 種類の機能があります。

  • ホームページの配信
  • 書籍の詳細の配信
  • 書籍のレビューの配信
  • 書籍の表紙画像の配信

モジュールを作成する

前のチュートリアルで説明したように、このモノリスは、前のセクションで特定した 4 つの機能をルートハンドラとして実装する単一の Flask アプリです。このアプリをモジュール化するには、それぞれのルートハンドラを取得して独自の Flask アプリに配置します。1 つの Flask アプリに 4 つのルートハンドラを含める代わりに、4 つの Flask アプリを作成し、それぞれに 1 つのルートハンドラを含めます。

次の図は、単一の Flask アプリから 4 つの個別の Flask アプリへの変換を示しています。

モノリスからモジュラー型のアプリへの変換

この図に示すように、モジュラー型のアプリでは各 Flask アプリが独立して実行され、異なるポート(8080、8081、8082、8083)でリッスンします。この設定が必要なのは、このチュートリアルの後半でモジュラー型のアプリをテストする際に、すべてのモジュールを同じマシンで実行するためです。競合を避けるため、アプリごとに異なるポート番号が必要です。

ホームページ モジュールには、ホームページを配信する役割と、他のモジュールと通信してウェブページに表示する必要があるデータを収集する役割があります。他の各モジュールは、レビュー、詳細、画像のいずれかを配信するという単一の機能に特化しています。この 3 つのモジュールは相互に通信せず、ホームページ モジュールからのリクエストにのみ応答します。

ホームページ モジュールにコーディネーターのロールがありますが、どのモジュールも他のモジュールに影響を与えずに更新できるため、これは確かにモジュラー型のアプリです。1 つの大きな Flask アプリケーションが、そのアプリケーションの特定の機能を処理する 4 つの部分に分割されています。

モジュール間の通信を有効にする

モジュールを作成した後、次のステップでは、モジュールが相互に通信できるようにします。Cymbal Books アプリでは、この通信ロジックがすでに実装済みです。ダウンロードしたコードの modular/ フォルダを見ると、アプリの主な機能(ホームページ、書籍の詳細、レビュー、画像の配信)がそれぞれ個別の Flask アプリとして実装されていることがわかります。各アプリでは独自の HTTP エンドポイントが定義され、モジュール間の通信はエンドポイントに HTTP リクエストを送信することによって行われます。

Cymbal Books モノリスのモジュール化はシンプルでした。モノリスには、ルートハンドラとして実装される明確に定義されたコンポーネントがあり、各ルートハンドラには明確に定義されたエンドポイントがあります。これらのルートハンドラは、個別の Flask アプリケーションに配置された後も、エンドポイントを介して通信する機能を維持します。ルートハンドラを個別の Flask アプリに配置するだけで、モジュールが作成され、モジュール間の通信が有効になります。

モジュール間の通信を実現する一般的なアプローチは、REST API の実装です。これにより、モジュールが互いに HTTP リクエストを送信できるようになります。Cymbal Books もこのアプローチを利用し、各モジュールで Flask の組み込みツールによって REST エンドポイントを定義しています。もう一つの一般的なアプローチは gRPC です。これを利用すると、モジュールが互いの関数を直接呼び出せるようになります。

Cymbal Books 内での通信がスムーズな理由

モジュラー型アプリの各モジュールは、ウェブサーバー内で実行される個別の Flask アプリケーションです。たとえば、ホームページ モジュールはホームページを配信し、書籍の詳細モジュールは書籍の詳細を配信します。ウェブサーバーは HTTP のリクエストとレスポンスを処理するように設計されているため、モジュール間の通信はスムーズです。各モジュールは、他のモジュールがデータをリクエストするために使用できるエンドポイントを公開します。

各モジュールに、必要なデータのみへのアクセス権を付与する

モノリスを適切にモジュール化するには、各モジュールがアクセスできるデータを必要なデータのみに制限する必要があります。この原則は「データ分離」と呼ばれ、モジュラー型アーキテクチャを確実に作成するうえで重要な要素です。

モジュール化でよくある間違いは、複数のモジュールが同じデータ(単一のデータベースなど)にアクセスできるようにすることです。このタイプの実装では、次のような問題が発生します。

  • 密結合: 共有データの構造が変更された場合(データベース テーブルの名前の変更や、列の追加の場合など)に、そのデータに依存するすべてのモジュールを更新する必要があります。モジュール化を適切に行えば、この問題を回避できます。
  • フォールト トレランスの問題: 複数のモジュールが同じデータソースを使用する場合、1 つのモジュールで無効なクエリや過剰なトラフィックなどのランタイム エラーが発生したときに、他のモジュールが中断される可能性があります。システムの一部で障害が発生すると、他の部分での障害につながる可能性があります。
  • パフォーマンスのボトルネック: 単一の共有データソースはボトルネックになる場合があります。つまり、複数のモジュールがデータソースとやり取りしようとしたときに、アプリケーション全体の速度が低下する可能性があります。

このような問題を回避するため、各モジュールに独自のデータソースを用意する必要があります。

仮に Cymbal Books がデータベースにデータを保存していたとすると、データ分離を適用して各モジュールが必要なデータのみにアクセスできるようにするため、データベースのレプリケーションまたはパーティショニングを行う必要が生じたでしょう。レプリケーションの場合はモジュールごとにデータベースのコピーを維持することになり、パーティショニングの場合は特定のテーブルまたは行へのアクセスを制限することになります。どちらのアプローチでも、モジュールが互いのデータに干渉することを防止できます。

次の図は、書籍アプリのモノリシックなアーキテクチャとモジュラー型のアーキテクチャを比較したものです。

アプリのモノリス バージョンとモジュラー バージョンがデータを処理する方法を示す図

モノリス型の実装は、すべての機能が単一の data/ ディレクトリにアクセスするため、データ分離の原則に沿っていません。

一方、モジュール型のアプリでは、データを個別のディレクトリに分割し、各モジュールが指定されたデータのみとやり取りするようにすることで、ある程度のデータ分離を実現しています。

  • 書籍の詳細モジュールは、details_data/ ディレクトリからのみデータを取得します。
  • 書籍のレビュー モジュールは、reviews_data/ ディレクトリからのみデータを取得します。
  • 画像モジュールは images/ ディレクトリからのみデータを取得します。

後のチュートリアルで、アプリのコンテナ化によってデータ分離をさらに強化する方法について説明します。

解説

ソフトウェア開発の業界では、「マイクロサービス」と「分散システム」という用語がよく使用されます。このセクションでは、これらの用語が Cymbal Books のモジュール型の実装とどのように関連しているかについて説明します。

マイクロサービス

マイクロサービスは特定のタスクを実行する自律的なモジュールであり、エンドポイントなどのインターフェースを介して他のモジュールと通信します。

Cymbal Books のモジュラー バージョンの各モジュールはこの定義に適合するため、マイクロサービスと言えます。後のチュートリアルでモジュール型のアプリをコンテナ化する際、コンテナ内で実行されるコードはモジュール内で実行されるコードと同じです。それで、このコードもマイクロサービスと言うことができます。

分散システム

分散システムは、ネットワーク経由で通信して共通の目標を達成する独立したモジュールで構成されます。これらのモジュールは別々のマシンで実行できますが、単一のシステムとして連携して動作します。

モジュラー型の Cymbal Books アプリでは、モジュールが独立して実行され、HTTP 経由でデータを交換しますが、全体として単一のシステムとして機能します。そのため、このアプリは分散システムの定義に当てはまります。次のセクションでは、わかりやすくするためにすべてのモジュールを単一のマシンで実行しますが、これは必須ではありません。各モジュールを別々のサーバーで実行することも容易であるため、Cymbal Books アプリのモジュラー型バージョンは分散システムとして分類できます。

モジュラー型の実装をテストする

Cymbal Books のモノリスをモジュラー型アプリ(各モジュールは Flask アプリ)に変換する方法を理解できたので、アプリケーションをテストして、各モジュールが独立して実行されることを確認しましょう。

このチュートリアルでは、同じマシンでモジュールを実行します。ただし、各モジュールは自律的であり、エンドポイントを介して他のモジュールと通信できるため、別々のサーバーで実行することもできます。

環境の設定

テストの準備を行う手順は次のとおりです。

  1. ターミナルで、クローンしたリポジトリの modular ディレクトリに移動します。

    cd modular
    
  2. 仮想環境 book-review-env が有効であることを確認します。有効化の手順については、仮想環境を作成して有効にするをご覧ください。

Flask アプリを起動する

/modular フォルダに、すべての Flask アプリケーションを同時に起動する bash スクリプトが含まれています。アプリの各モジュールは一意のポート(8080 や 8081 など)でリッスンします。

  • ホームページの Flask アプリ(home.py): ポート 8080
  • 書籍の詳細 Flask アプリ(book_details.py): ポート 8081
  • 書籍レビューの Flask アプリ(book_reviews.py): ポート 8082
  • Images Flask アプリ(images.py): ポート 8083

各モジュールが一意のポート番号でリッスンする必要があるのは、すべてのモジュールが同じマシンで実行されるためです。各モジュールが異なるサーバーにある場合は、ポートの競合を発生させずに同じポート番号でリッスンできます。

次のコマンドを使って bash スクリプトを実行します。

bash ./start_services.sh

起動に関する問題を特定しやすくするため、このスクリプトは各 Flask アプリ(home.py.logbook_details.py.log など)用に個別のログファイルを作成します。スクリプトが正常に完了すると、次のメッセージが表示されます。

All services have been started. Access the app at http://localhost:8080/

各 Flask アプリをテストする

ブラウザで次の URL にアクセスして、各モジュールをテストします。

  • ホームページ: http://localhost:8080/ にアクセスすると、モジュール化された Cymbal Books アプリケーションのホームページが表示されます。このページから他のモジュールにリクエストが送信されて、書籍の詳細、レビュー、画像が取得されます。
  • 書籍の詳細: http://localhost:8081/book/1 にアクセスすると、ID 1 の書籍の詳細が返されます。このレスポンスは JSON データであり、人が読みやすい形式にフォーマットされてから表示されます。
  • 書籍のレビュー: http://localhost:8082/book/1/reviews にアクセスすると、ID 1 の書籍のレビューが取得されて返されます。レビューは JSON 形式です。ホームページ モジュールはこのデータをリクエストし、書籍の詳細ページに統合します。
  • 画像: http://localhost:8083/images/fungi_frontier.jpg にアクセスすると、Fungi Frontier という書籍の表紙画像が配信されます。URL が正しければ、画像がブラウザに直接読み込まれます。

Flask アプリを停止する

テストが完了したら、次のコマンドを使用してすべての Flask アプリを停止します。

kill $(cat home.py.pid book_details.py.pid book_reviews.py.pid images.py.pid)

まとめ

このチュートリアルでは、Cymbal Books モノリスをモジュール化する方法について説明しました。このプロセスは次の手順で構成されています。

  1. アプリの個別のコンポーネントを特定する
  2. モジュールを作成する
  3. 各モジュールが、必要なデータにのみアクセスできるようにする

その後、モジュラー型の実装をローカルマシンでテストしました。

次のステップ

次のチュートリアル(モジュラー型のアプリをコンテナ化できるように準備する)では、モジュラー型のアプリをコンテナ化できるように準備する(localhost ではなく Kubernetes Service 名を使用するようにエンドポイントを更新する)方法について説明します。