不得不承认,博客是 IT 社区技术交流的重要途径。但是许多人的博客,其实并没有多少访问量,比如这一个。作为一个贫穷的人,我自然心疼每月十几甚至几十块钱的服务器费用,被白白地浪费在一个没有人看的博客上。于是我构建这个博客的时候,采取了无服务器的设计,以便尽可能地压缩投入。

什么是无服务器运算?

无服务器计算是一种基于函数的云计算服务。用户将逻辑写在函数中,并为函数指定 HTTP 请求、定时器、队列等触发器。当触发器条件满足时,相应的函数就会被触发,逻辑就被执行。

现在,国内外的公有云平台大都提供了这类服务。它们多按函数的执行次数与时间计费,并且拥有不小的免费额度。另外,无服务器计算的调度单位是函数,粒度非常细,这意味着我不需要为任何闲置资源付费。因此,使用无服务器架构,能节省大量运营成本。有些读者可能听说过 HaveIBeenPwned, 一个可以检查密码是否泄露的工具。这个网站便采用了无服务器架构,使得其月托管成本不超过个位数美元

无服务器架构的好处不仅仅在于省钱,它还可以带来很好的伸缩性。这些函数运行在极小的容器中,可以在极短的时间 (毫秒量级) 内部署启动,带来更低的伸缩延迟与成本。如果哪天这个博客爆红,我完全不用考虑升级服务器或者多实例负载均衡的问题。我不需要做任何更改,只需要比原来多掏点钱就是了。

当然,作为 PaaS 服务 *(也有人将无服务器视为比 PaaS 更高一级,但在 SaaS 之下的单独一层抽象)*,它也继承了面向平台开发的固有弊端,即难以在平台之间迁移。我今天托管在 Azure 上的函数,若不经过修改,必然是无法在 AWS 或其它平台上运行的。

另外,无服务器计算是为细粒度、轻量级的任务设计的。它不适合用来进行长时间的运算。这种情况下,我们有足够的需求来充分利用专用服务器所提供的计算资源,无服务器并不会为我们带来多少好处,甚至可能成本更高。

最后,要最大程度地享受上述优势,我们需要使用云服务提供商的服务器池来运行函数。由于服务器池里面的每台服务器都运行大量的函数,因此不能为每个用户都分配单独的 IP. 这就意味着这种情况下,如果我们想要使用自定义的域名,就必须在配置 DNS 时使用 CNAME 记录,而非 A 记录。而又因为域名 Apex 不能指定 CNAME, 这意味着我们不能使一个二级域名指向服务器池中的函数。

不过,如果使用得当,无服务器运算还是能带来巨大的好处。对于这个博客来说,无服务器的好处就在于我可以真正做甩手掌柜,既不用付出多少钱财,也不用付出多少精力。关于域名,我在这里使用一个三级域名来绕过这一限制。

这个博客是怎么来的?

这个博客使用 Hexo 搭建。Hexo 是一个非常好用的工具,可以从 Markdown 文件生成静态博客网页。关于 Hexo 的使用,可以参考他们的文档,这里我不再详述。不过需要提及的是 Hexo 里面的部署命令。这一命令可以将生成的网站推到某个 Git 仓库中。于是,当我写完博客并执行部署时,博客的内容便会被推入本人托管在 Azure DevOps 上的一个 Git 仓库中。

这个项目中同时还有一条流水线,当博客文件被推入上述仓库时,仓库中的文件便会自动地上传到一个 Azure 块存储中。你现在看到的这个网页,便是从块存储中获得的。

最后,利用 Azure Functions 做一个反向代理,将来自读者的请求定向到块存储中的资源。这个博客便部署完成了。至此,只要我写完博客,在 Hexo 中执行一下部署命令,其余的就可不用我干预了。

本博客的架构如上文所述

给我也整一个!

部署一个这样的博客并不难。如上一篇文章所述,我是微软的死忠,于是这里使用的多数是微软技术。大家利用相似的工具链,也可以实现类似的效果。

首先需要在 Azure DevOps 上创建好一个项目。这个项目会托管博客的远程仓库和流水线。然后在本地计算机上配置好 Git, 使得 Git 能与远程仓库顺利地通信。

接下来初始化一个 Hexo 博客,并且配置 _config.yml 中的部署选项,使其能自动地将生成站点推到远程仓库:

1
2
3
4
5
6
# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type: git
repo: https://git.example.com/uri/to/git/repo # 远程仓库的地址
branch: master # 目标分支

接下来在 Azure 中创建一个存储账户。该存储账户中开启静态网站功能,并注意设置默认页面为 index.html, 因为 Hexo 中生成的网页文件名称如此。这会在存储账户中创建一个名为 $web 的块容器。所有通过这一存储账户 URI 直接访问的请求,都视为要访问 $web 容器中的相应资源。为了测试这一点,可以在这一容器的根中放一个简单的 index.html 文件,然后直接访问存储账户的 URI.

接下来配置流水线。在 Azure DevOps 项目中,创建一个流水线。这里使用流水线自带的工具将仓库内容上传到 Azure 块存储中。上传时,默认只会上传新增或更改过的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml

trigger:
- master

pool:
vmImage: 'windows-latest'
# 注意目前这一工具只支持 Windows PowerShell 的运行环境。
# 如果不希望使用 Windows, 也可以考虑使用流水线的其它工具,
# 不过要进行一些相应配置。大家可参考文档。

steps:
- task: AzureFileCopy@3
inputs:
SourcePath: '$(Build.Repository.LocalPath)'
azureSubscription: 'Pay-As-You-Go(00000000-0000-0000-0000-000000000000)'
# 以上填块存储所在订阅的信息
Destination: 'AzureBlob'
storage: 'nameofstorageaccount'
# 以上填存储账户的名称
ContainerName: '$web'

此处有一个坑。因为 Azure 流水线的配置文件直接存储在仓库里面,而 Hexo 在进行部署操作时,这一文件会丢失,导致流水线无法启动。因此,我们需要设法将这一配置文件包含在 Hexo 的生成文件中,使其在部署时一并推入仓库。Hexo 有不少插件可以实现这一点,我们这里使用这一插件。它会在生成时,将 Hexo 工作目录里 ./source/_static 目录中的文件不经渲染直接复制到生成目录 ./public 中。我们在必要配置后,将流水线的配置文件放在 _static 目录中就好了。

最后配置反向代理。在 Azure 中创建一个函数资源,并新建一个代理。代理的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"$schema": "http://json.schemastore.org/proxies",
"proxies": {
"BlogRequest": {
"matchCondition": {
"route": "/{*path}"
},
"backendUri": "https://%BLOG_STORAGE%/{path}"
// 这里使用 %BLOG_STORAGE% 环境变量来指定存储账户的 URI.
// 需要在函数资源的 Configuration 页面配置这一环境变量。
}
}
}

至此,这个博客便基本可用了。各位可以在 Hexo 中尝试部署一次,测试部署是否成功,流水线是否可用,站点是否可以访问。此外,还可以对这一函数资源作其它配置,例如添加自定义的域名,配置 SSL 证书等,这里不再详述。