Підготовка до зими 2022-2023: інтернет, електрика, опалення, їжа та вода
0 (0)

10 Січня 2023

Вже давно просили написати пост про те, як я готувався до зими – ось, таки вмовили.

Хоча вже трошки запізно, бо половина зими пройдено, але – нехай буде.

Голове, що дуже спасає цією зимою це те, що ЖК, в якому живу, по-перше має газові плити, по-друге – опалення газовими котлами.

Однак, все ж були проблеми, которі довелося вирішувати.

В перші дні, коли почали вимикати світло, я мав пару павербанків на 10.000 mAh, і досі пам’ятаю це відчуття, коли в ноутбуку сіла батарея, провайдер інтернету вирубився, мобільний інтернет теж не працював, ще й телефон почав сідати і не було можливості навіть почитати книгу (хоча є ще паперова бібліотека – але чим світити ввечері?)

Повне відчуття ізоляції, як в печері.

Тож перше, що я зробив – це докупив пару павербанок на 20.000 для телефона. Але це було тільки початком.

Інтернет

Друге питання, яке потрібно було вирішити – це інтернет. Живу за містом, і як вимикається світло – то вежі мобільного вирубаються теж, лишається тільки одна. З телефону інтернет через неї не тягнуло зовсім, тож докупив собі комплект зовнішньої антени з 3/4G роутером. Див детальнішне тут – Networking: коли немає світла – модем 4G ZTE + зовнішня антена:

Антена стоїть за вікном, в квартирі мав два модеми з двома операторами – Водафон та Лайф: то один, то другий працювали більш-менш, і свої 1-2 мб/с я мав.

Обидва модеми були підключені до павербанок, тож працювали постійно: як тільки виключалось світло, і провайдер “падав” – ноутбук переключався на один з модемів.

Ще пізніше в ЖК нарешті дотягнули оптику, і тепер маю GPON з гігабітним підключенням:

Медіаконвертор підключений до павербанки через підвищуючий перехідник на 12 вольт:

Так як це GPON (Gigabit Passive Optical Network) – то ми не дуже залежимо від електрохарчування (с), і інтернет працює стабільно навіть декілька діб без світла поспіль.

Електрохарчування (с)

Заряд ноубтука

Ще до того, як я затарився купою всяких акумуляторів (зараз до них дойдемо) я максимально оптимізував роботу свого ноутбука – визначив, які саме процеси/сервіси найбільше витрачають заряд батареї, та почав іх відключати, див. Linux: збереження заряду батареї ноутбуку.

Щоб спростити процесс – накидав скрипта:

#!/usr/bin/bash

#sudo ifconfig enp2s0f0 down
sudo nmcli radio wifi off
sudo bash -c  'echo -n 60 > /sys/class/backlight/amdgpu_bl0/brightness'
#xrandr --output eDP-1 --auto --mode 1280x720 --output HDMI-1 --auto --left-of eDP-1 --output DP-1 --off
##xrandr --output eDP-1 --auto --mode 1280x720 --primary --output HDMI-1 --off --output DP-1 --off
#systemctl --user stop pulseaudio.socket
#systemctl --user stop pulseaudio.service
sudo systemctl stop bluetooth
sudo ip link set docker0 down
sudo systemctl stop docker.service
sudo systemctl stop docker.socket
killall polybar
killall ktorrent
killall slack
killall thunderbird

Далі в rc.xml (в мене Arch Linux та Openbox) додав дві комбінації для клавіатури – одна запускає скрипт setPowerDown.sh, наведений вище, друга – setPowerUp.sh, в якому робиться все теж саме, тільки навпаки – включає сервіси:

...
    <keybind key="C-A-d">
      <action name="Execute">
        <command>/home/setevoy/.local/bin/setPowerDown.sh</command>
      </action>
    </keybind>
    <keybind key="C-A-u">
      <action name="Execute">
        <command>/home/setevoy/.local/bin/setPowerUp.sh</command>
      </action>
    </keybind>
...

Але цього все ще було замало – я хотів довести свою автономність мінімум до 3-х діб без світла, тож пішов далі.

Павербанки

По-перше – потрібно було мати павербанки, які могли б заряджати ноутбук, батареї якого вистачає на 5-6 годин максимум.

Першими були куплені два Basesus на 30.000 mAh, які видають 60 ват по Type-C (на olx.ua можна взяти дешевше).

До них докупив два зарядних Baseus GaN3 з виходом на 30 ват, щоб банки швидше заряджались.

Ще пізніше докупив банку O2 Project 60000 mAh 65 wat – теж працює чудово.

Акумулятори, інвертори

По-перше – замовив зарядну станцію. Мене душила жаба віддавати купу грошей, та ще й чекати на доставку, тож я замовив не EcoFlow, а Kseon-168 – 168.000 mAh (14.000 грн, для військових дешевше), плюс інвертор на 500 ват. 4 виходи USB (без QuickCharge, нажаль), та з двома автомобільними виходами. І вже для автомобільного конектора – докупив Baseus Particular Digital Display QC+PPS Dual Quick Charger 65W Dark, тож спокійно можу заряджати і мобільні, і ноутбук:

Єдиний, як на мене, недолік Kseon – довга зарядка, до 18 годин для повного заряду. Але пізніше купив ще один, тож маю запас.

Ще треба було вирішити питання з опаленням: газовий котел теж потребує електроенергії для свого насоса, яким він жене воду по системі.

Спеціально для нього замовив звичайні автомобільні акумулятори та інвертор:

Інвертор до 600 ват, тож котел тягне без проблем, можна підключити ще й холодильник. Обійшлося все це приблизно в 15.000 грн – не скажу, де бралося, бо цим займався знайомий прораб.

Два кумулятори по 60 ампер-годин вистачає на 5-6 годин роботи котла, але квартира та будинок нові, і тепло тримають добре.

Ще пізніше купив аналогічний набор, але вже з гелевими AGM на 72 а/г (близько 10.000 грн) та інвертором CyberPower CPS1000E (обійшовся тоді у 23.000 грн – зато привезли все на наступний день – ніяких тобі “місяць чекати”, та ще й мати справу з поштою):

Цей акум та інвертор переважно живлять настільну лампу, колонки, ноутбук, зовнішній монітор та роутер.

Тож зараз в мене запаси по енергії:

  • 3х павербанка на 10.000 mAh
  • 1х павербанк на 20.000 mAh
  • 2х павербанка Baseus на 30.000 mAh, 65 ват
  • 1х павербанк на 60.000 mAh, 65 ват
  • 2х автоакуми на 60.000 mAh + інвертор 600 ват
  • 1х автоакум на 72.000 mAh + інвертор 700 ват
  • 2х зарядні станції Kseon по 168.000 mAh + інвертор 500 ват

Зараз, думаю, можу спокійно (якщо економно) прожити без світла до тижня.

Безпека

Акумлятори стоять на балконі, де температура мінімум +7. До того ж докупив декілька Вологовбирач Pouce, бо трохи переймався високою вологістю повітря та кондесатом.

Але першим жеж ділом купив два вогнегасники ВП-3:

Один в коридорі, біля вхідних дверей, другий в шкафчику біля балкона.

Брав два, бо ніколи ними не користувався, і якщо з першого разу щось не вийде – то буде друга спроба.

Продукти харчування

Ну, тут все просто: затарився консервами від Вербена, брав на Prom.ua (хоча взагалі цю площадку дуже не люблю) – коробок 20 різноманітних каш з м’ясом, плюс “мівіни”, паштети, галети і все таке інше із супермаркету.

Знайомі купляли Консерви Tushe, прям ящиками – теж рекомендують.

Крім того, купив 1.5 кг сала – лежить в морозильнику.

Ще гарна їдея мати запас шоколаду.

Вода

В ЖК свої насоси і ми не залежимо від міського водопостачання, але тут проблема, бо насоси – сюрпрайз! – працють від електрики.

Тому маю запас 2х20 літрів технічної води, плюс 2х20 питної бутильованної:

Пізніше ЖК купив генератор, мешканці щомісяця скидаються по 200 грн на паливо, і тепер маємо воду шонайменш двічі на добу.

Також, так як вода є не завжди, купив собі ось такий рукомийник:

Ще купив звичайний чайник для плити Ofenbach Happy Kettle 2л, бо гріти воду в кастрюльці швидко набридло.

Так наче і все.

В цілому – зараз проблеми не відчуваються зовсім, хоча спочатку було трохи… Неспокійно, скажімо так, да. Але все вийшло чудово 🙂

Loading

Grafana Loki: можливості LogQL для роботи з логами та створення метрик для алертів
0 (0)

30 Грудня 2022

Добре – Loki запускати навчились – Grafana Loki: архітектура та запуск в Kubernetes з AWS S3 storage та boltdb-shipper, як налаштовувати алерти теж розібрались – Grafana Loki: алерти з Ruler та labels з логів.

Тепер час розібратися з тим, що взагалі ми можемо робити в Loki використовуючи її LogQL.

Підготовка

Далі для прикладів будемо використовувати два поди – один з nginxdemo/hello для звичайних логів nginx, а інший thorstenhans/fake-logger, який буде писати логи в JSON.

Для Nginx додамо Service, что б мати можливість слати запити з curl:

apiVersion: v1
kind: Pod
metadata:
  name: nginxdemo
  labels:
    app: nginxdemo
    logging: test
spec:
  containers:
  - name: nginxdemo
    image: nginxdemos/hello
    ports:
      - containerPort: 80
        name: nginxdemo-svc
---
apiVersion: v1
kind: Service
metadata:
  name: nginxdemo-service
  labels:
    app: nginxdemo
    logging: test
spec:
  selector:
    app: nginxdemo
  ports:
  - name: nginxdemo-svc-port
    protocol: TCP
    port: 80
    targetPort: nginxdemo-svc
---
apiVersion: v1
kind: Pod
metadata:
  name: fake-logger
  labels:
    app: fake-logger
    logging: test
spec:
  containers:
  - name: fake-logger
    image: thorstenhans/fake-logger:0.0.2

Деплоїмо та прокидуємо порт:

[simterm]

$ kk port-forward svc/nginxdemo-service 8080:80

[/simterm]

І запустимо curl зі звичайним GET в циклі:

[simterm]

$ watch -n 1 curl localhost:8080 2&>1 /dev/null &

[/simterm]

Та ще один – з POST:

[simterm]

$ watch -n 1 curl -X POST localhost:8080 2&>1 /dev/null

[/simterm]

Поїхали.

Grafana Explore: Loki – інтерфейс

Декілька слів про сам інтерфейс Grafana Explore > Loki.

Ви можете використовувати декілька запитів одночасно:

Також, є можливість розділити інтефейс на дві частини, і в кожній виконувати окремі запити:

Як і в звичайних дашбордах Grafana, є можливість вибрати період, за який ви хочете отримати дані, та задати інтервал для автооновлювання:

Або можете включити Live-режим – тоді дані будуть з’являтися як тільки вони потраплять до Loki:

Для створення запитів є два режими – Builder та Code.

В режимі Builder Loki видає список доступних тегів та фільтрів:

В режимі Code вони будуть підставлятися автоматично по мірі набору:

Функція Explain буде роз’яснювати що саме ваш запит робить:

А Inspector відобразить деталі про ваш запит – скільки часу і ресурсів було використано для формування відповіді – корисно для оптимізації запитів:

Крім того, завжди можна відкрити Loki Cheat Sheet, натиснувши (?) з правої сторони від поля для запиту:

LogQL: overview

В цілому, робота з Loki та її LogQL майже аналогічна роботі з Prometheus та його PromQL – майже всі тіж самі функції та загальний підхід, це навіть відображено в опису Loki: “Like Prometheus, but for logs”.

Отже, основна вибірка базується на проіндексованих лейблах (або тегах, кому як більше до вподоби), за допомогою яких ми робимо основний пошук в логах – вибираємо стрім.

Типи запитів в Loki залежать від фінального результату:

  • Log queries: формують строки з лог-файлів
  • Metric queries: включають в себе Log queries, але в результаті формують числові значення, які можна використовувати для формування графіків в Grafana або для алертів в Ruler

В цілому, будь-який запит складається із трьох основних частин:

{Log Stream Selectors} <Log Pipeline "Log filter">

Тобто в запиті:

{app="nginxdemo"} |= "172.17.0.1"

{app="nginxdemo"} – це Log Stream Selector, в якому ми вибираємо конкретний стрім из Loki, |= – початок Log Pipeline, який включає в себе Log Filter Expression – "172.17.0.1".

Окрім Log filter, пайплайн може включати в себе Log або Tag formatting expression, який міняє отримані в пайплайн дані.

Обов’язковим є Log Stream Selector, тоді як Log Pipeline з його expressions являється опціональним, і використовуюється для уточненя або форматування результатів.

Log queries

Log Stream Selectors

Для селекторів викристовуються лейбли, які задаються агентом, який збирає логи – promtail, fluentd або іншими.

Log Stream Selector визначає скільки індексів та блоків данних будуть завантажені для повернення результату, тобто напряму впливає на швидкість роботи і ресурси CPU/RAM, задіяні для формування відповіді.

В прикладі вище в селекторі {app="nginxdemo"} ми використовуємо оператор “=“, який може бути:

  • = : дорівнює
  • != : не дорівнює
  • =~ : regex
  • !~ : негативний regex

Отже, за запитом {app="nginxdemo"} ми отримаємо логи всіх подів, у яких є тег app зі значенням nginxdemo:

Можемо комбінувати декілька селекторів, наприклад отримати всі логи з logging=test, але без app=nginxdemo:

{logging="test", app!="nginxdemo"}

Або використати regex:

{app=~"nginx.+"}

Або просто вибрати взагалі всі логи (стріми), в яких є тег app:

Log Pipeline

Данні, отримані зі стриму можна передати в пайплайн для подальшого фільтрування або форматування. При цьому результат роботи одного пайплайну можна передати в наступний, і так далі.

Pipeline може включати в себе:

  • Log line filtering expressions – для фільтрування попередніх результатів
  • Parser expressions – для отримання тегів з логів, які можна передати в Tag filtering
  • Tag filtering expressions – для фільтрування даних по тегам
  • Log line formatting expressions – використовується для редагування отриманних результатів
  • Tag formatting expressions – редагування тегів/лейбл

Log Line filtering

Фільтри використовуються для… фільтрування)

Тобто, коли ми отримали дані із стриму, і хочемо з нього вибрати окремі строки – то використовуємо log filter.

Фільтр складається з оператора та строкового запиту, за яким робиться вибірка данних.

Операторами можуть буди:

  • |=: строка містить строковий запит
  • ! =: строка НЕ містить строковий запит
  • |~: строка дорівнює регулярному виразу
  • ! ~: строка НЕ дорівнює регулярному виразу

При використанні regex майте на увазі, что використовується синтаксис Golang RE2, і за замочуванням він є case-sensitive. Щоб переключити його на незалежний від регистру режим – додаємо (i?).

Окрім того, Log Line filtering краще використовувати на початку запиту, бо вони працють швидко, і позбавлять наступні пайплайни він зайвої роботи.

Прикладом log filter може бути вибірка за строкою:

{job=~".+"} |= "promtail"

Або декілька виразів, використовуючи регулярку:

Parser expressions

Парсери… парсять) (гвинтокрили гвинтять) вхідні дані, та отримують з них лейбли, які потім можна використати в подальших фільтрах або для формування Metric queries.

Наразі, LogQL підтримує json, logfmt, pattern, regexp та unpack для роботи з тегами.

json

Наприклад, json формує всі json-ключі в лейбли, тобто запит {app="fake-logger"} | json замість:

Сформує новий набір тегів:

Отримані через json теги можна далі використати для додаткових фільтрів, наприклад – вибрати тільки строки з level=debug:

logfmt

Для формування тегів з логів не в форматі JSON можно використати logfmt, який всі знайдені поля перетворить на лейбли.

Наприклад, job="monitoring/loki-read" має поля ключ=значення:

level=info ts=2022-12-28T14:31:11.645759285Z caller=metrics.go:170 component=frontend org_id=fake latency=fast

Які за допомогою logfmt перетворяться на лейбли:

regexp

Парсер regex приймає аргумент, в якому вказується regex-група, яка сформує тег із запиту.

Наприклад, зі строки:

10.0.44.12 – – [28/Dec/2022:14:42:58 +0000] 204 “POST /loki/api/v1/push HTTP/1.1” 0 “-” “promtail/” “-“

Ми можемо динамічно сформувати теги ip та status_code:

{container="nginx"} | regexp "^(?P<ip>[0-9]{1,3}.{3}[0-9]{1,3}).*(?P<status_code>[0-9]{3})"

pattern

pattern дозволяє сформувати лейбли за шаблонами лог-запису, тобто строка:

10.0.7.188 – – [28/Dec/2022:15:27:04 +0000] 204 “POST /loki/api/v1/push HTTP/1.1” 0 “-” “promtail/2.7.0” “-“

Може бути описана у вигляді:

{container="nginx"} | pattern `<ip> - - [<date>] <status_code> "<request> <uri> <_>" <size> "<_>" "<agent>" <_>`

де <_> ігнорить, тобто не створює тегу.

І в результаті отримаємо набір лейбл за цим шаблоном:

Див. більше тут – Introducing the pattern parser.

Tag filtering expressions

Як видно з назви, дозволяє створювати нові фільтри з тегів які вже є в запису, або які були створені за допомогою попереднього парсеру, наприклад logfmt.

Візьмемо строку:

level=info ts=2022-12-28T15:56:31.449162304Z caller=table_manager.go:252 msg=”query readiness setup completed” duration=1.965µs distinct_users_len=0

Якщо пропустимо її через парсер logrmt, то отримаємо теги caller, msg, durarion та distinct_users_len:

Далі, можемо створити фільтр за цими тегами:

Доступні оператори тут ==, =, !=, >, >=, <, <=.

Також, можемо використати оператори and або or:

{job="monitoring/loki-read"} | logfmt | caller="table_manager.go:252" or org_id="fake" and caller!~"metrics.go.+"

Log line formatting expressions

Далі, можемо формувати те, які саме дані нам будуть відображені в записі.

Наприклад, візьмемо той же loki-read, в якому маємо теги:

Серед них нам цікаво відобразити тільки component та duration – використовуємо форматування:

{job="monitoring/loki-read"} | logfmt | line_format "{{.component}} {{.duration}}"

Label format expressions

За допомогою label_format можемо перейменувати, змінити чи додати нові лейбли.

Для цього, аргументом передаємо ім’я лейбли з оператором =, за яким йде потрібне значення.

Наприклад, маємо лейблу app:

Яку хочемо перейменувати в application – використовуємо label_format application=app:

Або можемо використати значення існуючого тегу для створення нового, для цього використовуємо шаблонізатор у вигляді {{.field_name}}, де можемо комбінувати декілька полів.

Тобто, якщо хочемо створити тег error_message в якому будуть значення полів level та msg – формуємо такий запит:

{job="default/fake-logger"} | json | label_format error_message="{{.level}} {{.msg}}"

Log Metrics

І розглянемо як із логів можна створювати метрики, які можна використовувати для формування графіків або алертів (див. Grafana Loki: алерти з Ruler та labels з логів).

Interval vectors

Для роботи з векторами за часом наразі є чотири доступні функції, які в принципі вже знайомі по Prometheus:

  • rate: кількість логів в секунду
  • count_over_time: підрахувати кількість записів стріму за заданий проміжок часу
  • bytes_rate: кількість байт в секунду
  • bytes_over_time: підрахувати кількість байт стріму за заданий проміжок часу

Наприклад, отримати queries per second для джоби fake-logger:

rate({job="default/fake-logger"}[5m])

Може бути корисно, щоб створити алерт на випадок як якийсь сервіс почав писати багато логів, що може бути ознакою того, що “щось пішло не так”.

Отримати кількість записів з рівнем warning за останні 5 хвилин можно за допомогою такого запиту:

count_over_time({job="default/fake-logger"} | json | level="warning" [5m])

Aggregation Functions

Також, можемо використовувати функції агрегації для об’єднання вихідних даних, всі також знайомі по PromQL:

  • sum: сумма за лейблою
  • min, max та avg: мінімальне, максимальне да середнє значення
  • stdev, stdvar: стандартне відхилення та розбіжність
  • count: кількість елементів у векторі
  • bottomk та topk: мінімальний та максимальний елементи

Синтаксис функцій агрегації:

<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]

Наприклад, отримати кількість записів в секунду від джоби fake-logger, та поділити їх по тегу label:

sum(rate({job="default/fake-logger"} | json [5m])) by (level)

Або з прикладів вище:

  • отримати записи з подів loki-read
  • із результату створити дві нові лейбли – component та duration
  • отримати кількість записів в секунду
  • прибрати записи без компоненту
  • та відобразити суму по кожному компоненту
sum(rate({job="monitoring/loki-read"} | logfmt | line_format "{{.component}} {{.duration}}" | component != "" [10s])) by (component)

Інші оператори

І зовсім вже коротко про інші можливості.

Математичні оператори:

  • + – додавання
  • - – віднімання
  • * – множення
  • / – ділення
  • % – коефіцієнт
  • ^ – зведення у ступінь

Логічні оператори:

  • and: та
  • or: або
  • unless: за виключенням

Оператори порівняння:

  • ==: дорівнює
  • !=: не дорівнює
  • >: більше ніж
  • >=: більше ніж або дорівнює
  • <: менше ніж
  • <=: менше ніж або дорівнює

Знов-таки з прикладів, які використовували раньше.

Створюємо лейблу request:

{container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>`

Отримаємо рейт запросів POST в секунду за останні 5 хвилин:

sum(rate({container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>` | request="POST" [5m]))

Спочатку перевіримо на графіку кількість запитів GET та POST:

sum(rate({container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>` [5m]))  by (request)

А теперь отримаємо процент з типом POST від загальної кількості запитів:

  • всі запити POST ділимо на загальну кількість запитів
  • результат множимо на 100
sum(rate({container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>` | request="POST" [5m])) / sum(rate({container="nginx"} | pattern `<_> - - [<_>] <_> "<request> <_> <_>" <_> "<_>" "<_>" <_>` [5m])) * 100

На цьому все.

Посилання по темі

Loading

Grafana Loki: алерти з Ruler та labels з логів
0 (0)

28 Грудня 2022

Загальну інформацію по Grafana Loki див. у Grafana Loki: архітектура та запуск в Kubernetes з AWS S3 storage та boltdb-shipper.

Серед інших сервісів, які складають собою Loki, є окремий сервіс ruler, який відповідає за роботу з алертами, які можно генерити прямо з логів.

Ідея дуже проста:

  • створюємо файл з алертами в  Prometheus-like форматі
  • підключаємо його до поду ruler (loki-read у випадку simple-scalable deployment)
  • ruler парсить логи по заданним в файлі конфігурації правилам, і якщо якийсь expression спрацьовує – то Ruler пушить Alertmanager, передаючи йому алерт

Алерти будемо описувати в ConfigMap, який потім підключимо до подів з Ruler.

Документація – Rules and the Ruler.

Тестовый под для OOM-Killed

Мені хочеться потестити на спрацювання OOM Killed, тому створимо под явно заниженими лімітами, який буде вбиватися “на зльоті”:

---
apiVersion: v1
kind: Pod
metadata:
  name: oom-test
  labels:
    test: "true"
spec:
  containers:
    - name: oom-test
      image: openjdk
      command: [ "/bin/bash", "-c", "--" ]
      args: [ "while true; do sleep 30; done;" ]
      resources:
        limits:
          memory: "1Mi"
  nodeSelector:
    kubernetes.io/hostname: eks-node-dev_data_services-i-081719890438d467f

Задаємо в nodeSelector ім’я ноди, щоб було простіше шукати в Локі.

При старті цього поду Kubernetes його вбиватиме через перевищення лімітів, а journald на WorkerNode записуватиме подію в системний журнал, який збирається promtail:

[simterm]

$ kk -n monitoring get cm logs-promtail -o yaml
...
    - job_name: journal
       journal:
        labels:
          job: systemd-journal
        max_age: 12h
        path: /var/log/journal
      relabel_configs:
      - source_labels:
        - __journal__systemd_unit
        target_label: unit
      - source_labels:
        - __journal__hostname
        target_label: hostname

[/simterm]

Запускаємо наш под:

[simterm]

$ kk apply -f test-oom.yaml 
pod/oom-test created

[/simterm]

Перевіряємо:

[simterm]

$ kk describe pod oom-test
...
Events:
  Type     Reason                  Age                 From               Message
  ----     ------                  ----                ----               -------
  Normal   Scheduled               91s                 default-scheduler  Successfully assigned default/oom-test to ip-10-0-0-27.us-west-2.compute.internal
  Normal   SandboxChanged          79s (x12 over 90s)  kubelet            Pod sandbox changed, it will be killed and re-created.
  Warning  FailedCreatePodSandBox  78s (x13 over 90s)  kubelet            Failed to create pod sandbox: rpc error: code = Unknown desc = failed to start sandbox container for pod "oom-test": Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: container init was OOM-killed (memory limit too low?): unknown

[/simterm]

І перевіряємо логі Loki:

Окей, тепер у нас є oom-killed под для тестів – давайте формувати запит для майбутнього алерту.

Формування запиту в Loki

В логах ми дивилися по запиту  {hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" – використовуємо його ж для тестового алерту.

Спочатку перевіримо що нам намалює сама Локі – використовуємо rate() та sum(), див. Log range aggregations:

sum(rate({hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" [5m])) by (hostname)

Гуд!

З цим вже можна працювати – створювати тестовий алерт.

Створення алерту для Loki Ruler

Створюємо файл з ConfigMap:

kind: ConfigMap
apiVersion: v1
metadata:
  name: rules-alerts
  namespace: monitoring
data:
  rules.yaml: |-
    groups:
      - name: systemd-alerts
        rules:
          - alert: TESTLokiRuler Systemd journal
            expr: |
              sum(rate({hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" [5m])) by (hostname) > 1
            for: 1s
            labels:
                severity: info
            annotations:
                summary: Test Loki OOM Killer Alert

Деплоїмо його:

[simterm]

$ kk apply -f rule-cm.yaml 
configmap/rules-alerts created

[/simterm]

Ruler та ConfigMap volume

Далі, нам треба підключити цей ConfigMap в под з ruler в каталог, який вказаний в конфізі Loki для компонента ruler:

...
    ruler:
      storage:
        local:
          directory: /var/loki/rules
...

Ruler у нас працює в loki-read подах – відкриваємо їх StatefulSet:

[simterm]

$ kk -n monitoring edit sts loki-read

[/simterm]

Описуємо новий volume:

...
      volumes:
      - configMap:
          defaultMode: 420
          name: rules-alerts
        name: rules
...

І його мапінг у под як  /var/loki/rules/fake/rules.yaml, де fake – це tenant_id, якщо використовується:

...
        volumeMounts:
        - mountPath: /etc/loki/config
          name: config
        - mountPath: /tmp
          name: tmp
        - mountPath: /var/loki
          name: data
        - mountPath: /var/loki/rules/fake/rules.yaml
          name: rules
          subPath: rules.yaml
...

В subPath вказуємо key з ConfigMap, щоб підключити саме як файл.

Налаштування Ruler alerting

Знаходимо Alertmanager URL:

[simterm]

$ kk -n monitoring get svc
NAME                                             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
...
prometheus-kube-prometheus-alertmanager          ClusterIP   172.20.240.159   <none>        9093/TCP                     110d
...

[/simterm]

У ConfigMap Loki для ruler вказуємо цю адресу:

...
    ruler:
      storage:
        local:
          directory: /var/loki/rules
        type: local
      alertmanager_url: http://prometheus-kube-prometheus-alertmanager:9093
...

Усі параметри для ruler – тут>>> .

Відкриваємо собі доступ до Alertmanager, щоб перевіряти алерти:

[simterm]

$ kk -n monitoring port-forward svc/prometheus-kube-prometheus-alertmanager 9093:9093

[/simterm]

Рестартим поди loki-read, можна просто через kubectl delete pod, і перевіряємо їх логи:

[simterm]

$ kk -n monitoring  logs -f loki-read-0
...
level=info ts=2022-12-13T16:37:33.837173256Z caller=metrics.go:133 component=ruler org_id=fake latency=fast query="(sum by(hostname)(rate({hostname=\"eks-node-dev_data_services-i-081719890438d467f\"} |~ \".*OOM-killed.*\"[5m])) > 1)" query_type=metric range_type=instant length=0s step=0s duration=120.505858ms status=200 limit=0 returned_lines=0 throughput=48MB total_bytes=5.8MB total_entries=1 queue_time=0s subqueries=1
...

[/simterm]

Перевіряємо Алерти в Алертменеджері – http://localhost:9093:

Loki та додаткові labels

В алертах хочеться виводити трохи більше інформації, ніж просто повідомлення “Test Loki OOM Killer Alert”, наприклад – відобразити ім’я пода, який був вбитий.

Додавання labels в Promtail

Перший варіант – це створювати нові лейбли ще на етапі збору логів, в самому Promtail через pipeline_stages, див. Grafana: Loki – Prometheus-like счётчики и функции агрегации в LogQL и графики DNS запросов к dnsmasq, наприклад так:

- job_name: journal
  pipeline_stages:
  - match:
      selector: '{job="systemd-journal"}'
      stages:
      - regex:
          expression: '.*level=(?P<level>[a-zA-Z]+).*'
      - labels:
          level:
      - regex:
          expression: '.*source="(?P<source>[a-zA-Z]+)".*'
      - labels:
          source:
  journal:
    labels:
      job: systemd-journal
    max_age: 12h
    path: /var/log/journal
  relabel_configs:
  - source_labels:
    - __journal__systemd_unit
    target_label: unit
  - source_labels:
    - __journal__hostname
    target_label: hostname

Тут я для тестів створював нові лейбли, які підключалися до логів – source і level.

Інший варіант із Promtail – використовуючи static_labels.

Але тут є проблема: оскільки Loki на кожний набір лейбл створює окремий лог-стрім, для якого створюються окремі індекси та блоки даних, то в результаті отримаємо по-перше проблеми з продуктивністю, по-друге – з вартістю, т.к. на кожен індекс і блок даних будуть виконуватися запити читання-запису в shared store, у нашому випадку це AWS S3, де за кожен запит доводиться платити гроші.

Дивись чудовий пост на цю тему тут – Grafana Loki and what can go wrong with label cardinality.

Додавання labels із запитів в Loki

Натомість, ми можемо створювати нові лейбли прямо із запиту за допомогою самої Loki.

Візьмемо запис із лога, в якому йдеться про спрацювання OOM Killer:

E1213 16:52:25.879626 3382 pod_workers.go:951] “Error syncing pod, skipping” err=”failed to \”CreatePodSandbox\” for \”oom-test_default(f02523a9-43a7-4370-85dd-1da7554496e6)\” with CreatePodSandboxError: \”Failed to create sandbox for pod \\\”oom-test_default(f02523a9-43a7-4370-85dd-1da7554496e6)\\\”: rpc error: code = Unknown desc = failed to start sandbox container for pod \\\”oom-test\\\”: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: container init was OOM-killed (memory limit too low?): unknown\”” pod=”default/oom-test” podUID=f02523a9-43a7-4370-85dd-1da7554496e6

Тут ми маємо поле pod з ім’ям пода, який був вбитий – pod="default/oom-test".

Використовуємо regex у вигляді pod=".*/(?P<pod>[a-zA-Z].*)".* щоб створити Named Capturing Group , перевіряємо наприклад на https://regex101.com :

Доповнюємо вибірку в Loki:

{hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" | regexp `pod=".*/(?P<pod>[a-zA-Z].*)".*`

І в результаті отримуємо лейблу pod  зі значенням ” oom-test “:

Перевіряємо запит алерту з sum() і rate():

sum(rate({hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" | regexp `pod=".*/(?P<pod>[a-zA-Z].*)".*` | pod!="" [5m])) by (pod)

Результат:

Оновлюємо алерт – додамо description в якому використовуємо {{ $labels.pod }}:

- alert: TESTLokiRuler Systemd journal
  expr: |
    sum(rate({hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" | regexp `.*pod=".*/(?P<pod>[a-zA-Z].*)".*` | pod!="" [15m])) by (pod) > 1
  for: 1s
  labels:
      severity: info
  annotations:
      summary: Test Loki OOM Killer Alert
      description: "Killed pod: `{{ $labels.pod }}`"

Чекаємо на його спрацювання:

І в Слаку:

Grafana Loki и ошибки 502 и 504

Зараз не вдається зарепрод’юсити, але іноді Grafana не може дочекатися відповіді від Loki виконання запиту і падає з помилками 502 або 504.

Є тред в Girthub , мені допомогло збільшення тайм-аутів HTTP в Loki ConfigMap:

...
server:
  http_server_read_timeout: 600s
  http_server_write_timeout: 600s
...

Загалом, на цьому поки що все.

Посилання по темі

Loading

Grafana Loki: архітектура та запуск в Kubernetes з AWS S3 storage та boltdb-shipper
0 (0)

25 Грудня 2022

Останній раз працював з Loki коли вона була ще в Beta, і виглядала вона тоді набагато простіше, ніж зараз.

У новому проекті системи логування немає взагалі, а так як у нас усі люблять Grafana-стек – то вирішили і для логів підняти Loki.

Правда мені думалося, що все буде набагато простіше. Виявилося – ні. Багато змінилося, і довелося знайомитися з нею по суті з нуля.

Що залишилося, як і раніше, – це така собі документація. Якщо опис архітектури та компонентів ще більш-менш нормально описаний, то коли справа доходить до налаштування – то натикаєшся на купу проблем, особливо те, що стосується сторейджа та зберігання в AWS S3 (хоча поки писав цей пост викотили реліз 2.7, і документацію теж оновили – можливо, тепер вона краща). Довелося збирати по шматочках, але в результаті все-таки завелося.

Розглянемо загальну архітектуру та компоненти, потім встановимо в AWS Kubernetes із Helm-чарта.

Архітектура Grafana Loki

Loki створена за мікросервісною архітектурою, при цьому всі мікросервіси зібрані в один бінарник.

Для запуску компонентів використовується опція --target, в якій можна визначити яку частину Loki запустити.

Вхідні дані діляться на стріми – це потік даних, логів, що мають загальний tenant_id (“відправник”), і загальний набір тегів/labels. Докладніше про стріми поговоримо в Storage.

Компоненти Loki

Робота системи ділиться на два основні потоки: Read path читання (обробка запитів на вибірку даних) та Write path – запис цих даних в сторейдж.

Загальна схема всіх компонентів:

Тут:

  • distributor (write path): займається обробкою вхідних даних від клієнтів – одержує від них дані, валідує їх, ділить дані на блоки, і відправляє в ingester. Бажано мати LoadBalancer перед дистриб’юторами, щоб вхідні стріми розподілялися по інстансах дистриб’юторів. Є stateless компонентом – не зберігає в собі жодних даних. Також відповідає за рейт-ліміти та препроцесинг тегів.
  • ingester (write, read path): відповідає за запис даних у довгострокове сховище та за передачу даних для обробки запитів на їх читання клієнтами. Для запобігання втратам даних у разі рестарту інжестера, їх зазвичай запускають у вигляді декількох інстансів (див. replication_factor)
  • querier (read path): обробляє LogQL запити, завантажуючи для відповіді дані з інжестерів та/або довгострокового сховища – спочатку запитує інжестер, якщо в пам’яті інжестера даних немає – то querier йде в сховище даних
  • query frontend (read path): опціональний сервіс, що надає доступ до querier API для прискорення операцій читання. При його використанні він зберігає запити, що надходять, а querier звертається до нього, щоб взяти з черги запит на обробку

Крім того, Loki має додаткові компоненти:

Data flow

Коротко про сам процес обробки даних – запити читання (read), і запис (write).

  • Loki отримує дані з promtail (або інших агентів, наприклад fluentd)
  • створює блоки даних (chunks), індекс, завантажує їх у довгострокове сховище
  • користувач використовує LogQL для вибірки логів у Grafana
  • ruler перевіряє данні, і при необхідності відправляє алерт у Prometheus Alertmanager

Read Path

При отриманні запиту на вибірку даних:

  1. querier отримує HTTP запит
  2. передає запит до ingesters для пошуку даних в пам’яті
  3. якщо ingesters знаходитт дані у себе – то повертає їх querier
  4. якщо в ingesters даних нема – querier йде в сховище даних, і отримує їх звідти
  5. querier повертає відповідь через теж HTTP-з’єднання

Write Path

При отриманні нових даних:

  1. distributor отримує HTTP/1  запрос на додавання даних в конкретний стрім
  2. distributor передає кожний стрім в ingester
  3. ingester створює новий chunk (“блок даних”, див. Loki Storage) або доповнює існуючий
  4. distributor відповідає ОК на HTTP/1  запит

Режими запуску

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

Monolithic mode

Дефолтний тип при використанні локальної filesystem для зберігання даних.

Підходить для швидкого запуску та невеликих об’ємів даних, до 100GB на день

Балансування запитів виконується по round robin.

Паралелізація запитів обмежена кількістю інстансів та налаштуванням кожної інстансу.

Основне обмеження – не можна використовувати object store, такі як AWS S3.

Simple scalable deployment mode

Дефолтний тип під час використання object store.

Якщо у вас логів більше кількох сотень гігабайт, але менше кількох терабайт на день, або ви хочете ізолювати читання та запис, то можна задеплоїти Loki в режимі simple scalable deployment:

У такому режимі Loki запускається з двома таргетами – read & write.

Потребує наявності лоад-балансера, який буде роутити запити до інстансів з компонентами Loki.

Microservices mode

І для найскладніших випадків, коли у вас логи йдуть терабайтами на день, має сенс деплоїти кожен сервіс окремо:

  • ingester
  • distributor
  • query-frontend
  • query-scheduler
  • querier
  • index-gateway
  • ruler
  • compactor

Дозволяє моніторити та скейлити кожен компонент незалежно.

Grafana Loki Storage

Див. документацию Grafana Loki Storage.

Loki для зберігання логів використовує два типи даних – chunks та індекси. Не придумав корректного перекладу для chunk, тому нехай буде “блок даних”.

Loki отримує дані від кількох стримів, де кожен стрім – це tenant_id та набір тегів. При отриманні нових записів від стриму вони упаковуються в блоки і відправляються в довгострокове сховище, в ролі якого можуть бути AWS S3, локальна файлова система, або бази даних типу AWS DynamoDB чи Apache Cassandra.

В індексах зберігається інформація про набір тегів кожного стриму і є посилання на пов’язані з цим стримом блоки даних.

Раніше Loki використовувала два окремі сховища – одне під індекси (наприклад, таблиці DynamoDB), і друге – безпосередньо під самі дані (AWS S3).

Десь з версії 2.0 у Loki з’явилася можливість зберігати індекси у вигляді BotlDB файлів та використовувати Single Store – єдине сховище і для блоків даних, і для індексів. Див. Single Store Loki (boltdb-shipper index type).

Ми будемо використовувати boltdb-shipper – він буде формувати індекси локально, а потім пушити їх у shared object store. Там же будуть зберігатися і самі chunks.

В Loki 2.7 з’явився ще один новий спосіб зберігання індексів – у вигляді TSDB-файлів, див. Grafana Loki 2.7 release: TSDB index, Promtail enhancements, and more.

Loki streams, labels та збереження даних

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

Тобто, якщо ви динамічно створюєте теги/лейбли, наприклад client_ip – то у вас буде формуватися окремий набір файлів на кожен клієнтський IP, що призведе до того, що на кожен такий файл будуть виконуватися окремі запити GET/POST/DELETE, що може привести по-перше до зростання вартості сховища (як у випадку з AWS S3, де оплачується кожен виклик), так і до проблем з швидкістю обробки запитів.

Див. Labels і чудовий пост Grafana Loki and what can go wrong with label cardinality.

Loki Helm charts

Окрім документації у Loki ще й із чартами трохи складнощів, оскільки переносили між репозиторіями, об’єднували, і тепер деякі стали deprecated (хоча посилання на них у документації зустрічаються).

Нижче – не про установку, а просто деякі особливості чартів Loki, з якими довелося повозитися.

Отже – є Хельм-репозиторій Графани – https://grafana.github.io/helm-charts, додаємо його:

[simterm]

$ helm repo add grafana https://grafana.github.io/helm-charts

[/simterm]

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

Chart documentation is available in grafana directory.

Переходимо за посиланням, і потрапляємо до git-репозиторію, в якому є пачка чартів:

Вони ж є при пошуку Хелмом:

[simterm]

$ helm search repo grafana loki
NAME                            CHART VERSION   APP VERSION     DESCRIPTION                                       
bitnami/grafana-loki            2.5.0           2.7.0           Grafana Loki is a horizontally scalable, highly...
grafana/loki                    3.3.4           2.6.1           Helm chart for Grafana Loki in simple, scalable...
grafana/loki-canary             0.10.0          2.6.1           Helm chart for Grafana Loki Canary                
grafana/loki-distributed        0.65.0          2.6.1           Helm chart for Grafana Loki in microservices mode 
grafana/loki-simple-scalable    1.8.11          2.6.1           Helm chart for Grafana Loki in simple, scalable...

[/simterm]

Може, залишили для сумісності, окей, але геморою з установкою це додає.

Можна завантажити та розпакувати локально, щоб подивитися, що там є:

[simterm]

$ helm pull grafana/loki --untar

[/simterm]

Дефолтні values – тут>>>.

Helm chart та Deployment Mode

Ще один момент, який трохи поламав мозок: окей, ми бачили, що Loki можна запустити з різними Deployment modes – але як це визначити у чарті? Якогось values типу -targetтам немає.

Нижче – трохи копання в чарті, можна пропустити, якщо вам підходить дефолтна установка.

Отже, якщо встановити з дефолтними values, то отримуємо наступні компоненти:

[simterm]

$ helm install loki grafana/loki
...
Installed components:
* grafana-agent-operator
* gateway
* read
* write

[/simterm]

Та поди:

[simterm]

$ kk get pod
NAME                                           READY   STATUS              RESTARTS   AGE
loki-canary-7vrj2                              0/1     ContainerCreating   0          12s
loki-gateway-5868b68c68-lwtfj                  0/1     ContainerCreating   0          12s
loki-grafana-agent-operator-684b478b77-zmw5t   1/1     Running             0          12s
loki-logs-kwxcx                                0/2     ContainerCreating   0          3s
loki-read-0                                    0/1     ContainerCreating   0          12s
loki-read-1                                    0/1     Pending             0          12s
loki-read-2                                    0/1     Pending             0          12s
loki-write-0                                   0/1     ContainerCreating   0          12s
loki-write-1                                   0/1     Pending             0          12s
loki-write-2                                   0/1     Pending             0          12s

[/simterm]

Тобто, по дефолту воно встановлюється в simple-scalable, при цьому в документації самих чартів про це нічого не сказано, як ні слова про те, як задати деплоймент-режим взагалі.

А якщо я хочу Single Binary?

Зносимо:

[simterm]

$ helm uninstall loki
release "loki" uninstalled

[/simterm]

Спробуємо повірити документації, і створюємо свої values:

loki:
  commonConfig:
    replication_factor: 1
  storage:
    type: 'filesystem'

Встановлюємо:

[simterm]

$ helm upgrade --install --values values-local.yaml loki grafana/loki
...
Installed components:
* grafana-agent-operator
* loki

[/simterm]

What?

Тобто, тупо перевизначивши сторейдж – ми змінюємо режим деплойменту?!?

Ах#*$ть – дайте два!

Як воно працює?

Відкриваємо файл templates/_helpers.tpl, в якому є два шаблони – loki.deployment.isScalable і loki.deployment.isSingleBinary, в яких одна і та ж умова, тільки з різними значеннями:

...
{{- eq (include "loki.isUsingObjectStorage" . ) "false" }}
...

Якщо true – то це isScalable, а якщо false – то isSingleBinary.

Окей, а що за isUsingObjectStorage?

Знаходимо його в тому же хелпері:

...
{{/* Determine if deployment is using object storage */}}
{{- define "loki.isUsingObjectStorage" -}}
{{- or (eq .Values.loki.storage.type "gcs") (eq .Values.loki.storage.type "s3") (eq .Values.loki.storage.type "azure") -}}
{{- end -}}
...

Тобто, якщо ми використовуємо .Values.loki.storage.type із значенням gcs, s3 або azure – то loki.isUsingObjectStorage прийме значення true, і Loki буде встановлено в режимі Simple Scale.

Зовсім не очевидно і не описано в документації до чарту.

Запуск Grafana Loki

А тепер, нарешті, перейдемо до запуску і налаштування Loki.

Для зберігання даних будемо використовувати AWS S3, для роботи з індексами – bottledb-shipper, для налаштування терміну зберігання логів – compactor.

Аутентифікацію Loki реалізуємо через підключення ServiceAccount із AWS IAM Role, але покажу приклад і зі звичайними ACCESS/SECRET keys.

Створення AWS S3 корзини

Почнемо зі створення корзини. Можна через AWS CLI та create-bucket, або через Terraform:

resource "aws_s3_bucket" "loki_object_store" {
  bucket = "${var.client}-${var.environment}-loki-object-store"
      
  tags = {
    Name        = "Grafana Loki Object Store"
    environment = var.environment 
    service     = var.service
  }   
}

Зараз для простоти створимо через AWS Console:

Запам’ятовуємо регіон, тут це us-west-2:

AWS IAM Role && Policy

Для корзини буде потрібна політика, яка дозволяє до неї доступ, і роль, яку потім підключимо до Kubernetes Pod.

Повертаючись до проблем документації Loki – на сторінці Grafana Loki Storage є приклад політики для AWS S3, яка… не проходить валідацію в AWS IAM :faceplam:

Взагалі часто виникали асоціації з Miscrosoft Azure – там документації теж вірити не можна від слова зовсім, і все треба перевіряти і збирати по шматочках.

Використовуючи ServiceAccount

Докладно ServiceAccount та IAM описував у Kubernetes: ServiceAccount з AWS IAM Role для Kubernetes Pod, тут швиденько.

Переходимо в AWS Console > IAM > Policies, створюємо Policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::test-loki-0",
                "arn:aws:s3:::test-loki-0/*"
            ]
        }
    ]
}

Переходимо в EKS, знаходимо OpenID Connect provider URL:

Переходимо в IAM > Identity providers, за ID 537***A10 знаходимо OIDC ARN:

Переходимо в Roles, створюємо роль: вибираємо тип Web identity, зі списку вибираємо наш Identity provider, в Audience вказуємо sts.amazon.com:

Підключаємо створену раніше політику:

Отримуємо Trusted Policy, зберігаємо нову роль:

Зберігаємо ARN ролі – використуємо його далі в параметрах Loki:

Використовуючи AWS Access и Secret Keys

Інший варіант – замість IAM ролі та ServiceAccount використовувати опції access_key_id та secret_access_key, див. s3-expanded-config.yaml:

...
storage_config:
  aws:
    bucketnames: bucket_name1, bucket_name2
    endpoint: s3.endpoint.com
    region: s3_region
    access_key_id: s3_access_key_id
    secret_access_key: s3_secret_access_key
    insecure: false
...

Це трохи простіше, ніж ServiceAccount, питання тільки в тому, як зберігати та передавати секрети з ключем.

Для приклада створимо через AWS Console звичайного користувача, якому підключимо політику.

Переходимо в IAM > Roles, створюємо Policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::test-loki-0",
                "arn:aws:s3:::test-loki-0/*"
            ]
        }
    ]
}

Створюємо користувача з Programmatic access:

Підключаємо йому цю політику:

Зберігаємо ключі:

Переходимо до конфігу Loki – тут теж вистачило болю та страждань із документацією та чартом.

Запуск Grafana Loki в Kubernetes

Ну і тепер, коли мені стало ясно і з чартами, і з тим, як же через Helm-чарт Loki задати Deployment Mode, і взагалі який чарт використовувати – спробуємо її запустити.

Готуємо мінімальний конфіг, в якому для початку відключимо весь її внутрішній моніторинг щоб зменшити кількість подів – буде простіше розбиратися з тим, як воно працює, і для початку використовуємо сховище filesystem, щоб зберігати дані та індекси локально в подах:

loki:
  
  auth_enabled: false
  commonConfig:
    path_prefix: "/var/loki"
    replication_factor: 1
    
  storage:
    type: "filesystem"

  schema_config:
    configs:
    - from: 2022-12-12
      store: boltdb
      object_store: filesystem
      schema: v12
      index:
        prefix: index_
        period: 168h
      
storage_config:
  boltdb:
    directory: /var/loki/index
  
  filesystem:
    directory: /var/loki/chunks
    
test: 
  enabled: false
monitoring:
  dashboards:
    enabled: false
  rules:
    enabled: false
  alerts:
    enabled: false
  serviceMonitor:
    enabled: false
  selfMonitoring:
    enabled: false
    lokiCanary:
      enabled: false
    grafanaAgent:
      installOperator: false

Деплоїмо в неймспейс test-loki-0:

[simterm]

$ helm upgrade --install --namespace test-loki-0 --create-namespace --values loki-minimal-values.yaml loki grafana/loki
...
Installed components:
* loki

[/simterm]

Перевіряємо под:

[simterm]

$ kk -n test-loki-0 get pod
NAME     READY   STATUS    RESTARTS   AGE
loki-0   1/1     Running   0          118s

[/simterm]

Окей – є один под, нічого зайвого.

Чарт створює StatefulSet, в якому описується створення цього поду і через який підключаються різні volumes:

[simterm]

$ kk -n test-loki-0 get sts
NAME   READY   AGE
loki   1/1     3m

[/simterm]

І ConfigMap, в якій зберігається конфіг, доповнений нашим loki-minimal-values.yaml:

[simterm]

$ kk -n test-loki-0 get cm loki -o yaml
apiVersion: v1
data:
  config.yaml: |
    auth_enabled: false
    common:
      path_prefix: /var/loki
      replication_factor: 1
      storage:
        filesystem:
          chunks_directory: /var/loki/chunks
          rules_directory: /var/loki/rules
...

[/simterm]

Grafana Loki S3 config

Дуже багато віддав би, щоб десь знайти повний конфіг для Grafana Loki з AWS S3 як у прикладі нижче, та ще й з авторизацією через ServiceAccount і AWS IAM – витратив багато часу, щоб змусити все це працювати.

Власне, сам конфіг, потім трохи про опції та підводні камені, з якими зіткнувся:

loki:

  auth_enabled: false
  commonConfig:
    path_prefix: /var/loki
    replication_factor: 1

  storage:
    bucketNames:
      chunks: test-loki-0
    type: s3

  schema_config:
    configs:
    - from: "2022-01-11"
      index:
        period: 24h
        prefix: loki_index_
      store: boltdb-shipper
      object_store: s3
      schema: v12
  
  storage_config:
    aws:
      s3: s3://us-west-2/test-loki-0
      insecure: false
      s3forcepathstyle: true
    boltdb_shipper:
      active_index_directory: /var/loki/index
      shared_store: s3
  rulerConfig:
    storage:
      type: local
      local:
        directory: /var/loki/rules

serviceAccount:
  create: true
  annotations:
    eks.amazonaws.com/role-arn: "arn:aws:iam::638***021:role/test-loki-0-role"
write:
  replicas: 2
    
read:
  replicas: 1

test:
  enabled: false
monitoring:
  dashboards:
    enabled: false
  rules:
    enabled: false
  alerts:
    enabled: false
  serviceMonitor:
    enabled: false
  selfMonitoring:
    enabled: false
    lokiCanary:
      enabled: false
    grafanaAgent:
      installOperator: false

Итак, тут:

  • auth_enabled: false – відключаємо авторизацію в самій Loki (в результаті отримаємо tenant_id fake в корзині – це ок, нормально, хоча могли б придумати щось красивіше ніж “фейк”)
  • storage.bucketNames.chunks – потрібно вказати ім’я корзини для блоків, інакше намагатиметься використовувати локальне сховище; у документації не вказано;
  • schema_config.configs.store:
    • boltdb-shipper – вказуємо на використання boltdb-shipper для роботи з індексами, оскільки він вміє в Single Store, тобто і блоки даних і їх індекси будуть в одній корзині
    • object_store: s3 – вказуємо тип сховища, яке налаштовується в storage_config.aws.s3 (але тут вказуємо саме як schema_config.configs.store.s3, а не schema_config.configs.store.aws.s3)
  • storage_config – найбільший біль:
    • aws.s3: вказуємо саме у вигляді s3://<S3_BUCKET_REGION>/<S3_BUCKET_NAME>, інакше при підключенні ServiceAccount Loki починає намагатися ходити для авторизації на https://sts.dummy.amazonaws.com – я так і не знайшов чому, але при використанні ServiceAccount потрібен саме такий формат
    • boltdb_shipper – вказуємо йому локальний шлях, де він створює індекси – active_index_directory, та shared_store – куди потім їх відправляти; візьме конфіг із тієї ж storage_config.aws.s3
  • rulerConfig.storage.type: local – для ruler поки вкажемо локальний каталог, з алертами розберемося в інший раз; якщо не вказати – буде постійно писати в лог помилку, що не може отримати доступ до своєї корзини, яка десь прописана у дефолтах, не пам’ятаю вже де саме
  • write.replicas: 2 – мінімальна кількість подів write, щоб Promatil міг писати дані

Оновлюємо Helm-реліз:

[simterm]

$ helm upgrade --install --namespace test-loki-0 --values loki-values.yaml loki grafana/loki
...
Installed components:
* gateway
* read
* write

[/simterm]

Тепер у нас є окремо поди read та write. У gateway просто Nginx, який розрулює запити:

[simterm]

$ kk -n test-loki-0 get pod
NAME                            READY   STATUS    RESTARTS   AGE
loki-gateway-55b4798bdb-g9hkl   1/1     Running   0          48s
loki-read-0                     0/1     Pending   0          48s
loki-write-0                    0/1     Running   0          48s
loki-write-1                    0/1     Running   0          47s

[/simterm]

Чекаємо хвилину, поки поди перейдуть в Running, перевіряємо логи пода loki-write-0, і після повідомлення:

msg=”joining memberlist cluster succeeded” reached_nodes=2 elapsed_time=1m39.087106032s

перевіряємо корзину:

[simterm]

$ aws --profile development s3 ls test-loki-0
2022-12-25 11:53:13        251 loki_cluster_seed.json

[/simterm]

І ще за кілька хвилин повинні повитися каталоги fake та index:

[simterm]

$ aws --profile development s3 ls test-loki-0
                           PRE fake/
                           PRE index/
2022-12-25 11:53:13        251 loki_cluster_seed.json

[/simterm]

У fake – chunks, в index – індекси.

Окей – начебто завелося.

Дивно, насправді, що цього разу все з першої спроби завелося… Поки сетапив Loki на проекті, де працюю – реально вже подумував взяти ELK та не гаяти час.

Тепер після додавання promatil, який писатиме дані – Loki Write через ingester писатиме блоки даних, а bottledb-shipper почне створювати індекси, і пушити їх у корзину.

Запуск Promtail

Знаходимо Service для Loki Gateway:

[simterm]

$ kk -n test-loki-0 get svc
NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
loki-gateway          ClusterIP   10.109.225.168   <none>        80/TCP              22m
...

[/simterm]

Деплоїмо, через --set вказуємо loki.serviceName:

[simterm]

$ helm upgrade --install --namespace test-loki-0 --set loki.serviceName=loki-gateway promtail grafana/promtail

[/simterm]

Перевіряємо поди:

[simterm]

$ kk -n test-loki-0 get pod
NAME                            READY   STATUS    RESTARTS   AGE
loki-gateway-55b4798bdb-7dzlf   1/1     Running   0          5m32s
loki-read-0                     1/1     Running   0          5m32s
loki-write-0                    1/1     Running   0          5m32s
loki-write-1                    1/1     Running   0          5m32s
promtail-6pw59                  0/1     Running   0          17s
promtail-8h78j                  0/1     Running   0          17s
promtail-jb6bz                  0/1     Pending   0          17s
...

[/simterm]

Пішли запускатися поди з promtail.

Перевіряємо логи Gateway – через нього повинні йти пуші від promtail:

[simterm]

$ kk -n test-loki-0 logs -f loki-gateway-55b4798bdb-7dzlf
...
10.0.87.55 - - [25/Dec/2022:09:58:19 +0000]  204 "POST /loki/api/v1/push HTTP/1.1" 0 "-" "promtail/2.7.0" "-"
10.0.109.239 - - [25/Dec/2022:09:58:19 +0000]  204 "POST /loki/api/v1/push HTTP/1.1" 0 "-" "promtail/2.7.0" "-"

[/simterm]

Тепер встановимо Grafana і підключимо до неї Loki.

Запуск Grafana

Встановлюємо з того самого репозиторію:

[simterm]

$ helm upgrade --install --namespace test-loki-0 grafana grafana/grafana

[/simterm]

Отримуємо пароль юзера admin:

[simterm]

$ kubectl get secret --namespace test-loki-0 grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo
ahUAdmUdpemotqICa6jGzvi9wiU01an5qZJx3WSb

[/simterm]

Прокидуємо порт:

[simterm]

$ kk -n test-loki-0 port-forward svc/grafana 8080:80

[/simterm]

Відкриваємо у браузері http://localhost:8080, логінимось, переходимо до Configuration – Data Sources:

Клікаємо Add data source, выбираємо Loki:

Додаємо Loki, в URL вказуємо http://loki-gateway:80:

Зберігаємо, перевіряємо:

Переходимо в Explore, зверху вибираємо Loki, і дивимось логи:

Готово.

Loading

Prometheus: моніторинг ендпоінтів в Kubernetes з blackbox-exporter
0 (0)

10 Грудня 2022

Про blackbox-exporter я вже колись писав, див. Prometheus: Alertmanager и blackbox-exporter – проверка срока действия SSL и нотификация в Slack, але там було чисто про моніторинг SSL-сертіфікатів, та й було то давно, та й сетапилось все без Кубернетісу та Хельму.

Цього разу трохи детальніше про його сетап і можливості.

Отже, blackbox-exporter – це експортер, який вміє моніторити різноманітні ендпоінти – це можуть бути або якісь URL-и в інтернеті, ваші LoadBalancer-и в Амазоні, або Services в Кубернетесі, такі як MySQL або PostgreSQL бази данних.

Вміє виводити статистику по швидкості відповіді HTTP, коди відповідей, інформацію по SSL-сертіфікатах тощо.

Що будемо робити:

  • за допомогую Helm розгорнемо kube-prometehus-stack в Minikube
  • додамо сам експортер
  • налаштуємо моніторинг ендпоінтів за допомогую ServiceMonitors, які будуть створені через конфіг blackbox-exporter
  • подивимось на основні probes, якими він опитує ендпоінти

Поїхали.

Запуск Kube Prometheus Stack

Робити будемо в мінікубі, куди встановимо Prometheus Operator із Helm-репозіторія.

Запускаємо сам Мінікуб:

[simterm]

$ minikube start

[/simterm]

Додаємо репозіторій чартів Prometheus:

[simterm]

$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update

[/simterm]

Створюємо неймспейс:

[simterm]

$ kubectl create ns monitoring

[/simterm]

Встановлюємо kube-prometheus-stack:

[simterm]

$ helm -n monitoring install prometheus prometheus-community/kube-prometheus-stack

[/simterm]

Чекаємо декілька хвилин, поки всі поди стануть Running:

[simterm]

$ kubectl -n monitoring get pod
NAME                                                     READY   STATUS            RESTARTS      AGE
alertmanager-prometheus-kube-prometheus-alertmanager-0   1/2     Running           1 (25s ago)   44s
prometheus-grafana-599dbccb79-zlklx                      2/3     Running           0             57s
prometheus-kube-prometheus-operator-689dd6679c-s66vp     1/1     Running           0             57s
prometheus-kube-state-metrics-6cfd96f4c8-84j26           1/1     Running           0             57s
prometheus-prometheus-kube-prometheus-prometheus-0       0/2     PodInitializing   0             44s
prometheus-prometheus-node-exporter-2h542                1/1     Running           0             57s

[/simterm]

Знаходимо Сервіс Прометеусу:

[simterm]

$ kubectl -n monitoring get svc
NAME                                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
alertmanager-operated                     ClusterIP   None             <none>        9093/TCP,9094/TCP,9094/UDP   7s
prometheus-grafana                        ClusterIP   10.97.79.182     <none>        80/TCP                       20s
prometheus-kube-prometheus-alertmanager   ClusterIP   10.106.147.39    <none>        9093/TCP                     20s
prometheus-kube-prometheus-operator       ClusterIP   10.98.222.45     <none>        443/TCP                      20s
prometheus-kube-prometheus-prometheus     ClusterIP   10.107.26.113    <none>        9090/TCP                     20s
...

[/simterm]

Прокидуємо порт через port-forward:

[simterm]

$ kubectl -n monitoring port-forward svc/prometheus-kube-prometheus-prometheus 9090:9090

[/simterm]

Відкриваємо http://localhost:9090, та перевіряємо, що все працює:

Запуск blackbox-exporter

Його чарт є в тому ж репозиторії, тож просто встановлюємо експортер:

[simterm]

$ helm -n monitoring upgrade --install prometheus-blackbox prometheus-community/prometheus-blackbox-exporter

[/simterm]

Перевіряємо под:

[simterm]

$ kk -n monitoring get pod
NAME                                                              READY   STATUS    RESTARTS        AGE
prometheus-blackbox-prometheus-blackbox-exporter-6865d9b44h546j   1/1     Running   0               27s
...

[/simterm]

Blackbox тримає свій конфіг в ConfigMap-і, яка підключається до поду і передає дефолтні параметри. Див. тут>>>.

[simterm]

$ kk -n monitoring get cm prometheus-blackbox-prometheus-blackbox-exporter -o yaml
apiVersion: v1
data:
  blackbox.yaml: |
    modules:
      http_2xx:
        http:
          follow_redirects: true
          preferred_ip_protocol: ip4
          valid_http_versions:
          - HTTP/1.1
          - HTTP/2.0
        prober: http
        timeout: 5s

[/simterm]

Власне, тут ми і бачимо модулі, точніше поки що один, який використвує prober http, який виконує HTTP-запроси до targets, які ще треба додати.

Blackbox та ServiceMonitor

Для того, щоб додати ендпоінти, котрі ми хочемо моніторити, можна використовувати ServiceMonitor, див. конфіг тут>>>.

Чомусь ніде в нагуглених гайдах цей момент толком не описаний, хоча він дуже зручний: в конфіг Блекбоксу додаємо список таргетів, а Блекбокс створює ServiceMonitor для кожного з них, і Prometheus починає їх моніторити.

Створюємо файл blackbox-exporter-values.yaml, в якому додаємо поки що один ендпоінт – просто перевірити, чи воно взагалі працює:

serviceMonitor:
  enabled: true
  defaults:
    labels:
      release: prometheus
  targets:
    - name: google.com
      url: https://google.com

Якщо не вказано інше, то Блекбокс використвує дефолтні значення із values.yaml чарту, в данному випадку це буде модуль http_2xx, який виконує GET запрос, та перевіряє код відповіді: якщо отримано 200 – то перевірка пройдена, якщо інший – то фейл.

Оновлюємо Helm-реліз з новим конфігом:

[simterm]

$ helm -n monitoring upgrade --install prometheus-blackbox prometheus-community/prometheus-blackbox-exporter -f blackbox-exporter-values.yaml

[/simterm]

Перевіряємо, чи створився ServiceMonitor

[simterm]

$ kk -n monitoring get servicemonitor
NAME                                                          AGE
prometheus-blackbox-prometheus-blackbox-exporter-google.com   4m43s

[/simterm]

Перевіряємо в Targets самого Прометеусу:

Для кожного Target, який ми вказуємо в конфігурації Блекбоксу, додається окрема scrape job в Прометеусі:

І перевіряємо метріки Блекбоксу:

Основна метрика, яку використую особисто я – це probe_success, яка власне говорить чи пройдена перевірка:

Тут в лейблу target через metricRelabelings підставляється значення з name, яке ми вказали для таргету Блекбокса, а в instance – його URL.

Моніторинг внутрішніх ендпоінтів

Чудово – ми сходили на Гугол, і він робить.

А як щодо перевірки ендпоінтів всередині кластеру?

Візьмемо приклад з nginx із документації Kubernetes, тільки задеплоїмо Под та Сервіс до власного неймпейсу, а не до default.

Створюємо неймспейс:

[simterm]

$ kk create ns test-ns
namespace/test-ns created

[/simterm]

Маніфест з подом та Сервісом, лише додаємо свій Namespace:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: test-ns
  labels:
    app.kubernetes.io/name: proxy
spec:
  containers:
  - name: nginx
    image: nginx:stable
    ports:
      - containerPort: 80
        name: http-web-svc
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: test-ns
spec:
  selector:
    app.kubernetes.io/name: proxy
  ports:
  - name: name-of-service-port
    protocol: TCP
    port: 80
    targetPort: http-web-svc

Деплоїмо:

[simterm]

$ kk apply -f testpod-with-svc.yaml 
pod/nginx created
service/nginx-service created

[/simterm]

Перевіряємо:

[simterm]

$ kk -n test-ns get all
NAME        READY   STATUS    RESTARTS   AGE
pod/nginx   1/1     Running   0          23s

NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/nginx-service   ClusterIP   10.106.58.247   <none>        80/TCP    23s

[/simterm]

Оновлюємо конфіг Blackbox:

serviceMonitor:
  enabled: true
  defaults:
    labels:
      release: prometheus
  targets:
    - name: google.com
      url: https://google.com
    - name: nginx-test
      url: nginx-service.test-ns.svc.cluster.local:80

Оновлюємо сетап екпортеру:

[simterm]

$ helm -n monitoring upgrade --install prometheus-blackbox prometheus-community/prometheus-blackbox-exporter -f blackbox-exporter-values.yaml

[/simterm]

Перевіряємо СервісМонітори ще раз:

[simterm]

$ kk -n monitoring get servicemonitor
NAME                                                          AGE
prometheus-blackbox-prometheus-blackbox-exporter-google.com   12m
prometheus-blackbox-prometheus-blackbox-exporter-nginx-test   5s

[/simterm]

І за хвилину – можемо перевіряти probe_success:

Взагалі, не обов’язково вказувати повний URL у вигляді nginx-service.test-ns.svc.cluster.local – достатньо буде servicename.namespace, тобто nginx-service.test-ns, але повний URL як на мене виглядає більш наочно в лейблах та алертах.

Модулі Blackbox Exporter

Все виглядає чудово, поки ми опитуємо звичайний HTTP-ендпоінт, який завжди віддає код 200.

Що як треба перевірити інші коди?

Створимо власний модуль, використвуючи probes Блекбоксу:

config:
  modules:
    http_4xx:
      prober: http
      timeout: 5s
      http:
        method: GET
        valid_status_codes: [404, 405]
        valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
        follow_redirects: true
        preferred_ip_protocol: "ip4"
serviceMonitor:
  enabled: true
  defaults:
    labels:
      release: prometheus
  targets:
    - name: google.com
      url: https://google.com
    - name: nginx-test
      url: nginx-service.test-ns.svc.cluster.local:80
    - name: nginx-test-404
      url: nginx-service.test-ns.svc.cluster.local:80/404
      module: http_4xx

Тут в modules задаємо ім’я нового модуля – http_4xx, який пробер він має викорисовувати – http, і параметри для цього пробера – яким саме запитом перевіряємо, і які коди відповіді будемо вважати правильними.

Далі, в Таргетах для nginx-test-404 явно вказуємо використання модулю http_4xx.

Тестування модулів

Окремо подивимось як саме можемо перевірити – чи буде модуль працювати так, як ми розраховуємо.

Все просто – запускаємо тестовий под, і curl-ом з опцією -I дивимось на відповідь ендпоінта.

Якшо перевіряємо TCP-коннект – то telnet.

Отже, створюємо под з Убунтою, підключаємось до нього – запускаємо всередені bash:

[simterm]

$ kk -n monitoring run pod --rm -i --tty --image ubuntu -- bash

[/simterm]

Встановлюємо curl та telnet:

[simterm]

root@pod:/# apt update && apt -y install curl telnet

[/simterm]

Та дивимось – чи працє URL nginx-service.test-ns.svc.cluster.local:80/404:

[simterm]

root@pod:/# curl -I nginx-service.test-ns.svc.cluster.local:80/404
HTTP/1.1 404 Not Found

[/simterm]

404 – як ми і очікували.

Оновлюємо Блекбокс з новим конфігом:

[simterm]

$ helm -n monitoring upgrade --install prometheus-blackbox prometheus-community/prometheus-blackbox-exporter -f blackbox-exporter-values.yaml

[/simterm]

Перевіримо його ConfigMap – чи додався модуль http_4xx, який ми вказали в нашому файлу конфіга:

[simterm]

$ kk -n monitoring get cm prometheus-blackbox-prometheus-blackbox-exporter -o yaml
apiVersion: v1
data:
  blackbox.yaml: |
    modules:
      http_2xx:
        http:
          follow_redirects: true
          preferred_ip_protocol: ip4
          valid_http_versions:
          - HTTP/1.1
          - HTTP/2.0
        prober: http
        timeout: 5s
      http_4xx:
        http:
          follow_redirects: true
          method: GET
          preferred_ip_protocol: ip4
          valid_http_versions:
          - HTTP/1.1
          - HTTP/2.0
          valid_status_codes:
          - 404
          - 405
        prober: http
        timeout: 5s

[/simterm]

І перевіряємо Прометеус:

probe_success{target="nginx-test-404"} == 1 – все робить.

TCP Connect і моніторинг баз данних

Ще один модуль, котрий дуже часто використовуємо – TCP, який просто намагається відкрити TCP-сессію на вказанний URL та порт. Підходить для перевірок баз данних та будь-яких інших не-HTTP-ресурсів.

Запустимо MySQL:

[simterm]

$ helm repo add bitnami https://charts.bitnami.com/bitnami
$ helm install mysql bitnami/mysql

[/simterm]

Знаходимо його Сервіс:

[simterm]

$ kk get svc
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
kubernetes       ClusterIP   10.96.0.1      <none>        443/TCP    20h
mysql            ClusterIP   10.99.71.124   <none>        3306/TCP   40s
mysql-headless   ClusterIP   None           <none>        3306/TCP   40s

[/simterm]

Оновлюємо конфіг Blackbox:

config:
  modules:
    ...
    tcp_connect:
      prober: tcp
serviceMonitor:
  ...
  targets:
    ...
    - name: mysql
      url: mysql.default.svc.cluster.local:3306
      module: tcp_connect

Деплоїмо, і перевіряємо:

Prometheus alerting

За альортінг особливо писати й нема чого – все стандартно, як будь-які інші алерти Прометеусу.

Наприклад ми моніторимо Сервіси Apache Druid з таким алертом (скрін з конфігу Terraform з деякими змінними):

Просто перевіряємо, що probe_success != 1.

Посилання по темі

Loading

Terraform: модулі, Outputs та Variables
0 (0)

28 Листопада 2022

Нарешті дійшов до модулей в Terraform, а саме – треба було розібратися, як між двома модулями передати значення змінних.

Далі – самі базові і прості приклади роботи з модулями та їх values && outputs.

Див. більше в документації – Modules.

Корневий модуль

Спочатку, створимо рутовий модуль, який просто створює локальний файл, і в якому далі будемо описувати модулі.

Створюємо тестовий каталог:

[simterm]

$ mkdir modules_example
$ cd modules_example/

[/simterm]

В ньому додаємо файл main.tf, в якому через resource типу local_file створюємо файл file.txt, в якому буде текст “file content“:

resource "local_file" "file" {
  content  = "file content"
  filename = "file.txt"
}

Запускаємо init, что б підтягнути необхідні модулі самого Тераформу:

[simterm]

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/local...
- Installing hashicorp/local v2.2.3...
- Installed hashicorp/local v2.2.3 (signed by HashiCorp)
...
Terraform has been successfully initialized!

[/simterm]

Потім plan – перевіряємо чи буде воно робити взагалі, та що саме:

[simterm]

$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.file will be created
  + resource "local_file" "file" {
      + content              = "file content"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "file.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

[/simterm]

Виглядає ОК – виконуємо apply:

[simterm]

$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.file will be created
  + resource "local_file" "file" {
      + content              = "file content"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "file.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

local_file.file: Creating...
local_file.file: Creation complete after 0s [id=87758871f598e1a3b4679953589ae2f57a0bb43c]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

[/simterm]

І перевіряємо зміст каталогу, в якому запускали:

[simterm]

$ ls -l
total 12
-rwxr-xr-x 1 setevoy setevoy  12 Nov 28 13:14 file.txt
-rw-r--r-- 1 setevoy setevoy  90 Nov 28 13:09 main.tf
-rw-r--r-- 1 setevoy setevoy 854 Nov 28 13:14 terraform.tfstate

[/simterm]

Та зміст файлу file.txt:

[simterm]

$ cat file.txt 
file content

[/simterm]

Робить, поїхали далі.

Модулі Terraform

Далі, переходимо власне до модулів.

Створюємо два каталоги під два модулі:

[simterm]

$ mkdir -p modules/file_1
$ mkdir -p modules/file_2

[/simterm]

В кожному створюємо свій main.tfmodules/file_1/main.tf та modules/file_2/main.tf.

В modules/file_1/main.tf використовуємо той же самий local_file для створення файлу file_1.txt:

resource "local_file" "file_1" {
  content  = "file_1 content"
  filename = "file_1.txt"
}

Аналогічно в modules/file_2/main.tf для file_2.txt:

resource "local_file" "file_2" {
  content  = "file_2 content"
  filename = "file_2.txt"
}

Оновлюємо корневий модуль, тобто modules_example/main.tf – видаляємо resource "local_file", і замість нього описуємо два модулі, в яких вказуємо шляхи до каталогів обох модулів:

module "file_1" {
  source = "./modules/file_1"
}

module "file_2" {
  source = "./modules/file_2"
}

Запускаеємо init знову, щоб Тераформ створив свою структуру модулей:

[simterm]

$ terraform init
Initializing modules...
- file_1 in modules/file_1
- file_2 in modules/file_2

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/local v2.2.3

Terraform has been successfully initialized!

[/simterm]

Перевіряємо в .terraform/modules/:

[simterm]

$ cat .terraform/modules/modules.json | jq 
{
  "Modules": [
    {
      "Key": "",
      "Source": "",
      "Dir": "."
    },
    {
      "Key": "file_1",
      "Source": "./modules/file_1",
      "Dir": "modules/file_1"
    },
    {
      "Key": "file_2",
      "Source": "./modules/file_2",
      "Dir": "modules/file_2"
    }
  ]
}

[/simterm]

Запускаємо plan:

[simterm]

$ terraform plan
local_file.file: Refreshing state... [id=87758871f598e1a3b4679953589ae2f57a0bb43c]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
  - destroy

Terraform will perform the following actions:

  # local_file.file will be destroyed
  # (because local_file.file is not in configuration)
  - resource "local_file" "file" {
      - content              = "file content" -> null
      - directory_permission = "0777" -> null
      - file_permission      = "0777" -> null
      - filename             = "file.txt" -> null
      - id                   = "87758871f598e1a3b4679953589ae2f57a0bb43c" -> null
    }

  # module.file_1.local_file.file_1 will be created
  + resource "local_file" "file_1" {
      + content              = "file_1 content"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "file_1.txt"
      + id                   = (known after apply)
    }

  # module.file_2.local_file.file_2 will be created
  + resource "local_file" "file_2" {
      + content              = "file_2 content"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "file_2.txt"
      + id                   = (known after apply)
    }

Plan: 2 to add, 0 to change, 1 to destroy.

[/simterm]

І теперь можна виконувати apply.

Щоб не вводити кожен раз “yes” – додаємо аргумент -auto-approve:

[simterm]

$ terraform apply -auto-approve
local_file.file: Refreshing state... [id=87758871f598e1a3b4679953589ae2f57a0bb43c]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
  - destroy

Terraform will perform the following actions:

  # local_file.file will be destroyed
  # (because local_file.file is not in configuration)
  - resource "local_file" "file" {
      - content              = "file content" -> null
      - directory_permission = "0777" -> null
      - file_permission      = "0777" -> null
      - filename             = "file.txt" -> null
      - id                   = "87758871f598e1a3b4679953589ae2f57a0bb43c" -> null
    }

  # module.file_1.local_file.file_1 will be created
  + resource "local_file" "file_1" {
      + content              = "file_1 content"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "file_1.txt"
      + id                   = (known after apply)
    }

  # module.file_2.local_file.file_2 will be created
  + resource "local_file" "file_2" {
      + content              = "file_2 content"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "file_2.txt"
      + id                   = (known after apply)
    }

Plan: 2 to add, 0 to change, 1 to destroy.
local_file.file: Destroying... [id=87758871f598e1a3b4679953589ae2f57a0bb43c]
module.file_2.local_file.file_2: Creating...
module.file_1.local_file.file_1: Creating...
local_file.file: Destruction complete after 0s
module.file_2.local_file.file_2: Creation complete after 0s [id=7225b36c22072cd558c23529d0d992c29cb873be]
module.file_1.local_file.file_1: Creation complete after 0s [id=82888a6ec6b05fc219759bd241ca5f0d6cba0e23]

Apply complete! Resources: 2 added, 0 changed, 1 destroyed.

[/simterm]

Перевіряємо, чи з’вились файли:

[simterm]

$ ll
total 24
-rwxr-xr-x 1 setevoy setevoy   14 Nov 28 13:21 file_1.txt
-rwxr-xr-x 1 setevoy setevoy   14 Nov 28 13:21 file_2.txt
-rw-r--r-- 1 setevoy setevoy  104 Nov 28 13:18 main.tf
drwxr-xr-x 4 setevoy setevoy 4096 Nov 28 13:15 modules

[/simterm]

Та їх зміст:

[simterm]

$ cat file_1.txt 
file_1 content

$ cat file_2.txt 
file_2 content

[/simterm]

Працює? Го далі.

Змінні в модулях Terraform

Тут нічого особливого – все теж саме, як зі звичайними змінними Terraform. Див. документацію – Input Variables.

В файлі modules_example/modules/file_1/main.tf об’являємо змінну user_name:

variable "user_name" {
  type = string
}    

resource "local_file" "file_1" {
  content  = "file_1 content from ${var.user_name}"
  filename = "file_1.txt"
}

Теж саме в другому модулі:

variable "user_name" {
  type = string
}    

resource "local_file" "file_2" {
  content  = "file_2 content from ${var.user_name}"
  filename = "file_2.txt"
}

Оновлюємо рутовий модуль – передаємо значення user_name для обох модулів:

module "file_1" {
  user_name = "user1"
  source = "./modules/file_1"
}

module "file_2" {
  user_name = "user2"
  source = "./modules/file_2"
}

Аплаїмо:

[simterm]

$ terraform apply -auto-approve
module.file_1.local_file.file_1: Refreshing state... [id=82888a6ec6b05fc219759bd241ca5f0d6cba0e23]
module.file_2.local_file.file_2: Refreshing state... [id=7225b36c22072cd558c23529d0d992c29cb873be]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # module.file_1.local_file.file_1 must be replaced
-/+ resource "local_file" "file_1" {
      ~ content              = "file_1 content" -> "file_1 content from user1" # forces replacement
      ~ id                   = "82888a6ec6b05fc219759bd241ca5f0d6cba0e23" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

  # module.file_2.local_file.file_2 must be replaced
-/+ resource "local_file" "file_2" {
      ~ content              = "file_2 content" -> "file_2 content from user2" # forces replacement
      ~ id                   = "7225b36c22072cd558c23529d0d992c29cb873be" -> (known after apply)
        # (3 unchanged attributes hidden)
    }
...
Apply complete! Resources: 2 added, 0 changed, 2 destroyed.

[/simterm]

І перевіряємо:

[simterm]

$ cat file_1.txt 
file_1 content from user1

[/simterm]

Модулі та Output значень змінних

Що як нам треба із модуля передати значення змінної в рутовий модуль? Використовуємо outputs – див. документацію Output Values.

Оновлюємо перший модуль – додаємо output з іменем “file_content“:

variable "user_name" {
  type = string
}    

resource "local_file" "file_1" {
  content  = "file_1 content from ${var.user_name}"
  filename = "file_1.txt"
} 

output "file_content" {
  value = file("file_1.txt")
}

Теж саме в другому:

variable "user_name" {
  type = string
}    

resource "local_file" "file_2" {
  content  = "file_2 content from ${var.user_name}"
  filename = "file_2.txt"
} 

output "file_content" {
  value = file("file_2.txt")
} 

І далі в рутовому модулі використовуємо отриманні значення за допомогою module.<MODULE_NAME>.<OUTPUD_NAME> для створення файлу concat_file.txt:

module "file_1" {
  user_name = "user1"
  source = "./modules/file_1"
}

module "file_2" {
  user_name = "user2"
  source = "./modules/file_2"
}

resource "local_file" "concat_file" {
  content  = "${module.file_1.file_content}\n${module.file_2.file_content}\n"
  filename = "concat_file.txt"
}

Запускаємо apply:

[simterm]

$ terraform apply -auto-approve
module.file_2.local_file.file_2: Refreshing state... [id=5771aa8b1046de4f4342d492faee20e3c289365d]
module.file_1.local_file.file_1: Refreshing state... [id=8a3552bfa2e46f311e7b718f8eed71b69bb8115f]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.concat_file will be created
  + resource "local_file" "concat_file" {
      + content              = <<-EOT
            file_1 content from user1
            file_2 content from user2
        EOT
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "concat_file.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.
local_file.concat_file: Creating...
local_file.concat_file: Creation complete after 0s [id=a4e1240fb9cdc96fffc1b0984392f6218422d194]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

[/simterm]

Перевіряємо:

[simterm]

$ cat concat_file.txt 
file_1 content from user1
file_2 content from user2

[/simterm]

Передача значень змінних між модулями

І, нарешті, те, з чого почали – як передати значення з одного модулю в інший?

В файлі main.tf першого модулю додаємо змінну з іменем module_1_value, якій задаємо дефолтне значення “module 1 value“:

...
variable "module_1_value" {
  type = string
  default = "module 1 value"
}
...

Там же створюємо другу змінну module_2_value, в яку потім передамо значення з другого модулю:

...
variable "module_2_value" {
  type = string
}
...

Додаємо output щоб повернути значення змінної module_1_value в рутовий модуль для подальшого використання в другому модулі:

...
output "module_1_value" {
  value = var.module_1_value
}
...

Повністю файл тепер виглядає так:

variable "user_name" {
  type = string
} 
  
variable "module_1_value" {
  type = string
  default = "module 1 value"
} 
  
variable "module_2_value" {
  type = string
}

resource "local_file" "file_1" {
  content  = "file_1 content from ${var.user_name} with value from file_2: ${var.module_2_value}"
  filename = "file_1.txt"
}

output "file_content" {
  value = file("file_1.txt")
}

output "module_1_value" {
  value = var.module_1_value
}

Тут в ресурсі local_file будемо використовувати значення, передане з другого модулю.

Повторюємо теж саме для модулю file_2:

variable "user_name" {
  type = string
} 
  
variable "module_2_value" {
  type = string
  default = "module 2 value"
} 
  
variable "module_1_value" {
  type = string
}

resource "local_file" "file_2" {
  content  = "file_2 content from ${var.user_name} with value from file_1: ${var.module_1_value}"
  filename = "file_2.txt"
}

output "file_content" {
  value = file("file_2.txt")
}

output "module_2_value" {
  value = var.module_2_value
}

Повертаємось до корневого модулю та додаємо передачу змінної module_2_value в першому модулі, та змінної module_1_value до другого модулю:

module "file_1" {
  user_name = "user1"
  module_2_value = module.file_2.module_2_value
  source = "./modules/file_1"
}   
  
module "file_2" {
  user_name = "user2"
  module_1_value = module.file_1.module_1_value
  source = "./modules/file_2"
} 

resource "local_file" "concat_file" {
  content  = "${module.file_1.file_content}\n${module.file_2.file_content}\n"
  filename = "concat_file.txt"
}

Теперь в файлі file_1.txt маємо отримати значення із module "file_2", і навпаки.

Запускаємо:

[simterm]

$ terraform apply -auto-approve
module.file_1.local_file.file_1: Refreshing state... [id=d74b0e0b3296ab02e7b4791942d983b175d071b4]
local_file.concat_file: Refreshing state... [id=e0e88293b53693a484db146b15bd2ab3d8f4d250]
module.file_2.local_file.file_2: Refreshing state... [id=12faf027dc96d886c6022b54c878324a21d3d112]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # local_file.concat_file must be replaced
-/+ resource "local_file" "concat_file" {
      ~ content              = <<-EOT # forces replacement
          - file_1 content from user1 with value from file_2: variable 2 value
          - file_2 content from user2 with value from file_1: variable 1 value
          + file_1 content from user1 with value from file_2: module 2 value
          + file_2 content from user2 with value from file_1: module 1 value
        EOT
      ~ id                   = "e0e88293b53693a484db146b15bd2ab3d8f4d250" -> (known after apply)
        # (3 unchanged attributes hidden)
    }

Plan: 1 to add, 0 to change, 1 to destroy.
local_file.concat_file: Destroying... [id=e0e88293b53693a484db146b15bd2ab3d8f4d250]
local_file.concat_file: Destruction complete after 0s
local_file.concat_file: Creating...
local_file.concat_file: Creation complete after 0s [id=648854841581b273d26646fff776b33fdf060a19]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

[/simterm]

Перевіряємо результат:

[simterm]

$ cat concat_file.txt 
file_1 content from user1 with value from file_2: module 2 value
file_2 content from user2 with value from file_1: module 1 value

[/simterm]

Готово.

Loading

Kubernetes: ServiceAccount з AWS IAM Role для Kubernetes Pod
0 (0)

25 Листопада 2022

Маємо Grafana Loki для логів, до подів якої треба підключити AWS IAM Role з AWS IAM Policy, котра дає доступ до AWS S3 бакету, в якому будуть зберігатися чанки та індекси (про сетап самої Loki з AWS S3 трохи пізніше окремим постом).

IAM ролі для Kubernetes подів працють тим самим чином, як ми це робимо, коли підключаємо IAM-ролі до ЕС2-інстансів – процес всередині пода виконує запит до AWS API, а AWS SDK чи AWS CLI, за допомогою якого робиться запит, виконує запит AssumeRole, якому передається сама IAM Role (див. AWS: ротация ключей IAM пользователей, EC2 IAM Roles и Jenkins).

Щоб перевірити, як воно взагалі буде працювати в Кубері – створимо тестову IAM Role, ServiceAccount з аннотацією цієї ролі, та запустимо под з цим ServiceAccount.

Забігаючи наперед – в самій Локі це все одно працює через якусь альтернативну реальність, тобто навіть коли їй підключаєш вже протестований ServiceAccount – вона валиться з помилками. Треба буде копати.

Документація – IAM roles for service accounts.

Перевірка IAM OIDC identity provider

EKS кластер розгорнутий за допомогою Terraform модуля aws_eks_cluster, і OpenID Connect (OIDC) provider вже має бути налаштований.

Заходимо на сторінку кластера, знаходимо OpenID Connect provider URL:

Або з консолі:

[simterm]

$ aws --profile development --region us-west-2 eks describe-cluster --name dev_data_services --query "cluster.identity.oidc.issuer" --output text
https://oidc.eks.us-west-2.amazonaws.com/id/537***A10

[/simterm]

Копіюємо URL без https://, та перевіряємо в IAM > Identity providers:

Окей, тут все є.

Створення Kubernetes ServiceAccount з AWS IAM role

Для початку, створимо IAM policy, яка дає права на AWS S3 bucket, потім IAM Role з TrustedPolicy, яка дозволяє виконувати AssumeRole, використовуючи OIDC identity provider (IDP) кластеру.

AWS IAM policy

Створюємо тестову політику:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::development-dev-loki-object-store",
                "arn:aws:s3:::development-dev-loki-object-store/*"
            ]
        }
    ]
}

Додаємо її в AWS IAM:

[simterm]

$ aws --profile development iam create-policy --policy-name test-iam-sa-pod-policy --policy-document file://test-iam-sa-pod-policy.json

[/simterm]

Переходимо до ролі.

AWS IAM role та TrustedPolicy

Знаходимо ARN нашого Identity Provier:

URL OIDC identity provider вже знаходили раніше:

[simterm]

$ aws --profile development --region us-west-2 eks describe-cluster --name dev_data_services --query "cluster.identity.oidc.issuer" --output text 
https://oidc.eks.us-west-2.amazonaws.com/id/537***A10

[/simterm]

Створення IAM Role з AWS CLI

Створюємо файл з TustedPolicy, в Principal якої вказуємо ARN IDP, а в Condition – URL OIDC кластеру, якому дозволяємо виконувати запит до sts.amazonaws.com:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::638***021:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/537***A10"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.us-west-2.amazonaws.com/id/537***A10:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}

Створюємо роль, якій передаємо цю TrustedPolicy:

[simterm]

$ aws --profile development  iam create-role --role-name test-iam-sa-pod-role --assume-role-policy-document file://test-iam-sa-role-trusted-policy.json

[/simterm]

Підключаємо їй IAM Policy з правами на S3, яку створили раніше:

[simterm]

$ aws --profile development iam attach-role-policy --role-name test-iam-sa-pod-role --policy-arn=arn:aws:iam::638***021:policy/test-iam-sa-pod-policy

[/simterm]

Створення IAM Role з AWS Console

Або робимо теж саме через адмінку Амазона – вибираємо тип Web identity, Identity Provider кластеру та Audience:

Додаємо IAM Policy для S3:

Зберігаємо:

Копіюємо ARN ролі:

Створення Kubernetes ServiceAccount

Створюємо маніфест ServiceAccount-у, в анотації якого вказуємо ARN ролі, яку створили:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-iam-sa-pod-service-account
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::638***021:role/test-iam-sa-pod-role

І переходимо до пода.

Запуск Kubernetes Pod з ServiceAccount

Додаємо опис поду, якому через serviceAccountName підключаємо ServiceAccount, тож повністю маніфест буде виглядати так:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-iam-sa-pod-service-account
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::638***021:role/test-iam-sa-pod-role
---
apiVersion: v1
kind: Pod
metadata:
  name: test-iam-sa-pod
  labels:
    app: test-iam-sa-app
spec:
  containers:
    - name: test-iam-sa
      image: amazon/aws-cli
      command: [ "/bin/bash", "-c", "--" ]
      args: [ "while true; do sleep 30; done;" ]
  serviceAccountName: test-iam-sa-pod-service-account

В поді використовємо docker-образ з AWS CLI, якому передаємо sleep, щоб він працював після запуску.

Деплоїмо ServiceAccount та Pod:

[simterm]

$ kk apply -f test-iam-sa-pod.yaml 
serviceaccount/test-iam-sa-pod-service-account created
pod/test-iam-sa-pod created

[/simterm]

Заходимо в нього:

[simterm]

$ kk exec -ti test-iam-sa-pod -- bash

[/simterm]

І перевіряємо доступ до корзини:

[simterm]

bash-4.2# aws s3 ls development-dev-loki-object-store
                           PRE test/

[/simterm]

Готово.

Loading

Linux: збереження заряду батареї ноутбуку
0 (0)

18 Листопада 2022

На цей раз стало мені цікаво – а чи можна якось поекономити заряд батерії ноутбука? Не сказати, що швидко разряжається – на 5-6 годин роботи вистачає, але зайвим не буде.

Знайшов декілька утіліт, про них сьогодні й запишу.

Upower

Перша утілітка – upower:

[simterm]

$ sudo pacman -S upower

[/simterm]

Спочатку можна визвати з опцією --monitor-detail – буде в ріалтаймі виводити інформацію про стан батареї:

[simterm]

$ $ sudo upower --monitor-detail
Monitoring activity from the power daemon. Press Ctrl+C to cancel.
[20:42:20.071]  device changed:     /org/freedesktop/UPower/devices/battery_BAT0
  native-path:          BAT0
  vendor:               SMP
  model:                LNV-5B10W13895
  serial:               2056
  power supply:         yes
  updated:              Fri Nov 18 20:42:20 2022 (0 seconds ago)
  has history:          yes
  has statistics:       yes
  battery
    present:             yes
    rechargeable:        yes
    state:               charging
    warning-level:       none
    energy:              36.7 Wh
    energy-empty:        0 Wh
    energy-full:         0 Wh
    energy-full-design:  0 Wh
    energy-rate:         15.538 W
    voltage:             12.391 V
    charge-cycles:       N/A
    time to full:        22.7 minutes
    percentage:          86%
    technology:          lithium-polymer
    icon-name:          'battery-full-charging-symbolic'
  History (charge):
    1668796940  86.000  charging
    1668796850  85.000  charging
  History (rate):
    1668796940  15.538  charging
    1668796910  15.784  charging
    1668796880  16.006  charging
    1668796850  16.253  charging

[/simterm]

Відразу бачимо модель батареї ноута – LNV-5B10W13895, та її стан – кількість ватт-годин (36.7 Wh), та скількі споживає зараз (15.538 W), і на якій потужності – 12.391 V.

Можна порахувати в ампер-годинах:

[simterm]

>>> 37.31/12.3
3.03

[/simterm]

Тобто зараз 3033 mAh заряду в батареї.

Заодно побачили сам девайс батареї – /org/freedesktop/UPower/devices/battery_BAT0.

Отримати інфо про неї без моніторингу, а один раз – опція -i з пристроєм – sudo upower -i /org/freedesktop/UPower/devices/battery_BAT0.

ACPI

Друга, аналогічна утіліта – acpi, але ще вміє відображати температуру (і не тільки – див. About ACPI):

[simterm]

$ sudo pacman -S acpi

[/simterm]

Виводимо інформацію щодо батареї:

[simterm]

$ acpi -i
Battery 0: Charging, 91%, 00:21:40 until charged
Battery 0: design capacity 3649 mAh, last full capacity 3431 mAh = 94%

[/simterm]

“design capacity 3649 mAh” – все майже як рахували вище, тільки там брали поточний заряд, який зараз десь 94% від design capacity.

Batstat

Третя корисна річ – batstat. Простенька, але може стати в нагоді.

Качаємо репозіторій, збираємо:

[simterm]

$ git clone https://github.com/Juve45/batstat.git
$ cd batstat
$ make
$ chmod +x batstat
$ sudo mv batstat /usr/local/bin/

[/simterm]

І запускаємо:

Powertop

Powertop вміє не просто виводити інформацю про батарею, але й по-перше – виводити інформацю про процеси, які споживать енергію, по-друге – може виконати налаштування для економії заряду.

[simterm]

$ sudo pacman -S powertop

[/simterm]

Запускаємо під рутом:

[simterm]

$ sudo powertop

[/simterm]

В табі Tunables виводиться інформація по налаштуванням, які можуть оптимізувати споживання енергії.

Вибираємо потрібний пункт, Enter – і опція переключається:

Вміє генерувати html:

[simterm]

$ sudo powertop --html=powerreport.html

[/simterm]

І результат:

Можна додати systemd-сервіс, який буде виконувати автоматичний тюнінг при старті системи.

Створюємо файл /etc/systemd/system/powertop.service:

[Unit]
Description=Powertop tunings

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/powertop --auto-tune

[Install]
WantedBy=multi-user.target

Та активуємо:

[simterm]

$ sudo systemctl start powertop.service
$ sudo systemctl enable powertop.service

[/simterm]

TLP

Це просто монстр. Краще почитати більше інформації тут>>> та тут>>>, а зараз про основні можливості.

Встановлюємо:

[simterm]

$ sudo pacman -S tlp
$ sudo pacman -S tlp-rdw

[/simterm]

Є графічний інтерфейс:

[simterm]

$ yay -S tlpui
$ sudo tlpui

[/simterm]

Під капотом вже має всі налаштування, що робить powertop, тож powertop.service краще вікдлючити.

Стартуємо сервіс:

[simterm]

$ sudo systemctl enable tlp.service

[/simterm]

І блокуємо (mask) сервіси, які можуть завадити tlp:

[simterm]

$ sudo systemctl mask systemd-rfkill.service
$ sudo systemctl mask systemd-rfkill.socket

[/simterm]

Файл налаштувань – /etc/tlp.conf, в каталозі /etc/tlp.d/ можна задавати свої параметри.

Вивести параметри, що є зараз:

[simterm]

$ sudo tlp-stat
--- TLP 1.5.0 --------------------------------------------

+++ Configured Settings:
defaults.conf L0004: TLP_ENABLE="1"
defaults.conf L0005: TLP_WARN_LEVEL="3"
defaults.conf L0006: TLP_PERSISTENT_DEFAULT="0"
defaults.conf L0007: DISK_IDLE_SECS_ON_AC="0"
...

[/simterm]

Виводить прям купу всього, що стосується заліза та опцій по оптимізації, і все це можна налаштувати під себе.

Загальні поради

Ну і не забуваємо про самі базові речі.

Яскравість екрану та девайси

Перш за все це, звичайно, яскравість екрану – знизити кнопками Fn +/-brightness, або через файл, в мене це /sys/class/backlight/amdgpu_bl0/brightness:

[simterm]

$ sudo bash -c  'echo -n 25 > /sys/class/backlight/amdgpu_bl0/brightness'

[/simterm]

Ще можна відключити непотрібні USB-девайси, такі як миша/клава/зовнішній диск, та Bluetooth чи/або WiFi, якщо підключені кабелем.

Сервіси

Також має сенс перевірити сервіси в автостарті:

[simterm]

$ systemctl list-unit-files --state=enabled
UNIT FILE                          STATE   PRESET  
acpid.service                      enabled disabled
bluetooth-autoconnect.service      enabled disabled
bluetooth.service                  enabled disabled
gdm.service                        enabled disabled
[email protected]                     enabled enabled 
NetworkManager-dispatcher.service  enabled disabled
NetworkManager-wait-online.service enabled disabled
NetworkManager.service             enabled disabled
pritunl-client.service             enabled disabled
sshd.service                       enabled disabled
tlp.service                        enabled disabled
remote-fs.target                   enabled enabled 

[/simterm]

Bluetooth мені постійно не потрібен, рідко його включаю, теж саме з Притунлом.

tick_sched_timer та Polybar

Ще виявив цікаву річ – tick_sched_timer, шедулєр ядра, з’їдав аж 1.7W:

Погуглив, знайшов цей тред, прибив процес Polybar, який зверху виводить всяку інформацю по системі, використовуючи власні модулі – і tick_sched_timer почав споживати лише 150-300 mW.

Networking: коли немає світла – модем 4G ZTE + зовнішня антена
0 (0)

13 Листопада 2022

Що робити, коли немає світла, вежі мобильного зв’язку відключаються, а подивитися відосиків з нашими котиками хочеться? Правильно – купити собі 3/4G модем з антеною!

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

Купував комплект 4G Zte Mf79U + Квадрат Mimo – цікавий магазин (не реклама), і продають відразу комплектами, що дуже зручно для таких як я, які не шарять в тому, як його вибирати, особливо коли діло стосується самої антени.

Окрема подяка @Artem за ідею та поміч при налаштуванні)

Сам комплект виглядає так:

Networking: модем 4G ZTE + зовнішня антена - коли немає світла

Підключення модему

Вставляємо карту micro-SIM в порт карти, я взяв карточку Водафона – він тут наче краще всіх робить, коли вирубає вишки.

Другий – під карту памя’ті:

Пароль та SSID є на девайсі:

Вставляємо в комп/ноут:

Сигнали описані в інструкції, кратко:

  • зверху – WiFi, знизу – мобільний
  • мобільний блимає зеленим – підключено до 3G, трафік йде
  • мобільний блимає блакитним – підключено до 4G, трафік йде

Заходимо на http://192.168.0.1:

Переходимо до налаштувань WiFi:

За бажанням – міняємо SSID та пароль на свої:

Сигнал модему навіть кращий за домашній роутер, який стоїть поруч – setevoy-linksys:

Переходимо до антени.

Підключення антени

Готуємо кабелі.

В комплекті йде один довгий, розрізаємо на дві однакові частини.

На кінцівках надрізаємо пластик, обережно, бо під ним обмотка. Знімаємо верхній шар ізоляції:

Мідну обмотку під ним разом з алюмінюєвий екраном загортаємо донизу, надрізаємо внутрішню ізоляцю, та оголяємо внутрішній мідний стержень:

 

Накручуємо конектор (всередені різьба) – готово:

З витою парою гемору більше 🙂

Повторюмо для всіх чотирьох, и врешті-решт підключаємо всю систему:

Розміщення антени

Встановлюємо на телефон утілітку Network Cell Info Lite & Wifi – показує найближчі вежі, до яких конектиться телефон.

В мене вишка Водафону як раз навпроти вікон, тож і антену розміщуємо “обличчям” до вежі:

Сила сигналу та тест швидкості

Краще антену взагалі виносити за вікно, бо, сюрпрайз – навіть склопакет впливає на силу сигналу.

Отже, за допомогою Network Cell Info знаходимо напрямок вежі, виставляємо антену в її напрямку, і перевіряємо сигнал, прямо на стартовій сторінці адміністрування самого модему:

-91 decibel-milliwatts (децибел-мілівати) – не вражає, але враховуючи, що я живу за містом, і вишка від мене приблизно в трьох кілометрах – то не так вже й погано.

Швидкість до відключення живлення “на районі”:

І під час вимкнення свіла:

Швидкість не найкраща, але для роботи/месенджерів та навіть Ютубу вистачає.

При цьому мобільний інтернет на телефоні не працює взагалі – Speedtest навіть не стартує, видає помилку.

В самому Києві результати набагато краще – кажуть, що 2Мбс на загрузку видає стабильно.

Або навіть так – Оболонь, з таким же комплектом обладнання:

Столичні мажори 😀

Google: прибрати сайти з .RU із результатів пошуку
0 (0)

18 Жовтня 2022

Хочеться прибрати россійські сайти із результатів пошуку в Гуголі, тож нагуглив декілька варіантів.

Перші два – “ручні”, через додавання параметру ?lr=-lang_ru або ?cr=-countryRU в URL Гугла, тобто виглядатиме він як https://google.com.ua/?lr=-lang_ru:

 

Але ж це мануальщина…

Нажаль, в Хромі автоматизувати не вийшло – вирізає цю частину при виконанні запиту:

Хоча, можливо, не вмію готувати.

Проте, існує інше рішення – плагін uBlacklist.

Встановлюємо, задаємо регулярку на кшталт *://*.ru/*:

Перевіряємо:

Та перевірити що саме було скрито із результатів пошуку – uBlacklist has blocked 3 sites Show:

Плюс, uBlacklist додає можливість банити сайти вручну – дуже часто хотілося, але не знав як: