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

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

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

Однак для початку обмовлюся, що різні замовники під збиранням логів розуміють дуже різне:

  • хтось хоче бачити security-і audit-логи;
  • хтось централізоване логування всієї інфраструктури;
  • а комусь достатньо збирати лише логи програми, виключивши, наприклад, балансувальники.

Про те, як ми реалізовували різні «хотілки» і з якими труднощами зіткнулися, — під катом.

Теорія: про інструменти для лігів

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

Логування пройшло довгий шлях, в результаті якого виробилися методології збирання та аналізу логів, що ми й застосовуємо сьогодні. Ще в 1950-х роках у Fortran з'явився аналог стандартних потоків введення-виводу, які допомагали програмісту у налагодженні його програми. Це були перші комп'ютерні логи, які полегшували життя програмістам тих часів. На сьогодні ми в них бачимо перший компонент системи логування. джерело або "виробник" (producer) логів.

Комп'ютерна наука не стояла дома: з'явилися комп'ютерні мережі, перші кластери… Почали працювати складні системи, які з кількох комп'ютерів. Тепер системні адміністратори змушені були збирати логи з кількох машин, а в особливих випадках могли додавати повідомлення ядра ОС на випадок, якщо потрібно розслідувати системний збій. Щоб описати системи централізованого збирання логів, на початку 2000-х виходить RFC 3164який стандартизував remote_syslog. Так виник ще один важливий компонент: колектор (складальник) логів та їх сховище.

Зі збільшенням обсягу логів та повсюдним впровадженням веб-технологій постало питання про те, що логи потрібно зручно показати користувачам. На зміну простим консольним інструментам (awk/sed/grep) прийшли більш просунуті переглядачі логів - Третій компонент.

У зв'язку зі збільшенням обсягу ліг стало ясно й інше: логи потрібні, але не всі. А ще різні логи вимагають різного рівня безпеки: одні можна втратити через день, а інші треба зберігати 5 років. Так до системи логування додався компонент фільтрації та маршрутизації потоків даних - назвемо його фільтром.

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

Зрештою, саме поняття лога розширилося до абстрактного потоку подій, які ми хочемо зберігати для історії. А точніше — на той випадок, коли потрібно буде провести розслідування або скласти аналітичний звіт…

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

Логи в Kubernetes (і не тільки) сьогодні: очікування та реальність
Якщо колись звичайних print'ів могло бути достатньо «системи логування», то тепер ситуація сильно змінилася.

Kubernetes та логи

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

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

  • хтось розгортає стек EFK (Elasticsearch, Fluentd, Kibana);
  • хтось - пробує нещодавно випущений Локі або використовує Logging operator;
  • нас (а можливо, і не лише нас?..) багато в чому влаштовує власна технологія loghouse...

Як правило, ми використовуємо такі зв'язки в K8s-кластерах (для self-hosted-рішень):

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

Практика з логами у K8s

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

«Повсякденні логи», скільки ж вас?

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

Спробуємо ClickHouse

Давайте розглянемо централізоване сховище на проекті із додатком, який досить активно генерує логи: понад 5000 рядків на секунду. Почнемо роботу з його логами, складаючи їх у ClickHouse.

Як тільки буде потрібний максимальний realtime, 4-ядерний сервер з ClickHouse вже буде перевантажений по дисковій підсистемі:

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

Подібний тип завантаження пов'язаний з тим, що ми намагаємося максимально швидко писати ClickHouse. І на це БД реагує підвищеним дисковим навантаженням, через що може видавати такі помилки:

DB::Exception: Too many parts (300). Merges are processing significantly slower than inserts

Справа в тому, що MergeTree-таблиці У ClickHouse (в них лежать дані логів) мають свої складнощі під час операцій запису. Дані, що вставляються в них, генерують тимчасову партицію, яка потім зливається з основною таблицею. В результаті запис виходить дуже вимогливим до диска, а також на нього поширюється обмеження, повідомлення про яке ми і отримали вище: в 1 секунду можуть зливатися не більше 300 субпартицій (фактично це 300 insert'ів в секунду).

Щоб уникнути подібної поведінки, слід писати в 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).
  • Логи мають бути компактними та з можливістю зміни ступеня логування, щоб налагодити можливі проблеми. При цьому в production-оточеннях слід запускати системи з рівнем логування на кшталт попередження або помилка.
  • Логи повинні бути нормалізованими, тобто в об'єкті лога усі рядки повинні мати однаковий тип поля.

Неструктуровані логи можуть призвести до проблем із завантаженням логів у сховище та повною зупинкою їх обробки. Як ілюстрація - приклад з помилкою 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-ФЗ).

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

  • Логувати докладно варто лише критичні компоненти, а інших систем можна налаштувати моніторинг і збирання помилок.
  • Логи у production варто робити мінімальними, щоб не давати зайвого навантаження.
  • Логи повинні бути машиночитаними, нормалізованими, мати строгий формат.
  • Справді критичні логи варто відправляти окремим потоком, який має бути відокремлений від основних.
  • Варто продумати акумулятор логів, який може врятувати від сплесків високого навантаження і зробить навантаження більш рівномірним.

Логи в Kubernetes (і не тільки) сьогодні: очікування та реальність
Ці прості правила, якщо їх застосовувати скрізь, дозволили б працювати і описаним вище схемам - навіть незважаючи на те, що в них не вистачає важливих компонентів (акумулятора). Якщо ж не дотримуватись таких принципів, завдання з легкістю приведе вас та інфраструктуру до ще одного високонавантаженого (і водночас малоефективного) компонента системи.

PS

Читайте також у нашому блозі:

Джерело: habr.com

Додати коментар або відгук