抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言

假定我们有一个 nginx 反代服务,其大概配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 443 ssl;
ssl_certificate xxx.cer
ssl_certificate_key xxx.key
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
server_name xxx.com;

location /someDynamicService/ {
proxy_pass http://127.0.0.1:12345;
}

location / {
alias /dir/of/files;
}
}

显然,这个nginx实现了两个效果:

  1. 访问路径为 /someDynamicService/ 时反代到内部位于 12345 端口的服务
  2. 其他访问路径时相当于访问 /dir/of/files 路径下的文件

但是这种部署方式有一些问题,首先是这种单机的部署不是很安全,这台机子挂了就停止服务了。其次这种部署需要维护的内容也比较多,比如说 /someDynamicService/ 下使用的是 docker 来运行一个 node.js 服务,改这个服务的代码需要重新部署 docker container;而且还需要配 acme bot 去更新 SSL 证书等等。我还发现经常有一些脚本小子去扫路径。

那么这个时候 Cloudflare workers 就进入了我的视野。尤其是他那句 Deploy To Earth 还是有点震撼的。

部署过程

我们的目标是提供与原本 nginx 反代 simultaneous 的体验,也就是使用同一个域名,依照不同的 path 设置来路由到不同的服务。这里具体拆分为了以下几个 workers 及其附属品,并一一解释设计的思路/原因。

SomeDynamicService

首先是这个动态服务,将其作为单独的一个 worker 的好处是之后的变更我只需要单独 commit + push,就可以用 GitHub 钩子将新版本的代码部署上去,而不影响其他服务。

VariableFiles

有一些被分享的文件其实是一些经常会被修改的资源,通常是一些配置文件,对于这些文件为了方便后续的更新维护以及记录变更方便回滚,同时也是出于自身文件大小不大的关系,我选择单独建立一个仓库,采用 commit + push + GitHub 钩子的方式将其部署到单独的 worker 上。

StaticFiles

更多的一些被分享的文件其实是较大的静态文件,比如工具包、备份的一些文件等等,这类文件很少被改动,因此可以搭配 Cloudflare 的 R2 存储来使用。这里单独新建一个 worker,并且实现 PUT, GET , DELETE 相关接口,就可以做一个自己的小型 OSS 了。

OSS 可以参考 Github

Router

那么最后要做的事情就是组装起这些 worker 了,这里正好使用了多种方式去做路由,且各有各的原因。

Workers Routes

首先对于 SomeDynamicService 这个服务,我没有给 worker domain,包括自定义的和 CloudFlare 自动提供的,因为没有必要将其作为单个服务队外暴露。对于这种情况,直接去 CloudFlare 的 Workers Routes 里面配置一下即可。

Route Worker
xxx.com/someDynamicService/
SomeDynamicServiceWorker

Router Code

VariableFiles 的服务是字节 assign 了一个自定义的 domain 的,因此它可以直接被外部访问。在这个情况下,则需要用代码方式创建路由

在 router worker 上链接 StaticFiles 的 R2 存储,对于请求,优先查询 R2 中是否存在这个对象,如果不存在,则直接请求 VariableFiles 的 worker ,完成整套流程,最终错误处理交给 VariableFiles。

以下是大致的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
async function handleRequest(request) {

let url = new URL(request.url);
const key = url.pathname.slice(1);

switch (request.method) {
case 'GET':
const object = await ${R2}.get(key);

if (object === null) {

url.hostname = ${VariableFiles.url};
url.protocol = 'https';

let newRequest=new Request(url,request);
return fetch(newRequest)
}

const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('etag', object.httpEtag);

return new Response(object.body, {
headers,
});

default:
return new Response('Method Not Allowed', {
status: 405,
headers: {
Allow: 'GET',
},
});
}
}

评论