Modul 5 — Webové servery a reverzní proxy

22. Nginx jako reverzní proxy a load balancer

Konfigurace Nginx jako reverzní proxy před aplikačním serverem a rozložení zátěže mezi více backendů pomocí upstream bloku.

Odhadovaná délka studia: 55 minut · Stav: nehotovo

Technologie: Nginx

Úvod a kontext

Minulá lekce ukázala Nginx jako server, který přímo servíruje statické
soubory z disku. Mnohem častější nasazení v produkci je ale jiné: Nginx
stojí před aplikačním serverem (Django, Node.js, Java aplikací) a
požadavky mu předává — funguje jako reverzní proxy. Když je backendů
víc, Nginx umí zátěž mezi ně i rozdělovat — funguje jako load
balancer
. Tato lekce ukazuje obě role, které jsou v reálném provozu
mnohem běžnější než čistě statický web z minulé lekce.

Teorie

Co je reverzní proxy

Reverzní proxy je server, který přijímá požadavky klientů a předává je
dalšímu serveru (backendu) na pozadí, aniž by klient backend viděl přímo.
Klient komunikuje jen s Nginx; Nginx komunikuje s aplikací. Důvody, proč
se toto uspořádání v produkci prakticky vždy používá:

  • TLS terminace — Nginx obstarává HTTPS (šifrování), aplikační server
    pak může běžet jednodušeji jen na HTTP dovnitř interní sítě,
    viz lekce o HTTP/TLS,
  • statický obsah — Nginx umí servírovat statické soubory (obrázky,
    CSS, JS) mnohem efektivněji než většina aplikačních frameworků,
  • load balancing — rozložení zátěže mezi víc instancí aplikace,
  • bezpečnost — aplikační server (např. Gunicorn pro Django) není
    navržený na přímé vystavení internetu; Nginx před ním funguje jako
    otestovaná, prověřená vrstva,
  • jednotný vstupní bod — víc aplikací/služeb na jednom serveru sdílí
    port 80/443 a Nginx podle domény nebo cesty směruje na správnou z nich.

Základní konfigurace proxy_pass

server {
    listen 80;
    server_name app.example.cz;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
  • proxy_pass http://127.0.0.1:8000 — všechny požadavky na tuto cestu se
    přepošlou na aplikační server běžící lokálně na portu 8000 (typicky
    Gunicorn/uWSGI proces),
  • proxy_set_header Host $host — bez tohoto řádku by aplikace v hlavičce
    Host viděla 127.0.0.1, ne skutečnou doménu, kterou zadal klient,
  • X-Real-IP a X-Forwarded-For — předávají skutečnou IP adresu klienta;
    bez nich by aplikace viděla jako zdroj všech požadavků jen IP adresu
    Nginx serveru (typicky 127.0.0.1), což znemožňuje např. rate limiting
    podle IP na úrovni aplikace,
  • X-Forwarded-Proto — informuje aplikaci, jestli původní požadavek od
    klienta přišel přes HTTP, nebo HTTPS (důležité, pokud Nginx dělá TLS
    terminaci a aplikace jinak vidí jen nešifrované HTTP).

upstream blok a load balancing

Pokud aplikace běží ve více instancích (více procesů, více serverů),
definuje se skupina backendů blokem upstream:

upstream aplikace {
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
    server 10.0.0.13:8000;
}

server {
    listen 80;
    server_name app.example.cz;

    location / {
        proxy_pass http://aplikace;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Ve výchozím nastavení Nginx rozkládá požadavky mezi servery v upstream
bloku metodou round-robin (postupně kolem dokola). Další dostupné
strategie:

upstream aplikace {
    least_conn;                  # pošli na server s nejméně aktivními spojeními
    server 10.0.0.11:8000;
    server 10.0.0.12:8000;
    server 10.0.0.13:8000 weight=2;   # dostane 2x víc požadavků než ostatní
    server 10.0.0.14:8000 backup;     # použije se, jen pokud ostatní selžou
}
  • least_conn — vhodné, když jednotlivé požadavky trvají různě dlouho,
  • weight=N — nerovnoměrné rozložení, typicky pro backendy s různým
    výkonem hardwaru,
  • backup — server v záloze, aktivuje se, jen když ostatní nejsou
    dostupné.

Health checks na úrovni upstream

Nginx (open-source verze) automaticky vyřadí backend, který přestane
odpovídat, na základě parametrů max_fails a fail_timeout:

upstream aplikace {
    server 10.0.0.11:8000 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:8000 max_fails=3 fail_timeout=30s;
}

Pokud daný backend selže třikrát (max_fails=3) za sebou, Nginx ho na 30
sekund (fail_timeout=30s) přestane zkoušet a požadavky posílá jen
zbylým funkčním backendům. Po uplynutí této doby ho opět zkusí zahrnout.

Praktický příklad

Kompletní konfigurace reverzní proxy před dvěma instancemi Django
aplikace běžícími na portech 8001 a 8002 stejného serveru (typická
konfigurace, kdy jeden fyzický/virtuální server běží víc worker procesů
aplikace pro využití více CPU jader):

upstream kurzy_app {
    least_conn;
    server 127.0.0.1:8001 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8002 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    server_name kurzy.example.cz;

    location /static/ {
        alias /home/petr/ai/tutor/staticfiles/;
        expires 30d;
    }

    location / {
        proxy_pass http://kurzy_app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Statické soubory (/static/) se servírují přímo z disku (rychlejší, bez
zatěžování aplikace), zatímco vše ostatní jde přes proxy_pass na jednu
ze dvou běžících instancí aplikace podle zátěže (least_conn). Po úpravě
opět následuje nginx -t a systemctl reload nginx.

Shrnutí

Nginx jako reverzní proxy předává požadavky aplikačnímu serveru pomocí
proxy_pass, přičemž hlavičky X-Real-IP, X-Forwarded-For a
X-Forwarded-Proto zajišťují, že aplikace nepřijde o informaci o
původním klientovi a protokolu. Blok upstream seskupuje víc backendů a
umožňuje rozložení zátěže (round-robin ve výchozím stavu, least_conn
nebo weight pro pokročilejší scénáře) i automatické vyřazení nedostupných
serverů pomocí max_fails/fail_timeout.

Kontrolní otázky

  1. Proč se bez hlaviček X-Real-IP a X-Forwarded-For aplikace za
    reverzní proxy "nedozví" skutečnou IP adresu klienta?
  2. Jaký je rozdíl mezi výchozí round-robin strategií a least_conn v
    upstream bloku a kdy byste dali přednost druhé z nich?
  3. K čemu slouží parametry max_fails a fail_timeout u serveru v
    upstream bloku?
  4. Rozšiřte ukázkovou konfiguraci o třetí backend na portu 8003, který má
    sloužit jen jako záložní server (backup), a vysvětlete, kdy se
    použije.

Lekce na sebe nejsou zamčené — libovolnou lekci můžete otevřít i označit jako hotovou v jakémkoliv pořadí.