Один VPN для всього? Практичний досвід із Netmaker

💡 Усі статті, обговорення, новини про DevOps — в одному місці. Приєднуйтесь до DevOps спільноти!

Усім вітання! Вже давно хотів випробувати 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.

👍ПодобаєтьсяСподобалось6
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Є ще подібна штука яка сподобалася мені на практиці (в організації мережі з acl) — netbird. Під капотом той ж самий wg з плюхами

так, задача більше стояла щоб була можливість менеджити у себе на інфраструктурі, не всі проекти дозволяють використовувати cloud-рішення, netbird здається якщо у себе розгортати то коштує грошей (або може вже щось помінялось)

також є проект headscale (tailscale opersource) але я коли тестував то не знайшов для себе того функціоналу що потрібно (хоча знову ж таки може додали)

Ніт. Я використовував NB only self-hosted та робив деплой з повним налаштуванням, воно також open-source

Це я до того що перед тим також розглядав варіант з netmaker але зупинився саме на nb, вони в цілому схожі але останній більше сподобався

Та, netbird кращий бо має більше плюшок в community версії — впритул до того, що можна його апку юзати

Підписатись на коментарі