Масштабована конфігурація nginx

Ігор Сисоєв

Ігор Сисоєв ( isysoev
Мене звуть Ігор Сисоєв, я автор nginx і співзасновник однойменної компанії.

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

Я буду говорити про масштабованої конфігурації nginx, але це не про те, як обслужити за допомогою nginx сотні тисяч одночасних з'єднань, тому що nginx для цього налаштовувати не треба. Потрібно виставити адекватне число робочих процесів або поставити його в режим «авто», поставити worker_connections в 100 000 з'єднань, після цього займатися налаштуванням ядра — це набагато більш глобальне завдання, ніж просто налаштування nginx. Тому я буду розповідати про іншу масштабованості — про масштабованості конфігурації nginx, тобто про те, як забезпечити зростання конфігурації від сотні рядків до кількох тисяч і при цьому витрачати мінімальну (бажано константна) час на супровід цієї конфігурації.



Чому, власне, виникла така тема? Близько 15 років тому я почав працювати в Рамблері і адміністрував сервера, зокрема, apache. А у apache є така неприємна особливість, яка добре ілюструється наступними двома конфігураціями:



Тут два location'а вони йдуть в різному порядку. Один і той же запит, залежно від того, яка конфігурація, буде оброблений різними файлами — або php-файл або html-файлом. Тобто при роботі з конфігурацією apache порядок має значення. І скасувати це не можна — apache при обробці запитів проходить по всім location'ам, намагається знайти ті, які збігаються якимось чином з цим запитом, і збирає конфігурацію з усіх цих location'ів. Він зливає її і, врешті-решт, використовує результуючу.

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

В apache щоб додати пеклі в конфігурацію є ще кілька секцій, які працюють так само, вони обробляються в різному порядку, але з них усіх збирається одна результуюча конфігурація. Все це робиться в runtime, тобто якщо у вас багато модулів, то в кожному модулі буде відбуватися злиття конфігурацій (це частково пояснює, чому nginx в деяких тестах швидше apache — тому що nginx в runtime конфігурації не зливає). Частина цих секцій і більшість директив можна розмістити у .htaccess файли, які розкидані по всьому сайту, а для того, щоб зробити життя вашу і ваших колег ще «цікавіше», ці файли можна перейменувати, і шукайте цю конфігурацію…

А «вишенькою на торті» є RewriteRules, які дозволяють зробити конфігурацію схожою на sendfile. Деякі оцінили гумор, оскільки, на щастя, більшість вже не знають, що це таке.

RewriteRules — взагалі кошмар. Дуже багато адміністраторів приходять не стільки з бекграундом apache, скільки з бекграундом адміністрування apache на поділюваному хостингу, тобто коли єдиним засобом адміністрування був .htaccess. І в ньому вони роблять дуже хитромудрі RewriteRules, які дуже важко розуміти і в силу синтаксису, і в силу логіки.

Це був один з недоліків apache, який мене дуже дратував, він не дозволяв створювати досить великі конфігурації. В ході розробки nginx я хотів змінити це, я виправив багато дратівливих рис apache, додав своїх власних. Ось так виглядає попередній приклад в nginx:



На відміну від apache, незалежно від порядку location'ів запит буде оброблений однаково, т. к. nginx шукає максимально можливе співпадіння з префиксным location'ом, не заданим регулярним виразом, і після цього вибирає цей location. Використовується конфігурація обраного location'а, а всі інші location'и ігноруються. Такий підхід дозволяє писати конфігурації з сотнею location'ів і не думати про те, як це буде впливати на все інше, тобто отримуємо свого роду контейнери. Ви ізолюєте обробку в одному невеликому місці.

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

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

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



На цьому слайді ілюстрація того, як не потрібно робити. Якщо у вас такі сайти вже є, то їх треба переробляти.

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

Після цього ми починаємо перевіряти location'и з регулярними виразами в зворотному порядку, тобто ми увійшли в самий вкладений location, дивимося, чи є там регулярний вираз. Якщо ні, то спускаємось на рівень нижче і т. д. Знову ж таки, перший збігся location з регулярним виразом «перемагає». Такий підхід дозволяє зробити таку обробку:



У нас тут є два location'а з регулярними виразами, але для запиту /admin/index.php буде вибрано вкладений перший location, а не другий.

Крім того, другу частину пошуку регулярних виразів можна заборонити, якщо позначити location символом ^~:



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

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



В nginx є інші методи виділення загальних частин конфігурації. Насамперед, це спадкування конфігурації з попереднього рівня. Наприклад, тут ми можемо написати на рівні http включити sendfile для всіх серверів і всіх location'ов:



Ця конфігурація успадковується у всі вкладені сервера і location'и. Якщо нам потрібно десь скасувати sendfile, тому що, припустимо, файлова система її не підтримує або з якихось інших причин, то ми можемо його вимкнути в конкретному location'е або в конкретному сервері.

Або, наприклад, для сервера ми можемо написати спільний корінь, де потрібно перевизначити.

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

Єдине, що не можна робити подільних, — наприклад, на рівні http не можна описувати location'и. Це було зроблено свідомо. В apache це можна робити, але доставляє чимало проблем при використанні.

Особисто я віддаю перевагу описувати location'и явно прямо в конфігурації. Якщо вам не хочеться цього робити, то ви можете його инклудить через зовнішній файл.



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

Правильний підхід — використання copy-paste. Тобто, всередині location'а повинні бути всі необхідні директиви для його обробки.

Звичайний аргумент під любителів DRY (don't Repeat Youself) полягає в тому, що якщо треба буде щось виправити, то це можна виправити в одному місці і все буде чудово.

Насправді, сучасні редактори мають функціональність find-replace. Якщо вам потрібно, наприклад, виправити ім'я/порт бекенду або поміняти root, заголовок, переданий бэкенду, і т. п., ви можете спокійно це зробити з допомогою find.

Для того щоб зрозуміти, чи потрібно вам в даному місці змінити якийсь параметр, досить пари секунд. Наприклад, у вас 100 location'ів, ви на кожен location витратите за 2 сек., разом 200 сек. ~ 3 хв. Це небагато. А от коли в майбутньому вам доведеться розв'язати якийсь location від загальної частини, то це буде вже набагато складніше. Вам потрібно буде зрозуміти, що змінювати, як це буде впливати на інші location'и і т. д. Тому, що стосується конфігурації nginx, потрібно використовувати copy-paste.

Взагалі кажучи, адміністратори не люблять витрачати багато часу на свої конфігурації. Я і сам такий. У адміністратора може бути 2-3 улюблених продукту, він може з ними возитися дуже багато, при цьому існує десяток інших продуктів, на які часу витрачати не хочеться. Наприклад, у мене на персональному сайті є пошта, це Exim, Dovecat. Їх я не люблю адмініструвати. Я просто хочу, щоб вони працювали, а якщо треба щось додати, щоб це зайняло не більше пари хвилин. Мені просто лінь вивчати конфігурацію, і, думаю, більшість адміністраторів nginx — вони такі ж, адмініструвати ngnix хочуть якомога менше, їм важливо, щоб він працював. Якщо ви такий адміністратор, то використовуйте copy-paste.

Приклади того, як можна коротенькі немасштабируемые конфігурації перетворити в те, що треба:



Тут людина думає, що написав регулярний вираз, все мало, все добре. Насправді, т. к. тут є регулярний вираз, це погано — воно може впливати на все інше. Тому особисто я роблю ось так:



Якщо у вас цей root загальний для всіх location'ів або, принаймні, використовується в більшості з них, то це можна зробити навіть так:



Це, взагалі, легальна конфігурація, тобто абсолютно порожня конфігурація location'ів.

Другий спосіб уникнути copy-paste — це ось такий приклад:



Адміністратори, які раніше працювали з apache, думають, що admin/index.php повинен запитувати авторизацію. В nginx це не працює, т. к. index.php обробляється в одному location'е, а location/admin зовсім інший. Але можна зробити вкладену конфігурацію і тоді index.php природно запросить авторизацію.

Часто буває потрібно використовувати регулярні вирази для того, щоб «выкусывать» якісь частини з URL і використовувати їх при обробці. Ось це поганий спосіб:



Правильно — це використовувати вкладені location'и, таким чином, ми ізолюємо регулярні вирази від конфігурації всього іншого сайту, тобто далі цього location/img/, який поміщається на екран, управління не піде:



Ще одне місце, де можна в nginx досить безпечно використовувати регулярні вирази, це map'и, тобто формувати змінні на основі якихось інших змінних за допомогою регулярних виразів і т. д.:



Я нічого не сказав про використання Rewrites, тому що їх не треба використовувати взагалі. Якщо ви не можете їх не використовувати, то використовуйте їх на стороні бекенду.

Evil — теж не рекомендована конструкція в nginx, тому що, як працює всередині Evil знає людина 10 у світі, і ви навряд чи входите до їх числа.

Ось така конфігурація, коли у нас два if (true):



Очікується, що у нас будуть вимкнені gzip і etag. Насправді відпрацює тільки останній if.

Є одне безпечне використання if — це коли ви використовуєте його для повернення відповіді клієнту. Можете використовувати rewrite в цьому місці, але я його не люблю, я використовую return (він дозволяє додати код і т. д.):



Резюмуємо:

  • бажано використовувати тільки префіксні location'и;
  • уникайте регулярних виразів, якщо ж регулярні вирази все-таки потрібні конфігурації, то їх краще ізолювати;
  • використовуйте map'и;
  • не слухайте людей, які кажуть, що DRY — це загальна парадигма. Це добре, коли вам подобається продукт, або ви програмуєте продукт. Якщо ж вам просто потрібно полегшити свою адміністраторську життя, то copy-paste — це для вас. Ваш друг — це редактор з хорошим find-replace;
  • не використовуйте rewrites;
  • використовуйте if тільки для повернення якоїсь відповіді клієнту.
Питання з залу: Якщо я використовую rewrites http https, де його краще використовувати в nginx або на бэкенде?

Відповідь: Його використовувати в nginx. Ідеально це так — ви робите два сервера. Один сервер у вас plane text'и і він робить тільки rewrites. У цьому місці буде буквально кілька директив — server listen на порту, server name, якщо потрібен, і return 301 або 302 на https з дублюванням request URI. Там навіть rewrite не потрібен, використовуйте return.

Якщо ви хочете щось більш складне зробити, то десь можна вставити if. Припустимо, частина location'ів у вас відпрацьовують в plane text'е, опишіть їх за допомогою регулярних виразів в map'е, наприклад, а все інше можна редірект на https. Або, навпаки, вставити всередині кожного location'а по одному if, який буде редірект на https.

Питання з залу: Спасибі за nginx. У мене кілька жартівливе запитання. Ви не плануєте додати ключик запуску або ключик компіляції, який не дасть використовувати директиву include, не дасть використовувати if, регулярні вирази в location'ах?

Відповідь: Ні, навряд чи. Зазвичай ми додаємо якісь директиви, покращуємо їх і потім робимо їх deprecated. Вони якийсь час виводять warning в балці, перш ніж зникнути повністю, але вони працюють в якомусь режимі. Ми навряд чи будемо робити те, що ви сказали, ми краще напишемо хороший User Guide, можливо, за матеріалами цього виступу.

Питання з залу: Звичайне бажання, яке виникає при використанні if, це тому що його можна використовувати на сервері і в location'е, а map використовувати не можна. Чому так вийшло?

Відповідь: Всі змінні в nginx обчислюються on the map, тобто якщо map описуємо на рівні http, це не означає того, що при обробці запиту ця змінна буде обчислюватися обов'язково. Map потрібен для того, щоб отмаппить що-то одне, а потім щось одне — в інший, а результуючу змінну ви можете використовувати if або всередині якогось виразу, проксировать кудись і т. п. Map — це просто декларація… Може бути, їх має сенс перемістити на сервер для того, щоб зробити їх локальними для сервера, якщо у вас одна і та ж змінна. Там просто було складніше програмувати, тому вони були винесені на глобальний сервер. В nginx немає змінних, які були б локальними всередині сервера.

З точки зору performance ніяких проблем немає, це просто незручно. Треба буде зробити, припустимо, три сервера і три map'а, а у змінної буде префікс «сервер такий-то»… Ви можете їх, в принципі, описувати перед сервером, тобто ці map'и — одна буде перед першим сервером, потім перед другим… конфіг не треба буде скакати вгору-вниз, вони будуть ближче до сервера.

Питання з залу: Я погано знайомий з логікою роботи return'ів. Розкажіть, будь ласка, де варто використовувати return'и замість rewrite'ів, якісь use case'и конкретні?

Відповідь: Взагалі, rewrite замінюється на таку конструкцію: location з регулярним виразом, в якому можна зробити якісь captures — захоплення, виділення, і на директиву return. Тобто один rewrite — його ліва частина в location'е, а права частина — це те, що буде в return'е після коду відповіді. Return пропонує можливість повернення різного коду відповіді, а в rewrite для повернення клієнту є лише 301, 302. Return може повернути 404 з якимось тілом, може — 200, 500, може повернути redirect. А в його тілі можна використовувати змінну, щось написати. Якщо це 301, 302, то це не тіло, це вже URL, на який потрібно зробити redirect. Загалом, у return'а багатшими функціональність.

Питання з залу: У мене прикладної питання. Nginx можна використовувати як поштовий проксі. Чи можна дати SMTP-доступ в поштовий клієнт, відправити лист через цей поштовий клієнт, і nginx'ом перехопити дані і відправити на скрипт, минаючи поштовий веб-сервер? Зараз ми цю задачу реалізуємо, використовуючи postfix — він перехоплює лист і далі кидає на скрипт, де відбувається обробка.

Відповідь: Я сумніваюся, що це можна зробити за допомогою nginx. Я можу описати коротко функціональність, яка є SMTP Proxy в nginx. Він вміє робити наступне — до нього з'єднується SMTP-клієнт, показує якусь аутентифікацію, nginx йде у зовнішній скрипт, перевіряє ім'я-пароль, а потім каже, пускати клієнта на якісь сервера (і передає, на які конкретно), або не пускати. Це все, що він вміє робити. Якщо вирішено кудись пускати, то nginx по SMTP з'єднується з сервером і передає йому. Лягає це Ваш сценарій, я не можу сказати. Навряд чи.

SMTP Proxy з авторизацією з'явився, тому що в Рамблері для клієнтів пошти є спеціальний сервер, через який ці клієнти відправляли пошту. І виявилося, що близько 90% сполук — це не клієнти Рамблера, спам та віруси. Щоб не навантажувати postfix'и, не піднімати зайві процеси, перед цим поставили nginx, який перевіряє, чи надає цей клієнт свої аутентифікаційні дані. Власне, для цього це і було зроблено — просто, щоб відбивати «сміттєвих» клієнтів.

Питання з залу: Ви сьогодні згадали про контейнери, це, звичайно, багатообіцяючий підхід, але він передбачає зміну топологію і динамічну конфігурацію. Зараз це поки призводить до того, що люди будують якісь зовнішні «милиці», які періодично реагують на події зміни топології, генерує через який-небудь шаблон актуальний конфіг nginx'а, підсовують його і штовхаються, щоб перерахував конфіг. Цікаво — у компанії є якісь плани щодо розвитку в бік контейнеризації, тобто в бік забезпечення більш зручних і природних коштів для цього тренда?

Відповідь: Дивлячись, що Ви маєте на увазі під контейнерами в даному випадку. Я, коли говорив про контейнери, порівнював, я говорив, що ці location'и виглядають ізольовано один від одного.

Запитання: Ми повернулися назад до docker'у, до можливості запуску бэкендов десь в контейнерах, який динамічно виконується на різних хостах, і треба, грубо кажучи, додати в балансування новий хост…

Відповідь: У нас в NGINX+ одна з частин Advansed Load Balansing, як раз, має на увазі, що ви можете динамічно додавати сервера в апстрім. Виходить, вам не потрібно робити reload конфига nginx'а, а все це робиться на льоту — для цього є API.

Ще туди включені активні хелсчеки. Коли звичайний open source nginx з'єднується з бекендом, якщо бекенд не відповідає, то до нього nginx деякий час не звертається, тобто своєрідний хелсчек теж тут є, але страждають клієнти. Якщо у вас 50 клієнтів одночасно пішло на один бекенд, а він лежить або по таймауту відвалиться через 5-10 сек., то клієнти це побачать, і тільки після цього їх перекинуть на інший апстрім. В NGINX+ у нас є проактивне тестування бэкендов, тобто бэкенды самі тестуються, і клієнти на впали бэкенды просто не відправляються.

Запитання: І, раз є активний хелсчек, то, може, вже і запилили тоді красиву JSON-подібну сторінку статусу, яку можна отпарсить?

Відповідь: Так, у нас є моніторинг, він доступний, в тому числі, і через JSON, а також він є у вигляді красивого html'а.

Контакти
» isysoev
» Блог компанії Nginx

Ця доповідь — розшифровка одного з кращих виступів на конференції розробників високонавантажених систем HighLoad++. Зараз ми активно готуємо конференцію 2016 року — у цьому році HighLoad++ пройде в Сколково, 7 і 8 листопада.

Секцію DevOps в цьому році готував окремий Програмний комітет, який займалася компанія Express42. Півтора десятка доповідей, включаючи доповідь Максима Дуніна про новини з світу Nginx.

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

0 коментарів

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