One-cloud — операционна система от ниво дата-център в Однокласниках

One-cloud — операционна система от ниво дата-център в Однокласниках

Здравейте, хора! Аз съм Олег Анастасиев, работя в Однокласниците в екипа на Платформата. Освен мен, в Однокласниците работи и много техника. Имаме четири дата центъра, в които има около 500 стелажа с повече от 8000 сървъра. В един момент разбрахме, че внедряването на нова система за управление ще ни позволи да използваме техниката по-ефективно, да улесним управлението на достъпа, да автоматизираме (преразпределението на) компютърните ресурси, да ускорим стартирането на нови услуги и да увеличим реакцията на мащабни аварии.

Какво излезе от всичко това?

Освен мен и многото техника, има и хора, които работят с тази техника: инженери, които са пряко в дата центровете; мрежови специалисти, които настройват мрежовото оборудване; администратори или SRE, които осигуряват устойчивост на инфраструктурата; и екипи от разработчици, всеки от които отговаря за част от функциите на портала. Създаваният от тях софтуер работи по следния начин:

One-cloud — операционна система от ниво дата-център в Однокласниках

Запитванията на потребителите поступват както на фронтовете на основния портал www.ok.ru, така и на други, например на фронтовете на API за музика. За обработка на бизнес логиката те извикват сървъра на приложенията, който при обработката на запитването извиква необходимите специализирани микросервизи — one-graph (граф на социалните връзки), user-cache (кеш на потребителските профили) и т. н.

Всеки от тези услуги е разположен на множество машини, и всеки от тях има отговорни разработчици, които отговарят за функционирането на модулите, тяхната експлоатация и технологично развитие. Всички тези услуги се стартират на физически сървъри, и до скоро ние стартирахме точно по една задача на един сървър, т. е. той беше специализиран за конкретна задача.

Защо така? Такъв подход имаше няколко предимства:

  • Улеснява масовото управление. Да предположим, че задачата изисква определени библиотеки, определени настройки. Тогава сървърът се приписва точно на една определена група, описва се политиката на cfengine за тази група (или тя вече е описана), и тази конфигурация централизирано и автоматично се разпределя на всички сървъри от тази група.
  • Улеснява диагностиката. Да предположим, че наблюдавате повишено натоварване на централния процесор и разбирате, че това натоварване може да е генерирано само от задачата, която работи на този хардуер. Търсенето на виновника приключва много бързо.
  • Улеснява мониторинг. Ако нещо не е наред със сървъра, мониторът уведомява за това и вие точно знаете кой е виновен.

На услугата, състояща се от няколко реплики, се назначават няколко сървъра – по един за всяка реплика. Тогава изчислителният ресурс за услугата се определя много просто: колкото сървъра има услугата, толкова ресурси максимално може да консумира. "Просто" тук не означава, че е лесно за използване, а че разпределението на ресурсите става ръчно.

Такъв подход също позволява да правим специализирани хардуерни конфигурации за задачата, изпълняваща се на този сървър. Ако задачата съхранява големи обеми данни, използваме 4U сървър с шаси за 38 диска. Ако задачата е чисто изчислителна, можем да закупим по-евтин 1U сървър. Това е ефективно от гледна точка на изчислителните ресурси. Включително такъв подход ни позволява да използваме четири пъти по-малко машини при натоварване, сравнимо с една социална мрежа, която ни е приятел.

Тази ефективност на използването на изчислителни ресурси трябва да осигури и икономическа ефективност, ако приемем, че най-скъпото е сървърите. Дълго време именно хардуерът беше най-скъпият компонент, и ние вложихме много усилия в намаляване на цената на хардуера, измисляйки алгоритми за осигуряване на отказоустойчивост, за да намалим изискванията за надеждност на оборудването. А днес достигнахме етап, в който цената на сървъра вече не е определяща. Ако не вземаме предвид свежата екзотика, конкретната конфигурация на сървърите в стелажа няма значение. В момента имаме друг проблем – цената на заеманото от сървъра място в дата центъра, т.е. мястото в стелажа.

Осъзнавайки, че е така, решихме да преброим колко ефективно използваме стелажите.
Взехме цената на най-мощния сървър в икономически оправданите, изчислихме колко такива сървъри можем да поберем в шкафовете, колко задачи бихме могли да стартираме на тях въз основа на старата модел "един сървър = една задача" и колко добре тези задачи биха могли да използват оборудването. Изчислихме - сълзи на очите. Оказа се, че ефективността на използване на шкафи е около 11%. Изводът е очевиден: нужно е да увеличим ефективността на използване на дата-центровете. На пръв поглед решението изглежда очевидно: трябва да стартираме няколко задачи на един сървър. Но тук започват трудностите.

Масовата конфигурация рязко се усложнява - сега е невъзможно да се назначи на сървъра някаква конкретна група. Нали на един сървър могат да бъдат стартирани няколко задачи от различни екипи. Освен това, конфигурацията може да създава конфликти за различни приложения. Диагностицирането също става по-сложно: ако видите увеличено потребление на процесори или дискове на сървъра, не знаете коя от задачите причинява проблеми.

Но най-важното е, че между задачите, стартирани на една машина, няма изолация. Например, графиката на средното време за отговор на сървърната задача преди и след като на същия сървър стартираме още едно, никак не свързано с първото, изчислително приложение - времето за получаване на отговор от основната задача значително се е увеличило.

One-cloud — операционна система от ниво дата-център в Однокласниках

Очевидно е, че задачите трябва да се стартират или в контейнери, или в виртуални машини. Тъй като почти всичките ни задачи се стартират под управлението на една ОС (Linux) или са адаптирани за нея, не се изисква поддържането на множество различни операционни системи. Съответно, виртуализация не е необходима, тъй като заради допълнителните разходи тя ще бъде по-малко ефективна от контейнеризацията.

Като решение за контейнеризация на задачи, които се изпълняват директно на Docker сървъри, е добър кандидат: образите на файловата система добре решават проблемите с конфликтуващи конфигурации. Фактът, че образите могат да се състоят от множество слоеве, позволява значително да намалим обема на данните, необходими за внедряване в инфраструктурата, отделяйки общите части в отделни основни слоеве. Тогава основните (и най-обемни) слоеве ще бъдат кеширани бързо в цялата инфраструктура и за доставянето на множество различни типове приложения и версии ще е необходимо да се предават само малки по обем слоеве.

Освен това, готовият регистър и таговете на образите в Docker ни осигуряват готови примитиви за версиониране и доставка на код в production.

Docker, подобно на всяка друга подобна технология, осигурява известен ниво на изолация на контейнерите от самото начало. Например, изолация по памет — всеки контейнер получава лимит на използването на паметта на машината, над който не може да оперира. Също е възможно да се изолират контейнерите по използването на CPU. За нас, обаче, стандартната изолация беше недостатъчна. Но за това — по-долу.

Непосредственото стартиране на контейнерите на сървърите е само част от проблемите. Другата част се отнася до разполагането на контейнерите на сървъри. Нужно е да разберем кой контейнер може да бъде поставен на кой сървър. Това не е толкова проста задача, защото контейнерите трябва да бъдат разположени на сървърите възможно най-плътно, без да се намалява скоростта на тяхната работа. Такова разполагане може да бъде сложно и от гледна точка на отказоустойчивост. Често искаме да разполагаме реплики на един и същ сервис в различни стойки или дори в различни зали на дата центъра, за да не загубим всички реплики на сервиса при отказ на стойка или зала.

Ръчно разпределяне на контейнерите — не е вариант, когато имате 8000 сървъра и 8000—16000 контейнера.

Освен това, искахме да предоставим на разработчиците повече независимост в разпределението на ресурсите, за да могат сами да разполагат своите услуги в production, без помощта на администратор. При това искахме да запазим контрола, така че някой вторичен сервис да не изразходва всичките ресурси на нашите дата центрове.

Очевидно е, че е необходим управляващ слой, който автоматично да се занимава с това.

Ето, ние достигнахме до проста и ясна картина, която всички архитекти обожават: три квадрата.

One-cloud — операционна система от ниво дата-център в Однокласниках

one-cloud masters — отказоустойчив кластер, който отговаря за оркестрацията на облака. Разработчикът изпраща в master манифест, съдържащ цялата необходима информация за разполагане на услугата. Master на основание на това дава команди на избраните миньони (машини, предназначени за стартиране на контейнери). На миньоните има наш агент, който получава командата, издава своите команди на Docker, а Docker конфигурира linux kernel за стартиране на съответния контейнер. Освен изпълнението на командите, агентът непрекъснато съобщава на master за измененията в състоянието както на машината-миньон, така и на стартираните на нея контейнери.

Распределение на ресурсите

А сега нека се запознаем със задачата за по-сложно разпределение на ресурсите за множество миньони.

Изчислителният ресурс в one-cloud — това е:

  • Изчислителната мощност на процесора, използвана от конкретна задача.
  • Обемът на паметта, достъпен за задачата.
  • Мрежовият трафик. Всеки от миньоните има конкретен мрежов интерфейс с ограничена пропускна способност, затова не можем да разпределяме задачи без да вземем предвид предавания обем данни по мрежата.
  • Дискове. Освен очевидно мястото под данни на задачата, ние също така определяме типа диск: HDD или SSD. Дисковете могат да обслужат краен брой заявки в секунда — IOPS. Затова за задачи, които генерират повече IOPS, отколкото един диск може да обслужи, ние също така определяме 'шпиндели' — т.е. дискови устройства, които е нужно изключително да резервираме за задачата.

Тогава за някаква услуга, например за user-cache, можем да запишем потребяваните ресурси по следния начин: 400 процесорни ядра, 2.5 Тб памет, 50 Гбит/с трафик в двете посоки, 6 Тб място на HDD, разположено на 100 шпиндела. Или по по-позната за нас форма така:

alloc:
    cpu: 400
    mem: 2500
    lan_in: 50g
    lan_out: 50g
    hdd:100x6T

Ресурсите на услугата user-cache консумират само част от всички налични ресурси в production-инфраструктурата. Затова искаме да направим така, че внезапно, поради грешка на оператора или не, user-cache да не потреби повече ресурси, отколкото му е определено. Тоест трябва да лимитираме ресурсите. Но с какво можем да свържем квотата?

Нека се върнем към нашата много опростена схема за взаимодействие на компонентите и да я прерисуваме с по-голямо количество детайли — ето така:

One-cloud — операционна система от ниво дата-център в Однокласниках

Какво прави впечатление:

  • Уеб фронтендът и музиката използват изолирани клъстери на същия сървър приложения.
  • Можем да разграничим логическите слоеве, на които принадлежат тези клъстери: фронтове, кешове, слой за съхранение и управление на данни.
  • Фронтендът не е хомогенен, това са различни функционални подсистеми.
  • Кешовете също могат да се разпределят между подсистемите, чиито данни кешират.

Още веднъж прерисуваме картината:

One-cloud — операционна система от ниво дата-център в Однокласниках

О, вижте! Виждаме йерархия! А това означава, че можем да разпределяме ресурсите в по-големи парчета: да назначим отговорен разработчик за възел от тази йерархия, който съответства на функционалната подсистема (като „music“ на картинката), и да свържем квота към същото ниво на йерархията. Тази йерархия също ни позволява по-гъвкаво да организираме услугите за удобство при управлението. Например, всички уеб услуги, тъй като това е много голяма група от сървъри, я разделяме на няколко по-малки групи, показани на картинката като group1, group2.

Премахвайки излишните линии, можем да запишем всеки възел от нашата картинка в по-плосък вид: group1.web.front, api.music.front, user-cache.cache.

Така достигаме до понятието "йерархична опашка". Тя има име, като "group1.web.front". За нея е назначена квота за ресурси и права за потребителите. На човек от DevOps ще дадем права за изпращане на услугата в опашката, и такъв служител може да стартира нещо в опашката, а на човек от OpsDev — администраторски права, и сега той може да управлява опашката, да назначава хора и да предоставя права и т.н. Услугите, стартирани в тази опашка, ще се изпълняват в рамките на квотата на опашката. Ако изчислителната квота на опашката не е достатъчна за едновременното изпълнение на всички услуги, те ще се изпълняват последователно, като по този начин формират самата опашка.

Нека разгледаме услугите по-подробно. Услугата има пълно име, което винаги включва името на опашката. Тогава услугата уеб фронт ще има име ok-web.group1.web.front. А услугата на сървъра приложения, на която тя се обръща, ще се нарича ok-app.group1.web.front. Всеки сервис има манифест, в който е посочена цялата необходима информация за разполагане на конкретни машини: колко ресурси консумира тази задача, каква конфигурация е необходима за нея, колко реплики трябва да има, свойства за обработка на откази на този сервис. И след разполагането на сервиса на самите машини се появяват неговите екземпляри. Те също се именуват еднозначно — с номера на екземпляра и името на сервиса: 1.ok-web.group1.web.front, 2.ok-web.group1.web.front, …

Това е много удобно: гледайки само името на стартирания контейнер, веднага можем да изясним много.

А сега нека се запознаем по-подробно с това, какво всъщност изпълняват тези екземпляри: задачите.

Класове за изолация на задачи

Всички задачи в ОК (и вероятно навсякъде) могат да бъдат разделени на групи:

  • Задачи с кратка забавяне — prod. За такива задачи и услуги е много важна забавянето на отговора (latency), колко бързо всеки от заявките ще бъде обработен от системата. Примери за задачи: уеб интерфейси, кешове, приложения, OLTP хранилища и т.н.
  • Задачи за изчисления — batch. Тук скоростта на обработка на всеки конкретен заявка не е важна. За тях е важно, колко изчисления общо задачата ще извърши за определен (голям) период от време (throughput). Това ще включва всякакви задачи MapReduce, Hadoop, машинно обучение, статистика.
  • Фонови задачи — idle. За тези задачи нито забавянето, нито производителността са особено важни. В тях влизат различни тестове, миграции, преизчисления, конвертиране на данни от един формат в друг. От една страна, те приличат на изчислителни, от друга — не е много важно колко бързо ще завършат.

Нека видим как такива задачи консумират ресурси, например, централния процесор.

Задачи с кратка забавяне. При такава задача моделът на потребление на ЦП ще изглежда така:

One-cloud — операционна система от ниво дата-център в Однокласниках

Получава се заявка от потребителя, задачата започва да използва всичките налични ядра на ЦП, обработва и връща отговор, чака следващата заявка и остава. Постъпва следваща заявка — отново заема всичко, което е налично, обработва, чака следващата.

За да гарантираме минимално забавяне за такава задача, трябва да вземем максимума от ресурсите, които тя консумира, и да резервираме необходимия брой ядра на миньона (машината, която ще изпълнява задачата). Тогава формулата за резервиране за нашата задача ще изглежда така:

alloc: cpu = 4 (max)

и ако имаме минион машина с 16 ядра, можем да поставим точно четири такива задачи върху нея. Особено важно е да отбележим, че средното потребление на процесора за тези задачи често е много ниско — което е очевидно, тъй като значителна част от времето задачата е в очакване на заявка и не прави нищо.

Изчислителни задачи. Шаблонът им ще бъде малко по-различен:

One-cloud — операционна система от ниво дата-център в Однокласниках

Средното потребление на ресурсите на процесора за тези задачи е доста високо. Често искаме изчислителната задача да бъде изпълнена в определено време, затова е необходимо да резервираме минималното количество процесори, от което се нуждае, за да приключи всичкото изчисление за приемливо време. Нейната формула за резервиране ще изглежда така:

alloc: cpu = [1,*)

«Моля, разположи на миниона, където има поне едно свободно ядро, а после всичко, което има — ще го погълне».

Тук ефективността на използването вече е значително по-добра, отколкото при задачите с кратко закъснение. Но печалбата ще бъде много по-голяма, ако комбинираме двата типа задачи на една минион машина и разпределяме ресурсите й на ход. Когато задачата с кратко закъснение изисква процесор — тя го получава незабавно, а когато ресурсите станат ненужни — те се предават на изчислителната задача, т.е. по някакъв начин така:

One-cloud — операционна система от ниво дата-център в Однокласниках

Но как да го направим?

Първо, нека разберем с prod и его alloc: cpu = 4. Нуждаем се от резервиране на четири ядра. В Docker run това може да се направи по два начина:

  • С помощта на опцията --cpuset=1-4, т.е. да се определят четири конкретни ядра за задачата на машината.
  • Използвайте --cpuquota=400_000 --cpuperiod=100_000, да се назначи квота за време на процесора, т.е. да се посочи, че на всеки 100 мс реално време задачата не може да потребява повече от 400 мс време на процесора. Получават се същите четири ядра.

Но кой от тези методи ще е подходящ?

Сравнително привлекателно изглежда cpuset. Задачата има четири запазени ядра, което означава, че кешовете на процесора ще работят максимално ефективно. Има и обратна страна на това: щеше да е необходимо ние да поемем задачата за разпределяне на изчисленията по незаетите ядра на машината вместо ОС, а това е доста нетривиална задача, особено ако опитаме да разположим batch задачи на такава машина. Тестовете показаха, че в този случай по-добре подхожда вариантът с квота: така операционната система има повече свобода при избора на ядро за изпълнение на задачата в текущия момент и времето на процесора се разпределя по-ефективно.

Нека разберем как в docker да направим резервиране по минимален брой ядра. Квотата за batch задачи вече не е приложима, тъй като ограничаването на максимума не е необходимо, достатъчно е само да се гарантира минимум. И тук добре подхожда опцията docker run --cpushares.

Съгласихме се, че ако batch изисква гаранция минимум на едно ядро, то ние указваме --cpushares=1024, а ако минимум на две ядра, то указваме --cpushares=2048. Cpu shares по никакъв начин не се намесват в разпределението на времето на процесора, докато го има в изобилие. Така, ако prod в момента не използва всичките си четири ядра — нищо не ограничава batch задачите и те могат да използват допълнителното време на процесора. А в ситуация на недостиг на процесор, ако prod е усвоил всичките си четири ядра и се е сблъскал с квота — останалото време на процесора ще бъде разпределено пропорционално на cpushares, т.е. в ситуация с три свободни ядра, едното ще получи задача с 1024 cpushares, а останалите две — задача с 2048 cpushares.

Но използването на квота и shares не е достатъчно. Трябва да направим така, че задачата с кратка забавяне да получава приоритет пред batch задачата при разпределението на времето на процесора. Без такава приоритизация, batch задачата ще отнема всичкото време на процесора в момента, когато то е необходимо на prod. В Docker run няма опции за приоритизация на контейнерите, но на помощ идват политиките на планировача на централния процесор в Linux. Подробно за тях можете да прочетете тук, а в рамките на тази статия ще ги разгледаме накратко:

  • SCHED_OTHER
    По подразбиране получават всички обикновени потребителски процеси на Linux машина.
  • SCHED_BATCH
    Предназначена за ресурсоемки процеси. Когато задача е поставена в процесора, се въвежда така нареченият неуспех при активирането: такава задача с по-малка вероятност ще получи ресурси от процесора, ако в момента задачата с SCHED_OTHER я използва.
  • SCHED_IDLE
    Фонов процес с много нисък приоритет, дори под nice -19. Ние използваме нашата библиотека с отворен код one-nio, за да зададем необходимата политика при стартиране на контейнера с извикването на

one.nio.os.Proc.sched_setscheduler( pid, Proc.SCHED_IDLE )

Но дори и ако не програмирате на Java, същото може да се направи с помощта на командата chrt:

chrt -i 0 $pid

Нека съберем всички наши нива на изолация в една таблица за по-добра видимост:

Клас на изолация
Пример alloc
Опции Docker run
sched_setscheduler chrt*

Prod
cpu = 4
--cpuquota=400000 --cpuperiod=100000
SCHED_OTHER

Batch
Cpu = [1, * )
--cpushares=1024
SCHED_BATCH

Idle
Cpu= [2, *)
--cpushares=2048
SCHED_IDLE

*Ако правите chrt от вътре на контейнера, може да ви е необходима способността sys_nice, тъй като Docker по подразбиране отнема тази способност при стартиране на контейнера.

Но задачите изискват не само процесор, но и трафик, който влияе на закъснението на мрежовата задача дори повече от неправилното разпределение на ресурсите на процесора. Затова ние, естествено, искаме да получим същата картина и за трафика. Тоест, когато prod-задача изпраща пакети в мрежата, ние ограничениям максималната скорост (формула alloc: lan=[*,500mbps) ), с която prod може да го прави. А за batch ние гарантираме само минимална пропускателна способност, но не ограничаваме максималната (формула alloc: lan=[10Mbps,*) ) В този случай трафикът на prod трябва да има приоритет пред batch-задачите.
Тук Docker няма никакви примитиви, които да можем да използваме. Но ни помага Linux Traffic Control. Успяхме да постигнем желаните резултати с помощта на дисциплината Hierarchical Fair Service Curve. С нея изолирахме два класа трафик: високоприоритетен prod и низкоприоритетен batch/idle. В крайна сметка конфигурацията за изходящия трафик изглежда така:

One-cloud — операционна система от ниво дата-център в Однокласниках

тук 1:0 — «кореновият qdisc» на дисциплините hsfc; 1:1 — дъщерен клас hsfc с общ лимит на пропускната способност от 8 Gbit/s, под който попада дъщерните класове на всички контейнери; 1:2 — дъщерен клас hsfc, общ за всички batch и idle задачи с „динамичен“ лимит, за който по-долу. Останалите дъщерни класове hsfc — това са специализирани класове за активни в момента prod-контейнери с лимити, съответстващи на техните манифести, — 450 и 400 Mbit/s. На всеки клас hsfc е назначена qdisc опашка fq или fq_codel, в зависимост от версията на ядрото linux, за да се избегнат загуби на пакети при изблици на трафик.

Обикновено дисциплините tc служат само за приоритизиране на изходящия трафик. Но искаме да приоритизираме и входящия трафик — защото някаква batch-задача може без проблем да използва целия входящ канал, получавайки, например, голям пакет входни данни за map&reduce. За това използваме модула ifb, който създава виртуален интерфейс ifbX за всеки мрежов интерфейс и пренасочва входящия трафик от интерфейса към изходящия на ifbX. След това за ifbX работят всички същите дисциплини за контрол на изходящия трафик, за който конфигурацията на hsfc ще бъде много сходна:

One-cloud — операционна система от ниво дата-център в Однокласниках

В хода на експериментите установихме, че най-добрите резултати от hsfc се получават, когато клас 1:2 на неприоритетния batch/idle трафик се ограничава на машините миньони до определена свободна лента. В противен случай неприоритетният трафик оказва прекалено голямо влияние на забавянето на prod-задачите. Текущата стойност на свободната лента miniond определя всяка секунда, измервайки средното потребление на трафик от всички prod-задачи на този миньон One-cloud — операционна система от ниво дата-център в Однокласниках и я изважда от пропускната способност на мрежовия интерфейс One-cloud — операционна система от ниво дата-център в Однокласниках с малък резерв, т.е.

One-cloud — операционна система от ниво дата-център в Однокласниках

Лентите се определят за входящия и изходящия трафик независимо. И в съответствие с новите стойности miniond преконфигурира лимита на неприоритетния клас 1:2.

По този начин реализирахме всички три класа изолация: prod, batch и idle. Тези класове оказват значително влияние върху характеристиките на изпълнението на задачите. Затова решихме да поставим този признак горе в йерархията, за да е ясно, от какво става въпрос при поглед върху името на йерархичната опашка:

One-cloud — операционна система от ниво дата-център в Однокласниках

Всички наши познати web и music фронтове след това се поставят в йерархията под prod. За пример, под batch нека поставим сервиз music catalog, който периодично съставя каталог на песните от набора на качените в «Однокласники» mp3 файлове. Пример за услуга в режим idle може да бъде music transformer, нормализиращ нивото на звука на музиката.

Отново премахвайки излишните линии, можем да запишем имената на нашите услуги по-плоско, добавяйки класа на изолация на задачата в края на пълното име на услугата: web.front.prod, catalog.music.batch, transformer.music.idle.

И сега, гледайки на името на услугата, разбираме не само каква функция изпълнява, но и нейния клас на изолация, следователно и критичността и т.н.

Всичко е чудесно, но има една горчива истина. Пълно изолиране на задачи, работещи на една машина, е невъзможно.

Какво успяхме да постигнем: ако batch интензивно потребява единствено ресурси на процесора, вграденият планировчик на ЦП Linux се справя много добре със задачата си и влиянието върху prod задачата е практически нулево. Но ако тази batch задача започне активно да работи с паметта, взаимното влияние вече се проявява. Това се случва, защото процесорните кешове на паметта на prod задачата „се изчерпват“ — в резултат на което в кеша се увеличават пропуските и процесорът обработва prod задачата по-бавно. Тази batch задача може да увеличи закъсненията на нашия типичен prod контейнер с 10%.

Изолирането на трафика е дори по-сложно заради факта, че съвременните мрежови карти имат вътрешна опашка от пакети. Ако пакет от batch задачата е достигнал там първи, той ще бъде предаден по кабела първи и нищо не може да се направи.

Освен това, до момента успяхме да решим само проблема с приоритизацията на TCP трафик: подходът с hsfc не работи за UDP. И дори в случая с TCP трафика, ако batch задачата генерира много трафик, това също дава около 10% увеличение на закъснението на prod задачата.

Отказоустойчивост

Една от целите при разработването на one-cloud беше да се подобри отказоустойчивостта на Однокласники. Затова по-нататък бих искал да разгледам по-подробно възможните сценарии на откази и инциденти. Нека започнем с прост сценарий — отказ на контейнер.

Контейнерът сам по себе си може да се провали по няколко начина. Това може да е някакъв експеримент, бъг или грешка в манифеста, поради която prod-задачата започва да потребява повече ресурси, отколкото е посочено в манифеста. Имахме случай: разработчикът реализира един сложен алгоритъм, преработваше го многократно, сам се усложняваше и се заплете толкова, че в крайна сметка задачата се зацикли по нетривиален начин. А тъй като prod-задачата е с по-висок приоритет от всички останали на същите миньони, тя започна да потребява всички налични ресурси на процесора. В тази ситуация изолацията, а по-точно квотата за процесорно време, ни спаси. Ако на задачата е зададена квота, тя не ще потреби повече. Затова batch- и други prod-задачи, които работеха на същата машина, не забелязаха нищо.

Втората възможна неприятност — падането на контейнера. И тук ни спасяват политиките за рестартиране, всички ги знаят, Docker сам се справя прекрасно. Почти всички prod-задачи имат политика за рестартиране always. Понякога използваме on_failure за batch-задачи или за отстраняване на бъгове в prod-контейнерите.

Какво можем да направим при недостъпност на целия миньон?

Очевидно е да стартираме контейнер на друга машина. Най-интересното тук е какво става с IP-адреса (адресите), назначени на контейнера.

Можем да назначаваме на контейнерите същите IP-адреси, каквито имат машините-миньони, на които тези контейнери се стартират. Тогава при стартиране на контейнер на друга машина, IP-адресът му се променя, и всички клиенти трябва да разберат, че контейнерът е преместен, а сега трябва да отиват на друг адрес, което изисква отделен сервис Service Discovery.

Service Discovery — това е удобно. На пазара има много решения с различна степен на отказоустойчивост за организиране на регистър на сервизите. Често в такива решения се реализира логиката на балансьора на натоварването, съхранение на допълнителна конфигурация под формата на KV-сторадж и т. н.
Въпреки това, бихме искали да се справим без необходимостта от внедряване на отделен регистър, тъй като това би означавало въвеждането на критична система, която се използва от всички сервизите в продукция. А това означава потенциална точка на отказ, нужно е да изберем или разработим много отказоустойчиво решение, което очевидно е доста сложно, отнема време и е скъпо.

И още един голям недостатък: за да работи нашата стара инфраструктура с новата, трябваше да пренапишем абсолютно всички задачи, за да използваме някаква система за Service Discovery. Работата е МНОГО, а в някои случаи дори невъзможно, когато става въпрос за устройства с ниско ниво, работещи на ядрото на операционната система или директно с хардуера. Реализирането на тази функционалност чрез установени модели на решения, като например side-car означаваше местно допълнително натоварване, на места — усложняване на експлоатацията и допълнителни сценарии на откази. Не искахме да усложняваме нещата, затова решихме да направим използването на Service Discovery опционално.

В one-cloud IP адресът следва контейнера, т.е. всеки екземпляр на задачата има свой собствен IP адрес. Този адрес е „статичен“: той се закрепва за всеки екземпляр в момента на първоначалното изпращане на услугата в облака. Ако по време на живота на услугата е имало различно количество екземпляри, то в крайна сметка за нея ще бъдат закрепени толкова IP адреси, колкото максимално е имало екземпляри.

По-късно тези адреси не се променят: те са присвоени веднъж и продължават да съществуват през целия живот на услугата в продукция. IP адресите следват контейнерите по мрежата. Ако контейнерът се премести на друг миньон, и адресът ще премине с него.

Така, съпоставянето на името на услугата с списъка на неговите IP адреси се променя много рядко. Ако отново погледнем имената на екземплярите на услугата, които споменахме в началото на статията (1.ok-web.group1.web.front.prod, 2.ok-web.group1.web.front.prod, …), то ще забележим, че те наподобяват FQDN, използвани в DNS. Всъщност, за показване на имената на инстанциите на услугите в техните IP адреси ние използваме DNS протокол. Важно е, че този DNS връща всички резервирани IP адреси на всички контейнери — както работещи, така и спряни (представете си, че се използват три реплики, а там имаме пет резервирани адреса — всички пет ще бъдат върнати). Клиентите, получавайки тази информация, ще се опитат да установят връзка с всичките пет реплики — и по такъв начин ще определят кои работят. Тази опция за определяне на наличността е значително по-надеждна, в нея не участват нито DNS, нито Service Discovery, така че няма и трудно решими задачи с поддържането на актуалността на информацията и отказоустойчивостта на тези системи. Освен това, в критични услуги, от които зависи работата на целия портал, можем изобщо да не използваме DNS, а просто да впишем IP адресите в конфигурацията.

Реализацията на такова преместване на IP адреси след контейнерите може да бъде нетривиална — и ще се спрем на това как работи, на следващия пример:

One-cloud — операционна система от ниво дата-център в Однокласниках

Да предположим, че мастера на one-cloud дава команда на миньона M1 да стартира 1.ok-web.group1.web.front.prod с адрес 1.1.1.1. На миньона работи BIRD, който анонсира този адрес в специални сървъри route reflector. Последните имат BGP сесия с мрежовото оборудване, в което се трансферира маршрутът на адреса 1.1.1.1 към M1. M1 маршрутизира пакетите вътре в контейнера с помощта на Linux. Сървърите route reflector са три, тъй като това е много критична част от инфраструктурата на one-cloud — без тях мрежата в one-cloud няма да работи. Разпределяме ги в различни шкафове, по възможност разположени в различни зали на дата центъра, за да намалим вероятността от едновременно отказване на всичките три.

Сега да предположим, че връзката между мастера на one-cloud и миньона M1 е загубена. Мастера на one-cloud сега ще действа, изхождайки от предположението, че M1 е отказал напълно. Т.е. ще даде команда на миньона M2 да стартира web.group1.web.front.prod с същия адрес 1.1.1.1. Сега имаме два конфликтуващи маршрута в мрежата за 1.1.1.1: на M1 и на M2. За да разрешим подобни конфликти, използваме Multi Exit Discriminator, който се посочва в BGP анонса. Това е число, което показва тежестта на анонсирания маршрут. От конфликтуващите маршрути ще бъде избран маршрутът с по-ниска стойност на MED. Мастърът one-cloud поддържа MED като неразривна част от IP адресите на контейнерите. При първоначалното изписване адресът получава достатъчно голям MED = 1 000 000. При ситуация с аварийно прехвърляне на контейнера, мастърът намалява MED, и M2 вече получава команда да анонсира адрес 1.1.1.1 с MED = 999 999. Екземплярът, работещ на M1, остава без свързаност и по-нататъшната му съдба не ни интересува до момента на възстановяване свързаност с мастъра, когато той ще бъде спрян като стар дубликат.

Аварии

Всички системи за управление на дата центрове винаги адекватно обработват малки повреди. Излизането на контейнер е норма почти навсякъде.

Нека да видим как обработваме авария, например отказ на електричеството в едно или повече зали на дата центъра.

Какво означава авария за системата за управление на дата центъра? Първо, това е масирано едновременно отказване на множество машини, и системата за управление трябва да мигрира много контейнерите едновременно. Но ако аварията е много мащабна, може да се случи така, че всички задачи да не могат да бъдат преразпределени на другите миньони, защото ресурсната капацитет на дата центъра пада под 100% натоварване.

Често аварии са придружени от отказ на управляващия слой. Това може да се случи поради повреда на оборудването, но по-често поради това, че аварии не се тестват, и управляващият слой сам пада от увеличеното натоварване.

Какво може да се направи с всичко това?

Масовите миграции означават, че в инфраструктурата се появява голямо количество действия, миграции и разполагания. Всяка миграция може да отнеме известно време, необходимо за доставка и разопаковане на образите на контейнерите до миньоните, стартиране и инициализация на контейнерите и т.н. Затова е желателно по-важните задачи да бъдат стартирани преди по-малко важните.

Нека отново погледнем познатата ни йерархия на услугите и да опитаме да решим кои задачи искаме да стартираме първо.

One-cloud — операционна система от ниво дата-център в Однокласниках

Разбира се, това са процесите, които пряко участват в обработката на потребителски запитвания, т.е. prod. Посочваме това с помощта на приоритета на разположение — число, което може да бъде присвоено на опашката. Ако приоритетът на дадена опашка е по-висок, нейните услуги се разполагат на първо място.

На prod назначаваме приоритети по-високи, 0; на batch — малко по-ниски, 100; на idle — още по-ниски, 200. Приоритетите се прилагат йерархично. Всички задачи по-долу в йерархията ще имат съответния приоритет. Ако искаме кешовете в prod да стартират преди фронтендите, назначаваме приоритети на cache = 0 и на фронт подопашки = 1. Ако обаче, например, искаме, основният портал да стартира предимно от фронтовете, а music фронт да стартира след него, можем да зададем по-нисък приоритет на последния — 10.

Следващият проблем е недостигът на ресурси. И така, имаме отказали много устройства, цели зали от дата центъра, а сме пуснали толкова много услуги, че сега за всички не стигат ресурсите. Трябва да решим кои задачи да пожертваме, за да работят основните критични услуги.

One-cloud — операционна система от ниво дата-център в Однокласниках

За разлика от приоритета на разположение, не можем безразборно да пожертваме всички batch задачи, защото някои от тях са важни за работата на портала. Затова отделихме приоритета на изтласкване задачата. При разположение задача с по-висок приоритет може да изтласка, т.е. да спре задача с по-нисък приоритет, ако вече няма свободни миньони. При това задача с нисък приоритет вероятно така и ще остане неразположена, т.е. за нея няма да има подходящ миньон с достатъчни свободни ресурси.

В нашата йерархия е много лесно да зададем такъв приоритет на изтласкване, за да изтласкват или спират prod- и batch-задачи idle задачи, но не и помежду си, задавайки за idle приоритет, равен на 200. Както и в случая с приоритета на разположение, можем да използваме нашата йерархия, за да опишем по-сложни правила. Например, можем да посочим, че жертваме функцията на музиката, ако ни недостигнат ресурси за основния уеб портал, задавайки на съответните възли по-нисък приоритет: 10.

Погасяването на целия ДЦ

Защо може да откаже целият дата център? Стихиен природен феномен. Имаше хубав пост, как ураганът е повлиял на работата на дата центъра. Елементите, свързани с бездомници, които веднъж изгори оптичен кабел в канализацията, доведоха до пълна загуба на връзка на дата центъра с останалите площадки. Причините за неизправности могат да бъдат свързани и с човешкия фактор: операторът може да издаде команда, която да доведе до срива на целия дата център. Това може да се случи поради сериозен бъг. Като цяло, дата центровете падат — това не е рядкост. При нас това се случва на няколко месеца.

И ето какво правим, за да се уверим, че никой #окживи не поства в Туитър.

Първата стратегия е изолация. Всеки инстанс на one-cloud е изолиран и може да управлява машините само в един дата център. Тоест загубата на облака поради бъгове или неправилна команда на оператора — това е загуба само на един дата център. Ние сме подготвени за това: имаме политика за резервиране, при която реплики на приложенията и данните се разполагат във всички дата центрове. Използваме отказоустойчиви бази данни и периодично тестваме откази.
Тъй като днес имаме четири дата центъра, то имаме и четири отделни, напълно изолирани инстанции на one-cloud.

Такъв подход не само предпазва от физически отказ, но може да защити и от грешки на оператора.

Какво още може да се направи с човешкия фактор? Когато операторът издаде нещо странно или потенциално опасно за облака, той може внезапно да бъде помолен да реши малък проблем, за да провери доколко добре е обмислил командата. Например, ако това е масово спиране на много реплики или просто странна команда — намаляване на числото на репликите или смяна на името на образа, а не само поредния номер на версията в новия манифест.

One-cloud — операционна система от ниво дата-център в Однокласниках

Резюме

Отличителни черти на one-cloud:

  • Йерархична и ясна схема на именуване на услуги и контейнери, която позволява много бързо да разберете каква е задачата, към какво се отнася и как функционира, и кой е отговорен за нея.
  • Прилагаме собствената си техника за съвместяване на prod- и batch-задачи на миньони, за да увеличим ефективността на съвместното използване на машините. Вместо cpuset използваме CPU квоти, дялове, политики на планировчика за CPU и Linux QoS.
  • Не успяхме напълно да изолираме контейнери, работещи на една машина, но тяхното взаимно влияние остава в границите на 20%.
  • Организацията на услугите в йерархия помага при автоматично ликвидиране на аварии чрез приоритети при разполагане и вытесняване..

Здравейте

Защо не избрахме готово решение.

  • Различните класове изолация на задачите изискват различна логика при разпределението на миньоните. Докато prod-задачите могат да бъдат разпределяни чрез просто резервиране на ресурси, batch и idle задачите трябва да се разпределят, следейки реалната употреба на ресурсите на миньоните.
  • Необходимостта от отчитане на ресурсите, консумирани от задачите, като:
    • пропускната способност на мрежата;
    • типове и „шпиндели“ на дисковете.
  • Необходимостта от задаване на приоритети на услугите при аварийните ситуации, права и квоти за ресурсите на екипите, което се решава чрез йерархични опашки в one-cloud.
  • Необходимостта от човешки именувания на контейнерите, за да се съкрати времето за реакция при аварии и инциденти.
  • Невъзможността за едновременно внедряване на Service Discovery навсякъде; необходимостта дълго време да съществуват паралелно задачи, разположени на физически хостове, — което се решава с „статични“ IP адреси, следващи контейнерите, и следователно, необходимостта от уникална интеграция с голямата мрежова инфраструктура.

Всички тези функции биха изисквали значителни преработки на съществуващите решения, и след като оценихме обема работа, разбрахме, че можем да разработим собствено решение с почти същите усилия. Но нашето решение ще бъде много по-лесно за експлоатация и развитие — в него няма ненужни абстракции, поддържащи ненужен функционал.

На всички, които прочетоха последните редове, — благодаря за търпението и вниманието!

Източник: habr.com

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster