Traefik v2 Weighted Load Balancing pro servery

Home » Blog » Traefik v2 Weighted Load Balancing for Servers

Při hledání reverzního proxy, které by sloužilo jako vstupní bod pro naší infrastrukturu serverů, jsme narazili na vynikající Traefik. Samozřejmě, že jsme chtěli vyvažování zátěže a samozřejmě, že jsme chtěli přiřazovat váhy; proto jsme prolistovali dokumentaci a rychle našli, co jsme hledali. Traefik podporuje vyvažování zátěže v podobě Váženého kolo robin (WRR), které směruje webový provoz cyklicky přes dostupné služby. To znělo přesně jako to, co jsme chtěli. Ovšem, jak jsme zjistili, nebylo to úplně tak.

Aktualizace: O den poté, co jsme navrhli nápravu v jejich úložišti GitHub, tým Traefik začal pracovat na této funkci. Nakonec bude součástí Traefiku verze 3.0.

TL;DR, můžete přejít přímo k řešení.

Služby versus Servery

Přehlédli jsme jednu věc: malou poznámku v dokumentaci, že WRR je dostupný pouze pro služby, ne pro servery.

Tato strategie je k dispozici pouze pro vyvažování zátěže mezi službami a ne mezi servery.

Původně jsme museli naučit konfigurovat Traefik correctamente a nevěděli, co služby a servery znamenají v kontextu Traefiku. Teď, když to víme, chceme sloužit jednu službu na několika serverech následovně:

services:
  my_service:
    loadBalancer:
      servers:
        - url: "https://server1:1234/"
        - url: "https://server2:1234/"
        - url: ...

Protože každý server má jiný výkon, jsme také chtěli vážit každý server. Ovšem není možné přiřadit váhy serverům v tomto nastavení a existuje otevřený problém na GitHubu od roku 2019.

Pouze Traefik v1 měl vlastnost váhy, ale nechtěli jsme se spoléhat na zastaralý software.

Návrhy

Měli jsme různé nápady, jak překonat tuto chybějící funkci.

Návrh 1: Zneužití kontrol zdraví pro simulaci špatných serverů

Představovali jsme si, že bychom mohli napodobit špatný server, když by dosáhl svého maximálního zatížení. Tímto způsobem by Traefik přestal směrovat provoz na tento server, jakmile by neměl žádné volné sloty.

# Example health check configuration

http:
  services:
    my-service:
      loadBalancer:
        healthCheck:
          path: /health
          interval: "5s"
          timeout: "3s"
<pre class="wp-block-syntaxhighlighter-code"># Example FastAPI endpoint

@app.get('/health')
def health():
    if COMPUTE_CAPABILITY / job_q.qsize() < 1:
        return Response('Too Many Requests', status_code=429)

    return Response('OK', status_code=200)</pre>

Kdykoli by fronta úloh převyšovala výpočetní schopnost serveru, hlásila by se jako nedostupná Traefiku nejpozději do osmi sekund. Traefik by pak tento server odebral z kola a nechal ostatní servery obsluhovat požadavek. Otestovali jsme toto řešení a ukázalo se, že to způsobilo problémy.

Nejprve je dropout serveru pomalý. I když byl server přetížený, stále dostával požadavky, dokud kontroly zdraví nahlásily nedostupnost. Osm sekund může být dlouhá doba, a snížit interval nebylo možností pro udržení zatížení serveru zvládnutelného.

Druhým a nejvíce překvapivě jsme narazili na chyby NS_ERROR_NET_PARTIAL_TRANSFER při některých požadavcích. Nevyšetřovali jsme to dále, ale věříme, že k tomu došlo proto, že servery byly zachyceny mezi obsluhou dlouhotrvajících žádostí a hlášeny jako nefunkční, což způsobilo přerušení klientské konektivity. Údržba takové infrastruktury je nežádoucí.

Třetím problémem bylo, že náš servis je stavový. To znamená, že používáme tzv. sticky relace, čímž zajišťujeme, aby každý klient komunikoval pouze s jedním přiděleným serverem přiřazeným na začátku relace. Pokud jeden z těchto serverů selže během relace, představuje to problém, o kterém jsme si mysleli, že ho můžeme vyřešit. Nicméně jsme nemohli. Proto jsme odmítli Ideu 1.

Idea 2: Manipulace se sesiónem

Při nastavení relace používáme JavaScriptový fetch() s přihlédnutím k přihlašovacím údajům:

await fetch('https://edge_server/', {credentials: 'include'})

Tímto způsobem Traefik přidělí klientovi novou relační cookie pomocí hlavičky Set-Cookie při inicializačním požadavku fetch.

# Example Traefik configuration with sticky sessions enabled

services:
  my_service:
    loadBalancer:
      sticky:
        cookie:
          name: session_name
          httpOnly: true

Pokud bychom vynechali přihlašovací údaje v žádosti, Traefik by přiřadil nové sezení při každé nové žádosti pomocí postupu kolo kolo. Druhý nápad byl umožnit klientovi shromažďovat nová sezení, dokud nenalezl server s volnými sloty.
left.

Museli jsme definovat nový koncový bod dispatch FastAPI, který informuje klienta, zda má server za aktuálním sezením volné sloty, a také metodu opakování fetch v JavaScriptu, abychom našli správný server. Pro koncový bod jsme mohli znovu použít metodu zdraví() z výše uvedeného textu. Pro metodu opakování fetch jsme samozřejmě také chtěli zachytit případ, kdy jsou všechny servery obsazené. Připravili jsme si toto:

<pre class="wp-block-syntaxhighlighter-code">async function fetch_retry(...args) {
    
    for (let i = 0; i < 300; i++) {
        for (let s = 0; s < N_SERVERS; s++) {
            const response = await fetch(...args);
    
            if (response.status === 503) {
                // Waiting for a free slot...
                if (s === (N_SERVERS - 1)) {
                  await new Promise(r => setTimeout(r, 20000));
                }
                continue;
            }
            
            return response;
        }
    }
    
    return response;
}
</pre>

Teď, před jakýmkoliv dalším voláním fetch(), jsme zavolali náš koncový bod dispatch bez přihlašovacích údajů:

await fetch_retry('https://edge_server/dispatch');

A skutečně jsme obdrželi nový hlavičkový řádek Set-Cookie pro každou iteraci smyčky.

Naneštěstí se stávající relační cookie při volání fetch_retry() neaktualizuje, jelikož k tomu dochází pouze tehdy, když jsou zahrnuty přihlašovací údaje, alespoň u prohlížeče Firefox. Pak jsme zkusili tyto údaje zahrnout a clears sessionStorage, když máme relaci pro „špatný“ server. Bohužel však sessionStorage.clear() nezruší ani cookie s přihlašovacím údajem, přestože jde také o relační cookie. Platí to i tehdy, když nastavíme httpOnly na false (ačkoliv to není doporučováno z důvodů bezpečnosti).

Tak jsme museli strávit další noc předtím, než nám došlo jiné řešení.

Konečné řešení: Falešné servery

Finální nápad vycházel z otázky: Jak Traefik zpracovává adresy serverů?

Pokud bychom mohli směrovat několik různých URL adres na stejné серверy v Traefiku, mohli bychom emulovat vážení serverů tím, že budeme fiktivně vytvářet několik serverových URL adres, které všechny směřují na stejné servery. A opravdu, tato metoda fungovala podle očekávání. Museli jsme pouze přidat neexistující cestu, jako například „/1/“ nebo „/2/“, k URL adresám a Traefik je zpracoval samostatně, ale bez chyb a bez připojení cesty k pravému serveru.

services:
  my_service:
    loadBalancer:
      servers:
        - url: "https://server_A:1234/1/"
        - url: "https://server_A:1234/2/"
        - url: "https://server_B:1234/"

Museli jsme zajistit, aby vytvořili odpovídající počet redundantních fiktivních serverových URL adres podle příslušné schopnosti serverů. Pokud bychom například měli server A, který byl dvakrát schopnější než server B, pak jsme museli přidat dvě URL adresy pro server A. Tento způsob, během Round Robin, by server A obdržel dvakrát více požadavků než server B.

Share it