Skip to content

Instantly share code, notes, and snippets.

@Ivlyth
Last active July 21, 2022 09:22
Show Gist options
  • Save Ivlyth/292d1c521594a8d6f6e215206b887da5 to your computer and use it in GitHub Desktop.
Save Ivlyth/292d1c521594a8d6f6e215206b887da5 to your computer and use it in GitHub Desktop.
use nginx to setup dynamic forward web server

作为 toB 的企业,有时候,在客户现场是有严格的防火墙限制的,只有一些常见的必要的端口开放允许访问,比如 22/80/443。

但是一个产品,通常用到了很多组件,部分开源或者自研组件是有 web 管理/调试 界面的,他们通常运行在不同的端口,甚至是
集群中的不同机器上边,而在客户现场,一般都是用 IP 来直接访问某台机器的 web 服务的,并没有 域名 可以使用(因为每一
个客户现场 IP 都不相同,另外客户内网可能也无法进行公网 dns 解析)。 而且更有甚者,部分客户可能仅允许在中控台访问
集群的部分 IP(比如所谓的 master 节点)。

这就导致我们在现场需要不同的 web 界面进行调试的时候异常尴尬,虽然部分客户可以沟通去更改防火墙策略,将我们要求的 IP+port
加白放开,但是毕竟还需要沟通,有时候跨版本可能又有新的端口需要开放而大家一般记不住,这就可能需要跟客户二次、三次沟通,
惹得客户也很厌烦。

但是 nginx 如果仅使用一个端口作为不同站点的 web server 的话,通常需要设定不同的 server_name,或者是使用前缀转发,
但是部分 web 站点前端在编写代码的时候,用到了绝对路径, 这就导致我们无法使用前缀转发,另外一般大家用到了前后端分离开发
的模式后, API 地址部分的拼接都是采用绝对路径的。

那我们可能就剩一个配置不同的 server_name 的方案可选了。 但是,无论是内网开发,还是客户生产环境,IP 均是不可预期的,
意味着我们无法提前配置 dns 解析,但是每次面对一个新环境去做 dns 解析,那就意味着可能不同的环境需要用到不同的域名,这
对大家记忆来说又增加了负担,但相同的域名又无法解析到不同的 IP (多 A 记录其实是可以的,但跟这里的需求不同)。

后来,突然想到了 xip.io 的这种动态 dns 方案,其通过子域名重定义 NS 记录的方法将 dns 解析权限转交到自定义 DNS server,
自定义的 server 会根据 query 的内容进行地址返回,比如 1.2.3.4.x.xip.io 这个 “看起来怪怪“ 的域名,其 dns 地址会
解析到 1.2.3.4,利用该机制,那么我们也可以动态的拼接域名,生成指向不同 IP 地址的域名,然后展示在我们的管理后台方便大家
点击即可,无需记忆。

有了动态 dns 以后,剩下的问题就是,我们真正要访问的 web 服务,在哪台机器的哪个端口?
这里我们重新梳理下我们的前提条件和需求:
前提条件:

  • 我们只有一台机器的 443 可以访问

需求:

  • 我们需要访问不特定的 web 服务,它可能运行在上述的服务器上,也可能在集群内的别的机器上,并且端口不一定是 443

那这里我们整理出一些指标:

  • 这台加白机器的 IP 地址,我们用 MASTER_IP 来指代
  • 目标 web 服务的协议,我们用 SCHEMA 来指代
  • 目标 web 服务所在 IP 地址,我们用 TARGET_IP 来指代
  • 目标 web 服务运行的端口,我们用 TARGET_PORT 来指代

此时,我们可以用以上信息,构建如下格式的域名,该域名的 dns 结果会指向 MASTER_IP,正符合我们的预期:
https://MASTER_IP.SCHEMA-TARGET_PORT.TARGET_IP.xip.io

然后,我们需要做的就是能够在 nginx conf 内从当前域名中提取 SCHEMA, TARGET_IP, TARGET_PORT 等跟目标相关的关键信息, 然后用来构建 proxy_pass 指令即可

另外,如果需要,我们可以利用 sslip.io 提供的 dns 服务,来配置使用自己的域名

使用该方案后,内网测试的过程中,大家电脑都是有公网 dns 解析能力的,只需打开对应环境的后台管理页面,然后
根据提示点击跳转不同的域名即可, 在部分有公网 dns 权限的客户现场,也是如此操作。 对于没有公网 dns 权限
的客户环境,我们在后台管理页面提供了一键拷贝 hosts 映射的功能, 会引导他们先配置 hosts 映射后再行访问

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name ~(\d+\.\d+\.\d+\.\d+\.)?(?<schema>https?)(?<port>\d+)\.(?<ip>\d+\.\d+\.\d+\.\d+).x.myth.ren;
rewrite ^(.*) https://$host$request_uri?;
}
server {
listen 443 ssl http2;
server_name ~(\d+\.\d+\.\d+\.\d+\.)?(?<schema>https?)(?<port>\d+)\.(?<ip>\d+\.\d+\.\d+\.\d+).x.myth.ren;
ssl_certificate /etc/nginx/ssl/nginx-selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/nginx-selfsigned.key;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Referer $schema://$host$request_uri;
proxy_pass $schema://$ip:$port;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment