12
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS CDK入門 - Lambda × S3 署名URLでファイルアップロードAPIを作る

12
Last updated at Posted at 2026-05-28

この記事を書いた背景

ソーイでは、機械学習プロジェクトでの SageMaker AIの構築や、1案件で複数のAWSサービスをまとめて管理する場面が日常になってきました。マネジメントコンソールでぽちぽち作ると再現性が落ち、CloudFormation の YAML は記述量が多く保守がつらい——そこでAWS CDK を採用するケースが増えています。

本記事は、CDKに初めて触れる方向けにステップバイステップで触れるように紹介します。

はじめに

AWS CDK(Cloud Development Kit)は、AWSのインフラを「コード」で定義できるツールです。CloudFormationのYAMLを書く代わりに、Python・TypeScript・Java などのプログラミング言語でインフラを記述できます。

この記事では、CDKを初めて触る人向けに、Lambda関数で発行した署名URL(presigned URL)を使ってS3にファイルをアップロードするAPI を、段階的に組み立てていきます。

作るもの

シンプルですが、実務でも頻出のパターンです。Lambda経由でURLを発行することで、S3バケットを直接公開せずに済むのがポイントです。

この記事のゴール

  • CDKの基本サイクル(init / synth / diff / deploy / destroy)を体感する
  • スタックを分割する書き方を覚える
  • スタック間でリソースを参照する方法を知る
  • 動くサンプルを手元に残す

想定読者と前提

  • AWSアカウントを持っていて、マネジメントコンソールに触れたことがある
  • Pythonの基本文法が読める
  • CDKは初めての方

用意するもの

ツール バージョン目安 用途
Node.js 22 以上 CDK CLI の実行に必要
Python 3.10 以上 CDKコード・Lambdaコードの記述
AWS CLI v2 認証情報の設定と動作確認
AWSアカウント - デプロイ先

なぜ CDK を選ぶか

CloudFormation の YAML は、複数サービスをまたいだ構成になると一気に記述量が膨らみ、IAMロール定義などのボイラープレートで本筋が見えにくくなります。CDK ならプログラミング言語の力で

  • IAMポリシーを bucket.grant_put(fn) の1行で自動生成できる
  • ループや条件分岐、関数化で重複を排除できる
  • 型補完でタイポや必須プロパティの抜け漏れを防げる

これらの恩恵が、複数サービスをまとめて管理するケースほど大きく効いてきます。

なぜ Lambda Function URL を使うか

Lambda を外から呼ぶ手段としては API Gateway も有力ですが、本記事では Function URL を採用しています。理由は次の通りです。

  • 設定がシンプル: 1行で公開でき、CDKのコード量が最小
  • 追加コストがほぼゼロ: API Gateway のような呼び出し単位の課金がない
  • 入門の主役に最適: 概念を増やしすぎず、本筋(CDK の使い方)に集中できる

本番要件(認証、レート制御、WAF、カスタムドメインなど)が増えてきたら、API Gateway への移行を検討する流れがおすすめです。

第1章 環境構築

1-1. AWS CLI の認証情報を設定する

すでに設定済みなら飛ばしてください。

aws configure
# AWS Access Key ID: ********
# AWS Secret Access Key: ********
# Default region name: ap-northeast-1
# Default output format: json

確認:

aws sts get-caller-identity

自分のアカウントIDとユーザーARNが返ってくればOKです。

1-2. CDK CLI をインストールする

$ npm install -g aws-cdk
$ cdk --version
2.1121.0 (build 7abdd4e)

CDK本体はPython(あるいは他の言語)ですが、CLIはNode.jsで動きます。最初に戸惑うポイントなので押さえておきましょう。

1-3. CDK Bootstrap

CDKを使う前に、デプロイ先のアカウント/リージョンに「ブートストラップ」が必要です。これはCDKがデプロイ時に使うS3バケットやIAMロールを事前に作っておく作業で、アカウント × リージョンごとに一度だけ実行します。

$ cdk bootstrap aws://<アカウントID>/ap-northeast-1
 ⏳  Bootstrapping environment aws://<アカウントID>/ap-northeast-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.
CDKToolkit: creating CloudFormation changeset...
 ✅  Environment aws://<アカウントID>/ap-northeast-1 bootstrapped.

アカウントIDは aws sts get-caller-identity で確認できます。

1-4. プロジェクトを作成する

作業用のディレクトリを作り、cdk init で雛形を生成します。

$ mkdir cdk-upload-api && cd cdk-upload-api
$ cdk init app --language python
Applying project template app for python

...

✅ All done!

生成後、仮想環境を有効化して依存をインストールします。

source .venv/bin/activate
pip install -r requirements.txt

ディレクトリ構成は次のようになります(一部抜粋)。

cdk-upload-api/
├── app.py                       # CDKのエントリポイント
├── cdk_upload_api/
│   ├── __init__.py
│   └── cdk_upload_api_stack.py  # デフォルトのスタック
├── cdk.json
└── requirements.txt

app.py がCDKアプリの入り口、cdk_upload_api_stack.py がスタック定義です。これから章ごとにこれらを書き換えていきます。

第2章 最小構成 - S3バケットを作る

まずはS3バケットだけのスタックを作って、CDKのデプロイサイクルに慣れます。

2-1. スタックを書き換える

cdk_upload_api/cdk_upload_api_stack.pys3_stack.py にリネームし、中身を次のように書き換えます。

# cdk_upload_api/s3_stack.py
from aws_cdk import (
    Stack,
    RemovalPolicy,
    aws_s3 as s3,
)
from constructs import Construct


class S3Stack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        self.upload_bucket = s3.Bucket(
            self,
            "UploadBucket",
            removal_policy=RemovalPolicy.DESTROY,  # cdk destroyで消えるように
            auto_delete_objects=True,              # 中身ごと削除を許可
            block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
        )

ポイント:

  • self.upload_bucket として保持しているのは、あとで別スタックから参照するため
  • RemovalPolicy.DESTROYauto_delete_objects=True学習用 の設定です。本番では既定の RETAIN のままにしてください。DESTROY のまま運用すると、スタック削除時にデータが消えて取り返しがつかなくなります
  • パブリックアクセスは全ブロック。アップロードは署名URL経由で行います

2-2. app.py を更新する

# app.py
import aws_cdk as cdk
from cdk_upload_api.s3_stack import S3Stack

app = cdk.App()
S3Stack(app, "UploadApiS3Stack")
app.synth()

2-3. CDKの基本3コマンド

CDKは「コードを書く → デプロイ前に内容を確認 → 反映する」のサイクルが基本です。

cdk synth — CloudFormationテンプレートを生成

$ cdk synth

Resources:
  UploadBucketD2C1DA78:
    Type: AWS::S3::Bucket
    Properties:
    # ... テンプレートが出力される

CDKコードから生成されたCloudFormationテンプレート(YAML)が標準出力に表示されます。「裏で何が作られるのか」を確認できます。

cdk diff — 既存環境との差分を確認

$ cdk diff
start: Building UploadApiS3Stack Template
success: Built UploadApiS3Stack Template
start: Publishing UploadApiS3Stack Template (current_account-current_region-85cfb58f)
success: Published UploadApiS3Stack Template (current_account-current_region-85cfb58f)
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --method=template to use a less accurate but faster template-only diff)

Stack UploadApiS3Stack
IAM Statement Changes
┌───┬─────────────────────────────────┬────────┬─────────────────────────────────┬───────────────────────────────────┬───────────┐
│   │ Resource                        │ Effect │ Action                          │ Principal                         │ Condition │
├───┼─────────────────────────────────┼────────┼─────────────────────────────────┼───────────────────────────────────┼───────────┤
│ + │ ${Custom::S3AutoDeleteObjectsCu │ Allow  │ sts:AssumeRole                  │ Service:lambda.amazonaws.com      │           │
│   │ stomResourceProvider/Role.Arn}  │        │                                 │                                   │           │
├───┼─────────────────────────────────┼────────┼─────────────────────────────────┼───────────────────────────────────┼───────────┤
│ + │ ${UploadBucket.Arn}             │ Allow  │ s3:DeleteObject*                │ AWS:${Custom::S3AutoDeleteObjects │           │
│   │ ${UploadBucket.Arn}/*           │        │ s3:GetBucket*                   │ CustomResourceProvider/Role.Arn}  │           │
│   │                                 │        │ s3:List*                        │                                   │           │
│   │                                 │        │ s3:PutBucketPolicy              │                                   │           │
└───┴─────────────────────────────────┴────────┴─────────────────────────────────┴───────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬─────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────┐
│   │ Resource                                                    │ Managed Policy ARN                                           │
├───┼─────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────┤
│ + │ ${Custom::S3AutoDeleteObjectsCustomResourceProvider/Role}{"Fn::Sub":"arn:${AWS::Partition}:iam::aws:policy/service-ro │
│   │                                                             │ le/AWSLambdaBasicExecutionRole"}                             │
└───┴─────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}

Conditions
[+] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":...

Resources
[+] AWS::S3::Bucket UploadBucket UploadBucketD2C1DA78
...



✨  Number of stacks with differences: 1

すでにデプロイ済みのスタックと比較して、何が増減するかを表示してくれます。毎回デプロイ前に確認すると事故が減ります。

cdk deploy — 実際にデプロイ

$ $ cdk deploy

✨  Synthesis time: 5.97s
....
"--require-approval" is enabled and stack includes security-sensitive updates: Do you wish to deploy these changes? (y/n) y

 ✅  UploadApiS3Stack

✨  Deployment time: 38.39s

IAM関連の変更がある場合は確認を求められるので y で進めます。

デプロイが終わったら、AWSマネジメントコンソールのS3画面でバケットが作られていることを確認しましょう。

💡 コラム: CDKとCloudFormationの関係
CDKは「最終的にCloudFormationテンプレートを生成して、それをデプロイするツール」です。cdk synth で見えるYAMLが実体。なので困ったら cdk synth でテンプレートを見ると理解が早まります。

デプロイまで終わるとコンソールからバケット作成を確認できます。

CleanShot 2026-05-25 at 20.23.51@2x.png

第3章 Lambda関数を追加する

S3バケットだけでは何もできないので、署名URLを発行するLambdaを追加します。ここでは 別スタックに分けて 作り、CDKのスタック分割パターンも学びます。

3-1. Lambdaのソースを用意する

プロジェクトルートに lambda/ ディレクトリを作り、ハンドラを置きます。

mkdir lambda
# lambda/handler.py
import json
import os
import uuid
import boto3
from botocore.config import Config

BUCKET_NAME = os.environ["BUCKET_NAME"]
BUCKET_REGION = os.environ["BUCKET_REGION"]
URL_EXPIRES_IN = 300  # 秒

s3 = boto3.client(
    "s3",
    region_name=BUCKET_REGION,
    endpoint_url=f"https://s3.{BUCKET_REGION}.amazonaws.com",
    config=Config(signature_version="s3v4"),
)


def handler(event, context):
    # クエリパラメータからファイル名を取得(無ければUUIDを生成)
    qs = event.get("queryStringParameters") or {}
    filename = qs.get("filename", f"{uuid.uuid4()}.bin")
    key = f"uploads/{filename}"

    url = s3.generate_presigned_url(
        ClientMethod="put_object",
        Params={"Bucket": BUCKET_NAME, "Key": key},
        ExpiresIn=URL_EXPIRES_IN,
        HttpMethod="PUT",
    )

    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({
            "uploadUrl": url,
            "key": key,
            "expiresIn": URL_EXPIRES_IN,
        }),
    }

boto3.client(..., region_name=BUCKET_REGION, endpoint_url=..., config=Config(signature_version="s3v4")) で、バケットのリージョン、S3エンドポイント、署名バージョン4を明示しています。署名URLのホストが s3.amazonaws.com のようなグローバルエンドポイントになると、PUT時にS3から TemporaryRedirect が返ることがあります。

3-2. LambdaStack を作る

cdk_upload_api/lambda_stack.py を新規作成します。

# cdk_upload_api/lambda_stack.py
from aws_cdk import (
    Stack,
    Duration,
    aws_lambda as _lambda,
    aws_s3 as s3,
)
from constructs import Construct


class LambdaStack(Stack):
    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        upload_bucket: s3.IBucket,
        **kwargs,
    ) -> None:
        super().__init__(scope, construct_id, **kwargs)

        self.presign_fn = _lambda.Function(
            self,
            "PresignFn",
            runtime=_lambda.Runtime.PYTHON_3_14,
            handler="handler.handler",
            code=_lambda.Code.from_asset("lambda"),
            environment={
                "BUCKET_NAME": upload_bucket.bucket_name,
                "BUCKET_REGION": self.region,
            },
            timeout=Duration.seconds(10),
        )

        # S3バケットへのPUT権限を付与(IAMポリシーが自動生成される)
        upload_bucket.grant_put(self.presign_fn)

ここがCDKの強みです。upload_bucket.grant_put(self.presign_fn) の1行で、必要なIAMポリシーを自動生成してくれます。CloudFormationで書くなら数十行になる箇所が、CDKでは1行です。

3-3. app.py でスタックを繋ぐ

# app.py
import aws_cdk as cdk
from cdk_upload_api.s3_stack import S3Stack
from cdk_upload_api.lambda_stack import LambdaStack

app = cdk.App()

s3_stack = S3Stack(app, "UploadApiS3Stack")
LambdaStack(
    app,
    "UploadApiLambdaStack",
    upload_bucket=s3_stack.upload_bucket,
)

app.synth()

s3_stack.upload_bucketLambdaStack のコンストラクタに渡しています。これでCDKが「LambdaStack は S3Stack に依存している」と認識し、デプロイ順序も自動で解決してくれます。

3-4. デプロイ

$ cdk diff
start: Building UploadApiS3Stack Template
success: Built UploadApiS3Stack Template
...

$ cdk deploy --all

✨  Synthesis time: 5.76s

UploadApiS3Stack
UploadApiLambdaStack: start: Building PresignFn/Code
UploadApiLambdaStack: success: Built PresignFn/Code
UploadApiS3Stack: creating CloudFormation changeset...

 ✅  UploadApiLambdaStack

✨  Deployment time: 59.12s

--all は全スタックをまとめてデプロイするオプションです。

コンソール画面でLambda関数がデプロイされたことを確認できます。
CleanShot 2026-05-25 at 20.35.43@2x.png


第4章 HTTPエンドポイントを追加する(Lambda Function URL)

Lambdaを外から呼ぶには、API GatewayかLambda Function URLが必要です。Function URLは設定がシンプルで、追加コストもないので入門に最適です。

4-1. LambdaStack に Function URL を追加

lambda_stack.py の末尾に追記します。

# cdk_upload_api/lambda_stack.py(追記)
from aws_cdk import CfnOutput
# ... 上の import に追加

# __init__ の最後に追記
fn_url = self.presign_fn.add_function_url(
    auth_type=_lambda.FunctionUrlAuthType.NONE,  # 認証なし(学習用)
    cors=_lambda.FunctionUrlCorsOptions(
        allowed_origins=["*"],
        allowed_methods=[_lambda.HttpMethod.GET, _lambda.HttpMethod.POST],
    ),
)

CfnOutput(self, "FunctionUrl", value=fn_url.url)

auth_type=NONE誰でも叩ける状態 です。学習用なので一旦これでよいですが、本番では AWS_IAM にするか、Lambdaコード側でトークン検証するなど対策を入れてください。

CfnOutput を使うと、デプロイ後にコンソール出力で URL が表示されます。

4-2. デプロイして URL を取得

# ログは省略
$ cdk diff
$ cdk deploy --all

成功すると、出力にこんな行が出ます。

UploadApiLambdaStack.FunctionUrl = https://xxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/

このURLを以降の動作確認で使います。

4-3. curl で動かしてみる

① 署名URLをもらう

FN_URL="https://xxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/"
curl "${FN_URL}?filename=hello.txt"

レスポンス例:

{
  "uploadUrl": "https://upload-bucket-xxxx.s3.ap-northeast-1.amazonaws.com/uploads/hello.txt?X-Amz-Algorithm=...",
  "key": "uploads/hello.txt",
  "expiresIn": 300
}

② 署名URLにPUTでファイルをアップロード

echo "hello from cdk" > hello.txt
UPLOAD_URL="(上で返ってきたuploadUrl)"
curl -X PUT --upload-file hello.txt "${UPLOAD_URL}"

成功すると何も表示されずに終了することがあります。TemporaryRedirect が返る場合は、署名URLのS3エンドポイントがバケットのリージョンと合っていない可能性が高いので、Lambdaコードの region_nameendpoint_url、環境変数 BUCKET_REGION を確認してください。

③ S3にファイルが入ったか確認

aws s3 ls s3://(バケット名)/uploads/

hello.txt が見えればゴールです。

コンソール画面でも当然確認できます。
CleanShot 2026-05-25 at 21.14.58@2x.png


第5章 片付けとまとめ

5-1. リソースを削除する

学習用に作ったリソースはきっちり消しましょう。

cdk destroy --all

y で確認して進めます。S3バケットも auto_delete_objects=True を設定しているので、中身ごと消えます。

5-2. ハマりやすいポイント

実際にやってみてつまずきそうなポイントを置いておきます。

  • cdk bootstrap を忘れている: 「Stack uses assets, so the toolkit stack must be deployed」と出たら未実行のサイン
  • リージョンの取り違え: AWS CLI のデフォルトリージョンと、コンソールで見ているリージョンがズレている。cdk.jsonenv= 指定でも明示できる
  • S3の TemporaryRedirect: 署名URLがバケットのリージョン用エンドポイントになっていない時に出ます。S3クライアントに region_nameendpoint_url を指定し、デプロイ後に新しい署名URLを取り直してください
  • CORS: ブラウザから叩く場合は cors 設定が必須。curl だけなら不要
  • grant_putgrant_read_write: 今回はアップロードだけなので grant_put で十分。最小権限の原則

おわりに

CDKは「IaCを書く」ハードルを大きく下げてくれるツールです。とくに bucket.grant_put(fn) のような意図がそのままコードになり、権限管理から解放してくれます。

今回作ったコードは、そのまま実務の足場としても使えます。ぜひ手を動かして、自分の手に馴染ませてみてください。


完成版のディレクトリ構成

cdk-upload-api/
├── app.py
├── cdk.json
├── requirements.txt
├── cdk_upload_api/
│   ├── __init__.py
│   ├── s3_stack.py
│   └── lambda_stack.py
└── lambda/
    └── handler.py

参考リンク


お知らせ

技術ブログを週1〜2本更新中、ソーイをフォローして最新記事をチェック!
https://qiita.com/organizations/sewii

12
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?