Как стигнахме до нула 502 грешки при деплойте
При всеки деплой на бекенда на Mythex няколко заявки се провалянха с 502 Bad Gateway. Не много — но 502 по време на деплой означава, че нечие кликване не е направило нищо или че билд, който някой гледа, е заседнал за момент. Проследихме го до два отделни проблема: самолекуващ се watchdog, който се бореше с нашите деплойи, и разминаване между момента, в който сървърът спира да слуша, и момента, в който балансьорът спира да му праща трафик. При три поредни продукционни деплоя след поправките броят падна от 10 на 6 и на 0.
Конфигурацията
Nашите API и оркестратор работят като контейнери на малка група сървъри зад Application Load Balancer (ALB) на AWS. Деплойът обновява по един сървър наведнъж: дренира старите контейнери, стартира нови и продължава нататък. Watchdog на всеки сървър проверява дали контейнерите работят и рестартира всеки, който не работи — съществува, за да се върне сринал се процес в 3 сутринта, без някой да се буди.
Два са разумни сами по себе си. Заедно не бяха.
Проблем 1: watchdog-ът се състезаваше с деплоя
Watchdog-ът работеше веднъж в минута и се „лекуваше“ при един неуспешен health check. Деплоят умишлено дренира услуги до 150 секунди — през което health check трябва да се провали. Така watchdog-ът се задействаше при всеки деплой.
Повечето пъти губеше състезанието безвредно. Веднъж спечели: пренаписа compose файла (описанието на това кои контейнери трябва да работят) по средата на деплой и остави сървър без контейнер на gateway изобщо — докато деплойният job докладваше успех.
Поправките:
- Един лок. Деплойът сега взема същия файлов лок като скрипта за първоначално настройване на сървъра, така че watchdog-ът и деплойът никога не могат да работят едновременно.
- Четири удара, не един. Watchdog-ът се нуждае от четири поредни неуспешни проверки, преди да действа. Дренираща се услуга вече не изглежда мъртва.
- Една дефиниция. Compose файлът вече има един източник, публикуван към сървърите, вместо копия, които можеха да се разминат.
- Проверявайте, не се доверявайте. Деплойният job вече не минава, защото една команда е върнала код. Той проверява API-то на всеки сървър, оркестратора, gateway-а и publish worker-а, преди да може да успее.
Проблем 2: сървърът си тръгваше преди трафика
Вторият проблем беше по-тих. Когато деплой дренира сървър, сървърът започва да отговаря на health endpoint-а си с 503, за да го махне балансьорът от ротацията. Нашият правеше това 5 секунди, после спираше.
Но ALB проверяваше здравето само от време на време и се нуждаеше от няколко неуспешни проверки поред, преди да спре да праща към цел — до около 60 секунди от край до край. Така за около 55 секунди балансьорът продължаваше да праща реални заявки към сървър, който вече не слушаше. Това бяха нашите 502.
Поправката беше двете времеви линии да се съгласуват:
- Target групата сега проверява на всеки 5 секунди и маркира цел като нездрава след 2 неуспеха — около 10 секунди, за да забележи.
- Дренажът продължава да обслужва реален трафик 15 секунди след като започне да проваля health проверките, така че винаги има припокриване.
- Тези настройки живеят в скрипта, който осигурява инфраструктурата, а не в конзола, която някой е кликнал веднъж.
Числата да се държат едно за друго
Тайм-аутовете като тези зависят един от друг. Дренажът трябва да трае по-дълго от времето, което балансьорът се нуждае, за да забележи; дренажът плюс спирането трябва да приключи, преди контейнерът да бъде принудително убит. Лесно е някой да промени едно число след шест месеца и тихо да върне бъга.
Затова записахме тези връзки като тестове: дренажът трябва да е по-дълъг от интервала на health проверката, умножен по прага на неуспех; времето да бъде отново маркиран като здрав трябва да остане кратко; целият дренаж трябва да се побира в grace period-а за спиране на контейнера. Променете едно число без останалите и CI се проваля. Тествахме и самите тестове с мутационно тестване — променихме всяко число на грешна стойност и проверихме дали някой тест го хваща.
Измерването на деплой е лесно да се обърка
Нашето първо измерване казваше, че чист деплой е около 3% счупен. Не беше. Броехме всеки отговор, различен от 200, а 503 от health endpoint по време на дренаж не е провал — това е дренажът, който си върши работата, като казва на балансьора да не избира този сървър, докато той продължава да обслужва реални заявки.
Това, което потребителите усещат, е 502: заявката не е достигнала до нищо. Броенето само на тях даде реалната картина — 10, после 6, после 0 при три продукционни деплоя.
Какво взехме от това
- Самолекуването трябва да знае за планирания престой. Watchdog, който не може да различи деплой от срив, рано или късно ще се бори с деплоя.
- Дренирането е ръкостискане. Сървърът трябва да продължи да обслужва, докато балансьорът доказуемо е спрял да праща трафик, а този тайминг се задава на балансьора, не на сървъра.
- Кодирайте инвариантите като тестове. Оперативните числа се разминават. Тестовете, които ги свързват едно с друго, не.
- Решете какво измервате, преди да измервате. „Грешки“ беше грешната метрика; „заявки, които не са достигнали до нищо“ беше правилната.
Ако публикувате приложение в Mythex, нищо от това не ви е видимо — и в това е смисълът. Свързано: как публикуваното ви приложение остава онлайн, докато го преиздавате.