Вступ
У моєму попередньому пості я описав налаштування двовузлового кластера Kubernetes за допомогою Raspberry Pi. Я розповів, як я налаштував кластер, як відкрив його для Інтернету та як налаштував зберігання даних. У цьому пості я продовжу розгляд решти налаштувань, а саме моніторинг та оповіщення, сервіси та стратегію резервного копіювання і відновлення після катастроф.
Як і раніше, повну конфігурацію можна знайти в моєму репозиторії GitHub.
Якщо ви пропустили першу частину, настійно рекомендую ознайомитись із нею спершу тут!
Зміст
У попередньому пості…
- Налаштування кластера
- Ingress
- Зберігання даних
У цьому пості:
-
Моніторинг та оповіщення
-
Сервіси
-
Резервне копіювання та відновлення після катастроф
Моніторинг та оповіщення
Моніторинг був однією з перших речей, які я хотів налаштувати. Я хотів мати можливість бачити стан кластера та сервісів, які на ньому працюють. Крім того, я хотів отримувати сповіщення, якщо щось йде не так, щоб я міг швидко зреагувати і виправити проблему.
Я вважаю, що моніторинг також є важливим для безпеки. Він дозволяє отримати загальний огляд того, що відбувається, помітити, якщо хтось часто викликає ваші сервіси, знати, якщо хтось намагається здійснити brute force (атаку на підбір паролів) на ваші сервіси і так далі.
Я обрав просте рішення з Grafana + Prometheus + Loki для відображення дашбордів, збору метрик і логів відповідно. Мені дуже подобається програмне забезпечення Grafana, тому я вибрав його, щоб глибше зануритись у його можливості.
Prometheus
Основне налаштування досить просте. Спочатку я використав helm chart для Prometheus з кількома простими конфігураціями, які можна знайти в репозиторії. Я налаштував час збереження даних та розмір, а також клас зберігання та розмір. Я також додав ресурс ingress, щоб відкрити доступ до Prometheus в Інтернеті за допомогою базової аутентифікації, якщо я захочу перевірити щось, коли мене не буде вдома. Я почуваюся досить безпечно з такою конфігурацією, оскільки я також налаштував сповіщення на випадок, якщо хтось спробує здійснити brute force атаку на пароль кластера.
Prometheus буде відповідати за збір метрик з моїх сервісів та дозволяти Grafana виконувати запити для побудови красивих графіків.
Grafana
Я також налаштував Grafana за допомогою офіційного helm chart. Конфігурація для Grafana дещо складніша. Спочатку я створив базу даних PostgreSQL, щоб зберігати дані. Я створив простий деплоймент PostgreSQL, який буде слугувати сховищем для всіх сервісів, які потребують бази даних. Потім потрібно було налаштувати кілька інших параметрів. Наприклад, я налаштував пароль за допомогою секрету, який потрібно змонтувати як файл, щоб конфігурація Grafana могла його прочитати. Я також налаштував джерело даних для Prometheus і облікові дані для бази даних для Grafana. Загалом, налаштування залишено мінімалістичним. І, звісно, я відкрив доступ до Grafana в Інтернеті, щоб я міг перевіряти свої красиві дашборди з будь-якої точки світу.
Grafana відповідає за виконання запитів до різних джерел даних (Prometheus і Loki в цьому випадку) і створення дашбордів з їхніх даних, а також надсилання сповіщень, коли щось йде не так. Я налаштував її так, щоб вона надсилала сповіщення в групу Telegram за допомогою бота, щоб я міг отримувати сповіщення на будь-якому пристрої та швидко реагувати на них.
Loki та Promtail
Конфігурація Loki є найбільш складною з трьох, оскільки я робив її з нуля.
Почнемо з основного елемента — Promtail. Promtail відповідає за читання логів усіх подів у кластері та їх надсилання в Loki. На кожному вузлі кластера має бути екземпляр Promtail. Для цього я використав DaemonSet, щоб забезпечити наявність екземпляра на кожному вузлі. Promtail має доступ до папок, де зберігаються логи, а також має файл, в якому відслідковує логи, які він прочитав в кожному поді. Він налаштований на відправку зібраних логів в шлюз відправки даних Loki (Loki push gateway), який ми налаштуємо пізніше.
Promtail налаштований з двома різними конфігураціями.
Перше завдання, яке називається pod-logs
, є основним. Воно знаходить усі поди на вузлі за допомогою Kubernetes API та збирає їхні логи. Також додаються деякі мітки, такі як namespace та ім’я поду.
Друге завдання, яке називається ingress-nginx-metrics
, є складнішим. Воно перевіряє лише логи з namespace ingress-nginx
, і не надсилає їх до Loki, оскільки це вже робить інше завдання. Замість цього, воно парсить логи, які мають формат JSON, завдяки нашій конфігурації ingress-nginx
, описаній у першій частині цього блогу. Потім, воно експонує метрики за допомогою дії Promtail metrics.
Я створив різні метрики, які експонують різну інформацію. Наприклад, http_requests_total_ip
має мітку для IP-адреси клієнта, а http_requests_total_ip_namespace_status
має мітки для IP-адреси клієнта, namespace ingress та статус-коду. Завдяки різним метрикам, які експонують різні набори міток, я можу мінімізувати кардинальність для кожної метрики, що дозволяє отримувати точніші запити пізніше.
Наприклад, якщо IP-адреса клієнта викликала ingress Grafana один раз, а ingress Prometheus — інший раз, то запит, який показує збільшення кількості запитів на IP-клієнта за допомогою метрики http_requests_total_ip_namespace_status
, дасть 2 різні серії з нульовим збільшенням, що в сумі дасть 0. Якщо ж я використаю метрику з лише міткою IP-клієнта, як-от http_requests_total_ip
, запит покаже збільшення на 1, оскільки зміна з відсутності даних на 1 буде врахована як 0.
Хоча ця неточність не є критично важливою для більших налаштувань з більшою кількістю запитів, вона дозволяє мені не пропустити клієнтів, які викликають мої сервіси дуже рідко.
Ще одна річ, яку я згадував у першому пості, — це геолокація IP-адрес. Для цього я використав етап geoip парсингу в Promtail, який використовує базу даних MaxMind для додавання геолокаційних даних до метрик. Таким чином, я можу створювати візуалізації на карті в Grafana. Для налаштування я просто використав Docker-контейнер geoipupdate
від MaxMind у DaemonSet деплойменті, який щодня оновлював базу даних на кожному вузлі, а потім я поділився цією папкою з Promtail. Точну конфігурацію можна знайти в репозиторії.
Після налаштування Promtail, я перейшов до налаштування Loki. Його конфігурація була досить простою — я створив деплоймент, який у цьому випадку прив’язав до конкретного вузла, і використав локальні персистентні томи. Це також могло б працювати з Longhorn, але в цей раз я вибрав саме таку конфігурацію, оскільки не турбувався про резервне копіювання цих даних і хотів залишити все простим.
Конфігурація та дашборди
На цьому етапі ми налаштували Loki, Prometheus та Grafana. Залишилось лише додати джерело даних Loki до Grafana і визначити, які сервіси слід збирати з Prometheus.
Конфігурація збору даних Prometheus дуже проста. За замовчуванням Prometheus має конфігурований pipeline для збору метрик на основі анотацій подів. Для того, щоб збирати метрики з поду, достатньо додати кілька анотацій:
prometheus.io/scrape: "true"
: Це вказує Prometheus, що потрібно збирати метрики з цього поду.prometheus.io/port: "8080"
: Це вказує Prometheus, який порт збирати. Ця анотація потрібна лише, коли под має кілька портів, інакше Prometheus збирає з першого порту.prometheus.io/path: "/metrics"
: Це вказує Prometheus, з якого шляху збирати метрики. Потрібно лише, коли у пода є шлях до метрик, відмінний від/metrics
.
Після налаштування правильних анотацій Prometheus автоматично виявить поди та почне збір метрик.
Далі залишиться лише створити дашборди в Grafana.
Наприклад, використовуючи кастомні метрики Promtail, я створив дашборд для відображення збільшення викликів за IP-адресами клієнтів, використовуючи цей запит:
sum by(client_ip) (
increase(promtail_custom_http_requests_total_ip{}[1h]) > 0
or
last_over_time(promtail_custom_http_requests_total_ip{}[5m])
) > 0
Дашборд HTTP-запитів Grafana
Я додав функцію last_over_time
до запиту, щоб показувати IP-адреси клієнтів, які викликали сервіс лише один раз, оскільки в іншому випадку вони з’являлись на графіку як 0 в єдиній точці. Таким чином, вони мають свою реальну величину і з’являються на графіку протягом 5 хвилин після виклику.
Коли ви налаштуєте всі необхідні дашборди, потрібно додати деякі сповіщення. Наприклад, у мене є сповіщення, коли певна IP-адреса часто викликає мої сервіси, коли под перезапускається занадто часто, коли є багато заборонених запитів (заблокованих WAF), …
Ви також можете створювати дашборди з джерел даних Loki. Наприклад, я створив дашборд для отримання інформації про запити, заблоковані WAF. Для цього ви можете парсити лінії логів ingress-nginx
, які містять ModSecurity:
, використовуючи цей запит:
{namespace="ingress-nginx"} |= `ModSecurity:` | pattern ` [] <_> <_> ModSecurity: [file "/etc/nginx/owasp-modsecurity-crs/rules/.conf<_> [id ""] [msg ""<_>[ver ""]<_>, client: , server: , request: " "<_>` | __error__=``
Ви також можете просто здійснити пошук за запитом {namespace="ingress-nginx"} |=
ModSecurity:`` для перегляду сирих логів і легше виявити, яка саме правило спрацювало, щоб ви могли легко додати його до білого списку, якщо це хибне спрацьовування.
Таблиця з розпарсеними логами ModSecurity
Ось і все для моєї налаштування моніторингу. Сподіваюся, що вам було цікаво. Якщо у вас є питання чи пропозиції — не соромтеся звертатися!
Послуги
Я використовую кілька послуг з різних причин. Ось їхній короткий список:
- ArgoCD: для деплою моїх сервісів за допомогою GitOps.
- Bunetz: мій особистий блог.
- Cert-manager: для випуску та поновлення SSL сертифікатів за допомогою Let’s Encrypt.
- Grafana: для моніторингу та сповіщень.
- Ingress-nginx: для виставлення сервісів в інтернет.
- Longhorn: для розподіленого сховища.
- Prometheus: для збору метрик.
- Sealed Secrets: для зберігання секретів в Git.
- Homebridge: для управління моїми пристроями розумного дому, які не сумісні з HomeKit.
- Immich: для збереження та резервного копіювання моїх фотографій. Дуже гарна заміна Google Photos.
- Loki: для збору логів.
- Myst node: для запуску вузла Myst і заробітку пасивного доходу за рахунок обміну моїм інтернет-з’єднанням.
- Pg-backup: кастомний сервіс, який я створив для резервного копіювання моїх PostgreSQL баз даних.
- Pi-metrics: кастомний сервіс, який я створив для отримання даних температури з моїх Raspberry Pi та експортую їх як метрики.
- Pi-hole: для блокування реклами та трекінгу в моїй мережі.
- Postgres: база даних для всіх сервісів, які потребують бази даних (grafana, immich, …).
- VaultWarden: реалізація відкритого Bitwarden.
- Whoami: простий сервіс, який повертає IP-адресу клієнта, який викликає його.
- Public-ip-server: простий сервіс, який визначає публічну IP-адресу мого Raspberry Pi і повертає її, щоб я міг використовувати її в автоматизаціях для SSH доступу до мого Raspberry Pi з будь-якого місця.
Я додав прості конфігурації для кожного з цих сервісів в репозиторії, тому ви можете використовувати їх як орієнтир, якщо хочете налаштувати будь-який з цих сервісів.
Єдиний сервіс, який трохи відрізняється, тому що має конфігурацію, яка знаходиться поза Kubernetes, — це Immich.
Щоб зберігати свої дані, я використовую локальний постійний том, але я не хотів просто зберігати дані в незашифрованому вигляді, оскільки, якщо хтось вкраде мій Raspberry Pi, вони зможуть отримати доступ до всіх моїх фотографій. Тому я створив зашифровану папку на своєму Raspberry Pi за допомогою fscrypt
. Це гарантує, що всі мої фотографії зберігаються на жорсткому диску в зашифрованому вигляді, і єдиний спосіб отримати до них доступ — це увійти в систему на моєму Raspberry Pi і змонтувати зашифровану папку. Я також додав до команди запуску Immich перевірку, чи змонтована зашифрована папка, і якщо вона не змонтована, сервіс не буде запущений. Таким чином, я гарантую, що Immich знайде порожню папку і припустить, що дані відсутні. Ця папка також резервується, але я поясню стратегію резервного копіювання в наступному розділі про резервне копіювання та відновлення після збоїв.
Резервне копіювання та відновлення після збоїв
Резервне копіювання та відновлення після збоїв є важливими для будь-якої системи. Мені довелося навчитися на власному досвіді, що важливо резервувати в цій системі, коли одна з моїх SD-карт постійно пошкоджувалась, і я змушений був кілька разів скидувати кластер з нуля. На щастя, кластер ледь працював, і я зміг відновити деякі важливі дані, які я не резервував. Спочатку я поясню просту стратегію резервного копіювання моїх даних, а потім розповім про стратегію відновлення після збоїв, яка включає резервне копіювання інших, не таких очевидних, даних.
Резервне копіювання
Є кілька речей, які важливо резервувати. По-перше, найочевидніше: бази даних. Я створив простий сервіс під назвою pg-backup, який є простим сервісом для резервного копіювання PostgreSQL. Він використовує команду pg_dump
для дампу однієї або кількох баз даних у файли з мітками часу та видаляє старі файли. Також він надає метрики для налаштування сповіщень у разі невдачі резервного копіювання. Цей сервіс також використовує том Longhorn для зберігання даних.
Логічно, найкритичніша частина для резервного копіювання — це томи Longhorn. На щастя, Longhorn вже пропонує рішення для резервного копіювання, тому я налаштував резервне копіювання на Azure Blob Storage. Після цього просто потрібно використовувати графічний інтерфейс для вибору томів, які потрібно резервувати, і які не потрібно.
Нарешті, останнє, що я резервую, — це мої фотографії Immich. Для цього я також використовую Azure Blob Storage, але оскільки це не том Longhorn, я налаштував cronjob у Kubernetes, який використовує rclone для синхронізації всієї папки з шифруванням, змонтувавши конфігураційний файл, збережений у секреті.
Відновлення після збоїв
Спочатку я вважав, що просто резервування моїх даних буде достатньо, і я зможу відновити все після будь-якої катастрофи. Це зовсім не так, адже, наприклад, що я можу зробити з томом моєї бази даних Vaultwarden, якщо не маю пароля для неї? Або як відновити мої фотографії, якщо пароль для шифрування знаходиться в Sealed Secret? Тому дуже важливо також резервувати всі секрети та паролі. Крім того, якщо ви хочете полегшити собі життя, слід резервувати ключі шифрування для контролера sealed-secrets. Так ви не будете змушені повторно шифрувати все. Для цих двох цілей я надаю два прості скрипти: один, який допоможе розшифрувати всі секрети, щоб їх можна було зберігати в безпечному місці, і ще один для резервного копіювання ключів шифрування, щоб ви могли відновити їх у разі катастрофи.
Ще одна хороша річ для резервного копіювання — це конфігурація ArgoCD. Я справді не хочу додавати всі мої сервіси заново в Argo, тому я створив скрипт, який виводить конфігурацію ArgoCD у файл, який можна імпортувати пізніше. Я додав цей скрипт також, але це дуже проста команда, яку можна знайти в документації ArgoCD.
Коли все це буде забекаплено, відновити кластер, якщо у вас є порожній кластер, досить просто. По-перше, потрібно встановити ArgoCD, а потім відновити його конфігурацію. Далі треба відновити ключі sealed-secrets за допомогою команди kubectl apply -f main.key
, а потім синхронізувати Sealed Secrets, щоб вони ініціалізувалися з старими ключами. Потім потрібно синхронізувати Longhorn і почати відновлення резервних копій.
Як тільки резервні копії будуть відновлені, і Sealed-secrets отримає ключі для розшифровки секретів, потрібно буде лише синхронізувати сервіси ArgoCD, і ви знову зможете працювати!
Висновок
Дякую за те, що прочитали цей пост і дали будь-які відгуки! Я сподіваюся, що цей пост буде корисний для тих, хто намагається пройти схожий шлях, як я. Будь ласка, не соромтеся повідомити мені про будь-які проблеми з моїм налаштуванням, пропозиції або запитання. Ви можете зв'язатися зі мною через Reddit або будь-яку іншу платформу, яку я вказав у розділі про мене на моєму вебсайті.
Дякую за те, що слідкуєте за мною! 🚀
Цей пост є копією оригіналу, який опублікований на моєму блозі.
Незабаром я напишу наступну частину, в якій поясню, як я автоматизував налаштування вузлів за допомогою Ansible.
Перекладено з: How I over-engineered my Home Kubernetes Cluster: part 2