
1. 项目概述为什么一个 Django 网站值得被“塞进” Lambda你有没有过这种体验辛辛苦苦用 Django 搭建了一个内容管理系统本地跑得飞起部署到服务器上却开始掉链子——数据库连接池配错、静态文件路径不对、环境变量漏传、Nginx 配置改了八遍还是 502……最后发现真正花时间的不是写业务逻辑而是和运维细节死磕。我干这行十多年从最早手动配 Apache 到后来用 Ansible、Docker再到今天终于在 AWS Lambda 上把一个完整的 WagtailDjango 的 CMS 分支站点跑通了。这不是为了炫技而是因为一个非常朴素的需求我想让一个内容型网站在没有专职运维的情况下也能扛住流量高峰、自动缩容到零、账单精确到毫秒且整个过程可重复、可测试、可回滚。这个项目标题里的 “Serverless Django” 并不是说 Django 本身被重写了而是指我们彻底剥离了对传统服务器EC2、ECS、甚至容器编排平台的依赖。核心思路很直接把 Django 应用打包成一个符合 WSGI 规范的 Python 包让它运行在 AWS Lambda 这个无状态、事件驱动的计算单元里把所有有状态的部分——数据库、静态资源、密钥、邮件服务——全部交给 AWS 原生的托管服务来承载。关键词里提到的 “AWS, Python, Best Practices, Cloud, Django”恰恰就是这个方案的四根支柱AWS 提供基础设施即服务IaaS和平台即服务PaaS的混合能力Python 是 Django 和 Lambda 的共同语言Best Practices 不是空话它体现在 12-Factor 应用原则、Terraform 的声明式配置、Secrets Manager 的密钥管理上Cloud 是最终形态而 Django 是我们最熟悉、最可控的业务逻辑载体。很多人一听 “Serverless Django” 就摇头觉得这是反模式。毕竟 Django 默认设计是长生命周期的进程而 Lambda 是毫秒级的短命函数。但问题从来不在框架本身而在我们如何桥接二者。关键在于理解 Lambda 的本质它不是一个“微型服务器”而是一个“按需加载的执行环境”。我们不需要让 Django 在 Lambda 里“常驻”而是让它在每次 HTTP 请求到来时快速初始化、处理请求、返回响应、然后优雅退出。这听起来像不可能的任务但 apig-wsgi 这个库加上精心设计的lambda_handler入口就完美地完成了这个翻译工作。它把 API Gateway 发来的 JSON 事件转换成标准的 WSGIenviron字典和start_response回调再把 Django 的响应原样打包回 API Gateway 能理解的格式。整个过程对 Django 来说就像它正运行在一个普通的 WSGI 服务器上一样透明。所以这不是 Django 的妥协而是我们对云原生架构的一次精准适配。它适合谁适合那些需要快速上线 MVP、预算有限但又不能牺牲安全与可维护性的团队适合那些已有 Django 技能栈不想为了一次部署就去学 Kubernetes 的工程师也适合那些想把老旧 CMS 迁移到云上又不想重写的运维负责人。它不是万能药但当你看到账单上那个 $5.39 的数字以及 Terraform 一行命令就把整套生产环境拉起来的时候你会明白这条路走对了。2. 整体架构设计与核心思路拆解2.1 为什么是 Lambda API Gateway RDS S3而不是其他组合在动手写任何一行代码之前我们必须回答一个根本问题为什么选择这套组合市面上有太多替代方案——Zappa、Serverless Framework、甚至直接用 ECS Fargate。答案藏在成本、控制力和成熟度三个维度的平衡里。首先看成本。Lambda 的计费模型是“按执行时间 × 内存分配 × 执行次数”单位是毫秒。这意味着如果你的网站一天只有 100 个访客那你的计算费用可能趋近于零。RDS Aurora Serverless v2 的计费模型是“按实际使用的 ACUAurora 计算单元”它能根据负载在 0.5 到 128 ACU 之间自动伸缩最低可以缩到 0.5 ACU也就是相当于一个非常小的虚拟机。S3 存储静态文件的成本更是低到可以忽略不计。相比之下一个最小规格的 EC2 t3.micro 实例无论有没有人访问每月都要收你 $7 左右Fargate 的最小任务定义也要消耗 0.5 vCPU 和 1GB 内存成本远高于 Lambda。所以对于低流量、间歇性负载的应用这套组合在成本上具有压倒性优势。其次看控制力。Zappa 是一个优秀的工具但它是一个黑盒。它帮你做了很多事比如自动生成 CloudFormation 模板、管理 Lambda 层、处理静态文件上传。但一旦出问题你很难深入到它的内部逻辑去调试。而我们选择 Terraform是因为它把一切“显式化”了。每一个 RDS 参数、每一个 S3 存储桶策略、每一个 API Gateway 的路由规则都是一行行清晰的 HCL 代码。你可以把它放进 Git做 Code Review用 CI/CD 自动化部署甚至在本地用terraform plan预览变更。这种对基础设施的完全掌控是 Zappa 这类抽象层无法提供的。它意味着当你的业务增长需要添加 VPC 流日志、配置 WAF、或者集成 CloudWatch Logs Insights 时你不需要等 Zappa 更新而是可以直接在 Terraform 里加几行代码。最后看成熟度与生态。API Gateway 是 AWS 最成熟的 API 管理服务它提供了开箱即用的身份认证Cognito、速率限制、缓存、监控和日志。RDS Aurora 是 AWS 最成熟的托管数据库它提供了自动备份、读写分离、跨区域复制等企业级特性。S3 是全球最可靠的对象存储配合 CloudFront能提供毫秒级的全球静态资源分发。它们之间的集成是官方深度优化过的比如 API Gateway 可以直接将请求头注入 Lambda 环境变量RDS 的 Secret ARN 可以被 Lambda 直接引用S3 的事件可以直接触发 Lambda 函数。这种“原生亲和力”是任何第三方工具都无法比拟的。所以这个架构不是为了标新立异而是经过深思熟虑后在 AWS 生态内找到的、成本、控制力和可靠性三者交集最大的那个点。2.2 为什么必须用 Terraform手写 CloudFormation 不行吗这个问题我被问过无数次。答案是理论上可以但实践中会把自己逼疯。CloudFormation 的 YAML 或 JSON 模板本质上是一种“声明式”的基础设施描述语言但它缺乏编程语言的基本能力变量、循环、条件判断、模块复用。想象一下你需要为开发、测试、生产三个环境创建几乎相同的 RDS 集群只是实例类型和参数组不同。用 CloudFormation你得写三份几乎一模一样的模板只改几个参数。而用 Terraform你只需要定义一个rds_cluster模块然后在三个不同的.tfvars文件里指定instance_class db.t3.small或db.r6g.large。这就是模块化的力量。更重要的是Terraform 的状态管理State机制是它区别于所有其他工具的核心。Terraform 不是简单地“创建资源”而是维护一个“期望状态”与“实际状态”的映射。当你运行terraform apply时它会先对比当前代码描述的“期望状态”和 AWS 上已有的“实际状态”然后只执行那些必要的变更操作Create/Update/Delete。这个状态文件terraform.tfstate就是你的基础设施的唯一真相源Source of Truth。它让你可以安全地进行协作A 同事修改了 S3 的 CORS 策略B 同事同时修改了 RDS 的备份保留天数Terraform 会智能地合并这些变更而不是互相覆盖。而 CloudFormation 的 Stack更像是一个“一次性快照”你很难在多个 Stack 之间建立清晰的依赖关系和状态同步。还有一个关键点Terraform 的 Provider 生态。AWS 官方维护了一个高质量的awsProvider它几乎覆盖了 AWS 所有 200 项服务的最新 API。更重要的是它支持“数据源”Data Sources的概念。比如在我们的项目中Lambda 函数需要知道 S3 存储桶的名字但这个名字是由 Terraform 创建的是动态生成的。我们不需要在代码里硬编码而是用data.aws_s3_bucket.static_bucket这样的数据源在运行时去查询这个值然后通过output传递给 Lambda 的环境变量。这种动态的数据流是 CloudFormation 很难优雅实现的。所以Terraform 不仅仅是一个工具它是一种基础设施即代码IaC的思维方式它把运维从“手工操作”提升到了“软件工程”的层面。2.3 为什么选择 Wagtail 而不是纯 DjangoCMS 的特殊性在哪里Wagtail 是一个基于 Django 的、专注于内容管理的框架。选择它不是因为它比 Django 更好而是因为它完美地契合了 Serverless 架构的“无状态”哲学。Django 本身是一个通用 Web 框架它可以构建任何东西电商、社交网络、后台系统。但这也意味着它默认包含了大量你可能永远用不到的、有状态的组件比如 Session 框架默认依赖数据库或缓存、消息框架django.contrib.messages、甚至 Admin 后台的复杂权限系统。在 Lambda 这种短命函数里Session 的持久化就是一个大坑——你不能指望两次请求会打到同一个 Lambda 实例上所以传统的数据库 Session 会变得极其低效。Wagtail 则不同。它的核心价值在于“页面即内容”。它把网站的结构Page Models和内容Page Instances清晰地分离。所有的页面内容都存储在数据库里而页面的渲染逻辑Templates是完全无状态的。你访问/about/Wagtail 就去数据库查AboutPage这个 Model 的实例然后用about.html模板渲染出来。整个过程不依赖任何本地内存或文件系统。这使得它天然适合 Serverless每一次请求都是一个独立的、幂等的、可并行的计算单元。而且Wagtail 的 Admin 后台其核心交互也是通过 AJAX 和 RESTful API 完成的这与 API Gateway 的设计理念高度一致。当我们把 Wagtail 部署到 Lambda 上时我们实际上是在用最现代的计算范式去运行一个最古老、最可靠的内容交付模式——静态网站生成器SSG的“动态版”。它既有 SSG 的性能和安全性又有 CMS 的易用性和灵活性。所以这个项目选 Wagtail不是为了技术尝鲜而是因为它代表了内容型应用在云原生时代最自然、最高效的演进方向。3. 核心细节解析与实操要点3.1 生产环境设置12-Factor 应用原则的落地实践Django 的settings.py是整个项目的“心脏”而生产环境的设置是安全与稳定的第一道防线。原文中的production.py看似简单但每一行都蕴含着多年踩坑的经验。我们来逐条拆解。DEBUG False是铁律。在生产环境中开启 DEBUG等于把你的整个代码结构、数据库查询、甚至敏感的环境变量都暴露在 HTTP 响应头里。这是一个巨大的安全风险没有任何商量余地。SECRET_KEY从os.environ[DJANGO_SECRET_KEY]读取这是 12-Factor 应用的第一条原则“代码与配置严格分离”。这个密钥绝不能写死在代码里也不能放在 Git 中。它应该由外部环境注入。在我们的架构里这个值来自 AWS Secrets Manager。install_secrets()函数就是干这个的它在 Lambda 启动时主动去 Secrets Manager 拉取一个名为wagtaildemo_env_secret的密钥并将其内容一个 JSON 字符串解析后更新到os.environ中。这样SECRET_KEY、DJANGO_DB_PASSWORD等所有敏感信息都得到了最高级别的保护。DATABASES配置是另一个关键点。它完全依赖环境变量这保证了同一份代码可以在开发、测试、生产环境无缝切换。但这里有个陷阱HOST: os.environ[DJANGO_DB_HOST]。这个 HOST 不是 RDS 的公网地址而是它在 VPC 内部的私有 DNS 名称形如mycluster.cluster-xxxxxx.us-east-1.rds.amazonaws.com。这是因为我们的 Lambda 函数是运行在 VPC 内的它必须通过内网访问 RDS以保证安全和低延迟。如果错误地配置成了公网地址Lambda 将无法连接数据库导致整个应用崩溃。ALLOWED_HOSTS的处理方式非常巧妙。它没有写死一个域名而是从os.environ[ALLOWED_HOSTS]这个环境变量里读取一个字符串然后用urllib.parse.urlsplit(spec).hostname解析出主机名。这意味着你可以在 Terraform 里定义allowed_hosts [https://myapp.com, http://localhost:8000]然后在 Lambda 的环境变量里设置ALLOWED_HOSTShttps://myapp.com http://localhost:8000。Terraform 会自动把这个值注入而 Django 会智能地提取出myapp.com和localhost。这种方式既安全防止 Host Header 攻击又灵活支持多域名、HTTPS/HTTP。STATIC_URL的设置指向一个 S3 存储桶这是性能优化的关键。Django 的collectstatic命令会把所有STATICFILES_DIRS里的文件打包到STATIC_ROOT目录下。在 Serverless 环境中我们把这个STATIC_ROOT目录同步到 S3然后让STATIC_URL指向 S3 的 URL例如https://my-bucket.s3.amazonaws.com/s/。这样浏览器请求 CSS、JS、图片时就直接从 S3或 CloudFront获取完全绕过了 Lambda极大地减轻了 Lambda 的负载和冷启动压力。这也是为什么我们在 Makefile 里专门有一条upload-static命令。提示TEMPLATES[0][OPTIONS][context_processors].append(django.template.context_processors.static)这行代码至关重要。它确保了在 Django 模板里使用{% static css/style.css %}时能正确地拼接出 S3 的完整 URL。如果没有这行模板里生成的链接会是/static/css/style.css这显然无法工作。3.2 Lambda 入口模块lambda_function.py的精妙设计这个文件是整个 Serverless Django 的“大脑”它的设计体现了对 Lambda 生命周期的深刻理解。我们来剖析它的三个核心函数。hello(event, context)是一个“健康检查”函数。它不涉及任何 Django 初始化只做最轻量的操作检查是否需要安装密钥。它的存在是为了在部署后快速验证 Lambda 的基础环境是否正常。你可以用 AWS 控制台的 Test 功能传入{}如果返回{message: Hello from the Wagtail Lambda Demo}就说明 Python 解释器、包导入、甚至基本的 boto3 客户端都工作正常。这是一个极佳的“分而治之”的调试策略先确保底层没问题再往上叠加复杂度。manage(event, context)是 Django 的“远程控制台”。它允许你通过 Lambda 的 Test 功能直接执行任意的 Django 管理命令。传入migrate它就会执行数据库迁移传入createsuperuser --no-input --username admin它就会创建管理员用户。这个设计的精妙之处在于install_secrets()的调用时机。它在management.call_command之前被调用确保了在 Django 初始化数据库连接时DJANGO_DB_PASSWORD等环境变量已经就位。这避免了在get_wsgi_application()时就因缺少密钥而崩溃。lambda_handler(event, context)是真正的“主入口”。它的核心是apig-wsgi库。这个库的作用是充当 API Gateway 和 Django 之间的“翻译官”。API Gateway 发来的事件是一个巨大的 JSON 对象里面包含了 HTTP 方法、路径、查询参数、请求头、Body 等。apig-wsgi会把这些信息按照 WSGI 规范组装成environ字典和start_response函数然后调用application(environ, start_response)。这里的application就是get_wsgi_application()返回的 Django WSGI 应用对象。整个过程对 Django 完全透明。最关键的一行是_real_handler make_lambda_handler(application)。make_lambda_handler会创建一个“处理器”这个处理器会缓存application的初始化结果。因为get_wsgi_application()本身是一个相对昂贵的操作它会加载所有 Django App、初始化数据库连接池虽然我们用的是连接池但在 Lambda 里每次请求都可能是新的进程所以连接池的意义不大但初始化本身仍有开销。通过将_real_handler缓存在模块全局变量里我们实现了“冷启动优化”第一次请求会慢一点因为要初始化 handler但后续的请求只要 Lambda 实例还在就可以直接复用这个 handler从而获得接近热启动的性能。注意lambda_venv_path模块的引入顺序是强制的。它必须是lambda_function.py里的第一行import。这是因为lambda_venv_path.py的作用是修改sys.path把我们 Docker 构建时安装的所有 Python 包的路径提前加到sys.path的最前面。这样当后续import django或import wagtail时Python 解释器才能在正确的路径下找到它们。如果这个 import 被放到后面那么在get_wsgi_application()时Django 就会因为找不到依赖而报错。3.3 Docker 构建流程为什么必须用 Amazon Linux 2Dockerfile 是整个构建流程的“配方”它的每一行都经过了反复验证。我们来解读其中最关键的几处。FROM amazonlinux:2.0.20210219.0 AS build-stage这行指定了基础镜像。为什么是amazonlinux而不是更流行的python:3.8-slim因为 AWS Lambda 的执行环境就是基于 Amazon Linux 2 的。它的内核版本、glibc 版本、SSL 证书库都与 Amazon Linux 2 完全一致。如果你用python:3.8-slim基于 Debian那么你在本地构建出来的psycopg2-binary包很可能因为 glibc 版本不兼容在 Lambda 上运行时报错ImportError: libpq.so.5: cannot open shared object file。这是一个经典的“构建环境与运行环境不一致”导致的问题。用 Amazon Linux 2就从根本上杜绝了这种兼容性风险。RUN yum install -y python38-devel这行安装了 Python 的开发头文件。这是为了后续pip install一些需要编译 C 扩展的包比如psycopg2所必需的。psycopg2-binary是一个预编译的二进制包但它仍然需要底层的libpq库而这个库在 Amazon Linux 2 上是通过postgresql13这个 yum 包提供的。所以yum install postgresql13也是必不可少的。RUN INSTBASE$INSTBASE venv/bin/python -c import os; import sys; instbase os.environ[INSTBASE]; print(import sys; sys.path[:0] %s % [p for p in sys.path if p.startswith(instbase)]) $INSTBASE/lambda_venv_path.py这行是整个构建流程的“点睛之笔”。它动态生成lambda_venv_path.py文件。这个文件的内容是一个 Python 语句它会把所有以/var/task开头的sys.path条目插入到sys.path的最前面。为什么需要这个因为在 Docker 构建时我们把所有东西都装进了/var/task这个目录。但 Lambda 的运行时并不会真的激活这个虚拟环境venv它只是把 zip 包解压到/var/task然后直接运行lambda_function.py。所以Python 解释器默认的sys.path里并不包含/var/task下的包。lambda_venv_path.py就是来“欺骗”解释器的让它以为自己正在一个正常的 venv 里运行。这是一种非常聪明的、轻量级的“环境模拟”。COPY指令的顺序也很有讲究。我们先把requirements.txt和lambda_function.py复制进去然后pip install。这样做的好处是Docker 的构建缓存机制会生效只要requirements.txt没变pip install这一层就不会重新执行大大加快了后续的构建速度。而blog,home,search,mysite这些应用代码是最后才COPY的因为它们变化最频繁。实操心得在lambda.dockerfile的最后我们用了FROM scratch AS export-stage。scratch是一个空的、0 字节的基础镜像。我们只从build-stage阶段拷贝/tmp/lambda.zip这一个文件过去。这样做有两个巨大好处一是最终输出的lambda.zip文件绝对干净不包含任何构建时的中间产物比如venv/bin下的可执行文件二是构建过程完全隔离build-stage里安装的gcc、make等编译工具一个字节都不会出现在最终的 zip 包里最大限度地减小了部署包的体积从而加快了 Lambda 的冷启动速度。4. 实操过程与核心环节实现4.1 从零开始本地 Wagtail 站点搭建与验证在动手写任何云上的代码之前我们必须确保本地的 Django/Wagtail 应用是健康的。这一步看似简单却是整个项目成功与否的基石。我建议你严格按照 Wagtail 官方的 “Your first Wagtail site” 教程来操作但要注意几个关键细节。首先创建一个干净的 Python 虚拟环境。不要用系统 Python也不要跳过venv。命令如下mkdir wagtail_lambda_demo cd wagtail_lambda_demo python3 -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows激活环境后pip install wagtail。注意这里安装的是 Wagtail 的最新稳定版而不是某个特定版本。Wagtail 的版本迭代很快但它的向后兼容性做得非常好所以我们不必锁定版本让pip自动选择最佳版本即可。接下来按照教程创建项目。wagtail start mysite会生成一个标准的 Django 项目骨架。然后cd mysitepip install -e .将当前目录以“可编辑模式”安装这样你对代码的任何修改都会立即生效无需重新安装。最关键的一步是数据库迁移和创建超级用户。运行python manage.py migrate然后python manage.py createsuperuser。此时你已经有了一个可以登录的后台。运行python manage.py runserver打开浏览器访问http://127.0.0.1:8000/admin/用你创建的用户名和密码登录。你应该能看到 Wagtail 的标志性“鸟图标”在右下角。点击它就能进入页面编辑界面。创建一个简单的“首页”Home Page发布它然后访问http://127.0.0.1:8000/你应该能看到一个空白但结构正确的页面标题是 “Home”。这证明了 Django 的 URL 路由、模板渲染、数据库读取全部工作正常。提示在本地开发时DEBUGTrueALLOWED_HOSTS[*]数据库用 SQLite。这些都是为了开发便利。但请务必记住这些设置绝不能出现在生产代码中。我们会在下一步的production.py里用一套完全不同的、安全的配置来覆盖它们。这种“开发与生产配置分离”的思想是专业 Django 开发的起点。4.2 Terraform 部署从代码到云资源的魔法之旅Terraform 的部署是整个流程中最激动人心也最容易出错的环节。它把一堆文本文件.tf变成了真实运行在 AWS 上的数据库、存储桶、函数。让我们一步步来。首先确保你的本地环境已准备好。你需要安装Terraform CLI推荐用tfenv管理多个版本AWS CLIpip install awscli配置好 AWS 凭据aws configure输入你的 Access Key ID 和 Secret Access Key并选择一个默认的region比如us-east-1然后进入tf目录即存放所有.tf文件的目录。第一步永远是terraform init。这个命令会下载所有你代码中用到的 Provider主要是aws并初始化本地的状态文件。如果这一步失败90% 的原因是网络问题因为 Provider 插件是从 HashiCorp 的官方仓库下载的。你可以尝试配置代理或者检查你的网络连接。第二步是terraform plan。这是 Terraform 最强大的功能之一。它会分析你的代码然后告诉你“如果我现在执行将会创建哪些资源、修改哪些资源、销毁哪些资源”。它会生成一个详细的、彩色的执行计划。在你敲下terraform apply之前务必仔细阅读这个计划确保它只创建了你期望的资源没有意外的删除或修改。这是一个“预演”是防止灾难性误操作的最后一道保险。第三步terraform apply。它会执行plan中描述的所有操作。这个过程可能需要几分钟因为 RDS 集群的创建是最耗时的。当它完成时你会看到类似这样的输出Apply complete! Resources: 24 added, 0 changed, 0 destroyed. Outputs: app_endpoint https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/ init_superuser_password xxxxxxxxxx static_bucket wagtaildemo-static-bucket-xxxxxxxxxx这些Outputs就是你后续所有操作的“钥匙”。app_endpoint是你的网站地址init_superuser_password是管理员密码static_bucket是 S3 存储桶的名字。但请注意terraform apply并不会自动完成数据库的初始化。这是整个流程中一个关键的手动步骤也是最容易被忽略的“断点”。Terraform 创建了 RDS 集群但它无法自动登录到数据库里去创建应用用户和数据库。这是因为 RDS 集群位于 VPC 的私有子网中而 Terraform 的执行环境你的本地电脑并不在那个 VPC 里所以它无法直接连接数据库。解决方案是利用 AWS 控制台的“Query Editor”。你需要打开 AWS Secrets Manager找到名为wagtaildemo_rds_master_credentials的密钥复制它的 ARN。打开 AWS RDS 控制台找到你刚创建的集群点击它然后在右上角点击 “Query editor”。在 Query Editor 的登录框里选择 “Connect with a Secrets Manager ARN”粘贴刚才的 ARN数据库名填postgres点击连接。连接成功后你会看到一个 SQL 查询窗口。在这里执行两条 SQL 语句CREATE ROLE appuser WITH PASSWORD your_actual_db_password LOGIN INHERIT; CREATE DATABASE appdb OWNER appuser;其中your_actual_db_password就是wagtaildemo_env_secret密钥里DJANGO_DB_PASSWORD的值。你可以在 Secrets Manager 里找到它。这一步完成后RDS 就准备好了。你可以回到终端再次运行terraform apply。这一次Terraform 会检测到数据库已经存在不会再尝试创建而是继续完成它剩下的工作比如配置 Lambda 的环境变量、设置 API Gateway 的路由等。实操心得我第一次部署时在terraform apply后直接去访问app_endpoint结果是一个空白页。我花了两个小时排查代码、Docker 构建、Lambda 日志最后才发现是数据库没初始化。所以请牢记Terraform 的apply是一个“半成品”它创建了基础设施但业务数据的初始化需要你手动介入。这是一个典型的“基础设施即代码”与“数据即代码”尚未完全融合的体现。未来我们可以用 AWS RDS Data API 或者一个专门的“初始化 Lambda 函数”来自动化这一步但目前手动操作是最稳妥的。4.3 静态资源发布与 Lambda 函数调用让网站真正活起来Terraform 部署完成后你的网站还只是一个“空壳”。它没有静态文件也没有数据库表结构更没有管理员用户。现在我们要用 Makefile 这个“自动化指挥官”来完成最后的“激活”步骤。首先make upload-static。这条命令会触发一系列操作rm -rf static ../venv/bin/python manage.py collectstatic --no-input清空本地的static目录然后运行 Django 的collectstatic命令把所有STATICFILES_DIRS里的文件包括wagtail自带的 CSS/JS都收集到static目录下。aws s3 sync static s3://$(shell cd tf; terraform output -raw static_bucket)/s --exclude staticfiles.json --delete这是最精彩的一行。$(shell cd tf; terraform output -raw static_bucket)是一个 Shell 命令替换它会先进入tf目录然后运行terraform output -raw static_bucket从而动态地获取 Terraform 输出的static_bucket值。aws s3 sync会把这个static目录高效地同步到 S3 存储桶的/s前缀下。--exclude staticfiles.json是为了避免把 Django 生成的staticfiles.json用于 ManifestStaticFilesStorage也上传因为我们用的是S3Boto3Storage它有自己的哈希机制。--delete选项则确保 S3 上任何在本地static目录里不存在的文件都会被删除保持两边完全一致。执行完这条命令后你的 CSS、JS、图片就已经躺在 S3 里了。你可以直接在浏览器里访问https://your-bucket-name.s3.amazonaws.com/s/css/wagtail-admin.css来验证。接下来是调用 Lambda 函数来初始化业务逻辑。打开 AWS Lambda 控制台找到wagtaildemo_manage这个函数。点击 “Test”在弹出的窗口里选择 “Create new event”然后在 JSON 编辑器里输入migrate点击 “Test”等待几秒钟。如果一切顺利你会看到一个成功的响应里面可能包含Operations to perform:这样的日志。这表示 Django 的数据库迁移已经成功执行所有models.py定义的表都已经在 RDS 里创建好了。然后再次点击 “Test”这次输入createsuperuser --no-input --username admin同样等待响应。成功后你就有了一个用户名为admin的超级用户。最后访问app_endpoint。第一次访问可能会比较慢冷启动但你应该能看到 Wagtail 的首页。在 URL 后面加上/admin/用admin用户名和init_superuser_password登录。恭喜你一个完全运行在 Serverless 架构上的 Django/Wagtail 网站正式上线了注意在wagtaildemo_manage函数的配置里有一个关键的设置Timeout。由于数据库迁移可能需要较长时间尤其是第一次要创建几十张表我建议把这个超时时间设置为 300 秒5 分钟而不是默认的 3 秒。否则迁移还没完成Lambda 就会被强制终止导致数据库处于不一致的状态。5. 常见问题与排查技巧实录5.1 冷启动与 API Gateway 30 秒超时如何优雅地应对“首屏白屏”这是 Serverless Django 最经典、最令人抓狂的问题。当你第一次访问app_endpoint时页面一片空白Network 面板显示一个 504 Gateway Timeout 错误而 Lambda 的日志里却显示“请求已成功处理”。这背后是 API Gateway 和 Lambda 之间一个微妙的“时间差”。原因很简单API Gateway 的默认超时时间是 30 秒。而 Lambda 的冷启动时间取决于几个因素代码包大小、内存分配、以及最重要的——数据库连接初始化时间。Aurora Serverless v2 在从 0.5 ACU 扩展到足够处理请求的 ACU 时需要大约 30-40 秒。也就是说Lambda 函数本身可能在 35 秒后才真正开始执行lambda_handler但 API Gateway 在 30 秒时就已经放弃了并返回了 504。这不是 Bug而是两个服务的设计约束。AWS 明确表示不会提高 API Gateway 的 30 秒上限因为它是为高并发、低延迟的 API 设计的而不是为长任务设计的。解决方案有三个层次预防性优化推荐在 Terraform 中为 RDS 集群配置一个合理的min_capacity。不要让它缩到 0.5 ACU而是设为2或4。这样即使在深夜无人访问时数据库也会保持一个最小的计算能力确保冷启动时的扩展时间缩短到 5 秒以内。这会略微增加你的月度账单大概 $1-2但换来的是完美的用户体验。架构性规避进阶用 AWS Application Load Balancer (ALB) 替代 API Gateway。ALB 的超时时间可以设置为 3600 秒1 小时完全足够应对任何冷启动。但 ALB 有固定的每小时费用约 $0.025并且需要额外的配置如 Target Group、Health Check。对于一个小型项目这笔固定开销可能不划算但对于一个需要 SLA 保障的