如果你已经折腾过建站流程大概能体会那种痛苦先买一台服务器、装系统、配Nginx、部署环境、担心流量一上来就宕机然后每隔几个月还得处理一次安全补丁。而这次我换个思路在 AWS 上搭了一套完完全全的无服务器架构网站——没有一台需要登录的机器所有服务按请求次数计费流量涨了系统自己扩容流量低的时候几乎零成本。这篇文章不是讲概念是我把从零到上线的完整过程包括每个步骤为什么这么做、每个坑是怎么踩出来的全部拆开给你看。这套架构适合谁想给自己的作品集、小型工具站或内容社区搭一个“不用担心服务器挂了”的站点又不想一上来就啃厚重的云原生文档的同学。你只需要懂一点 HTML会写最简单的 Python 或 Node.js 函数剩下的事情 AWS 帮你扛。我会先用一次完整的请求链路把架构讲明白再逐步落到 IAM、S3、API Gateway、Lambda、DynamoDB、CloudFront 这些具体服务上最后附上我实测的成本数据和五个最容易翻车的坑。1. 为什么我建议入门者直接上无服务器而不是租一台 VPS刚开始接触云计算的开发者多半都经历过一个摇摆期一边是传统的云服务器几十块钱一个月配置灵活一边是无服务器架构听起来高大上但总觉得门槛高。我的结论很直接除非你有特殊合规要求或者业务本身就是长时间高负载的批处理任务否则纯入门项目选无服务器绝对是性价比和心智负担双赢的选择。1.1 传统建站模式的最大痛点无服务器是怎么绕开的传统建站的痛苦在于“固定成本”和“不可预期流量”的矛盾。你买了一台最低配的服务器流量来了扛不住买了高配大部分时间在闲置。更麻烦的是你的代码和运行环境强绑定在一台机器上——更新一次系统、改一个 PHP 版本都可能把线上服务搞挂。我曾经见过一个朋友为了给博客加一个评论功能被服务器上的环境依赖折腾了整整一个周末。无服务器架构的思路完全反过来不用准备任何机器把“代码”和“请求”分开看。你的静态资源放在对象存储里业务逻辑写成一个函数数据存到托管数据库。没有常驻进程就是一个请求进来系统临时把函数跑起来执行完就释放。流量小的时候一个月可能只花几毛钱流量突然冲到峰值底层自动扩出上百个实例用户毫无感知。1.2 这套架构的真实形态一张请求画出的生命线为了让后面的步骤不那么抽象我先描述一下整个网站的一次完整请求路径后面所有操作都在为这条路服务。假设访客打开你的域名第一次请求会先打到 CloudFrontCDN 节点如果节点上有缓存直接返回整个过程不到 100 毫秒。如果缓存没命中CDN 会回源到 S3把你的网页、图片、JavaScript、CSS 这些静态文件取出来。与此同时页面里某个按钮需要把访客填写的留言保存下来这个请求会发到 API GatewayAPI Gateway 把请求转给 Lambda 函数函数校验数据后写进 DynamoDB再把成功结果原路返回。在这个流程里没有任何时刻存在一台“始终开机的服务器”。S3 负责存储CloudFront 负责分发API Gateway 负责接收动态请求Lambda 负责运算DynamoDB 负责存数据。每一样东西都只在自己该出现的时候出现。初期你可能觉得这套组合有点绕等你真正跑通一次就会明白这种分工的好处——每一环都可以单独伸缩、单独加监控、单独换实现。2. 动手前必须先搞懂的角色分工很多人做 AWS 项目时卡住不是因为操作复杂而是因为没想清楚每个服务到底是干什么的。AWS 有几十个服务名初学者一打开控制台就懵了。我帮你把这一套架构里涉及的服务收敛成五个角色各自只干一件事。2.1 静态资源、业务逻辑、数据存储各归哪家第一个角色是 S3全称 Simple Storage Service负责存放一切不需要计算就能返回的文件。你的 HTML、CSS、图片、视频全都可以丢进去。它本质上是一个无限大的网盘但通过 HTTP 协议对外提供访问。第二个角色是 CloudFront内容分发网络。它把 S3 里的文件缓存到离用户更近的节点上减少跨地域访问的延迟同时顺手帮你把 HTTPS 证书管了。第三个角色是 Lambda这是整套架构里真正“算东西”的地方。你写一个函数上传到 Lambda它负责处理特定类型的请求。比如接收表单数据、调用第三方接口、生成一段签名。第四个角色是 API Gateway它是 Lambda 的接待员——把外部的 HTTP 请求翻译成 Lambda 能理解的参数再把函数的返回值翻译回 HTTP 响应。第五个角色是 DynamoDBNoSQL 数据库用来存访客留言、计数器、用户配置这类结构化数据。这五个服务拼起来就是一个完整网站的所有零件。你可以把 S3 想象成家门口的货架CloudFront 是遍布全城的分店Lambda 是店里的师傅API Gateway 是前台DynamoDB 是仓库记账本。没有哪个角色是多余的。2.2 冷启动和并发入门阶段不值得焦虑的两个词一聊到 Lambda总有人问冷启动。所谓冷启动就是这个函数一段时间没人调用系统必须重新拉起一个运行环境再执行这个过程可能多出几百毫秒延迟。我第一次听到这个概念时也很紧张但实际跑下来个人网站、小型工具站完全不用在意。只要你不是那种需要毫秒级响应的实时服务冷启动的延迟人眼根本感知不到。真到了在意冷启动的规模你自然会去研究预留并发、Keep-Alive 这些手段那都是后话。并发同样不用太焦虑。Lambda 默认的并发上限是 1000绝大多数个人项目到天荒地老都撞不上。对比一下传统服务器一台 1核2G 的实例能同时撑住的连接数也就一两百还得小心翼翼调 Nginx 参数。无服务器把这种技术细节全部封装掉了你只需要关注自己的业务代码。3. 完整搭建过程从 IAM 到 CloudFront 分发下面进入实操环节。我假设你已经有 AWS 账号并且把控制台语言切换成中文了——AWS 控制台右上角可以改语言虽然翻译偶尔生硬但新手友好度确实提升不少。所有资源的创建我建议尽量用同一个区域比如 us-east-1这样可以减少很多跨区域配置的麻烦。3.1 第一步创建 IAM 用户别用根账号干活很多人第一次注册完 AWS就顺手用根账号开始敲命令。这是个坏习惯根账号权限太大一旦凭证泄露整个账号都被人拿走。正确做法是创建一个只包含必要权限的 IAM 用户日常操作都用它。进入 IAM 服务点击“用户”创建用户名字随便起一个比如 admin勾选“访问密钥 - 编程式访问”。在权限设置里最稳的方式是选择“直接附加现有策略”先选 AdministratorAccess等你熟练了再改成最小权限。创建完成后会生成一个 Access Key ID 和一个 Secret Access Key下载下来保存好它只显示这一次。这个用户后续用来执行 AWS CLI 命令、部署函数不要在别的地方乱贴。我以前见过有人把 Access Key 写进 GitHub 仓库的配置文件几小时后账号里就多了好几台不知名的机器这种教训一次就够。3.2 第二步DynamoDB 建表和 Lambda 函数的编写在 DynamoDB 控制台里创建一个表名字叫 VisitorMessages主键用 id类型选字符串。对入门项目来说这个配置已经够了不需要预留读写容量直接用按量模式。接下来是 Lambda。创建 Lambda 函数运行时选择 Python 3.12 或 Node.js 20 都可以我下面用 Python 示例。函数的作用很简单接收访问者提交的姓名和留言存进 DynamoDB然后返回成功消息。代码如下import json import uuid import boto3 from datetime import datetime dynamodb boto3.resource(dynamodb) table dynamodb.Table(VisitorMessages) def lambda_handler(event, context): try: body json.loads(event.get(body, {})) name body.get(name, 匿名) message body.get(message, ) if not message.strip(): return { statusCode: 400, headers: { Content-Type: application/json, Access-Control-Allow-Origin: * }, body: json.dumps({error: 留言内容不能为空}) } item { id: str(uuid.uuid4()), name: name, message: message, created_at: datetime.utcnow().isoformat() } table.put_item(Itemitem) return { statusCode: 200, headers: { Content-Type: application/json, Access-Control-Allow-Origin: * }, body: json.dumps({status: ok, id: item[id]}) } except Exception as e: return { statusCode: 500, headers: { Content-Type: application/json, Access-Control-Allow-Origin: * }, body: json.dumps({error: str(e)}) }这段函数没有牵扯任何框架就是纯标准库加 boto3。你要注意两点一是响应头里必须带上 Access-Control-Allow-Origin否则浏览器跨域请求会被拦下来后面我会专门展开讲这个坑二是函数本身没有处理 CORS 的预检请求 OPTIONS这个我们放在 API Gateway 里解决。创建 Lambda 时AWS 会让你选择一个执行角色。它在后台帮你生成一个基本角色但我们得自己补一条 DynamoDB 访问权限。进入函数配置里的“权限”标签找到执行角色给它附加 AmazonDynamoDBFullAccess 策略。此刻停下来想一想如果你直接把函数代码写出来了但请求一直返回权限错误十有八九就是漏了这一步。3.3 第三步API Gateway 把外部请求接进 Lambda打开 API Gateway 服务创建新 API协议选 HTTP API 就好不用选 REST API后者功能多但对入门项目来说全是噪音。创建完之后添加路由这里有两个路由POST /message绑定到刚才创建的 Lambda 函数。OPTIONS /message这个要配置成返回 CORS 预检的响应。POST 路由配置好之后还要在“CORS”设置里打开跨域支持允许来源填 *允许方法填 POST、OPTIONS允许请求头填 Content-Type。HTTP API 会自动帮你处理预检请求省去很多手工配置。最后你需要把 API 的调用 URL 记下来格式类似 https://xxxxxxxx.execute-api.us-east-1.amazonaws.com。先别急着接前端用命令行工具 curl 测一下接口是否正常curl -X POST https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/message \ -H Content-Type: application/json \ -d {name:test,message:hello}如果返回里带 statusCode 200就说明函数、数据库、网关这一整条链路已经通了。走到这里你手里已经有一个具备动态能力的后端了。3.4 第四步S3 托管静态页面CloudFront 负责加速和 HTTPS现在做网站的前半部分。先创建一个 S3 存储桶名字全局唯一比如 my-serverless-site-2025。把 index.html、style.css、app.js 这些文件拖进去。关键一步是存储桶属性里打开“静态网站托管”入口页面填 index.html。不过直接访问 S3 提供的静态网站域名不支持 HTTPS浏览器会给你一个大大的不安全警告。这时候 CloudFront 就派上用场了。创建一个 CloudFront 分发源选择刚才的 S3 存储桶如果控制台提醒你“Origin access control”选上就行。行为里 Viewer protocol policy 选 Redirect HTTP to HTTPS。其他保持默认。创建完分发你会拿到一个以 cloudfront.net 结尾的域名。如果只想先跑通直接用这个域名访问就足够了。想要自定义域名去 Route 53 或任意 DNS 服务商那里加一条 CNAME 记录指向这个 CloudFront 域名然后在 CloudFront 的“备用域名”里填上你的域名再申请一个 ACM 证书。证书申请属于另一条完整流程入门阶段建议先用默认的 cloudfront.net 域名体验等确认整套链路没问题后再接自定义域名。3.5 第五步整个链路串起来一起测试页面里的 JavaScript 需要往 API Gateway 的地址发请求。下面这段代码放在你的前端里用来提交留言async function submitMessage() { const name document.getElementById(name).value; const message document.getElementById(message).value; const response await fetch(https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/message, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ name, message }) }); const result await response.json(); console.log(result); }注意这里有一个容易踩的坑很多教程会用 axios 或者 fetch 直接调但如果你用浏览器打开的是 HTTPS 站点而接口地址是 HTTP浏览器会直接拦截。确保 CloudFront 和 API Gateway 默认都是 HTTPS前端代码里也写成 https就不会有这个问题。全部配置完成后打开 CloudFront 域名看到页面加载填一条测试留言并成功提交。恭喜到这里你已经跑通了一套完整的无服务器网站。它没有一台服务器却同时具备静态文件分发、动态接口处理和持久化存储三种能力。4. 真实成本测算免费额度之外的每一笔钱花在哪搭建过程里问得最多的一个问题就是这么一套东西到底要花多少钱我用一个典型的个人内容站来算账假设每月有大约 1 万次访问每个用户平均产生 5 个静态资源请求和 1 次接口调用。4.1 免费额度能覆盖多少AWS 对新账号有 12 个月的免费套餐而无服务器这组服务的好消息是不少免费额度是长期的不只是第一年服务免费额度超出后的价格大致Lambda每月 100 万次请求 40 万 GB-秒计算时间每百万请求 0.20 美元API Gateway每月 100 万次请求每百万请求 1.00 美元DynamoDB25 GB 存储 每月 2500 万次读取按量读写每百万次约 0.25 美元起S35 GB 标准存储 每月 2000 次 GET每 GB 约 0.023 美元CloudFront每月 1 TB 流量超出后每 GB 约 0.085 美元起按上面假设月访问量 1 万 PV 来算Lambda 请求数大概是 1 万次API Gateway 也是 1 万次DynamoDB 读取写入加起来不超过几万次S3 存储不到 100 MBCloudFront 流量不到 1 GB。这些数字全部落在免费额度内也就是说这个站在很长一段时间内每月的账单是 0 美元或者接近 0 美元。4.2 什么时候开始明显花钱当你把访问量放大到 100 万 PV/月成本结构就开始清晰了。以 us-east-1 区域为参考Lambda 大约 0.20 美元每百万次请求API Gateway 1.00 美元每百万次CloudFront 流量如果走了 500 GB大约是 42.5 美元DynamoDB 如果没做优化按量模式下可能跑到 10 到 20 美元。合计下来不到 100 美元一个月对比同样流量下需要两三台高配服务器的成本还是便宜很多。这里有一个不花冤枉钱的关键习惯给 DynamoDB 的查询建好索引别全表扫描。还有 CloudFront 的缓存命中率要盯着命中率低了回源流量就会变多S3 的请求费和 CloudFront 的流量费都会上去。我见过一个案例因为页面里忘了给静态资源加 Cache-Control结果缓存命中率只有 30%账单比预期高了三倍。4.3 预算监控别等出账单才看在账单面板里设置一个预算警报每月 1 美元的阈值就够了超过就发邮件提醒。很多人觉得这一步啰嗦但我告诉你云服务计费最吓人的不是用量大而是失控。写错一个 API Gateway 路由、被人刷了流量、某个 Lambda 死循环疯狂调用一天之内费用就可能冲到几百美元。预算警报不会阻止问题发生但它能让你在当天知道而不是月底收到账单后才知道。5. 入门最常踩的五个坑以及完整的排查链路每套架构都有自己独特的坑无服务器也不例外。下面这五个坑我基本都亲手踩过每个都按“现象 → 检查 → 定位 → 修复 → 验证”的顺序写没有直接给答案你照着走一遍以后遇到类似问题也能自己查。5.1 IAM 权限不足函数能创建但一运行就 500现象在 Lambda 测试页面手动执行函数返回的错误是 AccessDeniedException听名字就头疼。第一次碰到这个错我以为是代码写错了查了半小时日志才发现是执行角色没有 DynamoDB 权限。AWS 的权限模型是函数本身没有权限它只能借“执行角色”的身份去调用其他服务。创建 Lambda 时系统生成的默认角色只给了写入 CloudWatch 日志的权限访问 DynamoDB 的权限得自己加。排查链路打开函数的“配置 → 权限 → 角色名称”直接点进去在 IAM 角色的权限策略里检查是否包含 DynamoDB 相关的策略。如果没有附加 AmazonDynamoDBFullAccess保存后再去测试。修复完再去跑一次函数200 立刻出来。这个坑的深层启示是在 AWS 上排查问题先看权限再看代码。权限问题的报错信息往往一看就懂代码问题的报错则藏在日志里。顺序搞反了你会浪费很多时间。5.2 CORS 跨域拦截前端写好了请求却在浏览器里被挡现象接口用 curl 测试完全正常前端页面也能加载但浏览器控制台报错错误信息里明晃晃写着 CORS policy: No Access-Control-Allow-Origin header。我记得很清楚第一次遇到这个报错时还以为是自己字打错了。后来才知道CORS 是浏览器层面的安全机制它要求服务器在响应头里声明“允许哪个来源访问”。curl 不校验这个所以后端直接返回数据浏览器校验所以数据被拦了。排查链路分三步。第一步在浏览器开发者工具的 Network 面板找到那个请求看响应头里有没有 Access-Control-Allow-Origin。没有说明 API Gateway 配置里没开 CORS。第二步去 API Gateway 的 CORS 设置允许来源填 *允许方法勾上 POST 和 OPTIONS允许请求头填 Content-Type。第三步重新部署 API注意这一步容易漏——修改完配置不重新部署线上还是老版本测试起来会怀疑人生。还有一种隐蔽情况如果接口返回 4xx 错误CORS 头可能跟着丢失。所以遇到 CORS 报错时先确保接口本身返回 200再检查跨域配置。5.3 冷启动被误判为功能故障响应慢到怀疑人生现象函数偶尔一次请求要花 2 到 3 秒其余时候都是 200 毫秒以内。第一次看到那几秒的监控曲线我差点以为是网络问题。排查链路打开 Lambda 的监控面板查看“持续时间”指标如果时延分布有明显的长尾冷启动的概率很大。CloudWatch 日志里REPORT 行的 Duration 和 Billed Duration 能辅助判断。如果发现冷启动对你的场景确实不可接受三个思路一是给函数配置预留并发让运行环境常驻二是把 Java、.NET 这类启动重的运行时换成 Python、Node.js三是把不常用的接口做合并减少函数调用次数。入门阶段的建议还是那句把冷启动当成可接受的正常现象不要为了优化它引入更多复杂度。等你真正做到日活千人的规模再回头处理也不迟。5.4 DynamoDB 热分区数据一多单分区吞吐被打满现象业务刚上线时一切正常跑到一定量级后DynamoDB 开始报 ThrottlingException。分布式数据库有个隐藏特性数据分区是按主键哈希分布的。如果你把主键设计成自增数字或者时间戳那么新数据会集中落在某几个分区上导致这些分区读写被限流。入门项目通常不会碰到这个量级但一旦开始认真做了这是迟早要面对的问题。排查链路DynamoDB 控制台的“ Capacity” 面板看读或写受限事件如果确认了热点分区就把主键改成高基数键比如在时间戳前面拼接一个随机后缀或者使用 UUID 作为主键。之前我那段示例代码用的就是 uuid现在你知道为什么了——这一个细节能避免以后一次大修改。另一个常见原因是按量模式下某个业务高峰期的突发流量。DynamoDB 的按量模式会自动伸缩但伸缩速度有一个爬坡过程实在没法容忍的话可以预先设置一个偏高的上限代价是可能多花点钱。5.5 CloudFront 缓存不更新改了文件用户始终看到旧版本现象修改了网站里的 CSS 文件自己访问新版本没问题可用户那边还是旧的。原因有两个方向一是 CloudFront 的默认缓存行为是最小 TTL 为 24 小时二是浏览器自身对静态文件会缓存。这是分布式系统里经典的一致性难题。排查链路先在浏览器强制刷新CtrlShiftR排除浏览器缓存再到 CloudFront 控制台创建一条失效请求操作路径是“分发 → 失效 → 创建失效”路径填 /css/style.css。等待几分钟到十几分钟节点缓存被清除再访问就是新内容了。不过创建失效不是最优解。最佳实践是给静态资源文件名加哈希比如 style-a1b2c3.css每次内容变更后生成新的文件名从根上绕开缓存问题。这属于前端工程化范畴但你迟早会用到现在有这个意识就行。最后聊点真实感受整套架构跑通之后我最大的感受是只要理解了“每个服务只干一件事请求在各服务间传递”这个模型就再也没有什么云概念值得害怕了。我在实际运营这个网站的过程中最常做的事不是登录服务器而是打开 CloudWatch 看看函数执行时间打开账单面板看费用波动。整台网站像一台自己运转的机器你只需要负责更新内容。如果接下来你想继续深入我建议按这个顺序走先把自定义域名和 HTTPS 证书接上再给 API Gateway 加一层简单的限流然后尝试写第二个 Lambda 函数比如每天定时把 DynamoDB 的数据汇总发邮件最后把前端的构建流程从手动上传改成自动部署。每一步都是独立的可回退的不牵扯基础设施。无服务器架构真正打动我的地方就是它把运维复杂度收走把精力省下来留给业务本身——这种体验值得每一位入门者亲手试一次。