Получаем IP-адреса HTTPS-клиентов с HAProxy (frontend) на Nginx (backend) в режимах HTTP и TCP-балансировки

    Довольно часто требуется балансировать нагрузку между несколькими веб-серверами. При этом, как правило, необходимо, чтобы веб-приложения получали реальные IP-адреса клиентов, а не IP балансировщика.

    В случае балансировки и терминации HTTP(S)-трафика на HAProxy (Layer 7 [1]) данная задача легко решается добавлением заголовка “X-Real-IP” и его обработкой на Nginx при помощи модуля ngx_http_realip_module [2]. При балансировке TCP-трафика от HTTPS-клиентов и передаче его на веб-сервера напрямую без модификации или терминации (Layer 4 [3]) добавить данный заголовок невозможно, поэтому требуется воспользоваться возможностями, предоставляемыми Proxy Protocol [4, 5, 6].

    Рассмотрим оба варианта (балансировка L7 и L4) на примере выдержек из конфигурационных файлов haproxy 1.5.9 и nginx 1.6.2

    Балансировка на прикладном уровне (Layer 7): терминация HTTPS-трафика на HAProxy и передача по HTTPS на Nginx


    В данном примере HTTPS-трафик от клиента терминируется на HAProxy, модифицируется и передается на Nginx так же по HTTPS.

    haproxy.cfg


    global
            maxconn 4096
            chroot /usr/share/haproxy
            uid 99
            gid 99
            daemon
            tune.ssl.default-dh-param  2048
    
    defaults
            log     global
            option  redispatch
            option  tcp-smart-accept
            option  tcp-smart-connect
            retries 3
            maxconn 2000
            timeout connect 5000
            timeout check   3000
            timeout client  50000
            timeout server  50000
    
    frontend http_frontend *:80
            mode http
            redirect scheme https code 301 if !{ ssl_fc }
    
    frontend https_frontend_ssl_terminate
            mode http
            bind *:443 ssl crt /etc/haproxy/ssl/public.example.com.pem
            option forwardfor header X-Real-IP
            default_backend web_server_http
    
    backend web_server_http
            mode http
            balance roundrobin
            # Отправляем трафик на backend по HTTPS
            server s1_https 192.168.1.10:443 ssl verify none
            server s2_https 192.168.1.20:443 ssl verify none
    


    nginx.conf


    server {
        server_name localhost;
    
        listen 443 ssl default_server;
    
        ssl_certificate      /etc/nginx/ssl/internal.example.com.pem;
        ssl_certificate_key  /etc/nginx/ssl/internal.example.com.key;
    
        # Адрес HAProxy
        set_real_ip_from 192.168.1.254;
        real_ip_header X-Real-IP;
    
        root /usr/share/nginx/html;
        index index.html index.htm;
    
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root /usr/share/nginx/html;
        }
    
        location ~ /\.ht {
            deny all;
        }
    }
    


    Балансировка на транспортном уровне (Layer 4): передача TCP-трафика с HAProxy на Nginx


    В данном примере HTTPS-трафик клиентов не модифицируется (HAProxy вмешивается в транспортный уровень) и его терминация происходит непосредственно на Nginx.

    haproxy.cfg


    global
            maxconn 4096
            chroot /usr/share/haproxy
            uid 99
            gid 99
            daemon
    
    defaults
            log     global
            option  redispatch
            option  tcp-smart-accept
            option  tcp-smart-connect
            retries 3
            maxconn 2000
            timeout connect 5000
            timeout check   3000
            timeout client  50000
            timeout server  50000
    
    frontend http_frontend *:80
            mode http
            redirect scheme https code 301 if !{ ssl_fc }
    
    frontend https_frontend_ssl_pass
            mode tcp
            bind *:443
            default_backend web_server_tcp
    
    backend web_server_tcp
            mode tcp
            balance roundrobin
            # ВНИМАНИЕ! Работа с send-proxy возможна только,
            # когда принимающая сторона понимает, что это такое.
            # Для Nginx необходимо включить в директиву listen
            # опцию proxy_protocol.
            server s1_tcp 192.168.1.10:443 send-proxy
            server s2_tcp 192.168.1.20:443 send-proxy
    


    nginx.conf


    server {
        server_name localhost;
    
        # ВНИМАНИЕ! Работа с директивой proxy_protocol возможна только в связке с haproxy.
        # Для прямого доступа данную директиву необходимо отключить.
        listen 443 ssl default_server proxy_protocol;
    
        ssl_certificate      /etc/nginx/ssl/public.example.com.pem;
        ssl_certificate_key  /etc/nginx/ssl/public.example.com.key;
    
        # Адрес HAProxy
        set_real_ip_from 192.168.1.254;
        real_ip_header proxy_protocol;
    
        root /usr/share/nginx/html;
        index index.html index.htm;
    
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root /usr/share/nginx/html;
        }
    
        location ~ /\.ht {
            deny all;
        }
    }
    


    Заключение


    Используя описанные выше настройки мы смогли передать веб-серверу Nginx, расположенному за HAProxy, реальные IP-адреса клиентов при работе по HTTPS. Подобным подходом так же можно воспользоваться при работе со сторонними балансировщиками нагрузки, например CloudFlare [7, 8] и AWS ELB [9, 10].

    Литература


    1. Протоколы прикладного уровня сетевой модели OSI — ru.wikipedia.org
    2. Модуль ngx_http_realip_module — nginx.org
    3. Транспортный уровень сетевой модели OSI — ru.wikipedia.org
    4. The PROXY protocol — haproxy.org
    5. HAProxy Configuration Manual: send-proxy — cbonte.github.io
    6. Модуль ngx_http_core_module: директива listen — nginx.org
    7. Getting Real IP Addresses Using CloudFlare, Nginx, and Varnish — danielmiessler.com
    8. Getting Real IP Addresses Using Nginx and CloudFlare — babaei.net
    9. Using Proxy Protocol With Nginx — chrislea.com
    10. AWS Elastic Load Balancing — aws.amazon.com
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 14

      0
      Как вовремя! Спасибо :)
        0
        Всегда пожалуйста :)
        0
        А какая у вас схема кластера? Интересно один ли балансировщик или используется два (три) сервера с HAProxy? И как они перехватывают управление?
          0
          А можно просто использовать LVS (linux virtual server) в режиме DR (direct routing) и не нести накладных расходов в юзерспейсе и не иметь таких проблем вообще — адрес будет оригинальный на nginx-е.
            0
            На хабре рассматривалось:
            habrahabr.ru/post/104621/ by divanikus
              +2
              Можно и так конечно. Однако следует помнить, что haproxy довольно богат в функциональном плане и умеет еще массу полезных вещей, например проверять состояние backend'a при помощи кастомизируемых проверок, маршрутизировать через один ip разные домены при использовании TLS и т.д.

              0
              Правильно ли я понимаю, что способ маршрутизации на 4 уровне производительнее, но дает меньшую гибкость и работает не со всеми бэкэндами?
                +5
                Не совсем так. В статье я показал как можно воспользоваться расширением proxy protocol, если frontend и backend его поддерживают. Без proxy protocol TCP-балансировку можно настроить на любой tcp-сервис, будь то FTP, IMAP, SQL и т.п.

                И, пожалуй следует отметить, что в HAProxy так же можно использовать смешанную (http/tcp) маршрутизацию и балансировку [*]:
                frontend mixed_frontend
                	bind :80
                	mode tcp
                	option tcplog
                	tcp-request inspect-delay 5s
                	tcp-request content accept if HTTP
                	use_backend http_backend if HTTP
                	default_backend tcp_backend
                
                   backend tcp_backend
                        # tcp-only config
                
                   backend http_backend
                	# http-only config
                	mode http
                

                Таким образом можно, к примеру, расположить на 80 порту одновременно и веб-сервер и сервер SSH.
                  0
                  Интересная штука, надо будет попробовать.
                0
                А с HTTP протоколом оно действительно работать не должно, или я какой-то глюк словил?

                nginx ругался ошибкой 400 при пустых логах. Отключаю send-proxy или перевожу haproxy в http режим — всё ок.
                  0
                  В Nginx обычно помогает увеличить уровень логированная до Debug.

                  Так же очень важно, чтобы одновременно с send-proxy на HAProxy был включен proxy_protocol на Nginx.
                    0
                    Попробую, но даже в access_log ничего не писалось, а там уровней логирования нет, насколько я знаю.

                    Включено было с обеих сторон естественно. Для HTTPS части сервера описанный тут метод у меня замечательно заработал. Хотел его же для HTTP применить.
                      0
                      Вот тут описывается схожий случай, но причину ошибочного поведения так и не нашли:
                      trac.nginx.org/nginx/ticket/650
                  0
                  Жаль, что Tomcat не понимает proxy protocol. Ищу возможность балансировки через tcp mode для Tomcat. Используется проприетарный протокол.
                  Пока все тщетно

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое