Сеть в домашнем кластере

Сеть — самая непривычная часть Kubernetes для новичка. Здесь разберёмся по порядку: как поды видят друг друга внутри кластера, как открыть сервис для домашней сети и как впустить трафик из интернета через роутер.

Содержание

CNI: как поды общаются между собой

Каждый под в кластере получает собственный IP-адрес из внутренней подсети (обычно 10.x.x.x). Под на узле 1 может напрямую обратиться к поду на узле 2 по этому адресу — как будто все они в одной плоской сети. Эту магию обеспечивает сетевой плагин, который называется CNI (Container Network Interface).

flannel — то, что у вас уже работает

Если вы ставили k3s или следовали пути kubeadm с нашей инструкции, у вас уже стоит flannel — простой и надёжный плагин. Он создаёт оверлейную сеть поверх вашей домашней: трафик между узлами упаковывается в UDP-пакеты (VXLAN) и распаковывается на другом конце. Для домашнего кластера этого достаточно на годы вперёд.

Посмотреть, как устроена сеть подов:

# подсети, назначенные узлам
kubectl get nodes -o custom-columns=NAME:.metadata.name,PODCIDR:.spec.podCIDR

# поды flannel (в kubeadm) или их роль в k3s
kubectl get pods -n kube-flannel

Calico — когда захочется большего

Calico — более продвинутый плагин. Главные причины перейти на него дома:

Совет новичку Не меняйте CNI на старте. flannel из коробки закрывает все потребности домашнего кластера, а замена CNI на живом кластере — задача со звёздочкой. Изучите Calico позже, на отдельном тестовом кластере, если появится интерес.

Service: стабильные адреса для подов

Поды смертны: пересоздался под — изменился IP. Поэтому к подам никогда не обращаются напрямую. Поверх них создают Service — объект с постоянным внутренним IP и DNS-именем, который распределяет трафик между подами-мишенями.

# пример: сервис поверх deployment с тремя копиями nginx
apiVersion: v1
kind: Service
metadata:
  name: my-web
spec:
  selector:
    app: my-web       # какие поды обслуживать (по метке)
  ports:
    - port: 80         # порт сервиса
      targetPort: 80   # порт контейнера

Изнутри кластера такой сервис доступен по имени: http://my-web (а из другого namespace — http://my-web.имя-namespace). Имена раздаёт встроенный DNS — CoreDNS.

Типов Service четыре, и понимать разницу между ними важно:

ТипЧто делаетКогда использовать дома
ClusterIP (по умолчанию) Внутренний IP, доступен только внутри кластера Для связи сервисов между собой: приложение → база данных
NodePort Открывает порт 30000–32767 на каждом узле Быстрый доступ из домашней сети без лишних настроек
LoadBalancer Выдаёт сервису отдельный «внешний» IP Дома работает через MetalLB (см. ниже) — красивые адреса вида 192.168.1.200
ExternalName DNS-запись CNAME на внешнее имя Редко; для ссылок на внешние сервисы по имени

DNS внутри кластера (CoreDNS)

За служебные имена отвечает CoreDNS — он работает подом в namespace kube-system. Каждому сервису автоматически выдаётся имя вида имя-сервиса.имя-namespace.svc.cluster.local. Проверить DNS изнутри кластера:

# тестовый под с busybox
kubectl run dnstest --rm -it --image=busybox:1.36 --restart=Never -- nslookup kubernetes.default
# Server: 10.43.0.10 (в k3s) — это ClusterIP CoreDNS
# Name: kubernetes.default.svc.cluster.local

Если имена внутри кластера не резолвятся — первым делом смотрите, жив ли CoreDNS: kubectl get pods -n kube-system -l k8s-app=kube-dns.

NodePort: быстрый доступ из дома

Самый простой способ открыть сервис домашней сети — NodePort. Kubernetes откроет указанный порт (из диапазона 30000–32767) на каждом узле кластера и будет пересылать трафик на ваши поды.

apiVersion: v1
kind: Service
metadata:
  name: my-web
spec:
  type: NodePort
  selector:
    app: my-web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080   # можно не указывать — выберется автоматически

Теперь с любого устройства дома сервис доступен по адресу http://192.168.1.50:30080 (где 192.168.1.50 — любой узел кластера).

Ограничения NodePort Порты только из диапазона 30000+, в адресе всегда торчит порт (:30080), и никакой маршрутизации по доменным именам. Для «проверить, работает ли» — идеально. Для постоянных сервисов — переходите на LoadBalancer или Ingress.

MetalLB: настоящий LoadBalancer дома

Тип LoadBalancer в облаках выдаёт сервису публичный IP. Дома облачного балансировщика нет, но есть MetalLB — он делает то же самое из диапазона вашей домашней сети. Вы выделяете пул адресов (например, 192.168.1.200–192.168.1.220), и каждый сервис типа LoadBalancer получает свой постоянный IP.

Шаг 1. Установите MetalLB

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml

# дождитесь запуска подов
kubectl get pods -n metallb-system -w
Если у вас k3s В k3s есть встроенный балансировщик ServiceLB (Klipper). Он работает из коробки, но выдаёт сервисам адреса самих узлов, а не отдельные IP из пула. Если хотите MetalLB — установите k3s с флагом --disable servicelb (или удалите манифест из /var/lib/rancher/k3s/server/manifests/).

Шаг 2. Настройте пул адресов

Создайте файл metallb-pool.yaml. Адреса должны быть из вашей домашней подсети и не пересекаться с DHCP-диапазоном роутера:

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: home-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.200-192.168.1.220
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: home-l2
  namespace: metallb-system
spec:
  ipAddressPools:
    - home-pool
kubectl apply -f metallb-pool.yaml

Шаг 3. Проверьте

# создадим сервис типа LoadBalancer
kubectl create deployment web-test --image=nginx
kubectl expose deployment web-test --type=LoadBalancer --port=80

# смотрим выданный внешний IP
kubectl get svc web-test
# EXTERNAL-IP: 192.168.1.200

# проверяем с любого домашнего устройства
curl http://192.168.1.200

MetalLB в режиме L2 назначает один из узлов «ответственным» за адрес и отвечает на ARP-запросы в локальной сети. Если узел падает — адрес перехватывает другой узел.

Проброс портов на роутере

Чтобы сервис был доступен не только из дома, но и из интернета (например, ваш Nextcloud с телефона из любой точки), нужно пробросить порты на домашнем роутере.

Шаг 4. Настройте проброс на роутере

Общая схема одинакова для всех роутеров (раздел обычно называется «Port Forwarding», «Виртуальные серверы» или «Переадресация портов»):

  1. Внешний порт 80 → внутренний адрес узла или LoadBalancer-IP, порт 80;
  2. Внешний порт 443 → тот же адрес, порт 443.

Куда пробрасывать, зависит от вашей схемы: если Ingress-сервис получил IP от MetalLB (например, 192.168.1.200) — пробрасывайте на него. Если используете NodePort — на адрес любого узла и соответствующий порт 3xxxx.

Важные оговорки про интернет-доступ

Ingress: один вход для всех сервисов

NodePort и LoadBalancer выставляют сервисы наружу «в лоб», каждый на своём порту или IP. Ingress — умнее: это единая точка входа (обычно порты 80/443), которая маршрутизирует запросы по доменному имени и пути: cloud.home.lan → Nextcloud, media.home.lan → Jellyfin, и всё это через один IP-адрес.

Ingress состоит из двух частей:

Если у вас k3s — Traefik уже стоит

Проверьте:

kubectl get pods -n kube-system | grep traefik
kubectl get svc -n kube-system traefik

Если у вас kubeadm — поставьте ingress-nginx

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/baremetal/deploy.yaml

Для bare-metal-версии назначьте сервису контроллера тип NodePort или LoadBalancer (если стоит MetalLB):

kubectl get svc -n ingress-nginx
# при MetalLB: контроллер получит, например, 192.168.1.201

Шаг 5. Создайте Ingress-ресурс

Пример: у нас есть два сервиса — jellyfin и nextcloud (оба типа ClusterIP). Откроем их по разным именам через один контроллер. Файл ingress-home.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: home-services
  annotations:
    # для traefik класс обычно не нужен; для nginx раскомментируйте:
    # kubernetes.io/ingress.class: nginx
spec:
  rules:
    - host: media.home.lan
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: jellyfin
                port:
                  number: 8096
    - host: cloud.home.lan
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nextcloud
                port:
                  number: 80
kubectl apply -f ingress-home.yaml
kubectl get ingress

Шаг 6. Научите домашние устройства резолвить имена

Имена media.home.lan сами собой не заработают — нужна DNS-запись. Три варианта по возрастанию правильности:

  1. Файл hosts на вашем компьютере — для быстрой проверки. В Windows: C:\Windows\System32\drivers\etc\hosts, в Linux/macOS: /etc/hosts. Строка: 192.168.1.200 media.home.lan cloud.home.lan (IP — адрес Ingress/MetalLB).
  2. DNS на роутере — многие роутеры (Keenetic, OpenWrt, RouterOS) умеют статические DNS-записи: пропишите *.home.lan или каждое имя отдельно на адрес Ingress.
  3. Свой DNS — AdGuard Home или Pi-hole в том же кластере решают и блокировку рекламы, и локальные имена. Классический следующий шаг домашней лаборатории.

Шаг 7. Проверьте цепочку целиком

# с вашего компьютера:
curl -H "Host: media.home.lan" http://192.168.1.200
# или, если DNS уже настроен:
curl http://media.home.lan

Путь запроса: браузер → DNS (имя → IP Ingress) → Ingress-контроллер → смотрит поле Host → Service jellyfin → под Jellyfin. Если что-то не работает, проверяйте звенья по порядку снизу вверх: жив ли под, есть ли у сервиса endpoints (kubectl get endpoints jellyfin), создан ли ingress (kubectl describe ingress home-services).

Диагностика сетевых проблем

Когда «сервис не открывается», идите по цепочке от пода наружу:

Проверка 1. Жив ли под

kubectl get pods -o wide
# STATUS должен быть Running, IP — внутренний адрес пода

Проверка 2. Отвечает ли приложение внутри пода

# подставьте IP пода и порт контейнера
kubectl run tmp --rm -it --image=busybox:1.36 -- wget -qO- http://10.42.0.15:80

Проверка 3. Есть ли у сервиса endpoints

kubectl get endpoints имя-сервиса
# пустой список = selector сервиса не совпадает с метками подов — классическая опечатка

Проверка 4. Отвечает ли сервис по ClusterIP

kubectl get svc имя-сервиса
kubectl run tmp --rm -it --image=busybox:1.36 -- wget -qO- http://10.43.x.x:80

Проверка 5. NodePort / Ingress

# с самого узла — открыт ли NodePort
curl http://localhost:30081

# правила ingress
kubectl describe ingress имя
# логи контроллера
kubectl logs -n kube-system -l app.kubernetes.io/name=traefik   # k3s

Подробный разбор типовых поломок — на странице «Советы».

Что дальше Сервисы доступны, но куда они будут сохранять файлы? Переходите к странице «Хранение» — разберём постоянные тома, локальные диски и NFS.