AWS Lambda から Vertex AI（Gemini）をセキュアに呼び出す！


# 概要
Google Cloud の Vertex AI（Gemini）を、AWS Lambda から呼び出します！
Workload Identity Federation でセキュアな認証を実装します。

# なぜ AWS Lambda × Vertex AI？
Amazon Bedrock エージェント に Gemini を組み込みたいからです！

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/agents-lambda.html

Gemini API を直接利用する方法ありますが、セキュリティ的にAPIキーを管理したくないので Workload Identity Federation で認証します。

https://cloud.google.com/endpoints/docs/openapi/when-why-api-key?hl=ja#security_of_api_keys

せっかくなので技術検証も兼ねて Vertex AI をさわってみよう思った次第です！

# AWS から Vertex AI アクセス許可フロー
アクセス許可フローは下記の通りです。
 - AWS STS を利用し Google Cloud のサービスアカウントにアクセス許可する
 - Google Cloud 側で、Workload Identity Pool とその Provider を設定し、AWS IAM ロールが認証を通じ Google Cloud  サービスアカウント にアクセス許可する
 - Google Cloud サービスアカウントに、Vertex AI アクセス権限付与する
 - 結果 AWS から Vertex AI へアクセス可能になる！！！

# Workload Identity Federation
Google Cloud で下記設定を実施し、特定のAWSアカウントからのみアクセスを許可します。
 - Service Account (人ではなくプログラム等が Google Cloud のリソースにアクセスするためのアカウント)
 - Workload Identity
 - IAM

```hcl:main.tf
data "google_project" "current" {}
data "aws_caller_identity" "self" {}

locals {
  project_id     = data.google_project.current.project_id
  project_number = "************"
  services = toset([
    "iam.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "iamcredentials.googleapis.com",
    "sts.googleapis.com",
    "aiplatform.googleapis.com"
  ])
}

#######################################
# Service Account
#######################################
# Google Cloud で利用する API の 有効化
resource "google_project_service" "main" {
  for_each                   = local.services
  project                    = local.project_id
  service                    = each.value
  disable_dependent_services = true
}

resource "google_service_account" "main" {
  project      = local.project_id
  account_id   = local.project_id
  display_name = local.project_id
}

#######################################
# Workload Identity
#######################################
resource "google_iam_workload_identity_pool" "main" {
  project                   = local.project_id
  workload_identity_pool_id = "aws"
  display_name              = "aws"
}

resource "google_iam_workload_identity_pool_provider" "main" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.main.workload_identity_pool_id
  workload_identity_pool_provider_id = "aws"
  display_name                       = "aws"
  
  # AWS Lambda をデプロイするアカウントのみ許可
  aws {
    account_id = data.aws_caller_identity.self.id
  }
}

#######################################
# IAM
#######################################
# サービスアカウントに Vertex AI アクセス権限付与
resource "google_project_iam_member" "main" {
  project = local.project_id
  role    = "roles/aiplatform.user"
  member  = "serviceAccount:${google_service_account.main.email}"
}

# AWS がサービスアカウントの権限借用許可
resource "google_service_account_iam_binding" "main" {
  service_account_id = google_service_account.main.name
  role               = "roles/iam.workloadIdentityUser"
  members = [
    "principalSet://iam.googleapis.com/projects/${local.project_number}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.main.workload_identity_pool_id}/*",
  ]
}

```

# Google Cloud 認証情報設定
## Google Cloud 認証情報取得
- コンソールから IAMと管理 → Workload Identity 連携を選択
- 利用するプールを選択
- 接続済みサービス アカウントから構成をダウンロード
![スクリーンショット 2025-05-02 20.18.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567322/e146c287-dc2e-47e4-b4eb-c42f98637272.png)

## AWS Systems Manager Parameter Store へ Google Cloud 認証情報保存
Google Cloud 認証情報ファイルの内容を、AWS Systems Manager Parameter Storeへ登録します。
パラメータ名は GOOGLE_APPLICATION_CREDENTIALS で登録します。


# AWS Lambda
Vertex AI SDK for Pythonを利用する為、コンテナイメージで Lambda をデプロイします。
※ Lambda レイヤーでは容量制限に引っかかる為

メモリーサイズは 最小 128 MB の場合、メモリ不足でエラーになる場合があります。

```hcl:main.tf
#######################################
# Lambda Function
#######################################
resource "aws_lambda_function" "main" {
  function_name = "gemini"
  package_type  = "Image"
  image_uri     = "${aws_ecr_repository.main.repository_url}:latest"
  memory_size   = 512
  timeout       = 900
  role          = aws_iam_role.main.arn
  environment {
    variables = {
      GOOGLE_APPLICATION_CREDENTIALS_PATH = "GOOGLE_APPLICATION_CREDENTIALS" # SSM Parameter Storeのパラメータ名
      GCP_PROJECT_ID                      = data.google_project.current.project_id
    }
  }
}

#######################################
# ECR
#######################################
resource "aws_ecr_repository" "main" {
  name = "gemini"
}

#######################################
# IAM
#######################################
resource "aws_iam_role" "main" {
  name               = "LambdaRoleForGemini"
  assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
}

resource "aws_iam_role_policy_attachment" "main" {
  role       = aws_iam_role.main.name
  policy_arn = aws_iam_policy.main.arn
}

resource "aws_iam_policy" "main" {
  name   = "LambdaRoleForGemini"
  policy = data.aws_iam_policy_document.lambda_role.json
}
```

```hcl:data.tf
data "aws_caller_identity" "self" {}
data "aws_region" "current" {}
data "google_project" "current" {}

#######################################
# IAM
#######################################
data "aws_iam_policy_document" "lambda_assume_role" {
  version = "2012-10-17"
  statement {
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["lambda.amazonaws.com"]
    }
    actions = ["sts:AssumeRole"]
  }
}

data "aws_iam_policy_document" "lambda_role" {
  version = "2012-10-17"
  statement {
    effect = "Allow"
    actions = [
      "ssm:GetParameter",
      "ssm:GetParameters",
    ]
    resources = [
      "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.self.id}:parameter/*",
    ]
  }
  statement {
    effect = "Allow"
    actions = [
      "kms:Decrypt",
      "kms:Encrypt",
      "kms:GenerateDataKey",
    ]
    resources = [
      "arn:aws:kms:${data.aws_region.current.name}:${data.aws_caller_identity.self.id}:key/*"
    ]
  }
  statement {
    effect = "Allow"
    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]
    resources = ["arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.self.id}:log-group:/aws/lambda/${aws_lambda_function.main.function_name}:*"]
  }
}
```

## AWS Lambda コンテナ設定
Lambda の処理で利用するライブラリを定義します。
```toml:requirements.txt
boto3==1.37.17
google-cloud-aiplatform==1.90.0
google-auth==2.39.0
```

Dockerfile を記述しビルドします。
```dockerfile:Dockerfile
FROM public.ecr.aws/lambda/python:3.13

WORKDIR /var/task

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Lambdaの処理が存在するディレクトリ
COPY src .

CMD ["lambda_function.lambda_handler"]
```

ビルドが完了したら、ECRへプッシュします。

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/docker-push-ecr-image.html

## AWS Lambda の 処理
コンテナ内部に環境変数 GOOGLE_APPLICATION_CREDENTIALS を設定し、Google Cloud 認証情報ファイルを埋め込む必要があります。

https://cloud.google.com/docs/authentication/application-default-credentials?hl=ja

```python:lambda_function.py

import os

import boto3
import vertexai
from vertexai.generative_models import GenerationConfig, GenerativeModel

def lambda_handler(event, _):
    try:
        credentials = boto3.client("ssm").get_parameter(
            Name=os.environ.get("GOOGLE_APPLICATION_CREDENTIALS_PATH"),
            WithDecryption=True
        )

        # コンテナ内部に google cloud 認証情報を json で保持する
        credentials_path = "/tmp/gcloud-credential.json"
        with open(credentials_path, "w") as f:
            f.write(credentials)

        # json のパスを環境変数に設定する
        os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = credentials_path

        # GOOGLE_APPLICATION_CREDENTIALS を読み取り認証を実施
        gcp_project_id = os.environ.get("GCP_PROJECT_ID")
        vertexai.init(project=gcp_project_id, location="us-central1")
        model = GenerativeModel("gemini-2.5-flash-preview-04-17")

        # モデルに対しての詳細な設定を調整
        generation_config = GenerationConfig(
            temperature=0.9,
            top_k=40,
            top_p=0.9,
            candidate_count=1,
            max_output_tokens=8192,
        )

        text = "自己紹介してください"
        response = model.generate_content(
            text,
            generation_config=generation_config,
        )
        return {"status_code": 200, "message": "処理成功", "text": response.text}
    except Exception as e:
        return {"status_code": 500, "message": "処理失敗", "error": str(e)}

```

AWS Lambda のコンソールから実行し、Gemini から回答があれば成功です！！
![スクリーンショット 2025-05-03 16.05.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/567322/22f32cdb-a4c8-4f12-b16c-69d098ed1f77.png)


# まとめ
AWS と Google Cloud の認証関連や、本記事には記述していないですがコンテナイメージ Lambda の CD 関連の知見が増えてよかったです。

次は AWS Lambda から Azure OpenAI Service 経由で GPT を呼び出したい！！！