<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>サーバーレス - CayTech Lab</title>
	<atom:link href="https://caymezon.com/tag/%e3%82%b5%e3%83%bc%e3%83%90%e3%83%bc%e3%83%ac%e3%82%b9/feed/" rel="self" type="application/rss+xml" />
	<link>https://caymezon.com</link>
	<description></description>
	<lastBuildDate>Sun, 08 Mar 2026 05:02:41 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://caymezon.com/wp-content/uploads/2026/01/cropped-CayTechLab-32x32.jpg</url>
	<title>サーバーレス - CayTech Lab</title>
	<link>https://caymezon.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<atom:link rel='hub' href='https://caymezon.com/?pushpress=hub'/>
	<item>
		<title>AWSコンソールでチャットボットログAPIを構築する手順【API Gateway + Lambda + CloudWatch Logs ハンズオン / SAM版との比較付き】</title>
		<link>https://caymezon.com/aws-handson-console-chatbot-log-api/</link>
					<comments>https://caymezon.com/aws-handson-console-chatbot-log-api/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 08 Mar 2026 05:02:41 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[APIGateway]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[AWSコンソール]]></category>
		<category><![CDATA[CloudWatchLogs]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[MetricFilter]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[カスタムメトリクス]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[チャットボット]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[初心者]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20257</guid>

					<description><![CDATA[<p>目次 はじめにキーワード解説使用するAWSサービス全体の作業順序① CloudWatch Log Group を作成する② CloudWatch Metric Filter を作成するフィルターパターンの設定メトリクスの [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-console-chatbot-log-api/">AWSコンソールでチャットボットログAPIを構築する手順【API Gateway + Lambda + CloudWatch Logs ハンズオン / SAM版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-2" checked><label class="toc-title" for="toc-checkbox-2">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">キーワード解説</a></li><li><a href="#toc3" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc4" tabindex="0">全体の作業順序</a></li><li><a href="#toc5" tabindex="0">① CloudWatch Log Group を作成する</a></li><li><a href="#toc6" tabindex="0">② CloudWatch Metric Filter を作成する</a><ol><li><a href="#toc7" tabindex="0">フィルターパターンの設定</a></li><li><a href="#toc8" tabindex="0">メトリクスの設定</a></li></ol></li><li><a href="#toc9" tabindex="0">③ Lambda 関数を作成する</a><ol><li><a href="#toc10" tabindex="0">タイムアウトの設定</a></li><li><a href="#toc11" tabindex="0">環境変数の設定</a></li><li><a href="#toc12" tabindex="0">コードの入力</a></li></ol></li><li><a href="#toc13" tabindex="0">④ Lambda の実行ロールに権限を追加する</a><ol><li><a href="#toc14" tabindex="0">CloudWatch Logs と CloudWatch の権限をまとめて追加する</a></li></ol></li><li><a href="#toc15" tabindex="0">⑤ API Gateway REST API を作成する</a><ol><li><a href="#toc16" tabindex="0">5-1. API の作成</a></li><li><a href="#toc17" tabindex="0">5-2. /logs リソースを作成する</a></li><li><a href="#toc18" tabindex="0">5-3. POST メソッドを作成する（ログ書き込み）</a></li><li><a href="#toc19" tabindex="0">5-4. GET メソッドを作成する（ログ取得）</a></li><li><a href="#toc20" tabindex="0">5-5. API をデプロイする</a></li></ol></li><li><a href="#toc21" tabindex="0">⑥ 動作テスト</a><ol><li><a href="#toc22" tabindex="0">事前準備: API URL を変数に設定する</a></li><li><a href="#toc23" tabindex="0">テスト 1: ログを記録する（POST /logs）</a></li><li><a href="#toc24" tabindex="0">テスト 2: エラーログを記録する（Metric Filter のテスト）</a></li><li><a href="#toc25" tabindex="0">テスト 3: ログを取得する（GET /logs）</a></li><li><a href="#toc26" tabindex="0">CloudWatch でログを確認する</a></li><li><a href="#toc27" tabindex="0">CloudWatch Logs Insights でクエリを実行する（任意）</a></li><li><a href="#toc28" tabindex="0">カスタムメトリクスを確認する（任意）</a></li></ol></li><li><a href="#toc29" tabindex="0">⑦ リソースの削除</a><ol><li><a href="#toc30" tabindex="0">1. API Gateway を削除する</a></li><li><a href="#toc31" tabindex="0">2. Lambda 関数を削除する</a></li><li><a href="#toc32" tabindex="0">3. CloudWatch Log Group を削除する</a></li><li><a href="#toc33" tabindex="0">4. IAM ロールを削除する（任意）</a></li><li><a href="#toc34" tabindex="0">5. CloudWatch Lambda ロググループを削除する（任意）</a></li></ol></li><li><a href="#toc35" tabindex="0">SAM との対比</a></li><li><a href="#toc36" tabindex="0">トラブルシューティング</a></li><li><a href="#toc37" tabindex="0">まとめ</a><ol><li><a href="#toc38" tabindex="0">コンソール版で実感できたポイント</a></li></ol></li><li><a href="#toc39" tabindex="0">コンソール版と SAM 版を比較してみる</a></li><li><a href="#toc40" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「チャットボットの会話ログをAPIで受け取り、AWSに蓄積したい」——そんな要件を実現する構成が <strong>API Gateway + Lambda + CloudWatch Logs</strong> の組み合わせです。</p>
<p>この記事では、<strong>AWSコンソールのみ</strong>を使って、チャットボットの会話ログを記録・取得するREST APIをゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">クライアント
  ↓ POST /logs（会話ログを送信）
API Gateway（REST API）
  ↓ Lambda プロキシ統合
Lambda（ChatLogFunction / Python 3.12）
  ↓ boto3 logs.put_log_events()
CloudWatch Log Group（/chatbot/chatbot-logs）
  ↓ Metric Filter（level = "error" のログを検出）
CloudWatch カスタムメトリクス（ChatErrorCount）</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li>API Gateway REST API のリソース・メソッド作成と Lambda プロキシ統合</li>
<li>CloudWatch Logs カスタムロググループへのログ書き込み（<code>put_log_events</code>）</li>
<li>Metric Filter によるエラーログの自動メトリクス変換</li>
<li>CloudWatch Logs Insights でのログクエリ実行</li>
</ul>
<hr>
<blockquote>
<p><strong>この記事は <a href="#">SAM版ハンズオン</a> の比較記事です。</strong><br />コンソール操作でAPI Gateway・Lambda・CloudWatch Logsの連携を視覚的に学び、SAM と何が違うのかを比較したい方向けです。</p>
</blockquote>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>CloudWatch Log Group</strong></td>
<td>ログの論理的なまとまり。今回はチャットログ専用グループを作成する</td>
</tr>
<tr>
<td><strong>Log Stream</strong></td>
<td>Log Group の中の個別のログストリーム。今回はセッションIDごとに作成する</td>
</tr>
<tr>
<td><strong>put_log_events</strong></td>
<td>boto3 でカスタムロググループにログを書き込む API</td>
</tr>
<tr>
<td><strong>Metric Filter</strong></td>
<td>ロググループに届いたログを JSON フィルターでマッチさせ、カスタムメトリクスを自動生成する仕組み</td>
</tr>
<tr>
<td><strong>カスタムメトリクス</strong></td>
<td>CloudWatch に独自のメトリクスを記録する機能。アラーム設定やダッシュボードに使える</td>
</tr>
<tr>
<td><strong>Lambda プロキシ統合</strong></td>
<td>API Gateway が HTTP リクエスト全体を Lambda に渡す最もシンプルな統合方式</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc3">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>API Gateway</strong></td>
<td>REST APIのエンドポイント（POST /logs, GET /logs）</td>
<td>月100万リクエストまで無料</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>ログの書き込み・取得処理（Python 3.12）</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>チャットログの保存・クエリ</td>
<td>月5GBまで無料</td>
</tr>
<tr>
<td><strong>CloudWatch メトリクス</strong></td>
<td>エラーログのカスタムメトリクス化</td>
<td>月10件まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda の実行権限管理</td>
<td>無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">全体の作業順序</span></h2>
<pre><code class="language-plaintext">① CloudWatch Log Group を作成する
      ↓
② CloudWatch Metric Filter を作成する（error ログ → メトリクス）
      ↓
③ Lambda 関数を作成する（コード・環境変数・タイムアウト設定）
      ↓
④ Lambda の実行ロールに権限を追加する
  （CloudWatch Logs: 書き込み・読み取り / CloudWatch: PutMetricData）
      ↓
⑤ API Gateway REST API を作成する（POST /logs, GET /logs）
      ↓
⑥ 動作テスト（curl でログ送信 → コンソールで確認）
      ↓
⑦ リソースの削除</code></pre>
<blockquote>
<p><strong>Log Group を先に作る理由：</strong><br />Metric Filter の作成にはロググループが必要なため、最初に作成します。</p>
</blockquote>
<hr>
<h2><span id="toc5">① CloudWatch Log Group を作成する</span></h2>
<p><strong>AWSコンソール → CloudWatch → ロググループ → 「ロググループの作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>ロググループ名</td>
<td><code>/chatbot/chatbot-logs</code></td>
</tr>
<tr>
<td>保持期間</td>
<td><strong>7日間</strong></td>
</tr>
</tbody>
</table>
<p>「作成」をクリック。</p>
<p><strong>控えておく情報:</strong></p>
<ul>
<li><strong>ロググループ名</strong>: <code>/chatbot/chatbot-logs</code></li>
</ul>
<hr>
<h2><span id="toc6">② CloudWatch Metric Filter を作成する</span></h2>
<p>エラーログを自動的にカスタムメトリクスに変換するフィルターを設定します。</p>
<p><strong>CloudWatch → ロググループ → <code>/chatbot/chatbot-logs</code> → 「メトリクスフィルター」タブ → 「メトリクスフィルターを作成」</strong></p>
<h3><span id="toc7">フィルターパターンの設定</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>フィルターパターン</td>
<td><code>{ $.level = &quot;error&quot; }</code></td>
</tr>
</tbody>
</table>
<p>「パターンのテスト」でテストデータを入力して確認できます（任意）。「次へ」をクリック。</p>
<h3><span id="toc8">メトリクスの設定</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>フィルター名</td>
<td><code>ChatErrorFilter</code></td>
</tr>
<tr>
<td>メトリクスの名前空間</td>
<td><code>ChatBot/chatbot-logs</code></td>
</tr>
<tr>
<td>メトリクス名</td>
<td><code>ChatErrorCount</code></td>
</tr>
<tr>
<td>メトリクス値</td>
<td><code>1</code></td>
</tr>
<tr>
<td>デフォルト値</td>
<td><code>0</code></td>
</tr>
</tbody>
</table>
<p>「次へ」→「メトリクスフィルターを作成」。</p>
<blockquote>
<p><strong>Metric Filter の仕組み:</strong><br />CloudWatch Logs に届いたログが <code>{ $.level = &quot;error&quot; }</code> にマッチするたびに、<br /><code>ChatBot/chatbot-logs</code> 名前空間の <code>ChatErrorCount</code> に <code>1</code> が加算されます。<br />ログに <code>{&quot;level&quot;: &quot;error&quot;, ...}</code> という JSON が含まれていればマッチします。</p>
</blockquote>
<hr>
<h2><span id="toc9">③ Lambda 関数を作成する</span></h2>
<p><strong>AWSコンソール → Lambda → 「関数の作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>作成方法</td>
<td>一から作成</td>
</tr>
<tr>
<td>関数名</td>
<td><code>ChatLogFunction</code></td>
</tr>
<tr>
<td>ランタイム</td>
<td><strong>Python 3.12</strong></td>
</tr>
<tr>
<td>アーキテクチャ</td>
<td>x86_64</td>
</tr>
<tr>
<td>実行ロール</td>
<td>「基本的な Lambda アクセス権限で新しいロールを作成」（デフォルト）</td>
</tr>
</tbody>
</table>
<p>「関数の作成」をクリック。</p>
<h3><span id="toc10">タイムアウトの設定</span></h3>
<p><strong>「設定」タブ → 「一般設定」→「編集」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>タイムアウト</td>
<td><strong>10</strong> 秒</td>
</tr>
</tbody>
</table>
<p>「保存」をクリック。</p>
<h3><span id="toc11">環境変数の設定</span></h3>
<p><strong>「設定」タブ → 「環境変数」→「編集」→「環境変数を追加」</strong></p>
<table>
<thead>
<tr>
<th>キー</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>LOG_GROUP_NAME</code></td>
<td><code>/chatbot/chatbot-logs</code></td>
</tr>
</tbody>
</table>
<p>「保存」をクリック。</p>
<h3><span id="toc12">コードの入力</span></h3>
<p>「コード」タブ → <code>lambda_function.py</code> を開いて既存の内容を全て置き換えます。</p>
<pre><code class="language-python">import json
import os
import time
import uuid

import boto3
from botocore.exceptions import ClientError

logs_client = boto3.client("logs")
cloudwatch = boto3.client("cloudwatch")

LOG_GROUP_NAME = os.environ["LOG_GROUP_NAME"]
METRIC_NAMESPACE = "ChatBot/" + LOG_GROUP_NAME.split("/")[-1]  # "ChatBot/chatbot-logs"


def lambda_handler(event, context):
    http_method = event.get("httpMethod", "")
    resource = event.get("resource", "/")

    if http_method == "POST" and resource == "/logs":
        return post_log(event)
    elif http_method == "GET" and resource == "/logs":
        return get_logs(event)
    else:
        return _response(404, {"error": "Not Found"})


def post_log(event):
    try:
        body = json.loads(event.get("body") or "{}")
    except json.JSONDecodeError:
        return _response(400, {"error": "Invalid JSON"})

    session_id = body.get("session_id") or str(uuid.uuid4())
    user_id = body.get("user_id", "anonymous")
    message = body.get("message", "")
    response_text = body.get("response", "")
    level = body.get("level", "info")

    log_entry = {
        "session_id": session_id,
        "user_id": user_id,
        "message": message,
        "response": response_text,
        "level": level,
    }

    log_stream_name = f"session/{session_id}"
    _ensure_log_stream(log_stream_name)  # ストリームを作成（既存なら無視）
    logs_client.put_log_events(
        logGroupName=LOG_GROUP_NAME,
        logStreamName=log_stream_name,
        logEvents=[
            {
                "timestamp": int(time.time() * 1000),  # ミリ秒単位の Unix 時間
                "message": json.dumps(log_entry, ensure_ascii=False),
            }
        ],
    )

    cloudwatch.put_metric_data(
        Namespace=METRIC_NAMESPACE,
        MetricData=[
            {
                "MetricName": "MessageCount",
                "Dimensions": [{"Name": "Level", "Value": level}],
                "Value": 1,
                "Unit": "Count",
            }
        ],
    )

    return _response(200, {
        "session_id": session_id,
        "log_stream": log_stream_name,
        "message": "ログを記録した",
    })


def get_logs(event):
    params = event.get("queryStringParameters") or {}
    session_id = params.get("session_id")
    limit = min(int(params.get("limit", "20")), 100)

    if not session_id:
        return _response(400, {"error": "session_id クエリパラメーターは必須"})

    log_stream_name = f"session/{session_id}"

    try:
        resp = logs_client.get_log_events(
            logGroupName=LOG_GROUP_NAME,
            logStreamName=log_stream_name,
            limit=limit,
            startFromHead=False,  # 最新のログから取得
        )
        events = []
        for e in resp["events"]:
            try:
                msg = json.loads(e["message"])
            except json.JSONDecodeError:
                msg = e["message"]
            events.append({"timestamp_ms": e["timestamp"], "log": msg})
    except ClientError as e:
        if e.response["Error"]["Code"] == "ResourceNotFoundException":
            events = []  # ログストリームが存在しない場合は空リストを返す
        else:
            raise

    return _response(200, {
        "session_id": session_id,
        "log_count": len(events),
        "logs": events,
    })


def _ensure_log_stream(log_stream_name: str) -&gt; None:
    try:
        logs_client.create_log_stream(
            logGroupName=LOG_GROUP_NAME,
            logStreamName=log_stream_name,
        )
    except ClientError as e:
        if e.response["Error"]["Code"] != "ResourceAlreadyExistsException":
            raise  # 既存ストリームは無視、それ以外のエラーは再送出


def _response(status_code: int, body: dict) -&gt; dict:
    return {
        "statusCode": status_code,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps(body, ensure_ascii=False),
    }</code></pre>
<p>「Deploy」ボタンをクリックしてコードを保存します。</p>
<hr>
<h2><span id="toc13">④ Lambda の実行ロールに権限を追加する</span></h2>
<p>デフォルトの実行ロールは CloudWatch Logs への書き込み（Lambdaの実行ログ出力）のみです。<br />今回はカスタムロググループへの書き込み・読み取りと、カスタムメトリクスへの書き込み権限を追加します。</p>
<p><strong>Lambda → <code>ChatLogFunction</code> → 「設定」タブ → 「アクセス権限」→ 実行ロール名のリンクをクリック</strong></p>
<p>（例: <code>ChatLogFunction-role-XXXX</code>）→ IAM コンソールのロール画面が開きます。</p>
<h3><span id="toc14">CloudWatch Logs と CloudWatch の権限をまとめて追加する</span></h3>
<p>「許可を追加」→「インラインポリシーを作成」→「JSON」タブを選択して以下を入力します。</p>
<pre><code class="language-json">{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:GetLogEvents",
        "logs:DescribeLogStreams"
      ],
      "Resource": [
        "arn:aws:logs:ap-northeast-1:YOUR_ACCOUNT_ID:log-group:/chatbot/chatbot-logs",
        "arn:aws:logs:ap-northeast-1:YOUR_ACCOUNT_ID:log-group:/chatbot/chatbot-logs:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "cloudwatch:PutMetricData",
      "Resource": "*"
    }
  ]
}</code></pre>
<blockquote>
<p><code>YOUR_ACCOUNT_ID</code> を自分の AWS アカウント ID（12桁）に置き換えてください。<br />アカウント ID は <code>aws sts get-caller-identity</code> で確認できます。</p>
</blockquote>
<p>「次へ」→ ポリシー名: <code>ChatLogPolicy</code> → 「ポリシーを作成」。</p>
<blockquote>
<p><strong>なぜ 2 種類の権限が必要か:</strong></p>
<ul>
<li><code>logs:PutLogEvents</code> — <code>put_log_events()</code> でカスタムロググループにログを書き込む</li>
<li><code>logs:GetLogEvents</code> — <code>get_log_events()</code> でログを読み取る</li>
<li><code>cloudwatch:PutMetricData</code> — <code>put_metric_data()</code> でカスタムメトリクスを記録する</li>
</ul>
</blockquote>
<hr>
<h2><span id="toc15">⑤ API Gateway REST API を作成する</span></h2>
<h3><span id="toc16">5-1. API の作成</span></h3>
<p><strong>AWSコンソール → API Gateway → 「APIを作成」→ 「REST API」→「構築」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>API タイプ</td>
<td>REST API（WebSocket ではない方）</td>
</tr>
<tr>
<td>API 名</td>
<td><code>ChatLogAPI</code></td>
</tr>
<tr>
<td>エンドポイントタイプ</td>
<td>リージョン</td>
</tr>
</tbody>
</table>
<p>「API を作成」をクリック。</p>
<h3><span id="toc17">5-2. /logs リソースを作成する</span></h3>
<p><strong>「リソース」→「リソースを作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>リソース名</td>
<td><code>logs</code></td>
</tr>
<tr>
<td>リソースパス</td>
<td><code>/logs</code></td>
</tr>
</tbody>
</table>
<p>「リソースを作成」をクリック。</p>
<h3><span id="toc18">5-3. POST メソッドを作成する（ログ書き込み）</span></h3>
<p><code>/logs</code> リソースを選択した状態で「メソッドを作成」。</p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>メソッドタイプ</td>
<td><strong>POST</strong></td>
</tr>
<tr>
<td>統合タイプ</td>
<td><strong>Lambda 関数</strong></td>
</tr>
<tr>
<td>Lambda プロキシ統合</td>
<td><strong>有効（チェックを入れる）</strong></td>
</tr>
<tr>
<td>Lambda 関数</td>
<td><code>ChatLogFunction</code></td>
</tr>
</tbody>
</table>
<p>「メソッドを作成」をクリック。</p>
<h3><span id="toc19">5-4. GET メソッドを作成する（ログ取得）</span></h3>
<p>同様に <code>GET</code> メソッドを追加します。設定は POST と同じ（Lambda 関数: <code>ChatLogFunction</code>）。</p>
<h3><span id="toc20">5-5. API をデプロイする</span></h3>
<p><strong>「API をデプロイ」→</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>ステージ</td>
<td>「新しいステージ」</td>
</tr>
<tr>
<td>ステージ名</td>
<td><code>Prod</code></td>
</tr>
</tbody>
</table>
<p>「デプロイ」をクリック。</p>
<p><strong>デプロイ後に表示される URL を控えておきます:</strong></p>
<pre><code class="language-plaintext">https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod</code></pre>
<hr>
<h2><span id="toc21">⑥ 動作テスト</span></h2>
<h3><span id="toc22">事前準備: API URL を変数に設定する</span></h3>
<pre><code class="language-cmd">set API_URL=https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod</code></pre>
<blockquote>
<p><code>XXXXXXXXXX</code> を ⑤ でデプロイ後に表示された URL の値に置き換えてください。</p>
</blockquote>
<hr>
<h3><span id="toc23">テスト 1: ログを記録する（POST /logs）</span></h3>
<pre><code class="language-cmd">curl -X POST "%API_URL%/logs" ^
  -H "Content-Type: application/json" ^
  -d "{\"session_id\": \"session-001\", \"user_id\": \"user001\", \"message\": \"今日の天気は？\", \"response\": \"東京は晴れです\", \"level\": \"info\"}"</code></pre>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{
  "session_id": "session-001",
  "log_stream": "session/session-001",
  "message": "ログを記録した"
}</code></pre>
<p><!-- ![POST /logs のレスポンス確認](images/post-logs-response.jpg) --></p>
<hr>
<h3><span id="toc24">テスト 2: エラーログを記録する（Metric Filter のテスト）</span></h3>
<pre><code class="language-cmd">curl -X POST "%API_URL%/logs" ^
  -H "Content-Type: application/json" ^
  -d "{\"session_id\": \"session-001\", \"user_id\": \"user001\", \"message\": \"注文履歴を見せて\", \"response\": \"エラーが発生しました\", \"level\": \"error\"}"</code></pre>
<blockquote>
<p>このリクエスト後、CloudWatch メトリクス <code>ChatBot/chatbot-logs &gt; ChatErrorCount</code> が +1 されます。<br />反映まで数分かかることがあります。</p>
</blockquote>
<hr>
<h3><span id="toc25">テスト 3: ログを取得する（GET /logs）</span></h3>
<pre><code class="language-cmd">curl "%API_URL%/logs?session_id=session-001"</code></pre>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{
  "session_id": "session-001",
  "log_count": 2,
  "logs": [
    {
      "timestamp_ms": 1700000000000,
      "log": {
        "session_id": "session-001",
        "user_id": "user001",
        "message": "今日の天気は？",
        "response": "東京は晴れです",
        "level": "info"
      }
    }
  ]
}</code></pre>
<p><!-- ![GET /logs のレスポンス確認](images/get-logs-response.jpg) --></p>
<hr>
<h3><span id="toc26">CloudWatch でログを確認する</span></h3>
<p><strong>CloudWatch → ロググループ → <code>/chatbot/chatbot-logs</code> → ログストリーム</strong></p>
<ul>
<li><code>session/session-001</code> ストリームをクリック</li>
<li>送信した会話ログが JSON 形式で記録されていることを確認します</li>
</ul>
<p><!-- ![CloudWatch Logs のセッションストリーム](images/cloudwatch-log-stream.jpg) --></p>
<h3><span id="toc27">CloudWatch Logs Insights でクエリを実行する（任意）</span></h3>
<p><strong>CloudWatch → Logs Insights</strong></p>
<p>ロググループに <code>/chatbot/chatbot-logs</code> を選択し、<strong>時間範囲を「直近1時間」など幅のある範囲に設定</strong>してから以下のクエリを実行します。</p>
<blockquote>
<p><strong>注意</strong>: 開始時刻と終了時刻が同じだとエラー <code>Query&#39;s end date and time is either before the log groups creation time...</code> が出ます。カレンダーアイコンで時間幅を確保してください。</p>
</blockquote>
<pre><code class="language-plaintext">fields @timestamp, session_id, user_id, level, message
| filter level = "error"
| sort @timestamp desc
| limit 20</code></pre>
<p>エラーログだけを抽出して確認できます。</p>
<h3><span id="toc28">カスタムメトリクスを確認する（任意）</span></h3>
<p><strong>CloudWatch → メトリクス → すべてのメトリクス → カスタム名前空間 → <code>ChatBot/chatbot-logs</code></strong></p>
<table>
<thead>
<tr>
<th>メトリクス名</th>
<th>説明</th>
<th>発生元</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ChatErrorCount</code></td>
<td>error ログが書き込まれるたびに +1</td>
<td>Metric Filter（自動）</td>
</tr>
<tr>
<td><code>MessageCount</code></td>
<td>POST /logs が呼ばれるたびに +1</td>
<td>Lambda の put_metric_data</td>
</tr>
</tbody>
</table>
<p><!-- ![CloudWatch カスタムメトリクスの確認画面](images/cloudwatch-custom-metrics.jpg) --></p>
<hr>
<h2><span id="toc29">⑦ リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ず削除してください。</strong></p>
<h3><span id="toc30">1. API Gateway を削除する</span></h3>
<p><strong>API Gateway → API → <code>ChatLogAPI</code> → 「削除」→ API 名を入力 → 「削除」</strong></p>
<h3><span id="toc31">2. Lambda 関数を削除する</span></h3>
<p><strong>Lambda → 関数 → <code>ChatLogFunction</code> → 「アクション」→「削除」</strong></p>
<h3><span id="toc32">3. CloudWatch Log Group を削除する</span></h3>
<p><strong>CloudWatch → ロググループ → <code>/chatbot/chatbot-logs</code> → 「アクション」→「ロググループを削除」</strong></p>
<blockquote>
<p>Metric Filter はロググループを削除すると自動的に削除されます。</p>
</blockquote>
<h3><span id="toc33">4. IAM ロールを削除する（任意）</span></h3>
<p><strong>IAM → ロール → <code>ChatLogFunction-role-XXXX</code> を削除</strong></p>
<h3><span id="toc34">5. CloudWatch Lambda ロググループを削除する（任意）</span></h3>
<p><strong>CloudWatch → ロগগループ → <code>/aws/lambda/ChatLogFunction</code> → 削除</strong></p>
<hr>
<h2><span id="toc35">SAM との対比</span></h2>
<table>
<thead>
<tr>
<th>SAMの記述</th>
<th>コンソールでやること</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AWS::Logs::LogGroup</code></td>
<td>CloudWatch → ロググループを作成</td>
</tr>
<tr>
<td><code>AWS::Logs::MetricFilter</code></td>
<td>ロググループ → メトリクスフィルターを作成</td>
</tr>
<tr>
<td><code>Statement: logs:PutLogEvents</code></td>
<td>IAM → ロールにインラインポリシーを追加</td>
</tr>
<tr>
<td><code>Statement: cloudwatch:PutMetricData</code></td>
<td>同上（同じポリシーに含める）</td>
</tr>
<tr>
<td><code>Events: Type: Api</code>（POST + GET）</td>
<td>API Gateway → リソース・メソッドを作成してデプロイ</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>コンソール版のつまずきポイント:</strong><br />IAM のインラインポリシーに <code>YOUR_ACCOUNT_ID</code> を自分のアカウントIDに書き換えるのを忘れると <code>AccessDeniedException</code> が発生します。<br />SAM版では <code>!GetAtt ChatLogGroup.Arn</code> で自動解決されるため、この手作業が不要です。</p>
</blockquote>
<hr>
<h2><span id="toc36">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AccessDeniedException: logs:PutLogEvents</code></td>
<td>Lambda ロールに権限がない</td>
<td>④ の手順でインラインポリシーを追加する</td>
</tr>
<tr>
<td><code>ResourceNotFoundException</code> (Log Group)</td>
<td>環境変数のロググループ名が誤っている</td>
<td><code>LOG_GROUP_NAME</code> 環境変数と ① で作成したグループ名を確認する</td>
</tr>
<tr>
<td>POST で 500 エラー</td>
<td>Lambda 実行エラー</td>
<td>Lambda → 「モニタリング」→「CloudWatch Logs を表示」でエラー詳細を確認する</td>
</tr>
<tr>
<td>GET で <code>{&quot;log_count&quot;: 0}</code></td>
<td>ログがない or session_id が異なる</td>
<td>POST で使った session_id と一致しているか確認する</td>
</tr>
<tr>
<td>Metric Filter が反映されない</td>
<td>数分かかる</td>
<td>5〜10 分後に CloudWatch メトリクスを再確認する</td>
</tr>
<tr>
<td><code>InvalidParameterException</code> (put_log_events)</td>
<td>タイムスタンプが古すぎる or 未来すぎる</td>
<td><code>int(time.time() * 1000)</code> でミリ秒タイムスタンプを生成しているか確認する</td>
</tr>
<tr>
<td>API Gateway から 403 が返る</td>
<td>ステージのデプロイ忘れ</td>
<td>API Gateway → 「API をデプロイ」を再実行する</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc37">まとめ</span></h2>
<p>今回のハンズオンで体験できたこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>API Gateway + Lambda 連携</strong></td>
<td>Lambda プロキシ統合で REST API の POST / GET を1関数で処理</td>
</tr>
<tr>
<td><strong>カスタムロググループへの書き込み</strong></td>
<td><code>put_log_events()</code> でセッション単位のログストリームに記録</td>
</tr>
<tr>
<td><strong>Metric Filter</strong></td>
<td><code>{ $.level = &quot;error&quot; }</code> でエラーログを自動的にメトリクスへ変換</td>
</tr>
<tr>
<td><strong>CloudWatch Logs Insights</strong></td>
<td>クエリでエラーログだけを抽出・確認</td>
</tr>
</tbody>
</table>
<h3><span id="toc38">コンソール版で実感できたポイント</span></h3>
<ul>
<li>API Gateway のリソース・メソッド・デプロイという3段階の設定が視覚的に理解できる</li>
<li>IAM ポリシーのリソース ARN を手動で記述することで、権限スコープの意味が身につく</li>
<li>Metric Filter の仕組みを GUI で設定することで、JSON フィルター構文の動作が理解できる</li>
</ul>
<hr>
<h2><span id="toc39">コンソール版と SAM 版を比較してみる</span></h2>
<p>コンソールで API Gateway + Lambda + CloudWatch Logs の連携を理解したら、SAM で同じ構成をコードで定義することで「SAM が何を自動化しているか」が明確になります。IAM ポリシーの ARN 解決や Log Group の保持期間設定など、コンソールでの手動操作がコードに対応しています。</p>
<p><!-- TODO: SAM版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc40">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-console-chatbot-log-api/">AWSコンソールでチャットボットログAPIを構築する手順【API Gateway + Lambda + CloudWatch Logs ハンズオン / SAM版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-console-chatbot-log-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS SAM でチャットボットログAPIを構築しよう【API Gateway + Lambda + CloudWatch Logs ハンズオン / コンソール版との比較付き】</title>
		<link>https://caymezon.com/aws-handson-chatbot-log-api/</link>
					<comments>https://caymezon.com/aws-handson-chatbot-log-api/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 08 Mar 2026 05:02:36 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[APIGateway]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[CloudWatchLogs]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[MetricFilter]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[SAM]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[チャットボット]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[初心者]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20255</guid>

					<description><![CDATA[<p>目次 はじめにSAM vs コンソール：どれだけ違うかキーワード解説前提条件使用するAWSサービスStep 1: プロジェクトフォルダを開くフォルダ構造Step 2: SAMテンプレートの確認（template.yaml [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-chatbot-log-api/">AWS SAM でチャットボットログAPIを構築しよう【API Gateway + Lambda + CloudWatch Logs ハンズオン / コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-4" checked><label class="toc-title" for="toc-checkbox-4">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">SAM vs コンソール：どれだけ違うか</a></li><li><a href="#toc3" tabindex="0">キーワード解説</a></li><li><a href="#toc4" tabindex="0">前提条件</a></li><li><a href="#toc5" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc6" tabindex="0">Step 1: プロジェクトフォルダを開く</a><ol><li><a href="#toc7" tabindex="0">フォルダ構造</a></li></ol></li><li><a href="#toc8" tabindex="0">Step 2: SAMテンプレートの確認（template.yaml）</a><ol><li><a href="#toc9" tabindex="0">Log Group と Metric Filter の定義</a></li><li><a href="#toc10" tabindex="0">Lambda 関数の定義（IAMポリシーとAPIトリガー）</a></li></ol></li><li><a href="#toc11" tabindex="0">Step 3: Lambda コードの確認（src/app.py）</a></li><li><a href="#toc12" tabindex="0">Step 4: samconfig.toml を作成する</a></li><li><a href="#toc13" tabindex="0">Step 5: sam build（ビルド）</a></li><li><a href="#toc14" tabindex="0">Step 6: sam deploy（デプロイ）</a><ol><li><a href="#toc15" tabindex="0">デプロイ完了の確認</a></li><li><a href="#toc16" tabindex="0">デプロイされるリソース一覧</a></li></ol></li><li><a href="#toc17" tabindex="0">Step 7: 動作テスト</a><ol><li><a href="#toc18" tabindex="0">事前準備: API URL を変数に設定する</a></li><li><a href="#toc19" tabindex="0">テスト 1: ログを記録する（POST /logs）</a></li><li><a href="#toc20" tabindex="0">テスト 2: エラーログを記録する（Metric Filter のテスト）</a></li><li><a href="#toc21" tabindex="0">テスト 3: ログを取得する（GET /logs）</a></li></ol></li><li><a href="#toc22" tabindex="0">Step 8: AWSコンソールで確認（任意）</a><ol><li><a href="#toc23" tabindex="0">CloudWatch Logs でログを確認する</a></li><li><a href="#toc24" tabindex="0">CloudWatch Logs Insights でクエリを実行する</a></li><li><a href="#toc25" tabindex="0">カスタムメトリクスを確認する</a></li></ol></li><li><a href="#toc26" tabindex="0">Step 9: リソースの削除</a></li><li><a href="#toc27" tabindex="0">トラブルシューティング</a></li><li><a href="#toc28" tabindex="0">まとめ</a><ol><li><a href="#toc29" tabindex="0">SAMのメリットを実感できたポイント</a></li></ol></li><li><a href="#toc30" tabindex="0">コンソール版と比較してみる</a></li><li><a href="#toc31" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「チャットボットのログ収集基盤をコードで管理して、同じ環境を何度でも再現したい」という要件に、<strong>AWS SAM（Serverless Application Model）</strong> は最適な選択肢のひとつです。</p>
<p>この記事では、<strong>AWS SAM</strong> を使って、API Gateway + Lambda + CloudWatch Logs によるチャットボットログAPIをゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">クライアント
  ↓ POST /logs（会話ログを送信）
API Gateway（REST API / Prod ステージ）
  ↓ Lambda プロキシ統合
Lambda（ChatLogFunction / Python 3.12）
  ↓ boto3 logs.put_log_events()           boto3 cloudwatch.put_metric_data()
CloudWatch Log Group                      CloudWatch カスタムメトリクス
（/chatbot/STACK_NAME）                   （ChatBot/STACK_NAME）
  ↓ Metric Filter（$.level = "error"）         ↑ 自動変換
CloudWatch ChatErrorCount メトリクス ─────────┘</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li><code>AWS::Logs::LogGroup</code> / <code>AWS::Logs::MetricFilter</code> の SAM 定義</li>
<li>SAM 組み込みポリシーがない場合の <code>Statement</code> 直接記述による権限付与</li>
<li><code>!Sub</code> / <code>!GetAtt</code> / <code>!Ref</code> を使ったリソース間参照</li>
<li><code>sam build</code> + <code>sam deploy</code> の <strong>2コマンドで全リソースをデプロイ</strong></li>
</ul>
<p><strong>このハンズオンの特徴：</strong></p>
<ul>
<li>API Gateway + Lambda × 1 + CloudWatch Log Group + Metric Filter + IAM ロールを <code>template.yaml</code> 1ファイルで管理</li>
<li><code>sam delete</code> で<strong>全リソースを一括削除</strong>（コンソール版では API Gateway / Lambda / Log Group / IAM を個別に削除）</li>
</ul>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">SAM vs コンソール：どれだけ違うか</span></h2>
<table>
<thead>
<tr>
<th>比較項目</th>
<th>SAM（コード）</th>
<th>コンソール（手動）</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Log Group の保持期間</strong></td>
<td><code>RetentionInDays: 7</code> で定義</td>
<td>作成時に手動設定</td>
</tr>
<tr>
<td><strong>Metric Filter</strong></td>
<td><code>AWS::Logs::MetricFilter</code> で定義</td>
<td>ロググループのタブから手動作成</td>
</tr>
<tr>
<td><strong>IAM 権限</strong></td>
<td><code>Statement</code> で直接記述（ARN は <code>!GetAtt</code> で自動解決）</td>
<td>インラインポリシーに ARN を手動入力</td>
</tr>
<tr>
<td><strong>API Gateway</strong></td>
<td><code>Events: Type: Api</code> で POST / GET を1行ずつ定義</td>
<td>リソース・メソッド・デプロイを個別に操作</td>
</tr>
<tr>
<td><strong>削除</strong></td>
<td><code>sam delete</code> 1コマンド</td>
<td>API Gateway / Lambda / Log Group / IAM を個別に削除</td>
</tr>
<tr>
<td><strong>再現性</strong></td>
<td>チームで同じ環境を即座に再現できる</td>
<td>手順書が必要</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc3">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>CloudWatch Log Group</strong></td>
<td>ログの論理的なまとまり。SAM では <code>RetentionInDays</code> を設定できる</td>
</tr>
<tr>
<td><strong>Log Stream</strong></td>
<td>Log Group の中の個別のログストリーム。今回はセッションIDごとに作成する</td>
</tr>
<tr>
<td><strong>Metric Filter</strong></td>
<td>ログの JSON フィールドをマッチさせ、カスタムメトリクスを自動生成する仕組み</td>
</tr>
<tr>
<td><strong>Lambda プロキシ統合</strong></td>
<td>API Gateway が HTTP リクエスト全体を Lambda に渡す最もシンプルな統合方式</td>
</tr>
<tr>
<td><strong><code>!GetAtt</code></strong></td>
<td>CloudFormation 組み込み関数。リソースの属性値（ARN など）を参照する</td>
</tr>
<tr>
<td><strong><code>!Sub</code></strong></td>
<td>CloudFormation 組み込み関数。文字列の中に変数を埋め込む</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">前提条件</span></h2>
<table>
<thead>
<tr>
<th>ツール</th>
<th>確認コマンド</th>
<th>最低バージョン目安</th>
</tr>
</thead>
<tbody>
<tr>
<td>AWS CLI v2</td>
<td><code>aws --version</code></td>
<td>2.x</td>
</tr>
<tr>
<td>AWS SAM CLI</td>
<td><code>sam --version</code></td>
<td>1.x</td>
</tr>
<tr>
<td>Python 3.12</td>
<td><code>python --version</code></td>
<td>3.12</td>
</tr>
</tbody>
</table>
<p>AWS認証確認:</p>
<pre><code class="language-cmd">aws sts get-caller-identity</code></pre>
<p>アカウントIDが表示されれば認証設定済みです。</p>
<hr>
<h2><span id="toc5">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>API Gateway</strong></td>
<td>REST APIのエンドポイント（POST /logs, GET /logs）</td>
<td>月100万リクエストまで無料</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>ログの書き込み・取得処理（Python 3.12）</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>チャットログの保存・クエリ</td>
<td>月5GBまで無料</td>
</tr>
<tr>
<td><strong>CloudWatch メトリクス</strong></td>
<td>エラーログのカスタムメトリクス化</td>
<td>月10件まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda の実行権限管理</td>
<td>無料</td>
</tr>
<tr>
<td><strong>S3</strong></td>
<td>SAM デプロイパッケージ置き場</td>
<td>少量のため実質無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc6">Step 1: プロジェクトフォルダを開く</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\chatbot-log-api</code></pre>
<h3><span id="toc7">フォルダ構造</span></h3>
<pre><code class="language-plaintext">chatbot-log-api/
├── template.yaml       # SAMテンプレート（API Gateway + Lambda + CloudWatch）
├── samconfig.toml      # デプロイ設定（gitignore 対象・毎回手動作成が必要）
├── docs/
│   ├── 1_console.md    # AWSコンソール版手順
│   └── 2_sam.md        # SAM版手順
└── src/
    └── app.py          # Lambda 関数（POST /logs, GET /logs）</code></pre>
<hr>
<h2><span id="toc8">Step 2: SAMテンプレートの確認（template.yaml）</span></h2>
<p><code>template.yaml</code> は全 AWSリソースの設計図です。コンソール版との違いに注目しながらポイントを確認します。</p>
<h3><span id="toc9">Log Group と Metric Filter の定義</span></h3>
<pre><code class="language-yaml">Resources:
  ChatLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/chatbot/${AWS::StackName}"
      RetentionInDays: 7

  # Metric Filter: error ログを ChatErrorCount メトリクスに自動変換
  ErrorMetricFilter:
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName: !Ref ChatLogGroup
      FilterPattern: '{ $.level = "error" }'
      MetricTransformations:
        - MetricName: ChatErrorCount
          MetricNamespace: !Sub "ChatBot/${AWS::StackName}"
          MetricValue: "1"
          DefaultValue: 0</code></pre>
<blockquote>
<p><strong><code>AWS::Logs::LogGroup</code> を明示定義する理由:</strong><br />未定義のまま Lambda を実行すると、Lambda が自動でロググループを作成しますが保持期間が<strong>無期限</strong>になります。<br />SAM で明示的に定義することで <code>RetentionInDays: 7</code> が確実に適用されます。</p>
</blockquote>
<hr>
<h3><span id="toc10">Lambda 関数の定義（IAMポリシーとAPIトリガー）</span></h3>
<pre><code class="language-yaml">  ChatLogFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: app.lambda_handler
      Runtime: python3.12
      Timeout: 10
      Environment:
        Variables:
          LOG_GROUP_NAME: !Ref ChatLogGroup  # "/chatbot/スタック名"
      Policies:
        - Statement:
            # カスタムロググループへの書き込み・読み取り（スコープを特定グループに限定）
            - Action: [logs:CreateLogStream, logs:PutLogEvents, logs:GetLogEvents, logs:DescribeLogStreams]
              Effect: Allow
              Resource: [!GetAtt ChatLogGroup.Arn, !Sub "${ChatLogGroup.Arn}:*"]
            # カスタムメトリクスの書き込み
            - Action: cloudwatch:PutMetricData
              Effect: Allow
              Resource: "*"
      Events:
        PostLog:
          Type: Api
          Properties:
            Path: /logs
            Method: post
        GetLogs:
          Type: Api
          Properties:
            Path: /logs
            Method: get</code></pre>
<p><strong>template.yaml のポイント:</strong></p>
<table>
<thead>
<tr>
<th>ポイント</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AWS::Logs::LogGroup</code></td>
<td>SAMでロググループを明示定義することで <code>RetentionInDays</code> を設定できる。未定義だと Lambda が自動作成するが保持期間は無期限</td>
</tr>
<tr>
<td><code>AWS::Logs::MetricFilter</code></td>
<td>JSON フィルターパターンで特定フィールドのログを検出してメトリクスに変換する</td>
</tr>
<tr>
<td><code>FilterPattern: &#39;{ $.level = &quot;error&quot; }&#39;</code></td>
<td>CloudWatch Logs の JSON フィルター構文。<code>$</code> で JSON ルートを表す</td>
</tr>
<tr>
<td><code>Statement: logs:PutLogEvents</code></td>
<td>SAM組み込みポリシーにCloudWatch Logs書き込みポリシーはないため、IAMポリシー文を直接記述する</td>
</tr>
<tr>
<td><code>!Sub &quot;${ChatLogGroup.Arn}:*&quot;</code></td>
<td><code>arn:...:log-group:/chatbot/STACK:*</code> の形式でログストリームにアクセスする権限を付与</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc11">Step 3: Lambda コードの確認（src/app.py）</span></h2>
<p>コードはすでに作成済みです。各関数の役割とポイントを確認しておきます。</p>
<pre><code class="language-python">LOG_GROUP_NAME = os.environ["LOG_GROUP_NAME"]           # "/chatbot/スタック名"
METRIC_NAMESPACE = "ChatBot/" + LOG_GROUP_NAME.split("/")[-1]  # "ChatBot/スタック名"

def post_log(event):
    # リクエストボディを解析
    body = json.loads(event.get("body") or "{}")
    session_id = body.get("session_id") or str(uuid.uuid4())

    # セッション単位のログストリームに書き込む
    log_stream_name = f"session/{session_id}"
    _ensure_log_stream(log_stream_name)       # ストリームを作成（既存なら無視）
    logs_client.put_log_events(...)           # ログを書き込む

    # カスタムメトリクスを記録
    cloudwatch.put_metric_data(
        Namespace=METRIC_NAMESPACE,
        MetricData=[{"MetricName": "MessageCount", "Dimensions": [...], "Value": 1}],
    )

def get_logs(event):
    # session_id でログストリームを指定して取得
    logs_client.get_log_events(logGroupName=..., logStreamName=f"session/{session_id}")</code></pre>
<p><strong>app.py のポイント:</strong></p>
<ul>
<li><strong><code>_ensure_log_stream()</code></strong>: <code>create_log_stream()</code> は既存ストリームに対して <code>ResourceAlreadyExistsException</code> を返します。<code>botocore.exceptions.ClientError</code> でキャッチして無視することで冪等性を確保します</li>
<li><strong><code>int(time.time() * 1000)</code></strong>: CloudWatch Logs の <code>timestamp</code> はミリ秒単位の Unix 時間（整数）です</li>
<li><strong><code>startFromHead=False</code></strong>: 最新のログから取得します（デフォルトは古いものから）</li>
<li><strong><code>METRIC_NAMESPACE</code></strong>: 環境変数の <code>LOG_GROUP_NAME</code> からスタック名を抽出して名前空間に使います</li>
</ul>
<hr>
<h2><span id="toc12">Step 4: samconfig.toml を作成する</span></h2>
<p><code>samconfig.toml</code> は <code>.gitignore</code> で管理外のため、<strong>毎回手動で作成</strong>する必要があります。</p>
<p><code>chatbot-log-api/samconfig.toml</code> を新規作成して以下を貼り付けます。</p>
<pre><code class="language-toml">version = 0.1

[default.deploy.parameters]
stack_name = "chatbot-log-api-stack"
resolve_s3 = true
s3_prefix = "chatbot-log-api-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
disable_rollback = true
image_repositories = []

[default.global.parameters]
region = "ap-northeast-1"</code></pre>
<blockquote>
<p><strong><code>samconfig.toml</code> の置き場所:</strong><br /><code>chatbot-log-api/</code> フォルダの<strong>直下</strong>に置く必要があります。</p>
</blockquote>
<hr>
<h2><span id="toc13">Step 5: sam build（ビルド）</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\chatbot-log-api
sam build</code></pre>
<p>成功すると以下のように表示されます。</p>
<pre><code class="language-plaintext">Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml</code></pre>
<blockquote>
<p><strong><code>sam build</code> が行っていること:</strong><br /><code>src/</code> フォルダを ZIP パッケージ化して <code>.aws-sam/build/</code> に配置し、Lambda デプロイの準備を整えます。</p>
</blockquote>
<hr>
<h2><span id="toc14">Step 6: sam deploy（デプロイ）</span></h2>
<pre><code class="language-cmd">sam deploy</code></pre>
<p>変更内容が表示されて確認を求められます。</p>
<pre><code class="language-plaintext">Deploy this changeset? [y/N]: y</code></pre>
<p><code>y</code> を入力して進めます。数分でデプロイが完了します。</p>
<h3><span id="toc15">デプロイ完了の確認</span></h3>
<p>ターミナルに <strong>Outputs</strong> が表示されます。</p>
<pre><code class="language-plaintext">Outputs
----------------------------------------------------------------------
Key    ApiUrl
Value  https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod

Key    LogGroupName
Value  /chatbot/chatbot-log-api-stack

Key    MetricNamespace
Value  ChatBot/chatbot-log-api-stack
----------------------------------------------------------------------</code></pre>
<p><strong><code>ApiUrl</code> を控えておきます。</strong></p>
<h3><span id="toc16">デプロイされるリソース一覧</span></h3>
<pre><code class="language-plaintext">API Gateway REST API × 1  : chatbot-log-api-stack
Lambda 関数 × 1           : chatbot-log-api-stack-ChatLogFunction-XXXX
CloudWatch Log Group × 1  : /chatbot/chatbot-log-api-stack
CloudWatch Metric Filter   : ChatErrorFilter（error ログ検出）
IAM ロール × 1            : Lambda 用
S3                         : SAM デプロイパッケージ</code></pre>
<hr>
<h2><span id="toc17">Step 7: 動作テスト</span></h2>
<h3><span id="toc18">事前準備: API URL を変数に設定する</span></h3>
<pre><code class="language-cmd">set API_URL=https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod</code></pre>
<blockquote>
<p><code>XXXXXXXXXX</code> を Outputs の <code>ApiUrl</code> の値に置き換えてください。</p>
</blockquote>
<hr>
<h3><span id="toc19">テスト 1: ログを記録する（POST /logs）</span></h3>
<pre><code class="language-cmd">curl -X POST "%API_URL%/logs" ^
  -H "Content-Type: application/json" ^
  -d "{\"session_id\": \"session-001\", \"user_id\": \"user001\", \"message\": \"今日の天気は？\", \"response\": \"東京は晴れです\", \"level\": \"info\"}"</code></pre>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{
  "session_id": "session-001",
  "log_stream": "session/session-001",
  "message": "ログを記録した"
}</code></pre>
<p><!-- ![POST /logs のレスポンス確認](images/post-logs-response.jpg) --></p>
<hr>
<h3><span id="toc20">テスト 2: エラーログを記録する（Metric Filter のテスト）</span></h3>
<pre><code class="language-cmd">curl -X POST "%API_URL%/logs" ^
  -H "Content-Type: application/json" ^
  -d "{\"session_id\": \"session-001\", \"user_id\": \"user001\", \"message\": \"注文履歴を見せて\", \"response\": \"エラーが発生しました\", \"level\": \"error\"}"</code></pre>
<blockquote>
<p><code>level: &quot;error&quot;</code> のログが CloudWatch Logs に書き込まれると、<br />Metric Filter が反応して <code>ChatErrorCount</code> メトリクスが +1 されます（数分後に反映）。</p>
</blockquote>
<hr>
<h3><span id="toc21">テスト 3: ログを取得する（GET /logs）</span></h3>
<pre><code class="language-cmd">curl "%API_URL%/logs?session_id=session-001"</code></pre>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{
  "session_id": "session-001",
  "log_count": 2,
  "logs": [
    {
      "timestamp_ms": 1700000000000,
      "log": {
        "session_id": "session-001",
        "user_id": "user001",
        "message": "今日の天気は？",
        "response": "東京は晴れです",
        "level": "info"
      }
    },
    {
      "timestamp_ms": 1700000010000,
      "log": {
        "session_id": "session-001",
        "user_id": "user001",
        "message": "注文履歴を見せて",
        "response": "エラーが発生しました",
        "level": "error"
      }
    }
  ]
}</code></pre>
<p><!-- ![GET /logs のレスポンス確認](images/get-logs-response.jpg) --></p>
<hr>
<h2><span id="toc22">Step 8: AWSコンソールで確認（任意）</span></h2>
<p>SAMでデプロイしたリソースはコンソールでも確認できます。</p>
<h3><span id="toc23">CloudWatch Logs でログを確認する</span></h3>
<p><strong>CloudWatch → ロググループ → <code>/chatbot/chatbot-log-api-stack</code> → ログストリーム</strong></p>
<ul>
<li><code>session/session-001</code> ストリームに会話ログが JSON で記録されています</li>
</ul>
<p><!-- ![CloudWatch Logs のセッションストリーム](images/cloudwatch-log-stream.jpg) --></p>
<h3><span id="toc24">CloudWatch Logs Insights でクエリを実行する</span></h3>
<p><strong>CloudWatch → Logs Insights → ロググループに <code>/chatbot/chatbot-log-api-stack</code> を選択</strong></p>
<p><strong>時間範囲を「直近1時間」など幅のある範囲に設定</strong>してからクエリを実行します。</p>
<blockquote>
<p><strong>注意</strong>: 開始時刻と終了時刻が同じだとエラー <code>Query&#39;s end date and time is either before the log groups creation time...</code> が出ます。カレンダーアイコンで時間幅を確保してください。</p>
</blockquote>
<p>エラーログだけを抽出するクエリ:</p>
<pre><code class="language-plaintext">fields @timestamp, session_id, user_id, level, message
| filter level = "error"
| sort @timestamp desc
| limit 20</code></pre>
<p>セッション別メッセージ数を集計するクエリ:</p>
<pre><code class="language-plaintext">stats count(*) as message_count by session_id
| sort message_count desc</code></pre>
<h3><span id="toc25">カスタムメトリクスを確認する</span></h3>
<p><strong>CloudWatch → メトリクス → すべてのメトリクス → カスタム名前空間 → <code>ChatBot/chatbot-log-api-stack</code></strong></p>
<table>
<thead>
<tr>
<th>メトリクス名</th>
<th>説明</th>
<th>発生元</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ChatErrorCount</code></td>
<td>error ログが書き込まれるたびに +1</td>
<td>Metric Filter（自動）</td>
</tr>
<tr>
<td><code>MessageCount</code></td>
<td>POST /logs が呼ばれるたびに +1</td>
<td>Lambda の put_metric_data</td>
</tr>
</tbody>
</table>
<p><!-- ![CloudWatch カスタムメトリクスの確認画面](images/cloudwatch-custom-metrics.jpg) --></p>
<hr>
<h2><span id="toc26">Step 9: リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ずリソースを削除してください。</strong></p>
<pre><code class="language-cmd">sam delete --stack-name chatbot-log-api-stack --region ap-northeast-1</code></pre>
<pre><code class="language-plaintext">Are you sure you want to delete the stack chatbot-log-api-stack? [y/N]: y
Are you sure you want to delete the folder chatbot-log-api-stack in S3? [y/N]: y</code></pre>
<p>削除完了の確認:</p>
<pre><code class="language-cmd">aws cloudformation describe-stacks --stack-name chatbot-log-api-stack --region ap-northeast-1</code></pre>
<p><code>Stack with id chatbot-log-api-stack does not exist</code> が表示されれば削除完了。</p>
<blockquote>
<p><strong>削除されるリソース一覧（<code>sam delete</code> で自動削除）:</strong></p>
<ul>
<li>API Gateway（REST API + Prod ステージ）</li>
<li>Lambda 関数</li>
<li>CloudWatch Log Group（<code>/chatbot/chatbot-log-api-stack</code>）— ログも含めて削除</li>
<li>CloudWatch Metric Filter</li>
<li>IAM ロール</li>
</ul>
</blockquote>
<blockquote>
<p><strong>手動削除が必要なリソース:</strong></p>
<ul>
<li><strong>Lambda 実行ログ（<code>/aws/lambda/chatbot-log-api-stack-ChatLogFunction-xxxx</code>）</strong><br />Lambda が初回実行時に自動作成するロググループで、CloudFormation の管理外のため <code>sam delete</code> では削除されません。<br />CloudWatch → ログ管理 → 該当ロググループを選択 → アクション → 削除 で手動削除します。</li>
<li>CloudWatch カスタムメトリクス（<code>MessageCount</code>、<code>ChatErrorCount</code>）は独立して管理されます。<br />不要な場合は CloudWatch → メトリクス → アクション → 削除で個別に削除します（コストはかかりにくいため省略可）。</li>
</ul>
</blockquote>
<hr>
<h2><span id="toc27">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sam deploy</code> で <code>ROLLBACK_COMPLETE</code></td>
<td>template.yaml の構文エラー</td>
<td><code>sam validate</code> でテンプレートを検証する</td>
</tr>
<tr>
<td>POST で <code>{&quot;message&quot;: &quot;Internal server error&quot;}</code></td>
<td>Lambda 実行エラー</td>
<td><code>aws logs tail /aws/lambda/chatbot-log-api-stack-ChatLogFunction-XXXX --follow</code> でエラーを確認</td>
</tr>
<tr>
<td><code>AccessDeniedException: logs:PutLogEvents</code></td>
<td>IAM ポリシーが適用されていない</td>
<td><code>sam deploy</code> を再実行する</td>
</tr>
<tr>
<td>GET で <code>{&quot;log_count&quot;: 0}</code></td>
<td>session_id が一致していない</td>
<td>POST レスポンスの <code>session_id</code> をそのまま GET に使う</td>
</tr>
<tr>
<td>Metric Filter が反映されない</td>
<td>数分かかる</td>
<td>5〜10 分後に CloudWatch メトリクスを再確認する</td>
</tr>
<tr>
<td><code>InvalidParameterException</code> (put_log_events)</td>
<td>timestamp が不正</td>
<td>ミリ秒 Unix タイム（<code>int(time.time() * 1000)</code>）を使っているか確認</td>
</tr>
<tr>
<td><code>sam build</code> で <code>Build Failed</code></td>
<td><code>src/app.py</code> が存在しない</td>
<td><code>chatbot-log-api/src/app.py</code> が存在するか確認する</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc28">まとめ</span></h2>
<p>今回のハンズオンで実現したこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>API Gateway + Lambda 連携</strong></td>
<td><code>Events: Type: Api</code> でPOST / GETを定義し、Lambda プロキシ統合で一元処理</td>
</tr>
<tr>
<td><strong>Log Group の保持期間管理</strong></td>
<td><code>RetentionInDays: 7</code> で7日間保持を確実に設定</td>
</tr>
<tr>
<td><strong>Metric Filter</strong></td>
<td><code>FilterPattern: &#39;{ $.level = &quot;error&quot; }&#39;</code> でエラーログを自動メトリクス化</td>
</tr>
<tr>
<td><strong>IAM 権限のスコープ制限</strong></td>
<td><code>!GetAtt ChatLogGroup.Arn</code> で特定ロググループのみに権限を絞り込み</td>
</tr>
<tr>
<td><strong>一括削除</strong></td>
<td><code>sam delete</code> でAPI Gateway / Lambda / Log Group / IAM を一括クリーンアップ</td>
</tr>
</tbody>
</table>
<h3><span id="toc29">SAMのメリットを実感できたポイント</span></h3>
<ul>
<li><code>!GetAtt ChatLogGroup.Arn</code> により、IAM ポリシーの ARN をハードコードせず動的に解決できる</li>
<li><code>RetentionInDays</code> や <code>Metric Filter</code> をコードで管理することで、設定漏れ・設定ミスを防げる</li>
<li><code>sam delete</code> でコンソール版では手動削除が必要な複数リソースを1コマンドで削除できる</li>
</ul>
<hr>
<h2><span id="toc30">コンソール版と比較してみる</span></h2>
<p>SAMが裏で何をやっているか、同じ構成をAWSコンソールのみで構築する手順をまとめました。コンソール版では IAM インラインポリシーの ARN を手動入力する必要がある仕組みや、Metric Filter の GUI 設定方法など、コンソールならではの操作を解説しています。</p>
<p><!-- TODO: コンソール版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc31">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-chatbot-log-api/">AWS SAM でチャットボットログAPIを構築しよう【API Gateway + Lambda + CloudWatch Logs ハンズオン / コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-chatbot-log-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS SAM で画像認識パイプライン（S3 + Lambda + Rekognition）を構築しよう【コンソール版との比較付き】</title>
		<link>https://caymezon.com/aws-handson-rekognition-image-pipeline/</link>
					<comments>https://caymezon.com/aws-handson-rekognition-image-pipeline/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 08 Mar 2026 02:58:28 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[ML]]></category>
		<category><![CDATA[Rekognition]]></category>
		<category><![CDATA[S3]]></category>
		<category><![CDATA[SAM]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[初心者]]></category>
		<category><![CDATA[画像認識]]></category>
		<category><![CDATA[画像認識パイプライン]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20251</guid>

					<description><![CDATA[<p>目次 はじめにSAM vs コンソール：どれだけ違うかキーワード解説前提条件使用するAWSサービスStep 1: プロジェクトフォルダを開くフォルダ構造Step 2: SAMテンプレートの確認（template.yaml [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-rekognition-image-pipeline/">AWS SAM で画像認識パイプライン（S3 + Lambda + Rekognition）を構築しよう【コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-6" checked><label class="toc-title" for="toc-checkbox-6">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">SAM vs コンソール：どれだけ違うか</a></li><li><a href="#toc3" tabindex="0">キーワード解説</a></li><li><a href="#toc4" tabindex="0">前提条件</a></li><li><a href="#toc5" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc6" tabindex="0">Step 1: プロジェクトフォルダを開く</a><ol><li><a href="#toc7" tabindex="0">フォルダ構造</a></li></ol></li><li><a href="#toc8" tabindex="0">Step 2: SAMテンプレートの確認（template.yaml）</a></li><li><a href="#toc9" tabindex="0">Step 3: Lambda コードの確認（src/app.py）</a></li><li><a href="#toc10" tabindex="0">Step 4: samconfig.toml を作成する</a></li><li><a href="#toc11" tabindex="0">Step 5: sam build（ビルド）</a></li><li><a href="#toc12" tabindex="0">Step 6: sam deploy（デプロイ）</a><ol><li><a href="#toc13" tabindex="0">デプロイ完了の確認</a></li><li><a href="#toc14" tabindex="0">デプロイされるリソース一覧</a></li></ol></li><li><a href="#toc15" tabindex="0">Step 7: 動作テスト</a><ol><li><a href="#toc16" tabindex="0">事前準備: バケット名を変数に設定する</a></li><li><a href="#toc17" tabindex="0">テスト1: 画像をアップロードしてラベルを確認する</a></li><li><a href="#toc18" tabindex="0">テスト2: 複数枚アップロードして無料枠を確認する</a></li></ol></li><li><a href="#toc19" tabindex="0">Step 8: AWSコンソールで確認（任意）</a></li><li><a href="#toc20" tabindex="0">Step 9: リソースの削除</a></li><li><a href="#toc21" tabindex="0">トラブルシューティング</a></li><li><a href="#toc22" tabindex="0">まとめ</a><ol><li><a href="#toc23" tabindex="0">SAMのメリットを実感できたポイント</a></li></ol></li><li><a href="#toc24" tabindex="0">コンソール版と比較してみる</a></li><li><a href="#toc25" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「画像認識の仕組みをコードで管理して、いつでも同じ環境を再現できるようにしたい」という要件に、<strong>AWS SAM（Serverless Application Model）</strong> は最適な選択肢のひとつです。</p>
<p>この記事では、<strong>AWS SAM</strong> を使って、S3 + Lambda + Rekognition による画像認識パイプラインをゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">ユーザー
  ↓ aws s3 cp image.jpg s3://bucket/
S3 バケット（STACK-upload-ACCOUNT_ID）
  ↓ ObjectCreated イベント（自動）
Lambda（DetectFunction / Python 3.12）
  ↓ boto3 rekognition.detect_labels(S3Object={...})
Amazon Rekognition
  ↓ Labels: [{Name, Confidence}, ...]
Lambda
  ↓ print(json.dumps(result))
CloudWatch Logs</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li><code>AWS::S3::Bucket</code> / <code>AWS::Serverless::Function</code> の SAM 定義</li>
<li><code>S3ReadPolicy</code> / <code>RekognitionDetectOnlyPolicy</code> — SAM 組み込みポリシーによる1行での権限付与</li>
<li><code>Events: Type: S3</code>（ObjectCreated）— Lambda の S3 トリガーをコードで定義する方法</li>
<li>コンソール版との権限設定の違いを体感</li>
</ul>
<p><strong>このハンズオンの特徴：</strong></p>
<ul>
<li><code>sam build</code> + <code>sam deploy</code> の <strong>2コマンドで全リソースをデプロイ</strong></li>
<li>S3 バケット + Lambda + IAM ロールを <code>template.yaml</code> 1ファイルで管理</li>
<li><code>sam delete</code> で<strong>全リソースを一括削除</strong>（コンソール版では S3 / Lambda / IAM / ロググループを個別に削除）</li>
</ul>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">SAM vs コンソール：どれだけ違うか</span></h2>
<table>
<thead>
<tr>
<th>比較項目</th>
<th>SAM（コード）</th>
<th>コンソール（手動）</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>S3 トリガー</strong></td>
<td><code>Events: Type: S3</code> で1箇所に定義</td>
<td>Lambda → トリガーを追加（手動クリック）</td>
</tr>
<tr>
<td><strong>Rekognition 権限</strong></td>
<td><code>RekognitionDetectOnlyPolicy: {}</code> の1行</td>
<td>IAM → <code>AmazonRekognitionReadOnlyAccess</code> を手動アタッチ</td>
</tr>
<tr>
<td><strong>S3 GetObject 権限</strong></td>
<td><code>S3ReadPolicy</code> で特定バケットに自動付与</td>
<td>IAM → インラインポリシーを手動作成</td>
</tr>
<tr>
<td><strong>削除</strong></td>
<td><code>sam delete</code> 1コマンド</td>
<td>S3 / Lambda / IAM / ロググループを個別に削除</td>
</tr>
<tr>
<td><strong>再現性</strong></td>
<td>チームで同じ環境を即座に再現できる</td>
<td>手順書が必要</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc3">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>DetectLabels</strong></td>
<td>Rekognition API。画像内の物体・シーンを検出してラベル名と信頼度を返す</td>
</tr>
<tr>
<td><strong>S3Object 参照</strong></td>
<td>画像データを Lambda に転送せず、S3 の場所を Rekognition に渡す方式</td>
</tr>
<tr>
<td><strong>RekognitionDetectOnlyPolicy</strong></td>
<td>SAM 組み込みポリシー。<code>DetectLabels</code> / <code>DetectFaces</code> / <code>DetectText</code> などを付与</td>
</tr>
<tr>
<td><strong>S3ReadPolicy</strong></td>
<td>SAM 組み込みポリシー。特定バケットへの <code>s3:GetObject</code> を付与。コンソール版より スコープが狭くセキュア</td>
</tr>
<tr>
<td><strong>無料枠</strong></td>
<td>最初の12ヶ月 / 月5,000枚まで無料。超過後は $0.001/枚</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">前提条件</span></h2>
<table>
<thead>
<tr>
<th>ツール</th>
<th>確認コマンド</th>
<th>最低バージョン目安</th>
</tr>
</thead>
<tbody>
<tr>
<td>AWS CLI v2</td>
<td><code>aws --version</code></td>
<td>2.x</td>
</tr>
<tr>
<td>AWS SAM CLI</td>
<td><code>sam --version</code></td>
<td>1.x</td>
</tr>
<tr>
<td>Python 3.12</td>
<td><code>python --version</code></td>
<td>3.12</td>
</tr>
</tbody>
</table>
<p>AWS認証確認:</p>
<pre><code class="language-cmd">aws sts get-caller-identity</code></pre>
<p>アカウントIDが表示されれば認証設定済みです。</p>
<hr>
<h2><span id="toc5">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>S3</strong></td>
<td>画像のアップロード先バケット</td>
<td>月5GBまで無料</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>S3 イベントを受けて Rekognition を呼び出す</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>Rekognition</strong></td>
<td>画像ラベル検出（DetectLabels）</td>
<td>最初の12ヶ月 / 月5,000枚まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda の実行権限管理</td>
<td>無料</td>
</tr>
<tr>
<td><strong>S3（SAMデプロイ用）</strong></td>
<td>SAM デプロイパッケージ置き場</td>
<td>少量のため実質無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>Lambda の実行ログ</td>
<td>月5GBまで無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc6">Step 1: プロジェクトフォルダを開く</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\rekognition-image-pipeline</code></pre>
<h3><span id="toc7">フォルダ構造</span></h3>
<pre><code class="language-plaintext">rekognition-image-pipeline/
├── template.yaml       # SAMテンプレート（S3 + Lambda + Rekognition 定義）
├── samconfig.toml      # デプロイ設定（gitignore 対象・毎回手動作成が必要）
├── docs/
│   ├── 1_console.md    # AWSコンソール版手順
│   └── 2_sam.md        # SAM版手順
└── src/
    └── app.py          # Lambda 関数（detect_labels + ログ出力）</code></pre>
<hr>
<h2><span id="toc8">Step 2: SAMテンプレートの確認（template.yaml）</span></h2>
<p><code>template.yaml</code> は全 AWSリソースの設計図です。コンソール版との違いに注目しながらポイントを確認します。</p>
<pre><code class="language-yaml">Resources:
  UploadBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${AWS::StackName}-upload-${AWS::AccountId}"

  # Lambda 関数
  DetectFunction:
    Type: AWS::Serverless::Function
    Properties:
      Policies:
        # SAM 組み込みポリシー: 特定バケットへの s3:GetObject を付与
        - S3ReadPolicy:
            BucketName: !Sub "${AWS::StackName}-upload-${AWS::AccountId}"
        # SAM 組み込みポリシー: DetectLabels / DetectFaces / DetectText などを付与
        - RekognitionDetectOnlyPolicy: {}
      Events:
        S3UploadEvent:
          Type: S3
          Properties:
            Bucket: !Ref UploadBucket
            Events: s3:ObjectCreated:*</code></pre>
<p><strong>template.yaml のポイント:</strong></p>
<table>
<thead>
<tr>
<th>ポイント</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>RekognitionDetectOnlyPolicy: {}</code></td>
<td>SAM 組み込みポリシー。コンソール版では <code>AmazonRekognitionReadOnlyAccess</code> を手動アタッチ</td>
</tr>
<tr>
<td><code>S3ReadPolicy</code></td>
<td>特定バケットのみに <code>s3:GetObject</code> を付与。コンソール版よりスコープが狭くセキュア</td>
</tr>
<tr>
<td><code>BucketName: !Sub &quot;${AWS::StackName}-upload-${AWS::AccountId}&quot;</code></td>
<td>アカウントIDを含めることでグローバルユニークを保証</td>
</tr>
<tr>
<td><code>Events: Type: S3</code>（ObjectCreated）</td>
<td>Lambda に S3 トリガーをコードで定義。コンソール版では手動でトリガーを追加</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc9">Step 3: Lambda コードの確認（src/app.py）</span></h2>
<p>コードはすでに作成済みです。構造を把握しておきます。</p>
<pre><code class="language-python">import json
import urllib.parse
import boto3

rekognition = boto3.client("rekognition")   # Lambda 起動時に1回だけ初期化


def lambda_handler(event, context):
    for record in event["Records"]:
        bucket = record["s3"]["bucket"]["name"]
        key = urllib.parse.unquote_plus(record["s3"]["object"]["key"])  # 日本語対応

        response = rekognition.detect_labels(
            Image={"S3Object": {"Bucket": bucket, "Name": key}},
            MaxLabels=10,      # 最大10ラベル
            MinConfidence=70,  # 70%以上のラベルのみ返す
        )

        labels = [{"name": l["Name"], "confidence": round(l["Confidence"], 1)}
                  for l in response["Labels"]]

        print(json.dumps({"key": key, "label_count": len(labels), "labels": labels},
                         ensure_ascii=False))

    return {"processedCount": len(event["Records"])}</code></pre>
<p><strong>app.py のポイント:</strong></p>
<ul>
<li><code>S3Object</code> 参照: 画像データを Lambda のメモリに転送せず S3 の場所を Rekognition に渡す。メモリ効率が良い</li>
<li><code>urllib.parse.unquote_plus(key)</code>: 日本語や空白を含むファイル名は S3 イベントで URL エンコードされる</li>
<li><code>boto3.client(&quot;rekognition&quot;)</code> をグローバルスコープに置く: Lambda のコールドスタート時に1回だけ初期化され、ウォームスタート時は再利用される（パフォーマンス向上）</li>
</ul>
<hr>
<h2><span id="toc10">Step 4: samconfig.toml を作成する</span></h2>
<p><code>samconfig.toml</code> は <code>.gitignore</code> で管理外のため、<strong>毎回手動で作成</strong>する必要があります。</p>
<p><code>rekognition-image-pipeline/samconfig.toml</code> を新規作成して以下を貼り付けます。</p>
<pre><code class="language-toml">version = 0.1

[default.deploy.parameters]
stack_name = "rekognition-image-pipeline-stack"
resolve_s3 = true
s3_prefix = "rekognition-image-pipeline-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
disable_rollback = true
image_repositories = []

[default.global.parameters]
region = "ap-northeast-1"</code></pre>
<blockquote>
<p><strong><code>samconfig.toml</code> の置き場所：</strong><br /><code>rekognition-image-pipeline/</code> フォルダの<strong>直下</strong>に置く必要があります。</p>
</blockquote>
<hr>
<h2><span id="toc11">Step 5: sam build（ビルド）</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\rekognition-image-pipeline
sam build</code></pre>
<p>成功すると以下のように表示されます。</p>
<pre><code class="language-plaintext">Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml</code></pre>
<blockquote>
<p><strong><code>sam build</code> が行っていること：</strong><br /><code>src/</code> フォルダを ZIP パッケージ化して <code>.aws-sam/build/</code> に配置し、Lambda デプロイの準備を整えます。</p>
</blockquote>
<hr>
<h2><span id="toc12">Step 6: sam deploy（デプロイ）</span></h2>
<pre><code class="language-cmd">sam deploy</code></pre>
<p>変更内容が表示されて確認を求められます。</p>
<pre><code class="language-plaintext">Deploy this changeset? [y/N]: y</code></pre>
<p><code>y</code> を入力して進めます。数分でデプロイが完了します。</p>
<h3><span id="toc13">デプロイ完了の確認</span></h3>
<p>ターミナルに <strong>Outputs</strong> が表示されます。</p>
<pre><code class="language-plaintext">Outputs
----------------------------------------------------------------------
Key    BucketName
Value  rekognition-image-pipeline-stack-upload-123456789012

Key    DetectFunctionArn
Value  arn:aws:lambda:ap-northeast-1:123456789012:function:rekognition-image-pipeline-stack-DetectFunction-XXXX
----------------------------------------------------------------------</code></pre>
<p><strong><code>BucketName</code> を控えておきます。</strong></p>
<h3><span id="toc14">デプロイされるリソース一覧</span></h3>
<pre><code class="language-plaintext">S3 バケット × 1        : rekognition-image-pipeline-stack-upload-ACCOUNT_ID
Lambda 関数 × 1        : rekognition-image-pipeline-stack-DetectFunction-XXXX
IAM ロール × 1         : Lambda 用（S3 GetObject + Rekognition DetectLabels 権限付き）
S3（SAM用）            : デプロイパッケージ
CloudWatch Logs        : Lambda 自動生成ログ</code></pre>
<hr>
<h2><span id="toc15">Step 7: 動作テスト</span></h2>
<h3><span id="toc16">事前準備: バケット名を変数に設定する</span></h3>
<pre><code class="language-cmd">set BUCKET=rekognition-image-pipeline-stack-upload-123456789012</code></pre>
<blockquote>
<p><code>123456789012</code> を自分のアカウントIDに置き換える（Outputs の値をそのままコピー）。</p>
</blockquote>
<hr>
<h3><span id="toc17">テスト1: 画像をアップロードしてラベルを確認する</span></h3>
<p>任意の JPEG または PNG 画像（15MB 以下）を用意してアップロードする。</p>
<pre><code class="language-cmd">aws s3 cp "C:\Users\yourname\Pictures\dog.jpg" s3://%BUCKET%/ --region ap-northeast-1</code></pre>
<p>アップロード直後（数秒以内）に Lambda が自動起動する。</p>
<p><strong>CloudWatch Logs でラベル検出結果を確認:</strong></p>
<pre><code class="language-cmd">aws logs tail /aws/lambda/rekognition-image-pipeline-stack-DetectFunction-XXXX --follow</code></pre>
<p>期待するログ出力:</p>
<pre><code class="language-json">{"status": "start", "bucket": "rekognition-image-pipeline-stack-upload-...", "key": "dog.jpg"}
{
  "key": "dog.jpg",
  "label_count": 8,
  "labels": [
    {"name": "Dog", "confidence": 98.5},
    {"name": "Pet", "confidence": 98.5},
    {"name": "Animal", "confidence": 98.5},
    {"name": "Canine", "confidence": 98.5},
    {"name": "Mammal", "confidence": 97.2},
    {"name": "Golden Retriever", "confidence": 85.3},
    {"name": "Grass", "confidence": 76.1},
    {"name": "Outdoors", "confidence": 71.4}
  ]
}
{"processedCount": 1}</code></pre>
<p><!-- ![CloudWatch Logsのラベル検出結果：SAM版で犬の写真から8つのラベルが検出された様子](images/cloudwatch-detect-result-sam.jpg) --></p>
<hr>
<h3><span id="toc18">テスト2: 複数枚アップロードして無料枠を確認する</span></h3>
<pre><code class="language-cmd">aws s3 cp "C:\Users\yourname\Pictures\cat.jpg"  s3://%BUCKET%/ --region ap-northeast-1
aws s3 cp "C:\Users\yourname\Pictures\city.jpg" s3://%BUCKET%/ --region ap-northeast-1</code></pre>
<p>各ファイルのアップロードごとに Lambda が起動し、それぞれのラベル検出結果がログに記録される。</p>
<blockquote>
<p><strong>無料枠の管理：</strong><br />AWS コンソール → Billing → 無料利用枠 → 「Amazon Rekognition」で当月の使用枚数を確認できます。</p>
</blockquote>
<hr>
<h2><span id="toc19">Step 8: AWSコンソールで確認（任意）</span></h2>
<p>SAMでデプロイしたリソースはコンソールでも確認できます。</p>
<ul>
<li><strong>S3</strong>: S3 → <code>rekognition-image-pipeline-stack-upload-...</code> → アップロードしたファイルを確認</li>
<li><strong>Lambda</strong>: Lambda → 関数 → <code>DetectFunction-XXXX</code> → 「設定」→「トリガー」で S3 トリガーを確認</li>
<li><strong>IAM</strong>: IAM → ロール → Lambda のロールに <code>S3ReadPolicy</code> / <code>RekognitionDetectOnlyPolicy</code> が適用されていることを確認</li>
<li><strong>CloudWatch Logs</strong>: Lambda → 「モニタリング」→「CloudWatch Logs を表示」でログ詳細を確認</li>
</ul>
<hr>
<h2><span id="toc20">Step 9: リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ず削除してください。</strong></p>
<blockquote>
<p><strong>【注意】</strong> S3 バケットにオブジェクトが残っていると <code>sam delete</code> が失敗します。<br />先にバケットを空にしてから実行してください。</p>
</blockquote>
<pre><code class="language-cmd">rem Step 1: バケットを空にする
aws s3 rm s3://%BUCKET% --recursive --region ap-northeast-1

rem Step 2: スタック削除
sam delete --stack-name rekognition-image-pipeline-stack --region ap-northeast-1</code></pre>
<pre><code class="language-plaintext">Are you sure you want to delete the stack rekognition-image-pipeline-stack? [y/N]: y
Are you sure you want to delete the folder rekognition-image-pipeline-stack in S3? [y/N]: y</code></pre>
<p>削除完了の確認:</p>
<pre><code class="language-cmd">aws cloudformation describe-stacks --stack-name rekognition-image-pipeline-stack --region ap-northeast-1</code></pre>
<p><code>Stack with id rekognition-image-pipeline-stack does not exist</code> が表示されれば削除完了。</p>
<blockquote>
<p><strong>削除されるリソース一覧:</strong></p>
<ul>
<li>S3 バケット（事前に空にした場合）</li>
<li>Lambda 関数</li>
<li>IAM ロール</li>
<li>CloudWatch Logs ロググループ</li>
</ul>
<p>Rekognition はサービス自体（バケット・コレクション等）を作成しないため、削除作業は不要です。</p>
</blockquote>
<hr>
<h2><span id="toc21">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sam deploy</code> で <code>BucketAlreadyOwnedByYou</code></td>
<td>バケット名が既存と重複</td>
<td><code>stack_name</code> を変更して <code>samconfig.toml</code> を更新する</td>
</tr>
<tr>
<td>画像アップロード後 Lambda が動作しない</td>
<td>S3 トリガーが未設定（デプロイエラー）</td>
<td><code>sam deploy</code> が正常完了しているか確認。CloudFormation スタックイベントを確認</td>
</tr>
<tr>
<td><code>AccessDeniedException</code> (Rekognition)</td>
<td><code>RekognitionDetectOnlyPolicy</code> が適用されていない</td>
<td><code>sam deploy</code> を再実行</td>
</tr>
<tr>
<td><code>AccessDenied</code> (S3)</td>
<td><code>S3ReadPolicy</code> が適用されていない</td>
<td>Lambda と S3 が同一リージョンかを確認。<code>sam deploy</code> を再実行</td>
</tr>
<tr>
<td><code>InvalidImageFormatException</code></td>
<td>JPEG/PNG 以外のファイルをアップロードした</td>
<td>JPEG または PNG 形式の画像を使用する</td>
</tr>
<tr>
<td><code>ImageTooLargeException</code></td>
<td>15MB を超える画像をアップロードした</td>
<td>15MB 以下の画像を使用する</td>
</tr>
<tr>
<td><code>sam delete</code> で <code>BucketNotEmpty</code> エラー</td>
<td>S3 バケットにオブジェクトが残っている</td>
<td><code>aws s3 rm s3://%BUCKET% --recursive</code> で先に空にする</td>
</tr>
<tr>
<td>ラベルが 0 件または少ない</td>
<td><code>MinConfidence</code> が高い、または被写体が不明瞭</td>
<td><code>MinConfidence=70</code> を下げて再デプロイ。または別の画像を試す</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc22">まとめ</span></h2>
<p>今回のハンズオンで実現したこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>S3 + Lambda の連携</strong></td>
<td>画像アップロードを契機に Lambda が自動起動する仕組みをコードで定義</td>
</tr>
<tr>
<td><strong>Rekognition DetectLabels</strong></td>
<td>画像に写った物体・動物・シーンが自動でラベル付けされる様子を確認</td>
</tr>
<tr>
<td><strong>SAM 組み込みポリシー</strong></td>
<td><code>S3ReadPolicy</code> / <code>RekognitionDetectOnlyPolicy</code> で権限設定が1行で完結</td>
</tr>
<tr>
<td><strong>一括削除</strong></td>
<td><code>sam delete</code> でS3 / Lambda / IAM を一括クリーンアップ</td>
</tr>
</tbody>
</table>
<h3><span id="toc23">SAMのメリットを実感できたポイント</span></h3>
<ul>
<li><code>RekognitionDetectOnlyPolicy: {}</code> と <code>S3ReadPolicy</code> により IAM 権限設定がワンライナーで完結</li>
<li>S3 バケット名に <code>!Sub &quot;${AWS::StackName}-upload-${AWS::AccountId}&quot;</code> を使うことでグローバルユニークを自動保証</li>
<li><code>sam delete</code> で S3 / Lambda / IAM を一括削除。コンソール版のような個別削除が不要</li>
</ul>
<hr>
<h2><span id="toc24">コンソール版と比較してみる</span></h2>
<p>SAMが裏で何をやっているか、同じ構成をAWSコンソールのみで構築する手順をまとめました。コンソール版では IAM の権限追加を S3 用・Rekognition 用と2回行う必要があるなど、SAMの組み込みポリシーがどれだけ手間を削減しているかが明確に分かります。</p>
<p><!-- TODO: コンソール版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc25">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-rekognition-image-pipeline/">AWS SAM で画像認識パイプライン（S3 + Lambda + Rekognition）を構築しよう【コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-rekognition-image-pipeline/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWSコンソールだけで画像認識パイプラインを構築する手順【S3 + Lambda + Rekognition / SAM版との比較付き】</title>
		<link>https://caymezon.com/aws-handson-console-rekognition-image-pipeline/</link>
					<comments>https://caymezon.com/aws-handson-console-rekognition-image-pipeline/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 08 Mar 2026 02:58:22 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[AWSコンソール]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[ML]]></category>
		<category><![CDATA[Rekognition]]></category>
		<category><![CDATA[S3]]></category>
		<category><![CDATA[SAM比較]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[初心者]]></category>
		<category><![CDATA[画像認識]]></category>
		<category><![CDATA[画像認識パイプライン]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20249</guid>

					<description><![CDATA[<p>目次 はじめにキーワード解説使用するAWSサービス全体の作業順序① S3 バケットを作成する② Lambda 関数を作成するタイムアウトの設定コードの入力③ Lambda の実行ロールに権限を追加する3-1. Rekog [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-console-rekognition-image-pipeline/">AWSコンソールだけで画像認識パイプラインを構築する手順【S3 + Lambda + Rekognition / SAM版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-8" checked><label class="toc-title" for="toc-checkbox-8">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">キーワード解説</a></li><li><a href="#toc3" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc4" tabindex="0">全体の作業順序</a></li><li><a href="#toc5" tabindex="0">① S3 バケットを作成する</a></li><li><a href="#toc6" tabindex="0">② Lambda 関数を作成する</a><ol><li><a href="#toc7" tabindex="0">タイムアウトの設定</a></li><li><a href="#toc8" tabindex="0">コードの入力</a></li></ol></li><li><a href="#toc9" tabindex="0">③ Lambda の実行ロールに権限を追加する</a><ol><li><a href="#toc10" tabindex="0">3-1. Rekognition 権限を追加する</a></li><li><a href="#toc11" tabindex="0">3-2. S3 GetObject 権限を追加する</a></li></ol></li><li><a href="#toc12" tabindex="0">④ Lambda に S3 トリガーを追加する</a></li><li><a href="#toc13" tabindex="0">⑤ 動作テスト</a><ol><li><a href="#toc14" tabindex="0">テスト用画像の準備</a></li><li><a href="#toc15" tabindex="0">画像をアップロードする</a></li><li><a href="#toc16" tabindex="0">CloudWatch Logs でラベル検出結果を確認する</a></li><li><a href="#toc17" tabindex="0">複数枚でテストする</a></li></ol></li><li><a href="#toc18" tabindex="0">⑥ リソースの削除</a><ol><li><a href="#toc19" tabindex="0">1. S3 バケットのオブジェクトを全て削除する</a></li><li><a href="#toc20" tabindex="0">2. S3 バケットを削除する</a></li><li><a href="#toc21" tabindex="0">3. Lambda のトリガーを削除する</a></li><li><a href="#toc22" tabindex="0">4. Lambda 関数を削除する</a></li><li><a href="#toc23" tabindex="0">5. IAM ロールを削除する（任意）</a></li><li><a href="#toc24" tabindex="0">6. CloudWatch Logs のロググループを削除する（任意）</a></li></ol></li><li><a href="#toc25" tabindex="0">SAMとの対比</a></li><li><a href="#toc26" tabindex="0">トラブルシューティング</a></li><li><a href="#toc27" tabindex="0">まとめ</a><ol><li><a href="#toc28" tabindex="0">コンソール版で実感できたポイント</a></li></ol></li><li><a href="#toc29" tabindex="0">コンソール版と SAM 版を比較してみる</a></li><li><a href="#toc30" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「S3に画像をアップロードしたら、自動で中身を分析してほしい」——そんな仕組みを、<strong>AWSコンソールのみ</strong>でゼロから構築するハンズオンです。</p>
<p>Amazon Rekognition の <code>DetectLabels</code> API を使うと、画像に写っている物体・シーン・動物などを自動で検出できます。犬の写真をアップロードすれば「Dog（信頼度98%）」「Animal（97%）」といったラベルが返ってきます。</p>
<pre><code class="language-plaintext">ユーザー
  ↓ JPEG / PNG をアップロード
S3 バケット（UploadBucket）
  ↓ ObjectCreated イベント（自動）
Lambda（DetectFunction）
  ↓ rekognition:DetectLabels（S3Object を指定）
Amazon Rekognition
  ↓ [{"name": "Dog", "confidence": 98.5}, ...] を返す
Lambda
  ↓ print（JSON）
CloudWatch Logs</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li><strong>S3 トリガー</strong> — S3 へのアップロードを契機に Lambda を自動起動する仕組み</li>
<li><strong>Rekognition DetectLabels</strong> — 画像内の物体・シーン・動物を検出してラベルと信頼度を返す API</li>
<li><strong>IAM 権限管理</strong> — Lambda から S3・Rekognition を呼び出すために必要な権限の追加手順</li>
<li><strong>S3Object 参照</strong> — 画像データを Lambda に転送せず S3 の場所を直接 Rekognition に渡す効率的な方法</li>
</ul>
<hr>
<blockquote>
<p><strong>この記事は <a href="#">SAM版ハンズオン</a> の比較記事です。</strong><br />コンソール操作で S3・Lambda・Rekognition の連携を視覚的に学び、SAM と何が違うのかを比較したい方向けです。</p>
</blockquote>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>DetectLabels</strong></td>
<td>Rekognition API。画像内の物体・シーンを検出してラベル名と信頼度を返す</td>
</tr>
<tr>
<td><strong>信頼度（Confidence）</strong></td>
<td>ラベルが正確であるかどうかの確度（0〜100%）。今回は70%以上のみ返す</td>
</tr>
<tr>
<td><strong>S3Object 参照</strong></td>
<td>画像データを Lambda に転送せず、S3 の場所を Rekognition に渡す方式</td>
</tr>
<tr>
<td><strong>S3 イベント通知</strong></td>
<td>S3 バケットへのオブジェクト作成・削除などをトリガーに Lambda を呼び出す仕組み</td>
</tr>
<tr>
<td><strong>実行ロール</strong></td>
<td>Lambda が他の AWS サービスを操作するために使う IAM ロール</td>
</tr>
<tr>
<td><strong>無料枠</strong></td>
<td>最初の12ヶ月 / 月5,000枚まで無料。超過後は $0.001/枚</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc3">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>S3</strong></td>
<td>画像のアップロード先バケット</td>
<td>月5GBまで無料</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>S3 イベントを受けて Rekognition を呼び出す</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>Rekognition</strong></td>
<td>画像ラベル検出（DetectLabels）</td>
<td>最初の12ヶ月 / 月5,000枚まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda の実行権限管理</td>
<td>無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>Lambda の実行ログ（ラベル検出結果）</td>
<td>月5GBまで無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">全体の作業順序</span></h2>
<pre><code class="language-plaintext">① S3 バケットを作成する
      ↓
② Lambda 関数を作成する（コード入力・タイムアウト設定）
      ↓
③ Lambda の実行ロールに権限を追加する
  （S3: GetObject / Rekognition: DetectLabels）
      ↓
④ Lambda に S3 トリガーを追加する
      ↓
⑤ 動作テスト（画像をアップロード → ログを確認）
      ↓
⑥ リソースの削除</code></pre>
<hr>
<h2><span id="toc5">① S3 バケットを作成する</span></h2>
<p><strong>AWSコンソール → S3 → 「バケットを作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>バケット名</td>
<td><code>my-rekognition-upload</code>（グローバルユニークな名前）</td>
</tr>
<tr>
<td>リージョン</td>
<td><strong>アジアパシフィック（東京）ap-northeast-1</strong></td>
</tr>
<tr>
<td>その他</td>
<td>デフォルトのまま</td>
</tr>
</tbody>
</table>
<p>「バケットを作成」をクリック。</p>
<p><strong>控えておく情報:</strong></p>
<ul>
<li><strong>バケット名</strong>（削除時・IAMポリシー設定時に使用）</li>
</ul>
<blockquote>
<p><strong>バケット名のグローバルユニークについて：</strong><br />S3 のバケット名は全 AWS アカウントで一意である必要があります。<code>my-rekognition-upload-</code> に自分のアカウントIDや日付を追加すると重複を避けられます。</p>
</blockquote>
<hr>
<h2><span id="toc6">② Lambda 関数を作成する</span></h2>
<p><strong>AWSコンソール → Lambda → 「関数の作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>作成方法</td>
<td>一から作成</td>
</tr>
<tr>
<td>関数名</td>
<td><code>DetectFunction</code></td>
</tr>
<tr>
<td>ランタイム</td>
<td><strong>Python 3.12</strong></td>
</tr>
<tr>
<td>アーキテクチャ</td>
<td>x86_64</td>
</tr>
<tr>
<td>実行ロール</td>
<td>「基本的な Lambda アクセス権限で新しいロールを作成」（デフォルト）</td>
</tr>
</tbody>
</table>
<p>「関数の作成」をクリック。</p>
<h3><span id="toc7">タイムアウトの設定</span></h3>
<p><strong>「設定」タブ → 「一般設定」→「編集」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>タイムアウト</td>
<td><strong>30</strong> 秒</td>
</tr>
</tbody>
</table>
<p>「保存」をクリック。</p>
<blockquote>
<p><strong>タイムアウトを 30 秒にする理由：</strong><br />Rekognition API の呼び出しには数秒かかることがあります。デフォルト（3秒）では Rekognition のレスポンスを待ちきれずタイムアウトするため、余裕を持って 30 秒に設定します。</p>
</blockquote>
<h3><span id="toc8">コードの入力</span></h3>
<p>「コード」タブ → <code>lambda_function.py</code> を開いて既存の内容を全て置き換える。</p>
<pre><code class="language-python">import json
import urllib.parse
import boto3

rekognition = boto3.client("rekognition")


def lambda_handler(event, context):
    for record in event["Records"]:
        bucket = record["s3"]["bucket"]["name"]
        key = urllib.parse.unquote_plus(record["s3"]["object"]["key"])

        print(json.dumps({"status": "start", "bucket": bucket, "key": key}))

        response = rekognition.detect_labels(
            Image={
                "S3Object": {
                    "Bucket": bucket,
                    "Name": key,
                }
            },
            MaxLabels=10,
            MinConfidence=70,
        )

        labels = [
            {
                "name": label["Name"],
                "confidence": round(label["Confidence"], 1),
            }
            for label in response["Labels"]
        ]

        result = {
            "bucket": bucket,
            "key": key,
            "label_count": len(labels),
            "labels": labels,
        }
        print(json.dumps(result, ensure_ascii=False))

    return {"processedCount": len(event["Records"])}</code></pre>
<p>「Deploy」ボタンをクリックしてコードを保存する。</p>
<p><strong>コードのポイント:</strong></p>
<table>
<thead>
<tr>
<th>ポイント</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>rekognition = boto3.client(&quot;rekognition&quot;)</code> をグローバルに置く</td>
<td>Lambda のコールドスタート時に1回だけ初期化。ウォームスタート時は再利用されパフォーマンスが向上する</td>
</tr>
<tr>
<td><code>urllib.parse.unquote_plus(key)</code></td>
<td>日本語や空白を含むファイル名は S3 イベントで URL エンコードされるため、デコードが必要</td>
</tr>
<tr>
<td><code>S3Object</code> 参照</td>
<td>画像データを Lambda のメモリに転送せず S3 の場所を Rekognition に渡す。メモリ効率が良い</td>
</tr>
<tr>
<td><code>MinConfidence=70</code></td>
<td>信頼度 70% 未満のラベルは結果から除外。全件見たい場合は <code>0</code> に変更して Deploy する</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc9">③ Lambda の実行ロールに権限を追加する</span></h2>
<p>デフォルトの実行ロールは CloudWatch Logs への書き込みのみ。<br />S3 からの読み取りと Rekognition の呼び出し権限を追加する。</p>
<p><strong>Lambda → <code>DetectFunction</code> → 「設定」タブ → 「アクセス権限」→ 実行ロール名のリンクをクリック</strong></p>
<p>（例: <code>DetectFunction-role-XXXX</code>）→ IAM コンソールのロール画面が開く。</p>
<h3><span id="toc10">3-1. Rekognition 権限を追加する</span></h3>
<p>「許可を追加」→「ポリシーをアタッチ」</p>
<p>検索ボックスに <code>Rekognition</code> と入力 → <strong><code>AmazonRekognitionReadOnlyAccess</code></strong> にチェック → 「許可を追加」。</p>
<blockquote>
<p><strong>AmazonRekognitionReadOnlyAccess に含まれる主な権限:</strong></p>
<ul>
<li><code>rekognition:DetectLabels</code> — ラベル検出（今回使用）</li>
<li><code>rekognition:DetectFaces</code> — 顔検出</li>
<li><code>rekognition:DetectText</code> — テキスト検出</li>
</ul>
</blockquote>
<h3><span id="toc11">3-2. S3 GetObject 権限を追加する</span></h3>
<p>「許可を追加」→「インラインポリシーを作成」</p>
<p>「JSON」タブを選択して以下を入力する（<code>my-rekognition-upload</code> を ① のバケット名に変更）:</p>
<pre><code class="language-json">{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-rekognition-upload/*"
    }
  ]
}</code></pre>
<p>「次へ」→ ポリシー名: <code>S3ReadForRekognition</code> → 「ポリシーを作成」。</p>
<blockquote>
<p><strong>なぜ S3 の権限が必要か:</strong><br />Rekognition に <code>S3Object</code> を指定した場合、Rekognition が Lambda の IAM ロールを借りて<br />S3 からオブジェクトを取得します。Lambda ロールに <code>s3:GetObject</code> がないと 403 エラーになります。</p>
<p>SAM版では <code>S3ReadPolicy</code>（SAM 組み込みポリシー）が特定バケットのみに <code>s3:GetObject</code> を自動付与します。<br />コンソール版では手動でインラインポリシーを作成する必要があります。</p>
</blockquote>
<hr>
<h2><span id="toc12">④ Lambda に S3 トリガーを追加する</span></h2>
<p><strong>Lambda → <code>DetectFunction</code> → 「設定」タブ → 「トリガー」→「トリガーを追加」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>ソース</td>
<td><strong>S3</strong></td>
</tr>
<tr>
<td>バケット</td>
<td><code>my-rekognition-upload</code>（① で作成したバケット）</td>
</tr>
<tr>
<td>イベントタイプ</td>
<td><strong>すべてのオブジェクト作成イベント（s3:ObjectCreated:*）</strong></td>
</tr>
<tr>
<td>プレフィックス</td>
<td>空欄（全ファイルを対象）</td>
</tr>
<tr>
<td>サフィックス</td>
<td>空欄（全形式を対象）</td>
</tr>
</tbody>
</table>
<p>「再帰呼び出し」の確認チェックボックスにチェック → 「追加」をクリック。</p>
<blockquote>
<p><strong>再帰呼び出しについて：</strong><br />Lambda の実行結果を同じバケットに書き込む構成にすると、S3 イベントが再発火して無限ループになります。<br />今回は S3 への書き込みを行わないため問題ありません。</p>
</blockquote>
<p><!-- ![S3トリガー追加画面：バケットとイベントタイプの設定](images/s3-trigger-setting.jpg) --></p>
<hr>
<h2><span id="toc13">⑤ 動作テスト</span></h2>
<h3><span id="toc14">テスト用画像の準備</span></h3>
<p>任意の JPEG または PNG 画像を用意する（例: 犬・猫・風景など）。<br />ファイルサイズは <strong>15MB 以下</strong> であることを確認する。</p>
<h3><span id="toc15">画像をアップロードする</span></h3>
<p><strong>方法 A: S3 コンソール</strong></p>
<p>S3 → <code>my-rekognition-upload</code> → 「アップロード」→ ファイルをドラッグ＆ドロップ → 「アップロード」</p>
<p><strong>方法 B: AWS CLI</strong></p>
<pre><code class="language-cmd">aws s3 cp "C:\Users\yourname\Pictures\dog.jpg" s3://my-rekognition-upload/ --region ap-northeast-1</code></pre>
<h3><span id="toc16">CloudWatch Logs でラベル検出結果を確認する</span></h3>
<p><strong>Lambda → <code>DetectFunction</code> → 「モニタリング」タブ → 「CloudWatch Logs を表示」</strong></p>
<p>最新のロットストリームを開いて以下のような出力を確認する。</p>
<pre><code class="language-json">{"status": "start", "bucket": "my-rekognition-upload", "key": "dog.jpg"}
{
  "bucket": "my-rekognition-upload",
  "key": "dog.jpg",
  "label_count": 8,
  "labels": [
    {"name": "Dog", "confidence": 98.5},
    {"name": "Pet", "confidence": 98.5},
    {"name": "Animal", "confidence": 98.5},
    {"name": "Canine", "confidence": 98.5},
    {"name": "Mammal", "confidence": 97.2},
    {"name": "Golden Retriever", "confidence": 85.3},
    {"name": "Grass", "confidence": 76.1},
    {"name": "Outdoors", "confidence": 71.4}
  ]
}
{"processedCount": 1}</code></pre>
<p><!-- ![CloudWatch Logsのラベル検出結果：犬の写真から8つのラベルが検出された様子](images/cloudwatch-detect-result.jpg) --></p>
<blockquote>
<p><strong><code>MinConfidence: 70</code> の効果：</strong><br />信頼度 70% 未満のラベルは結果に含まれません。<br />全ラベルを確認したい場合は <code>MinConfidence=0</code> に変更して「Deploy」する。</p>
</blockquote>
<h3><span id="toc17">複数枚でテストする</span></h3>
<pre><code class="language-cmd">aws s3 cp "C:\Users\yourname\Pictures\cat.jpg"  s3://my-rekognition-upload/ --region ap-northeast-1
aws s3 cp "C:\Users\yourname\Pictures\city.jpg" s3://my-rekognition-upload/ --region ap-northeast-1</code></pre>
<p>各ファイルのアップロードごとに Lambda が起動し、それぞれのラベル検出結果がログに記録される。</p>
<blockquote>
<p><strong>無料枠の管理：</strong><br />AWS コンソール → Billing → 無料利用枠 → 「Amazon Rekognition」で当月の使用枚数を確認できます。<br />月5,000枚まで無料のため、学習目的のハンズオンでは通常課金されません。</p>
</blockquote>
<hr>
<h2><span id="toc18">⑥ リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ず削除してください。</strong></p>
<h3><span id="toc19">1. S3 バケットのオブジェクトを全て削除する</span></h3>
<pre><code class="language-cmd">aws s3 rm s3://my-rekognition-upload --recursive --region ap-northeast-1</code></pre>
<h3><span id="toc20">2. S3 バケットを削除する</span></h3>
<p><strong>S3 → <code>my-rekognition-upload</code> → 「削除」→ バケット名を入力 → 「削除」</strong></p>
<h3><span id="toc21">3. Lambda のトリガーを削除する</span></h3>
<p><strong>Lambda → <code>DetectFunction</code> → 「設定」→「トリガー」→ S3 トリガーを選択 → 「削除」</strong></p>
<h3><span id="toc22">4. Lambda 関数を削除する</span></h3>
<p><strong>Lambda → 関数 → <code>DetectFunction</code> → 「アクション」→「削除」</strong></p>
<h3><span id="toc23">5. IAM ロールを削除する（任意）</span></h3>
<p><strong>IAM → ロール → <code>DetectFunction-role-XXXX</code> を削除</strong></p>
<h3><span id="toc24">6. CloudWatch Logs のロググループを削除する（任意）</span></h3>
<p><strong>CloudWatch → ロググループ → <code>/aws/lambda/DetectFunction</code> → 削除</strong></p>
<blockquote>
<p><strong>SAM版との大きな違い（SAMのメリット）：</strong><br />SAM版では <code>sam delete</code> 1コマンドで上記すべてのリソースを一括削除できます。</p>
</blockquote>
<hr>
<h2><span id="toc25">SAMとの対比</span></h2>
<table>
<thead>
<tr>
<th>SAMの記述</th>
<th>コンソールでやること</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AWS::S3::Bucket</code></td>
<td>S3 → バケットを作成</td>
</tr>
<tr>
<td><code>S3ReadPolicy</code>（SAM組み込み）</td>
<td>IAM → ロールにインラインポリシーで <code>s3:GetObject</code> を追加</td>
</tr>
<tr>
<td><code>RekognitionDetectOnlyPolicy</code>（SAM組み込み）</td>
<td>IAM → ロールに <code>AmazonRekognitionReadOnlyAccess</code> をアタッチ</td>
</tr>
<tr>
<td><code>Events: Type: S3</code>（ObjectCreated）</td>
<td>Lambda → トリガーを追加（S3・全オブジェクト作成イベント）</td>
</tr>
<tr>
<td><code>sam delete</code></td>
<td>S3を空にしてから削除 + Lambda + IAM + CloudWatch Logs を個別に削除</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>コンソール版のつまずきポイント：</strong><br /><code>AccessDeniedException</code> が出た場合、S3 の GetObject 権限（インラインポリシー）と Rekognition の DetectLabels 権限（AmazonRekognitionReadOnlyAccess）の両方が揃っているか確認しましょう。どちらか一方が欠けていてもエラーになります。</p>
</blockquote>
<hr>
<h2><span id="toc26">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td>Lambda が動作しない（ログが出ない）</td>
<td>S3 トリガーが設定されていない</td>
<td>④ の手順で S3 トリガーを追加する</td>
</tr>
<tr>
<td><code>AccessDeniedException</code> for Rekognition</td>
<td>Lambda ロールに Rekognition 権限がない</td>
<td>③-1 の手順で <code>AmazonRekognitionReadOnlyAccess</code> をアタッチ</td>
</tr>
<tr>
<td><code>AccessDenied</code> for S3</td>
<td>Lambda ロールに S3 GetObject 権限がない</td>
<td>③-2 の手順でインラインポリシーを追加</td>
</tr>
<tr>
<td><code>InvalidImageFormatException</code></td>
<td>JPEG/PNG 以外のファイルをアップロードした</td>
<td>JPEG または PNG ファイルを使用する</td>
</tr>
<tr>
<td><code>ImageTooLargeException</code></td>
<td>15MB を超える画像をアップロードした</td>
<td>15MB 以下の画像を使用する</td>
</tr>
<tr>
<td>ラベルが 0 件</td>
<td><code>MinConfidence</code> が高すぎる</td>
<td>コードの <code>MinConfidence=70</code> を下げて「Deploy」する</td>
</tr>
<tr>
<td>日本語ファイル名でエラー</td>
<td>URL エンコードが解除されていない</td>
<td>コードに <code>urllib.parse.unquote_plus(key)</code> が含まれているか確認</td>
</tr>
<tr>
<td>Lambda がタイムアウトする</td>
<td>タイムアウトが短い（デフォルト3秒）</td>
<td>② の手順でタイムアウトを 30 秒に変更する</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc27">まとめ</span></h2>
<p>今回のハンズオンで体験できたこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>S3 トリガー</strong></td>
<td>画像アップロードと同時に Lambda が自動起動する仕組みを体験</td>
</tr>
<tr>
<td><strong>Rekognition DetectLabels</strong></td>
<td>画像に写った物体・動物・シーンが自動でラベル付けされる様子を確認</td>
</tr>
<tr>
<td><strong>S3Object 参照</strong></td>
<td>画像をLambdaに転送せずS3の場所だけを渡す効率的な連携方法</td>
</tr>
<tr>
<td><strong>IAM 権限の設定</strong></td>
<td>S3・Rekognition それぞれに必要な権限を個別に追加する手順</td>
</tr>
</tbody>
</table>
<h3><span id="toc28">コンソール版で実感できたポイント</span></h3>
<ul>
<li>S3 → Lambda → Rekognition という3つのサービスの<strong>データの流れ</strong>が視覚的に理解できる</li>
<li>実行ロールに必要な権限を1つずつ追加することで、IAM の仕組みが身につく</li>
<li>リソースを個別に作成・削除することで、各サービスの独立性と依存関係が明確になる</li>
</ul>
<hr>
<h2><span id="toc29">コンソール版と SAM 版を比較してみる</span></h2>
<p>コンソールで S3・Lambda・Rekognition の連携を理解したら、SAM で同じ構成をコードで定義することで「SAM が何を自動化しているか」が明確になります。<code>S3ReadPolicy</code> や <code>RekognitionDetectOnlyPolicy</code> による1行での権限付与など、コンソールでの手動操作がコードに対応しています。</p>
<p><!-- TODO: SAM版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc30">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-console-rekognition-image-pipeline/">AWSコンソールだけで画像認識パイプラインを構築する手順【S3 + Lambda + Rekognition / SAM版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-console-rekognition-image-pipeline/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS SAM で SNS + SQS + Lambda 通知システムを構築しよう【Pub/Sub・ファンアウト・DLQ / コンソール版との比較付き】</title>
		<link>https://caymezon.com/aws-handson-sns-sqs-lambda/</link>
					<comments>https://caymezon.com/aws-handson-sns-sqs-lambda/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 08 Mar 2026 00:44:42 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[DLQ]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[Pub/Sub]]></category>
		<category><![CDATA[SAM]]></category>
		<category><![CDATA[SNS]]></category>
		<category><![CDATA[SQS]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[ファンアウト]]></category>
		<category><![CDATA[初心者]]></category>
		<category><![CDATA[通知システム]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20245</guid>

					<description><![CDATA[<p>目次 はじめにSAM vs コンソール：どれだけ違うかキーワード解説前提条件使用するAWSサービスStep 1: プロジェクトフォルダを開くフォルダ構造Step 2: SAMテンプレートの確認（template.yaml [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-sns-sqs-lambda/">AWS SAM で SNS + SQS + Lambda 通知システムを構築しよう【Pub/Sub・ファンアウト・DLQ / コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-10" checked><label class="toc-title" for="toc-checkbox-10">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">SAM vs コンソール：どれだけ違うか</a></li><li><a href="#toc3" tabindex="0">キーワード解説</a></li><li><a href="#toc4" tabindex="0">前提条件</a></li><li><a href="#toc5" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc6" tabindex="0">Step 1: プロジェクトフォルダを開く</a><ol><li><a href="#toc7" tabindex="0">フォルダ構造</a></li></ol></li><li><a href="#toc8" tabindex="0">Step 2: SAMテンプレートの確認（template.yaml）</a><ol><li><a href="#toc9" tabindex="0">SNS Topic とキューの定義</a></li><li><a href="#toc10" tabindex="0">SQS キューポリシー（SNS からの受信許可）</a></li><li><a href="#toc11" tabindex="0">SNS サブスクリプション（ファンアウトとフィルター）</a></li><li><a href="#toc12" tabindex="0">Lambda 関数の定義</a></li></ol></li><li><a href="#toc13" tabindex="0">Step 3: Lambda コードの確認（src/）</a><ol><li><a href="#toc14" tabindex="0">log_handler.py のポイント</a></li><li><a href="#toc15" tabindex="0">alert_handler.py のポイント</a></li></ol></li><li><a href="#toc16" tabindex="0">Step 4: samconfig.toml を作成する</a></li><li><a href="#toc17" tabindex="0">Step 5: sam build（ビルド）</a></li><li><a href="#toc18" tabindex="0">Step 6: sam deploy（デプロイ）</a><ol><li><a href="#toc19" tabindex="0">デプロイ完了の確認</a></li><li><a href="#toc20" tabindex="0">デプロイされるリソース一覧</a></li></ol></li><li><a href="#toc21" tabindex="0">Step 7: 動作テスト</a><ol><li><a href="#toc22" tabindex="0">事前準備: ARN を変数に設定する</a></li><li><a href="#toc23" tabindex="0">テスト1: level=info（LogFunction のみ受信）</a></li><li><a href="#toc24" tabindex="0">テスト2: level=warning（ファンアウト確認）</a></li><li><a href="#toc25" tabindex="0">テスト3: level=warning + body=error（DLQ テスト）</a></li></ol></li><li><a href="#toc26" tabindex="0">Step 8: AWSコンソールで確認（任意）</a></li><li><a href="#toc27" tabindex="0">Step 9: リソースの削除</a><ol><li><a href="#toc28" tabindex="0">sam delete で削除されるリソース一覧</a></li></ol></li><li><a href="#toc29" tabindex="0">トラブルシューティング</a></li><li><a href="#toc30" tabindex="0">まとめ</a><ol><li><a href="#toc31" tabindex="0">SAMのメリットを実感できたポイント</a></li></ol></li><li><a href="#toc32" tabindex="0">コンソール版と比較してみる</a></li><li><a href="#toc33" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「通知システムをコードで管理して、チームで同じ環境を再現できるようにしたい」という要件に、<strong>AWS SAM（Serverless Application Model）</strong> は最適な選択肢のひとつです。</p>
<p>この記事では、<strong>AWS SAM</strong> を使って、SNS + SQS + Lambda によるPub/Sub通知システム（ファンアウトパターン）をゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">Publisher（aws sns publish）
  ↓ メッセージを1回発行するだけ
SNS Topic（sns-sqs-lambda-stack-topic）
  │
  ├─ サブスクリプション（フィルターなし）
  │    → LogQueue → LogFunction → CloudWatch Logs（全メッセージを記録）
  │
  └─ サブスクリプション（level=warning/critical のみ）
       → AlertQueue → AlertFunction → CloudWatch Logs（アラート処理）
                          ↓ 失敗（maxReceiveCount=3）
                        AlertDLQ</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li><code>AWS::SNS::Topic</code> / <code>AWS::SQS::Queue</code> / <code>AWS::SNS::Subscription</code> の SAM 定義</li>
<li><code>AWS::SQS::QueuePolicy</code> — コンソールでは自動追加されるが SAM では<strong>明示的な定義が必要</strong>な理由</li>
<li><code>FilterPolicy</code> で SNS フィルターポリシーを JSON 文字列で指定する方法</li>
<li><code>SQSPollerPolicy</code> による1行での SQS ポーリング権限付与</li>
</ul>
<p><strong>このハンズオンの特徴：</strong></p>
<ul>
<li><code>sam build</code> + <code>sam deploy</code> の <strong>2コマンドで全リソースをデプロイ</strong></li>
<li>SNS Topic + SQS キュー × 3 + Lambda × 2 + IAM ロール × 2 を <code>template.yaml</code> 1ファイルで管理</li>
<li><code>sam delete</code> で<strong>全リソースを一括削除</strong>（コンソール版では SNS / SQS × 3 / Lambda × 2 / IAM / ロググループを個別に削除）</li>
</ul>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">SAM vs コンソール：どれだけ違うか</span></h2>
<table>
<thead>
<tr>
<th>比較項目</th>
<th>SAM（コード）</th>
<th>コンソール（手動）</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SQS キューポリシー</strong></td>
<td><code>AWS::SQS::QueuePolicy</code> を明示的に定義が必要</td>
<td>SNS サブスクリプション作成時に<strong>自動追加</strong></td>
</tr>
<tr>
<td><strong>SNS フィルターポリシー</strong></td>
<td><code>FilterPolicy: &#39;{&quot;level&quot;: ...}&#39;</code> で定義</td>
<td>JSON を直接入力</td>
</tr>
<tr>
<td><strong>Lambda の SQS 権限</strong></td>
<td><code>SQSPollerPolicy</code> 1行で付与</td>
<td><code>AWSLambdaSQSQueueExecutionRole</code> を手動アタッチ</td>
</tr>
<tr>
<td><strong>削除</strong></td>
<td><code>sam delete</code> 1コマンド</td>
<td>SNS / SQS × 3 / Lambda × 2 / IAM / ロググループを個別に削除</td>
</tr>
<tr>
<td><strong>再現性</strong></td>
<td>チームで同じ環境を即座に再現できる</td>
<td>手順書が必要</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc3">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SNS Topic</strong></td>
<td>メッセージの送信先。複数のサブスクライバーに同時配信できる</td>
</tr>
<tr>
<td><strong>Pub/Sub</strong></td>
<td>Publisher（発行者）と Subscriber（購読者）が疎結合で通信するパターン</td>
</tr>
<tr>
<td><strong>ファンアウト</strong></td>
<td>1つのメッセージを複数の宛先に同時配信するパターン</td>
</tr>
<tr>
<td><strong>フィルターポリシー</strong></td>
<td>メッセージ属性に基づき、配信するかどうかを制御するルール</td>
</tr>
<tr>
<td><strong>DLQ（デッドレターキュー）</strong></td>
<td>処理に失敗したメッセージを退避させるキュー</td>
</tr>
<tr>
<td><strong>SNS エンベロープ</strong></td>
<td>SNS が SQS に配信する際にメッセージを包む JSON 構造</td>
</tr>
<tr>
<td><strong>SQSPollerPolicy</strong></td>
<td>SAM 組み込みポリシー。<code>sqs:ReceiveMessage</code> / <code>sqs:DeleteMessage</code> 権限を1行で付与</td>
</tr>
<tr>
<td><strong>RedrivePolicy</strong></td>
<td>SQS の DLQ 設定。失敗した場合の退避先キューと最大受信数を定義する</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">前提条件</span></h2>
<table>
<thead>
<tr>
<th>ツール</th>
<th>確認コマンド</th>
<th>最低バージョン目安</th>
</tr>
</thead>
<tbody>
<tr>
<td>AWS CLI v2</td>
<td><code>aws --version</code></td>
<td>2.x</td>
</tr>
<tr>
<td>AWS SAM CLI</td>
<td><code>sam --version</code></td>
<td>1.x</td>
</tr>
<tr>
<td>Python 3.12</td>
<td><code>python --version</code></td>
<td>3.12</td>
</tr>
</tbody>
</table>
<p>AWS認証確認:</p>
<pre><code class="language-cmd">aws sts get-caller-identity</code></pre>
<p>アカウントIDが表示されれば認証設定済みです。</p>
<hr>
<h2><span id="toc5">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SNS</strong></td>
<td>メッセージの発行・ファンアウト配信</td>
<td>月100万リクエストまで無料</td>
</tr>
<tr>
<td><strong>SQS</strong></td>
<td>メッセージキューイング（LogQueue / AlertQueue / AlertDLQ）</td>
<td>月100万リクエストまで無料</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>キューからメッセージを受信して処理（LogFunction / AlertFunction）</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda の実行権限管理</td>
<td>無料</td>
</tr>
<tr>
<td><strong>S3</strong></td>
<td>SAM デプロイパッケージ置き場</td>
<td>少量のため実質無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>Lambda の実行ログ</td>
<td>月5GBまで無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc6">Step 1: プロジェクトフォルダを開く</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\sns-sqs-lambda</code></pre>
<h3><span id="toc7">フォルダ構造</span></h3>
<pre><code class="language-plaintext">sns-sqs-lambda/
├── template.yaml           # SAMテンプレート（SNS + SQS × 3 + Lambda × 2）
├── samconfig.toml          # デプロイ設定（gitignore 対象・毎回手動作成が必要）
├── docs/
│   ├── 1_console.md        # AWSコンソール版手順
│   └── 2_sam.md            # SAM版手順
└── src/
    ├── log_handler.py      # 全ログ処理 Lambda（LogQueue 用）
    └── alert_handler.py    # アラート処理 Lambda（AlertQueue 用・DLQ テスト機能付き）</code></pre>
<hr>
<h2><span id="toc8">Step 2: SAMテンプレートの確認（template.yaml）</span></h2>
<p><code>template.yaml</code> は全 AWSリソースの設計図です。コンソール版との違いに注目しながらポイントを確認します。</p>
<h3><span id="toc9">SNS Topic とキューの定義</span></h3>
<pre><code class="language-yaml">Resources:
  NotificationTopic:
    Type: AWS::SNS::Topic          # メッセージの発行点

  LogQueue:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 180       # 可視性タイムアウト（DLQテストで 180秒×3回 待つ）

  AlertDLQ:
    Type: AWS::SQS::Queue          # AlertFunction が 3回失敗したメッセージの退避先

  AlertQueue:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 180
      RedrivePolicy:
        deadLetterTargetArn: !GetAtt AlertDLQ.Arn
        maxReceiveCount: 3         # 3回失敗で DLQ へ</code></pre>
<hr>
<h3><span id="toc10">SQS キューポリシー（SNS からの受信許可）</span></h3>
<p><strong>SAM版で最も注意が必要なポイント</strong>です。コンソールでは SNS サブスクリプション作成時に自動追加されますが、CloudFormation/SAM では<strong>明示的な定義が必要</strong>です。</p>
<pre><code class="language-yaml">  LogQueuePolicy:
    Type: AWS::SQS::QueuePolicy    # SNS が LogQueue に送信することを許可
    Properties:
      Queues:
        - !Ref LogQueue
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action: sqs:SendMessage
            Resource: !GetAtt LogQueue.Arn
            Condition:
              ArnEquals:
                aws:SourceArn: !Ref NotificationTopic   # この Topic からのみ許可

  AlertQueuePolicy:
    Type: AWS::SQS::QueuePolicy    # SNS が AlertQueue に送信することを許可
    Properties:
      Queues:
        - !Ref AlertQueue
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action: sqs:SendMessage
            Resource: !GetAtt AlertQueue.Arn
            Condition:
              ArnEquals:
                aws:SourceArn: !Ref NotificationTopic</code></pre>
<blockquote>
<p><strong><code>Condition: ArnEquals</code> を付ける理由：</strong><br />この条件がないと、同じ AWS アカウント内の<strong>他の SNS Topic</strong>からも SQS にメッセージを送れてしまいます。セキュリティのベストプラクティスとして、特定の Topic からのみ許可するよう制限します。</p>
</blockquote>
<hr>
<h3><span id="toc11">SNS サブスクリプション（ファンアウトとフィルター）</span></h3>
<pre><code class="language-yaml">  LogQueueSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: sqs
      TopicArn: !Ref NotificationTopic
      Endpoint: !GetAtt LogQueue.Arn
      # FilterPolicy なし → 全メッセージが届く

  # フィルターあり: level=warning/critical のみ AlertQueue に配信
  AlertQueueSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: sqs
      TopicArn: !Ref NotificationTopic
      Endpoint: !GetAtt AlertQueue.Arn
      FilterPolicy: '{"level": ["warning", "critical"]}'
      FilterPolicyScope: MessageAttributes   # メッセージ属性でフィルター（デフォルト）</code></pre>
<blockquote>
<p><strong><code>FilterPolicy</code> は JSON 文字列として指定：</strong><br />YAML 内では <code>&#39;{&quot;level&quot;: [&quot;warning&quot;, &quot;critical&quot;]}&#39;</code> のようにシングルクォートで囲んだ JSON 文字列として記述します。オブジェクト形式ではないことに注意してください。</p>
</blockquote>
<hr>
<h3><span id="toc12">Lambda 関数の定義</span></h3>
<pre><code class="language-yaml">  LogFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: log_handler.lambda_handler
      Runtime: python3.12
      Timeout: 30
      Policies:
        - SQSPollerPolicy:               # ReceiveMessage / DeleteMessage を自動付与
            QueueName: !GetAtt LogQueue.QueueName
      Events:
        LogQueueEvent:
          Type: SQS
          Properties:
            Queue: !GetAtt LogQueue.Arn
            BatchSize: 5                 # 1回の呼び出しで最大5件処理

  AlertFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: alert_handler.lambda_handler
      Runtime: python3.12
      Timeout: 30
      Policies:
        - SQSPollerPolicy:
            QueueName: !GetAtt AlertQueue.QueueName
      Events:
        AlertQueueEvent:
          Type: SQS
          Properties:
            Queue: !GetAtt AlertQueue.Arn
            BatchSize: 1    # 1件失敗しても他のメッセージに影響しないよう 1 に設定</code></pre>
<p><strong>template.yaml のポイント:</strong></p>
<table>
<thead>
<tr>
<th>ポイント</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AWS::SQS::QueuePolicy</code></td>
<td>コンソール版では自動付与されるが SAM では明示的に定義が必要</td>
</tr>
<tr>
<td><code>FilterPolicy</code></td>
<td>JSON 文字列で指定。<code>level</code> 属性が <code>warning</code> または <code>critical</code> のメッセージのみ通過</td>
</tr>
<tr>
<td><code>SQSPollerPolicy</code></td>
<td>SAM 組み込みポリシー。コンソール版では <code>AWSLambdaSQSQueueExecutionRole</code> を手動アタッチ</td>
</tr>
<tr>
<td><code>BatchSize: 1</code>（AlertFunction）</td>
<td>1件ずつ処理することで、失敗時に DLQ に移動するメッセージを1件に限定</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc13">Step 3: Lambda コードの確認（src/）</span></h2>
<p>コードはすでに作成済みです。各ファイルの役割を確認しておきます。</p>
<h3><span id="toc14">log_handler.py のポイント</span></h3>
<pre><code class="language-python">for record in event["Records"]:
    # SNS → SQS 配信では、SQS のボディに SNS エンベロープ（JSON文字列）が格納される
    sns_envelope = json.loads(record["body"])   # ← json.loads が必要
    message = sns_envelope["Message"]           # 実際のメッセージ本文
    attributes = sns_envelope.get("MessageAttributes", {})
    level = attributes.get("level", {}).get("Value", "info")</code></pre>
<blockquote>
<p><strong>SNS エンベロープの構造（SQS に届く body の中身）：</strong></p>
<pre><code class="language-json">{
  "Type": "Notification",
  "MessageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "TopicArn": "arn:aws:sns:ap-northeast-1:...:topic",
  "Message": "警告メッセージ",
  "MessageAttributes": {
    "level": {"Type": "String", "Value": "warning"}
  }
}</code></pre>
<p>SQS を直接使う場合（<code>record[&quot;body&quot;]</code> がそのまま文字列）とは構造が異なります。<br />SNS 経由の場合は必ず <code>json.loads</code> してエンベロープを展開してください。</p>
</blockquote>
<h3><span id="toc15">alert_handler.py のポイント</span></h3>
<ul>
<li><code>message == &quot;error&quot;</code> の場合は意図的に <code>ValueError</code> を発生させる（DLQ テスト用）</li>
<li><code>BatchSize: 1</code> のため、1件失敗すると AlertQueue に戻り、3回試行後 AlertDLQ へ移動する</li>
</ul>
<hr>
<h2><span id="toc16">Step 4: samconfig.toml を作成する</span></h2>
<p><code>samconfig.toml</code> は <code>.gitignore</code> で管理外のため、<strong>毎回手動で作成</strong>する必要があります。</p>
<p><code>sns-sqs-lambda/samconfig.toml</code> を新規作成して以下を貼り付けます。</p>
<pre><code class="language-toml">version = 0.1

[default.deploy.parameters]
stack_name = "sns-sqs-lambda-stack"
resolve_s3 = true
s3_prefix = "sns-sqs-lambda-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
disable_rollback = true
image_repositories = []

[default.global.parameters]
region = "ap-northeast-1"</code></pre>
<blockquote>
<p><strong><code>samconfig.toml</code> の置き場所：</strong><br /><code>sns-sqs-lambda/</code> フォルダの<strong>直下</strong>に置く必要があります。</p>
</blockquote>
<hr>
<h2><span id="toc17">Step 5: sam build（ビルド）</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\sns-sqs-lambda
sam build</code></pre>
<p>成功すると以下のように表示されます。</p>
<pre><code class="language-plaintext">Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml</code></pre>
<blockquote>
<p><strong><code>sam build</code> が行っていること：</strong><br /><code>src/</code> フォルダを ZIP パッケージ化して <code>.aws-sam/build/</code> に配置し、Lambda デプロイの準備を整えます。</p>
</blockquote>
<hr>
<h2><span id="toc18">Step 6: sam deploy（デプロイ）</span></h2>
<pre><code class="language-cmd">sam deploy</code></pre>
<p>変更内容が表示されて確認を求められます。</p>
<pre><code class="language-plaintext">Deploy this changeset? [y/N]: y</code></pre>
<p><code>y</code> を入力して進めます。数分でデプロイが完了します。</p>
<h3><span id="toc19">デプロイ完了の確認</span></h3>
<p>ターミナルに <strong>Outputs</strong> が表示されます。</p>
<pre><code class="language-plaintext">Outputs
----------------------------------------------------------------------
Key    TopicArn
Value  arn:aws:sns:ap-northeast-1:123456789012:sns-sqs-lambda-stack-topic

Key    LogQueueUrl
Value  https://sqs.ap-northeast-1.amazonaws.com/123456789012/sns-sqs-lambda-stack-log-queue

Key    AlertQueueUrl
Value  https://sqs.ap-northeast-1.amazonaws.com/123456789012/sns-sqs-lambda-stack-alert-queue

Key    AlertDLQUrl
Value  https://sqs.ap-northeast-1.amazonaws.com/123456789012/sns-sqs-lambda-stack-alert-dlq
----------------------------------------------------------------------</code></pre>
<p><strong><code>TopicArn</code> を控えておきます。</strong></p>
<h3><span id="toc20">デプロイされるリソース一覧</span></h3>
<pre><code class="language-plaintext">SNS Topic × 1          : sns-sqs-lambda-stack-topic
SNS サブスクリプション × 2  : LogQueue用（フィルターなし）/ AlertQueue用（フィルターあり）
SQS キュー × 3          : LogQueue / AlertQueue / AlertDLQ
Lambda 関数 × 2        : sns-sqs-lambda-stack-LogFunction-XXXX 他
IAM ロール × 2          : Lambda 用
S3                     : SAM デプロイパッケージ
CloudWatch Logs        : Lambda 自動生成ログ × 2</code></pre>
<hr>
<h2><span id="toc21">Step 7: 動作テスト</span></h2>
<h3><span id="toc22">事前準備: ARN を変数に設定する</span></h3>
<pre><code class="language-cmd">set TOPIC_ARN=arn:aws:sns:ap-northeast-1:123456789012:sns-sqs-lambda-stack-topic
set ALERT_DLQ_URL=https://sqs.ap-northeast-1.amazonaws.com/123456789012/sns-sqs-lambda-stack-alert-dlq</code></pre>
<blockquote>
<p><code>123456789012</code> を自分のアカウントIDに置き換えてください（Outputs の値をそのままコピーすればよいです）。</p>
</blockquote>
<hr>
<h3><span id="toc23">テスト1: level=info（LogFunction のみ受信）</span></h3>
<pre><code class="language-cmd">aws sns publish ^
  --topic-arn %TOPIC_ARN% ^
  --message "情報メッセージ" ^
  --message-attributes "{\"level\":{\"DataType\":\"String\",\"StringValue\":\"info\"}}" ^
  --region ap-northeast-1</code></pre>
<p>レスポンス例:</p>
<pre><code class="language-json">{
    "MessageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}</code></pre>
<p><strong>CloudWatch Logs で LogFunction を確認（数秒後）:</strong></p>
<pre><code class="language-cmd">aws logs tail /aws/lambda/sns-sqs-lambda-stack-LogFunction-XXXX --follow</code></pre>
<pre><code class="language-json">{"queue": "log", "level": "info", "message": "情報メッセージ"}
{"processedCount": 1}</code></pre>
<p><strong>AlertFunction は動作しないことを確認:</strong></p>
<p>Lambda コンソールで <code>AlertFunction</code> の呼び出し回数が増えていないことを確認します。</p>
<hr>
<h3><span id="toc24">テスト2: level=warning（ファンアウト確認）</span></h3>
<pre><code class="language-cmd">aws sns publish ^
  --topic-arn %TOPIC_ARN% ^
  --message "警告メッセージ" ^
  --message-attributes "{\"level\":{\"DataType\":\"String\",\"StringValue\":\"warning\"}}" ^
  --region ap-northeast-1</code></pre>
<p><strong>期待する動作: 1回の publish で LogFunction と AlertFunction の両方が動作する（ファンアウト）</strong></p>
<p><strong>LogFunction のログ:</strong></p>
<pre><code class="language-json">{"queue": "log", "level": "warning", "message": "警告メッセージ"}</code></pre>
<p><strong>AlertFunction のログ:</strong></p>
<pre><code class="language-json">{"queue": "alert", "level": "warning", "message": "警告メッセージ", "alert_triggered": true}</code></pre>
<p><!-- ![ファンアウト動作確認：LogFunction と AlertFunction が同時に実行](images/fanout-both-lambda.jpg) --></p>
<hr>
<h3><span id="toc25">テスト3: level=warning + body=error（DLQ テスト）</span></h3>
<pre><code class="language-cmd">aws sns publish ^
  --topic-arn %TOPIC_ARN% ^
  --message "error" ^
  --message-attributes "{\"level\":{\"DataType\":\"String\",\"StringValue\":\"warning\"}}" ^
  --region ap-northeast-1</code></pre>
<p><strong>期待する動作:</strong></p>
<ul>
<li><code>LogFunction</code> → 正常完了</li>
<li><code>AlertFunction</code> → <code>ValueError</code> で失敗 → 180秒後に再試行 × 3回 → <code>AlertDLQ</code> に移動</li>
</ul>
<blockquote>
<p><strong>DLQ に移動するまでの待機時間:</strong> 最大 180秒 × 3回 ≒ 9分。<br />CloudWatch Logs で <code>AlertFunction</code> に <code>[ERROR] ValueError</code> が 3回記録されたことを確認してから DLQ を確認するとよいです。</p>
</blockquote>
<p><strong>DLQ にメッセージが届いたか確認:</strong></p>
<pre><code class="language-cmd">aws sqs receive-message ^
  --queue-url %ALERT_DLQ_URL% ^
  --region ap-northeast-1</code></pre>
<p>レスポンスの <code>&quot;Body&quot;</code> フィールドに SNS エンベロープが格納されていれば DLQ 動作確認完了。</p>
<p><!-- ![DLQ にメッセージが届いた様子](images/dlq-message-arrived.jpg) --></p>
<hr>
<h2><span id="toc26">Step 8: AWSコンソールで確認（任意）</span></h2>
<p>SAMでデプロイしたリソースはコンソールでも確認できます。</p>
<ul>
<li><strong>SNS</strong>: SNS → <code>sns-sqs-lambda-stack-topic</code> → 「サブスクリプション」タブ → 2つのサブスクリプションと各フィルターポリシーを確認</li>
<li><strong>SQS</strong>: SQS → <code>sns-sqs-lambda-stack-alert-queue</code> → 「デッドレターキュー」タブ → AlertDLQ の ARN が設定されていることを確認</li>
<li><strong>Lambda</strong>: CloudWatch Logs で LogFunction / AlertFunction の呼び出し履歴を比較確認</li>
</ul>
<hr>
<h2><span id="toc27">Step 9: リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ずリソースを削除してください。</strong></p>
<pre><code class="language-cmd">sam delete --stack-name sns-sqs-lambda-stack --region ap-northeast-1</code></pre>
<pre><code class="language-plaintext">Are you sure you want to delete the stack sns-sqs-lambda-stack? [y/N]: y
Are you sure you want to delete the folder sns-sqs-lambda-stack in S3? [y/N]: y</code></pre>
<p>削除完了の確認:</p>
<pre><code class="language-cmd">aws cloudformation describe-stacks --stack-name sns-sqs-lambda-stack --region ap-northeast-1</code></pre>
<p><code>Stack with id sns-sqs-lambda-stack does not exist</code> が表示されれば削除完了。</p>
<h3><span id="toc28">sam delete で削除されるリソース一覧</span></h3>
<table>
<thead>
<tr>
<th>リソース</th>
<th>数</th>
</tr>
</thead>
<tbody>
<tr>
<td>SNS Topic + サブスクリプション</td>
<td>× 1 + × 2</td>
</tr>
<tr>
<td>SQS キュー（LogQueue / AlertQueue / AlertDLQ）</td>
<td>× 3</td>
</tr>
<tr>
<td>Lambda 関数（LogFunction / AlertFunction）</td>
<td>× 2</td>
</tr>
<tr>
<td>IAM ロール</td>
<td>× 2</td>
</tr>
<tr>
<td>Lambda デプロイパッケージ（S3）</td>
<td>× 1</td>
</tr>
<tr>
<td>CloudWatch Logs ロググループ</td>
<td>× 2</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>コンソール版との大きな違い（SAMのメリット）：</strong><br />コンソール版では SNS / SQS × 3 / Lambda × 2 / IAM / ロググループを個別に削除する必要があります。<br /><strong>SAMでは <code>sam delete</code> 1コマンドで全リソースを一括削除できます。</strong></p>
</blockquote>
<hr>
<h2><span id="toc29">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sam deploy</code> でキューポリシーエラー</td>
<td><code>AWS::SQS::QueuePolicy</code> の記述ミス</td>
<td>template.yaml の <code>LogQueuePolicy</code> / <code>AlertQueuePolicy</code> を確認</td>
</tr>
<tr>
<td><code>sns publish</code> 後 LogFunction が動作しない</td>
<td>SNS → SQS のサブスクリプションまたはキューポリシーが未設定</td>
<td><code>sam deploy</code> が正常完了しているか、スタックイベントを確認</td>
</tr>
<tr>
<td><code>level=warning</code> でも AlertFunction が動作しない</td>
<td>フィルターポリシーの属性名が間違っている</td>
<td><code>--message-attributes</code> の <code>&quot;level&quot;</code> がフィルターの <code>&quot;level&quot;</code> と一致しているか確認</td>
</tr>
<tr>
<td><code>json.loads(record[&quot;body&quot;])</code> でエラー</td>
<td>SQS を直接テストした（SNS 経由でない）</td>
<td>SNS publish コマンドを使っているか確認</td>
</tr>
<tr>
<td>DLQ にメッセージが届かない</td>
<td>可視性タイムアウト（180秒）× 3回 の待機中</td>
<td>最大 9分待つ</td>
</tr>
<tr>
<td><code>sam deploy</code> で <code>CAPABILITY_IAM</code> エラー</td>
<td><code>samconfig.toml</code> の <code>capabilities</code> が未設定</td>
<td><code>capabilities = &quot;CAPABILITY_IAM&quot;</code> を確認</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc30">まとめ</span></h2>
<p>今回のハンズオンで実現したこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SNS + SQS のファンアウト</strong></td>
<td>1回の publish で LogFunction と AlertFunction が並行して動作</td>
</tr>
<tr>
<td><strong>SNS フィルターポリシー</strong></td>
<td><code>level=info</code> は LogQueue のみ、<code>level=warning/critical</code> は両キューに配信</td>
</tr>
<tr>
<td><strong>DLQ</strong></td>
<td>AlertFunction が 3回失敗したメッセージが AlertDLQ に退避</td>
</tr>
<tr>
<td><strong>AWS::SQS::QueuePolicy</strong></td>
<td>コンソールでは自動追加、SAM では明示的定義が必要という差を体験</td>
</tr>
<tr>
<td><strong>一括削除</strong></td>
<td><code>sam delete</code> で全リソースをクリーンアップ</td>
</tr>
</tbody>
</table>
<h3><span id="toc31">SAMのメリットを実感できたポイント</span></h3>
<ul>
<li><code>SQSPollerPolicy</code> により Lambda の SQS ポーリング権限が1行で完結</li>
<li><code>FilterPolicy</code> で SNS フィルターポリシーをコードとして管理</li>
<li><code>sam delete</code> で SNS / SQS × 3 / Lambda × 2 / IAM を一括削除</li>
</ul>
<hr>
<h2><span id="toc32">コンソール版と比較してみる</span></h2>
<p>SAMが裏で何をやっているか、同じ構成をAWSコンソールのみで構築する手順をまとめました。コンソール版では SNS サブスクリプション作成時にキューポリシーが自動追加される仕組みや、フィルターポリシーの GUI 入力方法など、コンソールならではの操作を解説しています。</p>
<p><!-- TODO: コンソール版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc33">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-sns-sqs-lambda/">AWS SAM で SNS + SQS + Lambda 通知システムを構築しよう【Pub/Sub・ファンアウト・DLQ / コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-sns-sqs-lambda/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWSコンソールだけでSNS + SQS + Lambda 通知システムを構築する手順【Pub/Sub・ファンアウト・DLQ / SAM版との比較付き】</title>
		<link>https://caymezon.com/aws-handson-console-sns-sqs-lambda/</link>
					<comments>https://caymezon.com/aws-handson-console-sns-sqs-lambda/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 08 Mar 2026 00:44:37 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[AWSコンソール]]></category>
		<category><![CDATA[DLQ]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[Pub/Sub]]></category>
		<category><![CDATA[SAM比較]]></category>
		<category><![CDATA[SNS]]></category>
		<category><![CDATA[SQS]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[ファンアウト]]></category>
		<category><![CDATA[初心者]]></category>
		<category><![CDATA[通知システム]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20243</guid>

					<description><![CDATA[<p>目次 はじめにキーワード解説使用するAWSサービス全体の作業順序① SNS Topic を作成する② SQS キューを3つ作成する2-1. AlertDLQ（デッドレターキュー）を先に作成する2-2. LogQueue  [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-console-sns-sqs-lambda/">AWSコンソールだけでSNS + SQS + Lambda 通知システムを構築する手順【Pub/Sub・ファンアウト・DLQ / SAM版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-12" checked><label class="toc-title" for="toc-checkbox-12">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">キーワード解説</a></li><li><a href="#toc3" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc4" tabindex="0">全体の作業順序</a></li><li><a href="#toc5" tabindex="0">① SNS Topic を作成する</a></li><li><a href="#toc6" tabindex="0">② SQS キューを3つ作成する</a><ol><li><a href="#toc7" tabindex="0">2-1. AlertDLQ（デッドレターキュー）を先に作成する</a></li><li><a href="#toc8" tabindex="0">2-2. LogQueue を作成する（フィルターなし・全メッセージ用）</a></li><li><a href="#toc9" tabindex="0">2-3. AlertQueue を作成する（フィルターあり・アラート用）</a></li></ol></li><li><a href="#toc10" tabindex="0">③ SNS → SQS サブスクリプションを2つ作成する</a><ol><li><a href="#toc11" tabindex="0">3-1. LogQueue サブスクリプション（フィルターなし）</a></li><li><a href="#toc12" tabindex="0">3-2. AlertQueue サブスクリプション（フィルターあり）</a></li></ol></li><li><a href="#toc13" tabindex="0">④ Lambda 関数を2つ作成する</a><ol><li><a href="#toc14" tabindex="0">4-1. LogFunction（全ログ処理）</a></li><li><a href="#toc15" tabindex="0">4-2. AlertFunction（アラート処理・DLQ テスト機能付き）</a></li></ol></li><li><a href="#toc16" tabindex="0">⑤ SQS トリガーを各 Lambda に追加する</a><ol><li><a href="#toc17" tabindex="0">5-1. LogFunction に LogQueue トリガーを追加する</a></li><li><a href="#toc18" tabindex="0">5-2. AlertFunction に AlertQueue トリガーを追加する</a></li></ol></li><li><a href="#toc19" tabindex="0">⑥ 動作テスト</a><ol><li><a href="#toc20" tabindex="0">テスト1: level=info（LogFunction のみ受信）</a></li><li><a href="#toc21" tabindex="0">テスト2: level=warning（両 Lambda が受信 ＝ ファンアウト）</a></li><li><a href="#toc22" tabindex="0">テスト3: level=warning + body=error（AlertFunction 失敗 → DLQ）</a></li></ol></li><li><a href="#toc23" tabindex="0">⑦ リソースの削除</a><ol><li><a href="#toc24" tabindex="0">1. Lambda のトリガーを削除する</a></li><li><a href="#toc25" tabindex="0">2. Lambda 関数を削除する</a></li><li><a href="#toc26" tabindex="0">3. SNS サブスクリプションを削除する</a></li><li><a href="#toc27" tabindex="0">4. SNS Topic を削除する</a></li><li><a href="#toc28" tabindex="0">5. SQS キューを3つ削除する</a></li><li><a href="#toc29" tabindex="0">6. IAM ロールを削除する（任意）</a></li><li><a href="#toc30" tabindex="0">7. CloudWatch Logs のロググループを削除する（任意）</a></li></ol></li><li><a href="#toc31" tabindex="0">SAMとの対比</a></li><li><a href="#toc32" tabindex="0">トラブルシューティング</a></li><li><a href="#toc33" tabindex="0">まとめ</a><ol><li><a href="#toc34" tabindex="0">コンソール版で実感できたポイント</a></li></ol></li><li><a href="#toc35" tabindex="0">コンソール版と SAM 版を比較してみる</a></li><li><a href="#toc36" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「1回メッセージを送るだけで、複数のシステムに同時に通知したい」——そんな要件を実現する設計パターンが <strong>Pub/Sub（パブリッシュ・サブスクライブ）</strong> と <strong>ファンアウト</strong> です。</p>
<p>この記事では、<strong>AWSコンソールのみ</strong>を使って、SNS + SQS + Lambda による通知システムをゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">Publisher（aws sns publish）
  ↓ メッセージを1回発行するだけ
SNS Topic（NotificationTopic）
  │
  ├─ サブスクリプション（フィルターなし）
  │    → LogQueue → LogFunction → CloudWatch Logs（全メッセージを記録）
  │
  └─ サブスクリプション（level=warning/critical のみ）
       → AlertQueue → AlertFunction → CloudWatch Logs（アラート処理）
                          ↓ 処理失敗（3回）
                        AlertDLQ</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li><strong>Pub/Sub パターン</strong> — Publisher と Subscriber が疎結合で通信する仕組み</li>
<li><strong>ファンアウト</strong> — 1回の publish で複数の Lambda が並行して動作する体験</li>
<li><strong>SNS フィルターポリシー</strong> — メッセージ属性で配信先を絞り込む設定</li>
<li><strong>DLQ（デッドレターキュー）</strong> — 処理失敗メッセージの退避先を実際に確認</li>
</ul>
<hr>
<blockquote>
<p><strong>この記事は <a href="#">SAM版ハンズオン</a> の比較記事です。</strong><br />コンソール操作で SNS・SQS・Lambda の連携を視覚的に学び、SAM と何が違うのかを比較したい方向けです。</p>
</blockquote>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SNS Topic</strong></td>
<td>メッセージの送信先。複数のサブスクライバーに同時配信できる</td>
</tr>
<tr>
<td><strong>サブスクリプション</strong></td>
<td>Topic からメッセージを受け取るエンドポイントの設定</td>
</tr>
<tr>
<td><strong>Pub/Sub</strong></td>
<td>Publisher（発行者）と Subscriber（購読者）が疎結合で通信するパターン</td>
</tr>
<tr>
<td><strong>ファンアウト</strong></td>
<td>1つのメッセージを複数の宛先に同時配信するパターン</td>
</tr>
<tr>
<td><strong>フィルターポリシー</strong></td>
<td>メッセージ属性に基づき、配信するかどうかを制御するルール</td>
</tr>
<tr>
<td><strong>DLQ（デッドレターキュー）</strong></td>
<td>処理に失敗したメッセージを退避させるキュー</td>
</tr>
<tr>
<td><strong>SNS エンベロープ</strong></td>
<td>SNS が SQS に配信する際にメッセージを包む JSON 構造</td>
</tr>
<tr>
<td><strong>可視性タイムアウト</strong></td>
<td>SQS がメッセージを処理中に他のコンシューマーから見えなくする時間</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc3">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SNS</strong></td>
<td>メッセージの発行・ファンアウト配信</td>
<td>月100万リクエストまで無料</td>
</tr>
<tr>
<td><strong>SQS</strong></td>
<td>メッセージキューイング（LogQueue / AlertQueue / AlertDLQ）</td>
<td>月100万リクエストまで無料</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>キューからメッセージを受信して処理（LogFunction / AlertFunction）</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda の実行権限管理</td>
<td>無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>Lambda の実行ログ</td>
<td>月5GBまで無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">全体の作業順序</span></h2>
<pre><code class="language-plaintext">① SNS Topic を作成する
      ↓
② SQS キューを3つ作成する（AlertDLQ / LogQueue / AlertQueue）
      ↓
③ SNS → SQS サブスクリプションを2つ作成する
  （LogQueue: フィルターなし / AlertQueue: フィルターあり）
      ↓
④ Lambda 関数を2つ作成する
      ↓
⑤ SQS トリガーを各 Lambda に追加する
      ↓
⑥ 動作テスト（3パターン）
      ↓
⑦ リソースの削除</code></pre>
<blockquote>
<p><strong>AlertDLQ を先に作成する理由：</strong><br />AlertQueue の作成時に DLQ として AlertDLQ の ARN を指定するため、AlertDLQ を先に作る必要があります。</p>
</blockquote>
<hr>
<h2><span id="toc5">① SNS Topic を作成する</span></h2>
<p><strong>AWSコンソール → SNS → 「トピック」→「トピックを作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>タイプ</td>
<td><strong>スタンダード</strong></td>
</tr>
<tr>
<td>名前</td>
<td><code>NotificationTopic</code></td>
</tr>
</tbody>
</table>
<p>その他の設定はデフォルトのまま。「トピックを作成」をクリック。</p>
<p><strong>控えておく情報:</strong></p>
<ul>
<li><strong>トピック ARN</strong>（例: <code>arn:aws:sns:ap-northeast-1:123456789012:NotificationTopic</code>）</li>
</ul>
<blockquote>
<p><strong>スタンダードとFIFOの違い：</strong><br />スタンダードは順序保証なし・高スループット。FIFO は順序保証あり・1回限りの配信が必要な場合に使用します。今回のログ/アラート通知にはスタンダードが適しています。</p>
</blockquote>
<hr>
<h2><span id="toc6">② SQS キューを3つ作成する</span></h2>
<p><strong>AWSコンソール → SQS → 「キューを作成」</strong></p>
<h3><span id="toc7">2-1. AlertDLQ（デッドレターキュー）を先に作成する</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>タイプ</td>
<td>スタンダード</td>
</tr>
<tr>
<td>名前</td>
<td><code>AlertDLQ</code></td>
</tr>
<tr>
<td>メッセージ保持期間</td>
<td>1 日（学習用なので短く設定）</td>
</tr>
</tbody>
</table>
<p>「キューを作成」をクリック。</p>
<p><strong>控えておく情報:</strong></p>
<ul>
<li><strong>AlertDLQ の ARN</strong>（例: <code>arn:aws:sqs:ap-northeast-1:123456789012:AlertDLQ</code>）</li>
</ul>
<hr>
<h3><span id="toc8">2-2. LogQueue を作成する（フィルターなし・全メッセージ用）</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>タイプ</td>
<td>スタンダード</td>
</tr>
<tr>
<td>名前</td>
<td><code>LogQueue</code></td>
</tr>
<tr>
<td>可視性タイムアウト</td>
<td><strong>180</strong> 秒</td>
</tr>
</tbody>
</table>
<p>「キューを作成」をクリック。</p>
<blockquote>
<p><strong>可視性タイムアウトを 180 秒にする理由：</strong><br />Lambda のデフォルトタイムアウト（3秒）より長く設定することで、処理中に他のコンシューマーがメッセージを二重取得するのを防ぎます。DLQ テストでは 180秒 × 3回 ≒ 9分待つことになります。</p>
</blockquote>
<hr>
<h3><span id="toc9">2-3. AlertQueue を作成する（フィルターあり・アラート用）</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>タイプ</td>
<td>スタンダード</td>
</tr>
<tr>
<td>名前</td>
<td><code>AlertQueue</code></td>
</tr>
<tr>
<td>可視性タイムアウト</td>
<td><strong>180</strong> 秒</td>
</tr>
</tbody>
</table>
<p>「デッドレターキュー」セクション:</p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>デッドレターキューを有効にする</td>
<td><strong>オン</strong></td>
</tr>
<tr>
<td>デッドレターキューの ARN</td>
<td><code>AlertDLQ</code> の ARN を貼り付ける</td>
</tr>
<tr>
<td>最大受信数</td>
<td><strong>3</strong></td>
</tr>
</tbody>
</table>
<p>「キューを作成」をクリック。</p>
<p><strong>控えておく情報:</strong></p>
<ul>
<li><strong>LogQueue の ARN</strong></li>
<li><strong>AlertQueue の ARN</strong></li>
</ul>
<hr>
<h2><span id="toc10">③ SNS → SQS サブスクリプションを2つ作成する</span></h2>
<blockquote>
<p><strong>SQS キューポリシーの自動追加について：</strong><br />コンソールで SNS → SQS サブスクリプションを作成すると、<br />SNS が SQS キューのアクセスポリシーに <code>sns.amazonaws.com</code> からの <code>sqs:SendMessage</code> 権限を自動追加します。<br />手動でのポリシー設定は不要です（SAM版では <code>AWS::SQS::QueuePolicy</code> を明示的に定義する必要があります）。</p>
</blockquote>
<h3><span id="toc11">3-1. LogQueue サブスクリプション（フィルターなし）</span></h3>
<p><strong>SNS → <code>NotificationTopic</code> → 「サブスクリプション」タブ → 「サブスクリプションを作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>プロトコル</td>
<td><strong>Amazon SQS</strong></td>
</tr>
<tr>
<td>エンドポイント</td>
<td><code>LogQueue</code> の ARN</td>
</tr>
<tr>
<td>サブスクリプションフィルターポリシー</td>
<td><strong>設定しない</strong>（空欄のまま）</td>
</tr>
</tbody>
</table>
<p>「サブスクリプションを作成」をクリック。</p>
<hr>
<h3><span id="toc12">3-2. AlertQueue サブスクリプション（フィルターあり）</span></h3>
<p><strong>SNS → <code>NotificationTopic</code> → 「サブスクリプション」タブ → 「サブスクリプションを作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>プロトコル</td>
<td><strong>Amazon SQS</strong></td>
</tr>
<tr>
<td>エンドポイント</td>
<td><code>AlertQueue</code> の ARN</td>
</tr>
</tbody>
</table>
<p>「サブスクリプションフィルターポリシー」セクション:</p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>サブスクリプションフィルターポリシー</td>
<td><strong>有効にする</strong></td>
</tr>
<tr>
<td>フィルターポリシーのスコープ</td>
<td><strong>メッセージ属性</strong>（デフォルト）</td>
</tr>
<tr>
<td>JSON エディタ</td>
<td>以下を入力</td>
</tr>
</tbody>
</table>
<pre><code class="language-json">{
  "level": ["warning", "critical"]
}</code></pre>
<blockquote>
<p><strong>フィルターポリシーの意味：</strong><br /><code>level</code> メッセージ属性の値が <code>&quot;warning&quot;</code> または <code>&quot;critical&quot;</code> のメッセージのみ AlertQueue に配信します。<br /><code>level=info</code> のメッセージは AlertQueue に届かない（LogQueue には届く）。</p>
</blockquote>
<blockquote>
<p><strong>よくある間違い（フィルターが動作しない場合）：</strong></p>
<ul>
<li>スコープが「<strong>メッセージ本文</strong>」になっている → **「メッセージ属性」**に変更する</li>
<li>JSON のスペルミス（<code>&quot;warning&quot;</code> のクォートが抜けている等）</li>
</ul>
</blockquote>
<p>「サブスクリプションを作成」をクリック。</p>
<hr>
<h2><span id="toc13">④ Lambda 関数を2つ作成する</span></h2>
<p><strong>AWSコンソール → Lambda → 「関数の作成」</strong>（共通設定）</p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>作成方法</td>
<td>一から作成</td>
</tr>
<tr>
<td>ランタイム</td>
<td><strong>Python 3.12</strong></td>
</tr>
<tr>
<td>アーキテクチャ</td>
<td>x86_64</td>
</tr>
<tr>
<td>実行ロール</td>
<td>「基本的な Lambda アクセス権限で新しいロールを作成」（デフォルトのまま）</td>
</tr>
</tbody>
</table>
<p>コードを貼り付けたあと、必ず <strong>「Deploy」</strong> ボタンを押してコードを保存します。</p>
<hr>
<h3><span id="toc14">4-1. LogFunction（全ログ処理）</span></h3>
<p><strong>関数名</strong>: <code>LogFunction</code></p>
<p>コードエディタで <code>lambda_function.py</code> を開き、以下を貼り付けて「Deploy」をクリック。</p>
<pre><code class="language-python">import json


def lambda_handler(event, context):
    results = []

    for record in event["Records"]:
        sns_envelope = json.loads(record["body"])
        message = sns_envelope["Message"]  # 実際のメッセージ本文
        attributes = sns_envelope.get("MessageAttributes", {})
        level = attributes.get("level", {}).get("Value", "info")

        result = {
            "queue": "log",
            "level": level,
            "message": message,
        }
        print(json.dumps(result, ensure_ascii=False))
        results.append(result)

    print(json.dumps({"processedCount": len(results)}, ensure_ascii=False))
    return {"processedCount": len(results)}</code></pre>
<blockquote>
<p><strong><code>json.loads(record[&quot;body&quot;])</code> が必要な理由：</strong><br />SNS → SQS 配信では、SQS のメッセージボディに <strong>SNS エンベロープ</strong>（JSON文字列）が格納されます。<br />そのため <code>record[&quot;body&quot;]</code> はそのまま使えず、<code>json.loads</code> でパースする必要があります。</p>
<pre><code class="language-json">{
  "Type": "Notification",
  "TopicArn": "arn:aws:sns:...:NotificationTopic",
  "Message": "警告メッセージ",
  "MessageAttributes": {
    "level": {"Type": "String", "Value": "warning"}
  }
}</code></pre>
</blockquote>
<hr>
<h3><span id="toc15">4-2. AlertFunction（アラート処理・DLQ テスト機能付き）</span></h3>
<p><strong>関数名</strong>: <code>AlertFunction</code></p>
<pre><code class="language-python">import json


def lambda_handler(event, context):
    results = []

    for record in event["Records"]:
        sns_envelope = json.loads(record["body"])
        message = sns_envelope["Message"]
        attributes = sns_envelope.get("MessageAttributes", {})
        level = attributes.get("level", {}).get("Value", "unknown")

        # DLQテスト用: "error" というメッセージを受信したら意図的に例外発生
        if message.strip().lower() == "error":
            raise ValueError(
                f"アラート処理に失敗: level={level}, message={message}"
            )

        result = {
            "queue": "alert",
            "level": level,
            "message": message,
            "alert_triggered": True,
        }
        print(json.dumps(result, ensure_ascii=False))
        results.append(result)

    print(json.dumps({"processedCount": len(results)}, ensure_ascii=False))
    return {"processedCount": len(results)}</code></pre>
<blockquote>
<p><strong>DLQ テストのしくみ：</strong><br /><code>message == &quot;error&quot;</code> のとき意図的に <code>ValueError</code> を発生させています。<br />SQS は処理失敗を検知してメッセージをキューに戻し、可視性タイムアウト（180秒）後に再試行します。<br />これを3回繰り返すと <code>AlertDLQ</code> に移動します。</p>
</blockquote>
<hr>
<h2><span id="toc16">⑤ SQS トリガーを各 Lambda に追加する</span></h2>
<h3><span id="toc17">5-1. LogFunction に LogQueue トリガーを追加する</span></h3>
<p><strong>Lambda → <code>LogFunction</code> → 「設定」タブ → 「トリガー」→「トリガーを追加」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>ソース</td>
<td><strong>SQS</strong></td>
</tr>
<tr>
<td>SQS キュー</td>
<td><code>LogQueue</code></td>
</tr>
<tr>
<td>バッチサイズ</td>
<td><strong>5</strong></td>
</tr>
<tr>
<td>トリガーの有効化</td>
<td>オン</td>
</tr>
</tbody>
</table>
<p>「追加」をクリック。</p>
<p><strong>権限の追加:</strong></p>
<p>Lambda → <code>LogFunction</code> → 「設定」タブ → 「アクセス権限」→ 実行ロール名をクリック（IAM コンソールへ）<br />→「許可を追加」→「ポリシーをアタッチ」→ <code>AWSLambdaSQSQueueExecutionRole</code> をアタッチ。</p>
<blockquote>
<p><strong><code>AWSLambdaSQSQueueExecutionRole</code> が必要な理由：</strong><br />Lambda が SQS からメッセージを受信（<code>sqs:ReceiveMessage</code>）・削除（<code>sqs:DeleteMessage</code>）するために必要な権限がまとまったポリシーです。これがないと Lambda がメッセージを取得できません。</p>
</blockquote>
<hr>
<h3><span id="toc18">5-2. AlertFunction に AlertQueue トリガーを追加する</span></h3>
<p><strong>Lambda → <code>AlertFunction</code> → 「設定」タブ → 「トリガー」→「トリガーを追加」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>ソース</td>
<td><strong>SQS</strong></td>
</tr>
<tr>
<td>SQS キュー</td>
<td><code>AlertQueue</code></td>
</tr>
<tr>
<td>バッチサイズ</td>
<td><strong>1</strong></td>
</tr>
<tr>
<td>トリガーの有効化</td>
<td>オン</td>
</tr>
</tbody>
</table>
<p>「追加」をクリック。権限の追加（<code>AWSLambdaSQSQueueExecutionRole</code>）も同様に実施する。</p>
<blockquote>
<p><strong>AlertFunction のバッチサイズを 1 にする理由：</strong><br />1件ずつ処理することで、1件が失敗しても他のメッセージに影響が出ません。<br />DLQ へ移動するメッセージを1件に限定できるため、テストが分かりやすくなります。</p>
</blockquote>
<hr>
<h2><span id="toc19">⑥ 動作テスト</span></h2>
<p>3つのシナリオで SNS フィルター・ファンアウト・DLQ の動作を確認します。</p>
<table>
<thead>
<tr>
<th>テスト</th>
<th>メッセージ属性</th>
<th>LogFunction</th>
<th>AlertFunction</th>
<th>確認ポイント</th>
</tr>
</thead>
<tbody>
<tr>
<td>テスト1</td>
<td><code>level=info</code></td>
<td>処理 ✓</td>
<td>処理しない</td>
<td><strong>フィルター</strong>動作</td>
</tr>
<tr>
<td>テスト2</td>
<td><code>level=warning</code></td>
<td>処理 ✓</td>
<td>処理 ✓</td>
<td><strong>ファンアウト</strong>（1発行→2Lambda）</td>
</tr>
<tr>
<td>テスト3</td>
<td><code>level=warning</code> + body=<code>error</code></td>
<td>処理 ✓</td>
<td>失敗→DLQ</td>
<td><strong>DLQ</strong> 動作</td>
</tr>
</tbody>
</table>
<hr>
<h3><span id="toc20">テスト1: level=info（LogFunction のみ受信）</span></h3>
<pre><code class="language-cmd">aws sns publish ^
  --topic-arn arn:aws:sns:ap-northeast-1:123456789012:NotificationTopic ^
  --message "情報メッセージ" ^
  --message-attributes "{\"level\":{\"DataType\":\"String\",\"StringValue\":\"info\"}}" ^
  --region ap-northeast-1</code></pre>
<blockquote>
<p><code>123456789012</code> を自分のアカウントIDに置き換えてください。</p>
</blockquote>
<p><strong>期待する動作:</strong></p>
<ul>
<li><code>LogFunction</code> が処理 → CloudWatch Logs にログが記録される</li>
<li><code>AlertFunction</code> は動作しない（フィルターで除外）</li>
</ul>
<p><strong>CloudWatch Logs で確認:</strong></p>
<p>Lambda → <code>LogFunction</code> → 「モニタリング」タブ → 「CloudWatch Logs を表示」</p>
<pre><code class="language-json">{"queue": "log", "level": "info", "message": "情報メッセージ"}
{"processedCount": 1}</code></pre>
<hr>
<h3><span id="toc21">テスト2: level=warning（両 Lambda が受信 ＝ ファンアウト）</span></h3>
<pre><code class="language-cmd">aws sns publish ^
  --topic-arn arn:aws:sns:ap-northeast-1:123456789012:NotificationTopic ^
  --message "警告メッセージ" ^
  --message-attributes "{\"level\":{\"DataType\":\"String\",\"StringValue\":\"warning\"}}" ^
  --region ap-northeast-1</code></pre>
<p><strong>期待する動作:</strong></p>
<ul>
<li><code>LogFunction</code> が処理（<code>level=warning</code> を受信）</li>
<li><code>AlertFunction</code> も処理（フィルターを通過）</li>
<li><strong>1回の publish で 2つの Lambda が並行して動作する</strong> ← ファンアウトのポイント</li>
</ul>
<p><strong>LogFunction のログ:</strong></p>
<pre><code class="language-json">{"queue": "log", "level": "warning", "message": "警告メッセージ"}</code></pre>
<p><strong>AlertFunction のログ:</strong></p>
<pre><code class="language-json">{"queue": "alert", "level": "warning", "message": "警告メッセージ", "alert_triggered": true}</code></pre>
<p><!-- ![ファンアウト動作確認：LogFunction と AlertFunction が同時に実行](images/fanout-both-lambda.jpg) --></p>
<hr>
<h3><span id="toc22">テスト3: level=warning + body=error（AlertFunction 失敗 → DLQ）</span></h3>
<pre><code class="language-cmd">aws sns publish ^
  --topic-arn arn:aws:sns:ap-northeast-1:123456789012:NotificationTopic ^
  --message "error" ^
  --message-attributes "{\"level\":{\"DataType\":\"String\",\"StringValue\":\"warning\"}}" ^
  --region ap-northeast-1</code></pre>
<p><strong>期待する動作:</strong></p>
<ul>
<li><code>LogFunction</code> → 正常処理（<code>message=error</code> でも例外なし）</li>
<li><code>AlertFunction</code> → <code>ValueError</code> 発生</li>
<li>AlertQueue の可視性タイムアウト（180秒）後に再試行 × 3回 → <code>AlertDLQ</code> に移動</li>
</ul>
<blockquote>
<p><strong>DLQ に移動するまでの待機時間:</strong> 最大 180秒 × 3回 ≒ 9分かかります。<br />CloudWatch Logs で <code>AlertFunction</code> に <code>[ERROR] ValueError</code> が 3回記録されたことを確認してから DLQ をポーリングするとよいです。</p>
</blockquote>
<p><strong>DLQ にメッセージが届いたか確認:</strong></p>
<p>SQS → <code>AlertDLQ</code> → 「メッセージを送受信」→「メッセージをポーリング」</p>
<p><code>&quot;error&quot;</code> のメッセージ（SNS エンベロープ形式）が表示されれば DLQ 動作確認完了。</p>
<p><!-- ![AlertDLQ にメッセージが届いた様子](images/dlq-message-arrived.jpg) --></p>
<hr>
<h2><span id="toc23">⑦ リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ず削除してください。</strong></p>
<h3><span id="toc24">1. Lambda のトリガーを削除する</span></h3>
<ul>
<li><strong>Lambda → <code>LogFunction</code> → 「設定」→「トリガー」→ SQS トリガーを削除</strong></li>
<li><strong>Lambda → <code>AlertFunction</code> → 同様に削除</strong></li>
</ul>
<h3><span id="toc25">2. Lambda 関数を削除する</span></h3>
<ul>
<li><code>LogFunction</code> / <code>AlertFunction</code> を削除</li>
</ul>
<h3><span id="toc26">3. SNS サブスクリプションを削除する</span></h3>
<p><strong>SNS → <code>NotificationTopic</code> → 「サブスクリプション」タブ → 2つのサブスクリプションを削除</strong></p>
<h3><span id="toc27">4. SNS Topic を削除する</span></h3>
<p><strong>SNS → トピック → <code>NotificationTopic</code> → 「削除」</strong></p>
<h3><span id="toc28">5. SQS キューを3つ削除する</span></h3>
<p><strong>SQS → <code>LogQueue</code> / <code>AlertQueue</code> / <code>AlertDLQ</code> を各々削除</strong></p>
<h3><span id="toc29">6. IAM ロールを削除する（任意）</span></h3>
<p><strong>IAM → ロール → <code>LogFunction-role-XXXX</code> / <code>AlertFunction-role-XXXX</code> を削除</strong></p>
<h3><span id="toc30">7. CloudWatch Logs のロググループを削除する（任意）</span></h3>
<p><strong>CloudWatch → ロググループ → <code>/aws/lambda/LogFunction</code> / <code>/aws/lambda/AlertFunction</code> を削除</strong></p>
<blockquote>
<p><strong>SAM版との大きな違い（SAMのメリット）：</strong><br />SAM版では <code>sam delete</code> 1コマンドで上記すべてのリソースを一括削除できます。</p>
</blockquote>
<hr>
<h2><span id="toc31">SAMとの対比</span></h2>
<table>
<thead>
<tr>
<th>SAMの記述</th>
<th>コンソールでやること</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AWS::SNS::Topic</code></td>
<td>SNS → トピックを作成</td>
</tr>
<tr>
<td><code>AWS::SQS::QueuePolicy</code></td>
<td>SNS サブスクリプション作成時に<strong>自動追加</strong>される</td>
</tr>
<tr>
<td><code>AWS::SNS::Subscription</code></td>
<td>SNS → サブスクリプションを作成（フィルターポリシー含む）</td>
</tr>
<tr>
<td><code>FilterPolicy: &#39;{&quot;level&quot;: ...}&#39;</code></td>
<td>サブスクリプション作成時の「フィルターポリシー」に JSON を入力</td>
</tr>
<tr>
<td><code>SQSPollerPolicy</code> × 2</td>
<td>Lambda の IAM ロールに <code>AWSLambdaSQSQueueExecutionRole</code> をアタッチ</td>
</tr>
<tr>
<td><code>sam delete</code></td>
<td>SNS / SQS × 3 / Lambda × 2 / IAM / ロググループを個別に削除</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>コンソール版の最大のつまずきポイント：</strong><br />SNS の「サブスクリプション」を作り忘れると、SNS publish しても SQS キューにメッセージが届きません。<br />「キューにメッセージが来ない」と感じたら、まずサブスクリプションが正しく作成されているか確認しましょう。</p>
</blockquote>
<hr>
<h2><span id="toc32">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td>SNS publish してもキューにメッセージが来ない</td>
<td><strong>SNS サブスクリプションが未作成</strong></td>
<td>SNS → <code>NotificationTopic</code> → 「サブスクリプション」タブで2つ作成されているか確認</td>
</tr>
<tr>
<td>SNS publish してもキューにメッセージが来ない（その2）</td>
<td>SQS キューポリシーが設定されていない</td>
<td>SQS → キュー → 「アクセスポリシー」タブで <code>sns.amazonaws.com</code> からの <code>sqs:SendMessage</code> があるか確認</td>
</tr>
<tr>
<td><code>level=warning</code> でも AlertQueue に届かない</td>
<td>フィルターポリシーのスコープが「メッセージ本文」になっている</td>
<td>SNS → サブスクリプション → フィルターポリシーのスコープを**「メッセージ属性」**に変更</td>
</tr>
<tr>
<td>Lambda が実行されない</td>
<td>SQS トリガーが設定されていない</td>
<td>⑤ の手順でトリガーを追加する</td>
</tr>
<tr>
<td>Lambda が実行されない（SQS権限エラー）</td>
<td><code>AWSLambdaSQSQueueExecutionRole</code> がアタッチされていない</td>
<td>⑤ の権限追加手順を実施する</td>
</tr>
<tr>
<td>DLQ にメッセージが届かない</td>
<td>可視性タイムアウト（180秒）× 3回 の待機中</td>
<td>最大 9分待つ。CloudWatch Logs で 3回の ERROR ログを確認してからポーリング</td>
</tr>
<tr>
<td>SNS エンベロープの構造が分からない</td>
<td>Lambda のコードで <code>print(record[&quot;body&quot;])</code> して CloudWatch Logs で確認する</td>
<td></td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc33">まとめ</span></h2>
<p>今回のハンズオンで体験できたこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Pub/Sub パターン</strong></td>
<td>Publisher は誰が受け取るか知らずに SNS Topic に発行するだけ。Subscriber（SQS）が購読して処理</td>
</tr>
<tr>
<td><strong>ファンアウト</strong></td>
<td><code>level=warning</code> の1回の publish で LogFunction と AlertFunction の両方が並行して動作</td>
</tr>
<tr>
<td><strong>SNS フィルターポリシー</strong></td>
<td><code>level=info</code> は LogQueue のみ配信、<code>level=warning/critical</code> は両キューに配信</td>
</tr>
<tr>
<td><strong>DLQ</strong></td>
<td>AlertFunction が 3回失敗したメッセージが AlertDLQ に退避</td>
</tr>
</tbody>
</table>
<h3><span id="toc34">コンソール版で実感できたポイント</span></h3>
<ul>
<li>SNS Topic / SQS キュー / Lambda という3つのリソースの<strong>組み合わせ方</strong>が視覚的に理解できる</li>
<li>SNS サブスクリプションのフィルターポリシーJSON を直接入力することで、ルールの書き方が身につく</li>
<li>リソースを1つずつ作成することで、各サービスの役割と依存関係が明確になる</li>
</ul>
<hr>
<h2><span id="toc35">コンソール版と SAM 版を比較してみる</span></h2>
<p>コンソールで SNS + SQS + Lambda の連携を理解したら、SAM で同じ構成をコードで定義することで「SAM が何を自動化しているか」が明確になります。<code>AWS::SQS::QueuePolicy</code> の明示的な記述や <code>SQSPollerPolicy</code> によるワンライン権限付与など、コンソールでの手動操作がコードに対応しています。</p>
<p><!-- TODO: SAM版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc36">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-console-sns-sqs-lambda/">AWSコンソールだけでSNS + SQS + Lambda 通知システムを構築する手順【Pub/Sub・ファンアウト・DLQ / SAM版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-console-sns-sqs-lambda/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWSコンソールだけでCognito + API Gateway + Lambda の認証付きAPIを構築する手順【SAM版との比較付き / 新UI対応】</title>
		<link>https://caymezon.com/aws-handson-console-cognito-api-gateway-lambda/</link>
					<comments>https://caymezon.com/aws-handson-console-cognito-api-gateway-lambda/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 01 Mar 2026 08:50:35 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[APIGateway]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cognito]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[SAM比較]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[初心者]]></category>
		<category><![CDATA[新UI]]></category>
		<category><![CDATA[認証]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20237</guid>

					<description><![CDATA[<p>目次 はじめにCognito 新UIで戸惑いやすいポイント（先読み）キーワード解説前提条件使用するAWSサービス全体の作業順序① Cognito User Pool を作成する1-1. アプリケーションを定義する1-2. [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-console-cognito-api-gateway-lambda/">AWSコンソールだけでCognito + API Gateway + Lambda の認証付きAPIを構築する手順【SAM版との比較付き / 新UI対応】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-14" checked><label class="toc-title" for="toc-checkbox-14">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">Cognito 新UIで戸惑いやすいポイント（先読み）</a></li><li><a href="#toc3" tabindex="0">キーワード解説</a></li><li><a href="#toc4" tabindex="0">前提条件</a></li><li><a href="#toc5" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc6" tabindex="0">全体の作業順序</a></li><li><a href="#toc7" tabindex="0">① Cognito User Pool を作成する</a><ol><li><a href="#toc8" tabindex="0">1-1. アプリケーションを定義する</a></li><li><a href="#toc9" tabindex="0">1-2. オプションを設定する</a></li><li><a href="#toc10" tabindex="0">1-3. リターン URL（スキップ）</a></li><li><a href="#toc11" tabindex="0">控えておく情報</a></li></ol></li><li><a href="#toc12" tabindex="0">② テスト用アプリクライアントを作成する</a><ol><li><a href="#toc13" tabindex="0">2-1. アプリケーションを定義する</a></li><li><a href="#toc14" tabindex="0">2-2. リターン URL（スキップ）</a></li><li><a href="#toc15" tabindex="0">2-3. Client secret configuration</a></li><li><a href="#toc16" tabindex="0">2-4. 認証フローを確認する</a></li></ol></li><li><a href="#toc17" tabindex="0">③ Lambda 関数を作成する</a><ol><li><a href="#toc18" tabindex="0">コードの入力</a></li></ol></li><li><a href="#toc19" tabindex="0">④ API Gateway REST API を作成する</a><ol><li><a href="#toc20" tabindex="0">4-1. API タイプの選択</a></li><li><a href="#toc21" tabindex="0">4-2. リソース /hello を作成する</a></li><li><a href="#toc22" tabindex="0">4-3. リソース /profile を作成する</a></li><li><a href="#toc23" tabindex="0">4-4. Cognito Authorizer を作成する</a></li><li><a href="#toc24" tabindex="0">4-5. /profile GET メソッドに Authorizer を設定する</a></li><li><a href="#toc25" tabindex="0">4-6. API をデプロイする</a></li></ol></li><li><a href="#toc26" tabindex="0">⑤ テストユーザーを作成する</a></li><li><a href="#toc27" tabindex="0">⑥ 動作テスト</a><ol><li><a href="#toc28" tabindex="0">テスト1: 公開エンドポイント（/hello）→ 200</a></li><li><a href="#toc29" tabindex="0">テスト2: 未認証でプライベートエンドポイント（/profile）→ 401</a></li><li><a href="#toc30" tabindex="0">テスト3: IDトークンを取得する</a></li><li><a href="#toc31" tabindex="0">テスト4: 認証ありでプライベートエンドポイント（/profile）→ 200</a></li></ol></li><li><a href="#toc32" tabindex="0">⑦ リソースの削除</a><ol><li><a href="#toc33" tabindex="0">1. API Gateway を削除する</a></li><li><a href="#toc34" tabindex="0">2. Lambda 関数を削除する</a></li><li><a href="#toc35" tabindex="0">3. Cognito User Pool を削除する</a></li><li><a href="#toc36" tabindex="0">4. IAM ロールを削除する（任意）</a></li><li><a href="#toc37" tabindex="0">5. CloudWatch Logs のロググループを削除する（任意）</a></li></ol></li><li><a href="#toc38" tabindex="0">SAMとの対比</a></li><li><a href="#toc39" tabindex="0">トラブルシューティング</a></li><li><a href="#toc40" tabindex="0">まとめ</a></li><li><a href="#toc41" tabindex="0">コンソール版とSAM版を比較してみる</a></li><li><a href="#toc42" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「ログインしたユーザーだけが使えるAPIを作りたい」——そんな要件を、<strong>Amazon Cognito + API Gateway + Lambda</strong> はサーバー側の認証コードをほぼ書かずに実現します。</p>
<p>この記事では、<strong>AWSコンソールのみ</strong>を使って、以下の構成の認証付き REST API をゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">クライアント（curl）
  │
  ├─ GET /hello  → 認証不要 → Lambda → 200 OK
  │
  └─ GET /profile
        ├─ Authorizationヘッダーなし → API Gateway が 401 を返す（Lambda は呼ばれない）
        └─ Authorization: &lt;IDトークン&gt;
              → Cognito Authorizer が JWT を検証（Lambda 不要）
              → Lambda → email / sub を返す → 200 OK</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li>Amazon Cognito <strong>User Pool</strong> によるユーザー管理と JWT 発行</li>
<li>API Gateway の <strong>Cognito Authorizer</strong> を使ったエンドポイントごとの認証制御</li>
<li>Lambda 側で認証コードを書かずに <code>claims</code>（ユーザー情報）を取得するパターン</li>
<li><strong>新UI対応</strong> — Cognito コンソールの大幅変更点と、操作でハマりやすいポイント</li>
</ul>
<hr>
<blockquote>
<p><strong>この記事は <a href="#">SAM版ハンズオン</a> の比較記事です。</strong><br />コンソール操作でCognitoとAPI Gatewayの連携を視覚的に学び、SAMと何が違うのかを比較したい方向けです。</p>
</blockquote>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">Cognito 新UIで戸惑いやすいポイント（先読み）</span></h2>
<p>操作を始める前に、<strong>2026年時点の新UI</strong>で戸惑いやすい変更点を把握しておきましょう。</p>
<table>
<thead>
<tr>
<th>変更点</th>
<th>旧UI</th>
<th>新UI（現在）</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>User Pool 作成の入口</strong></td>
<td>「ユーザープールを作成」ボタンから</td>
<td>**「アプリケーションを作成」**ボタンから（名称が変わった）</td>
</tr>
<tr>
<td><strong>作成の粒度</strong></td>
<td>User Pool → App Client を別々に作成</td>
<td><strong>1ページのウィザード</strong>で User Pool とクライアントを同時作成</td>
</tr>
<tr>
<td><strong>クライアントシークレット</strong></td>
<td>デフォルトなし</td>
<td>「従来のウェブアプリケーション」を選ぶと<strong>シークレット必須</strong>になる</td>
</tr>
<tr>
<td><strong>User Pool 名</strong></td>
<td>指定した名前がそのまま付く</td>
<td>アプリ名の前に <strong><code>User pool - </code></strong> が自動で付く（例: <code>User pool - MyWebApp</code>）</td>
</tr>
<tr>
<td><strong>クライアント ID の場所</strong></td>
<td>概要ページに表示</td>
<td>左サイドバー → 「アプリケーション」から辿る</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>最大のハマりポイント（クライアントシークレット）：</strong><br />新UIの「アプリケーションを作成」で「従来のウェブアプリケーション」を選ぶと、クライアントシークレットが<strong>自動で付与</strong>されます。シークレットありのクライアントで <code>initiate-auth</code> を実行すると <code>SECRET_HASH was not received</code> エラーが出て詰まります。<br />このハンズオンでは②の手順でシークレットなしのクライアントを別途作成します。</p>
</blockquote>
<hr>
<h2><span id="toc3">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>User Pool</strong></td>
<td>Cognitoのユーザーディレクトリ。ユーザー登録・認証・JWT発行を一手に担う</td>
</tr>
<tr>
<td><strong>User Pool Client</strong></td>
<td>アプリが User Pool に接続するための設定。<strong>Client ID</strong> を持つ</td>
</tr>
<tr>
<td><strong>IDトークン</strong></td>
<td>ログイン成功時に Cognito が発行する JWT。email / sub などのユーザー情報を含む</td>
</tr>
<tr>
<td><strong>Cognito Authorizer</strong></td>
<td>API Gateway が IDトークンを検証する認可機能。Lambdaコードが不要で動作する</td>
</tr>
<tr>
<td><strong>claims</strong></td>
<td>JWT ペイロードに含まれる情報（email / sub / token_use など）</td>
</tr>
<tr>
<td><strong>FORCE_CHANGE_PASSWORD</strong></td>
<td>管理者作成ユーザーの初期状態。この状態では <code>initiate-auth</code> がトークンを返さない</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">前提条件</span></h2>
<table>
<thead>
<tr>
<th>ツール</th>
<th>確認コマンド</th>
<th>備考</th>
</tr>
</thead>
<tbody>
<tr>
<td>AWSアカウント</td>
<td>—</td>
<td>IAMユーザーまたはルートユーザー</td>
</tr>
<tr>
<td>AWS CLI v2（テスト用）</td>
<td><code>aws --version</code></td>
<td>IDトークン取得・ユーザー作成に使用</td>
</tr>
</tbody>
</table>
<blockquote>
<p>AWS CLI がない場合は、テスト操作に <strong>AWSコンソールの CloudShell</strong> を代用できます（後述）。</p>
</blockquote>
<hr>
<h2><span id="toc5">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Amazon Cognito</strong></td>
<td>ユーザー管理・認証・JWT発行</td>
<td>月5万MAU（月間アクティブユーザー）まで無料</td>
</tr>
<tr>
<td><strong>API Gateway</strong></td>
<td>REST API のエンドポイント管理・認可</td>
<td>月100万リクエストまで無料（無料期間12ヶ月）</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>APIのビジネスロジック（1関数）</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda の実行権限管理</td>
<td>無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>Lambda の実行ログ</td>
<td>月5GBまで無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc6">全体の作業順序</span></h2>
<pre><code class="language-plaintext">① Cognito User Pool を作成する（新UI対応）
      ↓
② テスト用アプリクライアントを作成する（シークレットなし）
      ↓
③ Lambda 関数を作成する
      ↓
④ API Gateway REST API を作成する
  （/hello・/profile エンドポイント + Cognito Authorizer 設定）
      ↓
⑤ テストユーザーを作成する
      ↓
⑥ 動作テスト（401確認 → JWTトークン取得 → 認証成功）
      ↓
⑦ リソースの削除</code></pre>
<hr>
<h2><span id="toc7">① Cognito User Pool を作成する</span></h2>
<p><strong>AWSコンソール → Cognito → 「アプリケーションを作成」</strong></p>
<p><!-- ![Cognito コンソールのトップ画面](images/cognito-top.jpg) --></p>
<blockquote>
<p><strong>新UIの注意：</strong><br />旧UIにあった「ユーザープールを作成」ボタンは廃止されています。現在は「<strong>アプリケーションを作成</strong>」からウィザードで User Pool とクライアントをまとめて作成します。</p>
</blockquote>
<h3><span id="toc8">1-1. アプリケーションを定義する</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>アプリケーションタイプ</td>
<td><strong>従来のウェブアプリケーション</strong></td>
</tr>
<tr>
<td>アプリケーション名</td>
<td><code>MyWebApp</code></td>
</tr>
</tbody>
</table>
<h3><span id="toc9">1-2. オプションを設定する</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>サインイン識別子のオプション</td>
<td><strong>メールアドレス</strong> にチェック（電話番号・ユーザー名はチェック不要）</td>
</tr>
<tr>
<td>自己登録を有効</td>
<td>チェックする（デフォルトでチェック済み）</td>
</tr>
<tr>
<td>サインアップのための必須属性</td>
<td>空欄のまま（属性を選択しない）</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>サインイン識別子は作成後に変更不可。</strong> 「メールアドレス」のみ選択しておくことを推奨します。</p>
</blockquote>
<h3><span id="toc10">1-3. リターン URL（スキップ）</span></h3>
<p>「リターン URL を追加」は入力不要（今回はマネージドログインページを使わないため）。</p>
<p>画面下部の「<strong>ユーザーディレクトリを作成する</strong>」をクリック。</p>
<h3><span id="toc11">控えておく情報</span></h3>
<p>作成後、User Pool の概要ページが開きます。</p>
<ul>
<li><strong>ユーザープール ID</strong>（例: <code>ap-northeast-1_XXXXXXXX</code>）<br />→ 概要ページの「ユーザープール情報」欄に表示</li>
<li><strong>クライアント ID</strong>（例: <code>XXXXXXXXXXXXXXXXXXXXXXXXXX</code>）<br />→ 左サイドバー「アプリケーション」→「アプリケーションクライアント」→ <code>MyWebApp</code> をクリック → 「クライアント ID」欄</li>
</ul>
<blockquote>
<p><strong>注意：</strong> 概要ページの「レコメンデーション」欄にも <code>MyWebApp</code> のリンクが表示されますが、これは Quick Setup ガイドへの誘導です。クライアント ID は左サイドバーから辿ってください。</p>
</blockquote>
<blockquote>
<p><strong>User Pool 名について：</strong><br />新UIでは、アプリ名の前に <code>User pool - </code> が自動で付きます（例: <code>User pool - MyWebApp</code>）。以降の手順では「作成した User Pool」と表記します。</p>
</blockquote>
<hr>
<h2><span id="toc12">② テスト用アプリクライアントを作成する</span></h2>
<p>新UIの「アプリケーションを作成」で自動作成された <code>MyWebApp</code> クライアントには<strong>クライアントシークレットが自動付与</strong>されています。シークレットありのクライアントで CLI から <code>initiate-auth</code> を実行すると <code>SECRET_HASH was not received</code> エラーが出ます。</p>
<p>そのため、<strong>シークレットなし・<code>ALLOW_USER_PASSWORD_AUTH</code> 有効</strong>の新しいクライアントを別途作成します。</p>
<p><strong>Cognito → 作成した User Pool → 左サイドバー「アプリケーション」→「アプリケーションクライアント」→「アプリケーションクライアントを作成」</strong></p>
<h3><span id="toc13">2-1. アプリケーションを定義する</span></h3>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>アプリケーションタイプ</td>
<td><strong>シングルページアプリケーション (SPA)</strong></td>
</tr>
<tr>
<td>アプリケーション名</td>
<td><code>MyWebAppClient</code></td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>「SPA」を選ぶ理由：</strong><br />新UIでは「従来のウェブアプリケーション」を選ぶとクライアントシークレットが<strong>必須</strong>になります。「SPA」を選ぶとシークレットなしで作成できます。CLI テスト目的ではこちらを使います。</p>
</blockquote>
<h3><span id="toc14">2-2. リターン URL（スキップ）</span></h3>
<p>入力不要。</p>
<h3><span id="toc15">2-3. Client secret configuration</span></h3>
<p>SPA を選択した場合、この欄は表示されません（シークレットなしで自動確定）。</p>
<p>「<strong>アプリケーションクライアントを作成</strong>」をクリック。</p>
<h3><span id="toc16">2-4. 認証フローを確認する</span></h3>
<p>作成後、<code>MyWebAppClient</code> をクリックして詳細画面を開きます。</p>
<p>「認証フロー」セクションに <strong><code>ALLOW_USER_PASSWORD_AUTH</code></strong> が含まれていることを確認します。</p>
<p>含まれていない場合は「編集」→「ALLOW_USER_PASSWORD_AUTH」にチェック → 「変更を保存」。</p>
<blockquote>
<p><strong>ALLOW_USER_PASSWORD_AUTH とは：</strong><br />CLI から username と password を直接指定して認証する「ユーザーパスワード認証」フローです。テスト用に有効化します（本番環境では SRP フローが推奨）。</p>
</blockquote>
<p><strong>控えておく情報（更新）：</strong></p>
<ul>
<li><strong>クライアント ID</strong> → 新しく作成した <code>MyWebAppClient</code> のクライアント ID を使う（① の <code>MyWebApp</code> のクライアント ID は使わない）</li>
</ul>
<hr>
<h2><span id="toc17">③ Lambda 関数を作成する</span></h2>
<p><strong>AWSコンソール → Lambda → 「関数の作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>作成方法</td>
<td>一から作成</td>
</tr>
<tr>
<td>関数名</td>
<td><code>CognitoApiFunction</code></td>
</tr>
<tr>
<td>ランタイム</td>
<td><strong>Python 3.12</strong></td>
</tr>
<tr>
<td>アーキテクチャ</td>
<td>x86_64</td>
</tr>
<tr>
<td>実行ロール</td>
<td>「基本的な Lambda アクセス権限で新しいロールを作成」（デフォルトのまま）</td>
</tr>
</tbody>
</table>
<p>「関数の作成」をクリック。</p>
<h3><span id="toc18">コードの入力</span></h3>
<p>関数ページ → 「コード」タブ → <code>lambda_function.py</code> を開いて既存の内容を<strong>全て置き換え</strong>て以下を貼り付けます。</p>
<pre><code class="language-python">import json


def lambda_handler(event, context):
    resource = event.get("resource", "/")  # API Gateway プロキシ統合: リクエストパスが "resource" に入る

    if resource == "/hello":
        return {
            "statusCode": 200,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps({"message": "Hello! This is a public endpoint."}),
        }

    if resource == "/profile":
        # event["requestContext"]["authorizer"]["claims"] に JWT ペイロードが格納される
        claims = (
            event.get("requestContext", {})
            .get("authorizer", {})
            .get("claims", {})
        )
        return {
            "statusCode": 200,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps(
                {
                    "message": "認証成功",
                    "email": claims.get("email"),
                    "sub": claims.get("sub"),          # Cognito が生成したユーザーの一意 ID
                    "token_use": claims.get("token_use"),  # "id" が返る（IDトークンを使っているため）
                },
                ensure_ascii=False,
            ),
        }

    return {
        "statusCode": 404,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"message": "Not Found"}),
    }</code></pre>
<p>「<strong>Deploy</strong>」ボタンをクリックしてコードを保存します。</p>
<blockquote>
<p><strong>Lambda 側で認証コードが不要な理由：</strong><br />API Gateway の Cognito Authorizer が IDトークン（JWT）の検証をすべて担っています。Lambda が呼ばれた時点では認証済みであることが保証されており、検証済みの <code>claims</code> だけを受け取って使えばよい設計です。</p>
</blockquote>
<hr>
<h2><span id="toc19">④ API Gateway REST API を作成する</span></h2>
<p><strong>AWSコンソール → API Gateway → 「APIを作成」</strong></p>
<h3><span id="toc20">4-1. API タイプの選択</span></h3>
<p>「REST API」の「<strong>構築</strong>」をクリック。</p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>API タイプ</td>
<td><strong>REST API</strong>（「プライベート」ではない方）</td>
</tr>
<tr>
<td>新しい API の作成</td>
<td>「新しい API」</td>
</tr>
<tr>
<td>API 名</td>
<td><code>CognitoSampleApi</code></td>
</tr>
<tr>
<td>エンドポイントタイプ</td>
<td>リージョン</td>
</tr>
</tbody>
</table>
<p>「<strong>APIを作成</strong>」をクリック。</p>
<hr>
<h3><span id="toc21">4-2. リソース /hello を作成する</span></h3>
<p>左のリソースツリーで <strong><code>/</code>（ルート）を選択</strong> → 「<strong>リソースを作成</strong>」ボタンをクリック</p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>リソースパス（親パス）</td>
<td><code>/</code>（変更不要）</td>
</tr>
<tr>
<td>リソース名</td>
<td><code>hello</code></td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>新UIの注意点：</strong><br />「リソースパス」はリソースを作成する<strong>親パスの選択</strong>（<code>/</code> = ルート配下という意味）です。<code>/hello</code> と入力する欄ではありません。「リソース名」に <code>hello</code> と入力するだけで OK です。</p>
</blockquote>
<p>「<strong>リソースを作成</strong>」をクリック。</p>
<p><strong><code>/hello</code> を選択した状態で → 「メソッドを作成」→ メソッドタイプ <code>GET</code> を選択</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>統合タイプ</td>
<td><strong>Lambda 関数</strong></td>
</tr>
<tr>
<td>Lambda プロキシ統合の使用</td>
<td><strong>チェックする</strong></td>
</tr>
<tr>
<td>Lambda 関数</td>
<td><code>CognitoApiFunction</code></td>
</tr>
</tbody>
</table>
<p>「保存」→「OK」（Lambda 呼び出し権限の追加確認）。</p>
<hr>
<h3><span id="toc22">4-3. リソース /profile を作成する</span></h3>
<p>左のリソースツリーで <strong><code>/</code>（ルート）を選択</strong> → 「<strong>リソースを作成</strong>」</p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>リソースパス（親パス）</td>
<td><code>/</code>（変更不要）</td>
</tr>
<tr>
<td>リソース名</td>
<td><code>profile</code></td>
</tr>
</tbody>
</table>
<p>「<strong>リソースを作成</strong>」をクリック。</p>
<p><strong><code>/profile</code> を選択した状態で → 「メソッドを作成」→ メソッドタイプ <code>GET</code> を選択</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>統合タイプ</td>
<td><strong>Lambda 関数</strong></td>
</tr>
<tr>
<td>Lambda プロキシ統合の使用</td>
<td><strong>チェックする</strong></td>
</tr>
<tr>
<td>Lambda 関数</td>
<td><code>CognitoApiFunction</code></td>
</tr>
</tbody>
</table>
<p>「保存」をクリック。</p>
<hr>
<h3><span id="toc23">4-4. Cognito Authorizer を作成する</span></h3>
<p>左側メニューの「<strong>オーソライザー</strong>」→「<strong>オーソライザーを作成</strong>」</p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>オーソライザー名</td>
<td><code>CognitoAuthorizer</code></td>
</tr>
<tr>
<td>オーソライザーのタイプ</td>
<td><strong>Cognito</strong></td>
</tr>
<tr>
<td>Cognito ユーザープール（リージョン）</td>
<td><code>ap-northeast-1</code>（変更不要）</td>
</tr>
<tr>
<td>Cognito ユーザープール（検索欄）</td>
<td>① で作成した User Pool を選択（例: <code>User pool - MyWebApp</code>）</td>
</tr>
<tr>
<td>トークンのソース</td>
<td><code>Authorization</code>（<strong>必ず入力する</strong>）</td>
</tr>
<tr>
<td>トークンの検証（オプション）</td>
<td>空欄のまま</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>新UIの注意点：</strong><br />「トークンのソース」はデフォルトで<strong>空欄</strong>になっています。<code>Authorization</code> と入力しないと動作しません。入力を忘れると全リクエストが 401 になります。</p>
</blockquote>
<p>「<strong>オーソライザーを作成</strong>」をクリック。</p>
<p><strong>動作確認：</strong></p>
<p>「オーソライザーのテスト」→ テスト用トークン欄に適当な文字を入力 → 「テスト」→ <code>401</code> が返れば正常（正しいトークンがないため 401 が正解）。</p>
<p><!-- ![Cognito Authorizer のテスト画面](images/cognito-authorizer-test.jpg) --></p>
<hr>
<h3><span id="toc24">4-5. /profile GET メソッドに Authorizer を設定する</span></h3>
<p>左のリソースツリーで <strong><code>/profile</code> → <code>GET</code></strong> を選択 → 「メソッドリクエストの設定」セクションの「<strong>編集</strong>」ボタンをクリック</p>
<p>「認可」ドロップダウンを開き、<strong><code>Cognito ユーザープールオーソライザー</code></strong> の配下にある <strong><code>CognitoAuthorizer</code></strong> を選択 → 「<strong>保存</strong>」</p>
<blockquote>
<p><strong>新UIの注意点：</strong><br />選択肢は <code>なし</code> / <code>AWS IAM</code> / <code>Cognito ユーザープールオーソライザー</code>（カテゴリ）→ <code>CognitoAuthorizer</code> の階層構造になっています。<br /><code>CognitoAuthorizer</code> はカテゴリ名ではなく、その下にインデントされた項目を選んでください。</p>
</blockquote>
<blockquote>
<p><strong>ポイント：</strong><br /><code>/hello GET</code> には Authorizer を<strong>設定しない</strong>（認証不要）。<br /><code>/profile GET</code> にのみ CognitoAuthorizer を設定することで、エンドポイントごとに認証要件を分けます。</p>
</blockquote>
<hr>
<h3><span id="toc25">4-6. API をデプロイする</span></h3>
<p><strong>「アクション」→「API のデプロイ」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>デプロイされるステージ</td>
<td><strong>「新しいステージ」</strong></td>
</tr>
<tr>
<td>ステージ名</td>
<td><code>Prod</code></td>
</tr>
</tbody>
</table>
<p>「デプロイ」をクリック。</p>
<p><strong>控えておく情報：</strong></p>
<p>「Prod ステージエディター」に表示される <strong>URL の呼び出し</strong>（例: <code>https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod</code>）をコピーします。</p>
<hr>
<h2><span id="toc26">⑤ テストユーザーを作成する</span></h2>
<p><strong>Cognito → 作成した User Pool → 左サイドバー「ユーザー管理」→「ユーザー」→「ユーザーを作成」</strong></p>
<table>
<thead>
<tr>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody>
<tr>
<td>招待メッセージ</td>
<td><strong>「招待を送信しない」</strong> を選択</td>
</tr>
<tr>
<td>E メールアドレス</td>
<td><code>test@example.com</code></td>
</tr>
<tr>
<td>仮パスワード</td>
<td><strong>「パスワードの設定」</strong> を選択</td>
</tr>
<tr>
<td>パスワード</td>
<td><code>TestPass1!</code></td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>パスワードポリシー（新UIのデフォルト）：</strong><br />最小8文字 / 数字・小文字・大文字・特殊文字をそれぞれ1文字以上含むこと。<br /><code>TestPass1</code> は特殊文字がないため不可。<code>TestPass1!</code> を使ってください。</p>
</blockquote>
<p>「<strong>ユーザーを作成</strong>」をクリック。</p>
<blockquote>
<p>ユーザーの「確認ステータス」列に <strong>「パスワードを強制的に変更」</strong> と表示されます（= FORCE_CHANGE_PASSWORD 状態）。<br />この状態で <code>initiate-auth</code> を実行してもトークンが返らず <code>NEW_PASSWORD_REQUIRED</code> チャレンジが返されます。<br />次の CLI コマンドで永続パスワードを設定して「確認済み」（CONFIRMED）に変更します。</p>
</blockquote>
<p><strong>CLI で永続パスワードを設定する：</strong></p>
<p><strong>方法 A: CloudShell（コンソール版ハンズオンにおすすめ）</strong></p>
<p>AWSコンソール画面下部の「<strong>CloudShell</strong>」をクリックして起動します。コンソールと同じ認証情報が自動で使われるため追加設定は不要。</p>
<pre><code class="language-bash">aws cognito-idp admin-set-user-password \
  --user-pool-id ap-northeast-1_XXXXXXXX \
  --username test@example.com \
  --password 'TestPass1!' \
  --permanent \
  --region ap-northeast-1</code></pre>
<blockquote>
<p>CloudShell は Linux シェルのため <code>\</code> で改行します。<code>!</code> を含むパスワードはシングルクォート <code>&#39;...&#39;</code> で囲んでください（ダブルクォートだと bash がエラーになる場合があります）。</p>
</blockquote>
<p><strong>方法 B: ローカルの AWS CLI（コマンドプロンプト）</strong></p>
<pre><code class="language-cmd">aws cognito-idp admin-set-user-password ^
  --user-pool-id ap-northeast-1_XXXXXXXX ^
  --username test@example.com ^
  --password TestPass1! ^
  --permanent ^
  --region ap-northeast-1</code></pre>
<blockquote>
<p><code>ap-northeast-1_XXXXXXXX</code> を ① で控えた<strong>ユーザープール ID</strong> に置き換えてください。</p>
</blockquote>
<p>コマンドが出力なしで返れば成功です。コンソールでユーザーの「確認ステータス」が**「確認済み」**に変わったことを確認してください。</p>
<hr>
<h2><span id="toc27">⑥ 動作テスト</span></h2>
<h3><span id="toc28">テスト1: 公開エンドポイント（/hello）→ 200</span></h3>
<pre><code class="language-cmd">curl https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod/hello</code></pre>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{"message": "Hello! This is a public endpoint."}</code></pre>
<hr>
<h3><span id="toc29">テスト2: 未認証でプライベートエンドポイント（/profile）→ 401</span></h3>
<pre><code class="language-cmd">curl https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod/profile</code></pre>
<p>期待するレスポンス（API Gateway が返す。Lambda は呼ばれていない）:</p>
<pre><code class="language-json">{"message": "Unauthorized"}</code></pre>
<hr>
<h3><span id="toc30">テスト3: IDトークンを取得する</span></h3>
<pre><code class="language-cmd">aws cognito-idp initiate-auth ^
  --auth-flow USER_PASSWORD_AUTH ^
  --auth-parameters "USERNAME=test@example.com,PASSWORD=TestPass1!" ^
  --client-id XXXXXXXXXXXXXXXXXXXXXXXXXX ^
  --region ap-northeast-1</code></pre>
<blockquote>
<p><code>XXXXXXXXXXXXXXXXXXXXXXXXXX</code> を <strong>② で控えた <code>MyWebAppClient</code> のクライアント ID</strong> に置き換えてください（① の <code>MyWebApp</code> のクライアント ID ではありません）。</p>
</blockquote>
<p>レスポンス例（抜粋）:</p>
<pre><code class="language-json">{
    "AuthenticationResult": {
        "AccessToken": "eyJra...",
        "ExpiresIn": 3600,
        "TokenType": "Bearer",
        "RefreshToken": "eyJjb...",
        "IdToken": "eyJra..."
    }
}</code></pre>
<p><strong><code>IdToken</code> の値をコピーしておきます</strong>（次のテストで使用）。</p>
<blockquote>
<p><strong>IdToken と AccessToken の違い：</strong></p>
<table>
<thead>
<tr>
<th>トークン</th>
<th>用途</th>
<th>含まれる情報</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>IdToken</strong></td>
<td>ユーザー認証（今回使用）</td>
<td>email / sub など</td>
</tr>
<tr>
<td>AccessToken</td>
<td>Cognito API へのアクセス</td>
<td>sub / scope など</td>
</tr>
</tbody>
</table>
<p>API Gateway の Cognito Authorizer に渡すのは <strong>IdToken</strong>。AccessToken を渡しても 403 になります。</p>
</blockquote>
<hr>
<h3><span id="toc31">テスト4: 認証ありでプライベートエンドポイント（/profile）→ 200</span></h3>
<p>IdToken は非常に長いため、変数に格納してから実行します。</p>
<p><strong>方法 A: CloudShell（おすすめ）</strong></p>
<pre><code class="language-bash">TOKEN="eyJra...（IdToken全体を貼り付ける）"
curl -H "Authorization: $TOKEN" https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod/profile</code></pre>
<p><strong>方法 B: Windows CMD（変数使用）</strong></p>
<pre><code class="language-cmd">set TOKEN=eyJra...（IdToken全体を貼り付ける）
curl -H "Authorization: %TOKEN%" https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod/profile</code></pre>
<blockquote>
<p><strong>重要：</strong> CMD では変数展開に <code>%TOKEN%</code> を使います。<code>$TOKEN</code>（bash 構文）を CMD で使うと文字列 <code>$TOKEN</code> がそのまま送られ 401 になります。<br />また <code>Bearer</code> プレフィックスは不要です。raw JWT をそのまま Authorization ヘッダーに指定してください。</p>
</blockquote>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{
  "message": "認証成功",
  "email": "test@example.com",
  "sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "token_use": "id"
}</code></pre>
<blockquote>
<p><code>sub</code> は Cognito が自動生成したユーザーの一意 ID です。<br /><code>token_use: &quot;id&quot;</code> は IDトークンを使っていることを示します。</p>
</blockquote>
<p><!-- ![認証成功レスポンス](images/cognito-auth-success.jpg) --></p>
<hr>
<h2><span id="toc32">⑦ リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ずリソースを削除してください。</strong></p>
<h3><span id="toc33">1. API Gateway を削除する</span></h3>
<p><strong>API Gateway → APIs → <code>CognitoSampleApi</code> を選択 → 「アクション」→「APIの削除」</strong></p>
<hr>
<h3><span id="toc34">2. Lambda 関数を削除する</span></h3>
<p><strong>Lambda → 関数 → <code>CognitoApiFunction</code> を選択 → 「アクション」→「削除」</strong></p>
<hr>
<h3><span id="toc35">3. Cognito User Pool を削除する</span></h3>
<p><strong>Cognito → 作成した User Pool → 右上の「ユーザープールを削除」ボタン → ユーザープール名を入力 → 「削除」</strong></p>
<blockquote>
<p>ユーザーとアプリクライアントも一緒に削除されます。</p>
</blockquote>
<hr>
<h3><span id="toc36">4. IAM ロールを削除する（任意）</span></h3>
<p><strong>IAM → ロール → <code>CognitoApiFunction-role-XXXX</code> を検索して削除</strong></p>
<hr>
<h3><span id="toc37">5. CloudWatch Logs のロググループを削除する（任意）</span></h3>
<p><strong>CloudWatch → ロググループ → <code>/aws/lambda/CognitoApiFunction</code> → 削除</strong></p>
<hr>
<h2><span id="toc38">SAMとの対比</span></h2>
<table>
<thead>
<tr>
<th>SAMの記述</th>
<th>コンソールでやること</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AWS::Cognito::UserPool</code></td>
<td>Cognito → 「アプリケーションを作成」で User Pool を作成</td>
</tr>
<tr>
<td><code>AWS::Cognito::UserPoolClient</code>（シークレットなし）</td>
<td>② の手順で SPA タイプのクライアント <code>MyWebAppClient</code> を別途作成</td>
</tr>
<tr>
<td><code>Auth.DefaultAuthorizer: CognitoAuthorizer</code></td>
<td>API Gateway → オーソライザーを作成して /profile GET に適用</td>
</tr>
<tr>
<td><code>Auth: Authorizer: NONE</code>（/hello 側）</td>
<td>/hello GET には Authorizer を設定しない</td>
</tr>
<tr>
<td><code>sam delete</code></td>
<td>API Gateway / Lambda / Cognito / IAM ロール / ロググループを個別に削除</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc39">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>initiate-auth</code> で <code>Client is configured with secret but SECRET_HASH was not received</code></td>
<td>① で自動作成された <code>MyWebApp</code>（シークレットあり）のクライアント ID を使っている</td>
<td>② の手順で <code>MyWebAppClient</code>（シークレットなし）を作成し、そちらの Client ID を使う</td>
</tr>
<tr>
<td><code>initiate-auth</code> で <code>NotAuthorizedException</code></td>
<td>ユーザーが FORCE_CHANGE_PASSWORD 状態</td>
<td><code>admin-set-user-password --permanent</code> で CONFIRMED に変更する</td>
</tr>
<tr>
<td><code>initiate-auth</code> で <code>InvalidParameterException</code></td>
<td><code>ALLOW_USER_PASSWORD_AUTH</code> が無効</td>
<td>User Pool Client の認証フロー設定を確認する</td>
</tr>
<tr>
<td><code>/profile</code> に IDトークンを送っても 401</td>
<td><code>Bearer TOKEN</code> 形式で送っている</td>
<td><code>Bearer</code> を外し raw JWT（<code>eyJra...</code>）をそのまま Authorization ヘッダーに指定する</td>
</tr>
<tr>
<td><code>/profile</code> に IDトークンを送っても 403</td>
<td>トークンが期限切れ（1時間で失効）</td>
<td><code>initiate-auth</code> で新しいトークンを取得し直す</td>
</tr>
<tr>
<td><code>/profile</code> に IDトークンを送っても 403</td>
<td>AccessToken を使っている（IdToken ではない）</td>
<td><code>AuthenticationResult.IdToken</code> を使用しているか確認する</td>
</tr>
<tr>
<td>Lambda の <code>claims</code> が空</td>
<td><code>/profile GET</code> に Authorizer が設定されていない</td>
<td>④ 4-5 の手順で CognitoAuthorizer を設定し直す</td>
</tr>
<tr>
<td>Lambda の <code>resource</code> が <code>/</code> になる</td>
<td>Lambda プロキシ統合が有効になっていない</td>
<td>GET メソッドで「Lambda プロキシ統合の使用」をチェックする</td>
</tr>
<tr>
<td>Authorizer テストが 500 になる</td>
<td>User Pool が選択されていない、またはトークンのソースが空</td>
<td>オーソライザーの設定を確認し <code>Authorization</code> を入力し直す</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc40">まとめ</span></h2>
<p>今回のハンズオンで体験できたこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Cognito User Pool</strong></td>
<td>ユーザー作成・JWT 発行・パスワードポリシーを新UIで設定</td>
</tr>
<tr>
<td><strong>Cognito Authorizer</strong></td>
<td>API Gateway がIDトークンを自動検証 → Lambda は認証コード不要</td>
</tr>
<tr>
<td><strong>エンドポイント別認証制御</strong></td>
<td>/hello（認証なし）と /profile（認証必須）を同じ Lambda で分岐</td>
</tr>
<tr>
<td><strong>FORCE_CHANGE_PASSWORD 対処</strong></td>
<td><code>admin-set-user-password --permanent</code> で CONFIRMED 状態に変更</td>
</tr>
<tr>
<td><strong>新UI操作</strong></td>
<td>「アプリケーションを作成」→ SPA クライアントの追加作成 → Authorizer のトークンソース設定</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc41">コンソール版とSAM版を比較してみる</span></h2>
<p>コンソールでCognitoとAPI Gatewayの連携を理解したら、SAMで同じ構成をコードで定義することで「SAMが何を自動化しているか」が明確になります。<code>DefaultAuthorizer</code> によるワンライン認証設定や、<code>ALLOW_USER_PASSWORD_AUTH</code> の自動有効化など、コンソールでの手動操作がコードに対応しています。</p>
<p><!-- TODO: SAM版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc42">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-console-cognito-api-gateway-lambda/">AWSコンソールだけでCognito + API Gateway + Lambda の認証付きAPIを構築する手順【SAM版との比較付き / 新UI対応】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-console-cognito-api-gateway-lambda/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWSハンズオン - Cognito + API Gateway + Lambda で認証付きAPIをSAMで構築しよう【コンソール版との比較付き】</title>
		<link>https://caymezon.com/aws-handson-cognito-api-gateway-lambda/</link>
					<comments>https://caymezon.com/aws-handson-cognito-api-gateway-lambda/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 01 Mar 2026 08:50:28 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[APIGateway]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cognito]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[SAM]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[初心者]]></category>
		<category><![CDATA[認証]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20235</guid>

					<description><![CDATA[<p>目次 はじめにSAM vs コンソール：どれだけ違うかキーワード解説前提条件使用するAWSサービスStep 1: プロジェクトフォルダを開くフォルダ構造Step 2: SAMテンプレートの確認（template.yaml [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-cognito-api-gateway-lambda/">AWSハンズオン - Cognito + API Gateway + Lambda で認証付きAPIをSAMで構築しよう【コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-16" checked><label class="toc-title" for="toc-checkbox-16">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">SAM vs コンソール：どれだけ違うか</a></li><li><a href="#toc3" tabindex="0">キーワード解説</a></li><li><a href="#toc4" tabindex="0">前提条件</a></li><li><a href="#toc5" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc6" tabindex="0">Step 1: プロジェクトフォルダを開く</a><ol><li><a href="#toc7" tabindex="0">フォルダ構造</a></li></ol></li><li><a href="#toc8" tabindex="0">Step 2: SAMテンプレートの確認（template.yaml）</a><ol><li><a href="#toc9" tabindex="0">Cognito リソースの定義</a></li><li><a href="#toc10" tabindex="0">API Gateway と Cognito Authorizer の定義</a></li><li><a href="#toc11" tabindex="0">Lambda 関数とエンドポイントの定義</a></li><li><a href="#toc12" tabindex="0">template.yaml のポイント解説</a></li></ol></li><li><a href="#toc13" tabindex="0">Step 3: Lambda コードの確認（src/app.py）</a></li><li><a href="#toc14" tabindex="0">Step 4: samconfig.toml を作成する</a></li><li><a href="#toc15" tabindex="0">Step 5: sam build（ビルド）</a></li><li><a href="#toc16" tabindex="0">Step 6: sam deploy（デプロイ）</a><ol><li><a href="#toc17" tabindex="0">デプロイ完了の確認</a></li><li><a href="#toc18" tabindex="0">デプロイされるリソース一覧</a></li></ol></li><li><a href="#toc19" tabindex="0">Step 7: 動作テスト</a><ol><li><a href="#toc20" tabindex="0">事前準備: 変数を設定する</a></li><li><a href="#toc21" tabindex="0">テスト1: テストユーザーを作成する</a></li><li><a href="#toc22" tabindex="0">テスト2: 公開エンドポイントのテスト（/hello）→ 200</a></li><li><a href="#toc23" tabindex="0">テスト3: 未認証でプライベートエンドポイント（/profile）→ 401</a></li><li><a href="#toc24" tabindex="0">テスト4: IDトークンを取得する</a></li><li><a href="#toc25" tabindex="0">テスト5: 認証ありでプライベートエンドポイント（/profile）→ 200</a></li></ol></li><li><a href="#toc26" tabindex="0">Step 8: AWSコンソールで確認（任意）</a></li><li><a href="#toc27" tabindex="0">Step 9: リソースの削除</a><ol><li><a href="#toc28" tabindex="0">削除完了の確認</a></li><li><a href="#toc29" tabindex="0">sam delete で削除されるリソース一覧</a></li></ol></li><li><a href="#toc30" tabindex="0">トラブルシューティング</a></li><li><a href="#toc31" tabindex="0">まとめ</a><ol><li><a href="#toc32" tabindex="0">SAMのメリットを実感できたポイント</a></li></ol></li><li><a href="#toc33" tabindex="0">コンソール版と比較してみる</a></li><li><a href="#toc34" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「認証付きAPIの構成をコードで管理して、チームで再現できるようにしたい」——そんな要件に、<strong>AWS SAM（Serverless Application Model）</strong> は最適な選択肢のひとつです。</p>
<p>この記事では、<strong>AWS SAM</strong> を使って、Cognito + API Gateway + Lambda による認証付き REST API をゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">クライアント（curl）
  │
  ├─ GET /hello  → API Gateway（認証なし）→ Lambda → 200 OK
  │
  └─ GET /profile
        ├─ Authorization ヘッダーなし → API Gateway が 401 を返す
        └─ Authorization: &lt;IDトークン&gt;
              → Cognito Authorizer が JWT を検証（Lambda 不要）
              → Lambda → email / sub を返す → 200 OK</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li><code>AWS::Cognito::UserPool</code> と <code>AWS::Cognito::UserPoolClient</code> の SAM 定義</li>
<li><code>DefaultAuthorizer</code> を使ったエンドポイント単位の認証制御（1行で全エンドポイントに認証を適用）</li>
<li><code>Authorizer: NONE</code> で特定エンドポイントだけ認証を除外するパターン</li>
<li>CLI でのテスト実行（ユーザー作成 → IDトークン取得 → 認証済みリクエスト）</li>
</ul>
<p><strong>このハンズオンの特徴：</strong></p>
<ul>
<li><code>sam build</code> + <code>sam deploy</code> の <strong>2コマンドで全リソースをデプロイ</strong></li>
<li>Cognito User Pool / User Pool Client / API Gateway / Lambda / IAM ロールを <code>template.yaml</code> 1ファイルで管理</li>
<li><code>sam delete</code> で<strong>全リソースを一括削除</strong>（コンソール版では各リソースを個別に削除する必要がある）</li>
</ul>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">SAM vs コンソール：どれだけ違うか</span></h2>
<table>
<thead>
<tr>
<th>比較項目</th>
<th>SAM（コード）</th>
<th>コンソール（手動）</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Cognito Client 作成</strong></td>
<td><code>GenerateSecret: false</code> の1行でシークレットなしクライアントを定義</td>
<td>新UIで「SPA」タイプのクライアントを別途手動作成</td>
</tr>
<tr>
<td><strong>Authorizer の紐付け</strong></td>
<td><code>UserPoolArn: !GetAtt UserPool.Arn</code> で自動連携</td>
<td>コンソールで User Pool を手動選択し、トークンのソースを手入力</td>
</tr>
<tr>
<td><strong>エンドポイント別認証</strong></td>
<td><code>DefaultAuthorizer</code> + 除外箇所に <code>Authorizer: NONE</code></td>
<td>エンドポイントごとにオーソライザーを手動設定</td>
</tr>
<tr>
<td><strong>削除</strong></td>
<td><code>sam delete</code> 1コマンド</td>
<td>API Gateway / Lambda / Cognito / IAM を個別に削除</td>
</tr>
<tr>
<td><strong>再現性</strong></td>
<td>チームで同じ環境を即座に再現できる</td>
<td>手順書が必要</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc3">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>User Pool</strong></td>
<td>Cognito のユーザーディレクトリ。ユーザー登録・認証・JWT 発行を担う</td>
</tr>
<tr>
<td><strong>User Pool Client</strong></td>
<td>アプリが User Pool に接続するための設定。<strong>Client ID</strong> を持つ</td>
</tr>
<tr>
<td><strong>IDトークン</strong></td>
<td>ログイン成功時に Cognito が発行する JWT。email / sub などのユーザー情報を含む</td>
</tr>
<tr>
<td><strong>Cognito Authorizer</strong></td>
<td>API Gateway が IDトークンを検証する認可機能。Lambda コードが不要で動作する</td>
</tr>
<tr>
<td><strong>claims</strong></td>
<td>JWT ペイロードに含まれる情報（email / sub / token_use など）</td>
</tr>
<tr>
<td><strong>DefaultAuthorizer</strong></td>
<td>全エンドポイントに一括でオーソライザーを適用する SAM の設定。個別に <code>Authorizer: NONE</code> で除外できる</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">前提条件</span></h2>
<table>
<thead>
<tr>
<th>ツール</th>
<th>確認コマンド</th>
<th>最低バージョン目安</th>
</tr>
</thead>
<tbody>
<tr>
<td>AWS CLI v2</td>
<td><code>aws --version</code></td>
<td>2.x</td>
</tr>
<tr>
<td>AWS SAM CLI</td>
<td><code>sam --version</code></td>
<td>1.x</td>
</tr>
<tr>
<td>Python 3.12</td>
<td><code>python --version</code></td>
<td>3.12</td>
</tr>
</tbody>
</table>
<pre><code class="language-cmd">aws sts get-caller-identity</code></pre>
<p>アカウントIDが表示されれば認証設定済みです。</p>
<hr>
<h2><span id="toc5">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Amazon Cognito</strong></td>
<td>ユーザー管理・認証・JWT発行</td>
<td>月5万MAUまで無料</td>
</tr>
<tr>
<td><strong>API Gateway</strong></td>
<td>REST API のエンドポイント管理・認可</td>
<td>月100万リクエストまで無料（無料期間12ヶ月）</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>APIのビジネスロジック（1関数）</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda の実行権限管理</td>
<td>無料</td>
</tr>
<tr>
<td><strong>S3</strong></td>
<td>SAM デプロイパッケージ置き場</td>
<td>少量のため実質無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>Lambda の実行ログ</td>
<td>月5GBまで無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc6">Step 1: プロジェクトフォルダを開く</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\cognito-api-lambda</code></pre>
<h3><span id="toc7">フォルダ構造</span></h3>
<pre><code class="language-plaintext">cognito-api-lambda/
├── template.yaml       # SAMテンプレート（Cognito + API Gateway + Lambda 定義）
├── samconfig.toml      # デプロイ設定（gitignore 対象・毎回手動作成が必要）
├── docs/
│   ├── 1_console.md    # AWSコンソール版手順
│   └── 2_sam.md        # SAM版手順
└── src/
    └── app.py          # Lambda 関数（/hello と /profile を処理）</code></pre>
<hr>
<h2><span id="toc8">Step 2: SAMテンプレートの確認（template.yaml）</span></h2>
<p><code>template.yaml</code> は全 AWSリソースの設計図です。ポイントを確認しておきます。</p>
<h3><span id="toc9">Cognito リソースの定義</span></h3>
<pre><code class="language-yaml">Resources:
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UsernameAttributes: [email]     # メールアドレスでログイン
      Policies:
        PasswordPolicy:
          MinimumLength: 8            # 記号・大文字不要（テスト用に緩和）

  # アプリクライアント: CLI から initiate-auth で使う
  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      GenerateSecret: false           # シークレットなし（CLI テスト用）
      ExplicitAuthFlows:
        - ALLOW_USER_PASSWORD_AUTH    # username/password で直接認証（テスト用）
        - ALLOW_REFRESH_TOKEN_AUTH</code></pre>
<h3><span id="toc10">API Gateway と Cognito Authorizer の定義</span></h3>
<pre><code class="language-yaml">  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Auth:
        # DefaultAuthorizer を指定 → 全エンドポイントにデフォルトで認証が必要になる
        DefaultAuthorizer: CognitoAuthorizer
        Authorizers:
          CognitoAuthorizer:
            UserPoolArn: !GetAtt UserPool.Arn  # User Pool と自動的に紐付け
            Identity:
              Header: Authorization            # JWT を Authorization ヘッダーで受け取る</code></pre>
<h3><span id="toc11">Lambda 関数とエンドポイントの定義</span></h3>
<pre><code class="language-yaml">  ApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      Events:
        PublicEndpoint:              # /hello: 公開エンドポイント
          Type: Api
          Properties:
            Path: /hello
            Method: get
            Auth:
              Authorizer: NONE       # DefaultAuthorizer を明示的に無効化

        PrivateEndpoint:             # /profile: 認証必須
          Type: Api
          Properties:
            Path: /profile
            Method: get
            # Auth 指定なし → DefaultAuthorizer（CognitoAuthorizer）が自動適用</code></pre>
<h3><span id="toc12">template.yaml のポイント解説</span></h3>
<table>
<thead>
<tr>
<th>ポイント</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong><code>GenerateSecret: false</code></strong></td>
<td>CLI テスト用にシークレットなしのクライアントを定義。コンソール版では SPA タイプのクライアントを手動作成</td>
</tr>
<tr>
<td><strong><code>DefaultAuthorizer</code></strong></td>
<td>全エンドポイントに Cognito 認証をデフォルト適用。コンソール版ではエンドポイントごとに手動設定</td>
</tr>
<tr>
<td><strong><code>Authorizer: NONE</code></strong></td>
<td>/hello など特定エンドポイントの認証を除外。コンソール版では /hello に Authorizer を設定しないことに対応</td>
</tr>
<tr>
<td><strong><code>UserPoolArn: !GetAtt UserPool.Arn</code></strong></td>
<td>Cognito Authorizer と User Pool を自動的に紐付け。コンソール版では手動でドロップダウン選択が必要</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc13">Step 3: Lambda コードの確認（src/app.py）</span></h2>
<p><code>src/app.py</code> の構造を把握しておきます。</p>
<pre><code class="language-python">import json


def lambda_handler(event, context):
    resource = event.get("resource", "/")   # API Gateway プロキシ統合: リクエストパスが入る

    if resource == "/hello":
        return {"statusCode": 200, "body": json.dumps({"message": "Hello! This is a public endpoint."})}

    if resource == "/profile":
        # Cognito Authorizer が JWT を検証済み
        # JWT ペイロードが event["requestContext"]["authorizer"]["claims"] に格納される
        claims = (
            event.get("requestContext", {})
            .get("authorizer", {})
            .get("claims", {})
        )
        return {
            "statusCode": 200,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps(
                {
                    "message": "認証成功",
                    "email": claims.get("email"),
                    "sub": claims.get("sub"),          # Cognito が生成したユーザーの一意 ID
                    "token_use": claims.get("token_use"),  # "id" が返る（IDトークンを使っているため）
                },
                ensure_ascii=False,
            ),
        }

    return {"statusCode": 404, "body": json.dumps({"message": "Not Found"})}</code></pre>
<p><strong>app.py のポイント：</strong></p>
<ul>
<li>Lambda 側でトークン検証は一切不要。Cognito Authorizer が自動で行う</li>
<li><code>claims</code> には email / sub / token_use など JWT ペイロードの全フィールドが格納される</li>
<li><code>/hello</code> への呼び出しでは <code>claims</code> は空（Authorizer が動作しないため）</li>
</ul>
<blockquote>
<p><strong>各コードの全文はコンソール版記事を参照してください。</strong><br />SAM版・コンソール版で使用する Lambda コードは共通です。コンソール版では手順とあわせてコードを全文掲載しています。</p>
<p>→ <a href="/aws-handson-console-cognito-api-gateway-lambda/">AWSコンソールだけでCognito + API Gateway + Lambda の認証付きAPIを構築する手順</a></p>
</blockquote>
<hr>
<h2><span id="toc14">Step 4: samconfig.toml を作成する</span></h2>
<p><code>samconfig.toml</code> は <code>.gitignore</code> で管理外のため、<strong>毎回手動で作成</strong>する必要があります。</p>
<p><code>cognito-api-lambda/samconfig.toml</code> を新規作成して以下を貼り付けます。</p>
<pre><code class="language-toml">version = 0.1

[default.deploy.parameters]
stack_name = "cognito-api-lambda-stack"
resolve_s3 = true
s3_prefix = "cognito-api-lambda-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
disable_rollback = true
image_repositories = []

[default.global.parameters]
region = "ap-northeast-1"</code></pre>
<blockquote>
<p><strong><code>samconfig.toml</code> の置き場所：</strong><br /><code>cognito-api-lambda/</code> フォルダの<strong>直下</strong>に置く必要があります。</p>
</blockquote>
<hr>
<h2><span id="toc15">Step 5: sam build（ビルド）</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\cognito-api-lambda
sam build</code></pre>
<p>成功すると以下のように表示されます。</p>
<pre><code class="language-plaintext">Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml</code></pre>
<blockquote>
<p><strong><code>sam build</code> が行っていること：</strong></p>
<ul>
<li><code>src/</code> フォルダを ZIP パッケージ化して <code>.aws-sam/build/</code> に配置</li>
<li><code>template.yaml</code> を <code>.aws-sam/build/template.yaml</code> にコピー</li>
<li>Lambda デプロイの準備を整える</li>
</ul>
</blockquote>
<hr>
<h2><span id="toc16">Step 6: sam deploy（デプロイ）</span></h2>
<pre><code class="language-cmd">sam deploy</code></pre>
<p>変更内容が表示されて確認を求められます。</p>
<pre><code class="language-plaintext">Deploy this changeset? [y/N]: y</code></pre>
<p><code>y</code> を入力して進めます。数分でデプロイが完了します。</p>
<h3><span id="toc17">デプロイ完了の確認</span></h3>
<p>ターミナルに <strong>Outputs</strong> が表示されます。</p>
<pre><code class="language-plaintext">Outputs
----------------------------------------------------------------------
Key    ApiUrl
Value  https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod

Key    UserPoolId
Value  ap-northeast-1_XXXXXXXX

Key    UserPoolClientId
Value  XXXXXXXXXXXXXXXXXXXXXXXXXX
----------------------------------------------------------------------</code></pre>
<p><strong>3つの値を全て控えておきます。</strong>（テスト実行コマンドで使用）</p>
<h3><span id="toc18">デプロイされるリソース一覧</span></h3>
<pre><code class="language-plaintext">Cognito User Pool × 1              : cognito-api-lambda-stack-user-pool
Cognito User Pool Client × 1       : シークレットなし（ALLOW_USER_PASSWORD_AUTH 有効）
API Gateway REST API × 1           : cognito-api-lambda-stack + Prod ステージ
Lambda 関数 × 1                    : cognito-api-lambda-stack-ApiFunction-XXXX
IAM ロール × 1                     : Lambda 実行ロール
S3                                 : SAM デプロイパッケージ
CloudWatch Logs                    : Lambda 自動生成ログ × 1</code></pre>
<hr>
<h2><span id="toc19">Step 7: 動作テスト</span></h2>
<h3><span id="toc20">事前準備: 変数を設定する</span></h3>
<pre><code class="language-cmd">set API_URL=https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod
set USER_POOL_ID=ap-northeast-1_XXXXXXXX
set CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXXXXX</code></pre>
<hr>
<h3><span id="toc21">テスト1: テストユーザーを作成する</span></h3>
<pre><code class="language-cmd">aws cognito-idp admin-create-user ^
  --user-pool-id %USER_POOL_ID% ^
  --username test@example.com ^
  --region ap-northeast-1</code></pre>
<p>ユーザーが FORCE_CHANGE_PASSWORD 状態で作成されます。次のコマンドで永続パスワードを設定して CONFIRMED 状態にします。</p>
<pre><code class="language-cmd">aws cognito-idp admin-set-user-password ^
  --user-pool-id %USER_POOL_ID% ^
  --username test@example.com ^
  --password TestPass1 ^
  --permanent ^
  --region ap-northeast-1</code></pre>
<p>レスポンスなし（エラーが出なければ成功）。</p>
<blockquote>
<p><strong>FORCE_CHANGE_PASSWORD 状態とは：</strong><br />Cognito で管理者が作成したユーザーの初期状態です。この状態で <code>initiate-auth</code> を実行すると <code>NEW_PASSWORD_REQUIRED</code> チャレンジが返されてトークンが取得できません。<code>--permanent</code> フラグを付けて <code>admin-set-user-password</code> を実行することで CONFIRMED（認証済み）状態に変えられます。</p>
</blockquote>
<hr>
<h3><span id="toc22">テスト2: 公開エンドポイントのテスト（/hello）→ 200</span></h3>
<pre><code class="language-cmd">curl %API_URL%/hello</code></pre>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{"message": "Hello! This is a public endpoint."}</code></pre>
<hr>
<h3><span id="toc23">テスト3: 未認証でプライベートエンドポイント（/profile）→ 401</span></h3>
<pre><code class="language-cmd">curl %API_URL%/profile</code></pre>
<p>期待するレスポンス（API Gateway が返す。Lambda は呼ばれていない）:</p>
<pre><code class="language-json">{"message": "Unauthorized"}</code></pre>
<hr>
<h3><span id="toc24">テスト4: IDトークンを取得する</span></h3>
<pre><code class="language-cmd">aws cognito-idp initiate-auth ^
  --auth-flow USER_PASSWORD_AUTH ^
  --auth-parameters "USERNAME=test@example.com,PASSWORD=TestPass1" ^
  --client-id %CLIENT_ID% ^
  --region ap-northeast-1</code></pre>
<p>レスポンス例（抜粋）:</p>
<pre><code class="language-json">{
    "AuthenticationResult": {
        "IdToken": "eyJra...",
        "AccessToken": "eyJra...",
        "RefreshToken": "eyJjb...",
        "ExpiresIn": 3600,
        "TokenType": "Bearer"
    }
}</code></pre>
<p><strong><code>IdToken</code> の値をコピーします</strong>（次のテストで使用）。</p>
<blockquote>
<p><strong>IdToken と AccessToken の違い：</strong></p>
<table>
<thead>
<tr>
<th>トークン</th>
<th>用途</th>
<th>JWT ペイロード</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>IdToken</strong></td>
<td>ユーザー認証（今回使用）</td>
<td>email / sub など</td>
</tr>
<tr>
<td>AccessToken</td>
<td>Cognito API へのアクセス</td>
<td>sub / scope など</td>
</tr>
</tbody>
</table>
<p>API Gateway の Cognito Authorizer に渡すのは <strong>IdToken</strong>。AccessToken を渡すと 403 になります。</p>
</blockquote>
<hr>
<h3><span id="toc25">テスト5: 認証ありでプライベートエンドポイント（/profile）→ 200</span></h3>
<pre><code class="language-cmd">set ID_TOKEN=eyJra...（上記で取得した IdToken の値）</code></pre>
<pre><code class="language-cmd">curl -H "Authorization: %ID_TOKEN%" %API_URL%/profile</code></pre>
<blockquote>
<p><strong>重要：</strong> <code>Bearer</code> プレフィックスは不要です。raw JWT（<code>eyJra...</code>）をそのまま Authorization ヘッダーに指定してください。</p>
</blockquote>
<p>期待するレスポンス:</p>
<pre><code class="language-json">{
  "message": "認証成功",
  "email": "test@example.com",
  "sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "token_use": "id"
}</code></pre>
<p><!-- ![認証成功レスポンス（SAM版）](images/cognito-sam-auth-success.jpg) --></p>
<hr>
<h2><span id="toc26">Step 8: AWSコンソールで確認（任意）</span></h2>
<p>SAM でデプロイしたリソースはコンソールでも確認できます。</p>
<ul>
<li><strong>API Gateway</strong>: APIs → <code>cognito-api-lambda-stack</code> → オーソライザー → <code>CognitoAuthorizer</code> が作成されていることを確認</li>
<li><strong>Cognito</strong>: ユーザープール → 作成された User Pool → ユーザー一覧で <code>test@example.com</code> が「確認済み」になっていることを確認</li>
<li><strong>Lambda</strong>: Lambda → 関数 → <code>cognito-api-lambda-stack-ApiFunction-XXXX</code> → 「モニタリング」タブ → テスト5実行後に呼び出し回数が増加していることを確認</li>
</ul>
<hr>
<h2><span id="toc27">Step 9: リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ずリソースを削除してください。</strong></p>
<pre><code class="language-cmd">sam delete --stack-name cognito-api-lambda-stack --region ap-northeast-1</code></pre>
<p>対話式で確認が入ります。両方 <code>y</code> で進めます。</p>
<pre><code class="language-plaintext">Are you sure you want to delete the stack cognito-api-lambda-stack? [y/N]: y
Are you sure you want to delete the folder cognito-api-lambda-stack in S3? [y/N]: y</code></pre>
<h3><span id="toc28">削除完了の確認</span></h3>
<pre><code class="language-cmd">aws cloudformation describe-stacks --stack-name cognito-api-lambda-stack --region ap-northeast-1</code></pre>
<pre><code class="language-plaintext">An error occurred (ValidationError): Stack with id cognito-api-lambda-stack does not exist</code></pre>
<p>このメッセージが表示されれば削除完了です。</p>
<h3><span id="toc29">sam delete で削除されるリソース一覧</span></h3>
<table>
<thead>
<tr>
<th>リソース</th>
<th>数</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cognito User Pool（ユーザーデータも削除される）</td>
<td>× 1</td>
</tr>
<tr>
<td>Cognito User Pool Client</td>
<td>× 1</td>
</tr>
<tr>
<td>API Gateway REST API（ステージ含む）</td>
<td>× 1</td>
</tr>
<tr>
<td>Lambda 関数</td>
<td>× 1</td>
</tr>
<tr>
<td>IAM ロール</td>
<td>× 1</td>
</tr>
<tr>
<td>Lambda デプロイパッケージ（S3）</td>
<td>× 1</td>
</tr>
<tr>
<td>CloudWatch Logs ロググループ</td>
<td>× 1</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>コンソール版との大きな違い（SAMのメリット）：</strong><br />コンソール版では API Gateway / Lambda / Cognito / IAM ロール / ロググループを個別に削除する必要があります。<br /><strong>SAMでは <code>sam delete</code> 1コマンドで全リソースを一括削除できます。</strong></p>
</blockquote>
<hr>
<h2><span id="toc30">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sam build</code> が失敗する</td>
<td><code>src/app.py</code> が存在しない</td>
<td><code>src/</code> フォルダに <code>app.py</code> があるか確認</td>
</tr>
<tr>
<td><code>sam deploy</code> が <code>CAPABILITY_IAM</code> エラー</td>
<td><code>samconfig.toml</code> の設定漏れ</td>
<td><code>capabilities = &quot;CAPABILITY_IAM&quot;</code> が含まれているか確認</td>
</tr>
<tr>
<td><code>sam deploy</code> が <code>Missing --stack-name</code> エラー</td>
<td><code>samconfig.toml</code> がない、または場所が違う</td>
<td><code>cognito-api-lambda/</code> 直下に <code>samconfig.toml</code> を作成する</td>
</tr>
<tr>
<td><code>initiate-auth</code> で <code>NotAuthorizedException: Incorrect username or password</code></td>
<td>ユーザーが FORCE_CHANGE_PASSWORD 状態</td>
<td><code>admin-set-user-password --permanent</code> で CONFIRMED にする</td>
</tr>
<tr>
<td><code>initiate-auth</code> で <code>InvalidParameterException</code></td>
<td><code>ALLOW_USER_PASSWORD_AUTH</code> が無効</td>
<td><code>sam deploy</code> で <code>UserPoolClient</code> が正しく作成されているか確認</td>
</tr>
<tr>
<td><code>/profile</code> に IDトークンを送っても 401</td>
<td><code>Bearer TOKEN</code> 形式で送っている</td>
<td>raw JWT（<code>eyJra...</code>）をそのまま Authorization ヘッダーに指定する</td>
</tr>
<tr>
<td><code>/profile</code> に IDトークンを送っても 403</td>
<td>IDトークンが期限切れ（1時間で失効）</td>
<td><code>initiate-auth</code> で新しいトークンを取得し直す</td>
</tr>
<tr>
<td><code>/profile</code> に IDトークンを送っても 403</td>
<td>AccessToken を使っている（IdToken ではない）</td>
<td><code>AuthenticationResult.IdToken</code> を使用しているか確認</td>
</tr>
<tr>
<td>Lambda の <code>claims</code> が空</td>
<td>AccessToken を使っている</td>
<td><code>IdToken</code> を Authorization ヘッダーに指定する</td>
</tr>
<tr>
<td>テストで <code>executionArn</code> が同じ名前で失敗する</td>
<td><code>admin-create-user</code> で <code>UsernameExistsException</code></td>
<td>すでに作成済みのためスキップして問題なし</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc31">まとめ</span></h2>
<p>今回のハンズオンで実現したこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SAM での Cognito 定義</strong></td>
<td><code>UserPool</code> + <code>UserPoolClient</code>（シークレットなし）を template.yaml で管理</td>
</tr>
<tr>
<td><strong>DefaultAuthorizer</strong></td>
<td>全エンドポイントに Cognito 認証を一括適用。<code>Authorizer: NONE</code> で /hello を除外</td>
</tr>
<tr>
<td><strong>JWT 認証フロー</strong></td>
<td>CLI で IDトークンを取得 → Authorization ヘッダーに指定 → claims を返す</td>
</tr>
<tr>
<td><strong>一括削除</strong></td>
<td><code>sam delete</code> で Cognito / API Gateway / Lambda を全てクリーンアップ</td>
</tr>
</tbody>
</table>
<h3><span id="toc32">SAMのメリットを実感できたポイント</span></h3>
<ul>
<li><code>GenerateSecret: false</code> + <code>ExplicitAuthFlows</code> でシークレットなしクライアントを定義（コンソール版では SPA タイプのクライアントを別途手動作成）</li>
<li><code>DefaultAuthorizer</code> の1行で全エンドポイントに Cognito 認証を適用（コンソール版では各エンドポイントに手動設定）</li>
<li><code>UserPoolArn: !GetAtt UserPool.Arn</code> で Cognito Authorizer と User Pool を自動連携（コンソール版では手動選択 + トークンのソースを手入力）</li>
</ul>
<hr>
<h2><span id="toc33">コンソール版と比較してみる</span></h2>
<p>SAMが裏で何をやっているか、同じ構成をAWSコンソールのみで構築する手順をまとめました。Cognitoの新UIで変わった操作（「アプリケーションを作成」からのウィザード、シークレットなしクライアントの作成方法、Authorizer のトークンソース設定）など、コンソール版ならではのつまずきポイントを解説しています。</p>
<p><!-- TODO: コンソール版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc34">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-cognito-api-gateway-lambda/">AWSハンズオン - Cognito + API Gateway + Lambda で認証付きAPIをSAMで構築しよう【コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-cognito-api-gateway-lambda/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWSハンズオン - Step Functions + Lambda で受注処理ワークフローをSAMで構築しよう【コンソール版との比較付き】</title>
		<link>https://caymezon.com/aws-handson-step-functions-lambda/</link>
					<comments>https://caymezon.com/aws-handson-step-functions-lambda/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 01 Mar 2026 05:25:21 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[SAM]]></category>
		<category><![CDATA[StepFunctions]]></category>
		<category><![CDATA[エラー処理]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[ステートマシン]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[ワークフロー]]></category>
		<category><![CDATA[並列処理]]></category>
		<category><![CDATA[初心者]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20231</guid>

					<description><![CDATA[<p>目次 はじめにSAM vs コンソール：どれだけ違うかキーワード解説前提条件使用するAWSサービスStep 1: プロジェクトフォルダを開くフォルダ構造Step 2: SAMテンプレートの確認（template.yaml [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-step-functions-lambda/">AWSハンズオン - Step Functions + Lambda で受注処理ワークフローをSAMで構築しよう【コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-18" checked><label class="toc-title" for="toc-checkbox-18">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">SAM vs コンソール：どれだけ違うか</a></li><li><a href="#toc3" tabindex="0">キーワード解説</a></li><li><a href="#toc4" tabindex="0">前提条件</a></li><li><a href="#toc5" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc6" tabindex="0">Step 1: プロジェクトフォルダを開く</a><ol><li><a href="#toc7" tabindex="0">フォルダ構造</a></li></ol></li><li><a href="#toc8" tabindex="0">Step 2: SAMテンプレートの確認（template.yaml）</a><ol><li><a href="#toc9" tabindex="0">Lambda 関数の定義</a></li><li><a href="#toc10" tabindex="0">ステートマシンの定義</a></li><li><a href="#toc11" tabindex="0">template.yaml のポイント解説</a></li></ol></li><li><a href="#toc12" tabindex="0">Step 3: Lambda コードの確認（src/）</a></li><li><a href="#toc13" tabindex="0">Step 4: samconfig.toml を作成する</a></li><li><a href="#toc14" tabindex="0">Step 5: sam build（ビルド）</a></li><li><a href="#toc15" tabindex="0">Step 6: sam deploy（デプロイ）</a><ol><li><a href="#toc16" tabindex="0">デプロイ完了の確認</a></li><li><a href="#toc17" tabindex="0">デプロイされるリソース一覧</a></li></ol></li><li><a href="#toc18" tabindex="0">Step 7: 動作テスト</a><ol><li><a href="#toc19" tabindex="0">事前準備: ARN を変数に設定する</a></li><li><a href="#toc20" tabindex="0">テスト1: 正常処理</a></li><li><a href="#toc21" tabindex="0">テスト2: 在庫切れエラー（ProcessInParallel Catch の確認）</a></li><li><a href="#toc22" tabindex="0">テスト3: 支払い失敗エラー（ProcessInParallel Catch の確認）</a></li><li><a href="#toc23" tabindex="0">テスト4: 検証エラー（ValidateOrder Retry + Catch の確認）</a></li></ol></li><li><a href="#toc24" tabindex="0">Step 8: AWSコンソールで確認（任意）</a></li><li><a href="#toc25" tabindex="0">Step 9: リソースの削除</a><ol><li><a href="#toc26" tabindex="0">削除完了の確認</a></li><li><a href="#toc27" tabindex="0">sam delete で削除されるリソース一覧</a></li></ol></li><li><a href="#toc28" tabindex="0">トラブルシューティング</a></li><li><a href="#toc29" tabindex="0">まとめ</a><ol><li><a href="#toc30" tabindex="0">SAMのメリットを実感できたポイント</a></li></ol></li><li><a href="#toc31" tabindex="0">コンソール版と比較してみる</a></li><li><a href="#toc32" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「複数のLambda関数をオーケストレーションするワークフローを、コードで管理したい」という要件に、<strong>AWS SAM（Serverless Application Model）</strong> は最適な選択肢のひとつです。</p>
<p>この記事では、<strong>AWS SAM</strong> を使って、受注処理ワークフロー（注文検証 → 在庫確認・支払い並列処理 → 注文確定）をゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">入力: {"order_id": "ORD-001", "item_id": "item-001", "quantity": 2}
  ↓
[ValidateOrder] 注文検証（Retry + Catch）
  ↓ 成功
[ProcessInParallel] 並列処理（Catch）
  ├─ [CheckInventory] 在庫確認
  └─ [ProcessPayment] 支払い処理
  ↓ 成功（結果リスト: [inventory_result, payment_result]）
[ConfirmOrder] 注文確定
  ↓
終了（成功）

[HandleError] エラー処理（Catch 先）
  ↓
終了（エラー通知済み）</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li><code>AWS::Serverless::StateMachine</code> を使った Step Functions の SAM 定義</li>
<li><code>DefinitionSubstitutions</code> で ASL 内の Lambda ARN を <strong>自動差し込み</strong>する仕組み</li>
<li><code>LambdaInvokePolicy</code> による1行での権限付与（コンソール版ではIAMロールを自動生成）</li>
<li>CLI でのテスト実行 + コンソールの実行グラフで視覚的に確認</li>
</ul>
<p><strong>このハンズオンの特徴：</strong></p>
<ul>
<li><code>sam build</code> + <code>sam deploy</code> の <strong>2コマンドで全リソースをデプロイ</strong></li>
<li>Step Functions（ステートマシン）+ Lambda × 5 + IAM ロール × 6 を <code>template.yaml</code> 1ファイルで管理</li>
<li><code>sam delete</code> で<strong>全リソースを一括削除</strong>（コンソール版では5つのLambda・ステートマシン・IAMロールを個別に削除）</li>
</ul>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">SAM vs コンソール：どれだけ違うか</span></h2>
<table>
<thead>
<tr>
<th>比較項目</th>
<th>SAM（コード）</th>
<th>コンソール（手動）</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Lambda ARN の参照</strong></td>
<td><code>${ValidateFunctionArn}</code> で自動差し込み</td>
<td>5関数のARNを手動でコピー・貼り付け</td>
</tr>
<tr>
<td><strong>IAM ロール</strong></td>
<td><code>LambdaInvokePolicy</code> × 5 で自動生成</td>
<td>「新しいロールを作成」で自動生成（1回限り）</td>
</tr>
<tr>
<td><strong>ASL 定義の管理</strong></td>
<td><code>template.yaml</code> で Git 管理</td>
<td>コンソールのエディタのみ</td>
</tr>
<tr>
<td><strong>削除</strong></td>
<td><code>sam delete</code> 1コマンド</td>
<td>Lambda × 5 / ステートマシン / IAMロール / ロググループを個別に削除</td>
</tr>
<tr>
<td><strong>再現性</strong></td>
<td>チームで同じ環境を即座に再現できる</td>
<td>手順書が必要</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc3">キーワード解説</span></h2>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ステートマシン</strong></td>
<td>ワークフローの全体定義。状態（State）の集合と遷移ルールをもつ</td>
</tr>
<tr>
<td><strong>Task ステート</strong></td>
<td>Lambda などの外部リソースを呼び出す状態</td>
</tr>
<tr>
<td><strong>Parallel ステート</strong></td>
<td>複数のブランチを<strong>同時に実行</strong>する状態。両ブランチの結果がリストで次のステートに渡る</td>
</tr>
<tr>
<td><strong>Retry</strong></td>
<td>エラー発生時に同じステートを自動で<strong>再実行</strong>するルール</td>
</tr>
<tr>
<td><strong>Catch</strong></td>
<td>リトライ後もエラーが続く場合に<strong>別のステートへ遷移</strong>するルール</td>
</tr>
<tr>
<td><strong>ResultPath</strong></td>
<td>Catch 時にエラー情報をどのフィールドに格納するかの指定（<code>&quot;$.error&quot;</code> → 元の入力の <code>error</code> キーにマージ）</td>
</tr>
<tr>
<td><strong>ASL</strong></td>
<td>Amazon States Language。ステートマシン定義の JSON 形式</td>
</tr>
<tr>
<td><strong>DefinitionSubstitutions</strong></td>
<td>ASL 内の <code>${変数名}</code> を実際の値（ARN など）に置換するSAMの仕組み</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc4">前提条件</span></h2>
<table>
<thead>
<tr>
<th>ツール</th>
<th>確認コマンド</th>
<th>最低バージョン目安</th>
</tr>
</thead>
<tbody>
<tr>
<td>AWS CLI v2</td>
<td><code>aws --version</code></td>
<td>2.x</td>
</tr>
<tr>
<td>AWS SAM CLI</td>
<td><code>sam --version</code></td>
<td>1.x</td>
</tr>
<tr>
<td>Python 3.12</td>
<td><code>python --version</code></td>
<td>3.12</td>
</tr>
<tr>
<td>Git</td>
<td><code>git --version</code></td>
<td>-</td>
</tr>
<tr>
<td>VSCode</td>
<td>-</td>
<td>最新版推奨</td>
</tr>
</tbody>
</table>
<pre><code class="language-cmd">aws sts get-caller-identity</code></pre>
<p>アカウントIDが表示されれば認証設定済みです。</p>
<hr>
<h2><span id="toc5">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Step Functions</strong></td>
<td>ワークフロー（ステートマシン）の管理・実行</td>
<td>月4,000回の状態遷移まで無料（標準タイプ）</td>
</tr>
<tr>
<td><strong>Lambda</strong></td>
<td>各ステートで実行されるビジネスロジック（5関数）</td>
<td>月100万リクエスト・400,000 GB-秒まで無料</td>
</tr>
<tr>
<td><strong>IAM</strong></td>
<td>Lambda・Step Functionsの実行権限管理</td>
<td>無料</td>
</tr>
<tr>
<td><strong>S3</strong></td>
<td>SAM デプロイパッケージ置き場</td>
<td>少量のため実質無料</td>
</tr>
<tr>
<td><strong>CloudWatch Logs</strong></td>
<td>Lambdaの実行ログ</td>
<td>月5GBまで無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc6">Step 1: プロジェクトフォルダを開く</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\step-functions-lambda</code></pre>
<h3><span id="toc7">フォルダ構造</span></h3>
<pre><code class="language-plaintext">step-functions-lambda/
├── template.yaml           # SAMテンプレート（Step Functions + Lambda 定義）
├── samconfig.toml          # デプロイ設定（gitignore 対象・毎回手動作成が必要）
├── docs/
│   ├── 1_console.md        # AWSコンソール版手順
│   └── 2_sam.md            # SAM版手順
└── src/
    ├── validate.py         # 注文検証 Lambda
    ├── inventory.py        # 在庫確認 Lambda
    ├── payment.py          # 支払い処理 Lambda
    ├── confirm.py          # 注文確定 Lambda
    └── error_handler.py    # エラー処理 Lambda</code></pre>
<hr>
<h2><span id="toc8">Step 2: SAMテンプレートの確認（template.yaml）</span></h2>
<p><code>template.yaml</code> は全 AWSリソースの設計図です。ポイントを確認しておきます。</p>
<h3><span id="toc9">Lambda 関数の定義</span></h3>
<pre><code class="language-yaml">Globals:
  Function:
    Runtime: python3.12
    Timeout: 10
    MemorySize: 128

Resources:
  ValidateFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/           # src/ 配下の全 .py を1つのパッケージとしてデプロイ
      Handler: validate.lambda_handler

  InventoryFunction:          # 在庫確認（Parallel ブランチ 1）
  PaymentFunction:            # 支払い処理（Parallel ブランチ 2）
  ConfirmFunction:            # 注文確定
  ErrorHandlerFunction:       # エラー処理（Catch 先）</code></pre>
<p><strong>ポイント:</strong> 5つの Lambda 関数が全て <code>CodeUri: src/</code> を共有しています。SAM は <code>src/</code> フォルダを1つの ZIP パッケージとして S3 にアップロードし、各関数は <code>Handler</code> でどのファイル・関数を使うかを指定します。</p>
<h3><span id="toc10">ステートマシンの定義</span></h3>
<pre><code class="language-yaml">  OrderWorkflow:
    Type: AWS::Serverless::StateMachine
    Properties:
      Type: STANDARD
      Definition:
        StartAt: ValidateOrder
        States:
          ValidateOrder:
            Type: Task
            Resource: "${ValidateFunctionArn}"   # DefinitionSubstitutions で実際の ARN に置換
            Next: ProcessInParallel
            Retry:
              - ErrorEquals: [States.ALL]
                IntervalSeconds: 2
                MaxAttempts: 1
                BackoffRate: 2.0
            Catch:
              - ErrorEquals: [States.ALL]
                Next: HandleError
                ResultPath: "$.error"

          ProcessInParallel:
            Type: Parallel
            Branches:
              - StartAt: CheckInventory
                States:
                  CheckInventory:
                    Type: Task
                    Resource: "${InventoryFunctionArn}"
                    End: true
              - StartAt: ProcessPayment
                States:
                  ProcessPayment:
                    Type: Task
                    Resource: "${PaymentFunctionArn}"
                    End: true
            Next: ConfirmOrder
            Catch:
              - ErrorEquals: [States.ALL]
                Next: HandleError
                ResultPath: "$.error"

          ConfirmOrder:
            Type: Task
            Resource: "${ConfirmFunctionArn}"
            End: true

          HandleError:
            Type: Task
            Resource: "${ErrorHandlerFunctionArn}"
            End: true

      DefinitionSubstitutions:           # ${変数名} を実際の ARN に自動置換
        ValidateFunctionArn: !GetAtt ValidateFunction.Arn
        InventoryFunctionArn: !GetAtt InventoryFunction.Arn
        PaymentFunctionArn: !GetAtt PaymentFunction.Arn
        ConfirmFunctionArn: !GetAtt ConfirmFunction.Arn
        ErrorHandlerFunctionArn: !GetAtt ErrorHandlerFunction.Arn

      Policies:                          # ステートマシンが Lambda を呼び出す IAM 権限
        - LambdaInvokePolicy:
            FunctionName: !Ref ValidateFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref InventoryFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref PaymentFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref ConfirmFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref ErrorHandlerFunction</code></pre>
<h3><span id="toc11">template.yaml のポイント解説</span></h3>
<table>
<thead>
<tr>
<th>ポイント</th>
<th>説明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong><code>DefinitionSubstitutions</code></strong></td>
<td>ASL 内の <code>${ValidateFunctionArn}</code> などを <code>!GetAtt ValidateFunction.Arn</code> で取得した実際の ARN に置換する。コンソール版で5回ARNをコピーする手間がなくなる</td>
</tr>
<tr>
<td><strong><code>LambdaInvokePolicy</code> × 5</strong></td>
<td>SAM 組み込みポリシー。<code>lambda:InvokeFunction</code> 権限を1行で付与。コンソール版ではIAMロールの自動生成で対応</td>
</tr>
<tr>
<td><strong><code>Type: STANDARD</code></strong></td>
<td>監査ログ・正確に1回実行・長時間実行対応。コンソール版では「標準」を選択する部分に対応</td>
</tr>
<tr>
<td><strong><code>ResultPath: &quot;$.error&quot;</code></strong></td>
<td>エラー情報を元の入力の <code>$.error</code> フィールドにマージして HandleError へ渡す</td>
</tr>
<tr>
<td><strong><code>CodeUri: src/</code>（共有）</strong></td>
<td>5関数で1つの src/ パッケージを共有。コードを変更すると全関数に反映される</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc12">Step 3: Lambda コードの確認（src/）</span></h2>
<p>各ファイルの役割を確認しておきます。</p>
<table>
<thead>
<tr>
<th>ファイル</th>
<th>ステート</th>
<th>特記事項</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>validate.py</code></td>
<td>ValidateOrder</td>
<td><code>order_id</code> / <code>item_id</code> / <code>quantity</code> を検証。欠けていると <code>ValueError</code></td>
</tr>
<tr>
<td><code>inventory.py</code></td>
<td>CheckInventory</td>
<td><code>item-999</code> で <code>ValueError</code>（在庫切れテスト用）</td>
</tr>
<tr>
<td><code>payment.py</code></td>
<td>ProcessPayment</td>
<td><code>fail_payment=True</code> で <code>RuntimeError</code>（支払い失敗テスト用）</td>
</tr>
<tr>
<td><code>confirm.py</code></td>
<td>ConfirmOrder</td>
<td><strong><code>event[0]</code>（在庫確認結果）と <code>event[1]</code>（支払い結果）を受け取る</strong>。Parallel 後はリスト形式になる</td>
</tr>
<tr>
<td><code>error_handler.py</code></td>
<td>HandleError</td>
<td><code>event[&quot;error&quot;]</code> にエラー情報、それ以外に元の入力が格納されている</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>各 Python コードの全文はコンソール版記事を参照してください。</strong><br />SAM版・コンソール版で使用する Lambda コードは共通です。コンソール版では各関数ごとにコードを貼り付ける手順とあわせて全文を掲載しています。</p>
<p>→ <a href="/aws-handson-console-step-functions-lambda/">AWSコンソールだけでStep Functions + Lambdaワークフローを構築する手順【SAM版との比較付き / 新UI対応】</a></p>
</blockquote>
<blockquote>
<p><strong><code>confirm.py</code> の <code>event[0]</code> / <code>event[1]</code> について：</strong><br />Parallel ステートが成功すると、各ブランチの出力が**配列（リスト）**にまとめられて次のステートに渡されます。このため ConfirmFunction は <code>event[0]</code> で在庫確認結果、<code>event[1]</code> で支払い結果にアクセスしています。この仕様を知らないと <code>list indices must be integers</code> エラーで詰まります。</p>
</blockquote>
<hr>
<h2><span id="toc13">Step 4: samconfig.toml を作成する</span></h2>
<p><code>samconfig.toml</code> は <code>.gitignore</code> で管理外のため、<strong>毎回手動で作成</strong>する必要があります。</p>
<p><code>step-functions-lambda/samconfig.toml</code> を新規作成して以下を貼り付けます。</p>
<pre><code class="language-toml">version = 0.1

[default.deploy.parameters]
stack_name = "step-functions-lambda-stack"
resolve_s3 = true
s3_prefix = "step-functions-lambda-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
disable_rollback = true
image_repositories = []

[default.global.parameters]
region = "ap-northeast-1"</code></pre>
<blockquote>
<p><strong><code>samconfig.toml</code> の置き場所：</strong><br /><code>step-functions-lambda/</code> フォルダの<strong>直下</strong>に置く必要があります。</p>
</blockquote>
<hr>
<h2><span id="toc14">Step 5: sam build（ビルド）</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\step-functions-lambda
sam build</code></pre>
<p>成功すると以下のように表示されます。</p>
<pre><code class="language-plaintext">Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml</code></pre>
<blockquote>
<p><strong><code>sam build</code> が行っていること：</strong></p>
<ul>
<li><code>src/</code> フォルダを ZIP パッケージ化して <code>.aws-sam/build/</code> に配置</li>
<li><code>template.yaml</code> を <code>.aws-sam/build/template.yaml</code> にコピー</li>
<li>Lambda デプロイの準備を整える</li>
</ul>
</blockquote>
<hr>
<h2><span id="toc15">Step 6: sam deploy（デプロイ）</span></h2>
<pre><code class="language-cmd">sam deploy</code></pre>
<p>変更内容が表示されて確認を求められます。</p>
<pre><code class="language-plaintext">Deploy this changeset? [y/N]: y</code></pre>
<p><code>y</code> を入力して進めます。数分でデプロイが完了します。</p>
<h3><span id="toc16">デプロイ完了の確認</span></h3>
<p>ターミナルに <strong>Outputs</strong> が表示されます。</p>
<pre><code class="language-plaintext">Outputs
----------------------------------------------------------------------
Key    OrderWorkflowArn
Value  arn:aws:states:ap-northeast-1:123456789012:stateMachine:step-functions-lambda-stack-order-workflow

Key    OrderWorkflowName
Value  step-functions-lambda-stack-order-workflow
----------------------------------------------------------------------</code></pre>
<p><strong><code>OrderWorkflowArn</code> を控えておきます。</strong>（テスト実行コマンドで使用）</p>
<h3><span id="toc17">デプロイされるリソース一覧</span></h3>
<pre><code class="language-plaintext">Step Functions ステートマシン × 1  : step-functions-lambda-stack-order-workflow
Lambda 関数 × 5                    : step-functions-lambda-stack-ValidateFunction-XXXX 他
IAM ロール × 6                     : ステートマシン用 × 1 / Lambda 用 × 5
S3                                 : SAM デプロイパッケージ
CloudWatch Logs                    : Lambda 自動生成ログ × 5</code></pre>
<hr>
<h2><span id="toc18">Step 7: 動作テスト</span></h2>
<h3><span id="toc19">事前準備: ARN を変数に設定する</span></h3>
<pre><code class="language-cmd">set SM_ARN=arn:aws:states:ap-northeast-1:123456789012:stateMachine:step-functions-lambda-stack-order-workflow</code></pre>
<blockquote>
<p><code>123456789012</code> を自分のアカウントIDに置き換えてください（Outputs の値をそのままコピーすればよいです）。</p>
</blockquote>
<hr>
<h3><span id="toc20">テスト1: 正常処理</span></h3>
<pre><code class="language-cmd">aws stepfunctions start-execution ^
  --state-machine-arn %SM_ARN% ^
  --name "test-normal-1" ^
  --input "{\"order_id\": \"ORD-001\", \"item_id\": \"item-001\", \"quantity\": 2}" ^
  --region ap-northeast-1</code></pre>
<p>レスポンス例:</p>
<pre><code class="language-json">{
    "executionArn": "arn:aws:states:ap-northeast-1:123456789012:execution:...:test-normal-1",
    "startDate": "2026-03-01T10:00:00.000000+09:00"
}</code></pre>
<p><strong>実行結果を確認（数秒後）:</strong></p>
<pre><code class="language-cmd">aws stepfunctions describe-execution ^
  --execution-arn "上記の executionArn" ^
  --region ap-northeast-1</code></pre>
<p><code>&quot;status&quot;: &quot;SUCCEEDED&quot;</code> と以下のような出力が表示されます。</p>
<pre><code class="language-json">{
  "order_id": "ORD-001",
  "item_id": "item-001",
  "quantity": 2,
  "amount": 2000,
  "confirmed_at": "2026-03-01T01:00:00.000000+00:00",
  "status": "confirmed"
}</code></pre>
<p><strong>AWSコンソールで実行グラフを確認（任意）:</strong></p>
<p>Step Functions → ステートマシン → <code>step-functions-lambda-stack-order-workflow</code> → 実行一覧 → <code>test-normal-1</code> をクリックすると、全ステートが緑色になり <code>ProcessInParallel</code> の2ブランチが同時に実行されたことを視覚的に確認できます。</p>
<p><!-- ![正常処理の実行グラフ](images/stepfunctions-sam-success.jpg) --></p>
<hr>
<h3><span id="toc21">テスト2: 在庫切れエラー（ProcessInParallel Catch の確認）</span></h3>
<pre><code class="language-cmd">aws stepfunctions start-execution ^
  --state-machine-arn %SM_ARN% ^
  --name "test-inventory-error" ^
  --input "{\"order_id\": \"ORD-002\", \"item_id\": \"item-999\", \"quantity\": 1}" ^
  --region ap-northeast-1</code></pre>
<p><strong>期待する動作:</strong></p>
<ol>
<li><code>ValidateOrder</code> → 成功</li>
<li><code>ProcessInParallel</code> → <code>CheckInventory</code> が <code>ValueError</code>（在庫切れ）を発生させる</li>
<li><code>ProcessInParallel</code> の Catch が動作 → <code>HandleError</code> へ遷移</li>
<li>実行ステータス: <code>SUCCEEDED</code>（HandleError が正常完了）</li>
</ol>
<p>出力例:</p>
<pre><code class="language-json">{
  "status": "error",
  "error_type": "ValueError",
  "error_cause": "在庫不足: item_id=item-999",
  "original_input": {
    "order_id": "ORD-002",
    "item_id": "item-999",
    "quantity": 1,
    "validated": true
  }
}</code></pre>
<blockquote>
<p><strong>実行ステータスが <code>SUCCEEDED</code> になる理由：</strong><br /><code>HandleError</code> 関数が正常終了（<code>return result</code>）しているため、ステートマシン全体としては「成功」扱いになります。エラーが「処理されずに残った」場合は <code>FAILED</code> になります。</p>
</blockquote>
<hr>
<h3><span id="toc22">テスト3: 支払い失敗エラー（ProcessInParallel Catch の確認）</span></h3>
<pre><code class="language-cmd">aws stepfunctions start-execution ^
  --state-machine-arn %SM_ARN% ^
  --name "test-payment-error" ^
  --input "{\"order_id\": \"ORD-003\", \"item_id\": \"item-001\", \"quantity\": 2, \"fail_payment\": true}" ^
  --region ap-northeast-1</code></pre>
<p><code>ProcessPayment</code> が <code>RuntimeError</code> を発生させ、同様に <code>HandleError</code> へ遷移します。</p>
<hr>
<h3><span id="toc23">テスト4: 検証エラー（ValidateOrder Retry + Catch の確認）</span></h3>
<pre><code class="language-cmd">aws stepfunctions start-execution ^
  --state-machine-arn %SM_ARN% ^
  --name "test-validate-error" ^
  --input "{\"item_id\": \"item-001\", \"quantity\": 2}" ^
  --region ap-northeast-1</code></pre>
<p><code>order_id</code> を省略しているため <code>ValidateOrder</code> が <code>ValueError</code> を発生させます。</p>
<p><strong>Retry の確認（AWSコンソール）:</strong></p>
<p>Step Functions コンソールの実行グラフで <code>ValidateOrder</code> を選択 → <strong>「イベント」タブ</strong> で以下の順序を確認します：</p>
<pre><code class="language-plaintext">TaskStateEntered → TaskScheduled → TaskStarted → TaskFailed
  → TaskScheduled（リトライ1回目）→ TaskStarted → TaskFailed
  → TaskStateExited（Catch 発動）→ HandleError へ</code></pre>
<p><code>ValidateFunction</code> が合計2回呼ばれてから <code>HandleError</code> へ遷移することが分かります。</p>
<hr>
<h2><span id="toc24">Step 8: AWSコンソールで確認（任意）</span></h2>
<p>SAMでデプロイしたリソースはコンソールで確認できます。</p>
<ul>
<li><strong>Step Functions</strong>: ステートマシン一覧 → <code>step-functions-lambda-stack-order-workflow</code> → 各実行のグラフを確認</li>
<li><strong>Lambda</strong>: Lambda → 関数 → <code>step-functions-lambda-stack-ValidateFunction-XXXX</code> → 「モニタリング」→ 呼び出し回数を確認</li>
<li><strong>CloudWatch Logs</strong>: Lambda → 対象関数 → 「モニタリング」タブ → 「CloudWatch Logs を表示」</li>
</ul>
<hr>
<h2><span id="toc25">Step 9: リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ずリソースを削除してください。</strong></p>
<pre><code class="language-cmd">sam delete --stack-name step-functions-lambda-stack --region ap-northeast-1</code></pre>
<p>対話式で確認が入ります。両方 <code>y</code> で進めます。</p>
<pre><code class="language-plaintext">Are you sure you want to delete the stack step-functions-lambda-stack? [y/N]: y
Are you sure you want to delete the folder step-functions-lambda-stack in S3? [y/N]: y</code></pre>
<h3><span id="toc26">削除完了の確認</span></h3>
<pre><code class="language-cmd">aws cloudformation describe-stacks --stack-name step-functions-lambda-stack --region ap-northeast-1</code></pre>
<pre><code class="language-plaintext">An error occurred (ValidationError): Stack with id step-functions-lambda-stack does not exist</code></pre>
<p>このメッセージが表示されれば削除完了です。</p>
<h3><span id="toc27">sam delete で削除されるリソース一覧</span></h3>
<table>
<thead>
<tr>
<th>リソース</th>
<th>数</th>
</tr>
</thead>
<tbody>
<tr>
<td>Step Functions ステートマシン</td>
<td>× 1</td>
</tr>
<tr>
<td>Lambda 関数</td>
<td>× 5</td>
</tr>
<tr>
<td>IAM ロール（ステートマシン用・Lambda 用）</td>
<td>× 6</td>
</tr>
<tr>
<td>Lambda デプロイパッケージ（S3）</td>
<td>× 1</td>
</tr>
<tr>
<td>CloudWatch Logs ロググループ</td>
<td>× 5</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>コンソール版との大きな違い（SAMのメリット）：</strong><br />コンソール版では Lambda × 5 / ステートマシン / IAM ロール × 6 / ロググループを個別に削除する必要があります。<br /><strong>SAMでは <code>sam delete</code> 1コマンドで全リソースを一括削除できます。</strong></p>
</blockquote>
<hr>
<h2><span id="toc28">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sam build</code> が失敗する</td>
<td><code>src/</code> フォルダ内のファイルが存在しない</td>
<td><code>src/</code> 配下に5つの <code>.py</code> ファイルがあるか確認</td>
</tr>
<tr>
<td><code>sam deploy</code> が <code>Missing --stack-name</code> エラー</td>
<td><code>samconfig.toml</code> がない、または場所が違う</td>
<td><code>step-functions-lambda/</code> 直下に <code>samconfig.toml</code> を作成する</td>
</tr>
<tr>
<td>実行が即座に <code>FAILED</code> になる</td>
<td>Lambda ARN が間違っている</td>
<td><code>sam deploy</code> のエラーログを確認</td>
</tr>
<tr>
<td>テストで <code>executionArn</code> が同じ名前で失敗する</td>
<td><code>--name</code> が重複している</td>
<td><code>--name</code> のサフィックスを変えて再実行する（例: <code>test-normal-2</code>）</td>
</tr>
<tr>
<td><code>describe-execution</code> の <code>status</code> が <code>RUNNING</code> のまま</td>
<td>Lambda がまだ実行中</td>
<td>数秒〜数十秒待ってから再実行する</td>
</tr>
<tr>
<td><code>ConfirmFunction</code> でエラー: <code>list index out of range</code></td>
<td>Parallel ブランチが正常完了していない</td>
<td><code>ProcessInParallel</code> の後にのみ呼ばれる関数かを確認する</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc29">まとめ</span></h2>
<p>今回のハンズオンで実現したこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SAM でのステートマシン定義</strong></td>
<td><code>AWS::Serverless::StateMachine</code> + <code>DefinitionSubstitutions</code> で ARN を自動差し込み</td>
</tr>
<tr>
<td><strong>Lambda × 5 の一括デプロイ</strong></td>
<td><code>CodeUri: src/</code> 共有パッケージ + <code>Handler</code> で各関数を定義</td>
</tr>
<tr>
<td><strong>Retry + Catch</strong></td>
<td>ValidateOrder でリトライ → 失敗 → HandleError へ遷移</td>
</tr>
<tr>
<td><strong>Parallel + Catch</strong></td>
<td>在庫確認と支払い処理を同時実行 → 一方が失敗したら HandleError へ</td>
</tr>
<tr>
<td><strong>一括削除</strong></td>
<td><code>sam delete</code> で全リソースをクリーンアップ</td>
</tr>
</tbody>
</table>
<h3><span id="toc30">SAMのメリットを実感できたポイント</span></h3>
<ul>
<li><code>DefinitionSubstitutions</code> により Lambda ARN のコピー貼り付けが不要</li>
<li><code>LambdaInvokePolicy</code> × 5 で IAM 権限設定が1行ずつで完結</li>
<li><code>sam delete</code> でステートマシン・Lambda 5つ・IAM ロール 6つを一括削除</li>
</ul>
<hr>
<h2><span id="toc31">コンソール版と比較してみる</span></h2>
<p>SAMが裏で何をやっているか、同じ構成をAWSコンソールのみで構築する手順をまとめました。Workflow Studioの新UI（3択モーダル・「{ } コード」タブ）での操作方法や、コンソール版ならではのつまずきポイントを解説しています。</p>
<p><!-- TODO: コンソール版記事へのリンクを追加 --></p>
<hr>
<h2><span id="toc32">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-step-functions-lambda/">AWSハンズオン - Step Functions + Lambda で受注処理ワークフローをSAMで構築しよう【コンソール版との比較付き】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-step-functions-lambda/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWSハンズオン - S3 + CloudFrontで静的サイトをCDN配信しよう【SAM版 / Windows対応】</title>
		<link>https://caymezon.com/aws-handson-s3-cloudfront-hosting/</link>
					<comments>https://caymezon.com/aws-handson-s3-cloudfront-hosting/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Mon, 23 Feb 2026 07:34:56 +0000</pubDate>
				<category><![CDATA[AWS Basic]]></category>
		<category><![CDATA[Cloud & Infra]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[CDN]]></category>
		<category><![CDATA[CloudFront]]></category>
		<category><![CDATA[OAC]]></category>
		<category><![CDATA[S3]]></category>
		<category><![CDATA[SAM]]></category>
		<category><![CDATA[キャッシュ]]></category>
		<category><![CDATA[サーバーレス]]></category>
		<category><![CDATA[ハンズオン]]></category>
		<category><![CDATA[初心者]]></category>
		<category><![CDATA[静的サイトホスティング]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20212</guid>

					<description><![CDATA[<p>目次 はじめにCDN と OAC の仕組みを理解するキーワード解説なぜ S3 を非公開にして CloudFront 経由で配信するのか前提条件使用するAWSサービスStep 1: プロジェクトフォルダを開くフォルダ構造S [&#8230;]</p>
<p>The post <a href="https://caymezon.com/aws-handson-s3-cloudfront-hosting/">AWSハンズオン - S3 + CloudFrontで静的サイトをCDN配信しよう【SAM版 / Windows対応】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-20" checked><label class="toc-title" for="toc-checkbox-20">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">CDN と OAC の仕組みを理解する</a><ol><li><a href="#toc3" tabindex="0">キーワード解説</a></li><li><a href="#toc4" tabindex="0">なぜ S3 を非公開にして CloudFront 経由で配信するのか</a></li></ol></li><li><a href="#toc5" tabindex="0">前提条件</a></li><li><a href="#toc6" tabindex="0">使用するAWSサービス</a></li><li><a href="#toc7" tabindex="0">Step 1: プロジェクトフォルダを開く</a><ol><li><a href="#toc8" tabindex="0">フォルダ構造</a></li></ol></li><li><a href="#toc9" tabindex="0">Step 2: SAMテンプレートの確認（template.yaml）</a><ol><li><a href="#toc10" tabindex="0">リソースの依存関係</a></li><li><a href="#toc11" tabindex="0">template.yaml の主要部分</a></li><li><a href="#toc12" tabindex="0">template.yaml のポイント解説</a></li></ol></li><li><a href="#toc13" tabindex="0">Step 3: website/ の確認</a><ol><li><a href="#toc14" tabindex="0">website/index.html（トップページ）</a></li><li><a href="#toc15" tabindex="0">website/error.html（カスタム404ページ）</a></li></ol></li><li><a href="#toc16" tabindex="0">Step 4: samconfig.toml を作成する</a></li><li><a href="#toc17" tabindex="0">Step 5: sam build（ビルド）</a></li><li><a href="#toc18" tabindex="0">Step 6: sam deploy（デプロイ）</a><ol><li><a href="#toc19" tabindex="0">デプロイ完了の確認</a></li></ol></li><li><a href="#toc20" tabindex="0">Step 7: HTMLファイルをアップロード</a></li><li><a href="#toc21" tabindex="0">Step 8: 動作テスト</a><ol><li><a href="#toc22" tabindex="0">8-1. トップページの確認</a></li><li><a href="#toc23" tabindex="0">8-2. HTTP → HTTPS リダイレクトの確認</a></li><li><a href="#toc24" tabindex="0">8-3. カスタム 404 ページの確認</a></li><li><a href="#toc25" tabindex="0">8-4. CDN キャッシュのテスト（任意）</a></li></ol></li><li><a href="#toc26" tabindex="0">Step 9: AWSコンソールで確認（任意）</a></li><li><a href="#toc27" tabindex="0">Step 10: リソースの削除</a><ol><li><a href="#toc28" tabindex="0">① S3バケットを先に空にする</a></li><li><a href="#toc29" tabindex="0">② スタックを削除する</a></li><li><a href="#toc30" tabindex="0">削除完了の確認</a></li></ol></li><li><a href="#toc31" tabindex="0">トラブルシューティング</a></li><li><a href="#toc32" tabindex="0">まとめ</a><ol><li><a href="#toc33" tabindex="0">SAMのメリットを実感できたポイント</a></li></ol></li><li><a href="#toc34" tabindex="0">コンソール操作と比較してみる</a></li><li><a href="#toc35" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>「ウェブサイトを公開したいけど、サーバーは持ちたくない」「静的なHTMLサイトを世界中から速く見せたい」という要件に、<strong>S3 + CloudFront の組み合わせ</strong>はベストな選択肢のひとつです。</p>
<p>この記事では、<strong>AWS SAM（Serverless Application Model）</strong> を使って、S3バケットにHTMLを配置してCloudFront CDN経由で配信する静的サイトホスティング環境をゼロから構築するハンズオンを紹介します。</p>
<pre><code class="language-plaintext">ブラウザ
  ↓ HTTPS（HTTPは自動リダイレクト）
CloudFront（CDN・エッジキャッシュ）
  ↓ OAC（AWS Signature v4 署名付きリクエスト）
S3 バケット（非公開・パブリックアクセスブロック）
  └── index.html / error.html</code></pre>
<p><strong>このハンズオンで体験できること：</strong></p>
<ul>
<li>S3バケットを非公開のまま CloudFront 経由でのみ公開する <strong>OAC（Origin Access Control）</strong> の仕組み</li>
<li>HTTP → HTTPS の自動リダイレクト</li>
<li>カスタム 404 エラーページ（<code>error.html</code>）</li>
<li>CDN キャッシュの動作と、更新時の <strong>キャッシュ無効化（Invalidation）</strong> の操作</li>
</ul>
<p><strong>このハンズオンの特徴：</strong></p>
<ul>
<li><strong>Lambda 関数なしの SAM ハンズオン</strong> — SAMはCloudFormationの拡張のため、Lambdaなしの純粋なインフラ構成もそのままデプロイできます</li>
<li>S3・OAC・CloudFront・バケットポリシーの4リソースを1ファイルで定義し、依存関係をSAMが自動解決</li>
<li><code>sam delete</code> でCloudFront含む全リソースを一括削除（コンソール版の「月末まで待つ」問題がない）</li>
</ul>
<hr>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51DEDQXj6oL._SL500_.jpg","\/41F589smNwL._SL500_.jpg","\/41R6f9yyCWL._SL500_.jpg","\/41HqWQ9BvmL._SL500_.jpg","\/41p8p0ZU79L._SL500_.jpg","\/41qLC-fndBL._SL500_.jpg","\/41fcLv9VT5L._SL500_.jpg","\/51lRvCsvHqL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815607850","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815607850","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%83%BB%E4%BB%95%E7%B5%84%E3%81%BF%E3%83%BB%E9%87%8D%E8%A6%81%E7%94%A8%E8%AA%9E%E3%81%8C%E5%85%A8%E9%83%A8%E3%82%8F%E3%81%8B%E3%82%8B%E6%95%99%E7%A7%91%E6%9B%B8%20(%E8%A6%8B%E3%82%8B%E3%81%A0%E3%81%91%E5%9B%B3%E8%A7%A3)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"eaCUB","s":"s"});</script></p>
<div id="msmaflink-eaCUB">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">CDN と OAC の仕組みを理解する</span></h2>
<h3><span id="toc3">キーワード解説</span></h3>
<table>
<thead>
<tr>
<th>用語</th>
<th>意味</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>CDN</strong></td>
<td>コンテンツをユーザーに近いエッジサーバーからキャッシュ配信する仕組み。日本からアクセスするユーザーには日本のエッジから配信される</td>
</tr>
<tr>
<td><strong>OAC（Origin Access Control）</strong></td>
<td>CloudFront が S3 に安全にアクセスするための認証方式（現在の推奨。旧: OAI）</td>
</tr>
<tr>
<td><strong>オリジン</strong></td>
<td>CDN が配信元として参照するサーバー（今回は S3 バケット）</td>
</tr>
<tr>
<td><strong>エッジロケーション</strong></td>
<td>CloudFront の世界中にあるキャッシュサーバーの拠点（日本は東京・大阪等）</td>
</tr>
<tr>
<td><strong>キャッシュ無効化（Invalidation）</strong></td>
<td>エッジのキャッシュを削除してオリジンから最新版を取得させる操作</td>
</tr>
</tbody>
</table>
<h3><span id="toc4">なぜ S3 を非公開にして CloudFront 経由で配信するのか</span></h3>
<p>S3 バケットを直接公開することもできますが、現在の推奨構成は <strong>S3 を非公開 + CloudFront OAC 経由</strong> です。</p>
<table>
<thead>
<tr>
<th>比較項目</th>
<th>S3 直接公開</th>
<th>CloudFront + OAC（推奨）</th>
</tr>
</thead>
<tbody>
<tr>
<td>HTTPS</td>
<td>独自ドメイン接続は複雑</td>
<td>標準でHTTPS</td>
</tr>
<tr>
<td>CDNキャッシュ</td>
<td>なし</td>
<td>世界中のエッジでキャッシュ</td>
</tr>
<tr>
<td>S3への直接アクセス</td>
<td>防げない</td>
<td>防げる（CloudFront経由のみ）</td>
</tr>
<tr>
<td>WAF連携</td>
<td>不可</td>
<td>可能</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc5">前提条件</span></h2>
<table>
<thead>
<tr>
<th>ツール</th>
<th>確認コマンド</th>
<th>最低バージョン目安</th>
</tr>
</thead>
<tbody>
<tr>
<td>AWS CLI v2</td>
<td><code>aws --version</code></td>
<td>2.x</td>
</tr>
<tr>
<td>AWS SAM CLI</td>
<td><code>sam --version</code></td>
<td>1.x</td>
</tr>
<tr>
<td>Git</td>
<td><code>git --version</code></td>
<td>-</td>
</tr>
<tr>
<td>VSCode</td>
<td>-</td>
<td>最新版推奨</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>Python は不要：</strong> 今回は Lambda 関数がないため Python のインストールは必要ありません。</p>
</blockquote>
<pre><code class="language-cmd">aws sts get-caller-identity</code></pre>
<hr>
<h2><span id="toc6">使用するAWSサービス</span></h2>
<table>
<thead>
<tr>
<th>サービス</th>
<th>役割</th>
<th>料金</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>S3</strong></td>
<td>HTMLファイルの保存</td>
<td>5GBまで無料（12ヶ月）</td>
</tr>
<tr>
<td><strong>CloudFront</strong></td>
<td>CDN配信・HTTPS・エッジキャッシュ</td>
<td>1TB/月・1,000万リクエスト/月まで無料</td>
</tr>
<tr>
<td><strong>OAC</strong></td>
<td>CloudFront → S3 の安全なアクセス制御</td>
<td>無料（CloudFrontの機能）</td>
</tr>
<tr>
<td><strong>S3</strong>（デプロイ用）</td>
<td>SAMのデプロイパッケージ置き場</td>
<td>少量なのでほぼ無料</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc7">Step 1: プロジェクトフォルダを開く</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\s3-cloudfront-hosting</code></pre>
<h3><span id="toc8">フォルダ構造</span></h3>
<pre><code class="language-plaintext">s3-cloudfront-hosting/
├── template.yaml       # SAMテンプレート（S3 + OAC + CloudFront + バケットポリシー）
├── samconfig.toml      # デプロイ設定（gitignore対象・毎回手動作成が必要）
├── website/
│   ├── index.html      # トップページ
│   └── error.html      # カスタム 404 ページ
└── docs/
    ├── 1_console.md    # AWSコンソール版手順
    └── 2_sam.md        # SAM版手順</code></pre>
<hr>
<h2><span id="toc9">Step 2: SAMテンプレートの確認（template.yaml）</span></h2>
<p><code>template.yaml</code> が4つのAWSリソース全体の設計図です。それぞれのリソースに依存関係があるため、SAMが自動的に正しい順序で作成します。</p>
<h3><span id="toc10">リソースの依存関係</span></h3>
<pre><code class="language-plaintext">WebsiteBucket（S3バケット）
    ↓ バケット名を参照
CloudFrontOAC（Origin Access Control）
    ↓ バケット名 + OAC ID を参照
WebsiteDistribution（CloudFrontディストリビューション）
    ↓ バケット名 + ディストリビューションARN を参照
WebsiteBucketPolicy（S3バケットポリシー）</code></pre>
<h3><span id="toc11">template.yaml の主要部分</span></h3>
<pre><code class="language-yaml">AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: S3 + CloudFront static site hosting with OAC

Resources:
  WebsiteBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${AWS::StackName}-website-${AWS::AccountId}"  # アカウントIDでユニーク化
    DeletionPolicy: Delete   # sam delete 時にバケットも削除（空の場合のみ）

  # OAC: CloudFrontがS3に署名付きリクエストを送るための設定
  CloudFrontOAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: !Sub "${AWS::StackName}-oac"
        OriginAccessControlOriginType: s3
        SigningBehavior: always      # 常に署名付きで送信
        SigningProtocol: sigv4       # AWS Signature Version 4

  # CloudFront ディストリビューション
  WebsiteDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        DefaultRootObject: index.html                    # / → index.html
        Enabled: true
        Origins:
          - DomainName: !GetAtt WebsiteBucket.RegionalDomainName  # リージョナルドメインを使用
            Id: S3Origin
            S3OriginConfig:
              OriginAccessIdentity: ""                   # OAC使用時は空文字列が必須
            OriginAccessControlId: !GetAtt CloudFrontOAC.Id
        DefaultCacheBehavior:
          ViewerProtocolPolicy: redirect-to-https        # HTTP → HTTPS リダイレクト
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6  # Managed-CachingOptimized
          AllowedMethods: [GET, HEAD]
          TargetOriginId: S3Origin
        CustomErrorResponses:
          - ErrorCode: 403             # S3が非公開のため404ではなく403が返る場合がある
            ResponseCode: 404
            ResponsePagePath: /error.html
          - ErrorCode: 404
            ResponseCode: 404
            ResponsePagePath: /error.html

  # バケットポリシー: このCloudFrontディストリビューションからのみ GetObject を許可
  WebsiteBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref WebsiteBucket
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudfront.amazonaws.com
            Action: s3:GetObject
            Resource: !Sub "arn:aws:s3:::${WebsiteBucket}/*"
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${WebsiteDistribution}"

Outputs:
  BucketName:
    Value: !Ref WebsiteBucket
  CloudFrontDomain:
    Value: !Sub "https://${WebsiteDistribution.DomainName}"
  DistributionId:
    Value: !Ref WebsiteDistribution</code></pre>
<h3><span id="toc12">template.yaml のポイント解説</span></h3>
<p><strong><code>!GetAtt WebsiteBucket.RegionalDomainName</code>（リージョナルドメインを使用）</strong><br />OACを使う場合、S3オリジンのドメイン名はリージョナルドメイン（<code>バケット名.s3.ap-northeast-1.amazonaws.com</code>）を使用する必要があります。S3ウェブサイトエンドポイント（<code>バケット名.s3-website-ap-northeast-1.amazonaws.com</code>）は使えません。</p>
<p><strong><code>S3OriginConfig.OriginAccessIdentity: &quot;&quot;</code>（空文字列）</strong><br />OACを使う場合は、旧方式（OAI）のフィールドに空文字列を設定することがCloudFormationの仕様上必要です。</p>
<p><strong><code>ErrorCode: 403 → 404</code>のマッピング</strong><br />S3バケットが非公開のため、存在しないパスへのアクセスは <code>403 Forbidden</code> が返ります。それを <code>404 Not Found</code> にマッピングして <code>error.html</code> を表示します。</p>
<p><strong><code>CachePolicyId: 658327ea-...</code>（Managed-CachingOptimized）</strong><br />AWSが提供するマネージドキャッシュポリシー「CachingOptimized」のIDです。S3の静的コンテンツ配信に最適化されたTTL設定が含まれています。</p>
<hr>
<h2><span id="toc13">Step 3: website/ の確認</span></h2>
<p><code>website/index.html</code> と <code>website/error.html</code> はプロジェクトに既に含まれています。内容を確認しておきます。</p>
<h3><span id="toc14">website/index.html（トップページ）</span></h3>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="ja"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;S3 + CloudFront ハンズオン&lt;/title&gt;
  &lt;style&gt;
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      max-width: 640px;
      margin: 60px auto;
      padding: 20px;
      color: #333;
    }
    h1 { color: #232f3e; }
    .badge {
      background: #ff9900;
      color: white;
      padding: 3px 10px;
      border-radius: 4px;
      font-size: 0.85em;
      font-weight: bold;
    }
    .info {
      background: #f4f6f8;
      border-left: 4px solid #ff9900;
      padding: 12px 16px;
      margin: 20px 0;
      border-radius: 0 4px 4px 0;
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;デプロイ成功！&lt;/h1&gt;
  &lt;p&gt;
    &lt;span class="badge"&gt;CloudFront CDN&lt;/span&gt; 経由でこのページが配信されています。
  &lt;/p&gt;
  &lt;div class="info"&gt;
    &lt;strong&gt;このページの配信構成:&lt;/strong&gt;&lt;br&gt;
    S3 バケット（オリジン） → CloudFront（CDN）→ あなたのブラウザ
  &lt;/div&gt;
  &lt;p&gt;S3 + CloudFront による静的サイトホスティングのハンズオンです。&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h3><span id="toc15">website/error.html（カスタム404ページ）</span></h3>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="ja"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;title&gt;404 - ページが見つかりません&lt;/title&gt;
  &lt;style&gt;
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      max-width: 640px;
      margin: 60px auto;
      padding: 20px;
      color: #333;
      text-align: center;
    }
    h1 { color: #cc0000; }
    a { color: #ff9900; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;404 - ページが見つかりません&lt;/h1&gt;
  &lt;p&gt;お探しのページは存在しないか、移動・削除された可能性があります。&lt;/p&gt;
  &lt;p&gt;&lt;a href="/"&gt;トップページへ戻る&lt;/a&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<blockquote>
<p><strong>重要: <code>sam deploy</code> ではHTMLファイルはS3にアップロードされません。</strong><br />SAMデプロイはインフラ（S3バケット・CloudFront等）を作成するだけです。<br />HTMLファイルは Step 7 で別途 <code>aws s3 sync</code> コマンドでアップロードします。</p>
</blockquote>
<hr>
<h2><span id="toc16">Step 4: samconfig.toml を作成する</span></h2>
<p><code>samconfig.toml</code> は <code>.gitignore</code> で管理外のため、<strong>毎回手動で作成</strong>する必要があります。</p>
<p><code>s3-cloudfront-hosting/samconfig.toml</code> を新規作成して以下を貼り付けてください。</p>
<pre><code class="language-toml">version = 0.1

[default.deploy.parameters]
stack_name = "s3-cloudfront-hosting-stack"
resolve_s3 = true
s3_prefix = "s3-cloudfront-hosting-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
disable_rollback = true
image_repositories = []

[default.global.parameters]
region = "ap-northeast-1"</code></pre>
<hr>
<h2><span id="toc17">Step 5: sam build（ビルド）</span></h2>
<pre><code class="language-cmd">cd C:\my-aws\aws-learning-projects\s3-cloudfront-hosting
sam build</code></pre>
<p>Lambda関数がないため、ビルド処理は短時間で完了します。</p>
<pre><code class="language-plaintext">Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml</code></pre>
<hr>
<h2><span id="toc18">Step 6: sam deploy（デプロイ）</span></h2>
<pre><code class="language-cmd">sam deploy</code></pre>
<pre><code class="language-plaintext">Deploy this changeset? [y/N]: y</code></pre>
<p><code>y</code> を入力して進めます。</p>
<blockquote>
<p><strong>CloudFront のデプロイには 5〜15分かかります。</strong><br />CloudFront はグローバルに世界中のエッジロケーションに設定を反映するため、他のプロジェクトよりデプロイ完了に時間がかかります。ターミナルがフリーズしているように見えても正常に処理中ですので、そのまま待ちます。</p>
</blockquote>
<h3><span id="toc19">デプロイ完了の確認</span></h3>
<pre><code class="language-plaintext">CloudFormation outputs from deployed stack
----------------------------------------------------------------------
Outputs
----------------------------------------------------------------------
Key    BucketName
Value  s3-cloudfront-hosting-stack-website-123456789012

Key    CloudFrontDomain
Value  https://xxxxxxxxxxxx.cloudfront.net

Key    DistributionId
Value  EXXXXXXXXXXXXXXXXX
----------------------------------------------------------------------</code></pre>
<p><strong><code>BucketName</code> と <code>CloudFrontDomain</code>（と <code>DistributionId</code>）を控えておきます。</strong></p>
<hr>
<h2><span id="toc20">Step 7: HTMLファイルをアップロード</span></h2>
<p>インフラのデプロイが完了したら、HTMLファイルをS3バケットにアップロードします。</p>
<pre><code class="language-cmd">set BUCKET=s3-cloudfront-hosting-stack-website-123456789012</code></pre>
<pre><code class="language-cmd">aws s3 sync C:\my-aws\aws-learning-projects\s3-cloudfront-hosting\website\ ^
  s3://%BUCKET%/ ^
  --region ap-northeast-1</code></pre>
<pre><code class="language-plaintext">upload: website\error.html to s3://s3-cloudfront-hosting-stack-website-123456789012/error.html
upload: website\index.html to s3://s3-cloudfront-hosting-stack-website-123456789012/index.html</code></pre>
<hr>
<h2><span id="toc21">Step 8: 動作テスト</span></h2>
<h3><span id="toc22">8-1. トップページの確認</span></h3>
<p>ブラウザでCloudFrontドメインにアクセスします。</p>
<pre><code class="language-plaintext">https://xxxxxxxxxxxx.cloudfront.net</code></pre>
<p><code>index.html</code> の内容が表示されれば成功です。</p>
<p><!-- ![CloudFront経由でindex.htmlが表示](images/cloudfront-index.jpg) --></p>
<h3><span id="toc23">8-2. HTTP → HTTPS リダイレクトの確認</span></h3>
<pre><code class="language-plaintext">http://xxxxxxxxxxxx.cloudfront.net</code></pre>
<p>HTTPでアクセスすると自動的にHTTPSにリダイレクトされることを確認します。ブラウザのアドレスバーが <code>https://</code> に変わるのを確認してください。</p>
<h3><span id="toc24">8-3. カスタム 404 ページの確認</span></h3>
<pre><code class="language-plaintext">https://xxxxxxxxxxxx.cloudfront.net/notfound</code></pre>
<p>存在しないパスにアクセスすると <code>error.html</code> の内容（「404 - ページが見つかりません」）が表示されることを確認します。</p>
<h3><span id="toc25">8-4. CDN キャッシュのテスト（任意）</span></h3>
<p>CloudFront CDNの重要な概念「キャッシュ」と「無効化（Invalidation）」を体験します。</p>
<p><strong>① index.html を更新して再アップロード</strong></p>
<pre><code class="language-cmd">aws s3 cp C:\my-aws\aws-learning-projects\s3-cloudfront-hosting\website\index.html ^
  s3://%BUCKET%/index.html ^
  --region ap-northeast-1</code></pre>
<p>ブラウザで CloudFront URL にアクセスしても、<strong>古いキャッシュが表示される場合があります</strong>（TTLが切れるまで）。</p>
<p><strong>② キャッシュを無効化（Invalidation）する</strong></p>
<pre><code class="language-cmd">set DIST_ID=EXXXXXXXXXXXXXXXXX</code></pre>
<pre><code class="language-cmd">aws cloudfront create-invalidation ^
  --distribution-id %DIST_ID% ^
  --paths "/*" ^
  --region ap-northeast-1</code></pre>
<p>レスポンス例：</p>
<pre><code class="language-json">{
    "Invalidation": {
        "Id": "IXXXXXXXXXXXXXXXXX",
        "Status": "InProgress"
    }
}</code></pre>
<p><strong>③ 無効化の完了確認（1〜5分後）</strong></p>
<pre><code class="language-cmd">aws cloudfront get-invalidation ^
  --distribution-id %DIST_ID% ^
  --id IXXXXXXXXXXXXXXXXX ^
  --region ap-northeast-1</code></pre>
<p><code>&quot;Status&quot;: &quot;Completed&quot;</code> になったらブラウザを強制リロード（<code>Ctrl+Shift+R</code>）して最新版が表示されることを確認します。</p>
<blockquote>
<p><strong>キャッシュ無効化の料金：</strong><br />毎月最初の 1,000 パスは無料。それ以降は $0.005/パス。<br /><code>/*</code> は「1パス」としてカウントされるため、ハンズオン数回程度なら無料枠内です。</p>
</blockquote>
<hr>
<h2><span id="toc26">Step 9: AWSコンソールで確認（任意）</span></h2>
<ul>
<li><strong>S3</strong>: S3 → <code>s3-cloudfront-hosting-stack-website-XXXX</code> → <code>index.html</code> / <code>error.html</code> が存在することを確認</li>
<li><strong>S3 バケットポリシー</strong>: S3 → バケット → 「アクセス許可」タブ → バケットポリシーにOACの条件が含まれていることを確認</li>
<li><strong>CloudFront</strong>: CloudFront → ディストリビューション → ステータスが「有効」、「オリジン」タブでOACが設定されていることを確認</li>
</ul>
<p><!-- ![CloudFrontディストリビューション確認画面](images/cloudfront-console.jpg) --></p>
<hr>
<h2><span id="toc27">Step 10: リソースの削除</span></h2>
<p><strong>課金を止めるために、ハンズオン完了後は必ずリソースを削除してください。</strong></p>
<blockquote>
<p><strong>削除時の注意点 2つ：</strong></p>
<ol>
<li>S3バケットにオブジェクトが残っていると <code>sam delete</code> が失敗 → 先に空にする</li>
<li>CloudFrontの削除には <strong>15〜20分かかります</strong>（グローバルに反映を待つため）</li>
</ol>
</blockquote>
<h3><span id="toc28">① S3バケットを先に空にする</span></h3>
<pre><code class="language-cmd">aws s3 rm s3://%BUCKET% --recursive --region ap-northeast-1</code></pre>
<h3><span id="toc29">② スタックを削除する</span></h3>
<pre><code class="language-cmd">sam delete --stack-name s3-cloudfront-hosting-stack --region ap-northeast-1</code></pre>
<pre><code class="language-plaintext">Are you sure you want to delete the stack s3-cloudfront-hosting-stack? [y/N]: y
Are you sure you want to delete the folder s3-cloudfront-hosting-stack in S3? [y/N]: y</code></pre>
<p>CloudFrontの削除処理が走るため、完了まで15〜20分かかる場合があります。ターミナルはそのまま待ちます。</p>
<h3><span id="toc30">削除完了の確認</span></h3>
<pre><code class="language-cmd">aws cloudformation describe-stacks --stack-name s3-cloudfront-hosting-stack --region ap-northeast-1</code></pre>
<pre><code class="language-plaintext">An error occurred (ValidationError): Stack with id s3-cloudfront-hosting-stack does not exist</code></pre>
<blockquote>
<p><strong>コンソール版との大きな違い（SAMのメリット）：</strong><br />コンソールの新UIで作成したディストリビューションは「Pricing plan」に加入するため、プランキャンセル後に月末まで削除できない制約があります。<br /><strong>SAMで作成したディストリビューションにはPricing planの概念がなく、<code>sam delete</code> 1コマンドで即座に削除手続きが完了します。</strong></p>
</blockquote>
<hr>
<h2><span id="toc31">トラブルシューティング</span></h2>
<table>
<thead>
<tr>
<th>症状</th>
<th>原因</th>
<th>対処</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sam deploy</code> が <code>BucketAlreadyExists</code> エラー</td>
<td>同名バケットが存在する</td>
<td><code>samconfig.toml</code> の <code>stack_name</code> を変更して別名にする</td>
</tr>
<tr>
<td>CloudFront URL で 403 が返る</td>
<td>HTMLのアップロードを忘れている</td>
<td>Step 7 の <code>aws s3 sync</code> を実行する</td>
</tr>
<tr>
<td>CloudFront URL で 403 が返る</td>
<td>CloudFrontがまだデプロイ中</td>
<td>ステータスが「有効」になるまで5〜15分待つ</td>
</tr>
<tr>
<td>更新したのに古い内容が表示される</td>
<td>CloudFrontキャッシュが残っている</td>
<td>Step 8-4 の <code>create-invalidation</code> を実行する</td>
</tr>
<tr>
<td><code>sam delete</code> が失敗する</td>
<td>S3バケットにオブジェクトが残っている</td>
<td><code>aws s3 rm s3://バケット名 --recursive</code> で先に空にする</td>
</tr>
<tr>
<td><code>sam delete</code> が20分経っても終わらない</td>
<td>CloudFrontの削除処理中（正常）</td>
<td>そのまま待つ。CloudFrontの削除は時間がかかる</td>
</tr>
</tbody>
</table>
<hr>
<h2><span id="toc32">まとめ</span></h2>
<p>今回のハンズオンで実現したこと：</p>
<table>
<thead>
<tr>
<th>確認項目</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>HTTPS配信</strong></td>
<td>CloudFront経由でHTTPS配信 / HTTPは自動リダイレクト</td>
</tr>
<tr>
<td><strong>S3非公開</strong></td>
<td>S3の直接URLは403 / CloudFront OAC経由のみアクセス可能</td>
</tr>
<tr>
<td><strong>カスタム404</strong></td>
<td>存在しないパスで <code>error.html</code> が表示される</td>
</tr>
<tr>
<td><strong>CDNキャッシュ</strong></td>
<td>ファイル更新後にキャッシュ無効化で最新版を反映</td>
</tr>
<tr>
<td><strong>削除</strong></td>
<td>S3を先に空にして <code>sam delete</code> で一括クリーンアップ</td>
</tr>
</tbody>
</table>
<h3><span id="toc33">SAMのメリットを実感できたポイント</span></h3>
<ul>
<li>4リソース（S3・OAC・CloudFront・バケットポリシー）の依存関係をSAMが自動解決</li>
<li><code>!GetAtt WebsiteDistribution.DomainName</code> でディストリビューションのドメインを自動参照（コンソール版では手動でコピー）</li>
<li><code>sam delete</code> でPricing planなしに即座に削除手続き完了（コンソール版は月末まで待機が必要）</li>
</ul>
<hr>
<h2><span id="toc34">コンソール操作と比較してみる</span></h2>
<p>SAMが裏で何をやっているか、同じ構成をAWSコンソールのみで構築する手順をまとめました。2026年のCloudFront新UI（6ステップウィザード形式）での手順と、削除時に新たに必要になった「Pricing planキャンセル」など、コンソールならではのつまずきポイントを解説しています。</p>
<ul>
<li>AWSコンソールだけでS3 + CloudFront静的サイトホスティングを構築する手順【SAM版との比較付き / 新UI対応】</li>
</ul>

<a rel="noopener" href="https://caymezon.com/aws-handson-console-s3-cloudfront-hosting/" title="AWSコンソールだけでS3 + CloudFront静的サイトホスティングを構築する手順【SAM版との比較付き / 新UI対応】" class="blogcard-wrap internal-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img decoding="async" width="160" height="90" src="https://caymezon.com/wp-content/uploads/2026/02/aws-handson-console-s3-cloudfront-hosting-featured-a0eeeb-160x90.jpg" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://caymezon.com/wp-content/uploads/2026/02/aws-handson-console-s3-cloudfront-hosting-featured-a0eeeb-160x90.jpg 160w, https://caymezon.com/wp-content/uploads/2026/02/aws-handson-console-s3-cloudfront-hosting-featured-a0eeeb-120x68.jpg 120w, https://caymezon.com/wp-content/uploads/2026/02/aws-handson-console-s3-cloudfront-hosting-featured-a0eeeb-320x180.jpg 320w, https://caymezon.com/wp-content/uploads/2026/02/aws-handson-console-s3-cloudfront-hosting-featured-a0eeeb-376x212.jpg 376w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">AWSコンソールだけでS3 + CloudFront静的サイトホスティングを構築する手順【SAM版との比較付き / 新UI対応】</div><div class="blogcard-snippet internal-blogcard-snippet">はじめにこの記事は、SAM版ハンズオン記事の比較版です。SAM版ではコマンド数本でS3・OAC・CloudFront・バケットポリシーが一括デプロイできましたが、実際にコンソールで操作してみると思わぬハマりポイントがいくつかあります。特に ...</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://caymezon.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">caymezon.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.02.23</div></div></div></div></a>
<hr>
<h2><span id="toc35">関連記事</span></h2>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BAWS%2B%25E9%2596%258B%25E7%2599%25BA%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D1DE63UBHFOR4K%26sprefix%3D%25E6%259C%25AC%2Baws%2B%25E9%2596%258B%25E7%2599%25BA%252Caps%252C167%26ref%3Dnb_sb_noss" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 AWS 開発]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"AWS運用入門 改訂第2版 押さえておきたいAWSの基本と運用ノウハウ [AWS深掘りガイド]","b":"SBクリエイティブ","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51AAOubymTL._SL500_.jpg","\/51VMG6YKHdL._SL500_.jpg","\/41EdPB8azAL._SL500_.jpg","\/41v2JFE-9jL._SL500_.jpg","\/41FEEqR-yDL._SL500_.jpg","\/41JfZAdnTPL._SL500_.jpg","\/41vGK0czQrL._SL500_.jpg","\/41-SnYtz2aL._SL500_.jpg","\/41sPrV5fi3L._SL500_.jpg","\/41p7JtvYJ1L._SL500_.jpg","\/4169GVNTs8L._SL500_.jpg","\/41BPI5HP3zL._SL500_.jpg","\/41QOyk60CYL._SL500_.jpg","\/41APjk6FphL._SL500_.jpg","\/41ezKUu7VRL._SL500_.jpg","\/41A1n3K+r5L._SL500_.jpg","\/41aY2T8lEOL._SL500_.jpg","\/419Ca1V6HZL._SL500_.jpg","\/41zQkYyLPzL._SL500_.jpg","\/41YpHcyxiTL._SL500_.jpg","\/41-tKN5mt6L._SL500_.jpg","\/419Mv6m55IL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4815631085","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4815631085","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=AWS%E9%81%8B%E7%94%A8%E5%85%A5%E9%96%80%20%E6%94%B9%E8%A8%82%E7%AC%AC2%E7%89%88%20%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%84AWS%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%A8%E9%81%8B%E7%94%A8%E3%83%8E%E3%82%A6%E3%83%8F%E3%82%A6%20%5BAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%5D","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"E8MM1","s":"s"});</script></p>
<div id="msmaflink-E8MM1">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p><p>The post <a href="https://caymezon.com/aws-handson-s3-cloudfront-hosting/">AWSハンズオン - S3 + CloudFrontで静的サイトをCDN配信しよう【SAM版 / Windows対応】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/aws-handson-s3-cloudfront-hosting/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
