haproxy + nginx + proxy protocol 得到客戶真實IP方法

公司網站架構爲:javascript

前面2臺HA負載均衡,後面3臺Nginx負載均衡反向代理,而後後面有N臺WEB服務器css

因爲要統計IP,須要在WEB服務器日誌裏體現客戶端真實IPhtml

那麼問題來了,經過HA代理的HTTP協議是沒有問題的,後端的WEB服務器能夠正常獲取到客戶端真實IP前端

可是經過HA代理的HTTPS協議就不行了,爲何呢,由於咱們HA設置的是代理模式就是TCP模式,TCP代理SSL協議跳轉到後面的NG上java

因爲4層協議是不能轉發heder的,那後端https天然獲取不到客戶端真實IP了,怎麼辦呢,網上找了些資料,作了些測試有2種方案node

第一:HA的https再也不使用HA代理,直接使用nginx來作負載均衡,這樣問題就解決了,方法和配置參照我上篇隨筆。mysql

第二:是最近2天測試出來的新方案,就是用proxy protocol 協議,也就是代理協議。nginx

什麼是代理協議?解釋以下,不是我本身寫的,網上COPY的redis

1.代理協議即 PROXY protocol,是haproxy的做者Willy Tarreau於2010年開發和設計的一個Internet協議,經過爲tcp添加一個很小的頭信息,來方便的傳遞客戶端信息(協議棧、源IP、目的IP、源端口、目的端口等),在網絡狀況複雜又須要獲取客戶IP時很是有用。sql

  • 多層NAT網絡
  • TCP代理(四層)或多層tcp代理
  • https反向代理http(某些狀況下因爲Keep-alive致使不是每次請求都傳遞x-forword-for)

代理協議分爲v1和v2兩個版本,v1人類易讀,v2是二進制格式,方便程序處理。Proxy protocol是比較新的協議,但目前已經有不少軟件支持,如haproxy、nginx、apache、squid、mysql等等,要使用proxy protocol須要兩個角色sender和receiver,sender在與receiver之間創建鏈接後,會先發送一個帶有客戶信息的tcp header,由於更改了tcp協議,需receiver也支持proxy protocol,不然不能識別tcp包頭,致使沒法成功創建鏈接。

以上就是解釋,haproxy和Nginx都支持這個協議,nginx須要1.5版本以上,有了這個協議就好辦了,haproxy依然使用tcp代理ssl(https),只須要加一點點配置就行,示例配置以下:

global
        maxconn 64000
        chroot  /usr/share/haproxy
        uid 99
        gid 99
        daemon
        nbproc  5
        tune.ssl.default-dh-param 2048
        stats bind-process 1
        stats socket /var/run/haproxy.stats level admin
defaults
        log     global
        log 127.0.0.1 local0
        mode    http
        option  dontlognull
        retries 3
        option  redispatch
        option  httpclose
        balance roundrobin
        #balance leastconn
        #option  forwardfor
        #option  forwardfor if-none

        maxconn 64000
        timeout http-request 5s
        timeout connect 5000
        timeout client  10000
        timeout server  30000
listen  monitor_stat :8088
        stats   uri /ihaproxy-stats
        stats   realm Haproxy\ Statistics
        stats   auth ha_house:ZW5dmKRTObmOuA1nnS5U
        stats   hide-version
        bind-process    1

frontend yidonghttps-in
        mode tcp
        bind *:443
        default_backend yidongclient_server_https

frontend http-in
        bind *:80
        log global
        option httplog
        option forwardfor
backend yidongclient_server_http
        server yidonghttp_41 172.17.2.110:80 weight 1 check inter 5000 rise 2 fall 5
backend yidongclient_server_https
        mode tcp
        server yidonghttps_37   172.17.2.110:443 send-proxy

上面配置其實沒添加什麼,只是在最後的backend  yidongclient_server_https的server裏的最後面添加上了 send-proxy 參數,這樣HA就把proxy protocol協議發送到後端Nginx上了。

配置完ha後,後面的nginx也須要配置,因爲80端口天然能獲取客戶端IP地址,咱們主要來配置443端口

配置文件以下

upstream ihouse443{
  server 172.17.3.108:443 max_fails=3 fail_timeout=60 weight=1;

}

server {
  listen 443 proxy_protocol;
  server_name ihouse.ifeng.com;
  access_log /data/logs/nginx/ihouse_access.log access;
  error_log  /data/logs/nginx/ihouse_error.log ;
  ssl on;
  ssl_certificate /data/ifengsite/htdocs/ihouse.ifeng.com.crt;
  ssl_certificate_key /data/ifengsite/htdocs/ihouse.ifeng.com.key;

  ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDH:AES:HIGH:!aNULL:!MD5:!ADH:!DH;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

   location / {
    proxy_pass https://ihouse443;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Real-IP $proxy_protocol_addr;
    #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-For $http_x_forwarded_for;
    proxy_set_header X-Forwarded-For $proxy_protocol_addr;
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
    proxy_max_temp_file_size 0;
    proxy_connect_timeout 90;
    proxy_send_timeout 90;
    proxy_read_timeout 90;
    proxy_buffer_size 4k;
    proxy_buffers 4 32k;
    proxy_busy_buffers_size 64k;
    proxy_temp_file_write_size 64k;
  }
}

主要添加以上紅色粗體內容,server段裏添加 proxy_protocol 使之支持代理協議

location 裏添加紅色粗體內容,定義新的headr信息,而且發送到後端WEB上

最後須要定義下日誌文件,從新定義下,這個日誌文件指的是負載均衡的nginx的日誌文件,至於後端的WEB則不須要

#user  www www;
worker_processes 1;

#error_log  /data/logs/nginx/error.log;
#pid        /var/run/nginx.pid;

#Specifies the value for maximum file descriptors that can be opened by this process.
worker_rlimit_nofile 1024;

events {
        use epoll;
        worker_connections 51200;
}

http
{
        include       mime.types;
        #include       proxy.conf;
        default_type  application/octet-stream;

        server_names_hash_bucket_size 128;
        client_header_buffer_size 32k;
        large_client_header_buffers 4 32k;
        client_max_body_size    300m;
        #client_max_body_size    32m;

        #limit_req_zone $baidu_spider zone=baidu_spider:10m rate=15r/m;
        sendfile on;
        tcp_nopush     on;

        keepalive_timeout 30;

        tcp_nodelay on;
        #ssi on;
        #ssi_silent_errors on;
        fastcgi_connect_timeout 180;
        fastcgi_send_timeout 180;
        fastcgi_read_timeout 180;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 8 128k;
        fastcgi_busy_buffers_size 128k;
        fastcgi_temp_file_write_size 128k;
        proxy_headers_hash_max_size 51200;
        proxy_headers_hash_bucket_size 6400;
        gzip on;
        gzip_min_length         2k;
        gzip_buffers            4 16k;
        gzip_http_version       1.0;
        gzip_comp_level 6;
        gzip_types              text/plain application/x-javascript text/css application/xml;
        gzip_vary               on;
        #log_format  access     '$proxy_protocol_addr-,$remote_addr--,$proxy_add_x_forwarded_for---,$http_x_forwarded_for----,$remote_user,$time_local,$host,$request,$status,$http_referer,$HTTP_X_UP_CALLING_LINE_ID,$request_time,$http_user_agent  $upstream_addr  $upstream_response_time  $upstream_cache_status';        
        log_format  access     '-$proxy_protocol_addr-,--$remote_addr--,---$http_x_forwarded_for---,----$proxy_add_x_forwarded_for----,$remote_user,$time_local,$host,$request,$status,$http_referer,$HTTP_X_UP_CALLING_LINE_ID,$request_time,$http_user_agent  $upstream_addr  $upstream_response_time  $upstream_cache_status';
        #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        #              '$status $body_bytes_sent "$http_referer" '
        #              '"$http_x_forwarded_for" "$http_user_agent"';

        #upstream backend {
        #       server 127.0.0.1:9000 weight=5  max_fails=3  fail_timeout=30s;
        #}

    include conf.d/*;

}

主要添加上以上紅色粗體日誌參數,這樣在負載均衡的nginx上就能夠看到https過來的客戶端真實IP了,後端的WEB的https也能夠獲取真實IP了

另外若是是HA後面再也不經過NG作反向代理轉發的話,那麼NG的主機文件配置須要以下配置

    server {
        listen       443 ssl proxy_protocol;
        server_name  ihouse.ifeng.com;
        ssl_certificate      /data/ifengsite/htdocs/ihouse.ifeng.com.crt;
        ssl_certificate_key  /data/ifengsite/htdocs/ihouse.ifeng.com.key;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;
        set_real_ip_from 172.17.3.0/24; real_ip_header proxy_protocol;

        location / {
            root   html;
            index  index.html index.htm;
        }
    }

依然須要設置 proxy_protocol

設置real_ip的前端代理主機IP或者IP段

設置real_ip 的header

參考文獻

https://www.52os.net/articles/PROXY_protocol_pass_client_ip.html

https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/

相關文章
相關標籤/搜索