Kubernetes tippek és trükkök: a kecses leállítás jellemzői az NGINX és a PHP-FPM rendszerben

Tipikus feltétel a CI/CD Kubernetesben való implementálásakor: az alkalmazásnak képesnek kell lennie arra, hogy ne fogadjon el új klienskéréseket a teljes leállás előtt, és ami a legfontosabb, sikeresen teljesítse a meglévőket.

Kubernetes tippek és trükkök: a kecses leállítás jellemzői az NGINX és a PHP-FPM rendszerben

Ennek a feltételnek való megfelelés lehetővé teszi, hogy nulla állásidőt érjen el a telepítés során. Azonban még nagyon népszerű csomagok (például NGINX és PHP-FPM) használatakor is előfordulhatnak olyan nehézségek, amelyek minden egyes telepítéskor hibatöbblethez vezetnek...

Elmélet. Hogyan él a pod

A hüvely életciklusáról már részletesen publikáltunk ez a cikk. A vizsgált témával összefüggésben a következőkre vagyunk kíváncsiak: abban a pillanatban, amikor a pod állapotba kerül megszüntetéséről, az új kérések nem érkeznek rá (pod eltávolították a szolgáltatás végpontjainak listájából). Így a telepítés közbeni leállások elkerülése érdekében elegendő, ha megoldjuk az alkalmazás helyes leállításának problémáját.

Ne feledje azt is, hogy az alapértelmezett türelmi időszak 30 másodperc: ezt követően a pod megszűnik, és az alkalmazásnak időnek kell lennie az összes kérés feldolgozására ezen időszak előtt. Megjegyzés: bár minden 5-10 másodpercnél tovább tartó kérés már problémás, és ezen már a kecses leállítás sem segít...

Ahhoz, hogy jobban megértse, mi történik, ha egy pod leáll, nézze meg a következő diagramot:

Kubernetes tippek és trükkök: a kecses leállítás jellemzői az NGINX és a PHP-FPM rendszerben

A1, B1 - Változások fogadása a kandalló állapotával kapcsolatban
A2 - Indulás SIGTERM
B2 – Pod eltávolítása a végpontokról
B3 – Változások fogadása (a végpontok listája megváltozott)
B4 - Az iptables szabályok frissítése

Figyelem: a végpont pod törlése és a SIGTERM elküldése nem egymás után, hanem párhuzamosan történik. És mivel az Ingress nem kapja meg azonnal a végpontok frissített listáját, a kliensek új kérései a podba kerülnek, ami 500-as hibát okoz a pod leállítása során. (a kérdéssel kapcsolatos részletesebb anyagokért, mi lefordított). Ezt a problémát a következő módokon kell megoldani:

  • Kapcsolat küldése: zárja be a válaszfejléceket (ha ez HTTP-alkalmazásra vonatkozik).
  • Ha nem lehetséges a kód módosítása, akkor a következő cikkben egy olyan megoldást ismertetünk, amely lehetővé teszi a kérések feldolgozását a türelmi időszak végéig.

Elmélet. Hogyan fejezi be folyamatait az NGINX és a PHP-FPM

nginx

Kezdjük az NGINX-szel, hiszen vele többé-kevésbé minden nyilvánvaló. Ha belemerülünk az elméletbe, megtudjuk, hogy az NGINX-nek egy főfolyamata és több „dolgozója” van – ezek olyan utódfolyamatok, amelyek feldolgozzák az ügyfelek kéréseit. Egy kényelmes lehetőség biztosított: a parancs használata nginx -s <SIGNAL> leállíthatja a folyamatokat gyorsleállítási vagy kecses leállítási módban. Nyilvánvalóan az utóbbi lehetőség érdekel minket.

Akkor minden egyszerű: hozzá kell adni preStop-hook egy parancs, amely kecses leállítási jelet küld. Ezt megteheti a Telepítésben, a tárolóblokkban:

       lifecycle:
          preStop:
            exec:
              command:
              - /usr/sbin/nginx
              - -s
              - quit

Most, amikor a pod leáll, a következőket fogjuk látni az NGINX konténernaplóiban:

2018/01/25 13:58:31 [notice] 1#1: signal 3 (SIGQUIT) received, shutting down
2018/01/25 13:58:31 [notice] 11#11: gracefully shutting down

Ez pedig azt jelenti, amire szükségünk van: az NGINX megvárja a kérések befejezését, majd leállítja a folyamatot. Az alábbiakban azonban egy olyan gyakori problémát is figyelembe veszünk, amely miatt még a paranccsal is nginx -s quit a folyamat hibásan fejeződik be.

És ebben a szakaszban készen vagyunk az NGINX-szel: legalább a naplókból megértheti, hogy minden úgy működik, ahogy kell.

Mi a helyzet a PHP-FPM-mel? Hogyan kezeli a kecses leállást? Találjuk ki.

PHP-FPM

A PHP-FPM esetében valamivel kevesebb az információ. Ha arra koncentrálsz hivatalos kézikönyv a PHP-FPM szerint a következő POSIX jeleket fogadja el:

  1. SIGINT, SIGTERM - gyors leállítás;
  2. SIGQUIT — kecses leállítás (amire szükségünk van).

A fennmaradó jelekre ebben a feladatban nincs szükség, ezért ezek elemzését mellőzzük. A folyamat helyes befejezéséhez meg kell írnia a következő preStop hookot:

        lifecycle:
          preStop:
            exec:
              command:
              - /bin/kill
              - -SIGQUIT
              - "1"

Első pillantásra ez minden, ami szükséges ahhoz, hogy mindkét tárolóban kecses leállítás történjen. A feladat azonban nehezebb, mint amilyennek látszik. Az alábbiakban két olyan eset látható, amikor a kecses leállítás nem működött, és a projekt rövid távú elérhetetlenségét okozta a telepítés során.

Gyakorlat. Lehetséges problémák a kecses leállítással

nginx

Először is hasznos megjegyezni: a parancs végrehajtása mellett nginx -s quit Van még egy szakasz, amire érdemes odafigyelni. Olyan problémába ütköztünk, hogy az NGINX továbbra is SIGTERM-et küld a SIGQUIT jel helyett, ami miatt a kérések nem fejeződtek be megfelelően. Hasonló eseteket találhatunk pl. itt. Sajnos nem tudtuk megállapítani ennek a viselkedésnek a konkrét okát: felmerült egy gyanú az NGINX verzióval kapcsolatban, de nem erősítették meg. A tünet az volt, hogy üzeneteket figyeltek meg az NGINX tárolónaplóiban: "nyissa ki a 10-es aljzatot az 5-ös csatlakozásnál", ami után a hüvely leállt.

Ilyen problémát figyelhetünk meg például az Ingress-re adott válaszokból:

Kubernetes tippek és trükkök: a kecses leállítás jellemzői az NGINX és a PHP-FPM rendszerben
Állapotkódok jelzői a telepítés időpontjában

Ebben az esetben csak egy 503-as hibakódot kapunk magától az Ingresstől: nem fér hozzá az NGINX tárolóhoz, mivel az már nem elérhető. Ha megnézi az NGINX konténernaplóit, a következőket tartalmazza:

[alert] 13939#0: *154 open socket #3 left in connection 16
[alert] 13939#0: *168 open socket #6 left in connection 13

A leállítási jel megváltoztatása után a konténer helyesen kezd leállni: ezt megerősíti, hogy az 503-as hiba már nem figyelhető meg.

Ha hasonló problémával találkozik, érdemes kitalálni, hogy melyik leállítási jelet használja a konténer, és pontosan hogyan néz ki a preStop horog. Nagyon valószínű, hogy az ok pontosan ebben rejlik.

PHP-FPM... és így tovább

A PHP-FPM problémáját triviálisan írjuk le: nem várja meg a gyermekfolyamatok befejezését, hanem leállítja azokat, ezért a telepítés és egyéb műveletek során 502 hiba lép fel. 2005 óta számos hibajelentés létezik a bugs.php.net oldalon (pl itt и itt), amely ezt a problémát írja le. De valószínűleg nem fog látni semmit a naplókban: a PHP-FPM hiba vagy harmadik fél értesítése nélkül jelenti be a folyamat befejezését.

Érdemes tisztázni, hogy maga a probléma kisebb vagy nagyobb mértékben függhet magától az alkalmazástól, és nem nyilvánulhat meg például a monitorozásban. Ha találkozik vele, először egy egyszerű megoldás jut eszébe: adjon hozzá egy preStop horgot sleep(30). Lehetővé teszi az összes korábbi kérés teljesítését (és újakat nem fogadunk el, mivel pod már képes megszüntetéséről), és 30 másodperc elteltével maga a pod egy jellel véget ér SIGTERM.

Kiderül, hogy lifecycle mert a konténer így fog kinézni:

    lifecycle:
      preStop:
        exec:
          command:
          - /bin/sleep
          - "30"

A 30 másodperc miatt azonban sleep mi vagyunk erősen növeljük a telepítési időt, mivel minden pod le lesz állítva minimum 30 másodperc, ami rossz. Mit lehet tenni ez ellen?

Forduljunk a kérelem közvetlen végrehajtásáért felelős félhez. A mi esetünkben az PHP-FPMhogy alapértelmezés szerint nem figyeli gyermekfolyamatainak végrehajtását: A főfolyamat azonnal leáll. Ezt a viselkedést az irányelv segítségével módosíthatja process_control_timeout, amely meghatározza azt az időkorlátot, amely alatt a gyermekfolyamatok várnak a mestertől érkező jelekre. Ha az értéket 20 másodpercre állítja, ez lefedi a tárolóban futó lekérdezések többségét, és leállítja a fő folyamatot, miután azok befejeződtek.

Ennek tudatában térjünk vissza utolsó problémánkhoz. Mint említettük, a Kubernetes nem egy monolitikus platform: a különböző összetevői közötti kommunikáció eltart egy ideig. Ez különösen igaz, ha figyelembe vesszük az Ingresses és más kapcsolódó komponensek működését, mivel a telepítéskori késedelem miatt könnyen előfordulhat, hogy 500-ra emelkedik a hiba. Például hiba léphet fel a kérés felfelé irányuló felé történő elküldésének szakaszában, de az összetevők közötti interakció „időeltolódása” meglehetősen rövid - kevesebb, mint egy másodperc.

ezért Összesen a már említett irányelvvel process_control_timeout a következő konstrukciót használhatja lifecycle:

lifecycle:
  preStop:
    exec:
      command: ["/bin/bash","-c","/bin/sleep 1; kill -QUIT 1"]

Ebben az esetben a késést a paranccsal kompenzáljuk sleep és ne növelje meg jelentősen a telepítési időt: van észrevehető különbség 30 másodperc és egy között?.. Valójában ez a process_control_timeoutÉs lifecycle csak „védőhálóként” használható késés esetén.

Általánosságban elmondható, a leírt viselkedés és a megfelelő megoldás nem csak a PHP-FPM-re vonatkozik. Hasonló helyzet így vagy úgy adódhat más nyelvek/keretrendszerek használatakor is. Ha a kecses leállást más módon nem tudja kijavítani - például a kód átírásával, hogy az alkalmazás megfelelően dolgozza fel a lezárási jeleket -, használhatja a leírt módszert. Lehet, hogy nem a legszebb, de működik.

Gyakorlat. Terhelési teszt a pod működésének ellenőrzésére

A terhelési tesztelés az egyik módja annak, hogy ellenőrizze a konténer működését, mivel ez az eljárás közelebb hozza a valós harci körülményekhez, amikor a felhasználók felkeresik az oldalt. A fenti ajánlások teszteléséhez használhatja Yandex.Tankom: Minden igényünket tökéletesen lefedi. Az alábbiakban tippeket és ajánlásokat adunk a teszteléshez, a tapasztalatainkból származó világos példával, a Grafana és a Yandex.Tank grafikonjainak köszönhetően.

A legfontosabb itt az lépésről lépésre ellenőrizze a változásokat. Új javítás hozzáadása után futtassa a tesztet, és nézze meg, hogy az eredmények változtak-e az utolsó futtatáshoz képest. Ellenkező esetben nehéz lesz azonosítani a nem hatékony megoldásokat, hosszú távon pedig csak árthat (például megnövelheti a telepítési időt).

Egy másik árnyalat, hogy meg kell nézni a konténernaplókat a megszüntetése során. Rögzítik a kecses leállásról szóló információkat? Vannak-e hibák a naplókban, amikor más erőforrásokhoz (például egy szomszédos PHP-FPM tárolóhoz) hozzáfér? Hibák az alkalmazásban (mint a fent leírt NGINX esetében)? Remélem, hogy a cikk bevezető információi segítenek jobban megérteni, mi történik a tárolóval a megszűnése során.

Tehát az első próbaüzemre anélkül került sor lifecycle és további direktívák nélkül az alkalmazáskiszolgálóhoz (process_control_timeout PHP-FPM-ben). A teszt célja a hibák hozzávetőleges számának meghatározása volt (és hogy van-e ilyen). Ezenkívül a további információk alapján tudnia kell, hogy az átlagos üzembe helyezési idő minden egyes pod esetében körülbelül 5-10 másodperc volt, amíg teljesen készen lett. Az eredmények a következők:

Kubernetes tippek és trükkök: a kecses leállítás jellemzői az NGINX és a PHP-FPM rendszerben

A Yandex.Tank információs panel 502 hibacsúcsot mutat, amely a telepítéskor fordult elő, és átlagosan 5 másodpercig tartott. Ez feltehetően azért volt, mert a régi podhoz érkezett kéréseket leállították, amikor azt megszüntették. Ezek után 503 hiba jelent meg, ami egy leállt NGINX konténer következménye volt, ami szintén megszakította a kapcsolatokat a backend miatt (ez megakadályozta, hogy az Ingress csatlakozzon hozzá).

Lássuk hogyan process_control_timeout PHP-FPM-ben segít megvárni a gyermekfolyamatok befejezését, azaz. javítsa ki az ilyen hibákat. Újratelepítés ezzel az irányelvvel:

Kubernetes tippek és trükkök: a kecses leállítás jellemzői az NGINX és a PHP-FPM rendszerben

Nincs több hiba az 500. telepítés során! A telepítés sikeres, kecses leállítás működik.

Érdemes azonban emlékezni az Ingress-tárolókkal kapcsolatos problémára, a hibák egy kis százalékára, amelyeket időeltolódás miatt kaphatunk. Ezek elkerülése érdekében nem kell mást tenni, mint hozzáadni egy szerkezetet -val sleep és ismételje meg a telepítést. Konkrét esetünkben azonban nem volt látható változás (ismét nem volt hiba).

Következtetés

A folyamat kecsesen befejezéséhez a következő viselkedést várjuk el az alkalmazástól:

  1. Várjon néhány másodpercet, majd hagyja abba az új kapcsolatok elfogadását.
  2. Várja meg, amíg az összes kérés befejeződik, és zárjon be minden olyan fenntartó kapcsolatot, amely nem hajt végre kéréseket.
  3. Fejezze be a folyamatot.

Azonban nem minden alkalmazás működik így. A probléma egyik megoldása a Kubernetes valóságában:

  • néhány másodpercig váró leállítás előtti horog hozzáadása;
  • háttérprogramunk konfigurációs fájljának tanulmányozása a megfelelő paraméterekért.

Az NGINX példája világossá teszi, hogy még egy olyan alkalmazásnak sem, amelynek először megfelelően kell feldolgoznia a lezárási jeleket, előfordulhat, hogy ezt nem teszi meg, ezért kritikus fontosságú az 500 hiba ellenőrzése az alkalmazás üzembe helyezése során. Ez azt is lehetővé teszi, hogy a problémát tágabban nézze meg, és ne egyetlen podra vagy konténerre összpontosítson, hanem a teljes infrastruktúra egészére nézzen.

Teszteszközként a Yandex.Tank bármilyen megfigyelő rendszerrel együtt használható (esetünkben az adatokat a Grafana Prometheus háttérprogramjával vettük át a teszthez). A kecses leállítással járó problémák jól láthatóak a benchmark által generált nagy terhelés alatt, és a monitorozás segít a helyzet részletesebb elemzésében a teszt alatt vagy után.

A cikkel kapcsolatos visszajelzésekre válaszolva: érdemes megemlíteni, hogy az NGINX Ingress-szel kapcsolatos problémákat és megoldásokat itt ismertetjük. Más esetekre más megoldások is vannak, amelyeket a sorozat következő anyagaiban megfontolhatunk.

PS

Egyéb a K8s tippek és trükkök sorozatból:

Forrás: will.com

Hozzászólás