nginx 负载均衡 upstream server 参数使用域名

此文记录一个配置 nginx 负载均衡,upstream 中的 server 参数使用域名时出现的问题,虽然最后得到了想要的结果,但还是疑云重重。看到此文的大佬们,如有更好解决方法,还望留言指教博主。

问题:

已有三个后端服务分别绑定在三个不同主机的 nginx 上,且给它们分配了三个不同的域名可直接访问:s1.example.coms2.example.coms3.example.com。此外这三台主机上还绑定着其他服务且分配了域名。

现在来配置主站点 s.example.com 的负载均衡和反向代理,nginx 配置文件相关内容:

upstream backend {
    server s1.example.com;
    server s2.example.com;
    server s3.example.com;
}

server
{
   listen 80;
    server_name s.example.com;
    index index.html;
    root /www/wwwroot/example;
    location / {
    proxy_pass http://backend;
    proxy_set_header Host backend;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header REMOTE-HOST $remote_addr;
	}
}

以为会产生的效果:访问 s.example.com 时轮询三个子站点实现负载均衡。可事实并不是这样:虽然轮询了,但返回的数据并非是三个子站点的内容,而是它们所在服务器未绑定任何域名的 nginx 默认页,和直接用 IP 访问一样。

为何会这样?

在 nginx 官方文档中对负载均衡 server 参数的说明:

Defines the address and other parameters of a server. The address can be specified as a domain name or IP address, with an optional port, or as a UNIX-domain socket path specified after the “unix:” prefix. If a port is not specified, the port 80 is used. A domain name that resolves to several IP addresses defines multiple servers at once.

定义服务器的地址和其他参数,地址可以是带端口的域名、IP,或是类似“unix:”开头的 unix 套接字。不指定端口号时默认为 80 端口。如果指定的域名能解析到多个 IP,则相当于定义了多个服务器地址。

其他教程中也有提到,无论填写的是域名还是 IP,nginx 都会在加载配置文件时,将配置中涉及到的域名解析成 IP。这也能解释为何返回的是默认页而不是子站点了。

此时的关键是在反向代理配置中,修改代理请求头中的 Host 参数: proxy_set_head Host $host,Host 请求头指明了请求将要发送到的服务器主机名和端口号,当服务器有多个虚拟主机时,用来识别代理的是哪个虚拟主机。

可问题仍未解决,查看各个子站点 nginx 日志,发现请求头里的 Host 参数是 s.example.com,即主站的 server_name,没有传递 upstream 中的 server 域名。如果将 Host 设置为某个子站点的域名如 proxy_set_head Host s1.example.com 即可正常返回该站数据,但仅可设置为一个域名。

解决方法

带着疑惑搜了半天也没找到简易的解决办法,最后只能:用主站点下不同的端口,分别将三个子站点进行反代再分发。配置如下:

upstream backend {
    server 127.0.0.1:9001;
    server 127.0.0.1:9002;
    server 127.0.0.1:9003;
}
# 分别反代三个子站点
server {
    listen 9001;
      location / {
        proxy_pass http://s1.example.com;
      }
}

server {
    listen 9002;
      location / {
        proxy_pass http://s2.example.com;
      }
}

server {
    listen 9003;
      location / {
        proxy_pass http://s3.example.com;
      }
}

server
{
   listen 80;
    server_name s.example.com;
    index index.html;
    root /www/wwwroot/example;
    location / {
    proxy_pass http://backend;
    proxy_set_header Host backend;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header REMOTE-HOST $remote_addr;
	}
}

但这样多次反向代理,不知会不会影响性能,暂未测试。

其实还有一种办法:分别设置三个后端服务器 IP 对应的默认站点为 s1.example.com、s2.example.com、s3.example.com,但这样大动干戈,很多情况下都不适用。

最后贴一段某大佬关于 proxy_set_head 中 Host 各参数的区别:

在使用 Nginx 做反向代理时,proxy_set_header 功能可以设置反向代理后的 http header 中的 Host,$http_host、$proxy_host,那么这几个有什么区别呢?

Nginx 官网文档中说下面这两条是做反代时默认的,所以 $proxy_host 自然是 proxy_pass 后面跟着的 host了
proxy_set_header Host $proxy_host;
proxy_set_header Connection close;

如果客户端发过来的请求的 header 中有 Host 这个字段时, $http_host 和 $host 都是原始的 Host 字段 ,比如请求的时候 Host 的值是 example.com 那么反代后还是 example.com

如果客户端发过来的请求 header 中没有有 Host 这个字段时,建议使用 $host,这表示请求中的server name。

参考资料:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注