Липсващият swap файл, който замрази сървърите ни
На 24 септември един от производствените ни сървъри спря да отговаря. Балансьорът на натоварването го маркира като нездрав, а инструментът ни за отдалечени команди не можеше да го достигне — но нищо не се беше сринало и нищо не беше убито заради прекалено използване на памет. Сървърът беше в thrashing: без swap пространство и контейнери, на които е позволено да ползват повече памет, отколкото машината има, Linux прекарваше времето си в изтласкване и повторно четене на файловете, от които се нуждае, за да работи, и почти не напредваше. Добавянето на swap файл от 2 GB на всеки сървър оправи нещата. По-интересният урок беше за health check-овете.
Как изглеждаше „замръзването“
- Health check-овете на балансьора изтичаха за този сървър.
- Командите, изпратени през AWS Systems Manager, стояха в „Pending“ и никога не се изпълняваха.
- В логовете нямаше грешка out-of-memory. Нито един процес не беше умрял.
Седмица по-рано бяхме преместили продукцията на по-малки ARM сървъри с по 2 GB RAM. Нашият staging сървър — още по-малък — беше замръзнал по същия начин, докато разопаковаше образ на контейнер по време на деплой.
Защо няма грешка за памет?
Linux убива процес само когато наистина изчерпа паметта, която може да възвърне. Преди тази точка той възвръща памет от page cache — копията на файлове, които държи в RAM, включително кода на всяка работеща програма.
В нашите сървъри общите лимити на паметта на контейнерите възлизаха на около 3,3 GB на машина с около 1,8 GB използваема. Обикновено не ги ползват всички до лимита си. Когато го направиха, ядрото продължи да изхвърля кода на програмите от кеша, за да освободи място, и после трябваше да го чете отново от диска в момента, в който този код отново се изпълняваше. Този цикъл — thrashing — може да направи машината почти напълно неотзивчива, без технически да изчерпва паметта. Така нищо не се убива и нищо не се възстановява.
Без swap ядрото няма къде да сложи паметта, която е заделена, но рядко се използва, затова трябва да я вземе от кеша.
Поправката
Скриптът за първоначално настройване на сървъра сега създава swap файл от 2 GB с ниска swappiness настройка (10) при всяко стартиране или поправка на сървър. Ниската swappiness означава, че ядрото пак предпочита да държи работната памет в RAM, но рядко докосваните страници могат да се преместят на диск, вместо да изтласкват кода на програмите от кеша. Приложихме го на живо и към двата производствени сървъра и към staging, а деплой конвейерът публикува отново скрипта за първоначално настройване при всеки производствен деплой, така че заменен сървър също да го получи.
Частта, която ни безпокои повече: нездравият сървър остана
Групата ни сървъри използваше EC2 health check-ове — „работи ли машината?“ — а не health check-ове на балансьора — „отговаря ли на заявки?“. Затова сървър, от който балансьорът се е отказал, за групата оставаше здрав. Никога нямаше да бъде заменен. Продукцията тихо щеше да върви на един сървър, докато някой не забележи и не рестартира другия.
Превключването на групата към health check-ове на балансьора звучи като очевидна поправка, но за нас не е прямолинейно: по време на деплой сървърите умишлено отговарят на health endpoint-а си с грешка, за да ги дренира балансьорът (вижте как стигнахме до нула 502 при деплойте). Група, която заменя сървъри по този сигнал, би ги заменяла по средата на всеки деплой.
Затова засега добавихме стъпка в нашия контролен списък за деплой: след всеки деплой проверяваме health-а на всяка цел в балансьора, а не само дали работата по деплоя е минала. Ако командите към сървър стоят в „Pending“, той е в thrashing — рестартирайте го; той вече е извън ротацията.
Какво взехме от това
- „Не се срива“ не значи „здрав“. Thrashing не дава грешка, а само такава бавност, че машината спира да отговаря.
- Малките сървъри се нуждаят от swap. Дори малко, с ниска swappiness, дава на ядрото къде да сложи студената памет.
- Лимити на паметта, които в сбор надхвърлят машината, са залог. Понякога го губите. Знайте кога сте го направили.
- Знайте на кой health check се доверява автоматичното ви мащабиране. Health check, който пита само „включена ли е VM-ът?“, няма да замени сървър, който е спрял да работи.