Nginx 动态反向代理实现

· Technology

Source Global CDN 的海外中转链路往往需要对多个域名进行反向代理,之前的方案是给每个域名单独创建 vhost。但在链路增长、服务器增多的现实情况下,这一方案变得越来越难以维护。

我作为 Source Global CDN 的主要运维人员,自然而然地承担起了此任务。

问题

随着服务使用服务器增多,链路增长,我们面临以下问题:

  1. 在配置出现更变时,需要为所有服务器进行内容修改,费时费力。
  2. 如有服务新增,需要在所有服务器中进行多个操作,且需进行相关测试。
  3. SSL 证书、vhost 的 conf 文件管理混乱。

在经过深思熟虑后,我们大致想出以下解决方案:

  1. 使用泛域名解析,提高内聚性,使得所有服务器可以自动配合增删改的操作,无需单独配置。
  2. 使用私有DNS服务,在一个平台管理链路,无需在服务器中进行修改。

高内聚的泛域名解析方案使得vhost文件数目和SSL数目大幅减小,私有DNS避免服务器内数据不修改的问题。

实施

泛域名解析

经过考虑,我们制定了以下方案:

  1. 由一个 SaaS 任务负责使用 acme.sh 操作 DNS 申请泛域名 SSL 证书
  2. 将证书存在存储桶中,通过另一 SaaS 任务更新 Content-MD5 meta
  3. 服务器的 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

参考资料

Comments

Send Comments

Markdown supported. Please keep comments clean.