コンテナ Lambda を AWS SAM でデプロイしよう !

~コンテナ利用者に捧げる AWS Lambda の新しい開発方式 ! ~ 第 4 回~

2021-07-05
デベロッパーのためのクラウド活用方法

Author : 下川 賢介

こんにちは、サーバーレス スペシャリストソリューションアーキテクトの下川 (@_kensh) です。

第 1 回 コンテナ Lambda  の”いろは”、AWS CLI でのデプロイに挑戦 !」では、AWS CLI を使ってコンテナ Lambda 関数を実際に AWS Lambda サービスにデプロイして動作確認をしてみました。
第 2 回 コンテナ Lambda を開発、まずは RIC と RIE を使ってみよう !」では、開発者のローカル環境でコンテナ Lambda 関数の動作確認をする方法を紹介しました。
第 3 回コンテナ Lambda をカスタマイズして、自分好みの PHP イメージを作ろう !」では、コンテナイメージサポート Lambda 関数のカスタムイメージ作成方法について紹介しました。

今回はこのコンテナイメージサポート Lambda 関数を AWS SAM で管理できるように、サーバーレスの Infrastructure As Code 環境を一緒に試していきたいと思います。

ご注意

本記事で紹介する AWS サービスを起動する際には、料金がかかります。builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。

builders.flash メールメンバーへの登録・特典の入手はこちら »

*ハンズオン記事およびソースコードにおける免責事項 »


サーバーレス サービスでも Infrastructure As Code は必要 ?

第 1 回から第 3 回まで、環境構築や Lambda 関数のビルド、そしてデプロイまで bash を使った実行を行なってきました。bash が手慣れた方はこれでも作業に困ることはないかもしれませんが、複数人でチームとして開発するときや、環境構成自体を再現性のある手段で記述したい場合など、bash で管理していくには可読性や bash script のデバッグなど別の課題を伴うことがあります。

前回の投稿では Dockerfile によるコンテナの定義を書いてみましたが、そこではコンテナ内の基底のOSバージョンや PHP バージョン、インストールモジュールについて、Dockerfile というテキストファイルに記述していました。こちらもコンテナ内の構成をコードで管理するという意味で Infrastructure As Code に従っています。

ビルドおよびデプロイにおいて、bash で実行していた部分は、以下の通りです。

# リージョン
$ REGION=$(aws configure get region)

# IAM Roleの作成
$ ACCOUNTID=$(aws sts get-caller-identity --output text --query Account)
$ aws iam create-role --role-name lambda-ex \
  --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'
$ ROLE_ARN=arn:aws:iam::${ACCOUNTID}:role/lambda-ex

# リポジトリ作成
$ aws ecr create-repository --repository-name phplambda

# イメージビルド
$ docker build -t phplambda:latest .

# イメージプッシュ
$ docker tag phplambda:latest \
  ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/phplambda:latest
$ aws ecr get-login-password | docker login --username AWS \
  --password-stdin ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com
$ docker push ${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/phplambda:latest

# デプロイ
$ DIGEST=$(aws ecr list-images --repository-name phplambda --out text --query 'imageIds[?imageTag==`latest`].imageDigest')
$ aws lambda create-function \
     --function-name phplambda  \
     --package-type Image \
     --code ImageUri=${ACCOUNTID}.dkr.ecr.${REGION}.amazonaws.com/phplambda@${DIGEST} \
     --role ${ROLE_ARN}

これを見ても、かなり長い bash の実行が続きますね。作業ミスを誘発したり、チーム間のオペレーションの共有が難しそうですね。

このビルド、デプロイの流れもできるだけ Infrastructure As Code に従う方が良さそうです。


AWS SAM のご紹介

AWS SAM (Serverless Application Model) は、YAML 形式の構文で記述できる Infrastructure as Code のフレームワークです。SAM は AWS CloudFormation の拡張構文で、CloudFormation の構文もそのまま利用可能です。SAM および SAM CLI は、Apache 2.0 ライセンスの下でオープンソース化されています。GitHub に公開されていますので興味がある方は是非コントリビュートください。

さて、簡単なサーバーレス の YAML 構文の例を見てみましょう。

yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sample

Resources:
  PingFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        Ping:
          Type: Api 
          Properties:
            Path: /ping
            Method: get

Resources : 配下に AWS 上に構築したいリソースを記述していきます。ここでは Type: AWS::Serverless::Function という Lambda 関数リソースタイプを指定して、PingFunction というリソース名の Lambda 関数を記述しています。CodeUri: でローカルのビルド対象ディレクトリパスを指定しています。Handler: で Lambda 関数のハンドラを指定しています。Runtime: では Lambda 関数のランタイム言語を指定できて、ここでは Python3.8 を選択しています。

Events 配下は、Lambda 関数を呼び出すイベントリソースが宣言されています。ここでは Type: Api が指定してあり、これは Amazon API Gateway の REST API リソースから呼び出されることを意味しています。

このように、Lambda 関数の定義や、Lambda 関数を呼び出すイベントリソースも同時に宣言しデプロイすることができるため、サーバーレス 構築に関して手軽に利用することができます。


AWS SAM CLIを覚えよう

AWS SAM には、サーバーレスアプリケーションの作成と管理を容易にするコマンドラインツールである AWS SAM CLI が用意されています。インストールの方法については AWS SAM CLI のインストール を参照ください。

SAM CLI には以下のような多種のコマンドが用意されています。ここでは今回使う一部をご紹介しますが他のコマンドについても AWS SAM CLI コマンドリファレンス に記載がありますので参考にしてください。

 

sam init

sam initコマンドは、AWS SAMテンプレートを含むサーバーレスアプリケーションを初期化された構造を提供します。このSAMテンプレートは、AWS Lambda関数のディレクトリ構造を提供します。
sam build

sam buildコマンドは、AWS SAMテンプレートファイル、アプリケーションコード、および該当する言語固有のファイルと依存関係を処理します。このコマンドは、ワークフローの後続のステップに適合する形式で所定の場所にビルドアーティファクトをコピーします。 Python関数のrequirements.txtやNode.js関数のpackage.jsonなど、アプリケーションに含めるマニフェストファイルで依存関係を解決します。

sam deploy

sam deployコマンドは、sam buildコマンドを使用してビルドされたアーティファクトは、.aws-samディレクトリにあり、生成されたtemplate.yamlという名前のテンプレートファイルを見つけようとします。次に、AWS SAM CLIは、現在の作業ディレクトリでtemplate.yamlという名前のテンプレートファイルを見つけようとします。そのAWSSAMテンプレートとそれが指すローカルリソースのみがデプロイされます。

sam local invoke

sam local invokeコマンドは、--eventパラメーターを使用してイベント本体を渡しローカル実行することができます。Amazon Simple Storage Service(Amazon S3)やAmazonKinesisイベントなどの非同期イベントを処理するサーバーレス関数の開発に役立ちます。テストケースのスクリプトを作成する場合にも役立ちます。 

 

前回の PHP Lambda コンテナを SAM 化

AWS SAM のテンプレートファイルやディレクトリ構成をイチから作っていくのは大変なので、上で紹介した SAM CLI の sam init コマンドを利用していきましょう。

$ sam --version
SAM CLI, version 1.24.1

まず、今回利用する SAM のバージョンを確認しましょう。1.24.1 を今回は利用します。

$ sam init
Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1

sam init を実行すると、インタラクティブに選択しつつ進めていきます。ここでは、AWS が用意しているテンプレートを利用します。

What package type would you like to use?
    1 - Zip (artifact is a zip uploaded to S3)    
    2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 2

今回は Lambda 関数をコンテナイメージでパッケージングしたいので、Image を選択します。

Which base image would you like to use?
    1 - amazon/nodejs14.x-base
    2 - amazon/nodejs12.x-base
    3 - amazon/nodejs10.x-base
    4 - amazon/python3.8-base
    5 - amazon/python3.7-base
    6 - amazon/python3.6-base
    7 - amazon/python2.7-base
    8 - amazon/ruby2.7-base
    9 - amazon/ruby2.5-base
    10 - amazon/go1.x-base
    11 - amazon/java11-base
    12 - amazon/java8.al2-base
    13 - amazon/java8-base
    14 - amazon/dotnet5.0-base
    15 - amazon/dotnetcore3.1-base
    16 - amazon/dotnetcore2.1-base
Base image: 1

sam init で選択可能な Lambda ベースイメージの一覧が表示されるので、利用したいイメージを選ぶのですが、今回は provided:al2 を利用する予定です。ここでは、選択肢がないので適当に選んでおきます。

Project name [sam-app]: phplambda     

Cloning from https://github.com/aws/aws-sam-cli-app-templates

    -----------------------
    Generating application:
    -----------------------
    Name: phplambda
    Base Image: amazon/nodejs14.x-base
    Dependency Manager: npm
    Output Directory: .

    Next steps can be found in the README file at ./phplambda/README.md

プロジェクト名を求められるので、適当につけておきます。
これで、雛形の AWS SAM プロジェクトが生成されました。

つくられたディレクトリ階層 (抜粋) を確認してみましょう。

├── events
├── hello-world
│   ├── Dockerfile
│   ├── app.js
│   └── package.json
└── template.yaml

今回は、template.yaml ファイルを利用したいのですが、自動生成された hello-world ディレクトリは Node.js の実装になっているのでディレクトリごと破棄します。

前回 つくった PHP カスタムイメージのソースをこのディレクトリ構造にはめ込んで利用します。

├── event
├── src
│   ├── Dockerfile
│   ├── lambda
│   │   └── app.php
│   └── runtime
│       └── bootstrap
└── template.yaml

src ディレクトリを作り、前回のディレクトリ構造 をそのままコピーして利用します。
ここから、このディレクトリ構造に合うように template.yaml を書き換えていきます。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  phplambda

Globals:
  Function:
    Timeout: 3

Resources:
  phplambda:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image

    Metadata:
      DockerTag: phpoci
      DockerContext: ./src/
      Dockerfile: Dockerfile

Outputs:
  customphplambda:
    Description: "phplambda Lambda Function ARN"
    Value: !GetAtt phplambda.Arn
  customphplambdaIamRole:
    Description: "Implicit IAM Role created for phplambda function"
    Value: !GetAtt phplambda.Arn

このテンプレートの中身をみると、PackageType が Image になっています。これで Lambda のアーティファクトタイプがコンテナイメージであることを指示しています。また Metadata として、イメージのタグ、docker build 対象の src ディレクトリ、そして Dockerfile のファイル名の指定をしています。docker build は sam build 時に合わせて実行されますが、この Metadata を参照してビルドが行われます。

ECR にリポジトリをまだ作成していない場合はつくっておきます。

$ aws ecr create-repository --repository-name phplambda
{
    "repository": {
        "repositoryArn": "arn:aws:ecr:ap-northeast-1:{Account ID}:repository/phplambda",
        "registryId": "{Account ID}",
        "repositoryName": "phplambda",
        "repositoryUri": "{Account ID}.dkr.ecr.ap-northeast-1.amazonaws.com/phplambda",
        "createdAt": "2021-06-12T23:11:05+09:00",
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": false
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}

作成した repositoryUri を利用するのでメモしておきましょう。
ここまでで、AWS SAM でビルド、デプロイする準備が整ったので、さっそく AWS SAM CLI で実行していきましょう。

$ sam build
・・・
Successfully tagged phplambda:phpoci

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

sam build を実行すると、src 配下の PHP Lambda 関数のビルドが始まります。成功すると、つぎのこのコマンドを使えますよといった推奨コマンドが表示されていますので、両方実行してみましょう。

$ sam local invoke
Invoking Container created from phplambda:phpoci
Image was not found.
Building image.............
Skip pulling image and use local one: phplambda:rapid-1.24.1.

START RequestId: a568fb70-62ab-49b9-b71e-3f0d65a39bac Version: $LATEST


{"function_name":"phplambda","function_version":"$LATEST","invoked_function_arn":[],"memory_limit_in_mb":"128","aws_request_id":["a568fb70-62ab-49b9-b71e-3f0d65a39bac"],"log_group_name":"aws\/lambda\/phplambda","log_stream_name":"$LATEST","deadline_ms":["3246622130627"],"trace_id":[],"x_amzn_trace_id":false,"identity":[],"client_context":[]}
PHP Notice:  Undefined index: queryStringParameters in /var/task/app.php on line 6
END RequestId: a568fb70-62ab-49b9-b71e-3f0d65a39bac
REPORT RequestId: a568fb70-62ab-49b9-b71e-3f0d65a39bac  Init Duration: 0.29 ms  Duration: 213.20 ms     Billed Duration: 300 ms Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode":200,"headers":{"Content-Type":"application\/json","Access-Control-Allow-Origin":"*","Access-Control-Allow-Headers":"Content-Type","Access-Control-Allow-Methods":"OPTIONS,POST"},"body":"queryStringParameters, null"}3

まずは sam local invoke を実行してみました。ローカル環境で Lambda をエミュレートしたコンテナ環境が SAM より提供されています。

よく見ると、エラー (PHP Notice: Undefined index: queryStringParameters) が発生しています。

app.php

<?php

function handler($event, $context)
{
    echo json_encode($context);
    return response("queryStringParameters, ". json_encode($event['queryStringParameters']));
}

function response($body)
{
    $headers = array(
        "Content-Type"=>"application/json"
    );
    return json_encode(array(
        "statusCode"=>200,
        "headers"=>$headers,
        "body"=>$body
    ));
}

前回作った PHP ソースを見ると event データから queryStringParameters を取得しようとして、そんな JSON フィールドが無いという理由でエラーとなっているようですね。

AWS SAM では実行時にユーザが定義した event データを渡す機能がありますので使ってみましょう。

sam local generate-event apigateway aws-proxy > event/apigateway-proxy.json

Amazon API Gateway から呼び出された場合の event スキーマを sam local generate-event コマンドを利用して生成することができます。このコマンドの出力を event ディレクトリに入れておきます。

さて、event データを渡して sam local invoke を再実行してみましょう。
-e オプションで生成した event データを渡しています。

$ sam local invoke -e event/apigateway-proxy.json 
Invoking Container created from phplambda:phpoci
Building image.....
Skip pulling image and use local one: phplambda:rapid-1.24.1.

START RequestId: 27719b0b-6161-4883-b0b3-5300c828273c Version: $LATEST

{"function_name":"phplambda","function_version":"$LATEST","invoked_function_arn":[],"memory_limit_in_mb":"128","aws_request_id":["27719b0b-6161-4883-b0b3-5300c828273c"],"log_group_name":"aws\/lambda\/phplambda","log_stream_name":"$LATEST","deadline_ms":["3246622633172"],"trace_id":[],"x_amzn_trace_id":false,"identity":[],"client_context":[]}
END RequestId: 27719b0b-6161-4883-b0b3-5300c828273c
REPORT RequestId: 27719b0b-6161-4883-b0b3-5300c828273c  Init Duration: 1.24 ms  Duration: 73.64 ms      Billed Duration: 100 ms Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode":200,"headers":{"Content-Type":"application\/json","Access-Control-Allow-Origin":"*","Access-Control-Allow-Headers":"Content-Type","Access-Control-Allow-Methods":"OPTIONS,POST"},"body":"queryStringParameters, {\"foo\":\"bar\"}"}

今度は実行に成功しました。


PHP Lambda コンテナを SAM を利用して AWS クラウドにデプロイしよう

sam deploy に —guided オプションを入れるとインタラクティブにデプロイオプションを指定できます。

まず、CloudFormation のスタック名を決めます。

$ sam deploy --guided

Configuring SAM deploy
======================

Looking for config file [samconfig.toml] :  Found
Reading default arguments  :  Success

Setting default arguments for 'sam deploy'
=========================================
Stack Name []: phplambda

そして、docker リポジトリの指定をします。上で作成したリポジトリの URI をしてして、docker push のターゲットとします。

それ以外のパラメータも適宜決めていきます。

AWS Region [ap-northeast-1]: 
Image Repository for phplambda []: {Account ID}.dkr.ecr.ap-northeast-1.amazonaws.com/phplambda
  phplambda:phpoci to be pushed to {Account ID}.dkr.ecr.ap-northeast-1.amazonaws.com/phplambda:phplambda-abcdefghi-phpoci

#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [Y/n]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
Save arguments to configuration file [Y/n]: y
SAM configuration file [samconfig.toml]: 
SAM configuration environment [default]: 

デプロイ対象となる Stack の差分 (チェンジセット) が表示され、これらのリソースを作成 / 更新 / 削除してよければ YES で回答して実際にデプロイします。

CloudFormation stack changeset
----------------------------------------------------------------
Operation  LogicalResourceId  ResourceType          Replacement 
----------------------------------------------------------------
+ Add      phplambdaRole      AWS::IAM::Role        N/A         
+ Add      phplambda          AWS::Lambda::Function N/A 
----------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:{Account ID}:changeSet/samcli-deploy1623806056/6696dd2a-1ffe-494f-a9d5-a69ddddc11c2


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y     

最後に Outputs としてリソースの ARN などの情報が出力されています。

CloudFormation outputs from deployed stack
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                                                
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 customphplambdaIamRole                                                                                                                             
Description         Implicit IAM Role created for phplambda function                                                                                                   
Value               arn:aws:lambda:ap-northeast-1:{Account ID}:function:phpstack-phplambda-w0JDE3lNIRlR                                                                

Key                 customphplambda                                                                                                                                    
Description         phplambda Lambda Function ARN                                                                                                                      
Value               arn:aws:lambda:ap-northeast-1:{Account ID}:function:phpstack-phplambda-w0JDE3lNIRlR                                                                
------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - phpstack in ap-northeast-1

Successfully と成功が報告されればデプロイ完了です。

phpstack-phplambda-w0JDE3lNIRlR という名前の関数が生成されたので実行してみましょう。

$ aws lambda invoke \
   --payload fileb://./event/apigateway-proxy.json \
   --function-name phpstack-phplambda-w0JDE3lNIRlR output ; cat output
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
{
    "statusCode": 200,
    "headers": {
        "Content-Type": "application/json"
    },
    "body": "queryStringParameters, {\"foo\":\"bar\"}"
}

ちゃんとデプロイした関数が実行できることが確認できました。


まとめ

サーバーレスアプリケーションでも Infrastructure as Code の考えで構成情報をコードとして管理していくことは開発の上で重要になってきます。今回は、AWS SAM がコンテナ Lambda でも利用できることをご紹介しました。実は SAM でもローカルテストができますので、RIE を含まないイメージでも AWS SAM ではローカルテスト実行が簡単にできるといった良さもあります。

Docker CLI で全てを完結するかよりリッチな AWS SAM CLI でローカルのビルド作業を行うかはプロジェクトやチームの考えで決定すると良いでしょう。

次回以降ではさらに、コンテナサポート Lambda 関数の特徴や利点を追っていきたいと思います。


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

photo_shimokawa-kensuke

下川 賢介 (@_kensh)
アマゾン ウェブ サービス ジャパン株式会社
シニア サーバーレススペシャリスト ソリューションアーキテクト

Serverless Specialist Solutions Architect として AWS Japan に勤務。
Serverless の大好きな特徴は、ビジネスロジックに集中できるところ。
ビジネスオーナーにとってインフラの管理やサービスの冗長化などは、ビジネスのタイプに関わらず必ず必要になってくる事柄です。
でもどのサービス、どのビジネスにでも必要ということは、逆にビジネスの色はそこには乗って来ないということ。
フルマネージドなサービスを使って関数までそぎ落とされたロジックレベルの管理だけでオリジナルのサービスを構築できるという Serverless の特徴は技術者だけでなく、ビジネスに多大な影響を与えています。
このような Serverless の嬉しい特徴をデベロッパーやビジネスオーナーと一緒に体験し、面白いビジネスの実現を支えるために日々活動しています。

AWS のベストプラクティスを毎月無料でお試しいただけます

さらに最新記事・デベロッパー向けイベントを検索

下記の項目で絞り込む
絞り込みを解除 ≫
フィルタ
フィルタ
1

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する