1. 选题定调:第一次大作业,为什么要咬咬牙上云
先说背景。我本科最后一年选了一门《云计算基础》的选修课,期末大作业要求是"使用云计算平台完成一个具备一定复杂度的应用,并提交架构设计文档"。当时身边同学的选择基本分两派:一派用虚拟机装几个服务糊弄过去,另一派老老实实做一个传统三层架构的Web应用,部署在学校机房的服务器上,通过公网IP访问就算完事。
我做了一个不同的决定:全程使用AWS(亚马逊云服务),从计算、存储、网络到数据库全部走云原生的路子。最后作业题目是"基于云平台的校园课程表查询与订阅系统",拿了A,还被老师拿去给下一届当示例。这篇文章不是晒成绩,而是想把我从零到一的过程完完整整拆开,把那些文档里查不到、看了官方教程也容易踩的坑讲清楚。
先说一个真实感受:第一次接触云平台做项目,最难的其实不是写代码,而是建立一整套"云上思维"。你在课本上学到的IP、端口、数据库连接,到了云上全都要重新理解一遍,因为有安全组、VPC、IAM、托管服务这一层又一层的封装。但一旦把这些东西吃透,你会发现自己对"部署"这件事的理解,和以前完全不在一个维度上。
2. 做需求分析时就把"云资源"当变量设计
2.1 我的作业题目到底要做什么
题目是"校园课程表查询与订阅系统",看起来很普通,但如果按照云原生的思路去设计需求,它会变成另外一个样子。
核心功能拆下来有三块:第一,用户可以查询指定院系、指定周次的课程表;第二,用户可以订阅某门课程的变动通知(比如调课、停课),系统通过邮件推送;第三,管理员可以后台更新课程信息。看起来就是个典型的CRUD项目,但如果往深了想,每一块都能对应到不同的云服务。
这里我提一个非常重要的建议:大作业的题目只是给你一个大致方向,具体做成什么样,完全取决于你自己。你可以做成一个简单的一台虚拟机搞定所有,也可以做成一个分布式、多服务协同的系统。老师不会要求你做到后者,但如果你想真正学到东西,就把这个普通题目当成一个真实上线的产品去设计。
我把需求进一步细化,得到了下面这张表,这张表也直接决定了我后续的选型:
| 功能模块 | 核心需求 | 对应的云原生方案 |
|---|---|---|
| 课程表查询 | 读多写少、响应要快 | Lambda + API Gateway + DynamoDB |
| 课程变动通知 | 异步任务、需要定时或触发 | SNS + Lambda 触发邮件 |
| 管理员后台 | 低频操作、有鉴权要求 | 单独的管理API + IAM权限控制 |
| 课程数据存储 | 结构灵活、字段可能变化 | DynamoDB(而非传统关系型数据库) |
| 前端页面 | 纯静态、低流量 | S3 静态网站托管 + CloudFront |
这个表我到现在回头看,最大的价值在于:它不是按照"技术栈"来组织的,而是按照"业务需求"来组织,每个业务诉求都映射到了一个云服务上。这就是云架构设计和传统软件设计最大的区别。
2.2 备课阶段必须先看的三份文档
如果你也是第一次接触云计算,我强烈建议不要一上来就打开控制台开始点鼠标,而是在动手前先把下面三类文档通读一遍:
第一是《AWS Well-Architected Framework》的白皮书,中文版和英文版都行,重点看运维卓越和安全性这两个支柱。第二是目标服务的FAQ页面,比如你要用DynamoDB就把DynamoDB的FAQ刷一遍,比看User Guide管用得多,因为FAQ里全是使用场景和限制。第三是整个项目的架构设计示例,AWS官方提供很多Reference Architecture图,找一个和你项目最接近的看图理解服务间的关系。
这里我想特别说一句:大作业的正确打开方式,不是"会用控制台",而是"能画出架构图并且说出为什么"。老师看重的不是你把网站跑起来了,而是你能不能解释清楚"为什么用队列而不是直接用同步调用""为什么把静态资源放在对象存储而不是服务器本地"。这些决策过程,才是作业拿高分的关键。
3. 云平台选型和账号准备的一个月经验
3.1 为什么选了AWS而不是其他平台
这个问题几乎是所有人都要面对的第一道选择题。选型本身没有绝对的对错,但一定要看你自己的场景和目的。
先说结论:我最终选了AWS,核心原因有三条。第一,AWS的免费套餐(Free Tier)对学习非常友好,尤其是Lambda每月100万次请求免费、S3有5GB免费额度、DynamoDB也有每月25GB存储免费——对大作业这个体量来说,基本可以做到零成本。第二,AWS的中文文档和中文社区资源多,遇到问题搜起来方便。第三,市场上大多数云计算的教材、认证课程都以AWS为例,考试、求职也认这个平台。
但我也要说,这不是说其他平台不好。如果你学校机房的网络访问某些海外平台不稳定,或者你后续想做的题目更偏向国内业务,那选择国内云厂商同样合理。关键在于:选定一个平台后就要坚持用下去,不要在中期因为"听说另一个平台更好"而切换,那会让你把大量时间浪费在重新熟悉控制台上,而不是花在学习云架构本身。
3.2 账号注册、支付方式和预算控制的三个坑
账号注册阶段,我踩了几个可以避免的坑,这里把关键经验列出来:
第一个坑是支付方式验证的时效问题。 AWS注册需要绑定国际信用卡,我当时用的是国内的双币信用卡,结果验证扣款(1美元的预授权)迟迟不到账,导致账号状态停留在"验证中"好几天。后来查了社区才知道,有时候需要打电话给银行跨境客服确认这笔授权请求是正常的,银行那边放行后AWS验证才会通过。所以建议大家注册前就先联系银行做好预沟通,把跨境支付的通道打开。
第二个坑是预算预警一定要在第一天就设置好。 免费套餐不是所有服务都免费的,而且免费额度用完后的价格非常高。我第一次不知道这个情况,直接开了个较大的EC2实例,跑了一周的爬虫任务,月底账单出来直接傻眼。后来我在成本管理里设置了月度预算和90%预警,从那以后再也没出过意外。强烈建议你在开始任何实验之前,先花五分钟设置Billing Alarm,这五分钟能帮你省下几百块钱。
第三个坑是Region(区域)的选择。 很多新手默认使用美东(弗吉尼亚北部),因为这是AWS默认区域。但对于国内用户来说,这个区域访问延迟高、控制台加载慢,而且部分服务在新加坡或东京区域可能价格更低。我当时选择了新加坡区域,速度和价格比较均衡。选Region的思路其实和选服务器机房一样,离你的目标用户尽可能近。
3.3 IAM权限管理:不该图省事用根账号
我第一次用AWS的时候,为了偷懒一直用根账号(Root User)登录控制台。后来在一个技术论坛上看到一条评论说"使用根账号进行日常操作,就像把家里大门的钥匙挂在门口",才意识到这种做法有多危险。
正确的做法是:用根账号创建一个管理员IAM用户,之后日常操作全部用这个IAM用户登录,根账号只在极少数需要高级权限的场景(比如修改账号信息)才使用。我按照这个思路创建了两个IAM用户:一个是我自己的管理员账号,权限策略是AdministratorAccess;另一个是部署专用账号,权限策略精简到只能操作Lambda、API Gateway、S3和DynamoDB这四个服务。
这个部署专用账号在后来的自动化部署过程中起了大作用,它限制了我每次执行部署命令时能调用的资源范围,即使我把Access Key不小心提交到代码仓库里,攻击者能造成的破坏也被限制在一个很小的范围里。权限最小化这个概念,第一次用云的人一定要刻在脑子里。
4. 核心架构落地全记录:从账号创建到第一个部署
4.1 按需生成而不是后台常驻:Serverless架构好在哪
说一个反直觉的事实:我第一次设计的架构其实是传统的EC2 + RDS方案,一台服务器常年运行网站后端和数据库。但后来在做成本预估的时候,我发现即使是最小的实例,一个月奔着200块去了。这时候一个学长提醒我:"你一个大作业,流量就那么点,有必要让服务器24小时亮着吗?"
于是我开始研究Serverless架构。所谓Serverless,直观理解就是你不需要自己管理一台服务器,而是把代码片段上传到云端,由平台负责运行。在这个模型下,你的代码只在请求到来时才被唤醒执行,执行完就"睡觉",不产生任何费用;有流量的时候平台自动扩容,根本不存在的流量当然也就没有成本。
对于大作业这种特性极为明确的项目——查询量不大、请求时间不规律、不存在高并发场景——Serverless几乎是完美匹配。按照官方定价粗略算了一笔账:Lambda每月100万次免费请求根本用不完,API Gateway每月100万次调用免费,DynamoDB读写容量按量付费,一个月算下来基本是0到几块钱人民币的水平。这个账一算出来,我的架构选择就没有悬念了。
4.2 从0开始搭建环境的完整操作过程(含关键参数)
接下来把我的实际操作过程完整写出来,这些参数和配置都是经过多轮验证的,直接照抄能少走至少一半弯路。
第一步,创建Lambda函数。 我用的运行环境是Python 3.9,因为牵涉到的依赖库(比如操作Excel的openpyxl)在Python里比较好处理。创建的时候选择"从空白创建",触发器先不配,函数代码直接内联写一个返回"hello world"的简单函数验证流程。
在这个阶段有一个非常关键的细节:Lambda函数超时时间的默认值为3秒,那个仅用于测试的"hello world"绰绰有余,但我后续需要读取外部文件做二次处理的函数,3秒明显不够,一定要去配置里手动加大。 我后来把超时时间改成了30秒,内存也从默认的128MB调整到512MB。网上很多报错"Task timed out",十有八九是这里没改动。
第二步,创建API Gateway作为访问入口。 在API Gateway中创建一个REST API,资源路径为/course,方法为GET,集成类型选择Lambda函数。这里要注意的是,Lambda函数执行的IAM角色需要有API Gateway调用权限,否则会报权限相关的错误。我当时折腾了半小时,最后在AWS文档里看到一句话"首次将Lambda绑定到API Gateway时,控制台会自动帮你创建一个带权限的Lambda函数版本",实际操作也确实是这样的——所以第一步不要效仿我先手动创建IAM角色,直接让控制台帮你做就好。
第三步,创建DynamoDB表存储课程数据。 这里的关键是理解"主键"设计。我设计了一个复合主键:分区键(Partition Key)是department(院系),排序键(Sort Key)是course_id(课程编号),这样同一个院系下的所有课程就能被高效地一起查询出来。DynamoDB的计费模式我选了按需模式(Pay Per Request),因为大作业流量低且不稳定,按需模式比预留容量模式更省开销。
第四步,把数据灌进去。 课程数据的初始来源是我们学校教务处的公开CSV文件,我写了一个Python脚本,先解析CSV再调用DynamoDB的PutItem接口批量写入。写脚本时有一个易错点:DynamoDB要求空字段不能直接存入,要么省略该字段,要么用NULL类型,脚本里必须做空值过滤,不然必报"InvalidAttributeType"错误。这个坑看似小,但排查起来特别耗时。
第五步,配置前端。 前端我用了一个非常轻量的Vue单页面,构建后的静态文件全部上传到S3桶,开启静态网站托管功能,然后用CloudFront做CDN加速,绑定了一个自己的域名。这里有个细节:S3桶需要关闭"阻止公有访问"的设置,然后通过桶策略允许CloudFront访问,而不是直接对所有人开放。这样做的安全性更好,也避免了S3被练手的黑客盯上改造成恶意文件的投放源。
4.3 部署的两种方式:控制台点击与基础设施即代码
如果只是跑通一个demo,上面介绍的控制台点击方式完全够用。但真实工作场景中,几乎没人会用控制台去一台一台地创建资源。业界通用的做法是"基础设施即代码"(Infrastructure as Code,IaC),也就是把云资源的定义写成一个代码文件,然后通过工具自动创建和配置。
我采用的是AWS官方的CloudFormation模板形式。通过YAML定义了一个包含Lambda函数、API Gateway、DynamoDB表、S3桶的完整堆栈,然后在CloudFormation服务里上传模板、指定参数、开始部署,大概5分钟所有资源自动就建好了。
这里有同学会问:大作业有必要用CloudFormation吗?我的看法是:如果你时间充足,非常值得。因为CloudFormation内置了资源清理的功能,你只要删除这个Stack,它会把里面定义的所有资源一并删掉,不会留下隐形账单。这一点对用免费额度做实验的人来说太重要了——独立创建的资源难记账,删不干净下个月账单就会让你怀疑人生。
5. 遇到的最让人抓狂的故障和完整的现场排查链路
5.1 排障方法论先于动作
真正让我觉得"自己在这门课上学到了东西"的,不是成功部署的过程,而是一次把人气到摔鼠标的报错排查经历。
场景是这样的:我部署完整个架构之后做端到端测试,打开浏览器输入CloudFront的域名,页面加载正常,但是一输入院系名称点击查询,页面就一直转圈。打开开发者工具,发现一个GET请求返回了HTTP 500错误。我一脸懵,因为单独测试API Gateway的地址时是能正常返回数据的,怎么绕过CDN就不行了?当时脑子里的第一反应是"CDN缓存导致的问题",但实际上这个直觉方向完全错误,如果顺着这个方向查下去,可能一晚上都解决不了。
后来我给自己定了一个排查纪律:先看日志,再猜原因。云平台比传统服务器更方便的一点是,它提供了完整的日志链路。点开CloudWatch日志,查看那段500错误对应的日志流,具体错误信息立刻浮现了出来:Lambda函数执行报了一个"module not found"错误,找不到名为"requests"的第三方库。
5.2 日志发现库缺失:依赖包打包策略的代价
原因很清晰:我的Lambda函数代码除了需要标准库之外,还调用了requests库向外部获取额外数据,但Lambda的执行环境中默认没有这个第三方库,需要把依赖以压缩包的形式上传。控制台创建Lambda时,往往有"内联编辑代码"的功能,这个便利性掩盖了依赖管理的复杂性——如果你直接在控制台里写代码,很容易出现"本地明明跑通了,云端死活报错"的尴尬。
解决办法有两种。第一种是把本地Python环境的site-packages目录下对应的依赖库文件夹,连同函数代码一起打包成ZIP上传。第二种是采用Lambda的部署工具,比如SAM CLI,它能自动检测函数需要的Python模块并打包。我当时用的是第一种,因为只缺一个requests库,手动打包工作量也不大。
这个坑让我顿悟了一个云开发的基本规则:Serverless平台不是一个随处可跑的Python解释器,它只负责运行你交给它的那部分代码和依赖,剩下的东西需要你自己准备好。理解了这点之后,后续所有函数的打包都走了"先本地测试+打包依赖+上传验证"的固定流程,再也没有出现过依赖相关的故障。
5.3 权限不足导致查询失败:IAM角色和策略的关系
依赖问题解决后,我以为万事大吉,结果再次点击查询,这回报了一个403权限错误。日志里显示"AccessDeniedException"。
这次的排除过程很费劲,因为它不是报"网络不通"或者"服务不可用",而是报了一个权限异常。我第一时间怀疑的是API Gateway的发布阶段设置,后来想到Lambda函数执行时是有独立身份的——执行角色(Execution Role)。这个角色决定了Lambda能调用哪些AWS服务。我在创建Lambda时,自动生成了一个默认角色,但这个默认角色只有基础的CloudWatch日志写入权限,没有DynamoDB的读写权限。
增长见识的时刻到了:没有权限的报错不会在控制台创建资源时出现,只会在实际运行时冒出来。 解决方法是去IAM服务里找到Lambda的执行角色,在该角色的权限策略中增加DynamoDB的读操作权限:GetItem、Query、Scan。
这个过程中我深切体会到:IAM权限模型就是云平台的"操作系统级权限"。底层没有给够权限,上层功能就会在运行时各种报错;多给权限虽然不会马上报错,但会留下安全隐患。"给刚刚好,不多给"的能力,是需要做几次真实项目才能在身体里沉淀下来的直觉。
6. 设计评审环节被追问最多的三个问题
部署整体通过之后,进入答辩环节。老师围绕架构设计提了不少问题,有些问题问完我就知道,如果当初草草做传统架构,可能直接愣住答不上来。这里把最有价值的三个问答复盘出来,供大家参考。
6.1 为什么数据库选NoSQL而不是MySQL
按照惯性思维,做Web应用首选MySQL,学校课程也主要教关系型数据库。我当时在文档里把这个决策单独写了一小节,并且在答辩时从三个角度解释了理由。
第一是数据模型的匹配度。我的课程数据虽然是结构化的,但不同院系、不同学期课程字段差异很大,有的课程有"实验课时"字段,有的没有,用NoSQL的灵活表结构能减少大量NULL字段的存储和维护成本。第二是存取模式单一。查询服务主要依赖"按院系查课程"这一个模式,没有复杂的多表关联需求,用DynamoDB的单表查询就能解决,如果强行用关系型数据库反而要写好几条JOIN语句。第三是运维成本。DynamoDB是托管服务,我不需要关心备份、主从同步、索引优化等运维事务,可以把精力全部放在业务代码上。
6.2 消息通知为什么要走SNS,自己发邮件不行吗
这是老师追问得最细的问题之一。我做课程变动通知功能时,第一版是直接在Lambda函数里调用邮箱SMTP服务去发邮件,但后来发现一个致命问题:一旦课程变动很多,Lambda函数会被调用很多次,SMTP服务器的连接数就会被打满,而且每封邮件都要同步等待SMTP服务器的响应,整个请求的延迟被拉得非常高。
后来改成了SNS(Simple Notification Service,简单通知服务)的方案:Lambda先把"课程有变动"这件事作为一个消息发布到SNS的主题(Topic),SNS会自动完成后续的邮件推送逻辑。生产者和消费者被完全解耦,我不需要关心邮件是怎么发的、什么时候发的,SNS会可靠地完成投递。
老师听到这里点头了,他追问了一句:"那如果SNS发送失败怎么办?"我答:"SNS有内置的重试策略和死信队列规则,可以在控制台设置最多重试次数;如果实在递送失败,会把消息转储到SQS队列里等待人工处理。"这个细节明显超出了作业要求,但我当时花了一个晚上读文档研究出来,答辩时派上了大用场。
6.3 安全性上做了哪些设计,不只是开个HTTPS
安全问题在大作业中是最容易被忽略但又最容易被问到的。我的做法是:第一,所有API都通过Lambda进行鉴权处理,在前端调用的请求头里附带一个简单的前后端共享密钥,后端验证后才放行,虽然比不过标准的JWT或OAuth,但作为大作业在"鉴权"意识上体现了完整体验。第二,数据库和Lambda函数都放在同一个VPC(虚拟私有云)的内部,没有直接暴露在公网的入口。第三,S3桶存储课程原始导出的CSV备份,和前端静态网站彻底分离,避免课程原始数据被意外公开。
这里我把安全策略汇总成一个表格,方便大家对照检查:
| 安全层面 | 具体设计 | 保护对象 |
|---|---|---|
| 网络边界 | 所有资源都在VPC内,安全组只放开必要端口 | 数据库和Lambda内网不暴露 |
| 访问控制 | API Gateway + 自定义Header密钥校验 | 防止API被恶意直接刷 |
| 数据存储 | S3桶分权限管理,前端桶只读公网,数据桶仅限IAM访问 | 防止敏感数据泄露 |
| 凭证管理 | IAM用户分离、Lambda通过角色而非硬编码密钥访问服务 | 防止密钥在代码中被发现 |
| DNS与传输 | CloudFront + 自建证书启用HTTPS | 传输加密、防篡改 |
7. 这次大作业之后,我再也不会犯的三个常识性错误
项目答辩完,分数也出了,但我复盘了整个过程中的决策和失误,总结出三个"如果我第一周就知道,能省下无数个熬夜"的经验。
第一,云端开发不等于本地开发。 本地开发时所有资源都在自己电脑上,日志在控制台直接打出来;云端的错误分散在不同的服务日志里。所以从第一天起就要建立"日志先行"的思维——出问题先去看日志,而不是盯着前端页面干瞪眼。这个过程刚开始很痛苦,但一旦习惯,排查效率高到惊人。
第二,成本和安全意识要刻进肌肉记忆。 云端的资源不是一次性购买,而是按使用量计费。第一周我学了两条血泪教训:不用了要立刻停止或销毁实例、预算报警器要第一时间配好。后来我每次新建资源前都会问自己三个问题:这个资源会不会跑出免费额度?它的访问权限需要多大?如果我不再用它了,怎么找得到并且删掉它?
第三,架构图是项目的灵魂。 很多同学做完项目才开始画架构图,我反而是画完架构图才开始写代码的。架构图不只是一种交付物,它更是一种思考工具。在图上你一眼就能看出哪些服务之间有强依赖、哪些地方有明显的单点故障、哪些时机需要消息中间件来削峰填谷。很多设计上的不合理之处,在纸面上就提前暴露了,根本不需要到部署阶段去试错。
最后再讲一个很实际的小技巧。如果你也是学生党,预算有限,又怕不小心产生高额账单,一定要学会用"资源标签+成本分组"这个组合拳。我给每个资源都打了标签,比如module=course-system、env=dev,然后在成本管理控制台里按标签分组看费用,哪个模块烧钱一目了然。这个做法不仅帮我控住了成本,答辩时拿出来展示也成了加分项——它证明你有"运营思维",而不仅仅是一个能敲代码的机器。希望这篇流水账一样的实战记录,能帮你绕过我当年踩过的那些坑,用更短的时间把云上项目做扎实。
