Один VPN для всього? Практичний досвід із Netmaker
Усім вітання! Вже давно хотів випробувати Netmaker і лише зараз «дійшли руки».
Відтоді як я вперше його побачив, проєкт добре розвинувся: має вже понад 11 тисяч зірок на GitHub та приємний UI. Можна подивитися відео з презентацією можливостей версії 0.1: там UI наче малював студент frontend-курсів. Також у Netmaker уже є власне хмарне рішення, але сьогодні ми розглянемо саме self-hosted-версію.
У мене на проєкті зараз налаштований WireGuard у Kubernetes-кластері. Через нього розробники мають доступ до внутрішніх ресурсів кластера, а також до деяких зовнішніх сервісів, де потрібен IP whitelist: трафік проходить через NAT gateway із зарезервованою IP-адресою. Для зв’язку деяких сервісів, наприклад Filebeat та Elasticsearch у різних кластерах, використовується Tailscale. І як вишенька на торті: один із клієнтів попросив надати через IPsec доступ ще до кількох сервісів кластера :D Тут, звичайно, ні WireGuard, ні Netmaker не допоможуть, але враження про збірну солянку вже складається.
Тому виникла ідея замінити це все чимось одним. Під капотом у Netmaker той самий WireGuard, тому за «вмілих ручок» і з допомогою Claude можна самостійно зробити власний WireGuard-контролер. Але навіщо заново винаходити велосипед, якщо він уже є?
Отож почнімо
Сервер Netmaker я вирішив налаштувати на окремій VM з публічною IP-адресою. Це не єдиний можливий варіант, але для першого знайомства це зручно: control plane не залежить від Kubernetes-кластера, а цю ж VM можна використати як gateway для вихідного трафіку. Netmaker встановлюється дуже легко: є готовий скрипт, аз операційних систем рекомендується Ubuntu 24.04. Якщо домену немає, можна використати xip, nip або sslip для генерації SSL-сертифіката. Після встановлення з’являється web UI, у якому можна створювати мережі, додавати вузли, бачити їхні overlay-адреси та керувати routes.
Для першого експерименту я вирішив налаштувати конфігурацію для ноутбука. Спочатку думав, що для цього доведеться встановлювати окремий Netmaker Desktop або nmcli, але насправді для звичайного remote access можна згенерувати стандартний WireGuard-конфіг і імпортувати його у нативний WireGuard-клієнт.
Спрощено він виглядає так:
[Interface] PrivateKey = <client-private-key> Address = 100.64.0.10/32 DNS = 1.1.1.1 MTU = 1420 [Peer] PublicKey = <gateway-public-key> AllowedIPs = 203.0.113.10/32, 198.51.100.20/32 Endpoint = vpn.example.com:51821 PersistentKeepalive = 20
Тут важливий AllowedIPs: якщо вам не потрібно «ганяти» через VPN увесь трафік ноутбука, не вказуйте 0.0.0.0/0. Достатньо додати IP-адреси тих зовнішніх сервісів, які мають бачити трафік з однієї відомої IP-адреси. Тобто маємо саме той IP whitelist, з якого все й починалося.
Продовжуємо з Selective Egress
У Netmaker для цього є egress gateway. Додаємо потрібні /32 routes, вказуємо VM як gateway і отримуємо маршрут лише до обраних адрес. Але самого маршруту недостатньо: на gateway-хості має бути увімкнений IP forwarding і NAT.
sysctl net.ipv4.ip_forward iptables -t nat -S POSTROUTING
Коли forwarding вимкнений або немає masquerading, пакети доходять до gateway, але далі зупиняються. Після налаштування forwarding curl ifconfig.me почав показувати публічну IP-адресу Netmaker VM, і сервіси з whitelist знову відкрилися.
Підключаємо Kubernetes
З ноутбуком усе запрацювало досить швидко, тож переходимо до Kubernetes. Тут схема проста: під’єднати по одному Netmaker-вузлу до кожного кластера і вказати Pod та Service CIDR як subnet routes. Це працює, але лише доти, доки адресні простори кластерів (Network CIDRs) не перетинаються.
У моєму випадку два кластери мають overlapping CIDR. Одна й та сама ClusterIP-адреса теоретично може існувати в обох кластерах, але жодна overlay-мережа не зможе вгадати, в який кластер ми хотіли потрапити. Можна було б перенумерувати мережі кластерів, але заради експерименту з Netmaker це overhead, тому я пішов іншим шляхом.
Не маршрутизувати весь кластер, а публікувати окремі сервіси
Замість доступу до всього Pod CIDR можна дати кожному кластеру унікальну Netmaker-адресу і проксіювати з неї лише потрібні сервіси. Тоді не має значення, які CIDR використовуються всередині кластерів.
На практиці це виглядає так:
laptop or remote cluster | | Netmaker overlay v gateway pod (100.64.0.20) | | Kubernetes DNS v service.namespace.svc.cluster.local
У gateway pod можна запустити netclient і простий TCP proxy, наприклад socat:
apiVersion: apps/v1 kind: Deployment metadata: name: netmaker-service-gateway spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: netmaker-service-gateway template: metadata: labels: app: netmaker-service-gateway spec: containers: - name: netclient image: gravitl/netclient:<version> securityContext: capabilities: add: ["NET_ADMIN"] - name: service-proxy image: alpine/socat:<version> args: - TCP-LISTEN:7700,fork,reuseaddr - TCP:internal-service.app.svc.cluster.local:7700 ports: - containerPort: 7700
Це не повноцінний service mesh, але для кількох конкретних TCP-сервісів це досить прозора компромісна схема. Тут є ще один неочевидний нюанс: кожен Netmaker-вузол повинен мати окрему identity, прив’язану до одного pod. Якщо Deployment під час rolling update на кілька секунд запустить старий і новий pod з однаковою identity, вони почнуть перетягувати peer configuration. Тому для такого gateway краще вказати replicas: 1 і strategy: Recreate.
До чого тут Netmaker Operator
Мануальний gateway pod добре підходить для PoC, але з кожним новим сервісом доводиться думати про ports, proxy та lifecycle вузла. Тому наступним кроком став Netmaker Kubernetes Operator. Він створює та керує Kubernetes-ресурсами, які під’єднують workloads до вже існуючої Netmaker-мережі.
Egress для доступу до сервісу можна описати за допомогою анотацій:
apiVersion: v1 kind: Service metadata: name: metrics-via-netmaker annotations: netmaker.io/egress: "true" netmaker.io/egress-target-ip: "100.64.0.30" spec: ports: - name: http port: 9090 targetPort: 9090
egress-target-ip — це не ClusterIP і не Pod IP. Це унікальна IP-адреса вузла в Netmaker overlay. Її можна побачити у Netmaker UI або через CLI. Саме до неї піде трафік з інших peers.
На практиці operator-схема виходить охайнішою за ручні proxy pods, але теж має свої умови. Наприклад, enrollment key з одним використанням підходить для одного вузла, але operator може створювати нові pods після restart або reschedule. Для такої моделі потрібен reusable/unlimited key з обмеженими network access і tags. Інакше все працюватиме до першого перезапуску.
Доступ до ресурсів через Internal DNS
Доступ до 100.64.0.30:9090 технічно вирішує завдання, але для користувача це не найзручніший інтерфейс. На MacBook я додав dnsmasq і split DNS для окремої локальної зони:
search-api.environment-a.netmaker.internal -> 100.64.0.20 metrics.environment-b.netmaker.internal -> 100.64.0.30
Тепер замість IP-адрес і портів можна використовувати зрозумілі імена. /etc/hosts теж спрацював би, але dnsmasq зручніший, коли записів стає більше або для однієї зони потрібен окремий resolver.
Де Netmaker виглядає доречно
Після цього експерименту я бачу для Netmaker кілька зрозумілих сценаріїв:
- доступ розробників до внутрішніх сервісів без публічних ingress;
- site-to-site зв’язок між VM, bare metal і кількома cloud-мережами;
- selective egress через одну публічну IP-адресу для сервісів з IP whitelist;
- доступ до окремих Kubernetes-сервісів через operator або gateway proxy;
- subnet routing до мережі, яку реально бачить один із Netmaker peers.
Але треба бути обережним, якщо ви хочете прозоро об’єднати кілька Kubernetes-кластерів з overlapping CIDR — Netmaker не скасовує законів IP-маршрутизації, у такому випадку потрібно або прибрати overlap, або публікувати окремі сервіси через унікальні overlay-адреси.
Ще одне обмеження — workload egress. Якщо pod звертається до публічного internet-ресурсу через звичайний TCP proxy у gateway pod, це ще не означає, що на тому боці буде видно публічну IP-адресу Netmaker VM. Для справжнього workload egress потрібен HTTP/SOCKS proxy, policy routing або окремий egress gateway.
Що в підсумку
Netmaker не зміг замінити все. IPsec він не замінить, overlapping Kubernetes CIDR самі не зникнуть, а для деяких egress-сценаріїв усе одно знадобиться додатковий proxy або routing. Але він добре закрив два реальні завдання: дав керований remote access на звичайному WireGuard-клієнті та дозволив публікувати окремі сервіси між кластерами без зовнішніх ingress. Крім того, виніс peer management, routes і enrollment з набору ручних WireGuard-конфігів у зрозумілий control plane.
7 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівЦікава стаття!
Peering network робить?
Є ще подібна штука яка сподобалася мені на практиці (в організації мережі з acl) — netbird. Під капотом той ж самий wg з плюхами
так, задача більше стояла щоб була можливість менеджити у себе на інфраструктурі, не всі проекти дозволяють використовувати cloud-рішення, netbird здається якщо у себе розгортати то коштує грошей (або може вже щось помінялось)
також є проект headscale (tailscale opersource) але я коли тестував то не знайшов для себе того функціоналу що потрібно (хоча знову ж таки може додали)
Ніт. Я використовував NB only self-hosted та робив деплой з повним налаштуванням, воно також open-source
Це я до того що перед тим також розглядав варіант з netmaker але зупинився саме на nb, вони в цілому схожі але останній більше сподобався
Та, netbird кращий бо має більше плюшок в community версії — впритул до того, що можна його апку юзати