Nginx 动态反向代理实现
序
Source Global CDN 的海外中转链路往往需要对多个域名进行反向代理,之前的方案是给每个域名单独创建 vhost。但在链路增长、服务器增多的现实情况下,这一方案变得越来越难以维护。
我作为 Source Global CDN 的主要运维人员,自然而然地承担起了此任务。
问题
随着服务使用服务器增多,链路增长,我们面临以下问题:
- 在配置出现更变时,需要为所有服务器进行内容修改,费时费力。
- 如有服务新增,需要在所有服务器中进行多个操作,且需进行相关测试。
- SSL 证书、vhost 的 conf 文件管理混乱。
在经过深思熟虑后,我们大致想出以下解决方案:
- 使用泛域名解析,提高内聚性,使得所有服务器可以自动配合增删改的操作,无需单独配置。
- 使用私有DNS服务,在一个平台管理链路,无需在服务器中进行修改。
高内聚的泛域名解析方案使得vhost文件数目和SSL数目大幅减小,私有DNS避免服务器内数据不修改的问题。
实施
泛域名解析
经过考虑,我们制定了以下方案:
- 由一个 SaaS 任务负责使用 acme.sh 操作 DNS 申请泛域名 SSL 证书
- 将证书存在存储桶中,通过另一 SaaS 任务更新 Content-MD5 meta
- 服务器的 Crontab 每小时自动从存储桶获取 MD5 并比对,如有修改则更新本地 SSL 并重载 Nginx
其中,acme.sh 操作可参考 使用 acme.sh 自动为 IP / 域名配置证书 并进行少许修改。
Nginx 动态反向代理
在完成上述简单任务后,我们开始考虑 Nginx 的动态反向代理。
之所以不考虑使用 Lua 脚本是因为在所有服务器重装 OpenResty 的时间成本太过巨大。
相信大多数人都对 Nginx 的反向代理早有耳闻,大多数情况下其反向代理呈现以下模式:
upstream backend {
server 127.0.0.1:8080 weight=1 fail_timeout=5s max_fails=3;
server 127.0.0.1:8081 weight=1 fail_timeout=5s max_fails=3;
server 127.0.0.1:8082 weight=1 fail_timeout=5s max_fails=3 backup;
}
server {
listen 80;
server_name www.example.com;
index index.html index.htm index.php;
location / {
proxy_pass http://backend;
}
}
在这一模式下,反向代理始终从 127.0.0.1:8080 获取资源,是静态的。
而我们的需求是这样的:
- 当
$host = gh.sourcegcdn.com
,反向代理https://gh.origin.sgcdn
- 当
$host = wp.sourcegcdn.com
,反向代理https://wp.origin.sgcdn
- 当
$host = <subdomain>.sourcegcdn.com
,反向代理https://`<subdomain>`.origin.sgcdn
即按请求头的子域名寻求前缀,并补全后缀,随后进行反向代理。
我们的最终实现如下:
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
resolver 8.8.8.8 valid=10s;
ssl_certificate /usr/local/nginx/conf/ssl/_.sourcegcdn.com.crt;
ssl_certificate_key /usr/local/nginx/conf/ssl/_.sourcegcdn.com.key;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256;
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;
ssl_conf_command Options PrioritizeChaCha;
ssl_prefer_server_ciphers on;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_buffer_size 2k;
add_header Strict-Transport-Security max-age=15768000;
ssl_stapling on;
ssl_stapling_verify on;
server_name ~^(?<subdomain>.+)\.sourcegcdn\.com$;
access_log /data/wwwlogs/_.sourcegcdn.com.nginx.log combined;
error_log /data/wwwlogs/_.sourcegcdn.com.nginx.error.log;
add_header X-Powered-By "Source Global CDN Global Accurate";
index index.html;
root /data/wwwroot/*.sourcegcdn.com;
#error_page 404 /404.html;
#error_page 502 /502.html;
add_header "X-Subdomain-Deliver" $subdomain;
location @proxy {
# 回源HOST
proxy_set_header Host $subdomain.sourcegcdn.com;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 配置后端服务器
proxy_pass https://$subdomain.origin.sgcdn;
proxy_redirect off;
}
location / {
try_files $uri @proxy;
}
location ~ .*\.(gifjpgjpegpngbmpswfflvmp4ico)$ {
expires 365d;
access_log off;
try_files $uri @proxy;
}
location ~ .*\.(jscss)?$ {
expires 30d;
access_log off;
try_files $uri @proxy;
}
location ~ /(\.user\.ini\.ht\.git\.svn\.projectLICENSEREADME\.md) {
deny all;
}
location /.well-known {
allow all;
}
}
DNS 解析
如此配置,我们只需在 CDN 测配置 *.sourcegcdn.com
这一统一节点,再按需逐个添加 DNS 解析,即可正常使用。
*.sourcegcdn.com1INA127.0.0.1