惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
V
V2EX
C
Check Point Blog
GbyAI
GbyAI
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
博客园 - Franky
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Security Blog
Microsoft Security Blog
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
The Cloudflare Blog
S
SegmentFault 最新的问题
Latest news
Latest news
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
I
InfoQ
博客园 - 【当耐特】
NISL@THU
NISL@THU
A
About on SuperTechFans
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
雷峰网
雷峰网
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
V
Vulnerabilities – Threatpost
Security Archives - TechRepublic
Security Archives - TechRepublic
A
Arctic Wolf
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
IT之家
IT之家
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
SecWiki News
SecWiki News
大猫的无限游戏
大猫的无限游戏
S
Security Affairs
The Register - Security
The Register - Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题
T
Tor Project blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
IP подов кончились, а обычные решения не подошли: как мы расширили сеть на проде, не пересоздавая кластер (кейс + гайд)
Nikita Ermilov · 2026-06-16 · via Все публикации подряд на Хабре

11 мин

8

Четверг, на часах 11:03. В мониторинге загорелся алерт K8SKubeletTooManyPods. Смотрим ближе: на нескольких узлах-гипервизорах нашего внутреннего облака заканчивается сеть подов. Каждый узел получил /24-подсеть — 254 адреса. Для обычного кластера этого хватит с запасом, но у нас на каждом гипервизоре запущены десятки виртуальных машин, и каждая из них — это под.

Меня зовут Никита Ермилов, я инженер в команде разработки Deckhouse в компании «Флант». В этой статье я расскажу, как мы расширяли подсеть подов в работающем кластере, зачем полезли руками в etcd и что из этого вышло. Материал будет полезен любому, кто столкнулся с нехваткой IP-адресов в Kubernetes — вне зависимости от дистрибутива и CNI.

Для контекста: облако на Kubernetes

Внутри компании у нас работает приватное облако. Архитектурно это обычный Kubernetes-кластер с подключённым хранилищем и модулем виртуализации на базе переработанного KubeVirt. Этот модуль позволяет запускать виртуальные машины и управлять ими так же, как и подами. Каждый сотрудник может поднять себе пару виртуалок или целый Kubernetes-кластер для разработки, тестирования, обучения. Наш кластер состоит из 10 узлов — это почти 2300 ядер, 9,5 ТБ ОЗУ и 50 ТБ пользовательских данных.

Приватное облако построено на базе Deckhouse Kubernetes Platform (DKP), но всё, о чём пойдёт речь ниже, относится к механике ванильного Kubernetes. Если вы используете kubeadm или любой другой дистрибутив, подход тоже применим.

Вот конфигурация сети в kube-controller-manager на момент инцидента:

Параметр

Значение

cluster-cidr

10.112.0.0/16

node-cidr-mask-size

24

CNI

Cilium (IPAM: kubernetes)

  • cluster-cidr— общий пул IP-адресов для всей сети подов кластера;

  • node-cidr-mask-size — маска подсети, которую каждый узел «откусывает» из пула cluster-cidr. Чем меньше это число, тем больше адресов получает узел, но при этом общая вместимость кластера по узлам снижается.

С такими параметрами кластер вмещает до 256 узлов. Каждый узел получает /24-подсеть — 254 адреса для подов. Для 10 узлов-гипервизоров с сотнями виртуальных машин этого оказалось мало — кластер позволял запустить больше ВМ по ресурсам, но мы упёрлись в сеть.

Стандартные решения и почему они не подошли

Сначала мы рассмотрели три классических подхода.

Добавить узел. Самый очевидный путь: больше узлов — больше адресов. Но серверы нашей конфигурации стоят дорого, а мониторинг показывал, что CPU и память утилизированы далеко не полностью. Мы упирались именно в сеть, а не в вычислительные ресурсы. Покупать сервер ради IP-адресов — расточительство.

Расширить пул на уровне CNI. Cilium в режиме ipam: cluster-pool позволяет добавить дополнительную подсеть в clusterPoolIPv4PodCIDRList — и она применится автоматически. Но в нашем кластере IPAM настроен как kubernetes: CNI не управляет подсетями, а получает их от kube-controller-manager. Такой подход даёт гибкость при смене CNI, но лишает возможности «просто добавить пул». Если ваш CNI работает в режиме cluster-pool — вам повезло, и эта статья вам, скорее всего, не нужна. С Calico, Flannel и всеми остальными CNI ситуация аналогичная.

Заново создать кластер. Обычно мы рекомендуем поднять новый кластер с нужной конфигурацией и перенести нагрузку. В нашем случае это было невозможно:

  • поднять второй аналогичный кластер — это удвоить стоимость железа;

  • миграция виртуальных машин и дисков займёт недели;

  • разделить текущий кластер пополам и собрать из половины узлов второй кластер тоже нельзя — нагрузка не поместится на половине узлов.

Итого: стандартные варианты нам не подходили — покупать сервер дорого и нерационально, расширить пул на уровне CNI или пересоздать кластер нельзя. Оставался один путь — изменить cluster-cidr и node-cidr-mask-size прямо в работающем кластере. Но, как выяснилось, просто поправить конфиг контроллера недостаточно.

Почему нельзя просто изменить параметры kube-controller-manager

На первый взгляд всё просто: меняем флаги --cluster-cidr и --node-cidr-mask-size в манифесте kube-controller-manager, перезапускаем — готово. Но есть нюанс: при добавлении узла в кластер kube-controller-manager выделяет ему подсеть из cluster-cidr и записывает её в поле spec.podCIDR объекта Node. Это поле immutable — изменить его через Kubernetes API нельзя. Даже после обновления параметров контроллера существующие узлы останутся со старыми подсетями.

Вот как это работает:

cluster-cidr = 10.112.0.0/16, node-cidr-mask-size = 24
Узел 0 → 10.112.0.0/24   (254 адреса)
Узел 1 → 10.112.1.0/24   (254 адреса)
...
Узел N → 10.112.N.0/24   (254 адреса)

Если мы хотим увеличить количество адресов на узел (например, до 1022 при /22), нужно одновременно изменить и cluster-cidr, и node-cidr-mask-size. Менять только маску опасно: старые /24-подсети могут пересечься с новыми /22-подсетями, и мы получим коллизии IP-адресов.

Вот значения параметров, которых нам хотелось добиться:

Параметр

Текущие значения

Желаемые значения

cluster-cidr

10.112.0.0/16

10.212.0.0/16

node-cidr-mask-size

24

22

При /22-подсети каждый узел получает 1022 адреса — четырёхкратный запас по сравнению с /24-подсетью. А смена самой подсети на 10.212.0.0/16 гарантирует отсутствие пересечений со старой.

Решение: etcdhelper и прямое редактирование etcd

Раз Kubernetes API не позволяет изменить spec.podCIDR, нужно обойти его. Можно сделать это через etcd. API-сервер лишь читает из него и пишет в него, поэтому, если записать/изменить значение напрямую в etcd, API-сервер увидит его как данность — безо всякой валидации.

В этом может помочь утилита etcdhelper — инструмент для чтения и редактирования объектов Kubernetes в etcd в обход API-сервера. В далёком 2020 году мы рассказывали про эту утилиту в отдельной статье на Хабре. Удивительно, но её даже не пришлось дорабатывать.

Важно понимать: прямое редактирование etcd — это крайняя мера. API-сервер не знает об изменениях, валидация не работает, откатить сложно: нет ни истории изменений, ни предыдущей версии объекта — придётся либо повторять операцию вручную в обратную сторону, либо восстанавливать весь etcd из снапшота. Мы использовали этот подход только потому, что штатные варианты в нашей ситуации не подошли.

Общий план действий такой:

  1. Подготовить кластер: остановить виртуальные машины, сжать до одного master-узла, отключить worker-узлы.

  2. Изменить конфигурацию cluster-cidr и node-cidr-mask-size.

  3. С помощью etcdhelper обновить spec.podCIDR у всех узлов в etcd.

  4. Обновить сетевое состояние на каждом узле.

  5. Вернуть кластер в рабочее состояние.

Разберём ключевые этапы.

Подготовка кластера

Прежде чем начинать работу с etcd, нужно минимизировать количество движущихся частей.

Остановка виртуальных машин. В нашем случае в кластере работают ВМ на базе модуля виртуализации. До начала работ их следует остановить. Если у вас stateful-нагрузка — аналогично: убедитесь, что данные в безопасности.

Сжатие до одного master-узла. Если кластер multi-master, оставляем один активный master-узел. Это упрощает работу с etcd: не нужно координировать изменения между несколькими членами кластера.

Drain всех worker-узлов. Эвакуируем поды со всех worker-узлов:

kubectl drain <node-name> --delete-emptydir-data --ignore-daemonsets

После drain’а worker-узлы можно остановить.

Изменение подсетей в etcd

Когда кластер подготовлен, применяем изменения. Сначала обновляем конфигурацию kube-controller-manager (в нашем случае — через конфигурацию Deckhouse Kubernetes Platform d8 platform edit cluster-configuration, а в vanilla-кластере это манифест /etc/kubernetes/manifests/kube-controller-manager.yaml):

# Было:
- --cluster-cidr=10.112.0.0/16
- --node-cidr-mask-size=24
# Стало:
- --cluster-cidr=10.212.0.0/16
- --node-cidr-mask-size=22

Затем — ключевой шаг. Запускаем etcdhelper на master-узле:

./etcdhelper \
  -cacert /etc/kubernetes/pki/etcd/ca.crt \
  -cert /etc/kubernetes/pki/etcd/server.crt \
  -key /etc/kubernetes/pki/etcd/server.key \
  -endpoint https://127.0.0.1:2379 \
  change-pod-cidr 10.212.0.0/16

Команда обходит все объекты Node в etcd и обновляет spec.podCIDR на подсети из нового диапазона. На выходе — список изменённых ключей.

Проверяем результат:

kubectl get no -o json | jq '[.items[] | {
  "name": .metadata.name,
  "podCIDR": .spec.podCIDR,
  "podCIDRs": .spec.podCIDRs
}]'

Если podCIDR у узлов обновился — etcd-часть готова. Но node-cidr-mask-size пока старый (/24) — он обновится при переподключении узлов.

Обновление сети на узлах

Это самый деликатный этап.

Важно! Прежде чем проводить работы, которые я описал ниже, нужно обязательно вернуть master-узлы в кластер, чтобы снова получился multi-master (процесс зависит от дистрибутива Kubernetes, вот инструкция для DKP). Необходимо сначала обновить сеть на worker-узлах, а затем уже на оставшемся master-узле — с ним будут нюансы (см. раздел «Грабли, на которые мы наступили»).

Мы протестировали два подхода к обновлению сети.

Быстрый способ: удаление узла из etcd с очисткой состояния CNI.

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

Для каждого узла последовательно:

# 1. Эвакуация подов.
kubectl drain <node-name> --delete-emptydir-data --ignore-daemonsets
# 2. На узле: остановка kubelet.
systemctl stop kubelet
# 3. На master-узле: удаление объекта Node.
kubectl delete node <node-name>
# 4. На узле: очистка состояния CNI (пример для Cilium).
rm -rf /var/run/cilium/
# 5. Пауза 30–60 секунд.
# 6. На узле: запуск kubelet.
systemctl restart kubelet

После перезапуска kubelet регистрируется в API-сервере как новый узел, kube-controller-manager выделяет ему подсеть из обновлённого cluster-cidr с новой маской, CNI инициализируется с чистым состоянием.

Проверка:

kubectl get no <node-name> -o jsonpath='{.spec.podCIDR}'
# Ожидаем: 10.212.X.0/22

Этот способ быстрый (минуты на узел), но зависит от специфики CNI. В нашем случае с Cilium он сработал. Для других CNI потребуется выяснить, где хранится локальный кеш подсетей, и очистить его.

Надёжный способ: полное пересоздание узла. Узел удаляется из кластера полностью.

Для кластеров на базе Deckhouse Kubernetes Platform подход следующий:

# 1. Эвакуация подов.
kubectl drain <node-name> --delete-emptydir-data --ignore-daemonsets
# 2. Очистка узла.
bash /var/lib/bashible/cleanup_static_node.sh --yes-i-am-sane-and-i-understand-what-i-am-doing

Скрипт удалит всё связанное с DKP (поды, их данные, сам CRI, исполняемые файлы платформы и т. д.), перезапустится, и у нас будет полностью чистый узел.

Система управления узлами (Cluster API Provider Static, он же CAPS) обнаружит его отсутствие и вернёт узел в кластер. Если вы не используете CAPS, верните узел в кластер так же, как добавляли его изначально.

Процесс очистки узла может варьироваться в зависимости от дистрибутива Kubernetes, но суть, как правило, везде одна: нужно зачистить узел и добавить его заново либо, если позволяет инфраструктура (не bare metal), можно заменить серверы на новые.

Плюс такого подхода — полная надёжность: узел возвращается в гарантированно чистом состоянии. Минус — время: если на узлах есть реплицируемое хранилище (LINSTOR, Rook/Ceph), потребуется предварительная эвакуация данных, что может занять часы. Оставлю ссылку для пользователей модуля sds-replicated-storage.

Грабли, на которые мы наступили

Процедура кажется линейной, но на практике мы столкнулись с несколькими проблемами.

Drain занимает очень много времени. На узлах работали виртуальные машины. Живая миграция большого количества ВМ, особенно для машин с большим объёмом памяти, — процесс небыстрый. Вывод: нужно остановить некритичную stateful-нагрузку заранее, чтобы ускорить drain.

Master-узел — отдельная история. При удалении и повторном добавлении master-узла он вылетает из etcd-кластера. Чтобы вернуть его, нужно дополнительно настроить membership в etcd (я опишу эти настройки в бонусном разделе ниже под катом). Поэтому мы сжали кластер до одного master-узла перед началом работ.

Узел вернулся со старым IP. Если после перезапуска kubelet узел получил подсеть из старого диапазона, значит, кеш CNI не был полностью очищен. Нужно повторить очистку и увеличить паузу перед перезапуском kubelet. Для Cilium критично удалить /var/run/cilium/.

Бонус. Возврат master-узла в etcd-кластер

Бонус. Возврат master-узла в etcd-кластер

Если мастер-узел покинул etcd-кластер в ходе работ, простой рестарт kubelet не поможет: etcd зафиксировал, что этого участника больше нет, и не примет его обратно без явной регистрации. Нужно вернуть узел через member add.

Проверяем текущее состояние кластера — смотрим, кто жив и отвечает:

export ETCDCTL_API=3
etcdctl --endpoints="https://127.0.0.1:2379" \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/peer.crt \
  --key=/etc/kubernetes/pki/etcd/peer.key \
  endpoint health --write-out=table

В DKP процесс аналогичный с единственным отличием — к командам etcdctl добавляется префикс kubectl -n kube-system exec -ti $(kubectl -n kube-system get pod -l component=etcd,tier=control-plane -o name | head -n1) --.

Опираясь на вывод команды выше, обозначим переменные для удобства:

KUBE0=<IP лидера etcd>
KUBE1=<IP возвращаемого master-узла>
KUBE2=<IP третьего master-узла>

На возвращаемом узле останавливаем kubelet и очищаем данные старого члена etcd. Без этого etcd откажется стартовать с --initial-cluster-state=existing:

sudo systemctl stop kubelet
sudo rm -rf /var/lib/etcd/member
sudo mkdir -p /var/lib/etcd/member/{wal,snap}
sudo chown -R root:root /var/lib/etcd

На узле-лидере регистрируем возвращаемый master-узел как нового участника:

export ETCDCTL_API=3
etcdctl --endpoints="https://127.0.0.1:2379" \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/peer.crt \
  --key=/etc/kubernetes/pki/etcd/peer.key \
  member add kube-1 \
  --peer-urls="https://$KUBE1:2380"

Команда вернёт строку ETCD_INITIAL_CLUSTER="kube-0=...,kube-1=..." — сохраним её.

На возвращаемом узле обновляем /etc/kubernetes/manifests/etcd.yaml, в секции command прописываем актуальные значения:

--name=kube-1
--initial-advertise-peer-urls=https://$KUBE1:2380
--listen-peer-urls=https://$KUBE1:2380
--advertise-client-urls=https://127.0.0.1:2379,https://$KUBE1:2379
--listen-client-urls=https://127.0.0.1:2379,https://$KUBE1:2379
--initial-cluster=<значение ETCD_INITIAL_CLUSTER из предыдущего шага>
--initial-cluster-state=existing

Флаг --initial-cluster-state=existing сигнализирует etcd, что он присоединяется к работающему кластеру, а не поднимает новый.

Запускаем kubelet — он поднимет etcd через static pod из обновлённого манифеста:

sudo systemctl start kubelet

Проверяем, что все участники вернулись в статус healthy:

export ETCDCTL_API=3
etcdctl --endpoints="https://127.0.0.1:2379" \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/peer.crt \
  --key=/etc/kubernetes/pki/etcd/peer.key \
  endpoint health --write-out=table

Финальная проверка

После обработки всех узлов — чек-лист:

# Все узлы получили новый podCIDR с нужным префиксом:
kubectl get no -o json | jq -r '.items[] | "\(.metadata.name): \(.spec.podCIDR)"'
# Нет подов со старыми адресами:
kubectl get po -A -o wide | grep "10\.112\."
# kube-controller-manager работает с новыми параметрами:
grep -E "(node-cidr-mask-size|cluster-cidr)" /etc/kubernetes/manifests/kube-controller-manager.yaml
# CNI обновил конфигурацию (пример для Cilium):
kubectl -n d8-cni-cilium get cm cilium-config -o yaml | grep native-routing-cidr

Ожидаемый результат для нашего случая:

etcdhelper-node-0: 10.212.0.0/22
etcdhelper-node-1: 10.212.4.0/22
etcdhelper-node-2: 10.212.8.0/22
etcdhelper-node-3: 10.212.12.0/22

Каждый узел теперь имеет 1022 адреса вместо 254.

Выводы

Смена подсети подов в работающем Kubernetes-кластере — операция нештатная, но выполнимая, если помнить о главном:

  • Меняйте cluster-cidr и node-cidr-mask-size одновременно. Смена только маски при той же подсети приведёт к коллизиям.

  • spec.podCIDR — immutable. Единственный способ обновить его на существующих узлах — прямое редактирование etcd.

  • Тестируйте на стенде. Мы прогнали всю процедуру на тестовом кластере, прежде чем трогать рабочее окружение. Это обязательный шаг.

  • Готовьтесь к тому, что это займёт время. Даже с быстрым способом обновления узлов нужно закладывать техническое окно. В нашем случае потребовалось четыре часа.

  • Подход не зависит от дистрибутива Kubernetes. Etcd, kube-controller-manager и spec.podCIDR — это vanilla Kubernetes. Инструкция применима к любому кластеру, где IPAM работает на стороне Kubernetes, а не CNI.

  • Живая миграция ВМ требует хорошего сетевого канала. Это следует учитывать при проектировании архитектуры. Это особенно важно для тяжёлых нагрузок, активно работающих с памятью.

Если у вас ещё есть возможность выбрать IPAM-стратегию, подумайте о cluster-pool в Cilium или аналогичных режимах в вашем CNI. Это позволит расширять пул адресов без приключений с etcd.

P.S.

Читайте также в нашем блоге: