Calico для мережі в Kubernetes: знайомство та трохи з досвіду
Мета статті – познайомити читача з основами мережевої взаємодії та управлінням мережевими політиками в Kubernetes, а також зі стороннім плагіном Calico, що розширює стандартні можливості. Принагідно буде продемонстровано зручність конфігурації та деякі фічі на реальних прикладах з досвіду нашої експлуатації.
У контексті цієї статті важливо відзначити, що за мережеву зв'язність між контейнерами та вузлами відповідає не сам K8s: для цього використовуються всілякі плагіни CNI (Container Networking Interface). Докладніше про цю концепцію ми теж розповідали.
Наприклад, найпоширеніший із таких плагінів — Фланель - Забезпечує повну мережеву зв'язність між усіма вузлами кластера за допомогою підняття мостів на кожному вузлі, закріплюючи за ним підсіти. Однак повна та нерегульована доступність не завжди корисна. Щоб забезпечити якусь мінімальну ізоляцію в кластері, необхідно втрутитися у конфігурування firewall'а. У загальному випадку воно віддано в управління того самого CNI, через що будь-які сторонні втручання в iptables можуть бути інтерпретовані некоректно або зовсім ігноруватися.
А з коробки для організації управління мережевими політиками в кластері Kubernetes надається NetworkPolicy API. Цей ресурс, який розповсюджується на вибрані простори імен, може містити правила для розмежування доступу від одних програм до інших. Він також дозволяє налаштовувати доступність між конкретними pod'ами, оточеннями (просторами імен) або блоками IP-адрес:
Цей не найпримітивніший приклад з офіційної документації може раз і назавжди відбити бажання розумітися на логіці роботи мережевих політик. Однак ми все ж таки спробуємо зрозуміти основні принципи та методи обробки потоків трафіку за допомогою мережевих політик…
Логічно, що є 2 типи трафіку: що входить у pod (Ingress) і що виходить із нього (Egress).
Власне, на ці дві категорії за напрямом руху і поділяється політика.
Наступний обов'язковий атрибут – селектор; той, до кого застосовується правило. Це може бути pod (або група pod'ів) або оточення (тобто простір імен). Важлива деталь: обидва види цих об'єктів повинні містити мітку (етикетка у термінології Kubernetes) - саме ними оперують політики.
Крім кінцевого числа селекторів, об'єднаних якоюсь міткою, існує можливість написання правил на кшталт «Дозволити/заборонити все/всім» у різних варіаціях. Для цього використовуються конструкції виду:
Повертаючись до вибору CNI-плагіну для кластера, варто зазначити, що не кожен мережевий плагін підтримує роботу з NetworkPolicy. Наприклад, згаданий Flannel не вміє конфігурувати мережеві політики, про що прямо сказано в офіційному репозиторії. Там же згадано альтернативу — Open Source-проект коленкор, який помітно розширює стандартний набір API Kubernetes щодо мережевих політик.
Знайомимося з Calico: теорія
Плагін Calico може використовуватися в інтеграції з Flannel (підпроект Канал) або самостійно, покриваючи як функції із забезпечення мережевої зв'язності, і можливості управління доступностью.
Які можливості дає використання «коробкового» рішення K8s та набору API із Calico?
Ось що вбудоване в NetworkPolicy:
політики обмежені оточенням;
політики застосовуються до pod'ів, помічених лейблами;
правила можуть бути застосовані до pod'ів, оточення або підмереж;
правила можуть містити протоколи, іменовані чи символьні вказівки портів.
А ось як Calico розширює ці функції:
політики можуть бути застосовані до будь-якого об'єкта: pod, контейнер, віртуальна машина або інтерфейс;
правила можуть містити конкретну дію (заборона, дозвіл, логування);
як ціль або джерело правил може бути порт, діапазон портів, протоколи, HTTP- або ICMP-атрибути, IP або підмережа (4 або 6 покоління), будь-які селектори (вузлів, хостів, оточень);
додатково можна регулювати проходження трафіку за допомогою налаштувань DNAT та політик прокидання трафіку.
Перші коміти на GitHub у репозиторії Сalico датуються липнем 2016 року, а вже через рік проект зайняв лідируючі позиції в організації мережевої зв'язності Kubernetes — про це свідчать, наприклад, результати опитування, проведеного The New Stack:
Багато великих managed-рішення з K8s, такі як Amazon EKS, Azure AKS, Google GKE та інші стали рекомендувати його до використання.
Що стосується продуктивності, тут все чудово. При тестуванні свого продукту команда розробки Calico продемонструвала астрономічні показники, запустивши понад 50000 500 контейнерів на 20 фізичних вузлах зі швидкістю створення XNUMX контейнерів за секунду. Проблем при масштабуванні не виявлено. Такі результати були озвучені вже за анонсу першої версії. Незалежні дослідження, спрямовані на пропускну здатність та обсяги споживання ресурсів, також підтверджують продуктивність Calico, що практично не поступається Flannel. Наприклад:
Проект швидко розвивається, підтримується робота в популярних рішеннях managed K8s, OpenShift, OpenStack, є можливість використовувати Calico при розгортанні кластера за допомогою удар ногою, зустрічаються згадки побудови Service Mesh-мереж (ось приклад використання разом із Istio).
Практика з Calico
У загальному випадку використання ванільного Kubernetes установка CNI зводиться до застосування файлу calico.yaml, завантаженого з офіційного сайту, з допомогою kubectl apply -f.
Як правило, актуальна версія плагіна сумісна з 2-3 останніми версіями Kubernetes: роботу в старіших версіях не тестують і не гарантують. За заявами розробників, Calico працює на ядрі Linux вище 3.10 під керуванням CentOS 7, Ubuntu 16 або Debian 8, поверх iptables чи IPVS.
Ізоляція всередині оточення
Для загального розуміння розглянемо простий випадок, щоб зрозуміти, чим відрізняються мережеві політики в нотації Calico від стандартних і як підхід до складання правил полегшує їх читання та гнучкість конфігурування:
У кластері розгорнуто 2 веб-додатки: на Node.js і PHP, - одна з яких використовує Redis. Щоб закрити доступ до Redis з PHP, залишивши при цьому зв'язок з Node.js, достатньо застосувати таку політику:
По суті, ми дозволили вхідний трафік на порт Redis з Node.js. І явно не забороняли нічого іншого. Як тільки з'являється NetworkPolicy, всі селектори, згадані в ньому, починають ізолюватися, якщо не вказано інше. При цьому правила ізоляції не поширюються на інші об'єкти, що не покриваються селектором.
У прикладі використовується apiVersion Kubernetes'а «з коробки», але ніщо не заважає використовувати однойменний ресурс із постачання Calico. Синтаксис там розгорнутий, тому потрібно переписати правило для вищеописаного випадку в наступному вигляді:
Згадані вище конструкції для дозволу чи заборони всього трафіку за допомогою звичайного NetworkPolicy API містять складні для сприйняття та запам'ятовування конструкції з дужками. У випадку з Calico, щоб змінити логіку роботи правила firewall'а на протилежну, достатньо змінити action: Allow на action: Deny.
Ізоляція по оточенням
Тепер представимо ситуацію, коли програма генерує бізнес-метрики для їх збору в Prometheus і подальшого аналізу за допомогою Grafana. У розвантаженні можуть бути чутливі дані, які за замовчуванням знову ж таки доступні для загального огляду. Закриємо від сторонніх очей ці дані:
Prometheus, як правило, винесено в окреме службове оточення - у прикладі це буде namespace наступного виду:
Поле metadata.labels тут виявилося невипадково. Як вище згадувалося, namespaceSelector (Як і podSelector) оперує лейблами. Тому, щоб дозволити забирати метрики з усіх pod'ів на певному порту, доведеться додати якусь мітку (або взяти з існуючих), а потім застосувати конфігурацію на кшталт:
Загалом, додаючи подібні політики під конкретні потреби, можна захистити від зловмисного чи випадкового втручання у роботу додатків у кластері.
Найкращою практикою, на думку творців Calico, є підхід «Заборони все і явно відкривай необхідне», зафіксований у офіційної документації (аналогічного підходу дотримуються й інші — зокрема, вже згаданої статті).
Застосування додаткових об'єктів Calico
Нагадаю, що за допомогою розширеного набору API Calico можна регулювати доступність вузлів, не обмежуючись pod'ами. У наступному прикладі за допомогою GlobalNetworkPolicy закривається можливість проходження ICMP-запитів у кластері (наприклад, пінги з pod'а на вузол, між pod'ами або з вузла на IP pod'а):
У наведеному вище кейсі залишається можливість вузлам кластера «достукатися» між собою ICMP. І це питання вирішується коштами GlobalNetworkPolicy, застосованої до сутності HostEndpoint:
Зрештою, наведу цілком реальний приклад використання функцій Calico для випадку з навколокластерною взаємодією, коли стандартного набору політик не вистачає. Для доступу до веб-застосунку клієнтами використовується VPN-тунель, і цей доступ жорстко контролюємо і обмежений конкретним списком дозволених до використання сервісів:
Клієнти підключаються до VPN через стандартний UDP-порт 1194 і при підключенні одержують маршрути до кластерних підмережів pod'ів та сервісів. Підмережі push'атся цілком, щоб не втрачати сервіси при перезапуску та зміні адрес.
Порт у конфігурації – стандартний, що накладає деякі нюанси на процес конфігурування програми та її перенесення в Kubernetes-кластер. Наприклад, у тому ж AWS LoadBalancer для UDP з'явився буквально наприкінці минулого року в обмеженому списку регіонів, а NodePort не можна використовувати через його прокидання на всіх вузлах кластера і неможливо масштабувати кількість інстансів сервера з метою стійкості до відмов. Плюс, доведеться змінювати діапазон портів, що вибирається за замовчуванням.
В результаті перебору можливих рішень було вибрано:
Pod'и з VPN плануються на вузол у режимі hostNetwork, тобто на фактичній IP.
Сервіс вивішується назовні через ClusterIP. На вузлі фізично піднімається порт, який доступний ззовні з невеликими застереженнями (умовна наявність реальної IP-адреси).
Визначення вузла, на якому піднявся pod, лежить за межами нашої оповіді. Скажу лише, що можна жорстко «прибити» сервіс до вузла або ж написати невеликий sidecar-сервіс, який слідкуватиме за поточною IP-адресою VPN-сервісу і правитиме DNS-записи, прописані у клієнтів — у кого на що вистачить фантазії.
З точки зору маршрутизації ми можемо однозначно ідентифікувати клієнта за VPN за його IP-адресою, що видається сервером VPN. Нижче примітивний приклад обмеження доступу такому клієнту до сервісів, ілюстрація на вищезгаданому Redis:
Тут жорстко забороняється підключення на порт 6379, але при цьому збережено роботу служби DNS, функціонування якої досить часто страждає при складанні правил. Тому що, як згадувалося раніше, при появі селектора до нього застосовується заборонна політика за умовчанням, якщо не вказано інше.
Підсумки
Таким чином, за допомогою розширеного API Calico можна гнучко конфігурувати та динамічно змінювати маршрутизацію в кластері та навколо нього. У загальному випадку його використання може виглядати як стрілянина з гармати по горобцях, а впровадження L3-мережі з BGP- та IP-IP-тунелями виглядає монструозно у простій інсталяції Kubernetes у плоскій мережі… Проте в іншому інструмент виглядає цілком життєздатним та корисним.
Ізоляція кластера для забезпечення вимог безпеки не завжди може бути реалізована, і саме у таких випадках на допомогу приходить Calico (або подібне рішення). Наведені в статті приклади (з невеликим доопрацюванням) використовуються в кількох інсталяціях наших клієнтів в AWS.