コンテンツにスキップ

デプロイメントのコンセプト

FastAPIを用いたアプリケーションをデプロイするとき、もしくはどのようなタイプのWeb APIであっても、おそらく気になるコンセプトがいくつかあります。

それらを活用することでアプリケーションをデプロイするための最適な方法を見つけることができます。

重要なコンセプトのいくつかを紹介します:

  • セキュリティ - HTTPS
  • 起動時の実行
  • 再起動
  • レプリケーション(実行中のプロセス数)
  • メモリー
  • 開始前の事前のステップ

これらがデプロイメントにどのような影響を与えるかを見ていきましょう。

最終的な目的は、安全な方法でAPIクライアントにサービスを提供し、中断を回避するだけでなく、計算リソース(例えばリモートサーバー/仮想マシン)を可能な限り効率的に使用することです。🚀

この章では前述したコンセプトについてそれぞれ説明します。

この説明を通して、普段とは非常に異なる環境や存在しないであろう将来の環境に対し、デプロイの方法を決める上で必要な直感を与えてくれることを願っています。

これらのコンセプトを意識することにより、あなた自身のAPIをデプロイするための最適な方法を評価し、設計することができるようになるでしょう。

次の章では、FastAPIアプリケーションをデプロイするための具体的なレシピを紹介します。

しかし、今はこれらの重要なコンセプトに基づくアイデアを確認しましょう。これらのコンセプトは、他のどのタイプのWeb APIにも当てはまります。💡

セキュリティ - HTTPS

前チャプターのHTTPSについてでは、HTTPSがどのようにAPIを暗号化するのかについて学びました。

通常、アプリケーションサーバにとって外部のコンポーネントであるTLS Termination Proxyによって提供されることが一般的です。このプロキシは通信の暗号化を担当します。

さらにセキュアな通信において、HTTPS証明書の定期的な更新を行いますが、これはTLS Termination Proxyと同じコンポーネントが担当することもあれば、別のコンポーネントが担当することもあります。

HTTPS 用ツールの例

TLS Termination Proxyとして使用できるツールには以下のようなものがあります:

  • Traefik
    • 証明書の更新を自動的に処理 ✨
  • Caddy
    • 証明書の更新を自動的に処理 ✨
  • Nginx
    • 証明書更新のためにCertbotのような外部コンポーネントを使用
  • HAProxy
    • 証明書更新のためにCertbotのような外部コンポーネントを使用
  • Nginx のような Ingress Controller を持つ Kubernetes
    • 証明書の更新に cert-manager のような外部コンポーネントを使用
  • クラウド・プロバイダーがサービスの一部として内部的に処理(下記を参照👇)

もう1つの選択肢は、HTTPSのセットアップを含んだより多くの作業を行うクラウド・サービスを利用することです。 このサービスには制限があったり、料金が高くなったりする可能性があります。しかしその場合、TLS Termination Proxyを自分でセットアップする必要はないです。

次の章で具体例をいくつか紹介します。


次に考慮すべきコンセプトは、実際のAPIを実行するプログラム(例:Uvicorn)に関連するものすべてです。

プログラム と プロセス

私たちは「プロセス」という言葉についてたくさん話すので、その意味や「プログラム」という言葉との違いを明確にしておくと便利です。

プログラムとは何か

プログラムという言葉は、一般的にいろいろなものを表現するのに使われます:

  • プログラマが書くコードPythonファイル
  • OSによって実行することができるファイル(例: python, python.exe or uvicorn
  • OS上で実行している間、CPUを使用し、メモリ上に何かを保存する特定のプログラム(プロセスとも呼ばれる)

プロセスとは何か

プロセスという言葉は通常、より具体的な意味で使われ、OSで実行されているものだけを指します(先ほどの最後の説明のように):

  • OS上で実行している特定のプログラム
    • これはファイルやコードを指すのではなく、OSによって実行され、管理されているものを指します。
  • どんなプログラムやコードも、それが実行されているときにだけ機能します。つまり、プロセスとして実行されているときだけです。
  • プロセスは、ユーザーにあるいはOSによって、 終了(あるいは "kill")させることができます。その時点で、プロセスは実行/実行されることを停止し、それ以降は何もできなくなります
  • コンピュータで実行されている各アプリケーションは、実行中のプログラムや各ウィンドウなど、その背後にいくつかのプロセスを持っています。そして通常、コンピュータが起動している間、多くのプロセスが同時に実行されています。
  • 同じプログラム複数のプロセスが同時に実行されていることがあります。

OSの「タスク・マネージャー」や「システム・モニター」(または同様のツール)を確認すれば、これらのプロセスの多くが実行されているの見ることができるでしょう。

例えば、同じブラウザプログラム(Firefox、Chrome、Edgeなど)を実行しているプロセスが複数あることがわかります。通常、1つのタブにつき1つのプロセスが実行され、さらに他のプロセスも実行されます。


さて、プロセスプログラムという用語の違いを確認したところで、デプロイメントについて話を続けます。

起動時の実行

ほとんどの場合、Web APIを作成するときは、クライアントがいつでもアクセスできるように、常に中断されることなく実行されることを望みます。もちろん、特定の状況でのみ実行させたい特別な理由がある場合は別ですが、その時間のほとんどは、常に実行され、利用可能であることを望みます。

リモートサーバー上での実行

リモートサーバー(クラウドサーバー、仮想マシンなど)をセットアップするときにできる最も簡単なことは、ローカルで開発するときと同じように、Uvicorn(または同様のもの)を手動で実行することです。 この方法は開発中には役に立つと思われます。

しかし、サーバーへの接続が切れた場合、実行中のプロセスはおそらくダウンしてしまうでしょう。

そしてサーバーが再起動された場合(アップデートやクラウドプロバイダーからのマイグレーションの後など)、おそらくあなたはそれに気づかないでしょう。そのため、プロセスを手動で再起動しなければならないことすら気づかないでしょう。つまり、APIはダウンしたままなのです。😱

起動時に自動的に実行

一般的に、サーバープログラム(Uvicornなど)はサーバー起動時に自動的に開始され、人の介入を必要とせずに、APIと一緒にプロセスが常に実行されるようにしたいと思われます(UvicornがFastAPIアプリを実行するなど)。

別のプログラムの用意

これを実現するために、通常は別のプログラムを用意し、起動時にアプリケーションが実行されるようにします。そして多くの場合、他のコンポーネントやアプリケーション、例えばデータベースも実行されるようにします。

起動時に実行するツールの例

実行するツールの例をいくつか挙げます:

  • Docker
  • Kubernetes
  • Docker Compose
  • Swarm モードによる Docker
  • Systemd
  • Supervisor
  • クラウドプロバイダーがサービスの一部として内部的に処理
  • そのほか...

次の章で、より具体的な例を挙げていきます。

再起動

起動時にアプリケーションが実行されることを確認するのと同様に、失敗後にアプリケーションが再起動されることも確認したいと思われます。

我々は間違いを犯す

私たち人間は常に間違いを犯します。ソフトウェアには、ほとんど常にバグがあらゆる箇所に隠されています。🐛

小さなエラーは自動的に処理される

FastAPIでWeb APIを構築する際に、コードにエラーがある場合、FastAPIは通常、エラーを引き起こした単一のリクエストにエラーを含めます。🛡

クライアントはそのリクエストに対して500 Internal Server Errorを受け取りますが、アプリケーションは完全にクラッシュするのではなく、次のリクエストのために動作を続けます。

重大なエラー - クラッシュ

しかしながら、アプリケーション全体をクラッシュさせるようなコードを書いてUvicornとPythonをクラッシュさせるようなケースもあるかもしれません。💥

それでも、ある箇所でエラーが発生したからといって、アプリケーションを停止させたままにしたくないでしょう。 少なくとも壊れていないパスオペレーションについては、実行し続けたいはずです。

クラッシュ後の再起動

しかし、実行中のプロセスをクラッシュさせるような本当にひどいエラーの場合、少なくとも2〜3回ほどプロセスを再起動させる外部コンポーネントが必要でしょう。

Tip

...とはいえ、アプリケーション全体がすぐにクラッシュするのであれば、いつまでも再起動し続けるのは意味がないでしょう。しかし、その場合はおそらく開発中か少なくともデプロイ直後に気づくと思われます。

そこで、将来クラッシュする可能性があり、それでも再スタートさせることに意味があるような、主なケースに焦点を当ててみます。

あなたはおそらく外部コンポーネントがアプリケーションの再起動を担当することを望むと考えます。 なぜなら、その時点でUvicornとPythonを使った同じアプリケーションはすでにクラッシュしており、同じアプリケーションの同じコードに対して何もできないためです。

自動的に再起動するツールの例

ほとんどの場合、前述した起動時にプログラムを実行するために使用されるツールは、自動で再起動することにも利用されます。

例えば、次のようなものがあります:

  • Docker
  • Kubernetes
  • Docker Compose
  • Swarm モードによる Docker
  • Systemd
  • Supervisor
  • クラウドプロバイダーがサービスの一部として内部的に処理
  • そのほか...

レプリケーション - プロセスとメモリー

FastAPI アプリケーションでは、Uvicorn のようなサーバープログラムを使用し、1つのプロセスで1度に複数のクライアントに同時に対応できます。

しかし、多くの場合、複数のワーカー・プロセスを同時に実行したいと考えるでしょう。

複数のプロセス - Worker

クライアントの数が単一のプロセスで処理できる数を超えており(たとえば仮想マシンがそれほど大きくない場合)、かつサーバーの CPU に複数のコアがある場合、同じアプリケーションで同時に複数のプロセスを実行させ、すべてのリクエストを分散させることができます。

同じAPIプログラムの複数のプロセスを実行する場合、それらは一般的にWorker/ワーカーと呼ばれます。

ワーカー・プロセス と ポート

HTTPSについてのドキュメントで、1つのサーバーで1つのポートとIPアドレスの組み合わせでリッスンできるのは1つのプロセスだけであることを覚えていますでしょうか?

これはいまだに同じです。

そのため、複数のプロセスを同時に持つにはポートでリッスンしている単一のプロセスが必要であり、それが何らかの方法で各ワーカー・プロセスに通信を送信することが求められます。

プロセスあたりのメモリー

さて、プログラムがメモリにロードする際には、例えば機械学習モデルや大きなファイルの内容を変数に入れたりする場合では、サーバーのメモリ(RAM)を少し消費します。

そして複数のプロセスは通常、メモリを共有しません。これは、実行中の各プロセスがそれぞれ独自の変数やメモリ等を持っていることを意味します。つまり、コード内で大量のメモリを消費している場合、各プロセスは同等の量のメモリを消費することになります。

サーバーメモリー

例えば、あなたのコードが 1GBのサイズの機械学習モデルをロードする場合、APIで1つのプロセスを実行すると、少なくとも1GBのRAMを消費します。

また、4つのプロセス(4つのワーカー)を起動すると、それぞれが1GBのRAMを消費します。つまり、合計でAPIは4GBのRAMを消費することになります。

リモートサーバーや仮想マシンのRAMが3GBしかない場合、4GB以上のRAMをロードしようとすると問題が発生します。🚨

複数プロセス - 例

この例では、2つのワーカー・プロセスを起動し制御するマネージャー・ プロセスがあります。

このマネージャー・ プロセスは、おそらくIPのポートでリッスンしているものです。そして、すべての通信をワーカー・プロセスに転送します。

これらのワーカー・プロセスは、アプリケーションを実行するものであり、リクエストを受けてレスポンスを返すための主要な計算を行い、あなたが変数に入れたものは何でもRAMにロードします。

そしてもちろん、同じマシンでは、あなたのアプリケーションとは別に、他のプロセスも実行されているでしょう。

興味深いことに、各プロセスが使用するCPUの割合は時間とともに大きく変動する可能性がありますが、メモリ(RAM)は通常、多かれ少なかれ安定します。

毎回同程度の計算を行うAPIがあり、多くのクライアントがいるのであれば、CPU使用率もおそらく安定するでしょう(常に急激に上下するのではなく)。

レプリケーション・ツールと戦略の例

これを実現するにはいくつかのアプローチがありますが、具体的な戦略については次の章(Dockerやコンテナの章など)で詳しく説明します。

考慮すべき主な制約は、パブリックIPポートを処理する単一のコンポーネントが存在しなければならないということです。

そして、レプリケートされたプロセス/ワーカーに通信を送信する方法を持つ必要があります。

考えられる組み合わせと戦略をいくつか紹介します:

  • GunicornUvicornワーカーを管理
    • GunicornはIPポートをリッスンするプロセスマネージャで、レプリケーションは複数のUvicornワーカー・プロセスを持つことによって行われる。
  • UvicornUvicornワーカーを管理
    • 1つのUvicornのプロセスマネージャーIPポートをリッスンし、複数のUvicornワーカー・プロセスを起動する。
  • Kubernetesやその他の分散コンテナ・システム
    • Kubernetesレイヤーの何かがIPポートをリッスンする。レプリケーションは、複数のコンテナにそれぞれ1つのUvicornプロセスを実行させることで行われる。
  • クラウド・サービスによるレプリケーション
    • クラウド・サービスはおそらくあなたのためにレプリケーションを処理します。実行するプロセスや使用するコンテナイメージを定義できるかもしれませんが、いずれにせよ、それはおそらく単一のUvicornプロセスであり、クラウドサービスはそのレプリケーションを担当するでしょう。

Tip

これらのコンテナやDockerそしてKubernetesに関する項目が、まだあまり意味をなしていなくても心配しないでください。

コンテナ・イメージ、Docker、Kubernetesなどについては、次の章で詳しく説明します: コンテナ内のFastAPI - Docker.

開始前の事前のステップ

アプリケーションを開始する前に、いくつかのステップを実行したい場合が多くあります。

例えば、データベース・マイグレーション を実行したいかもしれません。

しかしほとんどの場合、これらの手順を1度に実行したいと考えるでしょう。

そのため、アプリケーションを開始する前の事前のステップを実行する単一のプロセスを用意したいと思われます。

そして、それらの事前のステップを実行しているのが単一のプロセスであることを確認する必要があります。このことはその後アプリケーション自体のために複数のプロセス(複数のワーカー)を起動した場合も同様です。

これらのステップが複数のプロセスによって実行された場合、並列に実行されることによって作業が重複することになります。そして、もしそのステップがデータベースのマイグレーションのような繊細なものであった場合、互いに競合を引き起こす可能性があります。

もちろん、事前のステップを何度も実行しても問題がない場合もあり、その際は対処がかなり楽になります。

Tip

また、セットアップによっては、アプリケーションを開始する前の事前のステップが必要ない場合もあることを覚えておいてください。

その場合は、このようなことを心配する必要はないです。🤷

事前ステップの戦略例

これはシステムをデプロイする方法に大きく依存するだろうし、おそらくプログラムの起動方法や再起動の処理などにも関係してくるでしょう。

考えられるアイデアをいくつか挙げてみます:

  • アプリコンテナの前に実行されるKubernetesのInitコンテナ
  • 事前のステップを実行し、アプリケーションを起動するbashスクリプト
    • 利用するbashスクリプトを起動/再起動したり、エラーを検出したりする方法は以前として必要になるでしょう。

Tip

コンテナを使った具体的な例については、次の章で紹介します: コンテナ内のFastAPI - Docker.

リソースの利用

あなたのサーバーはリソースであり、プログラムを実行しCPUの計算時間や利用可能なRAMメモリを消費または利用することができます。

システムリソースをどれくらい消費/利用したいですか? 「少ない方が良い」と考えるのは簡単かもしれないですが、実際には、クラッシュせずに可能な限り最大限に活用したいでしょう。

3台のサーバーにお金を払っているにも関わらず、そのRAMとCPUを少ししか使っていないとしたら、おそらくお金を無駄にしている 💸、おそらくサーバーの電力を無駄にしている 🌎ことになるでしょう。

その場合は、サーバーを2台だけにして、そのリソース(CPU、メモリ、ディスク、ネットワーク帯域幅など)をより高い割合で使用する方がよいでしょう。

一方、2台のサーバーがあり、そのCPUとRAMの100%を使用している場合、ある時点で1つのプロセスがより多くのメモリを要求し、サーバーはディスクを「メモリ」として使用しないといけません。(何千倍も遅くなる可能性があります。) もしくはクラッシュすることもあれば、あるいはあるプロセスが何らかの計算をする必要があり、そしてCPUが再び空くまで待たなければならないかもしれません。

この場合、1つ余分なサーバーを用意し、その上でいくつかのプロセスを実行し、すべてのサーバーが十分なRAMとCPU時間を持つようにするのがよいでしょう。

また、何らかの理由でAPIの利用が急増する可能性もあります。もしかしたらそれが流行ったのかもしれないし、他のサービスやボットが使い始めたのかもしれないです。そのような場合に備えて、余分なリソースを用意しておくと安心でしょう。

例えば、リソース使用率の50%から90%の範囲任意の数字をターゲットとすることができます。

重要なのは、デプロイメントを微調整するためにターゲットを設定し測定することが、おそらく使用したい主要な要素であることです。

htopのような単純なツールを使って、サーバーで使用されているCPUやRAM、あるいは各プロセスで使用されている量を見ることができます。あるいは、より複雑な監視ツールを使って、サーバに分散して使用することもできます。

まとめ

アプリケーションのデプロイ方法を決定する際に、考慮すべきであろう主要なコンセプトのいくつかを紹介していきました:

  • セキュリティ - HTTPS
  • 起動時の実行
  • 再起動
  • レプリケーション(実行中のプロセス数)
  • メモリー
  • 開始前の事前ステップ

これらの考え方とその適用方法を理解することで、デプロイメントを設定したり調整したりする際に必要な直感的な判断ができるようになるはずです。🤓

次のセクションでは、あなたが取り得る戦略について、より具体的な例を挙げます。🚀