1. 项目概述:为什么一个 Django 网站值得被“塞进” Lambda?
你有没有过这种体验:辛辛苦苦用 Django 搭建了一个内容管理系统,本地跑得飞起,部署到服务器上却开始掉链子——数据库连接池配错、静态文件路径不对、环境变量漏传、Nginx 配置改了八遍还是 502……最后发现,真正花时间的不是写业务逻辑,而是和运维细节死磕。我干这行十多年,从最早手动配 Apache 到后来用 Ansible、Docker,再到今天,终于在 AWS Lambda 上把一个完整的 Wagtail(Django 的 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 的计费模型是“按实际使用的 ACU(Aurora 计算单元)”,它能根据负载在 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 而不是纯 Django?CMS 的特殊性在哪里?
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_HOSTS="https://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 2?
Dockerfile 是整个构建流程的“配方”,它的每一行都经过了反复验证。我们来解读其中最关键的几处。
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 mysite,pip 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 路由、模板渲染、数据库读取,全部工作正常。
提示:在本地开发时,
DEBUG=True,ALLOWED_HOSTS=['*'],数据库用 SQLite。这些都是为了开发便利。但请务必记住,这些设置绝不能出现在生产代码中。我们会在下一步的production.py里,用一套完全不同的、安全的配置来覆盖它们。这种“开发与生产配置分离”的思想,是专业 Django 开发的起点。
4.2 Terraform 部署:从代码到云资源的魔法之旅
Terraform 的部署是整个流程中最激动人心,也最容易出错的环节。它把一堆文本文件(.tf),变成了真实运行在 AWS 上的数据库、存储桶、函数。让我们一步步来。
首先,确保你的本地环境已准备好。你需要安装:
- Terraform CLI(推荐用
tfenv管理多个版本) - AWS CLI(
pip 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 保障的