Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

8 квітня на конференції Saint HighLoad++ 2019, в рамках секції «DevOps та експлуатація», прозвучала доповідь «Розширюємо та доповнюємо Kubernetes», у створенні якої брали участь три співробітники компанії «Флант». У ньому ми розповідаємо про численні ситуації, в яких нам хотілося розширити та доповнити можливості Kubernetes, але для чого ми не знаходили готового та простого рішення. Необхідні рішення у нас з'явилися у вигляді Open Source-проектів, і їм також присвячено цей виступ.

За традицією раді уявити відео з доповіддю (50 хвилин, набагато інформативніше статті) і основне вичавлення в текстовому вигляді. Поїхали!

Ядро та доповнення в K8s

Kubernetes змінює галузь та підходи до адміністрування, які давно устоялися:

  • завдяки його абстракційМи оперуємо вже не такими поняттями, як налаштування конфіга або запуск команди (Chef, Ansible…), а користуємося угрупуванням контейнерів, сервісами тощо.
  • Ми можемо готувати програми, не замислюючись про нюанси тієї конкретного майданчика, на якій його буде запущено: bare metal, хмара одного з провайдерів і т.п.
  • З K8s як ніколи стали доступні кращі практики з організації інфраструктури: техніки масштабування, самовідновлення, стійкості до відмови і т.п.

Однак, зрозуміло, все не так гладко: з Kubernetes прийшли і свої нові виклики.

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

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

У ядрі Kubernetes визначається базовий набір примітивів – для угруповання контейнерів, керування трафіком тощо. Докладніше про них ми розповідали у доповіді 2-річної давності.

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

З іншого боку, K8s пропонує чудові можливості розширення доступних функцій, що допомагають закрити й інші. специфічні - Потреби користувачів. За доповнення в Kubernetes відповідають адміністратори кластерів, які мають встановити та налаштувати все необхідне для того, щоб їх кластер «набув потрібну форму» [для вирішення їх специфічних завдань]. Що ж це за такі доповнення? Розглянемо деякі приклади.

приклади доповнень

Встановивши Kubernetes, ми можемо здивуватися, що мережа, яка так необхідна для взаємодії pod'ів як у рамках вузла, так і між вузлами, сама по собі не працює. Ядро Kubernetes не гарантує потрібні зв'язки — натомість воно визначає мережевий інтерфейс (CNI) для сторонніх доповнень. Ми повинні встановити одне з таких доповнень, яке відповідатиме за конфігурацію мережі.

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

Близький приклад – рішення для зберігання даних (локальний диск, мережевий блоковий пристрій, Ceph…). Спочатку вони були в ядрі, але з появою CSI ситуація змінюється на аналогічну вже описаній: у Kubernetes інтерфейс, а його реалізація - у сторонніх модулях.

Серед інших прикладів:

  • Вхід-контролери (їх огляд див. нашій недавній статті).
  • cert-менеджер:

    Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

  • Оператори - Це цілий клас доповнень (до яких відноситься і згаданий cert-manager), вони визначають примітив(и) та контролер(и). Логіка їх роботи обмежена лише нашою фантазією і дозволяє перетворювати готові компоненти інфраструктури (наприклад, СУБД) на примітиви, працювати з якими набагато простіше (ніж із набором із контейнерів та їх налаштувань). Операторів написано безліч - нехай багато з них ще і не готові до production, це лише питання часу:

    Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

  • Метрики — ще одна ілюстрація, як у Kubernetes відокремили інтерфейс (Metrics API) від реалізації (сторонні доповнення, такі як Prometheus adapter, Datadog cluster agent…).
  • Для моніторингу та статистики, де на практиці потрібні не тільки Prometheus та Grafana, а й kube-state-metrics, node-exporter тощо.

І це далеко не повний список доповнень… Наприклад, ми в компанії «Флант» на кожен Kubernetes-кластер на сьогодні встановлюємо 29 доповнень (Всі вони загалом створюють 249 об'єктів Kubernetes). Простіше кажучи, ми не бачимо життя кластеру без додатків.

Автоматизація

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

  1. Є приватний (тобто потребує логіна) registry з образами додатку. Передбачається, що кожному pod'у прив'язується спеціальний секрет, що дозволяє аутентифікуватись у registry. Наше завдання - забезпечити знаходження цього секрету в namespace'і, щоб pod'и могли завантажувати образи. Додатків (кожному з яких потрібен секрет) може бути дуже багато, а самі секрети корисно регулярно оновлювати, тому варіант з розкладанням секретів руками відпадає. Тут і приходить на допомогу оператор: ми створюємо контролер, який чекатиме появи namespace'а і за цією подією додасть секрет в namespace.
  2. Нехай за замовчуванням доступ із pod'ів до Інтернету заборонено. Але іноді він може вимагатися: логічно, щоб механізм дозволу доступу працював просто, не вимагаючи специфічних навичок, наприклад, за наявності певного лейбла в namespace'і. Як нам допоможе оператор? Створюється контролер, який очікує на появу лейбла в namespace'і і додає відповідний policy для доступу в інтернет.
  3. Схожа ситуація: нехай нам потрібно додавати на вузол певний заплямуватиякщо на ньому є аналогічний лейбл (з якимось префіксом). Дії з оператором очевидні.

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

Підсумовуючи всі описані історії, ми дійшли висновку, що для комфортної роботи в Kubernetes потрібно: а) встановлювати доповнення, б) розробляти оператори (Для вирішення повсякденних адмінських завдань).

Як написати оператор для Kubernetes?

Загалом схема проста:

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

…але тут з'ясовується, що:

  • Kubernetes API – досить нетривіальна річ, яка потребує чимало часу для освоєння;
  • програмування теж не для кожного (мова Go обрана як краща, тому що для неї є спеціальний фреймворк — Operator SDK);
  • з фреймворком як аналогічна ситуація.

Підсумок: для написання контролера (оператора) доводиться витратити суттєві ресурси вивчення матчасти. Це було б виправдано для великих операторів — скажімо, для СУБД MySQL. Але якщо ми пригадаємо описані вище приклади (розкладання секретів, доступ pod'ів в інтернет…), які хочеться теж робити правильно, то ми зрозуміємо, що зусилля, що витрачаються, переважать потрібний зараз результат:

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

Загалом виникає дилема: витратити багато ресурсів і знайти правильний інструмент для написання операторів або діяти «по-старому» (але швидко). Для її вирішення — знаходження компромісу між цими крайнощами ми створили свій проект: shell-operator (див. також його недавній анонс на хабрі).

Shell-operator

Як він працює? У кластері є pod, у якому лежить Go-бінарник із shell-operator. Поруч із ним зберігається набір хуків (Докладніше про них - див. нижче). Сам shell-operator підписується на певні події у Kubernetes API, за фактом настання яких він запускає відповідні хуки.

Як shell-operator розуміє, які хуки за яких подій викликати? Цю інформацію передають shell-operator'у самі хуки і роблять вони це дуже просто.

Хук - це скрипт на Bash або будь-який інший файл, що виконується, який підтримує єдиний аргумент --config та у відповідь на нього видає JSON. Останній визначає, які об'єкти його цікавлять і які події (для цих об'єктів) слід реагувати:

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

Проілюструю реалізацію на shell-operator одного з наших прикладів - розкладання секретів для доступу до приватного Registry з образами програми. Вона складається із двох етапів.

Практика: 1. Пишемо хук

Насамперед у хуку обробимо --config, Вказавши, що нас цікавлять namespace'и, а конкретно - момент їх створення:

[[ $1 == "--config" ]] ; then
  cat << EOF
{
  "onKubernetesEvent": [
    {
      "kind": "namespace",
      "event": ["add"]
    }
  ]
}
EOF
…

Як виглядатиме логіка? Теж досить просто:

…
else
  createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)
  kubectl create -n ${createdNamespace} -f - << EOF
Kind: Secret
...
EOF
fi

Першим кроком ми дізнаємося, який namespace був створений, а другим - створюємо через kubectl секрет цього простору імен.

Практика: 2. Збираємо образ

Залишилося передати створений хук shell-operator'у - як це зробити? Сам shell-operator поставляється у вигляді Docker-образу, так що наше завдання – додати хук до спеціального каталогу в цьому образі:

FROM flant/shell-operator:v1.0.0-beta.1
ADD my-handler.sh /hooks

Залишиться зібрати його та push'нути:

$ docker build -t registry.example.com/my-operator:v1 .
$ docker push registry.example.com/my-operator:v1

Фінальний штрих – задеплоїти образ у кластер. Для цього напишемо розгортання:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-operator
spec:
  template:
    spec:
      containers:
      - name: my-operator
        image: registry.example.com/my-operator:v1 # 1
      serviceAccountName: my-operator              # 2

У ньому потрібно звернути увагу на два моменти:

  1. вказівку щойно створеного образу;
  2. це системний компонент, якому (як мінімум) потрібні права на те, щоб підписатися на події в Kubernetes і щоб розкладати секрети по namespace'ам, тому ми створюємо для хука ServiceAccount (і набір правил).

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

Інші можливості shell-operator

Щоб обмежити об'єкти вибраного вами типу, з якими працюватиме хук, їх можна фільтрувати, відбираючи за певними лейблами (або за допомогою matchExpressions):

"onKubernetesEvent": [
  {
    "selector": {
      "matchLabels": {
        "foo": "bar",
       },
       "matchExpressions": [
         {
           "key": "allow",
           "operation": "In",
           "values": ["wan", "warehouse"],
         },
       ],
     }
     …
  }
]

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

При виклику хука shell-operator передає йому дані про об'єкт, які можуть бути використані для будь-яких потреб.

Події, при настанні яких викликаються хуки, не обмежені Kubernetes events: у shell-operator передбачена підтримка виклику хуків за часом (аналогічно crontab у традиційному планувальнику), а також спеціальної події onStartup. Всі ці події можуть комбінуватися і призначатися на той самий хук.

І ще дві особливості shell-operator:

  1. Він працює асинхронно. З моменту отримання події Kubernetes (наприклад, створення об'єкта) у кластері могли відбутися й інші події (наприклад, видалення того самого об'єкта), і це необхідно враховувати в хуках. Якщо хук виконався з помилкою, то за умовчанням він буде повторно викликати до успішного завершення (цю поведінку можна змінити).
  2. Він експортує метрики для Prometheus, за допомогою яких можна зрозуміти, чи працює shell-operator, дізнатися кількість помилок по кожному хуку та поточний розмір черги.

Підсумовуючи цю частину доповіді:

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

установка доповнень

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

Роботу з Kubernetes ми розпочинали з кількох кластерів, єдиним доповненням у яких був Ingress. У кожний кластер його потрібно було ставити по-різному, і ми зробили кілька YAML-конфігурацій для різних оточень: bare metal, AWS…

Кластерів ставало більше – більше ставало і змін. Крім того, ми покращували самі ці конфігурації, внаслідок чого вони стали досить різноманітними:

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

Щоб упорядкувати все, ми почали зі скрипту (install-ingress.sh), який приймав аргументом тип кластера, в який будемо деплоїтись, генерував потрібну YAML-конфігурацію і викочував її в Kubernetes.

Якщо коротко, то подальший наш шлях і пов'язані з ним міркування були такі:

  • для роботи з YAML-конфігураціями потрібен шаблонизатор (на перших етапах це простий sed);
  • зі зростанням числа кластерів прийшла необхідність для автоматичного оновлення (найраніше рішення - поклали скрипт в Git, по cron'у його оновлюємо і запускаємо);
  • Такий скрипт був потрібний для Prometheus (install-prometheus.sh), проте він примітний тим, що вимагає набагато більше вступних даних, а також їх зберігання (по-хорошому централізоване і в кластері), причому деякі дані (паролі) можна було автоматично генерувати:

    Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

  • ризик викотити щось неправильне на зростаючу кількість кластерів постійно зростав, тому ми зрозуміли, що інсталяторам (тобто двом скриптам: для Ingress та Prometheus) знадобилося стейджування (кілька гілок у Git, кілька cron'ів на їх оновлення у відповідних: стабільних чи тестових кластерах);
  • с kubectl apply стало складно працювати, тому що він не є декларативним і вміє лише створювати об'єкти, але не приймати рішення щодо їх статусу/видаляти їх;
  • не вистачало деяких функцій, які ми на той момент зовсім не реалізували:
    • повноцінного контролю результату оновлення кластерів,
    • автоматичного визначення деяких параметрів (вступних для скриптів установки) на основі даних, які можна отримати з кластера (discovery),
    • його логічний розвиток у вигляді постійної discovery.

Весь цей накопичений досвід ми реалізували в рамках іншого свого проекту. addon-operator.

Addon-operator

В його основі вже згаданий shell-operator. Вся система виглядає так:

До хуків shell-operator'а додаються:

  • сховище values,
  • Helm-чарт,
  • компонент, який стежить за сховищем values і – у разі будь-яких змін – просить Helm перевикотити чарт.

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

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

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

Модуль може бути безліч, а до них ми додаємо глобальні хуки, глобальне сховище values ​​і компонент, який стежить за цим глобальним сховищем.

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

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

Ця схема відповідає всім вимогам до встановлення доповнень, що були озвучені вище:

  • За шаблонизацію та декларативність відповідає Helm.
  • Питання автооновлення вирішено за допомогою глобального хука, який за розкладом ходить до registry і, якщо бачить там новий образ системи, перекочує її (тобто «сам себе»).
  • Зберігання налаштувань у кластері реалізовано за допомогою ConfigMap, В якому записані первинні дані для сховищ (при старті вони завантажуються в сховища).
  • Проблеми генерації паролів, discovery та continuous discovery вирішені за допомогою хуків.
  • Стейджування досягнуто завдяки тегам, які Docker підтримує із коробки.
  • Контроль результату здійснюється за допомогою метрик, якими ми можемо зрозуміти статус.

Вся ця система реалізована у вигляді єдиного бінарника на Go, який отримав назву addon-operator. Завдяки цьому схема виглядає простіше:

Розширюємо та доповнюємо Kubernetes (огляд та відео доповіді)

Головний компонент на цій схемі - набір модулів (Виділені сірим кольором внизу). Тепер ми можемо невеликими зусиллями написати модуль для потрібного доповнення та бути впевненими, що воно буде встановлене у кожен кластер, оновлюватиметься та реагуватиме на потрібні йому події у кластері.

"Флант" використовує addon-operator на 70+ Kubernetes-кластерах. Поточний статус - альфа-версія. Зараз ми готуємо документацію, щоб випустити бету, а поки що у репозиторії доступні приклади, на основі яких можна створити свій addon.

Де взяти самі модулі для addon-operator? Публікація своєї бібліотеки – наступний етап для нас, ми плануємо це зробити влітку.

Відео та слайди

Відео з виступу (~50 хвилин):

Презентація доповіді:

PS

Інші доповіді у нашому блозі:

Можливо, вас також зацікавлять такі публікації:

Джерело: habr.com

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