Cloud Functions 実行環境

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

ランタイム

Cloud Functions では、複数の言語のランタイムをサポートしています。

ランタイム 言語バージョン ベースイメージ
Node.js 6(非推奨) Node.js 6.16.0 Debian 8
Node.js 8 Node.js 8.15.0 Ubuntu 18.04
Node.js 10(ベータ版) Node.js 10.16.2 Ubuntu 18.04
Python Python 3.7.1 Ubuntu 18.04
Go 1.11 1.11.6 Ubuntu 18.04
Go 1.13 1.13.1 Ubuntu 18.04

特に断りのない限り、ランタイムの更新は通常自動的に行われます。言語コミュニティで利用可能になると、すべてのランタイムが言語バージョンに対する更新を自動的に受信します。同様に、Cloud Functions はオペレーティング システムや含まれているパッケージなど、実行環境の他の部分にも更新を適用します。これらの更新により、関数の保護を継続的に行うことができます。

ステートレス関数

Cloud Functions はサーバーレス パラダイムを実装しています。このパラダイムでは、サーバーや仮想マシンなどの基礎となるインフラストラクチャを意識せずにコードを実行できます。Google に関数の自動管理とスケーリングを許可するには、それらがステートレスである必要があります。つまり、1 つの関数呼び出しが、前の呼び出しで設定されたメモリ内の状態に依存しないようにする必要があります。ただし、多くの場合、既存の状態をパフォーマンスの最適化に再利用できます。詳細については、ヒントとアドバイスの推奨事項をご覧ください。

たとえば、呼び出しは、グローバル変数、メモリ、ファイル システムなどの状態を共有していない複数の関数インスタンスによって処理される可能性があるため、後続の関数によって返されるカウンタ値は総関数呼び出し回数と一致しません。

Node.js

// Global variable, but only shared within function instance.
    let count = 0;

    /**
     * HTTP Cloud Function that counts how many times
     * it is executed within a specific instance.
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.executionCount = (req, res) => {
      count++;

      // Note: the total function invocation count across
      // all instances may not be equal to this value!
      res.send(`Instance execution count: ${count}`);
    };

Python

# Global variable, modified within the function by using the global keyword.
    count = 0

    def statelessness(request):
        """
        HTTP Cloud Function that counts how many times it is executed
        within a specific instance.
        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>.
        """
        global count
        count += 1

        # Note: the total function invocation count across
        # all instances may not be equal to this value!
        return 'Instance execution count: {}'.format(count)

Go


    // Package http provides a set of HTTP Cloud Functions samples.
    package http

    import (
    	"fmt"
    	"net/http"
    )

    // count is a global variable, but only shared within a function instance.
    var count = 0

    // ExecutionCount is an HTTP Cloud Function that counts how many times it
    // is executed within a specific instance.
    func ExecutionCount(w http.ResponseWriter, r *http.Request) {
    	count++

    	// Note: the total function invocation count across
    	// all instances may not be equal to this value!
    	fmt.Fprintf(w, "Instance execution count: %d", count)
    }
    

関数呼び出し間で状態を共有する必要がある場合は、関数で DatastoreFirestoreCloud Storage などのサービスを使用してデータを保持する必要があります。利用可能なストレージ オプションについては、ストレージ オプションの選択をご覧ください。

自動スケーリングと同時実行

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

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

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

同時リクエストは別々の関数インスタンスによって処理されるため、変数またはローカルメモリが共有されません。詳細については、このドキュメントの後半で説明します。

自動スケーリングの制御

Cloud Functions では、同時に存在可能な関数インスタンスの数の上限を設定できます。無制限のスケーリングが好ましくな場合もあります。たとえば、関数が Cloud Functions と同程度にスケーリングできないリソース(データベースなど)に依存している場合があります。リクエスト量が急増すると、Cloud Functions がデータベースの許容範囲を超えて関数インスタンスを作成する可能性があります。

コールド スタート

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

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

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

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

関数インスタンスの寿命

一般的に、関数インスタンスを実行する環境は、インスタンス数の減少(持続トラフィックの不足による)や関数のクラッシュが発生しない限り、回復力があり、後続の関数呼び出しで再利用されます。つまり、ある関数の実行が終了すると、別の関数呼び出しが同じ関数インスタンスによって処理されます。そのため、可能であれば、グローバル スコープで呼び出し間の状態をキャッシュに保存することをおすすめします。次の呼び出しが同じ関数インスタンスに送られる保証はないため、このキャッシュが使用できなくても機能するように関数を準備する必要があります(ステートレス関数を参照)。

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

単一の関数呼び出しでは、エントリ ポイントとして宣言された関数の本体だけが実行されます。関数定義が含まれている必要がある関数ファイル内のグローバル スコープは、コールド スタートごとに実行されますが、インスタンスがすでに初期化されている場合は実行されません。

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)
    }
    

グローバル スコープは、関数コードが新しい関数インスタンスで(およびその後の新しい関数インスタンスの作成ごとに)呼び出される前に、一度だけ実行されているとみなすことができます。ただし、グローバル スコープの総実行回数やタイミングは Google が管理する自動スケーリングによって異なるため、それらに依存しないようにしてください。

関数実行タイムライン

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

たとえば、HTTP レスポンスの送信後に実行されたコードは、いつでも中断される可能性があります。

Node.js

/**
     * HTTP Cloud Function that may not completely
     * execute due to early HTTP response
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.afterResponse = (req, res) => {
      res.end();

      // This statement may not execute
      console.log('Function complete!');
    };

実行保証

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

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

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

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

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

エラー

関数でエラーを通知するための推奨されている方法は、関数の種類によって異なります。

  • HTTP 関数は、エラーを示す適切な HTTP ステータス コードを返す必要があります。例については、HTTP 関数をご覧ください。

  • バックグラウンド関数は、エラー メッセージをログに記録して返します。例については、バックグラウンド関数をご覧ください。

推奨されている方法でエラーが返された場合、エラーを返した関数インスタンスは正常に動作するものとしてラベル付けされ、必要に応じて以降のリクエストに対応できます。

自分のコードまたは自分が呼び出したコードが、捕捉されなかった例外をスローしたか、現在のプロセスをクラッシュさせた場合は、次の呼び出しを処理する前に関数インスタンスが再起動されることがあります。この方法は、コールド スタートが増えて、レイテンシが長くなる可能性があるため、推奨されていません。

Cloud Functions でエラーを報告する方法の詳細については、エラー報告をご覧ください。

タイムアウト

関数の実行時間は、関数のデプロイ時に指定されたタイムアウト時間によって制限されます。デフォルトでは、関数は 1 分後にタイムアウトしますが、この期間を最大 9 分まで延長できます。

関数の実行がタイムアウトするとすぐに、呼び出し側にエラー ステータスが返されます。タイムアウトした関数インスタンスが使用していた CPU リソースは調整されるため、リクエストの処理が一時停止する可能性があります。一時停止した作業は後続のリクエストで処理される場合もあれば、処理されない場合もあります。このため、予期しない問題が発生する可能性があります。

以下のスニペットのコードは、関数の実行開始から 2 分後に実行されるように設定されています。タイムアウトが 1 分に設定された場合は、このコードが実行されることはありません。

Node.js

/**
     * HTTP Cloud Function that may not completely
     * execute due to function execution timeout
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.afterTimeout = (req, res) => {
      setTimeout(() => {
        // May not execute if function's timeout is <2 minutes
        console.log('Function running...');
        res.end();
      }, 120000); // 2 minute delay
    };

Python

def timeout(request):
        print('Function running...')
        time.sleep(120)

        # May not execute if function's timeout is <2 minutes
        print('Function completed!')
        return 'Function completed!'

Go


    // Package tips contains tips for writing Cloud Functions in Go.
    package tips

    import (
    	"fmt"
    	"log"
    	"net/http"
    	"time"
    )

    // Timeout sleeps for 2 minutes and may time out before finishing.
    func Timeout(w http.ResponseWriter, r *http.Request) {
    	log.Println("Function execution started...")
    	time.Sleep(2 * time.Minute)
    	log.Println("Function completed!")
    	fmt.Fprintln(w, "Function completed!")
    }
    

上記のコードが正常に実行されても、予期しない結果になることがあります。関数がタイムアウトした場合について考えてみましょう。リクエストを処理しているインスタンスは一時停止します(CPU の調整により)。保留中の作業は一時停止になります。後続のリクエストが同じインスタンスにルーティングされると、作業が再開し、Function running... ログに出力されます。

この動作では、結果として 1 つのリクエストの作業とログが後続のリクエストに「リーク」されています。一時停止した作業が再開するかどうかはわからないため、この動作には依存しないでください。代わりに、次の方法を組み合わせて関数のタイムアウトを回避してください。

  1. 予想される関数の実行時間より長いタイムアウトを設定する。
  2. 実行中に残り時間を追跡し、クリーンアップまたは終了を早めに実行する。

gcloud コマンドライン ツールで関数の最大実行時間を設定するには、デプロイ時に --timeout フラグを使用します。

    gcloud functions deploy FUNCTION_NAME --timeout=TIMEOUT FLAGS...

上記のコマンドで、FLAGS... は、関数のデプロイ時に渡す他のオプションを表します。deploy コマンドの詳細については、gcloud functions deploy をご覧ください。

次のように、関数の作成中に Cloud Console でタイムアウトを設定することもできます。

  1. Cloud Console の Cloud Functions の概要ページに移動します。

  2. [関数を作成] をクリックします。

  3. 関数の必須フィールドを入力します。

  4. [詳細] をクリックして、詳細設定を表示します。

  5. [タイムアウト] フィールドに値を入力します。

ファイル システム

関数の実行環境には、実行可能な関数ファイルに加えて、ローカル依存関係などの、デプロイされた関数パッケージに含まれているファイルやディレクトリも存在します。これらのファイルは、読み取り専用ディレクトリで使用できます。このディレクトリは、関数ファイルの場所に基づいて決定できます。関数のディレクトリは、現在の作業ディレクトリと異なる場合があるので注意してください。

次の例は、関数ディレクトリに配置されたファイルを一覧表示します。

Node.js

const fs = require('fs');

    /**
     * HTTP Cloud Function that lists files in the function directory
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.listFiles = (req, res) => {
      fs.readdir(__dirname, (err, files) => {
        if (err) {
          console.error(err);
          res.sendStatus(500);
        } else {
          console.log('Files', files);
          res.sendStatus(200);
        }
      });
    };

Python

def list_files(request):
        import os
        from os import path

        root = path.dirname(path.abspath(__file__))
        children = os.listdir(root)
        files = [c for c in children if path.isfile(path.join(root, c))]
        return 'Files: {}'.format(files)

Go


    // Package tips contains tips for writing Cloud Functions in Go.
    package tips

    import (
    	"fmt"
    	"io/ioutil"
    	"log"
    	"net/http"
    )

    // ListFiles lists the files in the current directory.
    func ListFiles(w http.ResponseWriter, r *http.Request) {
    	files, err := ioutil.ReadDir("./")
    	if err != nil {
    		http.Error(w, "Unable to read files", http.StatusInternalServerError)
    		log.Printf("ioutil.ListFiles: %v", err)
    		return
    	}
    	fmt.Fprintln(w, "Files:")
    	for _, f := range files {
    		fmt.Fprintf(w, "\t%v\n", f.Name())
    	}
    }
    

この関数を使用して、デプロイされた他のファイルからコードを読み込むこともできます。

ファイル システムで書き込み可能な部分は /tmp ディレクトリだけです。このディレクトリは、関数インスタンスの一時ファイルの保存先として使用できます。このディレクトリは、ボリュームに書き込まれたデータがメモリに格納される「tmpfs」ボリュームと呼ばれるローカル ディスクのマウント ポイントです。これにより、関数用にプロビジョニングされたメモリリソースが消費されるので注意してください。

ファイル システムの残りの部分は読み取り専用で、関数からアクセスできます。

ネットワーク

関数は、ランタイム プロバイダまたはサードパーティ プロバイダから提供された標準ライブラリを使用して、パブリック インターネットにアクセスできます。たとえば、次のように HTTP エンドポイントを呼び出すことができます。

Node.js

const fetch = require('node-fetch');

    /**
     * HTTP Cloud Function that makes an HTTP request
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.makeRequest = async (req, res) => {
      const url = 'https://example.com'; // URL to send the request to
      const externalRes = await fetch(url);
      res.sendStatus(externalRes.ok ? 200 : 500);
    };

Python

def make_request(request):
        """
        HTTP Cloud Function that makes another HTTP request.
        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>.
        """
        import requests

        # The URL to send the request to
        url = 'http://example.com'

        # Process the request
        response = requests.get(url)
        response.raise_for_status()
        return 'Success!'

Go


    // Package http provides a set of HTTP Cloud Functions samples.
    package http

    import (
    	"fmt"
    	"net/http"
    	"time"
    )

    var urlString = "https://example.com"

    // client is used to make HTTP requests with a 10 second timeout.
    // http.Clients should be reused instead of created as needed.
    var client = &http.Client{
    	Timeout: 10 * time.Second,
    }

    // MakeRequest is an example of making an HTTP request. MakeRequest uses a
    // single http.Client for all requests to take advantage of connection
    // pooling and caching. See https://godoc.org/net/http#Client.
    func MakeRequest(w http.ResponseWriter, r *http.Request) {
    	resp, err := client.Get(urlString)
    	if err != nil {
    		http.Error(w, "Error making request", http.StatusInternalServerError)
    		return
    	}
    	if resp.StatusCode != http.StatusOK {
    		msg := fmt.Sprintf("Bad StatusCode: %d", resp.StatusCode)
    		http.Error(w, msg, http.StatusInternalServerError)
    		return
    	}
    	fmt.Fprintf(w, "ok")
    }
    

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

複数の関数

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

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