Логовете в Kubernetes (и не само) днес: очаквания и реалност

Логовете в Kubernetes (и не само) днес: очаквания и реалност

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

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

  • някои искат да виждат security и audit логове;
  • някои — централизирано логиране на цялата инфраструктура;
  • а на други им е достатъчно да събират само логовете на приложението, изключвайки, например, балансировачите.

За това как реализирахме различните "искания" и с какви трудности се сблъскахме — под кат.

Теория: за инструментите за логове

Предистория за компонентите на системата за логиране

Логването е преминало дълъг път, в резултат на което се разработиха методологии за събиране и анализ на логове, които ние и прилагаме днес. Още през 1950-те години в Fortran се появи аналог на стандартните потокове за вход и изход, които помагаха на програмистите в отстраняването на грешки в техните програми. Това бяха първите компютърни логове, които улесняваха живота на програмистите от онова време. Днес виждаме в тях първия компонент на системата за логиране — източник или "производител" (producer) на логове.

Компютърната наука не е стояла на място: появиха се компютърни мрежи, първите клъстери... Започнаха да функционират сложни системи, състоящи се от няколко компютъра. Системните администратори вече бяха принудени да събират логове от няколко машини, а в особени случаи можеха да добавят и съобщения от ядрото на ОС, в случай че е необходимо да се разследва системен срив. За да опишат системите за централизирано събиране на логове, в началото на 2000-те години излиза RFC 3164, който стандартизира remot_syslog. Така се появи още един важен компонент: колектор (събирач) на логове и тяхното хранилище.

С увеличаването на обема на логовете и разпространението на уеб технологиите, стана необходимо логовете да бъдат удобно показани на потребителите. На мястото на простите конзолни инструменти (awk/sed/grep) дойдоха по-усъвършенствани прегледници на логове — третият компонент.

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

Складовете също направиха сериозен скок: от обикновени файлове преминаха на релационни бази данни, а след това и на документоориентирани хранилища (например, Elasticsearch). Така от колектора се отдели хранилище.

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

В резултат на това, за относително кратък период от време, събирането на логове се разви в важна подсистема, която може да бъде наречена един от подразделите в Big Data.

Логовете в Kubernetes (и не само) днес: очаквания и реалност
Ако в миналото обикновените print-и бяха достатъчни за 'системата за логиране', то сега ситуацията се е променила значително.

Kubernetes и логовете

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

Поглеждайки напред, мога да констатирам, че в момента, за съжаление, няма стандартизиран вариант на логиране за Kubernetes, който да се различава значително от всички останали. Най-популярните схеми в общността са сведени до следното:

  • някой разгърна стека EFK (Elasticsearch, Fluentd, Kibana);
  • някой - пробва наскоро издаден Loki или използва Logging operator;
  • нас (а може би и не само нас?...) в голяма степен ни удовлетворява собствената разработка - loghouse

Обикновено използваме такива комбинации в K8s-кластери (за self-hosted решения):

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

Практика с логовете в K8s

Логовете в Kubernetes (и не само) днес: очаквания и реалност

„Ежедневни логове“, колко много сте вие?..

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

Нека опитаме ClickHouse

Нека разгледаме централизирано хранилище за проект с приложение, което генерира доста активно логове: повече от 5000 реда в секунда. Ще започнем работа с неговите логове, като ги съхраним в ClickHouse.

Както скоро ще стане необходимо максимално реално време, 4-ядрен сървър с ClickHouse вече ще бъде претоварен по дисковата подсистема:

Логовете в Kubernetes (и не само) днес: очаквания и реалност

Този тип натоварване е свързан с факта, че се опитваме да записваме възможно най-бързо в ClickHouse. И базата данни реагира с повишено дисково натоварване, поради което може да обработва следните грешки:

DB::Exception: Твърде много части (300). Обединяванията се обработват значително по-бавно от вмъкванията.

Работата е там, че MergeTree-таблици в ClickHouse (в които лежат данните от логовете) имат своите специфики при операциите по запис. Вмъкваните в тях данни генерират временна партиция, която след това се обединява с основната таблица. В резултат на това, записът се оказва много взискателен към диска, а също така се прилага ограничение, за което получихме уведомление по-горе: в 1 секунда могат да се обединяват не повече от 300 субпартиции (фактически това са 300 вмъквания в секунда).

За да избегнем подобно поведение, трябва да записваме в ClickHouse колкото е възможно по-големи парчета и не по-често от 1 път на 2 секунди. Въпреки това записът на големи пакети предполага, че трябва да записваме по-рядко в ClickHouse. Това, от своя страна, може да доведе до преливане на буфера и загуба на логове. Решението е да увеличим буфера на Fluentd, но тогава ще се увеличи и потреблението на памет.

Забележка: Друга проблемна страна на нашето решение с ClickHouse беше свързана с факта, че партиционирането в нашия случай (loghouse) е реализирано чрез външни таблици, свързани с Merge-таблица.. Това води до това, че при извличане на големи времеви интервали е необходимо прекомерно количество оперативна памет, тъй като метатаблицата обхожда всички партиции — включително и тези, които очевидно не съдържат нужните данни. Все пак, сега такъв подход може смело да се обяви за остарял за актуалните версии на ClickHouse (c 18.16).

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

А какво ще кажем за Elasticsearch?

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

Логовете в Kubernetes (и не само) днес: очаквания и реалност

Elasticsearch успя да обработи потока данни, но записването на подобни обеми в него значително натоварва CPU. Това може да се реши чрез организиране на клъстер. Чисто технически това не е проблем, но ще видим, че само за работата на системата за събиране на логове вече използваме около 8 ядра и имаме допълнителен компонент с високо натоварване в системата…

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

Тогава възниква закономерният въпрос:

Кои логове наистина са необходими?

Логовете в Kubernetes (и не само) днес: очаквания и реалност Нека опитаме да променим самия подход: логовете трябва едновременно да бъдат информативни и да не обхващат всички събития в системата.

Да предположим, че имаме процъфтяващ интернет магазин. Какви логове са важни? Събирането на максимално количество информация, например, от платежния шлюз — отлична идея. А логовете от услугата за обработка на изображения в каталога на продуктите не са критични: достатъчни са само грешките и разширения мониторинг (например, процентът на 500-ти грешки, които генерира този компонент).

Ето ни и стигнахме до заключението, че централизираното логване не е оправдано в много случаи. Много често клиентът иска да събере всички логове на едно място, въпреки че всъщност от всички логове са необходими само 5% съобщения, които са критични за бизнеса:

  • Понякога е достатъчно да настроим, например, само размера на лог файла на контейнера и средството за събиране на грешки (като Sentry).
  • За разследването на инциденти често може да е достатъчно известие за грешка и самият обширен локален лог.
  • Имахме проекти, които се справяха единствено с функционални тестове и системи за събиране на грешки. На разработчиците не им бяха нужни логовете, те виждаха всичко от трейсовете на грешките.

Илюстрация от живота

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

Нужно беше да "сдружим" системата за централизирано събиране на логове с корпоративния сензор за откриване на проблеми — QRadar. Тази система може да приема логове по протокола syslog, да ги взима от FTP. Въпреки това, интеграцията с плъгина remote_syslog за fluentd не се получи веднага. (както се оказа, не сме единствени). Проблемите с настройката на QRadar се оказаха на страната на екипа за сигурност на клиента.

В резултат, част от логовете, критични за бизнеса, се изпращаха на FTP QRadar, а друга част — се пренасочваха чрез remote syslog директно от възлите. За целта дори написахме опростен chart — възможно е да помогне на някой да реши подобен проблем… Чрез получената схема клиентът получаваше и анализираше критичните логове (с помощта на любимия си инструмент), а ние успяхме да намалим разходите за системата за логиране, запазвайки само последния месец.

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

Критерии за логовете

Подобни примери водят до заключението, че освен избора на система за събиране на логове, трябва да се проектират и самите логове! Какви са изискванията тук?

  • Логовете трябва да бъдат в машинно четим формат (например, JSON).
  • Логовете трябва да бъдат компактни и с възможност за промяна на степента на логиране, за да може да се отстранят възможни проблеми. В производствени среди трябва да се стартират системи с ниво на логиране като Предупреждение или Грешка.
  • Логовете трябва да са нормализирани, тоест в обекта на логовете всички редове трябва да имат един и същ тип поле.

Неструктурираните логове могат да доведат до проблеми с зареждането на логовете в хранилището и до пълна спиране на тяхната обработка. За илюстрация — пример с грешка 400, с която много хора сигурно са се сблъсквали в логовете на fluentd:

2019-10-29 13:10:43 +0000 [warn]: dump an error event: error_class=Fluent::Plugin::ElasticsearchErrorHandler::ElasticsearchError error="400 - Rejected by Elasticsearch"

Грешката означава, че изпращате в индекс с готов mapping поле, типът на което е нестабилен. Най-простият пример — поле в лог на nginx с променлива $upstream_status. В него може да има както число, така и низ. Например:

{ "ip": "1.2.3.4", "http_user": "-", "request_id": "17ee8a579e833b5ab9843a0aca10b941", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staffs/265.png", "protocol": "HTTP/1.1", "status": "200", "body_size": "906", "referrer": "https://example.com/staff", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.001", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "127.0.0.1:9000", "upstream_status": "200", "upstream_response_length": "906", "location": "staff"}
{ "ip": "1.2.3.4", "http_user": "-", "request_id": "47fe42807f2a7d8d5467511d7d553a1b", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staff", "protocol": "HTTP/1.1", "status": "200", "body_size": "2984", "referrer": "-", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.010", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "10.100.0.10:9000, 10.100.0.11:9000", "upstream_status": "404, 200", "upstream_response_length": "0, 2984", "location": "staff"}

В логовете се вижда, че сървър 10.100.0.10 е отговорил с грешка 404 и заявката е отишла към друго хранилище на съдържание. В резултат, в логовете стойността стана такава:

"upstream_response_time": "0.001, 0.007"

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

А какво с надеждността?

Има случаи, когато е жизненоважно да имате всички логове без изключение. И с това, типичните схеми за събиране на логове за K8s, предложени/разглеждани по-горе, имат проблеми.

Например, fluentd не може да събира логове от краткоживеещи контейнери. В един от нашите проекти контейнерът с миграцията на бази данни е живял по-малко от 4 секунди, а след това е бил изтрит — съгласно съответната анотация:

"helm.sh/hook-delete-policy": hook-succeeded

Поради това логът за изпълнението на миграцията не е попадал в хранилището. Помощ в този случай може да предостави политиката before-hook-creation.

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

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

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

Изводи

В тази статия не разглеждаме SaaS решения като Datadog. Много от описаните тук проблеми вече са разрешени от търговски компании, специализирани в събирането на логове, но не всички могат да използват SaaS по различни причини. (основните - това е цената и спазването на 152-ФЗ).

Централизираното събиране на логове първоначално изглежда като проста задача, но всъщност не е така. Важно е да се помни, че:

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

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

P.S.

Прочетете също в нашия блог:

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

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster