让 Google CDN 使用 Custom Origin(NEG) —— 不再受限于 GCP 机器

在之前的文章——「让博客变得更快——Google Load Balancer 和 Google CDN 使用小记」中,为了使用到 Google CDN,我们必须使用到 Google Load Balancer + GCP 上的 Instance,大致结构如下:

这样互联网的流量便是:用户 -> Google CDN 边缘节点 -> Google Load Balancer -> GCP Instance -(Wireguard)-> 实际的后端应用

虽然 Google CDN 很快,但是在这样一个网络结构下我们需要单独配置 GCP Instance 上的转发和到实际应用服务器上的隧道,增加了维护成本和机器费用。

无独有偶,前两天在排查 CDN 上的一个请求超时问题的时候发现 GCP 文档上多了这么一篇文章 Internet network endpoint groups overview

A network endpoint group (NEG) defines a set of backend endpoints for a load balancer. An internet NEG is a backend that resides outside of Google Cloud.

You should do this when you want to serve content from an origin that is hosted outside of Google Cloud, and you want your external HTTP(S) load balancer to be the frontend.

那还有什么好说的,直接上呗!

Create GCP CDN with NEG

首先是根据文档 Setting up a load balancer with a custom origin,创建一个 Custom Origin,这里需要填写端口信息,类比 Cloudflare 的 Full SSL,如果希望 Google 回源的时候使用 SSL 的话,填写 443,类似下图(图中 123.34.44.11 指的是源站 IP):

在这种情况下,GCP CDN 不会对源站的 SSL 进行检查(类似 Cloudflare 的 Full SSL),所以源站可以使用自签 SSL,如果希望对源站 SSL 检查的话,可以填写一个 FQDN(注意,这里的 FQDN 必须是 Google DNS 可以解析的地址)。

Configure Origin

配置源站也非常简单,这里给个 Example:

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        ssl_certificate /etc/nginx/ssl/ssl.pem;
        ssl_certificate_key /etc/nginx/ssl/ssl.key;
        server_name nova.moe;
        root /path/to/nova.moe;
        index index.html;

        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";

        if ($http_x_forwarded_proto = "http") {
            return 301 https://$host$request_uri;
        }
        absolute_redirect off;
}

Misc

Tuning

文章最初提到的超时问题表现情况如下:

  • 访客通过 CDN 访问一个地址,稳定在 60s 的时候返回 502
  • 后端服务器对应访问记录状态码 499

首先需要修改 Nginx 的 keepalive 时间(默认只有 75s),根据 External HTTP(S) Load Balancing overview 的建议,加入以下行:

keepalive_timeout 620s;

对于 499 的问题,加入如下后解决:

proxy_ignore_client_abort on

Speed/TTFB Compare

有了成功的尝试之后我们就可以对比一下这种模式的效果如何了,直接上对比图,其中从左到右分别是:

  1. Google CDN + GCP Instance(Osaka)
  2. Google CDN + NEG(Linode 东京 2)
  3. Cloudflare CDN + Vultr 日本

可以看到,在使用 NEG 的情况下,由于没有额外的 WG 隧道 + GCP 机器转发,TTFB 时间稳定减少了一些,且由于大部分流量在 Google 内网内,TTFB 时间均值远好于 Cloudflare 的直接源站反代回源方式。

回源策略

在之前的模式中,GCP 回源 IP 只有以下两个段:

  • 35.191.0.0/16
  • 130.211.0.0/22

在 NEG 的情况下,回源 IP 段是动态的,可以通过以下命令来获得:

dig TXT _cloud-eoips.googleusercontent.com | grep -Eo 'ip4:[^ ]+' | cut -d':' -f2

比如本文写作时得到的 IP 段是:

  • 34.96.0.0/20
  • 34.127.192.0/18

经过测试,在使用 NEG 的情况下,Google CDN 和 Cloudflare + Argo 回源策略类似,即尽量通过 Google 内网的优化线路回到源站(例如:荷兰访客 -> Google CDN 荷兰节点 -(尽量通过 Google 内网)-> 日本 GCP -> 日本源站),举例如下,这是两个 Nginx 源站服务器上的 Nginx 日志:

34.96.7.32 - - [05/Mar/2021:13:58:47 +0800] "HEAD / HTTP/1.1" 200 0 "https://nova.moe" "Mozilla/5.0+(compatible; UptimeRobot/2.0; http://www.uptimerobot.com/)"
34.96.5.143 - - [05/Mar/2021:14:00:01 +0800] "GET /feed/ HTTP/1.1" 200 304945 "https://nova.moe/feed/" "FreshRSS/1.17.0 (Linux; https://freshrss.org)"

分別 tracepath 结果如下(从 Linode 日本):

linode ~ # nali-tracepath 34.96.7.32
 1?: [LOCALHOST]                      pmtu 1500
 1:  139.162.65.3 [日本 东京都品川区 Linode 数据中心]                                          0.770ms 
 1:  139.162.65.3 [日本 东京都品川区 Linode 数据中心]                                          2.776ms 
 2:  139.162.64.14 [日本 东京都品川区 Linode 数据中心]                                         0.400ms 
 3:  72.14.196.114 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                         0.498ms 
 4:  108.170.242.144 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                       1.233ms 
 5:  209.85.242.235 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                        1.510ms 
 6:  ???                                                   3.663ms asymm  8 
 7:  ???                                                  89.380ms asymm  9 
 8:  209.85.250.4 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                        113.612ms asymm 10 
 9:  72.14.239.159 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                       129.022ms asymm 11 
10:  209.85.250.37 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                       132.117ms asymm 12 
11:  ???                                                 135.119ms asymm 13 
12:  216.239.40.171 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                      139.933ms 
13:  216.239.50.17 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                       133.778ms 
14:  no reply
15:  no reply
16:  no reply
17:  no reply
18:  no reply
19:  no reply
20:  no reply
21:  no reply
22:  no reply
23:  32.7.96.34 [美国].bc.googleusercontent.com                 133.120ms reached
     Resume: pmtu 1500 hops 23 back 24 
linode ~ # nali-tracepath 34.96.5.143
 1?: [LOCALHOST]                      pmtu 1500
 1:  139.162.65.2 [日本 东京都品川区 Linode 数据中心]                                          3.558ms 
 1:  139.162.65.2 [日本 东京都品川区 Linode 数据中心]                                          1.604ms 
 2:  139.162.64.30 [日本 东京都品川区 Linode 数据中心]                                         0.771ms 
 3:  72.14.196.114 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                         0.526ms 
 4:  108.170.242.176 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                       0.863ms 
 5:  no reply
 6:  no reply
 7:  209.85.246.133 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                       49.685ms asymm  9 
 8:  209.85.250.118 [美国 加利福尼亚州圣克拉拉县山景市谷歌公司]                                       48.992ms 
 9:  ???                                                  48.643ms 
10:  ???                                                  50.985ms 
11:  no reply
12:  no reply
13:  no reply
14:  no reply
15:  no reply
16:  143.5.96.34 [美国].bc.googleusercontent.com                 48.532ms reached
     Resume: pmtu 1500 hops 16 back 16 

刷 IP

如果你对你的创建 Load Balancer 的 IP 不满意(或者分配到了一个大陆 ping 不通的地址)的话,可以刷一些 IP 来看看有没有满意的,比如可以这样一瞬刷出来 20 个 IP(跑:

for num in {1..20};do  gcloud compute addresses create cdn-ip-$num --project=<Google Project Here> --global;done

不过最近 GCP CDN 到大陆看上去延迟也蛮高(换 IP 也一样),挺不爽的..

总结

这个应该是 2020 年年末才更新的功能,使用 NEG 可以减少一台机器的成本和额外维护转发的成本( 主要 GCP 上的机器真的是又贵又烂 ),简化了不少 DevOps 的 workflow。

搞了这么多,偶尔能看到一句:

可开心了~

References

  1. External HTTP(S) Load Balancing overview
  2. Setting up a load balancer with a custom origin