Cloud Functions 実行環境

Cloud Functions は、Google がユーザーの代わりにインフラストラクチャ、オペレーティング システム、およびランタイム環境を運用する、完全に管理されたサーバーレス環境で動作します。各関数は、互いに切り離された独自の安全な実行コンテキストで動作し、自動的にスケーリングします。また、そのライフサイクルは他の関数とは独立しています。

ランタイム

Cloud Functions では、複数の言語のランタイムをサポートしています。コマンドラインから、または Terraform を介して関数をデプロイする場合は、ランタイム ID 値が必要になります。

ランタイム ベースイメージ ランタイム ID
Node.js 16(推奨) Ubuntu 18.04 nodejs16
Node.js 14 Ubuntu 18.04 nodejs14
Node.js 12 Ubuntu 18.04 nodejs12
Node.js 10 Ubuntu 18.04 nodejs10
Node.js 8(非推奨) Ubuntu 18.04 nodejs8
Node.js 6(廃止) Debian 8 nodejs6
Python 3.10(推奨) Ubuntu 22.04 python310
Python 3.9 Ubuntu 18.04 python39
Python 3.8 Ubuntu 18.04 python38
Python 3.7 Ubuntu 18.04 python37
Go 1.16(推奨) Ubuntu 18.04 go116
Go 1.13 Ubuntu 18.04 go113
Go 1.11 Ubuntu 18.04 go111
Java 17(推奨) Ubuntu 22.04 java17
Java 11 Ubuntu 18.04 java11
.NET Core 3.1(推奨) Ubuntu 18.04 dotnet3
Ruby 3.0(推奨) Ubuntu 18.04 ruby30
Ruby 2.7 Ubuntu 18.04 ruby27
Ruby 2.6 Ubuntu 18.04 ruby26
PHP 8.1(推奨) Ubuntu 18.04 php81
PHP 7.4 Ubuntu 18.04 php74

特に明記されていない限り、更新とセキュリティ パッチは、関数をデプロイするときにランタイムとその依存関係に適用されます。これには、言語コミュニティによる更新とパッチが含まれています。これらは、一定期間の安定性のテスト後に提供されます。同様に、Cloud Functions はオペレーティング システムや含まれているパッケージなど、実行環境の他の部分にも更新を適用します。これらの更新により、関数の保護を継続的に行うことができます。

自動スケーリングの動作

Cloud Functions はサーバーレス パラダイムを実装しています。このパラダイムでは、サーバーや仮想マシンなどの基礎となるインフラストラクチャを意識せずにコードを実行できます。デプロイ後、関数は自動的に管理され、スケーリングされます。

Cloud Functions は、受信したリクエストを関数のインスタンスに割り当てて処理します。リクエストの量と既存の関数インスタンスの数に応じて、Cloud Functions は既存のインスタンスに要求を割り当てるか、新しいインスタンスを作成します。

インバウンド リクエストの量が既存のインスタンス数を超えた場合、Cloud Functions は複数の新しいインスタンスを開始し、リクエストを処理します。この自動スケーリングにより、Cloud Functions は関数の異なるインスタンスを使用して、複数のリクエストを同時に処理できるようになります。

無制限のスケーリングが好ましくない場合もあります。これに対応するため、Cloud Functions では、特定の関数について任意の時点で共存可能な最大インスタンス数を構成できます。

ステートレス性

関数の自動管理とスケーリングを有効にするには、関数がステートレスである必要があります。つまり、1 つの関数呼び出しが、前の呼び出しで設定されたメモリ内の状態に依存しないようにする必要があります。呼び出しは、グローバル変数、メモリ、ファイル システムなどの状態を共有しない別の関数インスタンスによって処理されます。

関数呼び出し間で状態を共有する必要がある場合は、関数で MemorystoreDatastoreFirestoreCloud Storage などのサービスを使用してデータを保持する必要があります。Google Cloud が提供するデータベースとストレージ オプションの詳細については、Google Cloud データベースGoogle Cloud ストレージ プロダクトをご覧ください。

同時実行

関数の各インスタンスは一度に 1 つのリクエストを処理します。コードが 1 つのリクエストを処理している間、次のリクエストが同じインスタンスにルーティングされることはありません。元のリクエストは、割り当てられたリソース(メモリと CPU)のすべてを使用できます。

同時リクエストは別々の関数インスタンスによって処理されるため、変数またはローカルメモリが共有されません。詳細については、ステートレス性関数インスタンスの寿命をご覧ください。

コールド スタート

新しい関数インスタンスは、次の 2 つのケースで開始されます。

  • 関数がデプロイされた時点

  • 負荷をスケールアップするため、または、場合によっては既存のインスタンスを置き換えるために、新しい関数インスタンスが自動的に作成された時点

新しい関数インスタンスを開始すると、ランタイムとコードが読み込まれます。関数インスタンスのスタートアップ(コールド スタート)を含むリクエストは、既存の関数インスタンスに送られたリクエストより遅くなる可能性があります。ただし、関数にかかる負荷が安定している場合は、関数が頻繁にクラッシュして、関数環境の再起動が必要にならない限り、コールド スタートの回数は無視できます。

関数コードがキャッチされていない例外をスローしたか、現在のプロセスをクラッシュさせた場合、関数インスタンスが再起動されることがあります。これにより、コールド スタートが増え、レイテンシが増加する可能性があります。例外をキャッチし、現在のプロセスの終了を回避することをおすすめします。Cloud Functions でエラーを処理して報告する方法については、エラーの報告をご覧ください。

関数がレイテンシの影響を受けやすい場合は、コールド スタートを回避できるようインスタンスの最小数を設定することを検討してください。

関数インスタンスの寿命

一般に、関数インスタンスは、持続トラフィックの不足や関数のクラッシュによってインスタンス数がスケールダウンされない限り、回復力があり、後続の関数呼び出しで再利用されます。つまり、ある関数の実行が終了すると、別の関数呼び出しが同じ関数インスタンスによって処理されます。

関数スコープとグローバル スコープ

単一の関数呼び出しでは、エントリ ポイントとして宣言された関数の本体だけが実行されます。関数のソースコードのグローバル スコープは、コールド スタートでのみ実行されます。すでに初期化されているインスタンスでは実行されません。

Node.js

// Global (instance-wide) scope
// This computation runs at instance cold-start
const instanceVar = heavyComputation();

/**
 * HTTP function that declares a variable.
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
exports.scopeDemo = (req, res) => {
  // Per-function scope
  // This computation runs every time this function is called
  const functionVar = lightComputation();

  res.send(`Per instance: ${instanceVar}, per function: ${functionVar}`);
};

Python

# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()

def scope_demo(request):
    """
    HTTP Cloud Function that declares a variable.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """

    # Per-function scope
    # This computation runs every time this function is called
    function_var = light_computation()
    return 'Instance: {}; function: {}'.format(instance_var, function_var)

Go


// h is in the global (instance-wide) scope.
var h string

// init runs during package initialization. So, this will only run during an
// an instance's cold start.
func init() {
	h = heavyComputation()
}

// ScopeDemo is an example of using globally and locally
// scoped variables in a function.
func ScopeDemo(w http.ResponseWriter, r *http.Request) {
	l := lightComputation()
	fmt.Fprintf(w, "Global: %q, Local: %q", h, l)
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

public class Scopes implements HttpFunction {
  // Global (instance-wide) scope
  // This computation runs at instance cold-start.
  // Warning: Class variables used in functions code must be thread-safe.
  private static final int INSTANCE_VAR = heavyComputation();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Per-function scope
    // This computation runs every time this function is called
    int functionVar = lightComputation();

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Instance: %s; function: %s", INSTANCE_VAR, functionVar);
  }

  private static int lightComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).sum();
  }

  private static int heavyComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).reduce((t, x) -> t * x).getAsInt();
  }
}

Ruby

# Global (instance-wide) scope.
# This block runs on cold start, before any function is invoked.
#
# Note: It is usually best to run global initialization in an on_startup block
# instead at the top level of the Ruby file. This is because top-level code
# could be executed to verify the function during deployment, whereas an
# on_startup block is run only when an actual function instance is starting up.
FunctionsFramework.on_startup do
  instance_data = perform_heavy_computation

  # To pass data into function invocations, the best practice is to set a
  # key-value pair using the Ruby Function Framework's built-in "set_global"
  # method. Functions can call the "global" method to retrieve the data by key.
  # (You can also use Ruby global variables or "toplevel" local variables, but
  # they can make it difficult to isolate global data for testing.)
  set_global :my_instance_data, instance_data
end

FunctionsFramework.http "tips_scopes" do |_request|
  # Per-function scope.
  # This method is called every time this function is called.
  invocation_data = perform_light_computation

  # Retrieve the data computed by the on_startup block.
  instance_data = global :my_instance_data

  "instance: #{instance_data}; function: #{invocation_data}"
end

パフォーマンスを最適化するためにグローバル変数を使用できますが、以前の関数呼び出しでグローバル スコープに設定された状態に依存しないでください。詳細については、ステートレス性をご覧ください。

関数インスタンスごとに、関数コードの呼び出し前にグローバル スコープが 1 回だけ実行されていると想定できますが、グローバル スコープの総実行回数やタイミングは自動スケーリングのアクティビティによって異なる可能性があるため、これらに依存しないようにしてください。

関数実行タイムライン

関数は、その実行期間にのみ、割り当てられたリソース(メモリと CPU)にアクセスできます。実行期間外に実行されるコードは必ず実行されるとは限りません。これらのコードはいつでも停止できます。そのため、必ず、関数実行の終了を正確に通知して、それを超えたコードの実行を回避する必要があります。詳細については、HTTP 関数バックグラウンド関数CloudEvent 関数をご覧ください。

関数の実行には、関数のタイムアウト時間も適用されます。詳細については、関数のタイムアウトをご覧ください。

アプリケーションを初期化する場合は、実行タイムラインを考慮してください。バックグラウンド タスクは初期化時にグローバル スコープで作成しないでください。作成すると、リクエストがリクエストの処理時間外に実行されます。

実行保証

通常、関数は、送られてきたイベントごとに 1 回ずつ呼び出されます。ただし、Cloud Functions は、エラーシナリオに違いがあるため、すべてのケースで単一の呼び出しを保証するわけではありません。

1 つのイベントで関数が呼び出される最大回数または最小回数は、関数の種類によって異なります。

  • HTTP 関数は、多くても 1 回しか呼び出されません。これは HTTP 呼び出しの同期性によるもので、関数呼び出し中に発生したエラーは再試行されずに返されることを意味します。HTTP 関数の呼び出し側は、エラーを処理し、必要に応じて再試行する必要があります。

  • イベント ドリブン関数は少なくとも 1 回呼び出されます。イベントの処理は基本的に非同期で行われます。つまり、呼び出し側がレスポンスを待つ必要はありません。まれなケースですが、イベントを確実に処理するため、イベント ドリブン関数が 2 回以上呼び出されることもあります。イベント ドリブン関数の呼び出しがエラーで失敗した場合、その関数に対する失敗時の再試行が有効になっていない限り、再び呼び出されることはありません。

関数が再実行時に正しく動作することを保証するには、イベントが複数回発生した場合でも望ましい結果(および副作用)をもたらすように関数を実装することで、関数をべき等にする必要があります。HTTP 関数の場合は、呼び出し側が HTTP 関数エンドポイントに対する呼び出しを再試行した場合でも、関数が望ましい値を返すことも意味します。関数をべき等にする方法の詳細については、イベント ドリブン関数の再試行をご覧ください。

メモリとファイル システム

各関数には一定量のメモリが割り当てられます。デプロイ時にメモリ容量を構成できます。詳細については、メモリ上限をご覧ください。

関数の実行環境には、関数でデプロイされたソースファイルとディレクトリを含むインメモリ ファイル システムが含まれています(ソースコードの構造化をご覧ください)。ソースファイルを含むディレクトリは読み取り専用ですが、ファイル システムの残りの部分は書き込み可能です(オペレーティング システムで使用されるファイルを除く)。ファイル システムの使用は、関数のメモリ使用量にカウントされます。

関数は、各プログラミング言語の標準メソッドを使用して、ファイル システムを操作できます。

ネットワーク

関数は、ランタイムに提供される組み込みライブラリか、依存関係として含まれるサードパーティ ライブラリを使用して、各プログラミング言語の標準メソッドを使用して公共のインターネットにアクセスできます。

ネットワークの最適化で説明されているように、関数呼び出し間でネットワーク接続を再利用してみてください。ただし、システムが 10 分以上未使用状態の接続を閉じた後、切断された接続を再利用しようとすると、「接続のリセット」エラーが発生することがあります。コードは、閉じられた接続を適切に処理するライブラリを使用するか、低レベルのネットワーキング構造を使用している場合は、閉じられた接続を明示的に処理する必要があります。

関数の分離

デプロイされた関数は他のすべての関数から分離されます。これには、同じソースファイルからデプロイされたものも含まれます。特に、メモリ、グローバル変数、ファイル システムなどの状態は共有されません。

デプロイされた関数間でデータを共有するには、MemorystoreDatastoreFirestoreCloud Storage などのサービスを使用します。また、関数で適切なトリガーを使用して必要なデータを渡し、別の関数を呼び出すこともできます。たとえば、HTTP 関数のエンドポイントに HTTP リクエストを送信します。また、Pub/Sub トピックにメッセージをパブリッシュして Pub/Sub 関数をトリガーします。