
Serverless:不是沒有伺服器,是你不管伺服器
Serverless 的核心概念是「只管程式碼,不管伺服器」。你上傳一個 function,平台負責部署、擴展、維運。沒有流量時不消耗資源、不產生費用;有流量時自動擴展處理。聽起來很理想,但 Serverless 有其適用範圍和限制。這篇文章從自架 Infra 團隊的角度出發,分析什麼時候用 Serverless、什麼時候不該用、以及如何混合部署。
架構概覽
flowchart TD Event[事件觸發\nHTTP / Webhook / Cron] -->|呼叫| Runtime[Function Runtime\n載入程式碼] Runtime -->|Cold Start| Init[初始化環境\n載入依賴] Init --> Exec[執行函式\n處理請求] Exec --> Resp[回傳結果] Resp --> Scale{有後續請求?} Scale -->|有| Exec Scale -->|一段時間無| Zero[Scale to Zero\n釋放資源]
架構概覽
flowchart TD Client[Client] --> CDN[CDN / Edge\nCloudflare / Vercel] CDN -->|static| Static[Static Files\nNext.js SSG / SPA] CDN -->|api routes| Serverless[Serverless Functions\nVercel / Cloudflare Workers] CDN -->|heavy api| Gateway[API Gateway\n自架 Nginx] Serverless -->|read/write| DB[Database\nPlanetScale / Supabase] Serverless -->|webhook| Webhook[Third-party Webhook\nLine / Slack / Discord] Gateway -->|proxy| SelfHosted[Self-hosted Services\nDocker + Portainer] SelfHosted --> PG[(PostgreSQL)] SelfHosted --> Redis[(Redis)] subgraph "Serverless 適合" Static Serverless Webhook end subgraph "自架 適合" SelfHosted PG Redis end
混合架構:前端和輕量 API 用 Serverless(Vercel/Cloudflare Workers),重度後端邏輯和資料庫用自架。兩者透過 API 互相溝通。
核心概念
-
Serverless 適合什麼:(1)流量不穩定的服務:例如 Bot Webhook,平時沒有流量、偶爾突然大量湧入,Serverless 自動擴展完美應對。(2)前端部署:Next.js 的 SSR/SSG 部署到 Vercel,零維運、自動 CDN 分發。(3)輕量 API:表單提交、email 寄送、第三方 API proxy,邏輯簡單、執行時間短的操作。(4)排程任務:每天跑一次的報表生成、資料同步,不需要 24 小時跑一台伺服器。
-
Serverless 不適合什麼:(1)長時間運行的任務:Serverless function 通常有執行時間限制(Vercel 10-60 秒、Lambda 15 分鐘),超時就會被 kill。(2)需要持久連線的服務:WebSocket、database connection pool 不適合 Serverless,因為每次 invocation 可能是不同的 instance。(3)對延遲敏感的服務:Cold start(第一次呼叫時要啟動容器)會增加 100ms-3s 的延遲,頻繁呼叫但流量不穩定的 API 會有明顯的 p99 延遲。(4)成本不可控的場景:流量非常大且穩定時,Serverless 的按次計費可能比一台固定伺服器貴很多。
-
Cold Start 問題:Serverless function 在一段時間沒有被呼叫後,平台會回收其執行環境。下一次呼叫時需要重新建立環境(載入程式碼、初始化依賴),這個過程就是 Cold Start。不同平台和語言的 Cold Start 時間差異很大:Cloudflare Workers(V8 isolate)約 0-5ms、Vercel Edge Functions 約 10-50ms、AWS Lambda(Node.js)約 100-500ms、AWS Lambda(Java)約 1-3 秒。降低 Cold Start 的方法:減小 bundle size、避免重型框架、用 Provisioned Concurrency(Lambda)或 Edge Runtime。
-
成本模型:Serverless 的計費通常是「呼叫次數 + 執行時間 + 記憶體」。例如 AWS Lambda:每 100 萬次呼叫 0.0000166。如果一個 function 每天被呼叫 1000 次、每次執行 200ms、用 256MB 記憶體,月費不到 20/月的 VPS 反而便宜。計算公式:月呼叫量 × 平均執行秒數 × 記憶體 GB × 單價,和等效 VPS 比較。
使用情境
-
Bot Webhook:Discord/Line/Telegram Bot 用 Webhook 接收訊息。平時沒有訊息、偶爾突然有大量訊息湧入(群組活動)。部署到 Cloudflare Workers,完全不需要維護伺服器。Worker 收到 Webhook、驗證簽名、處理指令、回覆訊息,整個流程在 50ms 內完成。
-
前端 + 輕量 API:行銷頁面或文件網站用 Next.js 開發,部署到 Vercel。前端 SSG 產生靜態頁面,API Routes 處理表單提交和 email 寄送。整個網站的維運成本接近零(Vercel free tier 通常足夠)。
-
混合架構:電商平台的核心服務(訂單、庫存、付款)部署在自架 Docker 上,因為需要穩定的資料庫連線和長時間的 transaction。但 Line 訊息通知、每日報表信寄送、圖片裁切等輕量任務部署到 Serverless。兩者透過 HTTP API 或 訊息佇列 溝通。
實作範例 / 設定範例
Cloudflare Workers(Bot Webhook)
// worker.ts - Discord Bot Webhook handler
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
// 驗證 Discord 簽名
const isValid = await verifyDiscordSignature(request, env.DISCORD_PUBLIC_KEY);
if (!isValid) {
return new Response('Invalid signature', { status: 401 });
}
const body = await request.json();
// Ping(Discord 驗證 Webhook URL 用)
if (body.type === 1) {
return Response.json({ type: 1 });
}
// Slash command
if (body.type === 2) {
const command = body.data.name;
if (command === 'status') {
// 查詢自架 API 的狀態
const status = await fetch(`${env.SELF_HOSTED_API}/health`);
const data = await status.json();
return Response.json({
type: 4,
data: {
content: `Server status: ${data.status}\nUptime: ${data.uptime}`,
},
});
}
}
return Response.json({ type: 1 });
},
};Vercel Serverless Function(API Route)
// app/api/contact/route.ts - Next.js API Route(Vercel 部署)
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const body = await request.json();
const { name, email, message } = body;
// 驗證
if (!name || !email || !message) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}
// 寄送 email(透過第三方服務)
await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'noreply@example.com',
to: 'team@example.com',
subject: `Contact form: ${name}`,
text: `From: ${name} (${email})\n\n${message}`,
}),
});
return NextResponse.json({ success: true });
}
// Edge Runtime(更快的 Cold Start)
export const runtime = 'edge';成本估算比較表
| 方案 | 月呼叫量 | 執行時間 | 月費用估算 |
|------|---------|---------|-----------|
| Cloudflare Workers (Free) | 10 萬次 | <10ms | $0 |
| Cloudflare Workers (Paid) | 1000 萬次 | <10ms | $5 |
| Vercel (Free) | 10 萬次 | <10s | $0 |
| Vercel (Pro) | 100 萬次 | <10s | $20 |
| AWS Lambda | 100 萬次 | 200ms/256MB | ~$0.50 |
| 自架 VPS (4GB) | 無限 | 無限 | $20-40 |何時從 Serverless 遷回自架
決策依據:
1. 月費用 > 等效 VPS 的 2 倍 → 考慮遷回
2. 執行時間經常超過限制 → 必須遷回
3. 需要持久連線(WebSocket / DB pool)→ 必須遷回
4. Cold Start 造成不可接受的 p99 延遲 → 考慮遷回
5. 需要存取內網服務且 VPN 設定太複雜 → 考慮遷回
常見問題與風險
-
Cold Start 延遲:使用者在低流量時段(例如凌晨)呼叫 API,因為 Cold Start 等了 2 秒才收到回應。避免方式:用 Edge Runtime / Cloudflare Workers(Cold Start 極短)。對延遲敏感的服務考慮 Provisioned Concurrency 或直接用自架。
-
Vendor Lock-in:用了太多平台特有的功能(Vercel 的 middleware、Cloudflare 的 KV/D1),日後要遷移到其他平台或自架就很痛苦。避免方式:核心商業邏輯用標準的 Web API(Fetch API、Web Crypto),不依賴平台特有 API。把平台特有的部分隔離到薄薄的 adapter 層。
-
Debug 困難:Serverless function 在遠端執行,沒辦法直接 attach debugger。日誌分散在平台的 dashboard 上,和自架的日誌系統不整合。避免方式:用結構化日誌(JSON format),有需要時整合到自架的 EFK。在本地用
wrangler dev(Cloudflare)或vercel dev(Vercel)模擬 Serverless 環境。 -
成本失控:某個 function 進入無限迴圈、或被惡意呼叫大量次數,帳單暴增。避免方式:在平台上設定 spending limit。設定 rate limiting(Cloudflare 可以用 WAF 規則)。監控每個 function 的呼叫量和費用趨勢。
優點
- 零維運:不需要管理伺服器、OS 更新、安全 patch
- 自動擴展:從 0 到數千個並發,平台自動處理
- 按使用量計費:低流量服務的成本極低
缺點 / 限制
- 執行時間限制(通常 10 秒 - 15 分鐘)
- Cold Start 延遲對即時性要求高的場景不利
- Vendor Lock-in 風險,遷移成本高
- 高穩定流量下,成本可能高於自架