このガイドでは、Knative serving サービスを設計、実装、テスト、デプロイするためのベスト プラクティスについて説明します。その他のヒントについては、既存のサービスを移行するをご覧ください。
効率の良いサービスを作成する
このセクションでは、Knative serving サービスを設計および実装するための一般的なベスト プラクティスについて説明します。
バックグラウンド アクティビティを回避する
Knative serving で実行中のアプリケーションがリクエストの処理を終了すると、コンテナ インスタンスの CPU へのアクセスが無効になるか、厳しく制限されます。したがって、リクエスト ハンドラの範囲外で実行されるバックグラウンド スレッドやルーティンを開始しないでください。
バックグラウンド スレッドを実行すると、同じコンテナ インスタンスへの後続のリクエストが中断されたバックグラウンド アクティビティを再開するため、予期しない動作が発生することがあります。
バックグラウンド アクティビティは、HTTP レスポンスの送信後に発生します。コードを確認し、レスポンスの送信前にすべての非同期処理が完了するようにしてください。
サービスにすぐに実行されないバックグラウンド アクティビティがあると思われる場合は、ログを確認して、HTTP リクエストのエントリの後に記録されたものを調べてください。
一時ファイルを削除する
Cloud Run 環境では、メモリ内ファイル システムがディスク ストレージになります。ディスクに書き込まれたファイルにより、サービスで本来使用されないメモリが次の呼び出しまで継続的に使用される可能性があります。これらのファイルを削除しないと、最終的にメモリ不足エラーにつながり、その結果コールド スタートが発生する可能性があります。
パフォーマンスを最適化する
このセクションでは、パフォーマンスを最適化するためのベスト プラクティスについて説明します。
サービスを迅速に開始する
コンテナ インスタンスは必要に応じてスケーリングされます。このため、実行環境を完全に初期化するのが一般的です。このような初期化をコールド スタートといいます。クライアント リクエストでコールド スタートがトリガーされると、コンテナ インスタンスの起動にレイテンシが発生します。
起動ルーティンでは次の処理が行われます。
- サービスを開始する
- コンテナを起動する
- entrypoint コマンドを実行してサーバーを起動する
- 開いているサービスポートを確認する
サービスの起動速度を最適化すると、コンテナ インスタンスのリクエスト処理を遅らせるレイテンシを最小限に抑えられます。
依存関係を上手に利用する
Node.js にモジュールをインポートするなど、依存ライブラリのある動的言語を使用する場合、こうしたモジュールの読み込み時間によってコールド スタート時のレイテンシが長くなります。起動時のレイテンシを短縮するには、次のような対策を行います。
- 依存関係の数とサイズを最小限に抑えてリーンサービスを構築する。
- 使用している言語でサポートされている場合は、使用頻度の低いコードの読み込みを延期する。
- PHP の Composer オートローダーの最適化など、コード ローディングの最適化を行う。
グローバル変数を使用する
Knative serving では、リクエスト間でサービスの状態が維持されるとは限りません。ただし、Knative serving はコンテナ インスタンスを再利用してトラフィックの処理を継続するため、グローバル スコープで変数を宣言することで、その値を以降の呼び出しで再利用できます。個々のリクエストで値が再利用されるかどうかを事前に確認することはできません。
サービス リクエストごとに再利用を行うとコストが高くなる場合は、オブジェクトをメモリ キャッシュに保存することもできます。これをリクエストのロジックではなく、グローバル スコープで行うと、パフォーマンスが向上します。
Node.js
Python
Go
Java
グローバル変数の初期化を遅らせる
起動時にグローバル変数の初期化が行われるため、コールド スタートの時間が長くなります。使用頻度の低いオブジェクトの場合は、初期化を延期することで時間の消費を先送りし、コールド スタートの時間を短縮できます。
Node.js
Python
Go
Java
同時実行を最適化する
Knative serving インスタンスは、構成可能な最大同時実行数まで複数のリクエストを同時に処理できます。これは、concurrency = 1
を使用する Cloud Functions とは異なります。
コードに特別な同時実行要件がある場合を除き、デフォルトの最大同時実行数の設定を維持する必要があります。
サービスの同時実行を調整する
各コンテナ インスタンスで処理できる同時リクエストの数は、技術スタックと、変数やデータベース接続などの共有リソースの使用によって制限される可能性があります。
最も安定した状態で同時実行が行われるようにサービスを最適化するには:
- サービスのパフォーマンスを最適化します。
- コードレベルの同時実行に、予想される同時実行のサポートレベルを構成します。すべてのテクノロジー スタックでこのような設定が必要になるわけではありません。
- サービスをデプロイします。
- Knative serving の同時実行の設定で、コードレベルと同等またはそれより低い値をサービスに構成します。コードレベルの構成がない場合は、予想される同時実行の値を使用します。
- 同時実行を構成できる負荷テストツールを使用します。予想される負荷と同時実行の設定でサービスの動作が安定していることを確認します。
- サービスのパフォーマンスが低下している場合は、ステップ 1 に戻ってサービスを改善するか、ステップ 2 に戻って同時実行数を少なくします。サービスが正常に動作するようになったら、ステップ 2 に戻って同時実行数を増やします。
同時実行が最も安定した状態になるまで、この操作を繰り返します。
メモリを同時実行に合わせる
サービスがリクエストを処理するたびに、ある程度の追加メモリが必要になります。このため、同時実行を調整する場合には、メモリ制限も調整する必要があります。
変更可能なグローバル状態を避ける
同時実行のコンテキストで変更可能なグローバル状態を使用する場合は、この処理が安全に行われるように対策を行う必要があります。グローバル変数の初期化を 1 回に限定することで競合を最小限に抑え、上記のパフォーマンスで説明したように再利用します。
同時に複数のリクエストを処理するサービスで変更可能なグローバル変数を使用する場合は、ロックまたはミューテックスを使用して競合状態を防ぐ必要があります。
コンテナ セキュリティ
コンテナ化されたアプリケーションにも、多くのソフトウェアで使用されているセキュリティ プラクティスが適用されます。このような対策の中には、コンテナに固有のものや、コンテナの概念やアーキテクチャに合わせて調整が必要なものがあります。
コンテナのセキュリティを向上させるには:
Google のベースイメージなど、積極的にメンテナンスされている安全なベースイメージか、Docker Hub の公式イメージを使用します。
コンテナ イメージを定期的にビルドしてサービスをデプロイし直すことで、サービスにセキュリティ更新プログラムを適用します。
サービスの実行に必要なものだけをコンテナに含めます。余分なコード、パッケージ、ツールはセキュリティ上の脆弱性になる可能性があります。上記のパフォーマンスへの影響をご覧ください。
特定のソフトウェアとライブラリのバージョンを含む確定的なビルドプロセスを実装します。これにより、コンテナに未検証のコードが追加されるのを防ぐことができます。
Dockerfile
USER
ステートメントを使用して、コンテナをroot
以外のユーザーとして実行するように設定します。コンテナ イメージによっては、特定のユーザーが構成されている場合があります。
セキュリティ スキャンの自動化
Artifact Registry に格納されているコンテナ イメージにセキュリティ スキャンを実行するため、脆弱性スキャンを有効にします。
また、Binary Authorization を使用して、セキュアなコンテナ イメージのみがデプロイされるようにすることもできます。
最小コンテナ イメージの作成
コンテナ イメージのサイズが大きいと、コードに必要以上の情報が含まれているため、セキュリティ上の脆弱性が高まります。
Knative serving では、コンテナ イメージのサイズはコールド スタートやリクエストの処理時間に影響せず、コンテナの使用可能なメモリにはカウントされません。
最小サイズのコンテナをビルドするには、次のようなリーンベースのイメージの使用を検討してください。
Ubuntu は、サイズが大きくなりますが、すぐに利用できる完全なサーバー環境でよく使用されているベースイメージです。
サービスでツールに依存するビルドプロセスがある場合は、実行時のコンテナを軽量化するために、マルチステージ ビルドの使用を検討してください。
リーンコンテナ イメージの作成に関する詳細については、次のリソースをご覧ください。