ぴろログ

Output Driven

AWS LambdaでAWS CLIが使えるCustomRuntimeをつくってLayerにしてSAMでデプロイするまでを試してみた

業務でLambda Functionを利用する機会があり、せっかくなので気になっていたCustom Runtime、Layer、AWS SAM(Serverless Application Model)を使うことにしました。

その下調べをしたので、記録を残しておきます。

試したもの

それぞれの概要です。

今回はAWS CLIを使うためのCustom Runtimeを作成(クラメソさんの記事を参考に)、SAMを使ってLayer化するところまで実施しました。

dev.classmethod.jp

なにがいいかって、LambdaでAWS CLIがそのまま動かせるので「シェルスクリプトしか書けないからLambda使えって言われても。。」なインフラ担当もLambdaデビューできちゃうところ。と、既存のシェルスクリプトを簡単に移行できちゃいそうなところ。※実行時間の制限:15分(2019/04/16現在)は考慮要。

作業

前提

今回使用したPCの環境です。

下準備

LambdaからS3にデータをアップロードする処理があるので、必要なリソースを準備をします。

S3 バケットを作成します。今回は「p-lambda-layer-awscli」という名称にしました。(バケット名はグローバルで一意である必要があるので、適宜書き換え。)

$ aws s3 mb s3://p-lambda-layer-awscli --region ap-northeast-1 --profile <profile-name>
make_bucket: p-lambda-layer-awscli

LambdaがS3にアクセスするので、コンソールからIAM Role「Lambda-S3FullAccess」を作成します。

Lambdaを信頼するエンティティとして設定。

お試しなので、S3のFullAccessをアタッチしちゃいます。

ロール名を入力して作成。

AWS CLIが使えるCustom Runtimeを作成

こちらの手順1、手順2を実行します。(上記のクラメソさん記事からリンク)

github.com

  1. Create AWS CLI Component ./shell/01.build_deploy.sh [Role] [S3 Bucket] [AWS CLI Profile]
  2. Create AWS CLI Layer ./shell/02.create_aws_layer.sh [S3 Bucket] [AWS CLI Profile]

流れはざっくりこんな感じです。

  • 01.build_deploy.sh
    • Lambda Functionを作成
    • Lambda(を動かすAmazonLinux2のコンテナ)上で、pipやAWS CLIをインストールして、Custom Runtimeを作成
    • ./python/ 配下一式をtarで圧縮し、ZIPをS3にアップロード
  • 02.create_aws_layer.sh
    • S3にアップロードしたデータを、ローカルにダウンロード
    • Layer用のデータを作成(aws-cli-layer.zip)
    • aws publish-layer-versionでLayerをデプロイ

実行してみます。

$ ./shell/01.build_deploy.sh Lambda-S3FullAccess p-lambda-layer-awscli <AWS CLIのprofile-name>
  adding: function.sh (stored 0%)
  adding: bootstrap (deflated 42%)
{
    "FunctionName": "layer-creater",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:<Account ID>:function:layer-creater",
    "Runtime": "provided",
    "Role": "arn:aws:iam::<Account ID>:role/Lambda-S3FullAccess",
    "Handler": "function.handler",
    "CodeSize": 1043,
    "Description": "",
    "Timeout": 900,
    "MemorySize": 256,
    "LastModified": "2019-04-16T06:28:36.293+0000",
    "CodeSha256": "rKPZwQXwtqQTWPSnUKfRLDg5eHxQ3vPcGkJEcEayz/g=",
    "Version": "$LATEST",
    "Environment": {
        "Variables": {
            "S3_BUCKET": "p-lambda-layer-awscli"
        }
    },
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "cab099e6-ceb5-4dac-bcc1-faed0452c899"
}
{
    "StatusCode": 202
}

$ ./shell/02.create_aws_layer.sh p-lambda-layer-awscli <AWS CLIのprofile-name>
download: s3://p-lambda-layer-awscli/provided.tgz to aws-cli-layer/provided.tgz
 ・・・
{
    "Content": {
        "Location": "https://awslambda-ap-ne-1-layers.s3.ap-northeast-1.amazonaws.com/snapshots/<Account ID>/aws-cli-layer-13637de3-c15f-4915-a212-0a18bf368726?versionId=XWPks5TjWf3ofzRmJTdhO8zQsWzsBcN2&X-Amz-Security-Token=AgoJb3JpZ2luX2VjEDYaDmFwLW5vcnRoZWFzdC0xIkcwRQIgFhXsJRXouon5uEdxpj%2FhOMZp686sEVlRZiQTKkBSzogCIQDbt2GJ4JGsIbQIAp36eSnao%2Bl6HfPjvjN1j3V%2F8S5ajSrtAwj%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDkxOTk4MDkyNTEzOSIM51W2l9dRkC5jtCRRKsED3YeVgrBNXLYStf2qgzPFNN2KqHlzQieaVSkMMl8SSU1CBQEhkhqKfC6otOV7BoG6isjNDwOWL3X4udi3VakgmTucpP0amB8%2BwTaquRQ321ENeMYavW1%2B7vWBk5q0G1cRG8az%2FiVOyWWrFAxg27Iy76CvqrNykX%2BydJqh10rXcEXWzlgeaoSjAeV1e3OR3ikTFlosnF6djZIDaHri4YG0D1%2Fy5yW7%2Fof%2FrSUVT8Y5nJTCvbD1NGkHhE03MXWcnocdvQadqsUkvUZ4lGE%2BJQMM%2F0VQnNjREu3iucxNQMiBDh2c7yntKtgW%2FD2J8jXqn9TP%2B%2Fv3u78CGbb4q1tdZGTcWHaaCYQk5SHGrw0TMOaji1hL6h8kf%2F8hZaE1MTfUlPEdZ7MSFfM24JujXQlhi6K2LBdHyaMQi4y%2FAOegq2m86p%2Ffam%2B9s5pWhtvRemL68ZRz3mr1j7Wi3GuvEQ%2B1Fv4qCZ5EMu3W%2Bat9ypYqtgFeClXcdBIP7UfRxgJLXbVXbaAFxV5yt7HPB6CFvAOQEuhZtbnIsXBc6xQk1aRH8vMj0FAiENy4EFCALKqDmPurfPKFXsPdf89G6QjtInP0HTPLG1Uwx%2BTV5QU6tAHcZkV2RO7hPJmxWBgG22GZX0kb0BXVFycasou%2FaliapZbc0Bbc4g45yewfBBz6TIuy9%2Bt%2BFe01DzKwFRYPY4y%2F37rG4mv%2BfEGiBWXjoYeEIWGA3UlTO81wG99PN22%2B3kC3Vapt99gHCHt%2BbimlrRNz8C%2FstD62Hz%2Ft%2BjKTZHVRL3P8xWmZFGMOAt1aFbuQEvoH%2FlypmXBHfbdiCs7kGDFi04QrKocJjBV7qgvACE8qgp6Rqrg%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20190416T064538Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIA5MMZC4DJWO6HV3XU%2F20190416%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=4b4c9499ce06f838f7ff7f94b315811d6a68337611f32c84f3c89088ef1c2c0d",
        "CodeSha256": "THTp3Fuyq2P8x+lRnf67berUV4sdMs7LRstuOT2b9Y8=",
        "CodeSize": 10610356
    },
    "LayerArn": "arn:aws:lambda:ap-northeast-1:<Account ID>:layer:aws-cli-layer",
    "LayerVersionArn": "arn:aws:lambda:ap-northeast-1:<Account ID>:layer:aws-cli-layer:1",
    "Description": "",
    "CreatedDate": "2019-04-16T06:45:46.357+0000",
    "Version": 1
}

※私の環境でうまく動作しない部分があったので、何点か修正しました。

  • 01.build_deploy.sh
    • aws s3 rm s3://${Bucket}/provided.tgz をコメントアウト(初回作成なので該当データ無し)
    • aws lambda create-function に --region ap-northeast-1 オプションを追加(直後のaws lambda invokeとリージョンを合わせる)
  • 02.create_aws_layer.sh
    • 上記と同じ理由で、aws lambda publish-layer-version に --region ap-northeast-1 を追加

これでLayerが作成されます。

ここでふと思ったのです。手元にLayerの元データ(aws-cli-layer.zip)があるし、SAMに組み込んだらAWS CLIを使ったシェルスクリプトLambda Functionがサクッと作れるのではないか、と。

SAMでLambda Function+Layer(AWS CLI)を作成

というわけで、SAMを試してみます。とりあえずデプロイできることを目標にしています。

aws-sam-cliという便利ツールがあるので、まずはHomebrewでインストールします。手順はこちら

lambda-awscliというプロジェクトを作成します。

$ sam init -n lambda-awscli

諸々のテンプレートが作成されますが、ほとんど削除しちゃって、最終的には以下の構成。

各種ファイルの中身はこんな感じです。awscli.yamlでLayerの作成やLambda関数との紐付けを記述、実際に実行されるコードがfunction.shAWS CLIのバージョン出力など)。

# awscli.yaml

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

Globals:
  Function:
    Timeout: 300

Resources:
  LambdaAWSCLI:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: function/
      Handler: function.handler
      Runtime: provided
      Layers:
        - !Ref LayerAWSCLI
  LayerAWSCLI:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: awscli
      Description: bash,awscli
      ContentUri: runtime/
      RetentionPolicy: Retain
# function.sh

function handler () {

  RESPONSE=$(cat <<EOS
uname -a: $(uname -a)
bash version: $(bash --version)
awscli version: $(aws --version 2>&1)
EOS
)

  echo $RESPONSE
}

sam packageでCloudFormationファイルを生成 & S3にアップロードし、sam deployでデプロイします。

$ sam package --template-file awscli.yaml --s3-bucket p-lambda-layer-awscli --output-template-file out.yaml --profile <profile-name>
Uploading to 04f9c35e4c311af1ba5f1f94ef3eaad2  10310668 / 10310668.0  (100.00%)
Successfully packaged artifacts and wrote output template to file out.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /Users/pirox/Documents/git/lambda-awscli/out.yaml --stack-name <YOUR STACK NAME>

作成されたout.yamlの中身。CodeUriやContentUriがS3にアップされたファイルのパスに書き換わっています。

# out.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'lambda_layer-awscli

  '
Globals:
  Function:
    Timeout: 300
Resources:
  LambdaAWSCLI:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: s3://p-lambda-layer-awscli/cf26864049c0148728f76c2836005c2e
      Handler: function.handler
      Runtime: provided
      Layers:
      - Ref: LayerAWSCLI
  LayerAWSCLI:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: awscli
      Description: bash,awscli
      ContentUri: s3://p-lambda-layer-awscli/04f9c35e4c311af1ba5f1f94ef3eaad2
      RetentionPolicy: Retain
$ sam deploy --template-file out.yaml --stack-name lambda-awscli --capabilities CAPABILITY_IAM --profile <profile-name>

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - lambda-awscli

コンソールでCloudFormationを見てみます。できてる!

次にLambda。Layerできてる!!(何度か作成したので、画面キャプチャ取得時にはバージョンが3になってます)

Lambda FunctionもLayerにアタッチ(って言い方であってるのかな?)されてできてるー!!!

AWS CLIもバッチリ使えてます。(Python2系だった。。)

別でLambda Functionを作成する場合には、yamlファイルの中で今回作成したLayerと紐づければ使い回しができます。

業務での利用を考えると、Python3系にしておきたかったり、AWS CLIのバージョンアップについていきたかったり、いくつか課題がありそうです。

メモ

構築中に調べたこと。

Custom Runtime

docs.aws.amazon.com

カスタムランタイムを使用するには、関数のランタイムを provided に設定します。

bootstrap という名前のファイルがデプロイパッケージにある場合、Lambda はそのファイルを実行します。ない場合、Lambda は関数のレイヤーにランタイムがないかどうかを確認します。ブートストラップファイルが見つからないか、実行可能でない場合、関数は呼び出し時にエラーを返します。

Custom Runtimeを使うには、bootstrapファイル内にモジュールのインストール処理を書く。(pip install --user awscli 等)

Layer

Layerがどこに紐づいているか知りたくて、whichコマンドを実行。

# Lambda関数でwhichコマンドを実行したログ
+++ which bash
++ echo /bin/bash
+++ which aws
++ echo /opt/bin/aws

ローカルのruntimeフォルダに配置したファイルが、そのままLambdaコンテナの/opt配下に配置されているようです。って公式ドキュメントにそのまま記載されていましたね。。

docs.aws.amazon.com

レイヤーは、関数実行環境の /opt ディレクトリに抽出されます。

LayerとLambda Functionは同一リージョンに配置する必要があるみたいです。東京リージョンのLambda FunctionにバージニアリージョンのLayerを紐付けようとしたらエラーになりました。

Layerにはバージョン管理の概念があるようで、ARNにもバージョン情報が含まれています。(バージョンARNと表記されています。)既存のLambda Functionのコードには手を加えずに、Layer(Runtime)のバージョンアップ運用が可能ですね。

また、version3を削除した後にLayerの更新(バージョンアップ)をした場合、新たに作成されるLayerはversion4になります。(3にはならない。)

Layerを紐付ける処理が必要になる分、実行速度は遅くなるのかな?と思いましたが、「Lambda Layersを使うとコールドスタートが高速化する」という検証結果があるようです。謎。

dev.classmethod.jp

SAM

各種リソースの定義方法を覚えました。

# awscli.yaml
 ・・・
Resources:
  LambdaAWSCLI:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: function/
      Handler: function.handler
      Runtime: provided
      Layers:
        - !Ref LayerAWSCLI
  LayerAWSCLI:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: awscli
      Description: bash,awscli
      ContentUri: runtime/
      RetentionPolicy: Retain

Resource typesの書き方。

  • Lambda Function
    • Type: AWS::Serverless::Function
  • Lambda Layer
    • Type: AWS::Serverless::LayerVersion

LayerのContentUriに記述したディレクトリに、Layerにしたいコンポーネントを格納しました(今回はAWS CLI)。

リソース名に使えるのは英数字のみです。ハイフンを使ってエラーが出ました。↓

Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state Status: FAILED. 
Reason: Transform AWS::Serverless-2016-10-31 failed with: Invalid Serverless Application Specification document. 
Number of errors found: 1. 
Resource with id [lambda-awscli] is invalid. 
Logical ids must be alphanumeric.

runtimeディレクトリにbin/のみを配置した場合はエラーが出たので、bootstrapの配置も必須みたいですね。

↓はリファレンス。

github.com

まとめ

  • AWS CLIが実行できるLambda FunctionをSAMで作成した。
  • SAMを使ってCustom RuntimeをLayer化することができる。
    • yamlファイル内でType: AWS::Serverless::LayerVersion
    • bootstrapと外部モジュールを配置して、ContentUri:でパス指定。