Pět chyb při nasazování první aplikace na Kubernetes

Pět chyb při nasazování první aplikace na KubernetesZklamání od Aris-Dreamer

Mnozí si myslí, že stačí přenést aplikaci na Kubernetes (buď pomocí Helm, nebo ručně) a bude po všem. Ale není to tak jednoduché.

Tým Mail.ru Cloud Solutions Přeložila jsem článek DevOps inženýra Julianna Gindyho. Vypráví o tom, na jaké úskalí jeho společnost narazila během migrace, abyste se nemuseli potýkat se stejnými problémy.

Krok první: nastavení požadavků podu a limitů

Začneme s nastavením čistého prostředí, ve kterém budou naše pody fungovat. Kubernetes skvěle zvládá plánování podů a zpracování stavů selhání. Ukázalo se však, že plánovač někdy nemůže umístit pod, pokud nedokáže posoudit, kolik zdrojů potřebuje pro úspěšnou činnost. Právě zde se dostávají na scénu požadavky na zdroje a limity. O nejlepší přístup k nastavení požadavků a limitů se vedou mnohé diskuse. Někdy se zdá, že je to spíše umění než věda. To je náš přístup.

Požadavky podu (pod requests) jsou základní hodnotou, kterou plánovač používá k optimalizaci umístění podu.

Z dokumentace Kubernetes: při filtrování se určuje sada uzlů, kde lze pod naplánovat. Například filtr PodFitsResources kontroluje, zda má uzel dostatek zdrojů pro splnění konkrétních požadavků podu na zdroje.

Požadavky na aplikace používáme tak, aby bylo možné odhadnout, kolik zdrojů ve skutečnosti potřebuje aplikace pro normální provoz. Plánovač tak bude schopen realisticky umístit uzly. Zpočátku jsme chtěli nastavit požadavky s rezervou, abychom zajistili dostatek zdrojů pro každý pod, ale všimli jsme si, že doba plánování se významně zvýšila a některé pody vůbec nebyly úplně naplánovány, jako by na ně nebyly žádné požadavky na zdroje.

V takovém případě plánovač často "vytlačil" pody a nemohl je znovu naplánovat, protože řídící rovina neměla žádnou představu o tom, kolik zdrojů bude aplikace potřebovat, a to je klíčová součást plánovacího algoritmu.

Limity podu (pod limits) jsou přesnější omezení pro pod. Představují maximální objem zdrojů, které cluster vyčlenil kontejneru.

Opět jde o to, že oficiální dokumentaci: pokud je pro kontejner nastaven limit paměti 4 GiB, kubelet (a prostředí pro spouštění kontejnerů) ho automaticky vynutí. Prostředí nenechá kontejner využívat více, než je nastavený limit zdrojů. Například, když proces v kontejneru zkouší použít více povoleného množství paměti, jádro systému tento proces ukončuje s chybou „out of memory“ (OOM).

Kontejner může vždy využívat více zdrojů, než je uvedeno v žádosti o zdroje, ale nikdy nemůže používat více, než je určeno v limitu. Toto hodnotu je těžké správně nastavit, ale je velmi důležité.

Ideálně chceme, aby požadavky na zdroje podu se měnily během životního cyklu procesu, aniž by zasahovaly do jiných procesů v systému – to je cíl nastavení limitů.

Bohužel, nemohu poskytnout konkrétní pokyny, jaké hodnoty nastavit, ale my sami dodržujeme následující pravidla:

  1. Pomocí nástroje pro zátěžové testování modelujeme základní úroveň provozu a sledujeme využívání zdrojů podu (paměti a procesoru).
  2. Nastavujeme požadavky podu na libovolně nízkou hodnotu (s limitem zdrojů přibližně 5krát vyšším než hodnoty požadavků) a sledujeme. Když jsou požadavky na příliš nízké úrovni, proces se nemůže spustit, což často způsobuje záhadné chyby za běhu Go.

Chci zdůraznit, že vyšší limity zdrojů ztěžují plánování, protože pod potřebuje cílový uzel s dostatečným množstvím dostupných zdrojů.

Představte si scénář, kdy máte lehký webový server s velmi vysokým limitem zdrojů, například 4 GB paměti. Tento proces bude pravděpodobně nutné horizontálně škálovat, a každý nový modul bude třeba plánovat na uzlu s dostupným objemem paměti alespoň 4 GB. Pokud takový uzel neexistuje, cluster musí přidat nový uzel k vyřízení tohoto podu, což může nějakou dobu trvat. Je důležité dosáhnout co nejmenšího rozdílu mezi požadavky na zdroje a limity, aby se zajistilo rychlé a plynulé škálování.

Krok druhý: nastavení testů Liveness a Readiness

Toto je další jemné téma, které se často probírá v komunitě Kubernetes. Je důležité dobře rozumět testům životnosti (Liveness) a připravenosti (Readiness), protože tyto zajišťují mechanismus pro stabilní fungování softwaru a minimalizují prostoje. Mohou však vážně narušit výkon vaší aplikace, pokud nejsou správně nakonfigurovány. Níže je stručný přehled toho, co obě zkoušky obnášejí.

Liveness ukazuje, zda kontejner funguje. Pokud selže, kubelet zabije kontejner a zapne se politika restartu. Pokud kontejner není vybaven testem Liveness, výchozím stavem bude úspěch — jak je uvedeno v dokumentace Kubernetes.

Zkoušky Liveness by měly být levné, tedy neměly by spotřebovávat mnoho zdrojů, protože se spouštějí často a mají informovat Kubernetes, že aplikace je spuštěna.

Pokud nastavíte parametr pro spuštění každou sekundu, přidá to 1 požadavek za sekundu, takže mějte na paměti, že pro zpracování tohoto provozu budou zapotřebí další zdroje.

V naší společnosti testy Liveness kontrolují základní komponenty aplikace, i když data (například z dálkové databáze nebo cache) nejsou zcela dostupná.

V aplikacích jsme nastavili koncový bod „zdravotního stavu“, který jednoduše vrací kód odpovědi 200. To je indikací, že proces běží a je schopen zpracovávat požadavky (ale ještě ne provoz).

Zkouška Readiness ukazuje, zda je kontejner připraven na zpracování požadavků. Pokud zkouška připravenosti selže, řadič koncových bodů odstraní IP adresu podu ze všech služeb odpovídajících podu. To je také uvedeno v dokumentaci Kubernetes.

Zkoušky Readiness spotřebovávají více zdrojů, protože musí proniknout do backendu tak, aby ukázaly připravenost aplikace přijímat požadavky.

V komunitě se vedou četné debaty o tom, zda by se mělo přímo přistupovat k databázi. Vzhledem k režijním nákladům (kontroly se provádějí často, ale dají se škálovat) jsme se rozhodli, že pro některé aplikace se připravenost obsluhovat provoz počítá pouze po ověření, že se z databáze vrací záznamy. Dobře promyšlené zkoušky připravenosti zajistily vyšší úroveň dostupnosti a odstranily prostoje během nasazení.

Pokud se rozhodnete provést dotaz na databázi pro ověření připravenosti aplikace, ujistěte se, že je co nejlevnější. Vezměme si takový dotaz:

SELECT small_item FROM table LIMIT 1

Zde je příklad, jak nastavujeme tyto dvě hodnoty v Kubernetes:

livenessProbe: 
 httpGet:   
   path: /api/liveness    
   port: http 
readinessProbe:  
 httpGet:    
   path: /api/readiness    
   port: http  periodSeconds: 2

Můžete přidat několik dalších konfiguračních parametrů:

  • initialDelaySeconds — kolik sekund uběhne mezi spuštěním kontejneru a začátkem spuštění zkoušek.
  • periodSeconds — interval, po který se čeká mezi spuštěními zkoušek.
  • timeoutSeconds — počet sekund, po uplynutí kterých se pod považuje za zhroucený. Obvyklý časový limit.
  • failureThreshold — počet neúspěšných testů, než bude podu odeslán signál pro restart.
  • successThreshold — počet úspěšných zkoušek, než pod přejde do stavu připravenosti (po selhání, kdy se pod spouští nebo obnovuje).

Krok tři: nastavení výchozích síťových politik podu

V Kubernetes je „plošná“ síťová topologie, výchozím způsobem všechny podů vzájemně komunikují přímo. V některých případech to není žádoucí.

Potenciální bezpečnostní problém spočívá v tom, že útočník může využít jedno zranitelné aplikace k odesílání provozu na všechny podů v síti. Stejně jako v mnoha oblastech bezpečnosti zde platí princip nejmenších privilegií. Ideálně by síťové politiky měly jasně udávat, jaká spojení mezi podů jsou povolena a jaká nikoli.

Například níže je jednoduchá politika, která zakazuje veškerý příchozí provoz pro konkrétní název prostoru:

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:  
 name: default-deny-ingress
spec:  
 podSelector: {}  
 policyTypes:  
   - Ingress

Vizualizace této konfigurace:

Pět chyb při nasazování první aplikace na Kubernetes
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
Podrobnější informace zde.

Krok čtvrtý: Netypické chování pomocí hooků a init kontejnerů

Jedním z našich hlavních cílů bylo zajistit nasazení do Kubernetes bez prostojů pro vývojáře. Je to obtížné, protože existuje mnoho způsobů, jak ukončit aplikace a uvolnit použité zdroje.

Zvláštní obtíže vznikly s Nginx. Všimli jsme si, že při sekvenčním nasazení těchto podů byla aktivní připojení přerušena před jejich úspěšným dokončením.

Po rozsáhlém pátrání na internetu jsme zjistili, že Kubernetes nečeká, až se připojení Nginx vyčerpají, než pod ukončí. Pomocí pre-stop hooku jsme tuto funkcionalitu implementovali a zcela se zbavili prostojů:

lifecycle: 
 preStop:
   exec:
     command: ["/usr/local/bin/nginx-killer.sh"]

Ale nginx-killer.sh:

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
   echo "Waiting while shutting down nginx..."
   sleep 10
done

Další velmi užitečná paradigma je použití init kontejnerů pro zpracování spouštění konkrétních aplikací. To je zvlášť užitečné, pokud máte proces migrace databáze, který je zdrojožravý a měl by být spuštěn před spuštěním aplikace. Pro tento proces můžete také nastavit vyšší limit zdrojů, aniž byste takový limit nastavili pro hlavní aplikaci.

Další běžný scénář je přístup k tajemstvím v init kontejneru, který poskytuje tyto přihlašovací údaje hlavnímu modulu, čímž se brání neoprávněnému přístupu k tajemstvím z hlavního modulu aplikace.

Jak bývá obvyklé, citát z dokumentace: Init kontejnery bezpečně spouštějí uživatelský kód nebo nástroje, které by jinak snížily bezpečnost obrazu kontejneru aplikace. Uchováváním nepotřebných nástrojů odděleně omezujete útočnou plochu obrazu kontejneru aplikace.

Krok pátý: Nastavení jádra

Nakonec se podělíme o pokročilejší techniku.

Kubernetes je nesmírně flexibilní platforma, která umožňuje spouštět pracovní zátěže tak, jak uznáte za vhodné. Máme řadu vysoce výkonných aplikací, které vyžadují mimořádně vysoké zdroje. Po rozsáhlém zátěžovém testování jsme zjistili, že jedna z aplikací má potíže s očekávanou zátěží provozu, pokud platí výchozí nastavení Kubernetes.

Kubernetes však umožňuje spustit privilegovaný kontejner, který mění parametry jádra pouze pro daný pod. Zde je to, co jsme použili k úpravě maximálního počtu otevřených připojení:

initContainers:
  - name: sysctl
     image: alpine:3.10
     securityContext:
         privileged: true
      command: ['sh', '-c', "sysctl -w net.core.somaxconn=32768"]

Je to pokročilejší technika, která často není potřebná. Ale pokud si vaše aplikace sotva poradí s velkým zatížením, můžete zkusit nastavit některé z těchto parametrů. Podrobnější informace o tomto procesu a nastavení různých hodnot jsou vždy v oficiální dokumentaci.

Na závěr

Ačkoli se může Kubernetes jevit jako hotové řešení 'z krabice', pro bezproblémový provoz aplikací je třeba podniknout několik klíčových kroků.

Během celé migrace na Kubernetes je důležité dodržovat 'cyklus zátěžového testování': spustíte aplikaci, testujete ji pod zátěží, sledujete metriky a chování při škálování, nastavujete konfiguraci na základě těchto dat a poté tento cyklus opakujete.

Realisticky odhadněte očekávaný provoz a zkuste překročit jeho hranice, abyste zjistili, které komponenty selžou jako první. S takovým iterativním přístupem může být k dosažení úspěchu potřeba jen několik uvedených doporučení. Nebo může být nutné provést hlubší nastavení.

Vždy si pokládejte tyto otázky:

  1. Kolik zdrojů aplikace spotřebovávají a jak se tento objem změní?
  2. Jaké jsou skutečné požadavky na škálování? Kolik provozu bude průměrně zpracovávat aplikace? A co špičkový provoz?
  3. Jak často bude služba potřebovat horizontální škálování? Jak rychle je třeba uvést nové pody do provozu, aby přijaly provoz?
  4. Jak dobře se podové ukončují? Je to vůbec potřeba? Lze dosáhnout nasazení bez výpadku?
  5. Jak minimalizovat rizika pro bezpečnost a omezit škody způsobené jakýmikoli kompromitovanými pody? Má některá služba oprávnění nebo přístupy, které nepotřebuje?

Kubernetes poskytuje neuvěřitelnou platformu, která umožňuje využívat nejlepší postupy pro nasazení tisíců služeb v clusteru. Přesto jsou všechna aplikace odlišná. Někdy implementace vyžaduje trochu více práce.

Naštěstí Kubernetes poskytuje potřebná nastavení pro dosažení všech technických cílů. Použitím kombinace požadavků na zdroje a limitů, probe Liveness a Readiness, init kontejnerů, síťových politik a nestandardního nastavení jádra můžete dosáhnout vysokého výkonu spolu s odolností vůči chybám a rychlou škálovatelností.

Co dalšího si přečíst:

  1. Nejlepší postupy a doporučení pro spuštění kontejnerů a Kubernetes v produkčních prostředích.
  2. 90+ užitečných nástrojů pro Kubernetes: nasazení, správa, monitoring, bezpečnost a další.
  3. Náš kanál Kolem Kubernetes na Telegramu.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster