One-cloud - ОС на ниво център за данни в Odnoklassniki

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

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

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

На услуга, състояща се от няколко реплики, се разпределят няколко сървъра - по един за всеки. След това изчислителният ресурс за услугата се разпределя много просто: броят на сървърите, които услугата има, максималното количество ресурси, които може да консумира. „Лесно“ тук не означава, че е лесно за използване, а в смисъл, че разпределението на ресурсите се извършва ръчно.

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

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

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

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

Плюс това, готовият регистър и маркирането на изображения в Docker ни дават готови примитиви за версии и доставяне на код към продукцията.

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

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

Ръчното разпределяне на контейнери не е опция, когато имате 8 хиляди сървъра и 8-16 хиляди контейнера.

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

Очевидно се нуждаем от контролен слой, който да прави това автоматично.

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

Сега нека да разгледаме проблема с по-сложното разпределение на ресурсите за много миньони.

Компютърен ресурс в един облак е:

  • Количеството мощност на процесора, консумирано от конкретна задача.
  • Количеството памет, налично за задачата.
  • Мрежов трафик. Всеки от миньоните има специфичен мрежов интерфейс с ограничена честотна лента, така че е невъзможно да се разпределят задачи, без да се вземе предвид количеството данни, които те предават по мрежата.
  • Дискове. В допълнение, очевидно, към пространството за тези задачи, ние също разпределяме типа на диска: HDD или SSD. Дисковете могат да обслужват краен брой заявки в секунда - IOPS. Следователно, за задачи, които генерират повече IOPS, отколкото един диск може да обработи, ние също разпределяме „шпиндели“ – тоест дискови устройства, които трябва да бъдат изключително запазени за задачата.

След това за някаква услуга, например за потребителски кеш, можем да записваме консумираните ресурси по следния начин: 400 процесорни ядра, 2,5 TB памет, 50 Gbit/s трафик в двете посоки, 6 TB място на HDD, разположено на 100 шпиндела. Или в по-позната форма като тази:

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

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

Какво ви хваща окото:

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

Нека преначертаем картината отново:

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

Така стигаме до понятието „йерархична опашка“. Има име като "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, …

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

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

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

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

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

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

Задачи с кратко забавяне. Такава задача ще има модел на потребление на процесора, подобен на този:

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

alloc: cpu = 4 (max)

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

alloc: cpu = [1,*)

„Моля, поставете го върху миньон, където има поне едно свободно ядро, и тогава колкото и да има, то ще погълне всичко.“

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

Но как да го направя?

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

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

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

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

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

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

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

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

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

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

chrt -i 0 $pid

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

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

Ръгане
процесор = 4
--cpuquota=400000 --cpuperiod=100000
SCHED_OTHER

Партида
Cpu = [1, *)
--cpushares=1024
SCHED_BATCH

Режим
Cpu = [2, *)
--cpushares=2048
SCHED_IDLE

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

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

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

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

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

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

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

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

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

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

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

Очевидно стартирайте контейнера на друга машина. Интересното тук е какво се случва с IP адресите, присвоени на контейнера.

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

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

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

В един облак 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 - ОС на ниво център за данни в Odnoklassniki

Да кажем, че главният облак дава команда на миньон M1 да стартира 1.ok-web.group1.web.front.prod с адрес 1.1.1.1. Работи върху миньон ПТИЧКА, който рекламира този адрес на специални сървъри маршрутен рефлектор. Последните имат BGP сесия с мрежовия хардуер, в който се транслира маршрутът на адрес 1.1.1.1 на M1. M1 маршрутизира пакети вътре в контейнера с помощта на Linux. Има три сървъра за отразяване на маршрути, тъй като това е много критична част от инфраструктурата на един облак - без тях мрежата в един облак няма да работи. Поставяме ги в различни стелажи, ако е възможно в различни стаи на центъра за данни, за да намалим вероятността и трите да се повредят едновременно.

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

злополука

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

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

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

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

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

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

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

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

Цели DC аварии

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

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

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

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

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

One-cloud - ОС на ниво център за данни в Odnoklassniki

Резултати от

Отличителни черти на един облак:

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

ЧЗВ

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

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

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

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

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

Добавяне на нов коментар