0%

Nginx tcp 反向代理根据客户真实ip分流的一种方式

问题背景

在Cluster中,我需要将一个service通过nodePort方式暴露给外部使用。但是,又需要限制只允许白名单的ip访问。

不论是 nginx-ingress,ambassador,好像都没找到合适的方式能直接实现。

于是,只能通过外层的proxy中来控制tcp的流量转发及访问控制了。

Nginx TCP 转发

一般情况,我们是使用

1
2
3
4
5
6
stream {
server {
listen port;
proxy_pass host:port;
}
}

然后,通过这种方式,接收方是无法获取客户请求的真实ip,需要透明代理的方式

1
2
3
4
5
6
7
stream {
server {
listen port;
proxy_pass host:port;
proxy_bind $remote_addr transparent;
}
}

但是问题是,直接这么配置,连接是不通的。还需要手动添加路由表才行。这样看来并不方便。

那么,还有什么方法呢?

Nginx proxy_protocol

查询资料,发现可以通过proxy_protocol的方式来获取到客户的真实ip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# proxy server 
stream {
server {
listen port;
proxy_pass internal_proxy:port;
proxy_protocol on;
}
}

# internal_proxy
stream {
upstream svc1 {
server host:port;
}
upstream svc2 {
server host:port;
}
map $remote_addr $backend_svr {
1.1.1.1 "svc1";
default "svc2";
}
server {
listen port proxy_protocol;
set_real_ip_from 0.0.0.0/0;
proxy_pass $backend_srv;
}
}

server接收到请求,通过proxy_protocol的方式,转发到内部的proxy。

内部proxy通过 map $remote_addr 方式,可以指定ip转发流量,达到需要的目的。

尝试解决实际的问题

需求:Cluster内部有个redis服务。需要对外网指定ip暴露访问。

  1. 建立redis服务的代理服务,redis-proxy (通过nginx)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    # Deployment的yaml就不写了,重点是nginx的配置
    # nginx.conf
    http {
    ...
    # 定义 default-svc 的应答
    server {
    listen 80;
    location / {
    return 200 "ok.";
    }
    }
    }
    stream {
    upstream redis-svc {
    server redis-svc:6379;
    }
    upstream default-svc {
    server 127.0.0.1:80;
    }
    map $remote_addr $backend_svr {
    x.x.x.x "redis-svc"; # xxxx是放开的ip,仅这个ip能通过公网访问redis
    default "default-svc"; # 其余的走默认处理
    }
    server {
    listen port proxy_protocol;
    set_real_ip_from 0.0.0.0/0;
    proxy_pass $backend_srv;
    }
    }
  2. 将redis-proxy通过nodePort方式暴露

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    apiVersion: v1
    kind: Service
    metadata:
    name: redis-proxy
    spec:
    type: NodePort
    externalTrafficPolicy: Local
    ports:
    - port: 6379
    targetPort: 6379
    protocol: TCP
    nodePort: 30001
    selector:
    app: redis-proxy
  3. server的nginx代理配置

    1
    2
    3
    4
    5
    6
    7
    8
    # nginx.conf
    stream {
    server {
    listen 6379;
    proxy_pass 127.0.0.1:30001; # nodePort的端口
    proxy_protocol on;
    }
    }
  4. 通过redis-cli进行测试 (redis是带密码的)

    1
    2
    3
    # redis-cli -h x.x.x.x -p 6379
    x.x.x.x:6379> AUTH 1
    (error) ERR invalid password

    在允许访问的机器上访问,能看到正常应答

    1
    2
    3
    # redis-cli -h x.x.x.x -p 6379
    x.x.x.x:6379> AUTH 1
    Error: Protocol error, got "H" as reply type byte

    在其他机器上请求,会看到协议错误,因为设置中是重定向到一个http的server中。

    1
    2
    # curl http://x.x.x.x:6379
    ok.%

    通过curl直接http访问,有正常的应答,符合预期,问题解决。

结语

实际上这种方式也非常不灵活,然而在 nginx-ingress/ambassador中,好像也看不到能简单实现满足这个需求的方式。http/https 可以轻易实现达到,但如果是tcp的流量,好像并没有方便的方式。有待学习研究。