用 ESA 边缘函数把静态站点推到离用户最近的地方
边缘函数这个名字容易让人以为它只能写动态逻辑。实际上对一个内容不常变的站点来说,它更像是一个不需要运维的、全球分布的 Node 运行时。
我为什么会走到这条路
起初我的需求非常小:一个静态页面,绑上自己的域名,能正常访问就行。最直接的做法是在 ECS 上装 Nginx 把文件扔进去。但很快发现两个问题——一是 HTTPS 证书要自己续期,二是源站 IP 会直接暴露在公网,任何扫描都会打到机器上。
ESA 这类边缘加速产品同时解决了这两件事:证书由平台签发和续期,回源只对边缘节点开放。而它的「边缘函数」还提供了第三种可能——干脆不要源站。
三种部署形态的差别
| 形态 | 需要源站 | 内容更新方式 | 适合场景 |
|---|---|---|---|
| 仅 DNS 解析 | 是(ECS) | 改服务器上的文件 | 备案核查期、需要固定出口 IP |
| 加速回源 | 是(ECS / OSS) | 改源站 + 刷新缓存 | 有大文件、需要隐藏源站 |
| 边缘函数直出 | 否 | 发布新版本代码 | 内容以文本为主、更新不频繁 |
第三种最有意思:请求在边缘节点就被函数接住并返回,根本不产生回源。对一个博客来说,这意味着没有需要维护的机器。
函数怎么把一个请求接住
边缘函数的模型接近 Cloudflare Workers:你注册一个处理 fetch 事件的监听器,自己构造 Response。
addEventListener('fetch', event => {
event.respondWith(handle(event.request))
})
async function handle(request) {
const path = new URL(request.url).pathname
if (path === '/') {
return new Response(INDEX_HTML, {
headers: { 'content-type': 'text/html; charset=utf-8' }
})
}
// 文章正文放 KV 里,避免每篇都编译进函数代码
const post = await KV.get('post:' + path.slice(6))
if (post) return new Response(render(post), {
headers: { 'content-type': 'text/html; charset=utf-8' }
})
return new Response('Not Found', { status: 404 })
}
把 HTML 模板写在代码里、正文放在 KV 里,是一个很舒服的分界:样式改动要发版,内容改动只需写 KV,秒级生效。
踩到的三个坑
一、函数创建了,但域名访问仍然 404
函数本身不会自动接管任何域名。它需要一个「关联记录」把站点的某条 DNS 记录绑上去,请求才会进入函数。这个关联动作很容易漏,漏了的表现就是域名压根解析不到函数。
# 把站点记录绑定到函数
aliyun esa CreateRoutineRelatedRecord \
--Name my-app --SiteId <siteId> --RoutingRule '<match-rule>'
二、默认测试域名不能当生产域名用
每个函数创建时会带一个平台分配的默认域名,形如 xxx.<hash>.er.aliyun-esa.net。它只适合调试,直接对外访问并不稳定,正式的站点还是要绑自己的域名。
三、国内覆盖必须先备案
如果加速区域选择中国内地,平台会校验域名的备案状态。没备案的域名只能选海外或全球,否则站点起不来。
这条路的代价
边缘函数不是万能的。它的执行有 CPU 时间上限,不适合重计算;冷启动行为和 Node 不一样,不能假定有本地磁盘;而且最重要的一点——它是运行时生成 HTML,构建期没有任何检查,链接写错了只能靠访问时才发现。
所以对我现在这个规模,我最终选了最朴素的方案:静态文件 + Nginx + 仅 DNS 解析。把边缘函数留给确实需要动态逻辑的场景。
不过这几种形态之间的切换成本很低,主要就是改一条 DNS 记录。等站点的核查期过去,想要加速随时可以把记录改成代理模式。
← 返回文章列表