TECHSCORE BLOG

マーケティングSaaS を提供するシナジーマーケティングのエンジニアブログです。

AWS SAMで、シンプルなAPI を構築してみた

はじめに

金融機関向けのトータルソリューションをBank Up Plus として、いくつかのサービスを提供しています。
そのサービスを作り上げる際の検証段階で、技術選定のために検証した内容の1つであるAWSで簡単にAPIを構築する方法と、その時に詰まったことについてまとめます。

AWS SAM を使って API Gateway → Lambda の構成を作ってみました。 Lambda の関数は TypeScript で書いています。

「SAM を使えばサーバーレス構成が簡単に作れる」とは聞いていたのですが、権限まわりで詰まってしまいました。同じところで躓く人の参考になればと思い記事にしました。

作ったもの

クライアント
    │
    ▼
API Gateway
    │  GET /hello
    ▼
Lambda(TypeScript)
    │
    ▼
JSONレスポンス返却

前提の環境

  • AWS CLI 2.32.1
  • SAM CLI 1.157.1
  • Node.js 24.x 

やったこと

①プロジェクト作成

sam init

対話式で、プロジェクトを作成できます。 ※ ログ等の選択、プロジェクト名は任意の設定をします。

$ sam init

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1 <- ★ 1を選択

Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Data processing
        3 - Hello World Example with Powertools for AWS Lambda
        4 - Multi-step workflow
        5 - Scheduled task
        6 - Standalone function
        7 - Serverless API
        8 - Infrastructure event management
        9 - Lambda Response Streaming
        10 - GraphQLApi Hello World Example
        11 - Full Stack
        12 - Durable Functions
        13 - Lambda EFS example
        14 - Serverless Connector Hello World Example
        15 - Multi-step workflow with Connectors
        16 - Hello World Durable Function Example
        17 - DynamoDB Example
        18 - Machine Learning
Template: 1 <- ★ 1を選択

Use the most popular runtime and package type? (python3.14 and zip) [y/N]: N <- ★ N で進む

Which runtime would you like to use?
        1 - dotnet10
        2 - dotnet8
        3 - dotnet6
        4 - go (provided.al2)
        5 - go (provided.al2023)
        6 - graalvm.java11 (provided.al2)
        7 - graalvm.java17 (provided.al2)
        8 - java25
        9 - java21
        10 - java17
        11 - java11
        12 - java8.al2
        13 - nodejs24.x
        14 - nodejs22.x
        15 - nodejs20.x
        16 - python3.9
        17 - python3.14
        18 - python3.13
        19 - python3.12
        20 - python3.11
        21 - python3.10
        22 - ruby3.4
        23 - ruby3.3
        24 - ruby3.2
        25 - rust (provided.al2)
        26 - rust (provided.al2023)
Runtime: 13 <- ★ 13 を選択

What package type would you like to use?
        1 - Zip
        2 - Image
Package type: 1 <- ★ 1を選択(Imageを使いたい場合そちらでもOK)

Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.

Select your starter template
        1 - Hello World Example
        2 - Hello World Example TypeScript
Template: 2 <- ★ 2 を選択

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: N

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: y 
AppInsights monitoring may incur additional cost. View https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/appinsights-what-is.html#appinsights-pricing for more details

Would you like to set Structured Logging in JSON format on your Lambda functions?  [y/N]: y 
Structured Logging in JSON format might incur an additional cost. View https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-pricing for more details

Project name [sam-app]: my-sam-api <- ★任意のプロジェクト名を入力

    -----------------------
    Generating application:
    -----------------------
    Name: my-sam-api
    Runtime: nodejs24.x
    Architectures: x86_64
    Dependency Manager: npm
    Application Template: hello-world-typescript
    Output Directory: .
    Configuration file: my-sam-api/samconfig.toml

    Next steps can be found in the README file at my-sam-api/README.md


Commands you can use next
=========================
[*] Create pipeline: cd my-sam-api && sam pipeline init --bootstrap
[*] Validate SAM template: cd my-sam-api && sam validate
[*] Test Function in the Cloud: cd my-sam-api && sam sync --stack-name {stack-name} --watch

生成されたファイルのうち、主に触るのはこの2つです。

my-sam-api/
├── hello_world/
│   └── app.ts        ← Lambda関数
└── template.yaml     ← SAMテンプレート

※tsconfig.json も生成されています。(今回は基本設定のままのため編集しませんでした。) tsconfig.json は型定義のルールやコンパイル対象を制御するための設定ファイルです。 esbuild は sam build を行う際、「どのルールに従ってトランスパイルするか」 を判断するために tsconfig.json を参照します。

②ビルドとデプロイ

通常は、① で生成されたファイルをそのままデプロイ可能です。

sam build
sam deploy --guided

--guided を初回に実行すると対話式で設定できます。入力した内容は samconfig.toml に保存されるので、次回以降は sam deploy だけで デプロイが可能です。

詰まったポイント

その① デプロイ時に AccessDeniedException が出た

デプロイを実行したら、いきなりこんなエラーが出ました。

An error occurred (AccessDeniedException) when calling the CreateRole operation:
User: arn:aws:iam::xxxx:user/my-user is not authorized to perform: iam:CreateRole

原因

SAM は Lambda の実行ロールをデプロイ時に自動で作成します。そのため、デプロイを実行するIAMユーザーに iam:CreateRole の権限が必要です。Lambda の権限は渡していたのですが、IAM 操作の権限を渡していなかったのが原因でした。

解消方法

IAM Identity Center の「許可セット」にて、IAM Role を作成する権限を追加します。 今回は検証用のため、IAM のフル権限を付与しました。

iam:*

※ 許可する権限について、本番運用では最小権限に絞ってください。
 エラーメッセージに記載の権限が何を許可するものかを確認しながら追加していきましょう。


その② API を叩いたら 502 が返ってきた

デプロイは成功したので、エンドポイントを叩いてみると、レスポンスがありました。

curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/

レスポンス↓

{"message": "Internal server error"}

ステータスコードは 500が返ってきています。

確認ポイント

  1. Lambda のコードの問題かと思ってログを見ましたが、そもそも Lambda が呼び出されていませんでした

  2. APIGateway のログを確認すると、以下のエラーが発生していました。   ※ sam deploy した状態では、APIGateway のログ出力がOFFになっていたため、ONに変更して再度 API へリクエストしてログを出力しました。  

Execution failed due to configuration error: Invalid permissions on Lambda function

原因

API Gateway が Lambda を呼び出すには、Lambda 側に「API Gateway からの呼び出しを許可する」リソースベースポリシーが必要です。これが設定されていないと、API Gateway は Lambda を実行できません。

SAM の Events セクションを使ってデプロイすれば自動で設定されるはずなのですが、一度手動で設定をいじってしまったため、「リソースベースのポリシーステートメント」が壊れた状態になっていました。

Lambda コンソールの「設定」→「アクセス権限」→「リソースベースのポリシーステートメント」で確認できます。

解消方法

方法①: 手動で壊れてしまった権限をもとに戻します。
    変更した点が認識できている場合はこの方法で問題ありません。

方法②: スタックを一度削除して、template.yaml からクリーンにデプロイし直しました。

※ 通常設定やLambdaのコードを更新する場合は、sam delete は不要です。 手動で設定変更をしてしまっため、今回は sam delete を実行しました。
 sam delete をしてリソースを作り直す場合、リソースに当てられるID等が変わり、APIGateway に割り当てられるURLが変更になるため注意が必要です。

sam delete
sam build
sam deploy

再デプロイ後、Lambda のリソースベースポリシーを確認するとこのように設定されていました。

{
  "Version": "2012-10-17",
  "Id": "default",
  "Statement": [
    {
      "Sid": "my-sam-api-HelloWorldFunctionHelloWorldPermissionProd-xxxxxxxxxxxx",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:my-sam-api-HelloWorldFunction-xxxxxxxxxx",
      "Condition": {
        "ArnLike": {
          "AWS:SourceArn": "arn:aws:execute-api:ap-northeast-1:xxxxxxxxxxxx:xxxxxxxxxx/*/GET/hello"
        }
      }
    }
  ]
}

再度 curl を叩くと無事レスポンスが返ってきました。

{message: "hello world"}

おわりに

最終的に、初めに提示したtemplate.yamlapp.tsで稼働する簡単なAPIの動作を確認できました。

SAM を使えば API Gateway と Lambda の統合、およびリソースベースポリシーの設定まで template.yaml に書いた内容から自動でやってくれます。手動で設定を変えてしまうと、次からの更新が大変なので一度 SAM で作ったリソースはコンソールから直接触らないというのが教訓です。

室 海七大のプロフィール画像
室 海七大

筋トレが好きです。

シナジーマーケティング株式会社では一緒に働く仲間を募集しています。

参考