[アップデート] AWS SAM CLIのlocal start-apiがTerraformをサポートしました

2023.07.07

初めに

本日リリースされたAWS SAM CLIのv1.90.0にてsam local start-apiがTerraformをサポートするようになりました。

以前からアップデートに本機能関連のアップデートが含まれていたのでついに来たかという感じです。

本機能限らずですがSAMでTerraformを利用可能な機能については厳密にはネイティブにサポートしているわけではなく、内々的にはterraformコマンドの結果を元にCloudFormationテンプレートに変換しているものとなります。

具体的な仕組みは過去のマージコミットからかいつまんで見ている程度となりますが、コア部分はおそらく以下の部分となりますので興味のある人は読んで見てはいかがでしょうか。

実行バージョン

前述した通り内部的にはterraformコマンドを利用していますのでsamとは別にterraformコマンドが必要となります。

% sam --version
SAM CLI, version 1.90.0
# しばらくterraform使っていなかった関係でバージョンが低いです
% terraform --version
Terraform v1.3.2
on darwin_arm64

コード

main.tf以外はsam initでデフォルトで生成されているhello_worldのコードを利用しています。

main.tf

provider "aws" {
  profile = "default"
  region  = "ap-northeast-1"
}

resource "aws_api_gateway_rest_api" "sam_api" {
  name        = "sam-api"
  description = "sam local testing"
}

resource "aws_api_gateway_resource" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.sam_api.id
  parent_id   = aws_api_gateway_rest_api.sam_api.root_resource_id
  path_part   = "hello"
}

resource "aws_api_gateway_method" "hello_world" {
  rest_api_id   = aws_api_gateway_rest_api.sam_api.id
  resource_id   = aws_api_gateway_resource.hello_world.id
  http_method   = "GET"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.sam_api.id
  resource_id = aws_api_gateway_resource.hello_world.id
  http_method = aws_api_gateway_method.hello_world.http_method
  uri         = aws_lambda_function.hello_world.invoke_arn
  type        = "AWS_PROXY"
}

resource "aws_lambda_function" "hello_world" {
  filename      = "hello_world/"
  function_name = "HelloWorldFunction"
  handler       = "app.lambda_handler"
  runtime       = "python3.9"
  role          = aws_iam_role.lambda_role.arn
  memory_size   = 128
  timeout       = 60
  architectures = ["arm64"]
}

resource "aws_iam_role" "lambda_role" {
  name = "LambdaRole"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

※ 2023/09/04追記
別件で検証してたところbuild後のアーティファクトにpythonコードのビルド結果がないと思ったらsam metadata resourceが必要らしいです。
今回のコードの場合はpythonコードがビルドが不要(requirements.txtにインストールが必要なものがない)だったので通ってました。

実行

現在terraformが対応している他のコマンド同様に、実行時に--hook-nameオプションにterraformを指定することで実行が可能です。

なお現時点ではSAM上でのterraform利用自体がベータ機能となっているため非対話式に実行したい場合は--beta-featuresオプションの指定が必要です。

--beta-featuresは指定がなくとも実行は可能ですが、実行のたびに確認が発生します。

% sam local start-api --hook-name terraform
Supporting Terraform applications is a beta feature.
Please confirm if you would like to proceed using AWS SAM CLI with terraform application.
You can also enable this beta feature with 'sam local invoke --beta-features'. [y/N]: y

Experimental features are enabled for this session.
Visit the docs page to learn more about the AWS Beta terms https://aws.amazon.com/service-terms/.

改めて実行してみます。

% sam local start-api --hook-name terraform --beta-features                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
...                                                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
Finished generating metadata file. Storing in /xxx/sam-app-terraform/.aws-sam-iacs/iacs_metadata/template.json                                                                                                                                                                                                                                                                                                                                                               
Prepare hook completed and metadata file generated at: /xxx/sam-app-terraform/.aws-sam-iacs/iacs_metadata/template.json                                                                                                                                                                                                                                                                                                                                                      
Initializing the lambda functions containers.                                                                                                                                                                                                                                                                                                                                                                                                                                                   
Local image is up-to-date                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
Using local image: public.ecr.aws/lambda/python:3.9-rapid-arm64.                                                                                                                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
Mounting /xxx/sam-app-terraform/hello_world as /var/task:ro,delegated, inside runtime container                                                                                                                                                                                                                                                                                                                                                                              
Containers Initialization is done.                                                                                                                                                                                                                                                                                                                                                                                                                                                              
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]                                                                                                                                                                                                                                                                                                                                                                                                                                
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. If you used sam build before running local commands, you will need to re-run sam build for the changes to be picked up. You only need to restart SAM CLI if you update your AWS SAM template                                                                                                  
2023-07-07 17:42:27 WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:3000
2023-07-07 17:42:27 Press CTRL+C to quit
# 別の画面でcurlを実行
# % curl http://127.0.0.1:3000/hello
# {"message": "hello world"}%
Invoking app.lambda_handler (python3.9)                                                                                                                                                                                                                                                                                                                                                                                                                                                         
Reuse the created warm container for Lambda function 'aws_lambda_function.hello_world'                                                                                                                                                                                                                                                                                                                                                                                                          
Lambda function 'aws_lambda_function.hello_world' is already running                                                                                                                                                                                                                                                                                                                                                                                                                            
END RequestId: 760b8b37-7fef-4ad8-95c6-fa8ad2ac07ba
REPORT RequestId: 760b8b37-7fef-4ad8-95c6-fa8ad2ac07ba  Init Duration: 0.26 ms  Duration: 108.17 ms     Billed Duration: 109 ms Memory Size: 128 MB     Max Memory Used: 128 MB
No Content-Type given. Defaulting to 'application/json'.                                                                                                                                                                                                                                                                                                                                                                                                                                        
2023-07-07 17:45:33 127.0.0.1 - - [07/Jul/2023 17:45:33] "GET /hello HTTP/1.1" 200 -

terraformのモジュールを利用してsam local start-apiが利用できることが確認できました。

実は以前のバージョンからある程度対応していた

先に書かせていただいた通り、本件に関する機能追加は行われており変換処理等の実装は進められていましたが、
sam local start-apiコマンドに--hook-nameオプションが存在しないため、実際直接の実行ができないものとなっておりました。

今回検証中に変更した内容が反映されずsam buildを試したところビルドの成果物として変換後のCloudFormationテンプレートが生成されている為、
もしやと思い確認したところ、あくまでstart-apiでの直接の呼び出しが対応していないだけでbuildを経由することで以前のバージョンでも利用可能でした。

やり方としてはsam build時に--hook-name terraformを指定し、sam loval start-apiを実行するだけです。

## キャッシュ等が影響しないように事前に.aws-sam(-iacs)フォルダを消しておく
% rm -rf .aws-sam*
## venvで環境を作成しpipで古いバージョンsamを準備しておく
## NOTE: brewは現在最新版しか保持していない
% /tmp/sam189/bin/sam --version
SAM CLI, version 1.89.0
## sam buildを実行する
% /tmp/sam189/bin/sam build --hook-name terraform --beta-features
...
Build Succeeded

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

Commands you can use next
=========================
[*] Invoke Function: sam local invoke --hook-name terraform
[*] Emulate local Lambda functions: sam local start-lambda --hook-name terraform

ビルドの成果物として.aws-sam/build/template.yamlに変換後のCloudFormationテンプレートが存在します。

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  AwsApiGatewayMethodHelloWorld0E3B471C:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      AuthorizationType: NONE
      RestApiId:
        Ref: AwsApiGatewayRestApiSamApiB178E174
      ResourceId:
        Ref: AwsApiGatewayResourceHelloWorld3E1F8155
      Integration:
        Uri:
          Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AwsLambdaFunctionHelloWorld4ED843CE.Arn}/invocations
        Type: AWS_PROXY
        ConnectionType: INTERNET
    Metadata:
      SamResourceId: aws_api_gateway_method.hello_world
  AwsApiGatewayResourceHelloWorld3E1F8155:
    Type: AWS::ApiGateway::Resource
    Properties:
      PathPart: hello
      RestApiId:
        Ref: AwsApiGatewayRestApiSamApiB178E174
      ParentId:
        Fn::GetAtt:
        - AwsApiGatewayRestApiSamApiB178E174
        - RootResourceId
    Metadata:
      SamResourceId: aws_api_gateway_resource.hello_world
  AwsApiGatewayRestApiSamApiB178E174:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: sam-api
    Metadata:
      SamResourceId: aws_api_gateway_rest_api.sam_api
  AwsLambdaFunctionHelloWorld4ED843CE:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: HelloWorldFunction
      Architectures:
      - arm64
      Code: /xxxx/sam-app-terraform/hello_world
      Handler: app.lambda_handler
      PackageType: Zip
      Runtime: python3.9
      Timeout: 60
      MemorySize: 128
    Metadata:
      SamResourceId: aws_lambda_function.hello_world
      SkipBuild: true
Metadata:
  AWS::SAM::Hook:
    HookName: terraform

この時点ですでにCloudFormationテンプレートができているのでsam local start-api実行時には--hook-nameオプション等は不要です。

% /tmp/sam189/bin/sam local start-api
Initializing the lambda functions containers.
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.9-rapid-arm64.

Mounting /xxx/git/sam-app-terraform/hello_world as /var/task:ro,delegated, inside runtime container
Containers Initialization is done.
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
...
 * Running on http://127.0.0.1:3000
2023-07-07 18:11:45 Press CTRL+C to quit
Invoking app.lambda_handler (python3.9)
Reuse the created warm container for Lambda function 'aws_lambda_function.hello_world'
Lambda function 'aws_lambda_function.hello_world' is already running
START RequestId: 9242f13c-2f7d-48d7-9016-edf50a10ba53 Version: $LATEST
END RequestId: 9242f13c-2f7d-48d7-9016-edf50a10ba53
REPORT RequestId: 9242f13c-2f7d-48d7-9016-edf50a10ba53	Init Duration: 0.78 ms	Duration: 120.52 ms	Billed Duration: 121 ms	Memory Size: 128 MB	Max Memory Used: 128 MB
No Content-Type given. Defaulting to 'application/json'.
2023-07-07 18:12:51 127.0.0.1 - - [07/Jul/2023 18:12:51] "GET /hello HTTP/1.1" 200 -

どこが境界かなと思いましたが少なくとも今回の実装内容であれば1.85.0から対応していたようです。

  • v1.82.0は変換が不十分で実行はできるがメソッド等が結びついていない(apigw関連の初回追加バージョン)
  • v1.84.0も変換が不十分でアクセスするとInternal server errorとなる
  • v1.85.0今回実装の内容では正常に実行可能

あくまで今回のサンプルの実装で実行をした場合となり1.90.0に比べ機能が不足している可能性がある点はご注意ください。

終わりに

今回のアップデートで実質的にsam local全般がterraformに対応しました。

厳密にはgenerate-eventは未対応ですがイベントを生成するだけでテンプレートが何であるかは関係ないので対応自体はないものと思っています。

デプロイに関してはそもそもterraformの機能でありますし、ローカルの開発サポートが完了したので安定確認できたあたりでベータが外れるのか、デプロイ諸々対応するまで外れないのかは少し気になるところです。