この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
CX事業本部@大阪の岩田です。Cloud Functionsはデフォルトで「App Engineデフォルトサービスアカウント」を利用しますが、明示的に自作したサービスアカウントを利用したいケースもあると思います。今回サービスアカウントとCloud Functionsの作成に加えて、作成したサービスアカウントから別のCloud Functionsを呼び指す権限を設定するまでをDeployment Managerで自動化してみたので、ハマりどころを含めて紹介させて頂きます。
1.Cloud Functionsの呼び出し権限を設定せずにデプロイしてみる
まずは呼び出し元の関数と呼び出される側の関数をそれぞれ準備します。
まず呼び出される側です。
def handler(event, conttext):
print('receiver')
ログを出力するだけのシンプルな内容です。こちらは後ほどreceiver
という名前でデプロイします。
続いて呼び出し側です
from google.cloud.functions_v1 import CloudFunctionsServiceClient
PROJECT_ID = '<対象プロジェクトID>'
def handler(event, conttext):
client = CloudFunctionsServiceClient()
func_name = client.cloud_function_path(PROJECT_ID, 'asia-northeast1', 'receiver')
res = client.call_function(name=func_name, data='{}')
print(res)
receiver
を起動するだけのシンプルな内容です。ではこれら2つの関数と関連するリソースをDeployment Managerでデプロイしましょう。事前にそれぞれのソースコードをZIP化し、
- invoker.zip
- receiver.zip
という名前で適当なストレージバケットにアップロードしていることが前提になります。ZIPの準備ができたら以下のテンプレートからまとめてデプロイします。
resources.yml
resources:
- type: pubsub.v1.topic
name: pubsub-topic
properties:
topic: test-topic
- type: gcp-types/iam-v1:projects.serviceAccounts
name: service-account
properties:
accountId: cloud-functions-invoker
displayName: Service Account For Invoke Cloud Functions
- type: gcp-types/cloudfunctions-v1:projects.locations.functions
name: invoker
properties:
parent: projects/<プロジェクトID>/locations/asia-northeast1
function: invoker
sourceArchiveUrl: gs://<適当なストレージバケット>/invoker.zip
serviceAccountEmail: $(ref.service-account.email)
entryPoint: handler
runtime: python39
eventTrigger:
resource: $(ref.pubsub-topic.name)
eventType: providers/cloud.pubsub/eventTypes/topic.publish
- type: gcp-types/cloudfunctions-v1:projects.locations.functions
name: receiver
properties:
parent: projects/<プロジェクトID>/locations/asia-northeast1
function: receiver
sourceArchiveUrl: gs://<適当なストレージバケット>/receiver.zip
entryPoint: handler
runtime: python39
eventTrigger:
resource: $(ref.pubsub-topic.name)
eventType: providers/cloud.pubsub/eventTypes/topic.publish
gcloud CLIを使ってデプロイします
gcloud deployment-manager deployments create cf-test --config resources.yml
デプロイできたらinvokerをテスト実行してみましょう
以下のエラーメッセージが表示され、実行に失敗してしまいます
Error: function terminated. Recommended action: inspect logs for termination reason. Additional troubleshooting documentation can be found at https://cloud.google.com/functions/docs/troubleshooting#logging Details:
500 Internal Server Error: The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
ログの詳細を確認すると、以下のようにPermission 'cloudfunctions.functions.call' denied on resource 'projects/<プロジェクト名>/locations/asia-northeast1/functions/receiver' (or resource may not exist).
というエラーメッセージが出力されていることが分かります。
エラーメッセージから、Deployment Managerの構成ファイル内でCloud Functions用に作成したサービスアカウントが別のCloud Functionsを呼び出す権限を持っていないことが原因と推測できます。
2.Deployment ManagerのaccessControlセクションを使って権限を追加してみる
原因がわかったので、Cloud Functions用のサービスアカウントにcloudfunctions.developer
のロールを割り当てましょう。今回デプロイしている関数はHTTPトリガーではないので、権限としてはcloudfunctions.functions.call
の権限が必要になります
Cloud Functions IAM Permissions
そのため、指定するロールはcloudfunctions.functions.call
の権限を持つroles/cloudfunctions.developer
のロールを指定します。
Deplyment ManagerのaccessControl
を利用して以下のように設定を追加します。
- type: gcp-types/cloudfunctions-v1:projects.locations.functions
name: receiver
properties:
parent: projects/<プロジェクトID>/locations/asia-northeast1
...略
accessControl:
gcpIamPolicy:
bindings:
- role: roles/cloudfunctions.developer
members:
- serviceAccount:$(ref.service-account.email)
構成ファイルが準備できたのでデプロイします
gcloud deployment-manager deployments update cf-test --config resources.yml
これでデプロイ完了すれば先程の権限エラーが解消されるはず...と思いきやこんなエラーが発生してしまいました
The fingerprint of the deployment is b'-FahLD1dFHh7hG29Y4yh-w=='
Waiting for update [operation-1630244417139-5cab2db617393-a5e426c8-7bd2e65c]...failed.
ERROR: (gcloud.deployment-manager.deployments.update) Error in Operation [operation-1630244417139-5cab2db617393-a5e426c8-7bd2e65c]: errors:
- code: RESOURCE_ERROR
location: /deployments/cf-test/resources/receiver
message: '{"ResourceType":"gcp-types/cloudfunctions-v1:projects.locations.functions","ResourceErrorCode":"404","ResourceErrorMessage":{"statusMessage":"Not
Found","requestPath":"https://cloudfunctions.googleapis.com/v1/:setIamPolicy","httpMethod":"POST"}}'
3.gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding
を使って権限を追加してみる
先程のエラーメッセージをググってみると、こんなissueを発見しました
setIamPolicy for Cloud Functions #494
Cloud FunctionsのAPI仕様的にaccessControlセクションが利用できないので、代替策としてgcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding
のリソースをデプロイして、サービスアカウントにロールを割り当てる必要があるそうです。issueを参考にDeployment Managerの構成ファイルに以下の記述を追加します
- name: binding-cf-developer
type: gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding
properties:
resource: $(ref.receiver.name)
role: roles/cloudfunctions.developer
member: serviceAccount:$(ref.service-account.email)
構成ファイルの全体は以下のようになります
resources.yml
resources:
- type: pubsub.v1.topic
name: pubsub-topic
properties:
topic: test-topic
- type: gcp-types/iam-v1:projects.serviceAccounts
name: service-account
properties:
accountId: cloud-functions-invoker
displayName: Service Account For Invoke Cloud Functions
- type: gcp-types/cloudfunctions-v1:projects.locations.functions
name: invoker
properties:
parent: projects/<プロジェクトID>/locations/asia-northeast1
function: invoker
sourceArchiveUrl: gs://<適当なストレージバケット>/invoker.zip
serviceAccountEmail: $(ref.service-account.email)
entryPoint: handler
runtime: python39
eventTrigger:
resource: $(ref.pubsub-topic.name)
eventType: providers/cloud.pubsub/eventTypes/topic.publish
- type: gcp-types/cloudfunctions-v1:projects.locations.functions
name: receiver
properties:
parent: projects/<プロジェクトID>/locations/asia-northeast1
function: receiver
sourceArchiveUrl: gs://<適当なストレージバケット>/receiver.zip
entryPoint: handler
runtime: python39
eventTrigger:
resource: $(ref.pubsub-topic.name)
eventType: providers/cloud.pubsub/eventTypes/topic.publish
- name: binding-cf-developer
type: gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding
properties:
resource: $(ref.receiver.name)
role: roles/cloudfunctions.developer
member: serviceAccount:$(ref.service-account.email)
再度デプロイを実行します
gcloud deployment-manager deployments update cf-test --config resources.yml
すると...
The fingerprint of the deployment is b'7nW3gWkSoKYs9lpVTd1tdA=='
Waiting for update [operation-1630245139328-5cab3066d2dbd-94d7bd65-43fae94a]...failed.
ERROR: (gcloud.deployment-manager.deployments.update) Error in Operation [operation-1630245139328-5cab3066d2dbd-94d7bd65-43fae94a]: errors:
- code: RESOURCE_ERROR
location: /deployments/cf-test/resources/binding-cf-developer
message: "{\"ResourceType\":\"gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding\"\
,\"ResourceErrorCode\":\"403\",\"ResourceErrorMessage\":{\"code\":403,\"message\"\
:\"Permission 'cloudfunctions.functions.setIamPolicy' denied on resource 'projects/<プロジェクト名>/locations/asia-northeast1/functions/receiver'\
\ (or resource may not exist).\",\"status\":\"PERMISSION_DENIED\",\"statusMessage\"\
:\"Forbidden\",\"requestPath\":\"https://cloudfunctions.googleapis.com/v1/projects/<プロジェクト名>/locations/asia-northeast1/functions/receiver:setIamPolicy\"\
,\"httpMethod\":\"POST\"}}"
今度はこんなエラーが出てしまいました
4.Deployment ManagerにIAM ポリシーを設定するための権限を付与してからリトライする
先程のエラーメッセージをググると、以下の公式ドキュメントに行き着きました。
Deployment Manager に IAM ポリシーを設定するための権限を付与する
Deployment Managerは「Google API サービス アカウント」を利用して他のGoogle APIを呼び出しており、この「Google API サービス アカウント」がroles/editor
のロールしか持たないため、Deployment Managerを利用したIAMポリシーの設定は実行できないそうです。上記公式ドキュメントの手順に従って「Google API サービス アカウント」にroles/owner
の権限を追加してみます。
gcloud projects add-iam-policy-binding <プロジェクトID> \
--member serviceAccount:<プロジェクトNo>@cloudservices.gserviceaccount.com --role roles/owner
権限が付与できたので、再度デプロイを実行します
gcloud deployment-manager deployments update cf-test --config resources.yml
今度は無事に成功しました
The fingerprint of the deployment is b'vfH8XsolpIBEGK0Hgo-nuQ=='
Waiting for update [operation-1630247071848-5cab3799d175a-c3b5e8e0-0673592f]...done.
Update operation operation-1630247071848-5cab3799d175a-c3b5e8e0-0673592f completed successfully.
NAME TYPE STATE ERRORS INTENT
binding-cf-developer gcp-types/cloudfunctions-v1:virtual.projects.locations.functions.iamMemberBinding COMPLETED []
invoker gcp-types/cloudfunctions-v1:projects.locations.functions COMPLETED []
pubsub-topic pubsub.v1.topic COMPLETED []
receiver gcp-types/cloudfunctions-v1:projects.locations.functions COMPLETED []
service-account gcp-types/iam-v1:projects.serviceAccounts COMPLETED []
改めてinvokerをテスト実行してみましょう
無事テスト実行に成功しました
まとめ
- デフォルトのポリシーではDeployment ManagerからIAMポリシーの設定ができない
- Deployment Managerの
accessControl
ではCloud Functionsの関数にロールのバインディングを設定できない
という点にハマってしまいました。似たような境遇でお悩みの方の参考になれば幸いです。
参考
- https://cloud.google.com/functions/docs/reference/iam/permissions
- https://cloud.google.com/functions/docs/reference/iam/roles
- https://cloud.google.com/deployment-manager/docs/configuration/set-access-control-resources#granting_permis
- https://stackoverflow.com/questions/62576013/how-to-set-iam-policy-to-cloud-function-with-deployment-manager
- https://github.com/GoogleCloudPlatform/deploymentmanager-samples/issues/494