Анатомия сквозной сетевой связности в самостоятельно управляемом Kubernetes на базе RKE2
Автор: Cuong Truong
Дата публикации: 2 октября 2026 года
Время чтения оригинала: 19 минут
Оригинал: Anatomy of End-to-End Connectivity in Self-Managed Kubernetes with RKE2
Дата публикации: 2 октября 2026 года
Время чтения оригинала: 19 минут
Оригинал: Anatomy of End-to-End Connectivity in Self-Managed Kubernetes with RKE2
Перевод статьи.
Сколько промежуточных узлов проходит запрос, прежде чем достигнет вашего Pod? В этой статье мы проследим каждый такой переход в самостоятельно управляемом кластере Kubernetes, построенном на RKE2.
Зачем нужна полная картина взаимодействий
Большинство сетевых инцидентов в Kubernetes связано не с поломкой отдельного компонента, а с нарушением связи между двумя компонентами. Ingress возвращает 502, Pod не может разрешить доменное имя, новый узел никак не переходит в состояние Ready — в каждом случае это одно разорванное звено длинной цепи.
В управляемых сервисах — EKS, GKE, AKS — провайдер скрывает большую часть этой цепи. Когда вы управляете кластером самостоятельно, за всё отвечаете вы: за балансировщик нагрузки, порты между узлами, CNI, kube-proxy, ingress-контроллер и DNS. Никто не будет отлаживать их за вас.
В статье рассматриваются четыре потока взаимодействия, от внешнего мира вглубь кластера:
- Управление: kubectl → kube-apiserver → etcd → kubelet → контейнер.
- Взаимодействие Pod между собой: CNI объединяет Pod на разных узлах в единую плоскую сеть.
- Входящий трафик: клиент из Интернета → балансировщик нагрузки → ingress → Service → Pod.
- DNS: Pod → NodeLocalDNS → CoreDNS → вышестоящий DNS-сервер.
Каждый поток сопоставляется с тем, как он реализован в RKE2, на основе официальной документации docs.rke2.io. Три схемы служат общими иллюстрациями; если поведение RKE2 отличается от показанного на схеме, это оговаривается в тексте.
Общая картина: всё взаимодействует через kube-apiserver
Kubernetes построен по звездообразной архитектуре: kube-apiserver находится в центре, а остальные компоненты подключаются к нему как лучи. Никто, кроме apiserver, не взаимодействует с etcd, и ни один компонент не отдаёт команды другому напрямую.
Рисунок 1. Полная архитектура и 16 потоков взаимодействия самостоятельно управляемого кластера Kubernetes.
Схема разделена на три зоны.
Если читать пронумерованные стрелки по порядку, получатся четыре группы потоков:
- Чтение и запись состояния, потоки 1–3. kubectl → apiserver ↔ etcd, по HTTPS и gRPC поверх TLS.
- Циклы управления, потоки 4–7. controller-manager, scheduler и cloud-controller-manager → apiserver, через HTTPS watch.
- Выполнение на узле, потоки 8–12. kubelet, kube-proxy и CNI → apiserver через HTTPS watch; kubelet → среда выполнения контейнеров через CRI gRPC.
- Данные приложения, потоки 13–16. клиент → ingress → Service → Pod, по HTTP/HTTPS и обычным TCP/UDP.
Ключевой момент: первые три группы относятся к плоскости управления — control plane, последняя — к плоскости передачи данных, data plane. Они разделены. Если apiserver откажет, вы не сможете развёртывать приложения, но уже запущенные Pod продолжат обслуживать трафик: необходимые правила уже находятся в ядре каждого узла.
При чтении рисунка нужно учитывать три особенности:
- Стрелка 13 не проходит через apiserver. На схеме фиолетовая линия от External Client касается блока kube-apiserver. В действительности трафик приложения направляется прямо в ingress-контроллер на рабочем узле и никогда не проходит через apiserver.
- Каждое соединение watch открывается со стороны клиента. Kubelet, kube-proxy и CNI подключаются к apiserver и поддерживают долгоживущее соединение. Apiserver обращается к kubelet на порт 10250 только при выполнении kubectl logs, exec или port-forward.
- Порты kube-proxy. Порт 10256 используется для проверки работоспособности; метрики kube-proxy доступны на 10249. Порты 10257, 10259 и 10258 у controller-manager, scheduler и cloud-controller-manager работают по HTTPS и предоставляют как проверки работоспособности, так и метрики.
Как RKE2 упаковывает эту архитектуру
RKE2 поставляет всё, что показано на рисунке 1, в виде одного бинарного файла, устанавливаемого на каждый узел. Узлы различаются только ролью при запуске: rke2 server или rke2 agent. В документации по архитектуре серверы описаны как «специализированные процессы агента»: сервер делает всё то же, что и агент, и дополнительно запускает плоскость управления.
Процесс rke2 выполняет функции супервизора в следующем порядке:
- Подготавливает необходимые файлы. Извлекает из образа rancher/rke2-runtime бинарные файлы и манифесты: containerd, kubelet, runc и утилиты ctr, crictl и kubectl.
- Запускает containerd. За этим дочерним процессом ведётся строгий контроль: если containerd завершается, rke2 тоже завершается.
- Запускает kubelet. Если kubelet завершается, rke2 перезапускает его.
- Записывает манифесты статических Pod. На сервере rke2 записывает манифесты etcd, kube-apiserver, kube-controller-manager и kube-scheduler в /var/lib/rancher/rke2/agent/pod-manifests/. Kubelet читает этот каталог и запускает соответствующие Pod.
- Устанавливает дополнения через helm-controller. Когда apiserver готов, helm-controller устанавливает входящие в поставку Helm-чарты: CNI, CoreDNS, ingress-контроллер и metrics-server.
Соответствие блоков рисунка 1 компонентам RKE2:
- kube-apiserver, kube-controller-manager, kube-scheduler, etcd: статические Pod на серверных узлах.
- cloud-controller-manager: встроенный CCM, который также включает ServiceLB; для замены на внешний используется disable-cloud-controller.
- kubelet и среда выполнения контейнеров: kubelet и containerd запускаются как дочерние процессы rke2.
- kube-proxy: статический Pod на каждом узле; отключается параметром disable-kube-proxy: true.
- Плагин CNI: Helm-чарт rke2-canal по умолчанию, rke2-cilium, rke2-calico или rke2-flannel.
- kube-dns / CoreDNS: Helm-чарт rke2-coredns с компонентом автоматического масштабирования.
- Ingress-контроллер: Helm-чарт rke2-traefik, используемый по умолчанию начиная с v1.36, или rke2-ingress-nginx.
Практическое следствие: здесь нет kubeadm и отдельного systemd-юнита для kubelet. Чтобы настроить дополнение, поместите HelmChartConfig в /var/lib/rancher/rke2/server/manifests/. Чтобы настроить плоскость управления, измените /etc/rancher/rke2/config.yaml и перезапустите службу.
Как агенты находят серверы
Отказоустойчивому кластеру нужен постоянный адрес регистрации перед серверными узлами: балансировщик четвёртого уровня, DNS с round-robin или виртуальный IP-адрес — VIP. Минимальная конфигурация для трёх типов узлов:
# Первый сервер token: my-shared-secret tls-san: - k8s.example.com # Второй и третий серверы server: https://k8s.example.com:9345 token: my-shared-secret tls-san: - k8s.example.com # Агент (рабочий узел) server: https://k8s.example.com:9345 token: my-shared-secret
Об этом подключении стоит помнить три вещи:
- Порт 9345 — это API супервизора, а не Kubernetes API. Новый узел обращается к нему для регистрации и получения сертификатов и конфигурации. Kubernetes API, как обычно, остаётся на порту 6443.
- После присоединения агент сам балансирует свои подключения. Kubelet и kube-proxy на агенте подключаются к клиентскому балансировщику, который находится внутри процесса rke2 agent и слушает на loopback-интерфейсе. Этот процесс отслеживает список серверов и распределяет подключения между ними. Исходный адрес регистрации больше не находится на пути каждого запроса.
- Обратное направление проходит через туннель. При egress-selector-mode: agent — значении по умолчанию — apiserver обращается к kubelet через WebSocket-туннель, ранее открытый агентом. Поэтому при выполнении logs или exec apiserver не нужно самостоятельно устанавливать соединение с каждым агентом.
Количество серверных узлов должно быть нечётным, чтобы etcd мог сохранять кворум. Три сервера выдерживают потерю одного узла; именно такую конфигурацию рекомендует RKE2.
Матрица портов: кому разрешено взаимодействовать с кем
Если узел RKE2 не может присоединиться к кластеру, причиной обычно оказывается межсетевой экран, блокирующий один из перечисленных ниже портов. Список взят со страницы Requirements документации RKE2 и относится к CNI по умолчанию — Canal.
- 6443/TCP — Kubernetes API.Все узлы → Серверные узлы.
- 9345/TCP — API супервизора RKE2: регистрация.Все узлы → Серверные узлы.
- 10250/TCP — kubelet.Все узлы → Все узлы.
- 2379/TCP — Клиентские подключения etcd.Серверные узлы → Серверные узлы.
- 2380/TCP — Взаимодействие участников etcd.Серверные узлы → Серверные узлы.
- 2381/TCP — Метрики etcd.Серверные узлы → Серверные узлы.
- 30000–32767/TCP — Диапазон NodePort.Все узлы → Все узлы.
- 8472/UDP — Canal VXLAN.Все узлы → Все узлы.
- 9099/TCP — Проверки работоспособности Canal.Все узлы → Все узлы.
- 51820, 51821/UDP — Canal WireGuard для IPv4 и IPv6, если включён.Все узлы → Все узлы.
- 80, 443/TCP — Порты, занимаемые ingress-контроллером.Внешний балансировщик → Узлы с ingress.
Как читать список портов с учётом ролей:
- Балансировщику регистрации достаточно перенаправлять TCP 9345 и 6443 на серверные узлы.
- Серверные узлы дополнительно открывают друг для друга порты 2379–2381. Никаким другим узлам доступ к etcd не разрешается.
- Все узлы открывают друг для друга UDP 8472. Через этот порт проходит весь трафик между Pod на разных узлах.
RKE2 прямо предупреждает: порт VXLAN нельзя открывать в Интернет. Любой, кто может отправлять на него пакеты, может получить доступ к сети Pod. Cilium, Calico и Flannel используют другие наборы портов; например, Calico нужны TCP 179 для BGP и UDP 4789 для VXLAN.
Поток управления: от kubectl apply до запущенного контейнера
Ни один компонент не «отправляет команды» другому. Каждый наблюдает за apiserver, действует, когда желаемое состояние отличается от фактического, и записывает результат обратно. При применении Deployment происходит следующая цепочка событий:
- kubectl → apiserver. kubectl читает kubeconfig — в RKE2 это /etc/rancher/rke2/rke2.yaml — и отправляет HTTPS-запрос на порт 6443.
- Apiserver проверяет запрос, затем записывает данные в etcd. Запрос проходит аутентификацию, авторизацию RBAC и admission-проверки. Если всё в порядке, apiserver записывает объект в etcd по gRPC на порт 2379.
- Controller-manager создаёт Pod. Контроллер Deployment замечает новый Deployment и создаёт ReplicaSet; контроллер ReplicaSet создаёт Pod, которым ещё не назначены узлы.
- Scheduler назначает узел. Он обнаруживает Pod без nodeName, выбирает подходящий узел и записывает привязку через apiserver.
- Kubelet подхватывает работу. Kubelet выбранного узла видит привязанный к нему Pod и обращается к containerd через CRI — gRPC по Unix-сокету — для создания изолированного окружения, sandbox.
- CNI настраивает сеть. При создании sandbox containerd вызывает плагин CNI, чтобы назначить IP-адрес и подключить сетевой интерфейс Pod к узлу.
- Контейнеры запускаются. Kubelet загружает образы, запускает контейнеры, выполняет проверки и сообщает состояние в apiserver.
- Service начинает указывать на Pod. Когда Pod переходит в состояние Ready, контроллер EndpointSlice добавляет его IP-адрес в EndpointSlice соответствующего Service.
- Плоскость передачи данных обновляется. Kube-proxy на каждом узле и ingress-контроллер наблюдают за EndpointSlice, затем обновляют свои правила и списки бэкендов.
Шаги 8 и 9 — место соприкосновения плоскости управления и плоскости передачи данных. Между переходом Pod в состояние Ready и появлением правил на каждом узле всегда есть небольшая задержка. Такая же задержка при удалении Pod объясняет, почему приложениям необходимо корректное завершение работы — graceful shutdown.
При выполнении kubectl logs или kubectl exec направление меняется: apiserver обращается к kubelet на порт 10250. В RKE2 это обращение проходит внутри туннеля, описанного в предыдущем разделе.
Сеть Pod: CNI превращает множество узлов в одну плоскую сеть
Сетевая модель Kubernetes предъявляет одно требование: каждый Pod должен иметь возможность обращаться к любому другому Pod по его реальному IP-адресу, без NAT. Плагин CNI — компонент, который обеспечивает выполнение этого требования.
RKE2 использует следующие диапазоны адресов по умолчанию, задаваемые в серверном config.yaml:
- cluster-cidr: 10.42.0.0/16. Диапазон IP-адресов Pod.
- service-cidr: 10.43.0.0/16. Диапазон ClusterIP сервисов.
- cluster-dns: 10.43.0.10. ClusterIP CoreDNS.
- service-node-port-range: 30000-32767. Диапазон NodePort.
На схемах в статье адреса Pod показаны как 10.244.x.x. Это привычный диапазон кластера kubeadm с Flannel; в RKE2 вы увидите 10.42.x.x.
Canal — выбор по умолчанию
Canal объединяет два проекта: Flannel отвечает за трафик между узлами, Calico — за трафик внутри узла и сетевые политики. Пакет проходит по одному из двух путей:
- В пределах одного узла. Пакет выходит из Pod через пару veth, попадает в таблицу маршрутизации узла и входит в veth целевого Pod. Инкапсуляции нет.
- Между разными узлами. Исходный узел инкапсулирует пакет в VXLAN и отправляет его по UDP 8472 на IP-адрес целевого узла. Тот снимает инкапсуляцию и доставляет пакет в Pod. На рисунке 2 это блок «If endpoint is on another node» — «Если конечная точка находится на другом узле».
Поскольку VXLAN добавляет заголовок к каждому пакету, MTU сети Pod должен быть меньше MTU сети узлов. Несоответствие MTU — классическая причина ситуации «маленькие запросы работают, большие зависают».
Четыре варианта CNI
- Canal, по умолчанию. Flannel VXLAN между узлами, Calico внутри узла. Сетевые политики: да. Узлы Windows: нет.
- Cilium. Плоскость передачи данных на eBPF, способная заменить kube-proxy; Hubble обеспечивает наблюдаемость. Сетевые политики: да. Узлы Windows: нет.
- Calico. Плоскость передачи данных на eBPF доступна начиная с v1.33.8+rke2r1; есть поддержка BGP. Сетевые политики: да. Узлы Windows: да.
- Flannel. Только VXLAN-бэкенд; самый простой вариант. Сетевые политики: нет. Узлы Windows: да.
CNI выбирается одной строкой в config.yaml:
cni: cilium
Три предупреждения из документации RKE2:
- После создания кластера изменить выбор нельзя. RKE2 не поддерживает смену основного CNI, его бэкенда или CIDR в работающем кластере. Dual-stack также нужно включать с самого начала.
- firewalld конфликтует с Canal. На узлах рекомендуется отключить firewalld.
- NetworkManager вмешивается в работу интерфейсов CNI. Укажите ему не управлять ими:
# /etc/NetworkManager/conf.d/rke2-canal.conf [keyfile] unmanaged-devices=interface-name:flannel*;interface-name:cali*;interface-name:tunl*
Плоскость передачи данных Service: kube-proxy не находится на пути пакета
ClusterIP — виртуальный адрес: он не назначен ни одному интерфейсу. Он существует только в виде правил в ядре каждого узла, а kube-proxy записывает эти правила.
Самое распространённое заблуждение — считать kube-proxy прокси-сервером, через который обязан пройти каждый пакет. На самом деле это контроллер: он наблюдает за Service и EndpointSlice в apiserver и программирует правила ядра. Пересылку выполняет ядро Linux. Если kube-proxy завершится, существующие соединения продолжат работать; просто перестанут обновляться правила.
Куда направляется пакет, адресованный ClusterIP
В режиме iptables пакет проходит следующую последовательность цепочек в таблице nat:
PREROUTING / OUTPUT -> KUBE-SERVICES сопоставление с ClusterIP:port сервиса -> KUBE-SVC-xxxx случайный выбор одной конечной точки -> KUBE-SEP-xxxx DNAT на PodIP:port
После DNAT адресом назначения становится реальный IP-адрес Pod, а дальнейшую маршрутизацию выполняет CNI. Ядро записывает преобразование в таблицу conntrack, поэтому адреса ответных пакетов преобразуются обратно без дополнительного правила. На рисунке 2 это зелёный «Response path» — «Обратный путь ответа».
Три режима в Linux
- iptables. Стабильный режим, используемый по умолчанию. Выбирает бэкенд случайным образом.
- ipvs. Объявлен устаревшим начиная с Kubernetes v1.35, отключается по умолчанию с v1.40 и удаляется в v1.43.
- nftables. Преемник iptables. Требует ядро 5.13 или новее; экспериментально поддерживается в RKE2 начиная с v1.36.3+rke2r1.
Если ваш кластер работает в режиме IPVS, запланируйте переход с него. В RKE2 режим переключается через kube-proxy-arg:
kube-proxy-arg: - proxy-mode=nftables
Четвёртый вариант — полностью отказаться от kube-proxy. Cilium заменяет его средствами eBPF и требует ядро 5.8 или новее:
# /etc/rancher/rke2/config.yaml cni: cilium disable-kube-proxy: true # /var/lib/rancher/rke2/server/manifests/rke2-cilium-config.yaml apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: rke2-cilium namespace: kube-system spec: valuesContent: |- kubeProxyReplacement: true k8sServiceHost: "localhost" k8sServicePort: "6443"
NodePort и externalTrafficPolicy
NodePort использует тот же механизм, но с другим условием сопоставления: вместо ClusterIP правило сопоставляет порт из диапазона 30000–32767 на IP-адресе узла. Поле externalTrafficPolicy определяет, куда принимающий узел может переслать пакет:
- Cluster: на любую готовую конечную точку кластера, в том числе на другом узле. Нагрузка распределяется равномерно, но исходный IP-адрес клиента теряется из-за SNAT.
- Local: только на конечные точки того же узла. Исходный IP-адрес сохраняется, но внешний балансировщик должен проверять работоспособность узлов, чтобы исключать те, на которых нет конечных точек.
Входящий трафик: два пути в кластер
Запрос из Интернета может попасть в ingress-контроллер двумя способами. Разница сводится к одному вопросу: проходит ли пакет через правила Service, созданные kube-proxy, прежде чем достигнет ingress?
Рисунок 2. Два сквозных пути входящего трафика. Слева — через Service типа NodePort или LoadBalancer. Справа — ingress напрямую занимает порты узла.
Случай 1: через Service типа NodePort или LoadBalancer
Внешний балансировщик перенаправляет запрос на NodeIP:NodePort. Пакет попадает в netfilter, соответствует правилу, созданному kube-proxy, и с помощью DNAT направляется в Pod ingress-контроллера. Этот Pod может находиться на другом узле, а не на том, который принял пакет.
Это наиболее распространённая конфигурация в облаках и при использовании локальных балансировщиков, например F5. Преимущество в том, что ingress-контроллер работает как обычный Deployment и свободно масштабируется. Недостаток — дополнительный DNAT и, возможно, ещё один переход между узлами.
Случай 2: ingress напрямую занимает порты узла
Ingress-контроллер слушает непосредственно на портах 80 и 443 узла. Внешний балансировщик лишь перенаправляет TCP-трафик на NodeIP:80 и NodeIP:443. Перед ingress нет Service.
Это конфигурация RKE2 по умолчанию. Ingress-контроллер развёртывается как DaemonSet с hostPort 80 и 443. Документация RKE2 прямо указывает следствие: эти два порта больше нельзя использовать для других HostPort или NodePort Service в кластере.
Есть два варианта, различающихся на уровне ядра:
- hostNetwork: true. Pod использует сетевое пространство имён узла. Процесс ingress открывает сокет непосредственно на IP-адресе узла; DNAT отсутствует.
- hostPort. У Pod по-прежнему есть собственный IP-адрес в сети Pod. Плагин CNI portmap устанавливает правило DNAT с NodeIP:80 на PodIP:80. Пакет всё ещё проходит через netfilter, но не через правила Service, созданные kube-proxy.
Переход после ingress: что упрощено на рисунке 2
На рисунке 2 ingress перенаправляет трафик на ClusterIP бэкенд-сервиса, после чего kube-proxy выполняет DNAT на Pod. Ни один из двух ingress-контроллеров, входящих в поставку RKE2, по умолчанию так не работает:
- ingress-nginx помещает список конечных точек — PodIP:port — непосредственно в upstream-конфигурацию NGINX. Только аннотация nginx.ingress.kubernetes.io/service-upstream переключает его на ClusterIP.
- Traefik самостоятельно балансирует нагрузку между IP-адресами Pod. Только опция nativeLB переключает его на ClusterIP.
Иными словами, при настройках по умолчанию ingress использует Service для поиска Pod, но не для обращения к ним. Шаги 9–11 в случае 1 и 5–7 в случае 2 выполняются только при включении одной из этих двух опций. Этап маршрутизации CNI остаётся в любом случае: если бэкенд-Pod находится на другом узле, пакет передаётся через VXLAN.
Полный путь в кластере RKE2 с настройками по умолчанию
- Клиент разрешает app.example.com в VIP внешнего балансировщика.
- Балансировщик перенаправляет TCP-трафик на NodeIP:443 работоспособного узла.
- Правило hostPort выполняет DNAT пакета в Pod ingress-контроллера на том же узле.
- Ingress-контроллер завершает TLS-соединение, сопоставляет хост и путь с правилами Ingress и выбирает IP-адрес Pod из EndpointSlice.
- Ingress открывает новое соединение с этим IP-адресом Pod. Если Pod на том же узле — через veth. Если на другом — через VXLAN, UDP 8472.
- Pod приложения обрабатывает запрос и отвечает. Благодаря conntrack ответ проходит тот же путь в обратном направлении.
Переход с ingress-nginx на Traefik
Сопровождение проекта ingress-nginx прекратилось в марте 2026 года. RKE2 реагирует на это по следующему графику:
- До v1.35 включительно: ingress-nginx используется по умолчанию и поддерживается.
- v1.36: по умолчанию используется Traefik; ingress-nginx всё ещё входит в поставку, но помечен как устаревший.
- v1.37: по умолчанию используется Traefik; ingress-nginx удалён и недоступен для новых кластеров.
В руководстве RKE2 по миграции оба контроллера запускаются параллельно: ingress-nginx сохраняет порты 80 и 443, а Traefik временно использует hostPort 8000 и 8443.
# /etc/rancher/rke2/config.yaml, этап параллельной работы ingress-controller: - ingress-nginx - traefik
При переключении балансировщика на Traefik измените и проверку работоспособности: ingress-nginx отвечает на /healthz, Traefik — на /ping.
DNS: поток взаимодействия, который чаще всего упускают из виду
Почти каждое соединение в кластере начинается с DNS-запроса, поэтому при поломке DNS кажется, что сломалось вообще всё. На рисунке 3 показана реальная DNS-архитектура тестового кластера staging и инцидент, из которого стоит извлечь урок.
Рисунок 3. DNS-архитектура тестового кластера: Pod → NodeLocalDNS → CoreDNS → вышестоящий DNS-сервер, а также ошибочная цепочка, вызвавшая петлю. Подписи на вьетнамском: «Luồng» — поток, «Trả kết quả» — возврат ответа, «Luồng lỗi cũ» — прежний ошибочный поток.
Три уровня разрешения имён
- Pod. Kubelet записывает /etc/resolv.conf для каждого Pod: один nameserver, три поисковых домена и options ndots:5.
- NodeLocalDNS. DaemonSet, запускающий CoreDNS в качестве кэша и слушающий на link-local-адресе непосредственно на узле. Pod сначала обращается к нему.
- CoreDNS. Deployment в kube-system. Плагин kubernetes отвечает на запросы кластерных имён на основе данных apiserver; плагин forward отправляет всё остальное вышестоящим DNS-серверам.
В документации Kubernetes указаны три причины использовать NodeLocalDNS: обход DNAT и conntrack уменьшает вероятность состояний гонки, соединение с CoreDNS переводится с UDP на TCP, а высокие задержки, вызванные потерями UDP-пакетов, сокращаются.
Три потока DNS-запросов
- Внутрикластерный Service, например kubernetes.default.svc.cluster.local. Отвечает плагин kubernetes в CoreDNS. Запрос не покидает кластер.
- Внутренний домен компании, например *.cmctelecom.vn. Отвечает вышестоящий DNS-сервер компании.
- Публичный домен, например google.com. Отвечает вышестоящий DNS-сервер.
При чтении рисунка 3 нужно учитывать два момента:
- Адреса на схеме отличаются от значений RKE2 по умолчанию. На рисунке для NodeLocalDNS используется 169.254.25.10, а для Service CoreDNS — 10.233.0.3, что соответствует значениям Kubespray по умолчанию. В RKE2 их аналоги — 169.254.20.10 и 10.43.0.10.
- Путь для внешних имён может быть короче. На схеме каждый запрос проходит через CoreDNS. В примере конфигурации Kubernetes NodeLocalDNS передаёт в CoreDNS только кластерные имена, а внешние отправляет напрямую вышестоящим DNS-серверам.
Цена ndots:5
При ndots:5 любое имя, содержащее меньше пяти точек, сначала проверяется с поочерёдным добавлением каждого поискового домена. Поэтому запрос google.com из Pod выглядит так:
google.com.<namespace>.svc.cluster.local -> NXDOMAIN google.com.svc.cluster.local -> NXDOMAIN google.com.cluster.local -> NXDOMAIN google.com. -> ответ
Один исходящий вызов превращается в четыре запроса, а если приложение запрашивает и A, и AAAA — в восемь. Кэш NodeLocalDNS берёт на себя большую часть этих издержек. Для приложений с большим количеством внешних обращений указывайте имена с завершающей точкой — api.example.com. — или уменьшайте ndots через dnsConfig Pod.
Инцидент с петлёй на рисунке 3
В правом нижнем углу схемы показана DNS-петля: CoreDNS перенаправляет запрос в NodeLocalDNS, а NodeLocalDNS — обратно в CoreDNS. Причина заключается в цепочке наследования настроек, которую мало кто замечает:
- Corefile в CoreDNS содержит forward . /etc/resolv.conf.
- Pod CoreDNS не использует кластерный DNS. Он наследует резолвер узла, то есть файл, на который kubelet указывает параметром --resolv-conf.
- На проблемном узле в этом файле nameserver был задан как 169.254.25.10 — адрес самого NodeLocalDNS.
- CoreDNS отправлял запросы «вверх», что на деле означало обратно в NodeLocalDNS, а тот снова передавал их в CoreDNS.
Плагин CoreDNS loop предназначен именно для таких случаев. Он отправляет самому себе пробный запрос; если тот возвращается больше двух раз, плагин останавливает CoreDNS и записывает в журнал Loop ... detected for zone ".". Обычный симптом — Pod CoreDNS переходит в состояние CrashLoopBackOff.
Исправление, показанное на схеме, задаёт для узла настоящий вышестоящий DNS-сервер:
# /etc/systemd/resolved.conf на рабочем узле [Resolve] DNS=183.91.10.1 183.91.10.2 Domains=cloud.cmctelecom.vn
После исправления /etc/resolv.conf внутри Pod CoreDNS содержит два реальных DNS-сервера, и цепочка становится линейной: Pod → NodeLocalDNS → CoreDNS → вышестоящий DNS-сервер.
Как с этим работает RKE2
В RKE2 есть встроенная защита. При запуске каждый узел проверяет /etc/resolv.conf и /run/systemd/resolve/resolv.conf. Файл с DNS-серверами на loopback-, multicast- или link-local-адресах отклоняется. Если оба файла непригодны, RKE2 создаёт резервный файл, указывающий на 8.8.8.8 и 2001:4860:4860::8888.
Защита предотвращает петлю, но оставляет две ловушки:
- Среды без доступа в Интернет. Резервный 8.8.8.8 недоступен, и внутренние домены компании также не разрешаются. Явно укажите файл резолвера параметром resolv-conf.
- Файл, заданный вручную, не проверяется. Если указать в resolv-conf файл с адресом NodeLocalDNS, вы сами воспроизведёте инцидент с рисунка 3.
NodeLocal DNSCache включается в RKE2 через HelmChartConfig:
# /var/lib/rancher/rke2/server/manifests/rke2-coredns-config.yaml apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: rke2-coredns namespace: kube-system spec: valuesContent: |- nodelocal: enabled: true
Дальнейшие настройки зависят от режима kube-proxy:
- iptables. NodeLocalDNS слушает и на ClusterIP kube-dns, и на link-local-адресе. Менять cluster-dns у kubelet не требуется.
- IPVS. NodeLocalDNS слушает только на link-local-адресе. Добавьте ipvs: true в значения чарта и укажите для kubelet cluster-dns=169.254.20.10.
- Cilium вместо kube-proxy. Включите Local Redirect Policy в Cilium и задайте use_cilium_lrp: true в чарте CoreDNS.
Список проверок при отладке: по уровням, снаружи внутрь
Если запрос не доходит, не гадайте. Проходите уровни в том порядке, в котором движется пакет, и останавливайтесь на первом, который работает неправильно.
- Внешний балансировщик. Симптом: тайм-аут сразу на клиенте. Первая проверка: успешна ли проверка работоспособности NodeIP:80/443?
- Ingress-контроллер. Симптом: ingress возвращает 404 или 503. Первая проверка: есть ли правило Ingress, соответствующее хосту и пути, и что пишется в журналах контроллера?
- Service и конечные точки. Симптом: 503 или отказ в соединении — connection refused. Первая проверка: есть ли IP-адреса Pod в EndpointSlice и находится ли Pod в состоянии Ready?
- kube-proxy. Симптом: обращение к ClusterIP зависает, а по IP-адресу Pod всё работает. Первая проверка: присутствуют ли на узле правила этого Service?
- CNI. Симптом: трафик внутри узла проходит, между узлами — зависает. Первая проверка: UDP 8472 между узлами, MTU, firewalld.
- DNS. Симптом: «could not resolve host» — «не удалось разрешить имя хоста». Первая проверка: resolv.conf внутри Pod и сообщения Loop в журналах CoreDNS.
- Плоскость управления. Симптом: узел в состоянии NotReady или не может присоединиться. Первая проверка: TCP 9345 и 6443 до серверов и журналы rke2-agent.
Соответствующие команды на узле RKE2:
# Утилиты RKE2 по умолчанию отсутствуют в PATH export PATH=$PATH:/var/lib/rancher/rke2/bin export KUBECONFIG=/etc/rancher/rke2/rke2.yaml export CRI_CONFIG_FILE=/var/lib/rancher/rke2/agent/etc/crictl.yaml # Плоскость управления: служба и статические Pod systemctl status rke2-server # или rke2-agent journalctl -u rke2-agent -f crictl ps # работающие контейнеры, даже если apiserver недоступен nc -zv k8s.example.com 9345 nc -zv k8s.example.com 6443 # Service и конечные точки kubectl get endpointslices -l kubernetes.io/service-name=my-svc kubectl get pods -o wide -l app=my-app # kube-proxy, режим iptables iptables-save -t nat | grep my-svc conntrack -L -d 10.43.0.10 | head # CNI: выходят ли VXLAN-пакеты с этого узла и приходят ли на другой tcpdump -ni any udp port 8472 ip -d link show flannel.1 # показывает MTU и VNI # DNS kubectl run dns-test --rm -it --restart=Never --image=busybox:1.36 \ -- nslookup kubernetes.default.svc.cluster.local kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50
Два совета, следующих из предыдущих разделов:
- Отделяйте DNS от сетевой связности. Сначала обратитесь к IP-адресу Pod, затем к ClusterIP, затем к имени Service. Первый неудачный шаг укажет на уровень: CNI, kube-proxy или DNS.
- Сравнивайте трафик внутри узла и между узлами. Если проблема возникает только тогда, когда исходный и целевой Pod находятся на разных узлах, причина почти всегда в оверлейной сети: заблокирован порт 8472, не совпадает MTU или мешает аппаратная разгрузка вычисления контрольных сумм — checksum offload.
Заключение
Запрос достигает Pod, потому что четыре уровня одновременно работают правильно: плоскость управления записала корректное состояние, CNI настроил нужную сеть, на узле присутствуют правила Service, а DNS возвращает правильный адрес. RKE2 не меняет эту модель; он упаковывает её в один бинарный файл и выбирает разумные настройки по умолчанию.
Знания этих настроек достаточно, чтобы правильно читать любую общую схему: Canal с VXLAN на UDP 8472, Pod в 10.42.0.0/16, Service в 10.43.0.0/16, ingress в виде DaemonSet с hostPort 80 и 443, CoreDNS на 10.43.0.10. Остальное — дисциплина при отладке: следуйте за пакетом, переход за переходом.
Источники
Документация RKE2
- «Анатомия дистрибутива Kubernetes нового поколения» — Anatomy of a Next Generation Kubernetes Distribution: архитектура, жизненный цикл процессов, статические Pod.
- «Требования» — Requirements: таблицы входящих портов.
- «Высокая доступность» — High Availability: постоянный адрес регистрации, порт 9345, кворум etcd.
- «Базовые сетевые настройки» — Basic Network Options: Canal, Cilium, Calico, Flannel, nftables.
- «Сетевые сервисы» — Networking Services: CoreDNS, NodeLocal DNSCache, ingress-контроллер, ServiceLB.
- «Справочник конфигурации сервера» — Server Configuration Reference: CIDR, DNS и диапазон NodePort по умолчанию.
- «Руководство по миграции с Ingress NGINX на Traefik» — Ingress NGINX to Traefik Migration Guide: DaemonSet, hostPort, параллельная работа.
- «Расширенные параметры и конфигурация» — Advanced Options and Configuration: проверки resolv.conf, каталог манифестов.
- «Известные проблемы и ограничения» — Known Issues and Limitations: firewalld, NetworkManager, checksum offload.
- «Доступ к кластеру» — Cluster Access: kubeconfig и пути к утилитам.
- Обсуждение «kubelet apiserver ha»: клиентский балансировщик внутри агента.
Kubernetes и связанные проекты
- «Виртуальные IP-адреса и прокси сервисов» — Virtual IPs and Service Proxies: kube-proxy, три режима, график удаления IPVS.
- «Использование NodeLocal DNSCache» — Using NodeLocal DNSCache: назначение, различия в поведении iptables и IPVS.
- «DNS для Service и Pod» — DNS for Services and Pods: DNS-записи, поисковые домены, ndots.
- «Взаимодействие между узлами и плоскостью управления» — Communication between Nodes and the Control Plane: звездообразная модель.
- Плагин CoreDNS loop: обнаружение петель и способы исправления.
- «Базовые сетевые настройки K3s» — K3s Basic Network Options: egress selector и WebSocket-туннель.
- Аннотации ingress-nginx: service-upstream.
- Traefik Kubernetes Service: nativeLB.
- SUSE, «Переход с NGINX на Traefik в RKE2» — Moving from NGINX to Traefik in RKE2: сроки прекращения сопровождения ingress-nginx.
kubernetes
devops