用Nginx侦听同端口的TCP应用和HTTP应用

建站的人,必定会使用到的一个工具——Nginx,大家或多或少都了解Nginx的一些特性,比如用作负载均衡服务器,再比如更常见的——在同一台主机上挂载好几个App,然后通过Nginx来匹配分流。

大部分时候,我们写Nginx配置时都是这样的,例如:


server {
    listen 443;
    server_name a.com;

    ...
}

server {
    listen 443;
    server_name b.com;

    ...
}

server {
    listen 443;
    server_name c.com;

    ...
}

不是说同一个端口只能由一个程序使用吗,为什么这里监听了3个程序,Nginx还能正常工作?

其实我们在使用Nginx时,大部分时候是使用了它的http模块,而上面的三个 server 其实都被包裹在 http里,即:

http {
    server {...}
    server {...}
    server {...}
}

而server里侦听的443端口,其实是由一个http模块共同侦听的,浏览器通过sni请求到此Nginx服务器,由http模块处理这些请求,并且让sni和server依次进行匹配操作,如果匹配成功,则返回在对应端口上运行的app。

stream模块分流

而如果我们只是单纯的想要转发TCP流量(它可能也是HTTP流量)到下级应用时,那么使用http模块就无法满足我们的要求,就需要添加stream模块支持到Nginx中,通常它在Nginx中是默认不开启的,需要手动在 /path/to/nginx.conf 中添加以下配置:

# nginx.conf
load_module /usr/lib/nginx/modules/ngx_stream_module.so;
...

include /etc/nginx/stream-enabled/*;

这样就启用了stream模块,stream模块的工作方式可参考下图:

它的工作模式和http模块很相似,也是通过侦听端口+SNI匹配的形式来完成应用的分流。但是注意,stream模块和http模块不能侦听同一个端口,这两个模块应该被看作是互相独立的应用,也就是说,上图中通过stream模块进行监听了443端口,那么http模块就无法再监听443端口了,但是对于浏览器前端而言,他们仍可通过443端口访问到位于http模块下挂载的app(a.com)

一个stream配置的例子:

stream {
   #sni match
   map $ssl_preread_server_name $backend_name {
    a.com http_app;
    d.com tcp_app;
    # 域名都不匹配情况下的默认值
    default web;
    }

    upstream http_app {
        server 127.0.0.1:1443;
    }

    upstream tcp_app {
        server 127.0.0.1:14443;
    }

    upsteam web {
        server 127.0.0.1:80;
    }

    server {
        listen 443 reuseport;
        listen [::]:443 reuseport;
        proxy_pass  $backend_name;
        ssl_preread on;
    }
}