Привет! На связи Борис Литвиненко из команды NOCDEV в Yandex Infrastructure — наша группа занимается автоматизацией сетей всего Яндекса. В прошлом году я уже рассказал о том, как и почему наши сетевые сервисы живут на отдельном железе с развёрнутым k8s‑кластером, избегая кольцевых зависимостей с остальной инфраструктурой компании. Среди прочего на тот момент мы использовали Calico VXLAN — с нашим разнообразным железом нам был нужен overlay, а Cilium тогда не умел работать с IPv6 для VXLAN. Однако всё меняется.

В 2025 году попытка включить Calico eBPF в нашем K8s‑кластере вылилась в запуск Cilium, в новом релизе которого как раз добавили поддержку IPv6 underlay для VXLAN. Поэтому сегодня я расскажу, как мы вместе с Кириллом Глушенковым @kglushen протестировали новый Cilium 1.18 — а заодно перешли с самописных salt‑рецептов для kubeadm на kubespray, столкнулись с не такими уж простыми особенностями dualstack, а ещё немного повайбкодили. Под катом — все наши приключения с нелинейным сюжетом.

Важная предыстория

Откуда взялся dualstack 

Одна из важных задач наших сервисов — прямое взаимодействие с сетевым оборудованием: как минимум, нужно собрать метрики, выполнить команды. При этом наши дата‑центры IPv6-only, а у части сетевых устройств в силу разных причин нет IPv6-адреса.

Проблему наличия IPv4 в IPv6-only‑кластерах решает подключение tun64 прямо в под, но так возникает лишняя точка отказа в виде декапсуляторов. Для повышения надёжности и резервирования мониторинга на случай сетевых проблем мы решили выделить пул железа с независимой связностью. На этом железе запланировано поднять нативную IPv4-сеть, а значит пора в кластере включать dualstack.

Для чего переходить с kubeadm на kubespray

Наших кластеров уже чуть более десятка, и их количество продолжает расти. В начале пути для создания кластера мы выбрали kubeadm в сочетании с парой самописных ролей на SaltStack. Но самописный salt‑рецепт для kubeadm не очень помогает в обслуживании: сложности вызывает обновление и отслеживание версий попутных бинарных файлов (containerd, crio и прочие).

Мы рассматривали разные возможности автоматизации, от k3s до cluster api. В итоге остановились на kubespray: там есть релизы, тесты, удобный апгрейд. Конечно же, kubespray не умел IPv6-only, пришлось добавить вот такой пул‑реквест. Однако для совместимости в dualstack‑конфигурации преимущество отдаётся IPv4-адресу хоста, и все IPv4-сетки настраиваются раньше IPv6. Запомним эту деталь, дальше это будет важно.

Ещё немного про Calico

В прошлой статье я уже рассказывал, что в нашей инфраструктуре очень разнообразные железки, у них может быть разная сеть, и самый простой способ жизни в разнородной сети — overlay, для чего мы используем Calico.

На Calico мы dualstack не делали, так как в этом случае Calico использует четвёртый underlay для IPv4 и шестой для IPv6. Но VXLAN внутри — это L2, а протокол транспорта не должен иметь значения. Я даже патчил Calico в попытке заставить использовать только IPv6 underlay.

При наличии такой фичи мы могли бы сделать dualstack‑кластер с IPv6 underlay, а четвёртую связанность наружу кластера сделать через tun64 на хостах. Но доказательства в виде кривого патча не заставили разработчиков Calico отрефакторить код и сделать такую фичу.

Ход событий и первая попытка перейти на eBPF

За время работы с k8s мы запомнили одно важное правило: «НЕ ПЫТАЙСЯ МЕНЯТЬ НАСТРОЙКИ СЕТИ В РАБОТАЮЩЕМ КЛАСТЕРЕ». Если что‑то идёт не так, и это что‑то надо поменять, есть два пути:

  • нет, тебе это не надо, просто терпи;

  • наливай новый кластер и переноси нагрузку.

Проект локализации мониторинга является greenfiled, а значит самое время пробовать обновлять Calico, включать dualstack и eBPF. Раз уж взялись за дело, то взялись по‑настоящему…

Обновляем Calico и включаем eBPF 

На первом же шаге включения eBPF всё сломалось.

Дебаг показал, что под даже не может получить MAC veth‑пары. NDP‑запросы обрезались, при этом политики в Calico разрешали всё.

С помощью чтения дебаг‑логов нам удалось понять, что неверным образом определяется IP protocol пакетов, из‑за этого дропается весь трафик. Далее мы не без помощи коллег подампали eBPF‑программы, проанализировали их map’ы, посмотрели код. Стало понятно, что часть eBPF map не заполнена, и мы получаем данную ошибку.

План на следующий день включал в себя включение ещё большего числа дебаг‑логов и метрик. Был получен ценный опыт дебага eBPF‑конструкций.

Документацию не читай, сразу тестируй! 

Параллельно с дебагом в одном из наших чатов коллеги закинули информацию о новом релизе Cilium 1.18, в котором добавилась поддержка IPv6 underlay для VXLAN. Неужели решена проблема, из‑за которой мы отказались от Cilium в самом начале выбора CNI? Ко всему прочему, Cilium обещает поддержку выбора underlay‑протокола, а ведь этого нам и не хватало в Calico!

Отступать было нельзя, надо было попробовать и Cilium. Кирилл поднял ещё один стенд с как тогда казалось рабочим IPv6-only Cilium. А я отложил Calico как бесперспективное направление. Мы не стали читать заранее все комментарии к пул‑реквестам, поскольку документация и PR были весьма разрознены. Как стало понятно дальше, это было зря.

Пробуем IPv6-only Cilium 

Конфигурация с IPv6-only была обкатана для Kubespray — всё‑таки именно для этого я разрабатывал пул‑реквест. Тестовый кластер на виртуалках LXD/KVM и SRIOV‑сетке засетапили быстро.

Cilium долго не удавалось запустить. В процессе дебага я перечитал ещё раз все values helm‑чарта Cilium (а там очень большой values). Обнаружил, что переменная underlayProtocol никак не зависит от остальных, то есть от выключенной четвёрки и включенной шестёрки. Значение переменной по умолчанию, конечно же, было "ipv4". 

Посетовав ещё раз на скорость внедрения IPv6 во всем мире, я поменял настройку, и всё заработало. Итоговый values получился таким:

Конфигурация Cilium для IPv6-only
enableIPv4: false
ipv4:
  enabled: false
enableIPv6: true
ipv6:
  enabled: true
ipam:
  mode: "kubernetes"
routingMode: tunnel
underlayProtocol: ipv6
tunnelProtocol: vxlan
tunnelPort: 6660
kubeProxyReplacement: true
localRedirectPolicies:
  enabled: true
k8sServiceHost: lab-k8s.net.yandex.net
k8sServicePort: 6443
MTU: 1450
securityContext:
  privileged: true
nodePort:
  enabled: true
monitor:
  enabled: true
hubble:
  enabled: true
  preferIpv6: true
  relay:
    enabled: true
  ui:
    enabled: true

Dualstack не так прост 

Виртуалки для тестирования не имели в наличии IPv4-адреса, поэтому я ожидал две проблемы:

  • Поднять базовый dualstack‑кластер без IPv4 на хосте, т. е. обмануть проверки kubespray и его приоритет IPv4 в dualstack‑сценарии.

  • Обмануть проверки Cilium на наличие IPv4 на хостах (если таковые будут).

Для dualstack IPv4 является приоритетом. Пришлось через vars частично обмануть Kubespray, получилось что‑то такое:

Кусок vars для kubespray
# Дуалстек в kubespray отдает приоритет ipv4, в том числе и для advertiseAddress kubeapi, данный хак решает проблему
kube_apiserver_address: "::"
kube_apiserver_ip: "{{ kube_service_addresses_ipv6 | ansible.utils.ipaddr('net') | ansible.utils.ipaddr(1) | ansible.utils.ipaddr('address') }}"
# Если на хосте нет четверки, то first_kube_control_plane_address получается 127.0.0.1 при дуалстеке, обманываем элегантным образом
first_kube_control_plane_address: "{{ hostvars[groups['kube_control_plane'][0]].ansible_default_ipv6.address }}"

# При решении проблемы выше возникает проблема, описанная в issue https://github.com/siderolabs/talos/issues/8115#issuecomment-2307280220
# ее обходим так (первый CIDR ставим с шестеркой, по умолчанию для дуалстека наоборот):
kube_kubeadm_apiserver_extra_args:
  service-cluster-ip-range: "{{ kube_service_addresses_ipv6 }},{{ kube_service_addresses }}"
kube_kubeadm_controller_extra_args:
  cluster-cidr: "{{ kube_pods_subnet_ipv6 }},{{ kube_pods_subnet }}"
  service-cluster-ip-range: "{{ kube_service_addresses_ipv6 }},{{ kube_service_addresses }}"

kube_apiserver_port: 6443  # (https)

# Kube-proxy proxyMode configuration.
# Can be ipvs, iptables
kube_proxy_bind_address: "::"
kube_proxy_mode: nftables
# kube_proxy_remove: true

kubelet_address: "::"
kubelet_bind_address: "::"

Можно представить, сколько итераций это потребовало!

Читать документацию полезно, а релизную информацию — жизненно необходимо 

Как мы помним, вся эта деятельность происходила в формате «а давай быстро попробуем». Соответственно обсуждения в пул‑реквестах тем более никто досконально не читал.

Dualstack‑кластер нам удалось сделать на Kubespray. Поэтому мы сразу задеплоили туда Cilium и... ничего не заработало. Агент Cilium пытался попинговать IPv4 с соседних нод.

Слово за слово, я даже написал на Ansible роль, которая делает full‑mesh‑VXLAN‑туннель с серой IPv4-сетью и вешает серую IPv4 на все хосты, чтобы обмануть Cilium агент. Выглядит это страшно, так что не будем это показывать.

Однако после создания самопальной IPv4-связанности всё заработало. По крайней мере, я думал, что всё заработало. Начал запускать сервисы и сразу почувствовал запах проблем с MTU.

Что ж, поэкспериментируем с MTU в Cilium‑конфиге и поменяем вручную на существующих роутах/интерфейсах. Посмотрев роуты и MTU на VXLAN‑интерфейсе Cilium через некоторое время дебага, мы увидели, что Cilium отрезает 50 байт для роута от размера основного вашего интерфейса. Но 50 — это общепринятое значение для IPv4, а для IPv6 — надо 70!

Нигде явно не было написано, какой underlay‑протокол в итоге используется для трафика. И у меня начало появляться смутное сомнение в том, что это НЕ шестёрка…

Вторая попытка и то, с чего стоило начинать

Я наконец решил почитать код и разрозненные комментарии к пул‑реквестам. Быстро выяснилось, что IPv6 underlay не поддержан для dualstack! А ещё не готова замена kubeproxy для IPv6-underlay‑транспорта.

Можно было сэкономить время, прочитав всё это заранее.

При этом код действительно отрезал для IPv6 70 байт. В этом плане всё было отлично, поэтому IPv6-only удалось запустить. Full‑mesh‑пинг между всеми хостами не был нужен Cilium‑агенту, но ему нужна была IPv4 на хосте: ведь через неё он и будет строить NAT наружу кластера.

Нашей затее конец? 

Стоит признать, что с высокой вероятностью мы даже не стали бы тестировать, если бы заранее прочитали всю нужную информацию, а не сделали неверный вывод из релизного сообщения. Но мы зашли слишком далеко, чтобы просто признать эксперимент не до конца удачным. IPv6-only нам завести удалось, то есть Calico уже можно было заменить в наших кейсах. Так что не опускаем руки.

Поиск решения я начал с чтения всех комментариев, связанных с темой закрытых

пул‑реквестов. В пул‑реквесте на добавления IPv6 underlay нашёлся связанный про добавление dualstack с IPv6 underlay, который был замёржен лишь 4 июля. Выглядело очень перспективно, но надо было ставить 1.19-dev, по сути, из main.

К чему в итоге пришли

Установка чарта из main сработала, и наш dualstack‑кластер с IPv6-only underlay заработал. Теперь достаточно накинуть tun64 на серверы без IPv4 — и мы получаем полностью рабочий кластер с dualstack и без хождения внутри по IPv4 underlay. Ура!

Выводы:

  • Мир наконец почти готов к IPv6! Но в целом IPv6 почти никому не нужен.

  • Даже при подходе «быстренько протестируем» лучше почитать всю связанную информацию, и этим сэкономить кучу времени.

  • Но если поторопиться, то можно научиться дебажить много чего нового и завести то, что и не стал бы пытаться, если бы заранее прочитал больше информации!

  • Вайбкодить full‑mesh‑VXLAN‑роль на yaml вполне можно, но лучше заранее подумать.

А что дальше?

Cilium развивается очень быстро, крупный релиз выходит раз в полгода или чаще. К моменту полноценного запуска production‑кластера с dualstack это как раз должно случиться. Поэтому делаем всё новые кластера на Cilium, осваиваем hubble и продолжаем развитие!

Комментарии (0)