使用 nginx 实现本地透明代理
尝试用 nginx 作为透明代理,修改报文头。
准备目标服务
目标服务是 试验环境 中启动的 echoserver,监听地址为 127.0.0.1:9090。
为目标服务准备一个域名 echo.example,用本地的 dnsmasq 解析,设置方法见 dnsmasq。
配置 nginx
配置nginx:
server {
listen 9000;
listen [::]:9000;
keepalive_requests 2000;
keepalive_timeout 60s;
location / {
resolver 127.0.0.1:8053; # 必须配置域名服务器
# 这里因为 nginx 和 目标服务在同一台机器上,使用了不同端口,所以写成 $host:9090
# 如果位于不同机器上,写成:proxy_pass http://$host:9000$request_uri;
proxy_pass http://$host:9090$request_uri;
proxy_set_header tranproxy "true";
}
}
通过 127.0.0.1:9000 端口访问,可以看到返回的请求头中多了 tranproxy=true:
$ curl -H "Host: echo.example" 127.0.0.1:9000
Hostname: 57e34b409aa1
Pod Information:
-no pod information available-
Server values:
server_version=nginx: 1.13.3 - lua: 10008
Request Information:
client_address=172.17.0.1
method=GET
real path=/
query=
request_version=1
request_scheme=http
request_uri=http://echo.example:8080/
Request Headers:
accept=*/*
connection=close
host=echo.example:9090
tranproxy=true
user-agent=curl/7.54.0
Request Body:
-no body in request-
实现无感知的本地透明代理
上面的测试因为环境限制,只是验证了透传的功能,这里研究一下怎样实现完全无感知本地 http 透明代理。
目标服务端口为 8080,在请求端设置 nginx,使本地发出的对 8080 端口的访问都经过本地 nginx 代理。
本地 nginx 的配置如下:
server {
listen 8080;
location / {
resolver 114.114.114.114;
proxy_pass http://$host:8080$request_uri;
proxy_set_header tranproxy "true"; # 为了验证代理效果,添加了一个请求头
}
}
接下来的关键问题是:怎样将本地发出的到 8080 端口的请求经过 nginx 送出,并且是在客户端无感知的情况下。
借鉴 istio 的做法,用 iptables 实现,istio 中的 envoy 同时代理流入的请求和流出的请求,我们这里只实现本地的代理,因此只需要设置 outbound 规则。
在 nat 表中添加一条规则链,存放本地代理的规则:
# 新建一个规则链
iptables -t nat -N LOCAL_PROXY
# 不代理 nginx 生成的报文,防止出现 nginx 代理 nginx 的死循环
iptables -t nat -A LOCAL_PROXY -m owner --uid-owner nginx -j RETURN
# 将本地生成的目标端口为 8080 的 tcp 报文,重定向到本地的 :8080 监听地址
iptables -t nat -A LOCAL_PROXY -p tcp -m tcp --dport 8080 -j REDIRECT --to-ports 8080
启用 LOCAL_PROXY:
iptables -t nat -A OUTPUT -p tcp -j LOCAL_PROXY
然后在本地直接访问目标服务,会发现请求头中多了 tranproxy=true:
$ curl 172.17.0.21:8080
Hostname: echo-597d89dcd9-m84tq
Pod Information:
-no pod information available-
Server values:
server_version=nginx: 1.13.3 - lua: 10008
Request Information:
client_address=172.17.0.28
method=GET
real path=/
query=
request_version=1
request_uri=http://172.17.0.21:8080/
Request Headers:
accept=*/*
connection=close
host=172.17.0.21:8080
tranproxy=true
user-agent=curl/7.61.1
上面的操作过程已经打包成了镜像,可以直接使用:
docker run -itd --cap-add NET_ADMIN --name nginx-tranproxy lijiaocn/nginx-tranproxy:0.1 \
-P 80 \
-P 8080 \
-H tranproxy:true \
-H tranproxy2:true \
-N 114.114.114.114 \
-N 8.8.8.8 \
--Set-Forwarded-For default \
--Set-Client-Hostname default \
-- tail -f /dev/null
在 kubernetes 中使用:
kubectl -n "Your NameSpace" create -f https://raw.githubusercontent.com/introclass/kubernetes-yamls/master/all-in-one/nginx-tranproxy.yaml