# 【AWS】Day 8：運算服務 - AWS Lambda（Serverless 運算服務）

從前幾篇我們一路看了 EC2（自己管主機）、Elastic Beanstalk（半自動化部署）。

但如果你希望：

- 完全不用開伺服器
- 完全不用自己處理機器、網路、Scaling
- 只要專心「寫功能」就好

那 AWS Lambda 正是為了這樣的世界而生的！

今天正式進入 AWS Serverless 世界 —— **Lambda**！

---

## 什麼是 AWS Lambda？

**AWS Lambda** 是一個「事件驅動」的計算服務。

你只需要撰寫小段的**程式碼 Function**，告訴 AWS：

- 什麼時候要執行（例如 API 呼叫、排程時間、檔案上傳）
- 要執行什麼邏輯（你的程式碼）
- 設定好記憶體、超時時間

剩下的：

- 執行環境（OS、Patch、Security）
- 擴展（Scaling up &amp; down）
- 負載均衡
- 日誌收集
- 資源回收

全部 AWS 幫你自動處理。

你只要負責寫功能，其他都不用煩惱！

---

## Lambda 的基本特性

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%AA%AA%E6%98%8E-%E9%81%8B%E7%AE%97%E5%96%AE%E4%BD%8D-function%EF%BC%88"><thead><tr><th>項目</th><th>說明</th></tr></thead><tbody><tr><td>運算單位</td><td>Function（函數）</td></tr><tr><td>部署方式</td><td>上傳程式碼（ZIP）或直接在 Console 編輯</td></tr><tr><td>支援語言</td><td>Node.js, Python, Java, Go, .NET, Ruby…</td></tr><tr><td>記憶體設定</td><td>128MB ~ 10GB（每64MB為單位）</td></tr><tr><td>執行時間限制</td><td>最長 15 分鐘</td></tr><tr><td>價格模式</td><td>依執行次數 + 記憶體用量 + 執行時間計價</td></tr><tr><td>自動擴展</td><td>是（幾乎無上限，橫向自動擴展）</td></tr></tbody></table>

---

## Lambda 的典型使用情境

- API Gateway ➔ Lambda ➔ 回應 Web API
- S3 上傳檔案 ➔ Lambda 處理轉檔（圖片縮圖、影片轉檔）
- DynamoDB 資料異動 ➔ Lambda 處理後續邏輯
- 排程任務（類似 crontab） ➔ 用 EventBridge 觸發 Lambda
- 輕量型後端服務（例如 Form submit、小型 Game Server）

---

## Lambda 的基本運作流程

以「API Server」為例：

1. 使用 **API Gateway** 建立 API Endpoint
2. 當使用者呼叫 API 時，API Gateway 觸發 Lambda
3. Lambda 執行你的程式邏輯（例如查資料庫、處理資料）
4. Lambda 執行完畢，把結果回傳給 API Gateway，再傳給使用者

整個過程中：

- 沒有 EC2
- 沒有 Load Balancer
- 沒有固定伺服器
- 自動擴展，不需要自己設定台數

---

## Lambda 的計費方式

Lambda 的收費方式非常彈性，也很適合小量或突發性使用的場景。

計費公式：

> **總費用 = 呼叫次數 +（記憶體用量 × 執行時間）**

詳細來說：

- **每個月前 1M 次請求免費**
- **每個月 400,000 GB-seconds 免費**
- 超出後，依照實際用量超便宜的單位收費

簡單說就是：

- 程式小、執行快，超便宜
- 不執行，不收費（真正的 pay-as-you-go）

---

## Lambda 的限制與注意事項

雖然 Lambda 很香，但也有一些天然限制要注意：

<table id="bkmrk-%E9%99%90%E5%88%B6%E9%A0%85%E7%9B%AE-%E8%AA%AA%E6%98%8E-%E6%9C%80%E5%A4%A7%E5%9F%B7%E8%A1%8C%E6%99%82%E9%96%93-15-%E5%88%86%E9%90%98"><thead><tr><th>限制項目</th><th>說明</th></tr></thead><tbody><tr><td>最大執行時間</td><td>15 分鐘</td></tr><tr><td>單次部署包大小上限</td><td>250MB（含 layer）</td></tr><tr><td>同步觸發最大負載</td><td>預設 1000 concurrency（可申請提升）</td></tr><tr><td>須設計無狀態</td><td>每次執行是全新環境，不保留記憶體資料</td></tr></tbody></table>

---

## 小結

今天介紹了 AWS Lambda 的核心概念：

- 完全免維護伺服器
- 按次計費、真正用多少付多少
- 適合 API、小型運算任務、背景作業
- 超級擴展性，適合不確定流量的應用

Lambda 完全顛覆了傳統開發部署方式，讓開發者能更專注在**功能本身**，而不是維護伺服器！

---

好！這裡直接幫你產出  
**AWS Lambda 實作教學（Hello World + API Gateway 快速串接）**  
讓讀者可以跟著馬上做出第一個 Lambda Function，並且用瀏覽器呼叫成功！

---

# AWS Lambda 實作教學（Hello World + API Gateway 快速串接）

前一篇我們認識了什麼是 AWS Lambda。  
這一篇直接來實戰，帶你：

- 建立一個 Hello World Lambda
- 用 API Gateway 快速串接
- 用瀏覽器直接呼叫！

超簡單，只需要 10 分鐘 🚀

---

## 1. 建立第一個 Lambda Function

登入 AWS Console  
打開 [AWS Lambda Console](https://console.aws.amazon.com/lambda/)

1. 點選「Create function」
2. 選擇「Author from scratch」
3. 填寫基本資訊：

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%A8%AD%E5%AE%9A-function-name-"><thead><tr><th>項目</th><th>設定</th></tr></thead><tbody><tr><td>Function name</td><td>`hello-world-lambda`</td></tr><tr><td>Runtime</td><td>Node.js 18.x（或 Python 3.11 也可）</td></tr><tr><td>Architecture</td><td>x86\_64（預設即可）</td></tr><tr><td>Permissions</td><td>建議選「Create a new role with basic Lambda permissions」</td></tr></tbody></table>

4. 點選「Create Function」

幾秒鐘後，一個新的 Lambda 就建立好了！

---

## 2. 編輯 Lambda 程式碼

在 Lambda Console 的編輯器中，把預設程式碼改成這樣：

### Node.js 版

```javascript
exports.handler = async (event) => {
    return {
        statusCode: 200,
        body: JSON.stringify('Hello from AWS Lambda!'),
    };
};

```

### Python 版

```python
def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': 'Hello from AWS Lambda!'
    }

```

編輯完後，記得按下「Deploy」！

這樣 Lambda 程式就設定完成了 🎯

---

## 3. 建立 API Gateway 串接 Lambda

接下來，我們讓這個 Lambda 能夠用 HTTP API 被呼叫。

1. 在 Lambda Function 頁面上方，點選「Add trigger」
2. 選擇「API Gateway」
3. 設定：

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%A8%AD%E5%AE%9A-api-type-http-"><thead><tr><th>項目</th><th>設定</th></tr></thead><tbody><tr><td>API type</td><td>HTTP API（比較簡單快速）</td></tr><tr><td>Security</td><td>Open（不設驗證，之後可以補強）</td></tr><tr><td>API Name</td><td>自動生成或自己命名也可以</td></tr></tbody></table>

4. 點選「Add」

AWS 會自動幫你建立一個 HTTP API，並串好 Lambda！

完成後，Lambda 頁面會顯示一個 API Endpoint，例如：

```
https://xxxxx12345.execute-api.ap-northeast-1.amazonaws.com/

```

---

## 4. 測試 API 呼叫

打開你的瀏覽器，直接輸入剛剛那個 URL！

你應該會看到畫面顯示：

```
"Hello from AWS Lambda!"

```

恭喜 🎉  
你的第一個 Lambda + API Gateway 已經成功部署！

---

## 5. 小結

這次的流程總結：

<table id="bkmrk-%E6%AD%A5%E9%A9%9F-%E8%AA%AA%E6%98%8E-%E5%BB%BA%E7%AB%8B-lambda-%E6%92%B0%E5%AF%AB-h"><thead><tr><th>步驟</th><th>說明</th></tr></thead><tbody><tr><td>建立 Lambda</td><td>撰寫 Hello World 程式碼</td></tr><tr><td>建立 API Gateway Trigger</td><td>快速建立 HTTP API 串接 Lambda</td></tr><tr><td>測試呼叫</td><td>瀏覽器直接打 API URL 測試結果</td></tr></tbody></table>

透過 Lambda + API Gateway，可以超快速上線一個基本 API，  
而且完全不用管 EC2、Scaling、Load Balancer 等基礎設施！

---

太好了！這裡接著給你完整的  
**AWS Lambda 進階教學：環境變數、IAM 權限設定、連接 RDS 資料庫**，  
讓你的 Lambda 應用從 Hello World 升級到真正可以跑正式服務！

---

# AWS Lambda 進階教學

（環境變數設定＋IAM 權限管理＋連接 RDS 資料庫）

---

## 一、設定 Lambda 環境變數（Environment Variables）

### ✅ 為什麼需要環境變數？

- 存放敏感資訊（資料庫帳號密碼、API Key）
- 存放不同環境（dev / prod）設定
- 程式更乾淨，不要把設定寫死在程式碼裡

### ✅ 如何設定環境變數

1. 打開 Lambda Function 頁面
2. 選擇「Configuration」→「Environment variables」
3. 點「Edit」→「Add environment variable」
4. 加入變數，例如：

<table id="bkmrk-key-value-db_host-yo"><thead><tr><th>Key</th><th>Value</th></tr></thead><tbody><tr><td>`DB_HOST`</td><td>`your-db.xxxx.rds.amazonaws.com`</td></tr><tr><td>`DB_USER`</td><td>`admin`</td></tr><tr><td>`DB_PASSWORD`</td><td>`your-password`</td></tr><tr><td>`DB_NAME`</td><td>`your-database-name`</td></tr></tbody></table>

5. 儲存即可，Lambda 會自動讀取這些變數

---

### ✅ 程式裡讀取環境變數（Python範例）

```python
import os

db_host = os.environ['DB_HOST']
db_user = os.environ['DB_USER']
db_password = os.environ['DB_PASSWORD']
db_name = os.environ['DB_NAME']

```

讀取環境變數超簡單，推薦大家一定要用這種方式管理設定！

---

## 二、設定 Lambda 的 IAM Role（權限）

### ✅ 為什麼需要設定 IAM Role？

Lambda 本身如果要呼叫其他 AWS 資源（像 S3、DynamoDB、RDS），需要有「授權」。

這個授權是透過「IAM Role」給的。

> 📌 Lambda 本身就會綁定一個 IAM Role，進行細部設定即可。

---

### ✅ 最小權限原則（Best Practice）

- 只開放 Lambda 需要用到的權限
- 不要直接給 Lambda admin 權限（避免安全漏洞）

---

### ✅ 範例：讓 Lambda 存取 RDS（或 VPC）

如果 Lambda 需要連到 RDS，需要設定：

- 讓 Lambda 進入正確的 VPC、Subnet、Security Group
- IAM Role 不需要特別開 RDS 權限（除非要操作 RDS API，例如開啟 RDS Snapshot）

所以通常 IAM Role 保持「基本執行」權限就好，例如：

- `AWSLambdaBasicExecutionRole`（CloudWatch log 權限）
- `AWSLambdaVPCAccessExecutionRole`（存取 VPC 權限）

可以在 Lambda → Configuration → Permissions → Execution Role 編輯。

---

## 三、Lambda 連接 RDS 資料庫（Python 範例）

假設我們已經設定好 RDS MySQL 或 PostgreSQL 資料庫，並且把連線資訊設在環境變數。

### ✅ 安裝必備套件

AWS Lambda 本身環境很乾淨，需要把 Python 連線資料庫的套件（例如 `pymysql`、`psycopg2`）打包進去。

**開發環境步驟：**

```bash
mkdir lambda_rds
cd lambda_rds
python3 -m venv venv
source venv/bin/activate
pip install pymysql
deactivate

```

把 `venv/lib/python3.11/site-packages` 的 pymysql 複製到專案目錄。

最後 ZIP 包含：

- `lambda_function.py`（你的程式）
- `pymysql/`（套件目錄）

---

### ✅ Lambda Python 連線 RDS (MySQL) 範例

```python
import pymysql
import os

def lambda_handler(event, context):
    # 讀取環境變數
    db_host = os.environ['DB_HOST']
    db_user = os.environ['DB_USER']
    db_password = os.environ['DB_PASSWORD']
    db_name = os.environ['DB_NAME']
    
    # 建立連線
    connection = pymysql.connect(
        host=db_host,
        user=db_user,
        password=db_password,
        database=db_name,
        connect_timeout=5
    )
    
    try:
        with connection.cursor() as cursor:
            sql = "SELECT NOW();"
            cursor.execute(sql)
            result = cursor.fetchone()
            return {
                'statusCode': 200,
                'body': f'Database Time: {result[0]}'
            }
    finally:
        connection.close()

```

這樣就能讓 Lambda 成功連接雲端資料庫，完成資料操作！

---

## 四、常見注意事項

<table id="bkmrk-%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85-%E8%AA%AA%E6%98%8E-lambda-%2B-rds"><thead><tr><th>注意事項</th><th>說明</th></tr></thead><tbody><tr><td>Lambda + RDS Latency</td><td>Lambda 通常要設在與 RDS 同一個 VPC / AZ</td></tr><tr><td>連線池設計</td><td>建議使用 RDS Proxy（可以省連線數）</td></tr><tr><td>超時設置</td><td>Lambda 最大執行時間 15 分鐘，連資料庫要小心逾時</td></tr><tr><td>打包套件</td><td>自訂套件一定要打包進 ZIP 中</td></tr></tbody></table>

---

# 小結

這篇進階教學重點整理：

- Lambda 用環境變數管理設定
- Lambda 設定 IAM Role，最小權限原則
- Lambda 可以直接連 RDS，但要注意 VPC 設定
- 打包套件進 ZIP 是必須的！

掌握這些，  
你的 Lambda 就可以從 Hello World 升級成**正式後端服務**了！

---

# AWS Serverless 全應用實戰

（API Gateway + Lambda + DynamoDB + RDS Proxy）

---

## 🔧 架構圖

<div id="bkmrk-css-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-%5Bclient%5D-%E2%86%93-"><div>css</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`<span class="hljs-selector-attr">[Client]</span>   ↓<span class="hljs-selector-attr">[API Gateway]</span>   ↓<span class="hljs-selector-attr">[AWS Lambda]</span>   ↓               ↘<span class="hljs-selector-attr">[DynamoDB]</span>      <span class="hljs-selector-attr">[RDS Proxy]</span> → <span class="hljs-selector-attr">[MySQL / PostgreSQL]</span>`</div></div>---

## 一、建立 DynamoDB Table

1. 打開 [DynamoDB Console](https://console.aws.amazon.com/dynamodb)
2. 點選「Create Table」
3. 設定：

<div id="bkmrk-%E9%A0%85%E7%9B%AE-%E5%80%BC-table-name-todo"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="752" data-start="610"><thead data-end="643" data-start="610"><tr data-end="643" data-start="610"><th data-end="624" data-start="610">項目</th><th data-end="643" data-start="624">值</th></tr></thead><tbody data-end="752" data-start="681"><tr data-end="717" data-start="681"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="697" data-start="681">Table name</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="717" data-start="697">`Todos`</td></tr><tr data-end="752" data-start="718"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="734" data-start="718">Partition key</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="752" data-start="734">`id`（String）</td></tr></tbody></table>

</div></div>4. 其他預設即可，點「Create table」

---

## 二、撰寫 Lambda Function

### ✅ Lambda 功能：新增一筆 Todo 資料

程式語言以 Python 為例，並假設請求是 `POST`，Body 包含 `title` 欄位。

<div id="bkmrk-python-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-import-j"><div>python</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`<span class="hljs-keyword">import</span> json<span class="hljs-keyword">import</span> uuid<span class="hljs-keyword">import</span> boto3dynamodb = boto3.resource(<span class="hljs-string">'dynamodb'</span>)table = dynamodb.Table(<span class="hljs-string">'Todos'</span>)<span class="hljs-keyword">def</span> <span class="hljs-title function_">lambda_handler</span>(<span class="hljs-params">event, context</span>):    body = json.loads(event[<span class="hljs-string">'body'</span>])    todo_id = <span class="hljs-built_in">str</span>(uuid.uuid4())    title = body.get(<span class="hljs-string">'title'</span>)    table.put_item(        Item={            <span class="hljs-string">'id'</span>: todo_id,            <span class="hljs-string">'title'</span>: title        }    )    <span class="hljs-keyword">return</span> {        <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,        <span class="hljs-string">'body'</span>: json.dumps({<span class="hljs-string">'id'</span>: todo_id, <span class="hljs-string">'title'</span>: title})    }`</div></div>✅ Lambda 要有以下 IAM 權限：

<div id="bkmrk-json-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-%7B-%22effect%22"><div>json</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`<span class="hljs-punctuation">{</span>  <span class="hljs-attr">"Effect"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Allow"</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">"Action"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>    <span class="hljs-string">"dynamodb:PutItem"</span>  <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">"Resource"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"arn:aws:dynamodb:*:*:table/Todos"</span><span class="hljs-punctuation">}</span>`</div></div>> 可以在 Lambda 執行角色（Execution Role）中加上這段 policy。

---

## 三、建立 API Gateway 串接 Lambda

1. 打開 [API Gateway Console](https://console.aws.amazon.com/apigateway)
2. 建立一個「HTTP API」
3. 新增路由：
    
    
    - 方法：`POST`
    - 路徑：`/todos`
    - 整合：選擇剛剛的 Lambda
4. Deploy 完後，你會得到一個 API URL

使用 curl 或 Postman 測試：

<div id="bkmrk-bash-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-curl--x-po"><div>bash</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`curl -X POST https://xxx.execute-api.ap-northeast-1.amazonaws.com/todos \  -H <span class="hljs-string">"Content-Type: application/json"</span> \  -d <span class="hljs-string">'{"title":"Write Lambda guide"}'</span>`</div></div>---

## 四、加碼：改用 RDS + RDS Proxy 作為資料層

### ✅ 為什麼使用 RDS Proxy？

- 解決 Lambda 對 RDS 連線過多問題
- 支援連線池（Connection pooling）
- 提高效率，降低 DB crash 風險

---

### 步驟 1：建立 RDS Proxy

1. 前往 [RDS Console](https://console.aws.amazon.com/rds)
2. 選單點「Proxies」→「Create proxy」
3. 設定：

<div id="bkmrk-%E9%A0%85%E7%9B%AE-%E5%80%BC-proxy-name-todo"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="2582" data-start="2230"><thead data-end="2280" data-start="2230"><tr data-end="2280" data-start="2230"><th data-end="2247" data-start="2230">項目</th><th data-end="2280" data-start="2247">值</th></tr></thead><tbody data-end="2582" data-start="2335"><tr data-end="2388" data-start="2335"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2354" data-start="2335">Proxy name</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2388" data-start="2354">`todo-proxy`</td></tr><tr data-end="2436" data-start="2389"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2408" data-start="2389">Engine</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2436" data-start="2408">MySQL / PostgreSQL（需有資料庫）</td></tr><tr data-end="2484" data-start="2437"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2456" data-start="2437">Target RDS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2484" data-start="2456">選擇現有的 RDS 實例</td></tr><tr data-end="2531" data-start="2485"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2504" data-start="2485">IAM Auth</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2531" data-start="2504">啟用（建議使用）</td></tr><tr data-end="2582" data-start="2532"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2551" data-start="2532">VPC</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="2582" data-start="2551">與 Lambda 相同 VPC</td></tr></tbody></table>

</div></div>> 建好後會給你一個 Proxy endpoint：例如 `todo-proxy.proxy-cdefg.ap-northeast-1.rds.amazonaws.com`

---

### 步驟 2：Lambda 改連 Proxy（Python）

安裝並打包 `pymysql`：

<div id="bkmrk-bash-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-pip-instal"><div>bash</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`pip install pymysql -t .zip -r lambda-rds.zip .`</div></div>Lambda 程式碼範例：

<div id="bkmrk-python-%E8%A4%87%E8%A3%BD%E7%B7%A8%E8%BC%AF-import-p"><div>python</div><div><div><div><span class="" data-state="closed"><button aria-label="複製" class="flex gap-1 items-center select-none px-4 py-1"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>複製</button></span><span class="" data-state="closed"><button class="flex items-center gap-1 px-4 py-1 select-none"><svg class="icon-xs" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg>編輯</button></span></div></div></div><div>`<span class="hljs-keyword">import</span> pymysql<span class="hljs-keyword">import</span> os<span class="hljs-keyword">import</span> json<span class="hljs-keyword">import</span> uuid<span class="hljs-keyword">def</span> <span class="hljs-title function_">lambda_handler</span>(<span class="hljs-params">event, context</span>):    conn = pymysql.connect(        host=os.environ[<span class="hljs-string">'DB_PROXY_HOST'</span>],  <span class="hljs-comment"># Proxy endpoint</span>        user=os.environ[<span class="hljs-string">'DB_USER'</span>],        password=os.environ[<span class="hljs-string">'DB_PASSWORD'</span>],        database=os.environ[<span class="hljs-string">'DB_NAME'</span>],        connect_timeout=<span class="hljs-number">5</span>    )        <span class="hljs-keyword">try</span>:        body = json.loads(event[<span class="hljs-string">'body'</span>])        todo_id = <span class="hljs-built_in">str</span>(uuid.uuid4())        title = body[<span class="hljs-string">'title'</span>]                <span class="hljs-keyword">with</span> conn.cursor() <span class="hljs-keyword">as</span> cursor:            cursor.execute(<span class="hljs-string">"INSERT INTO todos (id, title) VALUES (%s, %s)"</span>, (todo_id, title))            conn.commit()        <span class="hljs-keyword">return</span> {            <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,            <span class="hljs-string">'body'</span>: json.dumps({<span class="hljs-string">'id'</span>: todo_id, <span class="hljs-string">'title'</span>: title})        }    <span class="hljs-keyword">finally</span>:        conn.close()`</div></div>---

### ✅ 注意：

- Lambda 要設定進入 **同 VPC + Subnet + SG**
- 建議設定 `AWSLambdaVPCAccessExecutionRole`
- Lambda 的 IAM Role 不需要存取 RDS Proxy，只需要 DB 連線資訊與網路存取權

---

## 五、小結

這篇教你從 0 建立一個完整的無伺服器應用：

<div id="bkmrk-%E5%85%83%E4%BB%B6-%E7%94%A8%E9%80%94-api-gateway-%E6%8F%90%E4%BE%9B"><button class="hover:bg-token-main-surface-secondary text-token-text-secondary pointer-events-auto rounded-lg px-1 py-1 opacity-0 transition-opacity duration-200 group-focus-within:opacity-100 group-hover:opacity-100"><svg class="icon-md-heavy" fill="none" height="24" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"></svg></button><div><table class="min-w-full" data-end="4064" data-start="3781"><thead data-end="3828" data-start="3781"><tr data-end="3828" data-start="3781"><th data-end="3798" data-start="3781">元件</th><th data-end="3828" data-start="3798">用途</th></tr></thead><tbody data-end="4064" data-start="3881"><tr data-end="3927" data-start="3881"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3900" data-start="3881">API Gateway</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3927" data-start="3900">提供外部 API 接口</td></tr><tr data-end="3974" data-start="3928"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3947" data-start="3928">Lambda</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3974" data-start="3947">執行商業邏輯 Function</td></tr><tr data-end="4022" data-start="3975"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="3994" data-start="3975">DynamoDB / RDS</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="4022" data-start="3994">儲存資料</td></tr><tr data-end="4064" data-start="4023"><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="4042" data-start="4023">RDS Proxy</td><td class="max-w-[calc(var(--thread-content-max-width)*2/3)]" data-end="4064" data-start="4042">提升連線穩定性與效率</td></tr></tbody></table>

</div></div>這套架構非常適合：

- 小型 SaaS API
- 行動 App 後端
- MVP 初期應用快速開發
- 大型應用中的 Serverless 子系統（例如上傳任務、登入、Webhook 處理等）

---

太好了！這裡是你完整的加強版：  
**AWS Serverless Todo API 進階整合：Cognito 驗證、錯誤處理、Serverless Framework 快速部署**

這篇會在前面 Lambda + API Gateway + DynamoDB/RDS 架構上，補上以下三個常用進階功能：

1. ✅ **API Gateway JWT 驗證（Cognito）**
2. ✅ **Lambda 錯誤處理與 Logging 最佳實踐**
3. ✅ **使用 Serverless Framework 快速部署整套應用**

---

# Serverless Todo API 完整實戰進階篇

---

## 一、加入 Cognito 驗證保護 API

### ✅ 為什麼需要？

不管是 Web 前端還是 App，只要對外開 API，**基本保護機制不可少**。

AWS 的 Cognito 可以幫你處理：

- 使用者註冊 / 登入
- JWT Token 簽發與驗證
- 無需自建帳號系統！

---

### ✅ 建立 Cognito User Pool

1. 開啟 [Cognito Console](https://console.aws.amazon.com/cognito)
2. 建立一個 **User Pool**
    
    
    - Pool name: `todo-api-users`
    - 啟用 Email 登入即可
    - 完成後記住 Pool ID 和 Region
3. 建立 App Client
    
    
    - 禁用 client secret（Lambda 驗證用不到）
    - 儲存產生的 `App client ID`

---

### ✅ API Gateway 整合 JWT 驗證

1. 回到 API Gateway HTTP API Console
2. 選「Authorization」→「Create authorizer」
3. 建立 Cognito JWT authorizer：

<table id="bkmrk-%E9%A0%85%E7%9B%AE-%E8%A8%AD%E5%AE%9A-type-jwt-ident"><thead><tr><th>項目</th><th>設定</th></tr></thead><tbody><tr><td>Type</td><td>JWT</td></tr><tr><td>Identity source</td><td>`Authorization` header</td></tr><tr><td>Issuer URL</td><td>`https://cognito-idp.<region>.amazonaws.com/<pool-id>`</td></tr><tr><td>Audience</td><td>你剛剛的 App Client ID</td></tr></tbody></table>

4. 把 `/todos` 路徑設定為需要驗證（選上剛剛的 authorizer）

---

### ✅ 呼叫 API 時加上 JWT token

前端從 Cognito 登入取得 Token 後，呼叫 API 時帶上：

```http
Authorization: Bearer <token>

```

這樣 API Gateway 就會自動驗證使用者身份，Lambda 裡也可以透過 `event['requestContext']` 取得使用者資訊！

---

## 二、Lambda Logging 與 Error Handling 最佳實踐

### ✅ 記錄 Log 到 CloudWatch

在 Lambda 中使用標準輸出即可：

```python
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

logger.info("New todo created")

```

CloudWatch Logs 會自動收集這些 log。

---

### ✅ 最佳錯誤處理模式

```python
import json
import traceback

def lambda_handler(event, context):
    try:
        # 主邏輯
        ...
        return {
            'statusCode': 200,
            'body': json.dumps({'message': 'Success'})
        }
    except Exception as e:
        logger.error("Error: %s", traceback.format_exc())
        return {
            'statusCode': 500,
            'body': json.dumps({'error': 'Internal Server Error'})
        }

```

這樣可避免直接顯示錯誤堆疊給使用者，又方便自己 debug。

---

## 三、使用 Serverless Framework 快速部署整套應用

### ✅ 為什麼用 Serverless Framework？

- 自動建 Lambda、API Gateway、DynamoDB、IAM、環境變數
- 一鍵部署、一鍵刪除
- 適合團隊開發與 CI/CD

---

### ✅ 安裝 CLI 工具

```bash
npm install -g serverless

```

---

### ✅ 初始化專案

```bash
serverless create --template aws-python --path todo-api
cd todo-api

```

---

### ✅ 編輯 `serverless.yml`

以下是 Todo API + DynamoDB + JWT 驗證的簡化範例：

```yaml
service: todo-api

provider:
  name: aws
  runtime: python3.11
  region: ap-northeast-1
  environment:
    DB_TABLE: Todos
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:PutItem
      Resource: arn:aws:dynamodb:*:*:table/Todos

functions:
  createTodo:
    handler: handler.create
    events:
      - http:
          path: todos
          method: post
          authorizer:
            type: COGNITO_USER_POOLS
            userPoolArn: arn:aws:cognito-idp:ap-northeast-1:xxxx:userpool/xxxx

resources:
  Resources:
    TodosTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: Todos
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

```

---

### ✅ Lambda 程式 `handler.py`

```python
import json
import uuid
import boto3
import os

table = boto3.resource('dynamodb').Table(os.environ['DB_TABLE'])

def create(event, context):
    data = json.loads(event['body'])
    todo_id = str(uuid.uuid4())

    table.put_item(Item={'id': todo_id, 'title': data['title']})

    return {
        'statusCode': 200,
        'body': json.dumps({'id': todo_id, 'title': data['title']})
    }

```

---

### ✅ 部署指令

```bash
sls deploy

```

幾十秒後，你就會拿到公開的 API URL！🎉

---

### ✅ 刪除整個堆疊（清資源）

```bash
sls remove

```

超級適合測試環境清除資源時使用，防止殘留帳單！

---

## ✅ 加碼：Lambda 搭配 RDS Proxy 用 Serverless Framework 管理

你可以在 `serverless.yml` 裡加上 VPC 設定：

```yaml
provider:
  ...
  vpc:
    securityGroupIds:
      - sg-xxxxxxxx
    subnetIds:
      - subnet-aaaa
      - subnet-bbbb

```

這樣部署後的 Lambda 就能存取你已經建立好的 RDS Proxy！

---

## 🔚 小結

這次進階整合包含：

✅ Cognito 驗證 ➜ 提供安全 API  
✅ Lambda Logging + Error Handling ➜ 更穩定、更易除錯  
✅ Serverless Framework ➜ 快速一鍵部署 / 一鍵刪除  
✅ 支援 DynamoDB / RDS Proxy ➜ 彈性資料層選擇

這套完整架構，適合你用來開發：

- SaaS MVP
- 微服務架構中的 User Service
- 內部工具（支援 Cognito 驗證）
- 全無伺服器 RESTful API

---

下一篇，我們將繼續探討  
**Day 9：AWS 容器服務 - EKS（Elastic Kubernetes Service）**，  
看看當需要更複雜的大型應用，想用 Kubernetes 架構時，AWS 是怎麼支援的！

Day 9 再見 👋