
Сталкивались ли вы с ситуацией, когда нужно получить доступ к сети подов или сервисов в кластере Kubernetes? Кто-то может возразить, что маппинга портов через port-forward или использования NodePort вполне достаточно, однако часто это не так. Список реальных кейсов велик, рассмотрим несколько для примера:
-
разработчикам нужен прямой доступ к сервисам по ClusterIP для дебага;
-
используются внешние балансировщики (например, SIP/RTP-прокси для телефонии или антиспам-решения), когда они не могут быть размещены внутри Kubernetes;
-
присутствуют аппаратные решения вроде NGFW от именитых производителей.
В тексте мы в первую очередь будем опираться на практику Managed Kubernetes-сервиса Selectel, но он также будет полезен, если у вас свой K8s с CNI Calico. Cразу оговоримся: для других CNI «рецепты» из текста не подойдут.
Используйте навигацию, если не хотите читать текст целиком:
Если было бы интересно ознакомиться с рекомендациями для Cilium — пишите в комментариях! Скоро мы добавим выбор CNI в управляемом K8s, и Cilium в списке будет первым. Stay tuned! 🙂
Сетевая модель Kubernetes
Прежде чем перейти к практике, вспомним базовую сетевую модель Kubernetes — особенно важную часть, связанную с внешним (North-South) трафиком и публикацией сервисов.
Типовая схема взаимодействия включает:
-
поды, объединенные в
ClusterIP-сервисы; -
(опционально)
Ingress-контроллер, обеспечивающий маршрутизацию на L7-уровне; -
NodePortиLoadBalancer-сервисы для выхода за пределы кластера; -
протоколы маршрутизации, такие как BGP или gARP, на границе с физической сетью.
Ниже — пример, как Kubernetes-ресурсы «наслаиваются» друг на друга, чтобы обеспечить обмен трафика между «внешним миром» и кластером (North-South):


Облачная инфраструктура для ваших проектов
Виртуальные машины в Москве, Санкт-Петербурге и Новосибирске с оплатой по потреблению.
Пример инфраструктуры
Представим, что в облаке у вас развернута небольшая инфраструктура — виртуальная машина и managed-кластер Kubernetes. Все это показано на схеме ниже:
Казалось бы, ничего не мешает посмотреть, какие сети использует K8s для подов и сервисов, и добавить маршруты вручную. Но что, если у нас десятки или сотни нод? Работает автоскейлер, и новые ноды добавляются и удаляются из кластера? Узлов, на которые нужно добавить маршруты, становится все больше с ростом инфраструктуры?
Очевидно, для уменьшения объема работы администратора нужен обмен маршрутной информацией в реальном времени. Значит, ищем сервис с поддержкой протокола динамической маршрутизации. В нашем случае этим протоколом будет BGP, так как используемый в Managed Kubernetes-сервисе Calico CNI поддерживает именно его.
Задача: настроить обмен маршрутами между воркер-нодами (worker node) и виртуальной машиной по BGP. Ноды будут передавать маршруты ВМ до сервисов и подов, а ВМ — анонсировать префикс VPN-сети. И главное — при появлении новых воркер-нод в кластере подсети подов будут анонсироваться автоматически. Осталось только все настроить. Приступим!
Настройка компонентов
На управляющей машине
На любимом ноутбуке или десктопе (или может быть, виртуалке) потребуется стандартный набора инструментов: bash, awk, curl, jq, OpenStack CLI и любимый текстовый редактор. Дополнительно рекомендуем установить calicoctl — статически скомпилированный бинарный файл на Go, который достаточно скопировать и запустить. Подробная инструкция по установке — в официальной документации. Далее приведем «выжимки» для разных ОС.
Linux
curl -sSL https://github.com/projectcalico/calico/releases/
Windows
Invoke-WebRequest -Uri "https://github.com/projectcalico/calico/releases/download/v3.29.4/calicoctl-windows-amd64.exe" -OutFile "calicoctl.exe"
MacOS
Скомпилированный бинарный файл:
curl -sSL https://github.com/projectcalico/calico/releases/download/v3.29.4/calicoctl-darwin-amd64 -o calicoctl
brew:
brew install calicoctl
calicoctl необязателен, так как начиная с версии 3.19 конфигурацией Calico можно управлять через CustomResourceDefinitions. Однако calicoctl все еще нужен для следующих подкоманд:
-
сalicoctl node, -
calicoctl ipam, -
calicoctl convert, -
calicoctl version.
Инфраструктура
Если вы используете облачную инфраструктуру (как в нашем примере), важно учитывать возможную фильтрацию трафика со стороны провайдера. Например, в облаке Selectel действует IP/MAC-антиспуфинг — и для корректной работы Kubernetes-кластера нужно добавить разрешенные IP-адреса на сетевые порты.
Наша команда уже добавила подсеть подов в список разрешенных адресов, но подсеть сервисов нужно указать вручную. Ниже — пошаговая инструкция.
1. Проверяем, включена ли фильтрация трафика.
openstack network show <cloud_network_name> -c port_security_enabled -f value
2. Если фильтрация включена — продолжаем настройку. Добавляем подсеть сервисов в разрешенные «пары» для порта воркер-ноды MKs. Выясняем, какая подсеть используется для подов, фиксируем:
Обратите внимание: в Selectel MKs подсети фиксированы.
-
10.10.0.0/16— для подов. -
10.96.0.0/12— для сервисов.
kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' | awk '/^clusterCIDR/ { print $2 }'
3. Выясняем, какая подсеть используется для сервисов, фиксируем:
echo '{"apiVersion":"v1","kind":"Service","metadata":{"name":"one"},"spec":{"clusterIP":"198.51.100.1","ports":[{"port":80}]}}' | kubectl apply -f - 2>&1 | sed 's/.*valid IPs is //'
4. Разрешаем дополнительные IP-адреса на портах.
Важно! Сеть подов уже добавляется в разрешенные при создании в Selectel кластера MKs. Выбираем один из вариантов.
Вручную
Фиксируем идентификаторы портов ВМ:
openstack server list -c Name -c Networks openstack port list
Сопоставляем их по IP-адресам. Далее для каждого порта воркер-ноды выполняем:
openstack port set --allowed-address ip-address=<k8s_service_cidr> <worker_node_port_uuid>
Полуавтоматически
Применим небольшой shell-скрипт:
#!/bin/bash k8s_service_cidr=$(echo '{"apiVersion":"v1","kind":"Service","metadata":{"name":"one"},"spec":{"clusterIP":"198.51.100.1","ports":[{"port":80}]}}' | kubectl apply -f - 2>&1 | sed 's/.*valid IPs is //') vm_ips=$(openstack server list --long --tags mks_cluster=true -c Networks -f json | jq -r '.[].Networks[][0]') port_ids=$(openstack port list --any-tags mks_cluster=true --long -f json | jq -r --arg nodes_ip "$vm_ips" '.[] | select(."Fixed IP Addresses"[0].ip_address as $ips | ($nodes_ip|split("\n")) | index($ips)) | .ID') for id in $port_ids do openstack port set --allowed-address ip-address=${k8s_service_cidr} ${id} echo "Port AAPs: " openstack port show -c allowed_address_pairs ${id} done
В кластере MKs
Для CNI Calico можно использовать два подхода к настройке динамической маршрутизации:
-
«Установка на не-кластерный узел» (например, в Docker-контейнере).
-
BGP-пиринг с ВМ, сервером или маршрутизатором.
Мы рассмотрим второй вариант как более универсальный, так как его можно применить и в случае использования программного или аппаратного маршрутизатора.
1. Просматриваем глобальную конфигурацию BGP:
kubectl get bgpconfiguration default -o json | jq 'pick(.apiVersion, .kind, .spec)'
Пример вывода:
{ "apiVersion": "crd.projectcalico.org/v1", "kind": "BGPConfiguration", "spec": { "asNumber": 65065, "logSeverityScreen": "Info", "nodeToNodeMeshEnabled": false } }
Фиксируем AS-номер, который используется в кластере (в выводе выще — AS 65065). Он потребуется для дальнейшей конфигурации.
2. Добавляем глобальный BGP-пир:
cat <<EOF | kubectl apply -f - apiVersion: crd.projectcalico.org/v1 kind: BGPPeer metadata: name: vpn-server spec: peerIP: 10.15.1.50 asNumber: 64999 keepOriginalNextHop: true EOF
-
10.15.1.50— наша ВМ с VPN и bird/frr. -
keepOriginalNextHop— важный параметр. Без него на ВМ окажется несколько маршрутов с одинаковыми метриками и next-hop в виде воркер-ноды кластера MKs. Корректность работы будет под вопросом.
Добавляем подсеть сервисов для анонсирования.
При использовании kubectl:
kubectl patch bgpconfiguration default -p '{"spec":{"serviceClusterIPs":[{"cidr": "10.96.0.0/12"}]}}' --type='merge'
Через calicoctl:
calicoctl patch bgpconfiguration default -p '{"spec":{"serviceClusterIPs":[{"cidr": "10.96.0.0/12"}]}}' --allow-version-mismatch
Директиву —allow-version-mismatch используйте с осторожностью. Можно использовать референс из официальной документации. Важно: serviceClusterIPs применяется только в конфигурации по умолчанию (default). Если вы создадите дополнительный конфиг, параметр serviceClusterIPs будет проигнорирован в ней.
Еще один момент: в базовой конфигурации подсеть сервисов по BGP не анонсируется. Значение serviceClusterIPs — это пустой список.
Добавляем BGP-фильтры (опционально). Пока фильтров нет, но в будущем их важно добавить, чтобы не принимать лишние или ошибочные маршруты. Подробнее — в официальной документации.
На виртуальной машине
ВМ с VPN-сервером находится в облаке. IP-адрес по схеме — 10.15.1.50. Подключаемся к виртуальной машине. Напомним, что ОС сервера — Ubuntu 22.04.5 LTS.
Установка ПО
Для настройки маршрутизации с поддержкой BGP выберите один из двух популярных вариантов: Bird 2 или FRRouting. Оба подходят, выбор зависит от ваших предпочтений.
Bird 2:
sudo add-apt-repository ppa:cz.nic-labs/bird sudo apt update && sudo apt install bird2
FRRouting:
curl -s https://deb.frrouting.org/frr/keys.gpg | sudo tee /usr/share/keyrings/frrouting.gpg > /dev/null export FRRVER="frr-stable" echo deb '[signed-by=/usr/share/keyrings/frrouting.gpg]' https://deb.frrouting.org/frr $(lsb_release -s -c) $FRRVER | sudo tee -a /etc/apt/sources.list.d/frr.list sudo apt update sudo apt install frr frr-pythontools
Конфигурация ПО
BGP-сессии строятся не с мастер-нодами, а только с воркер-нодами. Мастер-ноды отклоняют подключения по BGP.
Первый вариант: bird2.
1. Приводим конфигурационный файл /etc/bird/bird.conf к следующему виду:
log syslog all; protocol device { } protocol direct { disabled; # Disable by default ipv4; # Connect to default IPv4 table ipv6; # ... and to default IPv6 table } protocol kernel { ipv4 { # Connect protocol to IPv4 table by channel export all; # Export to protocol. default is export none }; } protocol kernel { ipv6 { export all; }; } protocol static { ipv4; # Again, IPv4 channel with default options } filter out_prefix { if ( net ~ [172.19.7.0/24] ) then { accept; } else reject; } template bgp mks_worker_nodes { #bfd; description "MKS Worker Nodes"; local 10.15.1.50 as 64999; neighbor as 65065; ipv4 { #next hop self bgp; import all; export filter out_prefix; }; debug { states, routes, filters, interfaces, events }; } protocol bgp worker_node_1 from mks_worker_nodes { description "my-mks-stage-cluster-node-0wt9d"; neighbor 10.15.1.11; } protocol bgp worker_node_2 from mks_worker_nodes { description "my-mks-stage-cluster-node-c12uj"; neighbor 10.15.1.12; } protocol bgp worker_node_3 from mks_worker_nodes { description "my-mks-stage-cluster-node-9vpor"; neighbor 10.15.1.13; }
2. Задействуем и запускаем Bird:
sudo systemctl enable bird # по умолчанию Ubuntu запускает bird после установки # поэтому перезапускаем сервис sudo systemctl restart bird
3. Проверяем статус:
birdcl show status
Ожидаемый вывод:
BIRD 2.17.1 ready. BIRD 2.17.1 Router ID is 10.15.1.50 Hostname is vpn-server Current server time is 2025-04-30 14:47:02.497 Last reboot on 2025-04-30 14:43:11.050 Last reconfiguration on 2025-04-30 14:43:11.050 Daemon is up and running
4. Проверяем, что демон прослушивает порт:
ss -ptln | grep 179
Пример вывода:
LISTEN 0 8 0.0.0.0:179 0.0.0.0:*users:(("bird",pid=4740,fd=8))
Второй вариант: frrouting.
1. Убеждаемся, что bgpd для frr задействован:
grep bgp /etc/frr/daemons bgpd=yes bgpd_options=" -A 127.0.0.1" # bgpd_wrap="/usr/bin/daemonize /usr/bin/mywrapper"
2. Приводим конфигурацию FRR (/etc/frr/frr.conf) к следующему виду:
frr version 10.3 frr defaults traditional hostname vpn-server log syslog informational no ipv6 forwarding service integrated-vtysh-config ! ip prefix-list default_mks description "MKS default prefixes for pods and services" ip prefix-list default_mks seq 10 permit 10.10.0.0/16 ip prefix-list default_mks seq 11 permit 10.96.0.0/12 ip prefix-list default_mks seq 1000 deny any ip prefix-list my_vpn seq 10 permit 172.19.7.0/24 ip prefix-list my_vpn seq 1000 deny any ! ip router-id 10.15.1.50 ! router bgp 64999 bgp router-id 10.15.1.50 bgp log-neighbor-changes no bgp network import-check neighbor 10.15.1.11 remote-as 65065 neighbor 10.15.1.11 description my-mks-stage-cluster-node-0wt9d neighbor 10.15.1.11 interface eth0 neighbor 10.15.1.12 remote-as 65065 neighbor 10.15.1.12 description my-mks-stage-cluster-node-c12uj neighbor 10.15.1.12 interface eth0 ! address-family ipv4 unicast network 172.19.7.0/24 redistribute local neighbor 10.15.1.11 prefix-list default_mks in neighbor 10.15.1.11 prefix-list my_vpn out neighbor 10.15.1.12 prefix-list default_mks in neighbor 10.15.1.12 prefix-list my_vpn out exit-address-family exit !
Используйте vtysh, чтобы конфигурировать frr императивно. Это может быть удобно, если вы привыкли настраивать сетевое оборудование через CLI.
no bgp network import-check — параметр, который отключает проверку наличия маршрута в RIB перед анонсом. Можно не использовать, но тогда важно заранее проверить порядок запуска демона и VPN-сервиса.
3. Задействуем и запускаем frr:
sudo systemctl enable frr # по умолчанию в Ubuntu systemd запускает frr # после установк поэтому перезапускаем сервис sudo systemctl restart frr
4. Проверяем, что демон прослушивает порт:
ss -ptln | grep 179 LISTEN 0 4096 0.0.0.0:179 0.0.0.0:*users:(("bgpd",pid=799,fd=22)) LISTEN 0 4096 [::]:179 [::]:*users:(("bgpd",pid=799,fd=23))
Пример вывода:
LISTEN 0 4096 0.0.0.0:179 0.0.0.0:*users:(("bgpd",pid=799,fd=22)) LISTEN 0 4096 [::]:179 [::]:*users:(("bgpd",pid=799,fd=23))
Проверка работоспособности
Похоже, все настроено. Однако важно убедиться в корректности работы.
1. Просмотрим информацию по BGP. Выполняем на ВМ c VPN.
frrouting:
sudo vtysh -c 'show bgp summary' sudo vtysh -c 'show bgp ipv4 all' sudo vtysh -c 'show ip route bgp'
bird2:
sudo birdc show route sudo birdc show status
2. Развернем тестовый echoserver. На управляющей машине выполним:
cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: cilium-echoserver namespace: default spec: replicas: 3 selector: matchLabels: app: cilium-echoserver template: metadata: labels: app: cilium-echoserver spec: containers: - name: cilium-echoserver image: cilium/echoserver:latest imagePullPolicy: IfNotPresent ports: - containerPort: 8088 protocol: TCP env: - name: PORT value: "8088" affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: "app" operator: In values: - cilium-echoserver topologyKey: "kubernetes.io/hostname" EOF
Обратите внимание, что для развертывания было указано три реплики.
3. Проверим:
kubectl get pods -o json | jq '.items[]|pick(.kind, .metadata.name, .status.hostIP, .status.podIP)' { "kind": "Pod", "metadata": { "name": "cilium-echoserver-795b4455-47v9k" }, "status": { "hostIP": "10.15.1.13", "podIP": "10.10.224.45" } } { "kind": "Pod", "metadata": { "name": "cilium-echoserver-795b4455-xcnhb" }, "status": { "hostIP": "10.15.1.11", "podIP": "10.10.73.249" } } { "kind": "Pod", "metadata": { "name": "cilium-echoserver-795b4455-xf5r4" }, "status": { "hostIP": "10.15.1.12", "podIP": "10.10.113.3" } }
Вывод подтверждает: поды распределены по воркер-нодам «один к одному». Если увеличить количество подов, часть будет в статусе Pending из‑за anti-affinity.
4. Опубликуем сервис.
Простая императивная публикация:
kubectl expose deploy cilium-echoserver --name cilium-echo-svc --type ClusterIP
Публикация с политикой:
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: cilium-echo-svc-local spec: internalTrafficPolicy: Local ports: - port: 80 protocol: TCP targetPort: 8088 selector: app: cilium-echoserver sessionAffinity: None type: ClusterIP EOF
Публикация NodePort:
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: cilium-echo-svc-np spec: externalTrafficPolicy: Local ports: - port: 80 protocol: TCP targetPort: 8088 selector: app: cilium-echoserver sessionAffinity: None type: NodePort EOF
5. Еще раз проверим маршруты, полученные по BGP.
Bird2:
sudo birdc show route BIRD 2.17.1 ready. Table master4: 10.10.120.0/26 unicast [worker_node_1 14:43:11.514 from 10.15.1.13] * (100) [AS65065i] via 10.15.1.13 on eth1 unicast [worker_node_3 14:43:12.011] (100) [AS65065i] via 10.15.1.13 on eth1 unicast [worker_node_2 14:43:12.300 from 10.15.1.12] (100) [AS65065i] via 10.15.1.13 on eth1 10.10.246.0/26 unicast [worker_node_1 14:43:11.514 from 10.15.1.13] * (100) [AS65065i] via 10.15.1.12 on eth1 unicast [worker_node_3 14:43:12.011 from 10.15.1.13] (100) [AS65065i] via 10.15.1.12 on eth1 unicast [worker_node_2 14:43:12.300] (100) [AS65065i] via 10.15.1.12 on eth1 10.96.0.0/12 unicast [worker_node_1 14:43:11.514] * (100) [AS65065i] via 10.15.1.13 on eth1 unicast [worker_node_3 14:43:12.011] (100) [AS65065i] via 10.15.1.13 on eth1 unicast [worker_node_2 14:43:12.300] (100) [AS65065i] via 10.15.1.12 on eth1 10.10.37.0/26 unicast [worker_node_1 14:43:11.514] * (100) [AS65065i] via 10.15.1.13 on eth1 unicast [worker_node_3 14:43:12.011 from 10.15.1.13] (100) [AS65065i] via 10.15.1.13 on eth1 unicast [worker_node_2 14:43:12.300 from 10.15.1.12] (100) [AS65065i] via 10.15.1.13 on eth1 10.107.30.248/32 unicast [worker_node_1 14:43:11.514] * (100) [AS65065i] via 10.15.1.13 on eth1 unicast [worker_node_3 14:43:12.011] (100) [AS65065i] via 10.15.1.13 on eth1 unicast [worker_node_2 14:43:12.300] (100) [AS65065i] via 10.15.1.12 on eth1
FRRouting:
sudo vtysh -c 'show ip route bgp' Codes: K - kernel route, C - connected, L - local, S - static, R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR, f - OpenFabric, t - Table-Direct, > - selected route, * - FIB route, q - queued, r - rejected, b - backup t - trapped, o - offload failure IPv4 unicast VRF default: B>* 10.10.37.0/26 [20/0] via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 B>* 10.10.120.0/26 [20/0] via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 B>* 10.10.246.0/26 [20/0] via 10.15.1.12, eth1, weight 1, 00:04:39 via 10.15.1.12, eth1, weight 1, 00:04:39 via 10.15.1.12, eth1, weight 1, 00:04:39 via 10.15.1.12, eth1, weight 1, 00:04:39 via 10.15.1.12, eth1, weight 1, 00:04:39 via 10.15.1.12, eth1, weight 1, 00:04:39 via 10.15.1.12, eth1, weight 1, 00:04:39 B>* 10.96.0.0/12 [20/0] via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 * via 10.15.1.12, eth1, weight 1, 00:04:39 * via 10.15.1.13, eth1, weight 1, 00:04:39 B>* 10.107.30.248/32 [20/0] via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 via 10.15.1.13, eth1, weight 1, 00:04:39 * via 10.15.1.12, eth1, weight 1, 00:04:39 * via 10.15.1.13, eth1, weight 1, 00:04:39
Так как маршруты появятся в основной таблице маршрутизации, для их просмотра можно использовать iproute2-утилиты.
Пример для bird2:
ip ro li | grep -E 'bird|bgp' 10.10.37.0/26 via 10.15.1.13 dev eth1 proto bird metric 32 10.10.120.0/26 via 10.15.1.13 dev eth1 proto bird metric 32 10.10.246.0/26 via 10.15.1.12 dev eth1 proto bird metric 32 10.15.1.0/24 dev eth1 proto kernel scope link src 10.15.1.50 10.96.0.0/12 via 10.15.1.13 dev eth1 proto bird metric 32 10.107.30.248 via 10.15.1.13 dev eth1 proto bird metric 32 10.222.7.0/24 dev eth0 proto kernel scope link src 10.222.7.3
Пример для frrouting:
10.10.37.0/26 nhid 40 via 10.15.1.13 dev eth1 proto bgp metric 20 10.10.120.0/26 nhid 45 via 10.15.1.13 dev eth1 proto bgp metric 20 10.10.246.0/26 nhid 46 via 10.15.1.12 dev eth1 proto bgp metric 20 10.96.0.0/12 nhid 42 proto bgp metric 20 10.107.30.248 nhid 42 proto bgp metric 20
Обратите внимание, что при выставлении спецификации сервиса externalTrafficPolicy: Local по BGP анонсируется префикс /32.
6. Проверим маршруты на воркер-нодах кластера с помощью node-shell.
На управляющей машине:
kubectl node-shell $(kubectl get nodes -o name | head -1)
В консоли воркер-ноды:
ip ro li | grep 10.15.1.50 172.19.7.0/24 via 10.15.1.50 dev eth0 proto bird
Как видим, анонсированный маршрут до подсети VPN есть в таблице маршрутизации.
4. Проверим доступ к подам и сервисам. Выполняем шаг на виртуальной машине с VPN-сервером, не забывая при этом заменить IP-адреса на полученные сервисами:
svc_ips="10.10.246.2 10.10.120.2 10.10.37.3" for ip in $svc_ips; do curl -sS http://${ip}:8088 ; done curl -sS http://10.104.145.219:8088 curl -sS http://10.96.239.8 dig @10.96.0.10 selectel.org curl -k -sS https://10.96.0.1
Пример успешного вывода:
dig @10.96.0.10 selectel.ru ; <<>> DiG 9.18.30-0ubuntu0.22.04.2-Ubuntu <<>> @10.96.0.10 selectel.ru ; (1 server found) ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32703 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ; COOKIE: d73752b62ecce39e (echoed) ;; QUESTION SECTION: ;selectel.ru. IN A ;; ANSWER SECTION: selectel.ru. 30 IN A 85.119.149.3 ;; Query time: 4 msec ;; SERVER: 10.96.0.10#53(10.96.0.10) (UDP) ;; WHEN: Wed May 14 16:12:41 MSK 2025 ;; MSG SIZE rcvd: 79 host kubernetes.default.svc.cluster.local 10.96.0.10 Using domain server: Name: 10.96.0.10 Address: 10.96.0.10#53 Aliases: kubernetes.default.svc.cluster.local has address 10.96.0.1 curl -ksS https://10.96.0.1 { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"", "reason": "Forbidden", "details": {}, "code": 403 }
Заключение
Что можно улучшить в описанном решении? Во-первых, добавить поддержку BFD — это ускорит сходимость BGP и сделает маршрутизацию более устойчивой. Во-вторых, при желании можно внедрить фильтрацию входящих префиксов в Calico — это поможет исключить влияние человеческих ошибок.
И все же, чего мы добились? Настроили облачную инфраструктуру так, чтобы маршруты до подов автоматически появлялись на нужных серверах. Разработчики теперь могут подключаться по VPN и обращаться к сервисам в Kubernetes напрямую по IP-адресам, без ручного добавления маршрутов. Это уже серьезный шаг к автоматизации и удобству.
А дальше — еще интереснее. Если вам полезен такой подход, можно отдельно разобрать:
-
как подключить BFD для повышения отказоустойчивости BGP-сессий;
-
как организовать разрешение имен вида
<namespace>.svc.cluster.localчерез dnsmasq и unbound для внешних сервисов, чтобы обращаться к подам по DNS, а не по IP.
Если такой разбор был бы интересен — дайте знать в комментариях!
ссылка на оригинал статьи https://habr.com/ru/articles/932696/
Добавить комментарий