AWS LambdaからCloud Storage(GCS)にファイルをアップロードする

記事タイトルとURLをコピーする

G-gen の杉村です。当記事では、AWS Lambda のサーバーレス関数から Google Cloud (旧称 GCP) の Cloud Storage (GCS) にファイルをアップロードする方法をご紹介します。認証・認可には Workload Identity を利用します。

はじめに

検証の概要

当記事では、AWS Lambda 関数から Google Cloud の Cloud Storage (GCS) バケットにファイルをアップロードする方法をご紹介します。

AWS Lambda 関数では Python や Node.js のような各種言語でプログラムを動作させることができます。そのため、Google Cloud のクライアントライブラリ (Cloud SDK) をパッケージすれば、Google Cloud の API を実行して Cloud Storage 等にデータを投入することも容易です。

しかしながら、クロスクラウドでデータを扱うエンジニアは、それぞれのクラウドのクライアントライブラリ (SDK) の扱い方の違いに戸惑うことも多いでしょう。当記事で掲載するサンプル構成とサンプルコードが、エンジニアリングの一助になれば幸いです。

留意事項

当記事では簡易化のため、IAM 権限の割り当てに関する考慮を最小限にしています。実際には最小権限の原則に基づき、適切な権限設定をしてください。

またサンプルコードでは例外処理やロギングに関する考慮を省略しています。実際には適切な考慮をお願いいたします。

構成

検証環境

上記の構成で検証を行います。作成リソースは以下です。

  • Amazon Web Services (AWS)
    • Amazon S3 バケット
    • AWS Lambda 関数 (Python)
    • IAM Role
  • Google Cloud
    • Cloud Storage バケット
    • Workload Identity Pool
    • サービスアカウント

AWS から Google Cloud への認証

クロスクラウドでは認証・認可もポイントになります。

Google Cloud API を実行するには Google アカウントまたはサービスアカウントの認証情報が必要です。そのため、まず思いつくのは「Google Cloud でサービスアカウントを作成し、サービスアカウントキーを発行。これを AWS Secrets Manager に保存する。キーを Lambda プログラムから参照して、Google Cloud への認証に用いる」といった方法でしょう。

しかしこれには、キーローテーションの手間やキー漏洩の危険性がつきまといます。

今回の検証では Workload Identity を使い、サービスアカウントキーのダウンロードなしに AWS から Google Cloud への認証を実現します。

Workload Identity の概要

Workload Identity では信頼する対象の AWS アカウントと IAM Role 名等を指定することで、AWS の IAM プリンシパルが Google Cloud サービスアカウントの権限を借用することを許可します。

Google Cloud 側では借用させるサービスアカウントに必要な IAM 権限を付与しておき、そのサービスアカウントから権限借用する形で AWS の IAM Role 等が Google Cloud API を実行できます。

Workload Identity の概要

なお「権限の借用」とは、実際には OAuth 2.0 仕様に従ったトークン交換を指します。一時的なトークンが Google Cloud から AWS へ払い出され、これを用いて AWS 環境から Google Cloud API が実行可能になるのです。

構築の流れ

以下の流れで上記環境を構築します。

  • [AWS] IAM Role を作成・権限付与
  • [Google Cloud] Cloud Storage バケットを作成
  • [Google Cloud] サービスアカウントを作成・権限付与
  • [Google Cloud] Workload Identity Pool を作成
  • [Google Cloud] Workload Identity Pool にサービスアカウントを接続
  • [ローカル] デプロイパッケージを作成
  • [AWS] Lambda 関数をデプロイ
  • [AWS] S3 バケット作成
  • [AWS] 動作テスト

以降では、見出しの [G] は Google Cloud 側での作業を、[A] は AWS 側での作業を、[L] はローカル環境での作業を意味します。

[A] IAM Role 作成・権限付与

まずは、Lambda 関数にアタッチするための IAM Role を作成します。Google Cloud 側の設定で、信頼する対象の IAM Role として名称が必要なため、先に作成します。

AWS マネジメントコンソールの IAM の画面に遷移し、左部メニューから ロール を選択します。

ボタン「ロールを作成」を押下します。

「信頼されたエンティティタイプ」として「AWS のサービス」を、ユースケースとして「Lambda」を選択し、ボタン「次へ」を押下します。

ロールの作成

次のステップ 許可を追加 では、付与する IAM Policy として以下を追加します。

  • AWSLambdaBasicExecutionRole
  • AmazonS3ReadOnlyAccess

後者の AmazonS3ReadOnlyAccess については、AWS アカウント内の全てのバケットの全オブジェクトに読取アクセスができてしまう強力な権限です。実際のプロジェクトでは、対象バケットを絞った独自の IAM Policy を作成することを推奨します。

次のステップではロール名を s3-to-gcs-role とし (今回の検証ではこの名称としますが、名称は任意です)、最下部のボタン「ロールを作成」を押下します。

[G] Cloud Storage バケット作成

Google Cloud コンソールで Cloud Storage 画面へ遷移し、Cloud Storage バケットを作成します。(Cloud Storage > バケット > ボタン「作成」)

今回のバケット名は my-target-bucket-01 とします。

CREATE ボタンから作成

[G] サービスアカウント作成

次に IAM と管理 画面の左部メニューから サービス アカウント を選択して画面遷移し、サービスアカウントを作成します。(IAM と管理 > サービス アカウント > ボタン「サービス アカウントを作成」)

今回のサービスアカウント名は s3-to-gcs とします。割り当てられるメールアドレスは s3-to-gcs@(your-pj-name).iam.gserviceaccount.com となりました。

なおウィザードの中で以下のように「このサービス アカウントにプロジェクトへのアクセスを許可する」というプロセスがありますが、ここでは何の権限もつけなくても構いません (次の見出しで権限付与します)。

サービスアカウントの作成画面

[G] サービスアカウントへの権限付与

再び Cloud Storage 画面へ遷移し、先程作成したサービスアカウントに、目当ての Cloud Storage バケットへのオブジェクト管理権限を付与します。

バケット一覧画面から先程のバケット名を選択したあと、権限 タブへ移動します。画面中程にあるボタン「アクセス権を付与」をクリックします。

バケットへのアクセス権を付与

新しいプリンシパル に先ほど作成したサービスアカウントのメールアドレスを、ロールとして Storage オブジェクト管理者 を割り当てます。

IAM ロールの割り当て

なお必要な処理がアップロードだけであれば Storage オブジェクト作成者 でも構いません。この権限の場合、オブジェクトの読み取り (ダウンロード) や削除、上書きはできません (上書きは、一度削除してから再度書き込みという処理と同等のため)。

[G] Workload Identity Pool 作成

IAM と管理 画面の左部メニューから Workload Identity 連携 を選択します。

ボタン「プールを作成」を押下し、新しい Workload Identity プール (Workload Identity 設定の管理単位) を作成します。

作成画面を遷移すると以下の順で入力することになります。今回は以下のような値を入力しました。各名称は任意ですので、実際の環境では適切な名称を付けてください。

設定名
名前 my-aws-environment
プール ID my-aws-environment
プロバイダの選択 AWS
プロバイダ名 my-aws-account
プロバイダ ID my-aws-account
AWS アカウント ID (自分の AWS アカウント ID を入力)

なお最後に「プロバイダ属性を構成する」がありますが、ここは何も手を加えずにボタン「保存」を押下します。

プロバイダ属性は今回は編集しない

ここで詳細な属性マッピングを構成することもできますが、デフォルトのままで通常の用途には足ります。詳細が知りたい場合、以下のドキュメントをご参照ください。

[G] Workload Identity Pool にサービスアカウントを接続

Workload Identity プール作成が完了すると、プールの詳細画面に遷移するはずです。

上部のボタン「アクセスを許可」を押下し、このプールとリンクさせるサービスアカウントを選択します。

先程作成したサービスアカウント s3-to-gcs を選択します。

プリンシパル(サービス アカウントにアクセスできる ID)の選択 では、デフォルトでは プール内のすべての ID が選択されています。この状態だと、先程指定した AWS アカウント ID 内の全てのプリンシパル (IAM User や IAM Role 等) がサービスアカウント権限を借用できるようになります。

フィルタに一致する ID のみ を選択し、属性名として aws_role を選択、属性値に arn:aws:sts::(AWS Account ID):assumed-role/(IAM Role 名) を入力することで、特定の IAM Role だけを許可することもできます。

サービスアカウントを接続

ボタン「保存」を押下すると、構成ファイルのダウンロードができます。このファイルは単なる設定ファイルであり、機密データは含まれていません。このファイルをデプロイパッケージに入れることになります。プロバイダとして先程の my-aws-account を選択して、ボタン「構成をダウンロード」を押下し、ローカル環境にダウンロードしてください。

[L] デプロイパッケージ作成

サンプルコード

Lambda 関数のソースコードは以下を使います。繰り返しになりますが、以下はあくまでサンプルコードであり、実際には例外処理やロギング、セキュアなコーディングに関する考慮をしたうえでご利用ください。

import boto3
  
from google.cloud import storage
  
  
def get_from_s3(s3_bucket_name, s3_object_name):
  
    # クライアント生成等
    s3 = boto3.client('s3')
    s3_object_path=f"my_s3_path/{s3_object_name}"
    tmp_file_path=f"/tmp/{s3_object_name}"
  
    # ファイルを Lambda の一時領域にダウンロード
    s3.download_file(s3_bucket_name, s3_object_path, tmp_file_path)
  
    print(f"{s3_bucket_name}/{s3_object_path} was downloaded to {tmp_file_path}.")
  
    return tmp_file_path
  
  
def upload_to_gcs(tmp_file_path, gcs_bucket_name):
  
    # クライアント生成等
    gcs = storage.Client()
    file_name=tmp_file_path.split('/')[-1]
    gcs_object_path=f"my_gcs_path/{file_name}"
  
    bucket = gcs.bucket(gcs_bucket_name)
    blob = bucket.blob(gcs_object_path)
  
    # オブジェクトを Cloud Storage バケットにアップロード
    blob.upload_from_filename(tmp_file_path)
  
    print(f"{tmp_file_path} was uploaded to {gcs_bucket_name}/{gcs_object_path}.")
  
    return None
  
  
def lambda_handler(event, context):
    
    # event から各種情報を取得
    s3_bucket_name=event['s3_bucket_name']
    s3_object_name=event['s3_object_name']
    gcs_bucket_name=event['gcs_bucket_name']
  
    # オブジェクトを S3 バケットから取得
    tmp_file_path=get_from_s3(
        s3_bucket_name=s3_bucket_name,
        s3_object_name=s3_object_name
    )
  
    # オブジェクトを Cloud Storage バケットにアップロード
    upload_to_gcs(
        tmp_file_path=tmp_file_path,
        gcs_bucket_name=gcs_bucket_name
    )
    
    return {'statusCode': 200}

この Lambda 関数のソースコードは、以下のような処理を行います。

  • event として s3_bucket_name s3_object_path gcs_bucket_name を受け取る
    • s3_bucket_name : ファイル取得元の S3 バケット
    • s3_object_name : 取得対象の S3 オブジェクト名
    • gcs_bucket_name : アップロード先の Cloud Storage バケット名
  • Amazon S3 バケットの my_s3_path/ パスからオブジェクトを Lambda 内の /tmp にダウンロード
  • /tmp にダウンロードしたオブジェクトを Cloud Storage の my_gcs_path/ パスにアップロード

クライアントライブラリの使いかたは、以下の公式ドキュメントをご参照ください。

パッケージング

ローカル環境 (Linux ライクを想定) で、新規ディレクトリを作成して同ディレクトリに移動したあと、上記ソースコードを lambda_function.py として配置します。

また先程ダウンロードした Workload Identity Pool の構成ファイルも同じディレクトリに配置します。

Lambda では Google Cloud クライアントライブラリのような外部ライブラリはデプロイパッケージに含ませる必要があるため、以下のコマンドでディレクトリ内に展開します。

pip install google-cloud-storage -t .

pip コマンドの -t オプションは、指定ディレクトリに Python パッケージをダウンロードできるオプションです。 -t . によりカレントディレクトリに、依存関係を含む各種モジュールが展開されます。

次に以下のコマンドで1段階上位のディレクトリに zip ファイルを作成します。

zip -r ../function.zip .

[A] Lambda 関数デプロイ

以下のコマンドで作成した zip パッケージを Lambda 関数としてデプロイします。aws lambda create-function コマンドを用います。

Lambda 関数には環境変数を持たせることができます。Workload Identity 構成ファイル名と、Google Cloud プロジェクト ID を環境変数として持たせます。以下コマンドの最初の3行はご自身の環境の値に置き換えてください。

AWS_ACCOUNT_ID="123456789012"
CONFIG_FILE_NAME="clientLibraryConfig-my-aws-account.json"
GOOGLE_CLOUD_PROJECT_ID="my-project-id"
  
aws lambda create-function  \
    --function-name s3-to-gcs \
    --zip-file fileb://../function.zip \
    --handler lambda_function.lambda_handler \
    --role arn:aws:iam::${AWS_ACCOUNT_ID}:role/s3-to-gcs-role \
    --runtime python3.10 \
    --region ap-northeast-1 \
    --environment "Variables={GOOGLE_APPLICATION_CREDENTIALS=${CONFIG_FILE_NAME},GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT_ID}}" \
    --timeout 120

一度デプロイしたあとの関数は、以下のコマンドで修正できます。もちろん、コンソール画面から修正することもできます。

[A] S3 バケット作成・動作テスト

Lambda の Web コンソール画面でデプロイした関数を選択し、テスト タブから今回の関数の動作確認を行うことができます。

事前に適当な S3 バケットを作成し、my_s3_path フォルダ配下に test.txt を配置します。

その後、以下の JSON を イベント JSON に入力し、右上のボタン「テスト」を押下して動作テストを実行してください。JSON の値は自環境のものに置き換えます。

{
  "s3_bucket_name": "my-test-bucket",
  "s3_object_name": "test.txt",
  "gcs_bucket_name": "my-target-bucket-01"
}

イベント JSON を指定してテストを実行

画面上に標準出力や標準エラー出力が表示されます。実際に Cloud Storage バケットの中身も確認しましょう。

杉村 勇馬 (記事一覧)

執行役員 CTO / クラウドソリューション部 部長

元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。