Використання HAproxy iptables+еtcd+confd для автоматичного service discovery в мінливих мережах



Сергій Пузирьов (Mail.Ru Group)
Мене звуть Сергій Пузирьов, я системний адміністратор в Mail.ru я займаюся проектом «Пошук». Так, на подив, у Mail.ru є пошук. Я люблю сервіси, які не вимагають уваги. Я системний адміністратор, і я не люблю працювати системним адміністратором дуже багато, я люблю робити так, щоб роботи було менше, тому одне з рішень, яке ми намагаємося використовувати у своїй роботі, я вам опишу.



Спочатку я скажу пару слів про те, що таке сервіс-орієнтована архітектура.



Досить великі веб-додатки можна будувати за допомогою комбінацій з великої кількості невеликих, досить простих за своєю суттю сервісів, які спілкуються один з одним. Це трохи схоже на Unix, тільки це не в межах одного комп'ютера, це в межах додатка. І принципи тут майже ті ж самі, тобто додатки повинні робити мало речей, але повинні робити їх добре. Вони не повинні бути сильно пов'язані один з одним, вони повинні використовувати більш-менш стандартні процедури зв'язку один з одним.

Додатки не повинні знати, хто ними користується. Відповідно, якщо ми використовуємо storage статики, то storage'у статики не важливо, що в ньому лежить – відео, картинки, що завгодно, його завдання просто зберігати блобы і віддавати блобы, більше він не робить нічого, і занадто розумним він не намагається.

Точно так само, як в Unix, клієнти не повинні нічого знати про сервіси, про те, як сервіси влаштовані. Знову ж таки, якщо ми використовуємо сервіс storage'а статики, то ми просто в нього заливаємо. Storage статики – це річ складна, але клієнта це не хвилює, клієнт хоче покласти блоб і хоче забрати блоб, де воно там ляже, скільки там буде чинників реплікацій, як це буде синхронно асинхронно між дата-центрами, як воно буде відновлюватися в разі аварії, – їх, взагалі, це не хвилює, вони хочуть просто ні про що не думати. Розробники теж не хочуть ні про що думати, тобто концепт слабосвязанних сервісів дозволяє не турбуватися про внутрішній устрій інших компонентів і з-за цього розробнику простіше. І розробники теж такі ж люди, як ми, вони теж не хочуть думати занадто багато.

Можливо, четвертий пункт здається трохи очевидним, але слабосвязанних сервіси, тим не менш, один з одним взаємодіють і користуються один одним.

Давайте подивимося на те, що таке сервіс.



Коли я вживаю слово «сервіс», я маю на увазі більш-менш ізольоване додаток, у якого є три обов'язкові частини. Ну, обов'язкових частин у нього дві, третя – опціональна.

  1. По-перше, повинен існувати протокол спілкування з сервісом, тобто той, який він надає сервіс. Це ключовий момент, сам сервіс характеризується протоколом, а не тим, як він влаштований всередині.
  2. У нашому випадку, конкретно в нашому проекті так історично склалося, що всі сервіси, по більшій частині, мережеві сервіси працюють поверх протоколу TCP, поверх якихось верхніх протоколів (так вийшло, що у нас є самописні вищерозміщені протоколи, але в контексті розглянутого доповіді це не дуже суттєво). Відповідно, у кожного сервісу є точка входу. У нашому випадку це завжди пара IP+port. У загальному випадку у сервісу, зараз це особливо модно зі всякими архітектурами зразок REST, це найчастіше http точки входу, але не ними єдиними, тому що сервіс – це зовсім не обов'язково http-сервіс.
  3. І сервіси можуть користуватися одне одним. Тут ми приходимо до найважливішого елементу, про що власне весь доповідь, – коли один сервіс хоче скористатися іншим сервісом, цей сервіс повинен знати, де інший сервіс знайти, і цю інформацію необхідно йому повідомити.
Які у нас є прості сервіси?



Наприклад, у нас є абсолютно тупучий сервіс – memcached. Я думаю, ним користувалися всі. Він працює по протоколу memcached, у нього завжди є IP-адресу і порт, і сам він нічим не користується, він просто запустився, слухає порт і працює, більше він ні про що не думає. Клієнт з ним спілкується, клієнту потрібно просто знати, куди постукати по TCP або UDP. Memcached, до речі, працює по UDP, іноді це може бути зручно. І нічого складного.

Сервіс трохи складніше.



Клієнт хоче відмовостійкий memcached. Існує така штука як mcrouter, її розробила копання Facebook. Mcrouter дозволяє, наприклад, повторити записи, які йдуть в mcrouter в два різних бекенду memcached. Клієнт у цьому випадку спілкується з mcrouter по протоколу memcached і, знову ж, він нічого не знає про те, як mcrouter влаштований далі, його не хвилює, скільки там memcached, він просто хоче спілкуватися з memcached сервісом, який буде резервний. Mcrouter, у свою чергу, знає, де у нього є два бекенду memcached, і він повторює туди дані. Клієнт нічого не знає про memcached, а mcrouter повинен знати про memcached, але при цьому клієнт повинен знати, де знаходиться mcrouter, а mcrouter повинен знати, де знаходяться memcached. Тобто тут у нас вже три місця, які необхідно сконфігурувати:

  1. клієнт повинен знайти mcrouter,
  2. mcrouter повинен знайти перший memcached ,
  3. mcrouter повинен знайти другий memcached .
Підемо далі.



Така архітектура, насправді нічого особливого, це будь-середній php сайт. Nginx, кілька php нод, storage статики, часто він зовнішній, MySQL master, MySQL slave, під mcrouter (просто я люблю mcrouter, тому ним вийшло), часто використовується один memcached сервер і балансування зсередини php. Але, тим не менш, в такому насправді нескладному сервісі, який складається з пари-трійки фізичних серверів, у нас спостерігаються вже 16 місць, які необхідно сконфігурувати – nginx повинен знати про php, він повинен знати про те, де знаходиться memcached разом з mcrouter, nginx повинен знати, де знаходиться static storage… Я далі не буду читати картинку, ми можемо порахувати кількість зв'язків між сервісами і подивитися, скільки місць необхідно сконфігурувати, і переконатися, що їх тут 16. А у нас всього лише простий php сайт.

Що ж ми будемо робити, коли у нас буде складний додаток?



Ця картинка не моя. У мене є OpenStack в роботі, ми його використовуємо, і він дійсно досить складний. Картинка гуглится за запитом «OpenStack diagram», можете самі порахувати, скільки тут місць необхідно сконфігурувати. Насправді, тут є невеликі чити, тому що підходи, які я розповідаю в цій доповіді використовуються в OpenStack, але, тим не менш, там дуже нетривіально і з часом конфігурувати стомлює. Робити-то що? Я не знаю.

Спочатку ми хапалися за голови, потім ми почали думати, як вирішувати цю проблему. Як сервісів розповідати, як їм спілкуватися один з одним.



Найпростіший шлях – ми просто беремо конфіги і записуємо в них, де знаходяться сервіси, якими хоче користуватися наш сервіс. Тобто в прикладі з mcrouter, ми забиваємо в конфіг mcrouter'а адреси memcached. Дуже швидко, дуже легко, взагалі нічого не потрібно. Тобто пішли, наконфигурили, рестартнули, працює. Нам більше ні про що не треба думати. Поки у нас два фізичних сервера, у нас все добре. Навіть коли у нас 30 фізичних серверів, нам майже нормально, вони у нас вилітають раз в два місяці, і нічого у нас не відбувається. Коли у нас 3000, у нас починаються проблеми, тому що вони ніколи не працюють всі разом. Весь час кілька штук лежить, весь час вони якісь зламані, десь стійка впала по мережі, де сервер выломался, де якийсь сервер треба виламати і т. п., і починається паніка. Це момент №1.

Момент №2 – з-за того, що у нас в конфігах купа IP-адрес і незрозумілих портів, все зрозуміло. Ти заходиш в конфіг і дивишся: що за фігня? А там цифри і нічого не зрозуміло. Доводиться тримати документацію, а документація має властивість відставати від реальності. І посеред ночі у тебе зламалося додаток, ти йдеш, намагаєшся зрозуміти, що за фігня, нічого не знаходиш, матюкаєшся, потім абияк ремонтуєш, плачеш… Є деякі проблеми.

Третій момент – коли у вас є сервіс, яким користуються 1000 інших сервісів, і він раптово змінює, наприклад, свій endpoint (точку входу), вам необхідно переконфигурить аж 1000 сервісів, щоб вони тепер ходили в інше місце. І це боляче, бо боляче переконфигурить 1000 місць. Це важко, в принципі, хоч яку архітектуру ви будете використовувати.

Тому тривіальне рішення – давайте замість IP-адрес використовувати DNS.



Дуже легко. Тут особливої різниці немає, просто місце, яке ми виправляємо, місце, в яке ми будемо вносити зміни, намагаємося перемістити з конфига демона, яким ми користуємося, в DNS-зону.

Ми, як би, частина проблеми вирішуємо, а частина проблеми ми таким шляхом створюємо. Тому що все одно потрібна документація, яка буде відставати від реальності. У нас це буде краще працювати, ніж IP-адреси у великому проекті, але трохи краще, тому що ніякого зв'язку з реальністю, з тим, що зараз працює, а що не працює, в конфігах самостійно з'являтися не буде. У вас так само будуть падати стійки, і вам потрібно буде йти і виправляти десь вручну, якщо софт не пристосований з цим жити. А софт часто не пристосований з цим жити, особливо, коли софту 10 років. Наприклад, у випадку з mcrouter і кількома memcached, бэкендами, особливо важко додавати нові инстансы memcached. Або, наприклад, ви хочете вже реплікацію не два, а три, і ви ніяк не можете тільки правкою DNS'ів виправити ситуацію. Вам все одно треба йти і правити конфіг і, можливо, не в одному місці, а потім ще де-небудь правити конфіг. І ті ж самі проблеми. Коли серверів мало, ніяких проблем немає, коли багато, тоді проблеми є.

По-четверте, DNS – це з коробки, він апріорі асинхронний, він ніколи не може бути синхронним, ми не можемо внести зміни в DNS і отримати по клацанню, щоб нам стало добре. Ні, навіть при TTL=60 сек. у нас може бути допливши цієї інформації до демонів кілька хвилин. Тому DNS нам теж не дуже підходить. У нас ще й така проблема є. Тому що на сотні сервісів нормально DNS працює, на тисячі – вже погано.



Ми всі говоримо, що використовуємо системи управління конфігурацією, це модно. Але часто, на жаль, видно, що вони не використовуються. Здавалося б, нас врятує, але не все так добре, тому що система управління конфігурацією, знову ж таки, на великому проекті вирішує проблеми, але проблема, про яку я говорив, це не та проблема, яка вирішується системою управління конфігурацією, т. к. розкочування системи і конфігурація на тисячі машин займає багато часу. Костянтин з Mail.ru доповідав про те, як у них працює Puppet, можете поцікавитися, скільки у них займає розкочування на все. Це навіть не хвилини, це скоріше годинник, і тому ми знову не можемо швидко реагувати на зміни в інфраструктурі. Це момент №1.

Момент №2. Враховуючи, що система управління конфігурацією – це часто купа шаблонів і купа структурованої інформації, при неправильно організації шаблонів заздалегідь ви можете отримати проблеми при розширенні. Тобто для того, щоб вам якесь, здається, банальне змінити місце, яке при хардкоде endpoint'ів в конфігах ви змогли б виправити дуже швидко, вам доведеться переписувати шаблони, тестувати шаблони, тестувати це в dev-оточенні, потім викочувати в продакшн. І ви на просту процедуру збільшення кількості бэкендов з двох до п'яти витратите півдня роботи. Тому, на жаль, системи управління конфігурацією теж не повністю вирішують цю задачу.



Мені здається, що основна проблема тут у тому, що до всієї цієї штуці притягнутий людина, яка коммитит, який конфігурує. Це все неправильно. Фактично у нас роботи ламаються, роботи один одного експлуатують. Мені здається, ми там зайві. Давайте подивимося, що ми можемо зробити.



Існують системи виявлення сервісів, вони характеризуються декількома речами. По-перше, ми вводимо дві концепції – сервіс може реєструватися в системі виявлення сервісів, в цьому випадку він стартує і реєструється. Після цього інший сервіс, який хоче скористатися сервісом, який у нас тільки що зареєструвався, йде в систему виявлення сервісів і питає: «А чи є у тебе такий сервіс, і де він, якщо він у тебе є?». Система виявлення сервісів йому каже: «Дивись, у мене є ось такий», і сервіс, який хотів дізнатися, куди йому піти, дізнався і пішов, куди йому потрібно. Чи не пішов, якщо не зміг.

Це дуже схоже на DNS, але це не зовсім DNS, скоріше, це зовсім не DNS. Так вийшло, що історично система виявлення сервісів (їх досить багато, знизу список з декількох штук, які в принципі існують) також часто працюють як засоби розподіленої блокування, і використовувати їх, відповідно, необхідно з урахуванням цього.



Я трохи розповім конкретно про те стеку софта, про який піде далі мова. Що таке Etcd? Etcd – це спеціальне сховище даних, яке суворо консистентное, транзакції в ньому виконуються тільки на всім кластері відразу, точніше, на кворумі кластера, і тому вони строго серіалізовать. Не можна внести зміни в кластер, коли у нас немає кворуму ні в одній половині, це неможливо. Etcd працює за алгоритмом Raft. Алгоритм Raft – це погляд на Паксос, якщо хто-небудь слухав про Паксос, та спроба зробити Паксос більш простим. На жаль, Raft все одно залишився досить складним, тому я залишу його опис за межами цієї конференції, це просто занадто довго. Але, тим не менш, як працює etcd? На кожному сервері у нас запущений etcd -демон, який зв'язується з іншими etcd-демонами за допомогою первинного входу. Тобто ми на кожному сервері запускаємо etcd, і ми повинні вказати хоча б одна вже перебуває в кластері etcd, далі вони самі знайдуть один одного. Ця вся магія зашита в etcd. І наступного разу вони вже будуть знати, як їм стартувати. І коли нам буде необхідно поспілкуватися з системою виявлення сервісів, а саме etcd, ми завжди будемо спілкуватися з локальною петлею. Тобто etcd завжди складе локальну петлю на будь-якому сервері, який у нас, в принципі, є.



Наступний шматочок мозаїки – це Confd. Confd – це слухавка etcd, тобто etcd зберігає ключі і дозволяє ці ключі змінювати. Confd дозволяє подцепиться до ключа і відстежувати зміни в etcd. Робиться це через http long polling, тобто сonfd фактично являє собою http-клієнт, а etcd – це http-сервер. Confd запитує у etcd: «Дивись, є такий ключ, я хочу за ним стежити», і вішається в long polling. Як тільки в etcd хтось інший ключ записав або зрадив, або видалив, апдейтнул, що завгодно – etcd обриває з'єднання і дає відповідь. Тому реакція відбувається дуже швидко. Тобто якщо відбувається транзакція, то підключений confd відразу ж про це дізнається. Т. о. etcd використовується як шина повідомлень.

Confd вміє реагувати на зміни в etcd і що-небудь запускати. Спочатку він був пристосований для того, щоб брати дані з etcd, шаблонизировать, підкладати конфіг і рестартить демон. Але по факту, він може просто смикати якийсь скрипт, який буде робити більш складну логіку, якщо те, що можна запхати в confd, саме по собі не працює. І враховуючи, що etcd – це просто http, з ним цілком можна жити, спілкуючись fget'ом або будь-яким іншим вашим улюбленим http-клієнтом, на Python'е, на що завгодно. Можна писати ці маленькі скрипти на чому завгодно, не використовувати confd, якщо його логіка вам не підходить. Просто ми його використовували, так вийшло.



Як це працює? Тут є та сама схема з клієнтом, mcrouter'ом і memcached'ами. Що відбувається? Я намалював etcd окремо, тому що малювати його в кожне місце не потрібно, а etcd присутній на всіх машинах. На кожній машині, яка у нас є, є etcd. І кожен раз, коли ми спілкуємося з etcd, ми спілкуємося з петлею. Це важливо. На деяких машинах присутній confd. Confd не присутній у нас на memcached, confd займається тим, що він читає в etcd дані і конфігурує демон, сконфігурований на локальній машині. Тобто машини – це фіолетові прямокутники, а помаранчеві прямокутники – це демони, які запущені на машинах. Клієнт – це якийсь абстрактний клієнт, це може бути сервіс, це може бути якесь наше додаток, але воно хоче покористуватися mcrouter'ом. Mcrouter, в свою чергу, – це клієнт до memcached. Т. о. у нас є різні типи: у нас є клієнт, у нас є mcrouter, memcached і, крім цього, у нас є спільні компоненти – confd і announcer.

Що відбувається далі? Коли у нас стартує memcached, поруч з ним запускається маленький bash'евый скриптик і намагається визначити endpoint, по якому доступний memcached. Він бере цю рядок, тобто endpoint цього memcached (announcer-то знає, де запущений memcached) і пише в etcd: «Привіт, я memcached, я доступний тут». Другий memcached робить точно так само і пише: «Привіт, я теж memcached, і я доступний ось тут». І тепер у etcd є два записи про доступні memcached: memcached 1 і memcached 2, і у них різні IP-адреси, тому що це різні машини, і в etcd про це інформація вже є. Після цього у нас старутет mcrouter. На mcrouter точно так само є announcer, який розповідає, де доступний mcrouter. Etcd – це не одна машина, це кластерний сервіс, інстанси якого знаходиться на кожній машині. Confd читає з etcd те, що туди написали announcer'и memcached. Після цього confd дізнається, що memcahed доступні за цими двома адресами та конфігурує mcrouter, щоб mcrouter ходив саме в ці memcached. Далі, якщо один з цих memcahed впаде, etcd через кілька секунд проэкспайрится ключ, і ця запис зникне. Confd, який стежить за ключами у etcd, про це дізнається і переконфигурирует mcrouter так, щоб mcrouter перестав використовувати мертвий memcahed. Якщо з'явиться третій memcahed, то confd, знову ж таки, про це дізнається, бо confd на mcrouter відстежує etcd зміни всіх ключів, ну, умовно, memcahed*. Тобто як тільки там з'являється хоч якийсь memcahed, confd на mcrouter про це дізнається і переконфигурирует mcrouter. Для клієнта вся ця штука відбувається прозоро, він просто спілкується з mcrouter, і клієнт у свою чергу дізнається, як поспілкуватися з mcrouter з допомогою все того ж etcd. Працює воно приблизно так. На досить простому кейсі.



З плюсів тут – воно працює по-справжньому швидко. Тобто це відбувається миттєво. У випадку з memcahed і mcrouter це буде відбуватися менше секунди, тому що всі ці демони дуже швидко відновлений. І якщо у нас немає проблем зі зв'язаністю, кластер не розірваний, і все у нас нормально, то нам взагалі не потрібно пам'ятати про те, де знаходяться ці memcahed, ми їх неважливо де стартуємо, і вони відразу ж з'являються в кластері, туди відразу ж йде навантаження. І я як адмін стаю від цього щасливий, радісний, мені хочеться стрибати і бігати від щастя.

Крім того, мені не треба навіть думати про документацію, тому що мені не потрібно записувати, що у мене і де працює. У мене завжди вже записано, що де працює. Я завжди це бачу, тому що документація з'являється від того, що де запущено, а не навпаки. Тобто первинний запуск, а не документація. А у випадку, коли ми працюємо з хардкодом, спочатку ми хардкодим, а потім ми запускаємо.

Є, звичайно, кілька мінусів, вони написані, я думаю, їх навіть коментувати не варто, тому що система дійсно ускладнюється і дійсно стає страшно, що робити, якщо воно все розвалюється, і у нас з дата-центром приходить кошмар.

Тут є два моменти. Перший пункт – складність. Складність не так страшна, особливо враховуючи, що у нас дуже багато сервісів. Складність з хардкодом, складність з DNS значно вище, тому що тут один раз налаштували, а після цього рутинні операції по переміщенню сервісів до стопу сервісів, до старту сервісів, додавання, зміни – вони стають майже безкоштовними, і ми не правимо постійно DNS-зони, ми не правимо конфіги, ми не боїмося копіпаст циферки з одного місця в інше, воно все само працює. Тому воно з одного боку складно, але з іншого боку, воно насправді простіше, тому що час економить знатно.

По-друге, страшность. Звичайно, страшно, раптом вся ця штука зникне і прийде їй гаплик. Тут нас рятує в etcd таке щастя, що навіть у випадку, якщо відбувається повний краш все, що можна припустити, etcd залишається доступним для читання. Писати в розламаний кластер не можна, читати з розламаного кластера останні зміни, які в розламані частина кластера прилетіли, можна. Кожна нода etcd завжди зберігає повний дамп даних, які у нас є. Etcd не шардит дані, навіть якщо у машини опушені всі мережеві інтерфейси, що вона отримала в etcd, поки була в кластері, на ній залишається. Якщо у вас відвалився дата-центр, то у вас все одно залишиться кворум у кластері в частині дата-центрів, які у вас залишилися всі разом, і туди, якщо вам пощастить, навіть можна буде писати. Ну, як мінімум, звідти можна буде читати. Незважаючи на те, що зміни відбуваються часто, вони, тим не менше, відбуваються не кожні дві секунди, тому, навіть якщо у нас все розвалиться, нам все одно більш-менш нормально.

І є ще один мінус, це третій пункт – що демонам необхідно в хорошому випадку вміти з etcd працювати нативно. Так зазвичай відбувається краще, тому що багато демони не люблять релоад, тому що ми можемо помилитися в шаблонах, з допомогою яких confd буде конфігурувати наші демони, ми можемо помилитися де-небудь ще. Якщо це буде код, який вшитий прямо в наші сервіси, це буде працювати стабільніше і краще. Це бажано, тому що якщо б було необхідно, то такі штуки як confd і скриптики announcer не з'являлися б.



Ми намагалися вирішити кілька проблем, і насправді система service-discovering в такому вигляді, в якому я описав, повністю наші проблеми вирішити не можуть. Є кілька моментів.

Наше додаток в пошуку стартує, читає 40-200 Гбайт в оперативну пам'ять, лочит це в пам'яті, і потім, нарешті, починає слухати порт, і починає з цим працювати. І так воно живе довго. Записи у нас мало, у нас дуже багато читання. Це пошукова система, зрозуміло, що пошук майже нічого не дізнається про те, коли з ним працюють клієнти. Але, на жаль, з-за такого типу життя демон у нас стартує п'ять хвилин. Відповідно, якщо у мене буде змінюватися якась дрібна штука, я не можу дозволити собі п'ять хвилин простою сервісу, ну ніяк, тому що вони всі одночасно рестартнутся – я ж спеціально домігся того, що вони швидкі, а тепер вони все швидко самі рестартятся, я так жити не можу. Це перша проблема. Ситуація з confd і постійним рестартом демонів погана, ми не можемо собі дозволити.

Друга проблема, як в будь-якому достатньо довго живе проекті, а Пошук Mail.ru існує з 2008 року, є достатня кількість коду, який ніхто не хоче влазити. Розробники звільнилися і ін. І, відповідно, ми ніяк не можемо навчити деякі наші демони працювати з etcd. Деякі можемо, які в гарячій розробці.

Це з тих двох проблем, які ми не можемо вирішити самі.

Третій пункт – це задача, яку ми хотіли вирішити, щоб нам не було необхідності переконфігурувати тисячі місць.

Нам, начебто, стає краще, але у нас є перший пункт, за яким ми не можемо перезапускати нічого.

І ще один момент, це загальна вимога до будь-якої системи – нам не повинно стати гірше з моменту, як ми її втілимо. Якщо у нас зараз використовується хардкод і DNS, то якщо ми впроваджуємо систему виявлення сервісів, ми не повинні одержати деградацію в надійності, порівняно з тим, що у нас є. Тому ми пішли далі і почали впроваджувати нові хороші милиці.



Ми почали думати і вирішили зробити трошки хитрозакрученный сервіс, такий щоб додатка або наші сервіси, клієнти не мали необхідності взагалі змінювати тип зв'язку з якимись іншими сервісами, серверами. В різних контекстах один і той же сервіс може бути як клієнт, так і сервер.



Цю схему ви вже бачили. Ось так воно у нас живе в нормальному режимі, і в цьому випадку confd конфигурит нашого клієнта. Клієнт – це той додаток, яке я не хочу ребутать.

І тому ми вирішили зробити ось так:



Ми додаємо HAproxy між клієнтом і mcrouter. Знову ж таки, клієнт і mcrouter – це абстрактні якісь приклади, можна засунути сюди фактично що завгодно.

Який ми отримуємо від цього profit? Клієнт спілкується з HAproxy. HAproxy слухає петлю, в конфіг клієнта у нас забито, що memcached доступний за 127.001:11.211. Завжди, скрізь, на будь-якому сервері, який захоче скористатися mcrouter. У нас запущений HAproxy, який слухає петлю, і в конфіги завжди забита петля. Confd, в свою чергу, конфигурит вже не клієнт, confd конфигурит HAproxy, HAproxy слухає петлю і проксирует ці звернення в справжній mcrouter. Де знаходиться справжній mcrouter, HAproxy дізнається з confd, confd, в свою чергу, – з etcd.

Тут, мені здається, нічого сильно не стає складніше. Відразу питання: чому на mcrouter немає HAproxy для того, щоб з допомогою HAproxy спілкуватися з memcached? Насправді, його можна туди вставити, але mcrouter розумний і працює вище, ніж по протоколу TCP, він залазить у протокол memcached, і тому mcrouter, на жаль, доводиться конфигурить вручну. Але mcrouter майже не зберігає стану, тому йому рестарт не страшний. Демони, які не зберігають стану, ми можемо легко ребутать, і вони швидко стартують. Демони, які зберігають стану, ми не можемо ребутать, тому що вони стартують довго. І взагалі це боляче. Тому ми до них вонзаем милицю у вигляді HAproxy.



Чому ми робили саме так? Тому що ми не могли нічого ребутать. Тепер ми можемо не ребутать і можемо ребутать тільки HAproxy, це безкоштовно. Це умовно безкоштовно, але набагато більше безкоштовно, ніж п'ять хвилин простою демона з-за зміни десь якогось маленького конфижика.

По-друге, враховуючи, що багато сервісів і коду різного багато, конфігурувати потрібно багато, і ці шаблони писати в confd теж стомлює. Один раз написати всі шаблони для HAproxy сильно простіше, і ми потім ні про що не думаємо, просто скрізь забиваємо петлю. Для memcached це буде порт 11.211, для MySQL це буде порт 33.06, але всі програми завжди спілкуються з петлею. Вони, взагалі, ні про що не думають. Нам навіть переконфігурувати нічого ніколи ніде не потрібно.

По-третє, у HAproxy є чудові репорти, з яких можна знімати статистику, яку часто з програми дістати важко, це дісталося нам безкоштовно, це не було метою, але це раптово можна використовувати, і це допомагає.

По-четверте, HAproxy – це не просто proxy, це ще й балансувальник. Якщо у нас є прості демони, які вимагають балансування, яка має можливість бути реалізованою за допомогою HAproxy, ми можемо отримати її безкоштовно.

І додаткові плюшки – таймаутинг на коннекти, додаткові активні чеки сполук до бэкендов. Це все в HAproxy є, і нам немає необхідності вбудовувати це демони, які спілкуються з нестандартним протоколами, які зовсім навіть не http.

Але є проблема, HAproxy не вміє UDP, а UDP демони існують. Костянтин розповідав про Graphite, Graphite працює по UDP, він уміє TCP, але коли метрик багато, TCP – це сильний оверхед тут, а HAproxy UDP не вміє принципово. Тому ми використовуємо DNAT.



DNAT має той же функціонал, при необхідності він може умовно замінити HAproxy у цьому кейсі, але у нього немає ніяких додаткових булочок HAproxy. Тому з ним можна жити і обмежено використовувати. Якщо у нас реально проблема з продуктивністю HAproxy, наприклад, то ми можемо вставити DNAT замість HAproxy і для TCP, але в цьому випадку всі проблеми TCP, коннектов, таймаутів і всього такого, про що я говорив, повинні бути все-таки вирішені клієнтом, якщо ми використовуємо в TCP-режимі. Тут показаний приклад зі statsd і Carbon'ом для Graphite, коли одне з іншим спілкується по UDP. І тут багато компонентів не вказано зразок annauncer'ів, просто тому що вони не влазять в цю схему, екран маленький, а руки великі.



До чого ми все це робили? Нам стало майже безболісно рухати сервіси між різними місцями. Мережі Mail.ru володіють цікавою особливістю – якщо необхідно, умовно, сервер витягнути з однієї стійки і поставити в іншу, то мені потрібно переконфігурувати йому мережу. Я не можу по-іншому. Тому в мене ці процедури болючі. Навіть просто взяти опустити один memcached на одному сервері, підняти його на іншому і так, щоб замінити, мені все одно необхідно переконфігурувати мережу. Ось так у нас влаштована мережа. Це завдання ми вирішували за допомогою цього інструменту, і в цілому вона стає сильно простіше, тому що нам немає необхідності йти і правити 25 мережевих конфіги і потім ще 1000 конфіги і демонів.

Другий момент – це, коли у нас є mcrouter або якийсь інший демон схожою ролі, під ним є кілька memcached і memcahed раптово потрібно зробити більше. Ці штуки з допомогою DNS не вирішуються, в принципі, а з допомогою etcd вони вирішуються, тому що ми можемо написати правильні шаблони і правильно розкласти дані в etcd при старті демонів, так щоб пули розширювалися і броди наші були шовковистим.

Третій момент – це додатковий момент, який нам дістався безкоштовно. Демонів дуже багато, щось запущено 35 років тому, і ми про це не знаємо, воно не в моніторингу, документації на це немає, виявляємо тільки, коли падає. Якщо у нас все засунуто в etcd, то ми завжди бачимо, хто з ким спілкується. По-друге, якщо у нас є HAproxy ми ще і бачимо, скільки спілкується. Ми отримуємо всі метрики безкоштовно. По-третє, у нас в цьому випадку відділений момент конфігурування демона, який працює, і конфігурування клієнтського демона. Ні, вони якраз поєднані, в звичайному кейсі вони об'єднані, з-за цього ми можемо просто взяти опечататься в порту, коли ми конфігуруємо клієнтський демон, і отримати проблему.

Це не вигаданий кейс. Я з цим кейсом зіткнувся два дні тому. У мене адмін просто помилився і вніс інший порт. На 10-ти серверах це викотилася, сервіс почав деградувати, і ми почали сумувати. Відбувається це тому, що люди не досконалі, так буває. Але якщо ми використовуємо роботів, то роботи при правильній експлуатації сильно більше здійснені, ніж люди, якщо за ними правильно доглядати. І коли у нас з'являється новий сервіс, тільки в цьому випадку нам необхідно конфігурувати щось вручну. Якщо ми переконфигурируем старі сервіси, у нас все відбувається автоматично і у нас немає необхідності постійно коммитить, що вручну, рестартовать, жорстко моніторити і все таке.

Контакти
» s.puzirev@corp.mail.ru
» Блог компанії Mail.Ru Group

Ця доповідь — розшифровка одного з кращих виступів на конференції з експлуатації та devops RootConf.

Також деякі з цих матеріалів використовуються нами в навчальному онлайн-курс по розробці високонавантажених систем HighLoad.Guide — це ланцюжок спеціально підібраних листів, статей, матеріалів, відео. Вже зараз у нашому підручнику понад 30 унікальних матеріалів. Підключайтеся!

Ну і головна новина — ми почали підготовку весняного фестивалю "Російські інтернет-технології", в який входить вісім конференцій, включаючи RootConf. Набираємо доповідачів!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.