Lambdaから別アカウントのLambdaを呼び出す

AmazonLambda

こんにちは、虎塚です。

システムのログ収集、監視、アラート通知、アクション実行などにAWS Lambdaを使っていると、LambdaとLambdaをチェーンして使いたいことがあります。さらに、複数のアカウントで発生したイベントを1つのアカウントに集めるようなときには、Lambdaから別のAWSアカウントのLambdaを呼び出したいこともあります。

クロスアカウントでLambdaを呼び出す構成の例

この記事では、AWS Lambdaから別アカウントのAWS Lambdaを呼びだす方法を説明します。

概要

本記事で設定手順を説明するAWS構成を次の図に示します。

本記事で説明するAWS構成

アカウント000000000000のAWS Lambda (caller) が、アカウント111111111111のAWS Lambda (callee) を呼び出します。

動作確認のために、1つ目のAWS Lambdaの前にKinesis Streamを配置しました。また、2つ目のAWS Lambdaは、Lambda関数内の処理で、実行中のEC2インスタンスを停止します。

それぞれのLambdaに必要な権限を次の図に示します。

クロスアカウントでLambdaを呼び出す際に必要な権限

次の2点がポイントです。

  • 呼び出される側のLambdaのリソースポリシーで、アカウント000000000000による操作を許可します。

クロスアカウント呼び出しの許可をリソースポリシーで与えるので、呼び出される側のLambdaの実行ロールで、アカウント000000000000を信頼ポリシーに含める必要はありません。

以降にチュートリアルを用意しましたので、ピンとこない場合は、実際にお試しいただければと思います。

設定手順

Lambdaから別アカウントのLambdaを呼び出す構成を作成します。

  • 2つのAWSアカウント000000000000と、111111111111を利用できるものとします。
  • Kinesis Stream, EC2, AWS LambdaなどのAWSリソースは、すべて東京リージョンに作成するものとします。

基本的にAWS CLIで作業します。コマンド実行時に、対象のアカウントを間違えないように注意してください。手順では、呼び出し側アカウントに対するコマンドに--profile caller、呼び出される側アカウントに対するコマンドに--profile calleeをつけます。

1. Kinesis Streamの作成

アカウント0000000000でKinesis Streamを作成します。

次のコマンドで、シャードを1個だけ持つKinesis Stream (my-stream) を作成します。

aws kinesis create-stream --profile caller\
  --stream-name my-stream \
  --shard-count 1

作成されたことを確認します。

aws kinesis describe-stream --profile caller\
  --stream-name my-stream

ストリームのARNは、ステップ2.4で使うので控えておきます。

2. 呼び出し側のLambdaの作成

アカウント0000000000でAWS Lambda (caller) を作成します。

2.1. デプロイパッケージの作成

ローカルで、次のPythonスクリプトをテキストファイルに保存します。

from __future__ import print_function

import base64
import boto3
import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def create_function_arn(data):
    region = data['region']
    account = data['accountId']
    function_name = data['functionName']
    return 'arn:aws:lambda:' + region + ':' + account + ':function:' + function_name

def lambda_handler(event, context):
    logger.info(event)
    for record in event['Records']:
        data = json.loads(base64.b64decode(record['kinesis']['data']))

        function_arn = create_function_arn(data)

        params = {
            'instanceId': data['parameters']['instanceId']
        }
        response = boto3.client('lambda').invoke(
            ClientContext = 'string',
            FunctionName = function_arn,
            InvocationType = 'Event',
            LogType = 'Tail',
            Payload = json.dumps(params)
        )
        logger.info(response)

このスクリプトでは、Kinesisからデータを受け取って、呼び出される側のLambda (callee) を特定し、パラメータとしてインスタンスIDを渡して呼び出す処理をします。

デプロイパッケージを作成する環境として、EC2インスタンスを起動します。このインスタンスは、アカウント000000000000とアカウント111111111111のどちらで起動しても構いません。今回は、次のAMIを利用しました。

  • Amazon Linux AMI 2016.09.1 (HVM), SSD Volume Type - ami-56d4ad31

ec2-userのホームディレクトリに、Pythonスクリプトをローカルからコピーします。

scp -i ~/.ssh/key.pem ./caller.py ec2-user@XXX.XXX.XXX.XXX:

EC2インスタンスにSSHログインします。

このAMIに初期インストールされているPythonは2.7.12で、pipが含まれているので、pipのインストールを省略できます。

必要なパッケージをインストールします。

sudo yum install python27-devel python27-pip gcc

パッケージ作成の作業環境を整えます。

virtualenv ~/caller
source ~/caller/bin/activate

boto3はAWS上の実行環境で利用できるので、ローカルデバッグで利用したいときだけインストールします。

pip install boto3

デプロイパッケージ (caller.zip) に関連ファイルを含めます。関連ファイルがない場合は、スキップします (コマンドを実行してしまっても問題ありません)。

cd $VIRTUAL_ENV/lib/python2.7/site-packages
zip -r9 ~/caller.zip *

cd $VIRTUAL_ENV/lib64/python2.7/site-packages
zip -r9 ~/caller.zip *

デプロイパッケージにスクリプトを含めます。

cd ~
zip -g caller.zip caller.py

EC2インスタンスからログアウトして、作成したデプロイパッケージをローカルにダウンロードしておきます。

scp -i ~/.ssh/key.pem ec2-user@XXX.XXX.XXX.XXX:caller.zip .

このEC2インスタンスは、ステップ3.1でもう一度使うので、起動したままにしておきます。

2.2. Lambdaの実行ロールの作成

まず、IAMロールの信頼ポリシーを、ローカルにファイルで作成します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

次に、上の信頼ポリシーを指定して、IAMロールを作成します。

aws iam create-role --profile caller \
  --role-name caller-role \
  --assume-role-policy-document file://TrustPolicyForLambda.json

戻り値に含まれるロールのARNは、ステップ2.3で使うので控えておきます。

最後に、作成したIAMロールにアクセスポリシーを追加します。

# Lambdaの実行ログの出力を許可します。一般的なLambdaの実行ロールの権限です。
aws iam attach-role-policy --profile caller \
  --role-name caller-role \
  --policy-arn arn:aws:iam::aws:policy/AWSLambdaExecute

# Kinesis Streamからデータの取得を許可します。
aws iam attach-role-policy --profile caller \
  --role-name caller-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole

# Lambdaの呼び出しを許可します。calleeの呼び出しに必要です。
aws iam attach-role-policy --profile caller \
  --role-name caller-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaRole

2.3. デプロイパッケージのアップロード

ステップ2.1で作成したデプロイパッケージのzipファイルを指定して、Lambda関数を作成します。

aws lambda create-function --profile caller \
  --function-name caller \
  --runtime python2.7 \
  --role arn:aws:iam::000000000000:role/caller-role \
  --handler caller.lambda_handler \
  --zip-file fileb://caller.zip

2.4. イベントソースにKinesis Streamを設定

ステップ1で作成したKinesis Stream (my-stream) を、Lambdaのイベントソースに設定します。これによって、Kinesis Streamにデータが入ると、Lambda (caller) が起動するようになります。

aws lambda create-event-source-mapping --profile caller \
  --event-source-arn arn:aws:kinesis:ap-northeast-1:000000000000:stream/my-stream \
  --function-name caller \
  --starting-position LATEST

3. 呼び出される側のLambdaの作成

アカウント111111111111でAWS Lambda (callee) を作成します。

3.1. デプロイパッケージの作成

ローカルで次のPythonスクリプトをテキストファイルに保存します。

from __future__ import print_function

import boto3
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    logger.info(event)

    instance = event['instanceId']
    response = boto3.client('ec2').stop_instances(
        InstanceIds=[
            instance,
        ]
    )

ステップ2.1で使ったインスタンスに上のスクリプトをコピーして、前回と同じ手順でデプロイパッケージを作成します。

scp -i ~/.ssh/key.pem ./callee.py ec2-user@XXX.XXX.XXX.XXX:

EC2インスタンスにSSHログインして、パッケージ作成の作業環境を整えます。

virtualenv ~/callee
source ~/callee/bin/activate

デプロイパッケージ (callee.zip) に関連ファイルを含めます。

cd $VIRTUAL_ENV/lib/python2.7/site-packages
zip -r9 ~/callee.zip *

cd $VIRTUAL_ENV/lib64/python2.7/site-packages
zip -r9 ~/callee.zip *

デプロイパッケージにスクリプトを含めます。

cd ~
zip -g callee.zip callee.py

EC2インスタンスからログアウトして、作成したデプロイパッケージをローカルにダウンロードしておきます。

scp -i ~/.ssh/key.pem ec2-user@XXX.XXX.XXX.XXX:callee.zip .

このEC2インスタンスは、停止して削除してしまって構いません。

3.2. Lambdaの実行ロールの作成

アカウント111111111111でIAMロールを作成します。

まず、IAMロールの信頼ポリシーを、ローカルにファイルで作成します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

ステップ2.2で作成した信頼ポリシーと同じように、Lambdaサービスロールとして必要な設定を定義します。

次に、上の信頼ポリシーを指定して、IAMロールを作成します。

aws iam create-role --profile callee \
  --role-name callee-role \
  --assume-role-policy-document file://TrustPolicyForLambdaAndCallerAccount.json

戻り値に含まれるロールのARNは、ステップ3.3で使うので控えておきます。

最後に、作成したIAMロールにアクセスポリシーを追加します。

# Lambdaの実行ログの出力を許可します。一般的なLambdaの実行ロールの権限です。
aws iam attach-role-policy --profile callee \
  --role-name callee-role \
  --policy-arn arn:aws:iam::aws:policy/AWSLambdaExecute

# EC2の操作を許可します。今回はEC2を停止させるために追加します。
aws iam attach-role-policy --profile callee \
  --role-name callee-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess

3.3. デプロイパッケージのアップロード

前のステップで作成したデプロイパッケージのzipファイルを指定して、Lambda関数を作成します。

aws lambda create-function --profile callee \
  --function-name callee \
  --runtime python2.7 \
  --role arn:aws:iam::111111111111:role/callee-role \
  --handler callee.lambda_handler \
  --zip-file fileb://callee.zip

3.4. 権限の追加

アカウント000000000000からこのLambda関数を呼び出せるように、Lambdaのリソースポリシーに権限を追加します。

aws lambda add-permission --profile callee \
  --function-name callee \
  --statement-id AllowActionFromCaller \
  --principal 000000000000 \
  --action lambda:InvokeFunction

これで設定が完了しました。

動作確認

Lambda (caller) から、クロスアカウントでLambda (callee) を呼び出せることを確認します。

1. 対象インスタンスの起動確認

Lambda (callee) が停止させるEC2インスタンス (i-12345678901234567) を、アカウント111111111111の東京リージョンに起動します。i-12345678901234567が起動していることを確認します。

aws ec2 describe-instances --profile callee \
  --instance-id i-12345678901234567 \
  | jq '.Reservations[].Instances[].State.Name'

"running"

2. Kinesisへデータ投入

Kinesis Stream (my-stream) に投入するデータを、ローカルにファイルで作成します。

{
    "accountId" : "111111111111",
    "region" : "ap-northeast-1",
    "functionName" : "callee",
    "parameters" : {
        "instanceId" : "i-12345678901234567"
    }
}

上のデータには、呼び出される側のLambdaを特定するための情報 (アカウントID、Lambdaが配置されたリージョン、Lambda関数名) と、処理対象のインスタンスIDが含まれています。

次のコマンドで、Kinesis Streamにデータを投入します。

aws kinesis put-record --profile caller \
  --stream-name my-stream \
  --data file://sample_event.json  \
  --partition-key test

3. Lambdaのログ確認

AWS Management Consoleにログインして、CloudWatch LogsでLambdaのログを確認します。

  • アカウント000000000000の/aws/lambda/callerロググループ
  • アカウント111111111111の/aws/lambda/calleeロググループ

エラーが出力されていないことを確認します。

4. インスタンスの確認

EC2インスタンスが停止されたことを確認します。

aws ec2 describe-instances --profile callee \
  --instance-id i-12345678901234567 \
  | jq '.Reservations[].Instances[].State.Name'

"stopped"

おわりに

LambdaからLambdaをクロスアカウントで呼び出すための設定方法を説明しました。アカウントをまたいで複数のLambdaを繋ぎたいときに、この記事がお役に立てば幸いです。

なお、実際にこういった構成で環境を作る場合は、Lambdaに与える権限を適切に絞りましょう。(たとえば、今回の設定例でいうと、calleeの実行ロールに与える権限は、EC2のフルアクセスよりも停止だけのほうが望ましいです)

それでは、また。