Пяць промахаў пры разгортванні першага дадатку на Kubernetes

Пяць промахаў пры разгортванні першага дадатку на KubernetesFail by Aris-Dreamer

Многія лічаць, што дастаткова перанесці дадатак на Kubernetes (альбо з дапамогай Helm, альбо ўручную) — і будзе шчасце. Але не ўсё так проста.

Каманда Mail.ru Cloud Solutions перавяла артыкул DevOps-інжынера Джуліяна Гіндзі. Ён расказвае, з якімі падводнымі камянямі яго кампанія сутыкнулася ў працэсе міграцыі, каб вы не наступалі на тыя ж граблі.

Крок першы: настройка запытаў пода і лімітаў

Пачнём з наладкі чыстага асяроддзя, у якім будуць працаваць нашы поды. Kubernetes цудоўна спраўляецца з планаваннем падоў і апрацоўкай станаў адмовы. Але аказалася, што планавальнік часам не можа размясціць пад, калі абцяжарваецца ацаніць, колькі рэсурсаў яму трэба для паспяховай працы. Менавіта тут усплываюць запыты на рэсурсы і ліміты. Вядзецца шмат спрэчак аб найлепшым падыходзе да наладкі запытаў і лімітаў. Часам падаецца, што гэта сапраўды хутчэй мастацтва, чым навука. Вось наш падыход.

Запыты пода (pod requests) - гэта асноўнае значэнне, якое выкарыстоўваецца планавальнікам для аптымальнага размяшчэння пода.

З дакументацыі Kubernetes: на кроку фільтрацыі вызначаецца набор вузлоў, дзе можна запланаваць пад. Напрыклад, фільтр PodFitsResources правярае, ці дастаткова на вузле рэсурсаў для задавальнення канкрэтных запытаў пода на рэсурсы.

Запыты прыкладанняў мы выкарыстоўваем так, каб па іх можна было ацаніць, колькі рэсурсаў на самой справе трэба з дадаткам для нармальнай працы. Так планавальнік зможа рэалістычна размясціць вузлы. Першапачаткова мы хацелі ўсталяваць запыты з запасам, каб гарантаваць дастаткова вялікую колькасць рэсурсаў для кожнага пода, але заўважылі, што час планавання значна павялічыўся, а некаторыя поды так і не былі цалкам запланаваны, нібыта для іх не паступіла ніякіх запытаў на рэсурсы.

У гэтым выпадку планавальнік часта "выціскаў" поды і не мог паўторна іх запланаваць з-за таго, што плоскасць кіравання паняцці не мела, колькі рэсурсаў запатрабуецца з дадаткам, хоць гэта ключавы кампанент алгарытму планавання.

Ліміты пода (pod limits) - Гэта больш дакладнае абмежаванне для пода. Яно ўяўляе сабой максімальны аб'ём рэсурсаў, які кластар вылучыць кантэйнеру.

Зноў жа, з афіцыйнай дакументацыі: калі для кантэйнера ўсталяваны ліміт памяці 4 ГіБ, то kubelet (і асяроддзе выканання кантэйнера) увядзе яго прымусова. Асяроддзе выканання не дазваляе кантэйнеру выкарыстоўваць больш зададзенага ліміту рэсурсаў. Напрыклад, калі працэс у кантэйнеры спрабуе выкарыстоўваць больш дапушчальнага аб'ёму памяці, ядро ​​сістэмы завяршае гэты працэс з памылкай "out of memory" (OOM).

Кантэйнер заўсёды можа выкарыстоўваць больш рэсурсаў, чым паказана ў запыце на рэсурсы, але ніколі не можа выкарыстоўваць больш, чым указана ў абмежаванні. Гэтае значэнне складана ўсталяваць правільна, але яно вельмі важна.

У ідэале мы жадаем, каб патрабаванні да рэсурсаў пода змяняліся на працягу жыццёвага цыклу працэсу, не ўмешваючыся ў іншыя працэсы ў сістэме - гэта мэта ўсталявання лімітаў.

Нажаль, я не магу даць пэўныя ўказанні, якія значэнні ўсталёўваць, але мы самі прытрымліваемся наступных правіл:

  1. Выкарыстоўваючы інструмент нагрузачнага тэсціравання, мадэлюем базавы ўзровень трафіку і назіраем за выкарыстаннем рэсурсаў пода (памяці і працэсара).
  2. Усталеўваны запыты падачы на ​​адвольна нізкае значэнне (з абмежаваннем рэсурсаў прыкладна ў 5 разоў больш за значэнне запытаў) і назіраем. Калі запыты на занадта нізкім узроўні, працэс не можа пачацца, што часта выклікае загадкавыя памылкі часу выканання Go.

Жадаю адзначыць, што больш высокія абмежаванні рэсурсаў ускладняюць планаванне, паколькі поду патрэбен мэтавы вузел з дастатковай колькасцю даступных рэсурсаў.

Прадстаўце сітуацыю, калі ў вас легкаважны вэб-сервер з вельмі высокім абмежаваннем рэсурсаў, напрыклад 4 ГБ памяці. Верагодна, гэты працэс прыйдзецца маштабаваць гарызантальна і кожны новы модуль давядзецца планаваць на вузле з даступным аб'ёмам памяці не менш за 4 ГБ. Калі такога вузла не існуе, кластар павінен увесці новы вузел для апрацоўкі гэтага пода, што можа заняць некаторы час. Важна дабіцца мінімальнай розніцы паміж запытамі рэсурсаў і лімітамі, каб забяспечыць хуткае і плыўнае маштабаванне.

Крок другой: настройка тэстаў Liveness і Readiness

Гэта яшчэ адна тонкая тэма, якая часта абмяркоўваецца ў супольнасці Kubernetes. Важна добра разбірацца ў тэстах жыццяздольнасці (Liveness) і гатоўнасці (Readiness), паколькі яны забяспечваюць механізм устойлівай працы праграмнага забеспячэння і мінімізуюць час прастою. Аднак яны могуць нанесці сур'ёзны ўдар па прадукцыйнасці вашага прыкладання, калі не настроены правільна. Ніжэй прыводзіцца кароткі выклад, што з сябе ўяўляюць абедзве пробы.

Жвавасць паказвае, ці працуе кантэйнер. Калі яна выходзіць са строю, kubelet забівае кантэйнер, і для яго ўключаецца палітыка перазапуску. Калі кантэйнер не абсталяваны Liveness-пробай, то станам па змаўчанні будзе поспех так гаворыцца ў дакументацыі Kubernetes.

Пробы Liveness павінны быць таннымі, гэта значыць не спажываць шмат рэсурсаў, таму што яны запускаюцца часта і павінны інфармаваць Kubernetes, што дадатак запушчана.

Калі вы ўсталюеце параметр для запуску кожную секунду, тое гэта дадасць 1 запыт у секунду, так што прыміце да ўвагі, што для апрацоўкі гэтага трафіку спатрэбяцца дадатковыя рэсурсы.

У нас у кампаніі тэсты Liveness правяраюць асноўныя кампаненты прыкладання, нават калі дадзеныя (напрыклад, з выдаленай базы дадзеных ці кэша) не цалкам даступныя.

Мы наладзілі ў дадатках канчатковы пункт «працаздольнасці», які проста вяртае код адказу 200. Гэта паказчык таго, што працэс запушчаны і здольны апрацоўваць запыты (але яшчэ не трафік).

проба Гатоўнасць паказвае, ці гатовы кантэйнер да абслугоўвання запытаў. Калі спроба гатовасці выходзіць з ладу, кантролер канчатковых кропак выдаляе IP-адрас пода з канчатковых кропак усіх службаў, якія адпавядаюць поду. Гэта таксама гаворыцца ў дакументацыі Kubernetes.

Пробы Readiness спажываюць больш рэсурсаў, так як яны павінны трапляць у бэкэнд такім чынам, каб паказаць гатоўнасць дадатку да прыёму запытаў.

У суполцы вядзецца шмат спрэчак, ці варта звяртацца непасрэдна да базы дадзеных. Улічваючы накладныя выдаткі (праверкі выконваюцца часта, але іх можна рэгуляваць), мы вырашылі, што для некаторых прыкладанняў гатоўнасць абслугоўваць трафік залічваецца толькі пасля праверкі таго, што з базы даных вяртаюцца запісы. Добра прадуманыя пробы гатоўнасці забяспечылі больш высокі ўзровень даступнасці і ўхілілі прастоі падчас разгортвання.

Калі вы вырашыце рабіць запыт да базы дадзеных для праверкі гатоўнасці прыкладання, пераканайцеся, што ён абыходзіцца як мага танней. Возьмем такі запыт:

SELECT small_item FROM table LIMIT 1

Вось прыклад, як мы наладжваем гэтыя два значэнні ў Kubernetes:

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

Можна дадаць некаторыя дадатковыя параметры канфігурацыі:

  • initialDelaySeconds - колькі секунд пройдзе паміж запускам кантэйнера і пачаткам запуску спроб.
  • periodSeconds - Інтэрвал чакання паміж запускамі спроб.
  • timeoutSeconds - Колькасць секунд, па заканчэнні якіх пад лічыцца аварыйным. Звычайны тайм-аўт.
  • failureThreshold - Колькасць адмоў тэстаў, перш чым у пад будзе адпраўлены сігнал перазапуску.
  • successThreshold - Колькасць паспяховых спроб, перш чым пад пераходзіць у стан гатоўнасці (пасля збою, калі пад запускаецца або аднаўляецца).

Крок трэці: настройка дэфолтных сеткавых палітык

У Kubernetes «плоская» сеткавая тапаграфія, па змаўчанні ўсе поды ўзаемадзейнічаюць сябар з сябрам напроста. У некаторых выпадках гэта непажадана.

Патэнцыйная праблема бяспекі заключаецца ў тым, што зламыснік можа выкарыстоўваць адзінае ўразлівае дадатак, каб адпраўляць трафік на ўсе поды ў сетцы. Як і ў многіх галінах бяспекі, тут прымянім прынцып найменшых прывілеяў. У ідэале сеткавыя палітыкі павінны відавочна паказваць, якія злучэнні паміж подамі дазволеныя, а якія не.

Напрыклад, ніжэй прыведзена простая палітыка, якая забараняе ўвесь уваходны трафік для канкрэтнай прасторы імёнаў:

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

Візуалізацыя гэтай канфігурацыі:

Пяць промахаў пры разгортванні першага дадатку на Kubernetes
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
Больш падрабязна тут.

Крок чацвёрты: нестандартныя паводзіны з дапамогай хукаў і init-кантэйнераў

Адной нашых з галоўных задач было забеспячэнне дэплояў у Kubernetes без прастою для распрацоўшчыкаў. Гэта цяжка з-за таго, што існуе мноства варыянтаў завяршэння працы прыкладанняў і вызвалення імі скарыстаных рэсурсаў.

Асаблівыя цяжкасці ўзніклі з Nginx. Мы заўважылі, што пры паслядоўным разгортванні гэтых подаў актыўныя злучэнні перарываліся да паспяховага завяршэння.

Пасля шырокіх пошукаў у інтэрнэце высветлілася, што Kubernetes не чакае, пакуль злучэнні Nginx вычарпаюць сябе, перш чым завяршыць працу пода. З дапамогай pre-stop хука мы ўкаранілі такую ​​функцыянальнасць і цалкам пазбавіліся ад даунтайма:

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

А вось 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

Яшчэ адна надзвычай карысная парадыгма - выкарыстанне init-кантэйнераў для апрацоўкі запуску канкрэтных прыкладанняў. Гэта асабліва карысна ў выпадку, калі ў вас ёсць рэсурсаёмісты працэс міграцыі базы дадзеных, які трэба запусціць да запуску прыкладання. Для гэтага працэсу вы таксама можаце пазначыць больш высокі ліміт рэсурсаў, не усталёўваючы такі ліміт для асноўнага прыкладання.

Іншы распаўсюджанай схемай з'яўляецца доступ да сакрэтаў у init-кантэйнеры, які дае гэтыя ўліковыя дадзеныя галоўнаму модулю, што прадухіляе несанкцыянаваны доступ да сакрэтаў з самага асноўнага модуля прыкладання.

Як звычайна, цытата з дакументацыі: init-кантэйнеры бяспечна запускаюць карыстацкі код або ўтыліты, якія інакш знізяць бяспеку выявы кантэйнера прыкладання. Захоўваючы асобна непатрэбныя інструменты, вы абмяжоўваеце паверхню атакі ладу кантэйнера прыкладання.

Крок пяты: настройка ядра

Напрыканцы раскажам пра больш прасунутую тэхніку.

Kubernetes - выключна гнуткая платформа, якая дазваляе запускаць працоўныя нагрузкі так, як вы лічыце патрэбным. У нас ёсць шэраг высокаэфектыўных прыкладанняў, якія патрабуюць надзвычай шмат рэсурсаў. Правёўшы шырокае нагрузачнае тэсціраванне, мы выявілі, што адно з прыкладанняў з цяжкасцю спраўляецца з чаканай нагрузкай трафіку, калі дзейнічаюць налады Kubernetes па змаўчанні.

Аднак Kubernetes дазваляе запусціць прывілеяваны кантэйнер, які змяняе параметры ядра толькі для канкрэтнага пода. Вось што мы выкарыстоўвалі для змены максімальнай колькасці адкрытых злучэнняў:

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

Гэта больш прасунутая тэхніка, якая часта не патрэбна. Але калі ваша прыкладанне ледзь спраўляецца з вялікай нагрузкай, можаце паспрабаваць наладзіць некаторыя з гэтых параметраў. Больш падрабязная інфармацыя аб гэтым працэсе і наладзе розных значэнняў - як заўсёды у афіцыйнай дакументацыі.

У заключэнне

Хоць Kubernetes можа здацца гатовым рашэннем са скрынкі , для бесперабойнай працы прыкладанняў неабходна зрабіць некалькі ключавых крокаў.

На працягу ўсёй міграцыі на Kubernetes важна прытрымлівацца «цыклу нагрузачнага тэставання»: запускаеце дадатак, тэстуеце яго пад нагрузкай, назіраеце за метрыкамі і паводзінамі пры маштабаванні, наладжваеце канфігурацыю на аснове гэтых дадзеных, затым зноў паўтараеце гэты цыкл.

Рэалістычна ацаніце чаканы трафік і паспрабуйце выйсці за яго мяжу, каб паглядзець, якія кампаненты зламаюцца першымі. З такім ітэрацыйным падыходам для дасягнення поспеху можа хапіць толькі некалькі з пералічаных рэкамендацый. Або можа запатрабавацца глыбейшая налада.

Заўсёды задавайце сабе такія пытанні:

  1. Колькі рэсурсаў спажываюць прыкладанні і як зменіцца гэты аб'ём?
  2. Якія рэальныя патрабаванні да маштабавання? Колькі трафіку ў сярэднім будзе апрацоўваць дадатак? А як наконт пікавага трафіку?
  3. Як часта сэрвісу запатрабуецца гарызантальнае маштабаванне? Як хутка трэба ўводзіць у строй новыя поды, каб прымаць трафік?
  4. Наколькі карэктна завяршаецца праца подаў? Ці трэба гэта ўвогуле? Ці можна дамагчыся разгортвання без даунтайма?
  5. Як мінімізаваць рызыкі для бяспекі і абмежаваць шкоду ад любых скампраметаваных подаў? Ці ёсць у нейкіх сэрвісаў дазволы або доступы, якія ім не патрабуюцца?

Kubernetes дае неверагодную платформу, якая дазваляе выкарыстоўваць лепшыя практыкі для разгортвання тысяч сэрвісаў у кластары. Тым не менш усе прыкладанні розныя. Часам ўкараненне патрабуе крыху больш працы.

На шчасце, Kubernetes дае неабходныя наладкі для дасягнення ўсіх тэхнічных мэт. Выкарыстоўваючы камбінацыю запытаў рэсурсаў і лімітаў, спроб Liveness і Readiness, init-кантэйнераў, сеткавых палітык і нестандартнай наладкі ядра, вы можаце дамагчыся высокай прадукцыйнасці нараўне з адмоваўстойлівасцю і хуткай маштабаванасцю.

Што яшчэ пачытаць:

  1. Лепшыя практыкі і рэкамендацыі для запуску кантэйнераў і Kubernetes у вытворчых асяроддзях.
  2. 90+ карысных інструментаў для Kubernetes: разгортванне, кіраванне, маніторынг, бяспека і не толькі.
  3. Наш канал Вакол Kubernetes у Тэлеграме.

Крыніца: habr.com

Дадаць каментар