Под OpenVPN в Kubernetes ничего не передаёт? Проверьте ip_forward
Клиенты пингуют под VPN, а дальше ничего. В namespace пересылка выключена, и исправление должно пережить admission, перезапуски и обратный путь.
Ноутбук показывает зелёный туннель, журнал клиента сообщает, что маршруты были отправлены, а единственный отвечающий адрес — собственный туннельный IP VPN-сервера. Пинги дальше умирают, куда бы вы ни целились: ClusterIP, адрес другого пода или публичное имя хоста. Эта комбинация не расплывчата, а диагностична: декапсуляция работает, а второй переход — нет.
Я встретил это на узле Debian с образом OpenVPN, установленным через chart, и туннель был простой частью. Клиент получил аренду 10.8.0.x, 10.8.0.1 ответил, а 10.96.0.10, кластерная служба DNS, завершилась по тайм-ауту. В журнале сервера ничего не выглядело больным, и именно поэтому люди начинают переписывать правила iptables, которых им никогда не нужно было касаться.
Под VPN — это маршрутизатор, и стоит сказать это прямо. Зашифрованные пакеты приходят, распаковываются в собственном сетевом namespace пода, а затем должны выйти через другой интерфейс. Linux отказывает в этом втором шаге, если пересылка не включена внутри именно этого namespace.
Чего клиент может и не может достичь
Отвечающий адрес принадлежит конечной точке туннеля, и процесс VPN владеет им локально, поэтому пересылка вообще не участвует. Всё остальное, включая другой под, VIP службы или интернет, требует, чтобы ядро переместило пакет с tun0 на eth0. Когда это перемещение отвергается, симптом — равномерная тишина, а не отказ, потому что ничто не генерирует ICMP-ошибку обратно в туннель. Вы получаете тайм-ауты, а не сообщения о недостижимости, и это подсказка, что никто вообще не маршрутизирует.
Две минуты классификации сужают слой. Если имена DNS не разрешаются, а сырые пинги к IP службы проходят, проблема в сантехнике DNS, а не в пересылке. Если каждый пункт назначения за адресом пира истекает по тайм-ауту, включая голый IP, начинайте с namespace.
Прочитайте настройку внутри namespace
Каждый под получает собственный сетевой namespace, а net.ipv4.ip_forward — это sysctl для каждого namespace. Изменение его на узле ничего не говорит о контейнере, и это надёжный способ провести день не на той машине.
kubectl exec -it deploy/openvpn -- sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 0
Ноль подтверждает диагноз. Значение также не наследуется из конфигурации времени выполнения узла: оно начинается с образа по умолчанию каждый раз при создании контейнера. Поэтому исправление, применённое вручную с kubectl exec ... sysctl -w, исчезает при следующем перезапуске и возвращается как неисправность, которая выглядит периодической.
Включить так, чтобы манифест это сохранил
Долговечный вариант — init-контейнер, который работает в том же pod sandbox и записывает значение до запуска контейнера VPN. Контейнеры в поде разделяют сетевой namespace, поэтому настройка переносится в основной контейнер.
initContainers:
- name: enable-forwarding
image: busybox:1.36
command: ["sysctl", "-w", "net.ipv4.ip_forward=1"]
securityContext:
privileged: true
Именно privileged: true — та строка, против которой возражают рецензенты, и они правы. Запись sysctl требует либо привилегии, либо узко ограниченной возможности, а кластер с ограничительным Pod Security Standard может вовсе отказать в привилегированном init-контейнере. Если admission отклонит под, вы узнаете об этом при создании, что по крайней мере честно и легко читается.
Более чистый вариант — список securityContext.sysctls на уровне пода, который позволяет kubelet применять sysctl с пространством имён, не давая привилегий ни одному контейнеру. Он покрывает только имена из списка разрешённых кластера. Есть ли net.ipv4.ip_forward в этом списке, зависит от того, как настроен kubelet, а под, запрашивающий sysctl вне его, отклоняется при admission или застревает в CreateContainerConfigError, пока sandbox повторяется. Проверьте список разрешённых, прежде чем считать, что поле сломано.
Прежде чем строить любой из подходов вручную, посмотрите, не выставляет ли chart переключатель, часто в виде чего-то вроде ipForwardInitContainer: true. Использование значения из chart оставляет ваш файл переопределения маленьким и переживает следующее обновление, чего ручные правки Deployment не делают.
Вторая половина: обратный путь и политика
Включённая пересылка необходима, но недостаточна. Если клиенты используют частный пул, для которого в сети кластера нет маршрута, ответы не находят дорогу обратно в туннель, и под должен маскировать этот трафик в сторону своего назначения.
Политика лежит сверху. Объекты NetworkPolicy и правила облачного файрвола оцениваются по адресу источника, который несёт пакет после трансляции, поэтому правило, написанное для пула клиентов, может тихо перестать совпадать, как только включена маскировка.
Когда клиентам нужно достигать только служб внутри кластера, завершение VPN на VIP службы — более компактный дизайн, чем маршрутизация в сеть подов. Он также полностью держит пул клиентов вне таблицы маршрутизации CNI.
Изоляция сбойного перехода от клиента
Работайте наружу и остановитесь на первом тихом шаге. Начните с пира VPN, затем попробуйте адрес какого-нибудь пода, затем VIP службы и только потом публичное назначение. Каждый успех сдвигает границу на один переход, а каждая неудача называет слой, который стоит осмотреть. После любого развёртывания войдите через exec и прочитайте sysctl снова, потому что заменённый под начинается с образа по умолчанию, а исправление, которого никогда не было в манифесте, уже исчезло.
Документация Kubernetes об использовании sysctl в кластере перечисляет, какие имена можно задать через контекст безопасности пода, и это первое место, куда смотреть, когда admission отклоняет изменение.