🐙

Cloud SchedulerでCloud Functionsを起動する

2024/01/09に公開

Cloud SchedulerからCloud Functionsを起動しようとしたところ、認証部分でハマってしまったため、備忘を兼ねて設定手順を記録しました。

やったこと

Cloud Functions上にagraffeを使ってAPIサーバを構築し、Cloud Schedulerで定期的に呼び出しを行うジョブを作成しました。Cloud Functions第二世代を用いています。

手順

以下の手順で構築します。

  1. Cloud Functionsへの関数のデプロイ
  2. Cloud Scheduler Jobの作成
    • HTTPターゲットでの認証に用いるサービスアカウントを作成する
    • サービスアカウントにCloud Functionsの呼び出しを許可する
    • Scheduler Jobを作成する

1. Cloud Functionsへの関数のデプロイ

今回デプロイするコードは以下です。
agraffeを用いてCloud Function上にAPIサーバを構築します。

requirements.txt
agraffe==0.7.0
fastapi==0.108.0
main.py
from agraffe import Agraffe
from fastapi import FastAPI

app = FastAPI()

@app.get("/hello/{hello_id}")
def get_hello_world(hello_id: str):
    print("hello:", hello_id)

@app.post("/hell/{hell_id}")
def get_hell_world(hell_id: str):
    print("hell:", hell_id)

entry_point = Agraffe.entry_point(app)

AuthenticationRequire authenticationを選択し、関数の呼び出しは認証付きとします。
今回はagraffeを用いているため、Entry pointentry_pointを指定します(参考: agraffe)。

2. Cloud Scheduler Jobの作成

Cloud Functionsの関数を呼び出すCloud Scheduler Jobを作成します。
今回はHTTPターゲットを使用するので、認証で用いるサービスアカウントを作成します。

HTTPターゲットでの認証に用いるサービスアカウントを作成する

今回は invoke-gcf というサービスアカウントを作成しました。ロールは空のまま作成しています。

サービスアカウントにCloud Functionsの呼び出しを許可する

関数の呼び出しを行うサービスアカウントにCloud Functionsの呼び出しを許可する必要があります。

Cloud FunctionsはCloud Runの上で動いています。権限の付与はCloud Runのコンソールから行います。先ほど作成したサービスアカウントにCloud Run Invokerを許可します。

手順は「HTTP ターゲットで認証を使用する」の「サービス アカウントを設定する」に記載があります。

Scheduler Jobを作成する

Cloud Functionsを起動するジョブを作成します。

Configure the executionを下記の通り設定します。

  • Target type: HTTP
    HTTPターゲットを使用するためHTTPを指定します。
  • URL: https://region-project-id.cloudfunctions.net/gcf_id/hello/id12345
    Cloud Functionsを呼び出す際のURLを入力します。
    今回は /hello/{hello_id} を呼び出したいので上記のURLとしています。
  • HTTP method: GET
  • HTTP headers
    Auth headerのみ設定します。
    他のヘッダは空のままでもUser-Agent: Google-Cloud-Schedulerが自動で付与されます。
    Auth headerの設定は下記の通りです。
    • Auth header: Add OIDC token
      こちらを参考にOIDCを指定しています。

    • Service account: invoke-gcf
      先ほど作成したサービスアカウントを指定します。
      HTTP呼び出しの際の認証に使用されます。

    • Audience: https://region-project-id.cloudfunctions.net/gcf_id
      こちらを参照しました。

      通常は、ジョブのターゲット URL(URL パラメータなし)です。

      とのことなので、パスパラメータ/hello/id12345を除いています。
      因みにCloud RunのURL https://gcf-id-xxxxxxxxxx-uc.a.run.app でも動作します。
      未入力の場合、デフォルトでリクエスト先のURLが入力されます。今回はパスパラメータ/hello/id12345がついているため、これを除かないと下記の401エラーが発生します(参考)。

      The request was not authorized to invoke this service. Read more at https://cloud.google.com/run/docs/securing/authenticating Additional troubleshooting documentation can be found at: https://cloud.google.com/run/docs/troubleshooting#401
      

ここまで完了すると、Cloud SchedulerのジョブからCloud Functionsを起動することができます。
手順は以上です。

Audienceについて

今回ハマったのがSchedulerのAudienceの設定間違いだったため、Audienceは何に使われるのか、少し調べてみました。サービス間認証のページの認証サンプルコードが参考になりました。

実際にサンプルコードを参考に作成したスクリプトが下記です。

main.py
import os
import requests

import google.auth.transport.requests
import google.oauth2.service_account

def make_authorized_get_request(endpoint: str, audience: str, sa_creds_path: str) -> requests.models.Response:
    # ID Tokenの取得
    ## audienceとサービスアカウントのクレデンシャルを使ってIDトークンを取得している
    creds = google.oauth2.service_account.IDTokenCredentials.from_service_account_file(
        sa_creds_path,
        target_audience=audience,
    )
    # HTTP GETリクエスト
    ## endpoint宛にHTTPリクエストを送信している
    authed_session = google.auth.transport.requests.AuthorizedSession(creds)
    response = authed_session.get(endpoint)
    return response

if __name__ == "__main__":
    endpoint = "https://region-project-id.cloudfunctions.net/gcf-id/hello/hello54321"
    audience = "https://region-project-id.cloudfunctions.net/gcf-id"
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "./credential.json"

    response = make_authorized_get_request(
        endpoint, audience,
        os.environ["GOOGLE_APPLICATION_CREDENTIALS"],
    )

参照したサンプルコードではurllibを使用していますが、上記ではAuthorizedSessionを用いています。AuthorizedSessionrequests.Sessionを継承しています。
また、サンプルコードではfetch_id_tokenを用いているところを上記ではIDTokenCredentials.from_service_account_fileを用いています。fetch_id_tokenIDTokenCredentials.from_service_account_infoを使って実装されているため、上記コードもほぼ同じ意味となっています。

上記のコードからわかるように、Audience (audience) はIDトークン取得のために使われ、Audienceを使って取得したIDトークンを用いてURL (endpoint) に対してリクエストを送出しています。

一旦ここまでで自分としてはAudienceが何かについて納得することはできました。
この先はOpen ID Connectについて理解を深める必要がありそうなため、本記事はここまでとし詳細は別記事とします (作成するかどうかはわかりません)。
以下ページが参考になりそうです。

終わりに

以上、Cloud Scheduerから認証付きのCloud Functionを呼び出す際の手順についての記録でした。

参照

Discussion