Как стать автором
Обновить

Комментарии 45

очень круто! Но не рассмотрена одна из проблем — что будет с Real IP клиента после попадания в кластер. Даже в дефолтной инсталляции MetalLB с ней есть нюансы.

Если использовать externalTrafficPolicy=Local то IP-адрес клиента передаётся в workload без изменений это работает из коробки like a charm.


Подробности: https://kubernetes.io/docs/tutorials/services/source-ip/

И… ломаешь балансировку трафика на поды, не ?


The downside of this policy is that incoming traffic only goes to some pods in the service. Pods that aren’t on the current leader node receive no traffic, they are just there as replicas in case a failover is needed.

https://metallb.universe.tf/usage/

Комбинация iptables с рутингом (когда iptables влияет на маршрут) — это всегда 'code smell'. Вы уверены, что вам надо использовать iptables, если можно обойтись ip rule?


Объясняю, почему это code smell. У вас есть две сущности: iptables и рутинг. iptables идеологически определяет "можно или нет" пакету пройти сетевой стек. рутинг говорит "куда идти" пакету. Вы создаёте патологическую связность между двумя разными сущностями, решающими разные задачи.


На выходе — хрупкая архитектура, которая "но у меня всё работает" (а у меня нет), в которой трудно разобраться, трудно отладить и которая содержит в себе добротную порцию WTF для любого пришедшего (все косвенные признаки паталогической связности).

Спасибо за отзыв, смею с ним согласиться.
Но в моём кейсе обойтись простым ip rule не удалось, по этому пришлось маркировать пакеты и принимать решение о маршрутизации в зависимости от интерфейса на который они были получены.

Мне лениво в бездны докера нырять. Скажите, что вы хотели от ip rule? (Будем считать интересным админским упражнением).

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


Имеется n физических нод, на каждой из них есть несколько интерфейсов и IPVS, скажем:


  • bond0 (10.10.0.0/16) — внутренняя сетка
  • kube-bridge — бридж с контейнерами на ноде
  • kube-dummy-if — dummy интерфейс для IPVS, сюда вешаются все сервисные IP из куба (как внешние так и clusterIP), по сути это выглядит следующим образом:

Котейнеры получают адреса из podSubnet: 10.112.0.0/12, на каждую ноду выделяется свой рейдж из этого диапазона.
Есть ещё serviceSubnet: 10.96.0.0/12 (специальная сеть для сервисов Kubernetes, считайте это сетью для virtualserver-адресов в IPVS)


Скрытый текст

strict arp включён:


echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce

но на MetalLB он не влияет.


(вывод изменён)


# ip addr
1: bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc noqueue state UP group default qlen 1000
    link/ether 94:f1:28:c6:87:39 brd ff:ff:ff:ff:ff:ff
    inet 10.10.130.182/16 brd 10.10.255.255 scope global bond0

2: kube-dummy-if: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default 
    link/ether 22:71:5f:ff:3a:05 brd ff:ff:ff:ff:ff:ff
    inet 1.2.3.4/32 brd 1.2.3.4 scope link kube-dummy-if
       valid_lft forever preferred_lft forever
    inet 10.96.221.9/32 brd 10.96.221.9 scope link kube-dummy-if
       valid_lft forever preferred_lft forever
    inet 10.96.113.129/32 brd 10.96.113.129 scope link kube-dummy-if
       valid_lft forever preferred_lft forever

3: bond0.100@bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc noqueue state UP group default qlen 1000
    link/ether 94:f1:28:c6:87:39 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::96f1:28ff:fec6:8739/64 scope link 
       valid_lft forever preferred_lft forever

4: kube-bridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 5e:79:2d:c4:d3:e9 brd ff:ff:ff:ff:ff:ff
    inet 10.113.63.1/24 scope global kube-bridge
       valid_lft forever preferred_lft forever
    inet6 fe80::5c79:2dff:fec4:d3e9/64 scope link 
       valid_lft forever preferred_lft forever

5: veth11694f93@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master kube-bridge state UP group default 
    link/ether e2:66:16:a9:fd:25 brd ff:ff:ff:ff:ff:ff link-netnsid 4
    inet6 fe80::e066:16ff:fea9:fd25/64 scope link 
       valid_lft forever preferred_lft forever
6: vethe332a950@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master kube-bridge state UP group default 
    link/ether e6:1b:9a:1b:d8:a2 brd ff:ff:ff:ff:ff:ff link-netnsid 5
    inet6 fe80::e41b:9aff:fe1b:d8a2/64 scope link 
       valid_lft forever preferred_lft forever

# ipvsadm -L -n
TCP  10.96.113129:9000 rr
  -> 10.112.58.28:9000            Masq    1      0          0      
TCP  10.96.221.9:80 rr
  -> 10.112.147.25:80             Masq    1      0          0         
  -> 10.113.14.200:80             Masq    1      0          0         
  -> 10.113.63.39:80              Masq    1      0          0     
TCP  1.2.3.4:80 rr
  -> 10.112.147.25:80             Masq    1      0          0         
  -> 10.113.14.200:80             Masq    1      0          0         
  -> 10.113.63.39:80              Masq    1      0          0     

Таблица маршрутизации внутри кластера сторится с помощью kube-router (BGP):


# ip route
# дефолтный гейтвей во внутренней сети
default via 10.10.0.1 dev bond0 proto static
# маршрут в локальный контейнер
10.113.63.0/24 dev kube-bridge proto kernel scope link src 10.113.63.1 
# маршруты к контейнерам на других нодах
10.113.14.0/24 via 10.10.130.1 dev bond0 proto 17 
10.113.14.0/24 via 10.10.130.1 dev bond0 proto 17
10.113.58.0/24 via 10.10.130.186 dev bond0 proto 17

есть также vlan интерфейс с внешней сеткой


  • bond0.100 (1.2.3.0/24)

Особенность MetalLB в том что он кофигурит роутинг от слова "никак", то есть механика его работы заключается тупо в том, чтобы в нужный момент добавить внешний 1.2.3.4/32 адрес на ноду.
Соответсвенно все роуты должны должны быть настроенны на ноде заранее, включая роут во внешнюю подсеть, т.к. нода не имеет IP-адреса из таковой.


задача:


  1. Настроить чтобы ответ с source ip 1.2.3.0/24 уходил через bond0.100 (по умолчанию он, ожидаемо, идёт в default gateway внутренней сети)
  2. В тоже время нужно оставить возможность обращаться из контейнеров к внешней подсети 1.2.3.0/24, эти пакеты не должны уходить через bond0.100, а должны попадать на ноду, чтобы быть отмаршрутизированными в друие контейнеры средствами IPVS.

если перая задача может быть легко решена дополнительной таблицей маршрутизации со своим gateway и правилом типа:


ip rule add from 1.2.3.0/24 lookup 100

и это работает даже без переключения rp_filter


то со второй задачей возникает проблема:
когда контейнеры генерируют пакеты к 1.2.3.4 они уходят в bond0.100, а должны попадать на dummy интерфейс чтобы быть перенаправленными через IPVS

(спасибо, challenge accepted, я сегодня вечером ей займусь).

Обрезаю задачу до минимальной. У вас опечатки. eth0.100? Я предполагаю, что речь про bond0.100.


Моё понимание задачи:
На сервере три интерфейса:
bond0 — 10.10.x.x/16, default gateway
bond0.100 — 1.2.3.x/24 — internet, куда надо маршуртизировать тарфик от 1.2.3.х/24. Я предполагаю, что где-то там есть шлюз 1.2.3.1.
есть dummy интерфейс kube-dummy-if, который содержит много адресов из 1.2.3.х/24.
....


Дальше, конейнеры локальные или трафик через bond0 может прилетать? Я вижу дополнительные роуты на контейнеры, но из описания я не понял, трафик на 1.2.3.х идёт только из "своих" контейнеров или может приходить с bond0?


Я предполагаю, что может приходить снаружи (bond0). В этом случае у нас:


Есть адреса из 10.112.х.х сетей, как на локальном бридже, так и за bond0 (куда настроены дополнительные машруты на них).


Задача: обеспечить маршрутизацию между контейнерами и kube-dummy-if, плюс обеспечить отправку трафика от kube-dummy-if для посторонних адресов (т.е. для интернета) в bond0.100.


ip rule add from {{ external_ips }} to {{ container_net }} table 0 # default gateway
ip rule add from {{ external_ips }} table 1 #source_based

дальше


ip route add default via 1.2.3.1/24 dev bond0.100 table 1

Или я что-то пропустил?

Дальше, конейнеры локальные или трафик через bond0 может прилетать?

Да всё верно, трафик из контейнеров может идти как локально из kube-bridge, так и с других нод через bond0.


Хорошая попытка, но к сожалению ip rule to у меня не заработало, как ни крути ответные пакеты всегда уходят в bond.100:



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


# ip rule
0:      from all lookup local
32762:  from all to 10.112.0.0/12 lookup local
32764:  from 1.2.3.0/24 lookup 100

Спасибо, вспоминается старый хабр, где читать коментарии порой было интереснее чем саму статью

Т.е. вы утверждаете, что в списке правил всегда применяется последнее matched? man с вами не согласен.


Each policy routing rule consists of a selector and an action predicate. The RPDB is scanned in order of decreasing priority (note that lower number means higher priority, see the description of PREFERENCE below). The selector of each rule is applied to {source address, destination address, incoming interface, tos, fwmark} and, if the selector matches the packet, the action is performed. The action predicate may return with success. In this case, it will either give a route or failure indication and the RPDB lookup is terminated. Otherwise, the RPDB program continues with the next rule.


Т.е. правило может либо вернуть success (с route'ом или с failure), либо lookup продолжится.
И у нас есть action unicast — the rule prescribes to return the route found in the routing table referenced by the rule..


Мне кажется, вы просто недочитали ip rule. Может быть я тоже не дочитал, но пока то, что вы говорите, не звучит как "не подходит как решение". После этого становятся применимы мои аргументы по поводу путать iptables с ip route.


Где вы ошиблись: пытались отладить ip rule на боевой настройке, а не в уютной виртуалке с тремя интерфейсами. Minimal viable case, как stackoverflow требует. В процессе изготовления обычно много тумана из головы уходит и появляется два-три правильных вопроса, ответы на которые и содержат решение.

Пардон, я не так выразился, я всего-лишь хотел сказать что правило


ip rule add to 10.112.0.0/12 lookup local

в моём случае не работало и вместо него выполнялось следующее попадающее под условие.


Разобрался, проблема оказалось в глупой ошибке, нужно использовать таблицу main, а не local


Теперь о хорошем, ваш метод работает:


ip rule add from 1.2.3.0/24 lookup 100
ip rule add from 1.2.3.0/24 to 10.112.0.0/12 lookup main

где 10.112.0.0/12 — сеть подов в Kubernetes.


Но правило to должно добавляться после, что бы имело наивысший приоритет.


Заметным плюсом данного подхода является также то что не нужно шаманить с rp_filter, всё и так работает с дефолтными настройками.


Огромное спасибо за содействие, я собираюсь обновить статью чтобы описать данный метод как рекомендованный.

А еще у нас есть sysctl ) уже надо три сущности настраивать ))

Ну kube-proxy и так sysctls правит и кучу iptables-правил генерит, с маркировкой и прочим винегретом, а если добавить к этому делу ещё CNI-планин, то вообще можно свихнуться:





BTW, мы перешли на kube-router в качестве CNI и service-proxy. Он активно юзает ipset, так что это уже не выглядит так страшно.


Что касается правил и настройки маршрутизации для MetalLB, это всего-лишь небольшой скрипт запускающийся как init-контейнер для спикеров MetalLB, это небольшая плата за возможность автоматически выдавать внешние IP-адреса через Kubernetes.

Дело не в количестве вещей для настройки. Изменение sysctl при рутинге — это нормально (вы рутинг-то включаете sysctl'ом). А вот правила iptables, которые влияют на ip rule — это уже начало спагетти в зависимостях.

А режим l3 не решает таких проблем?

У MetalLB есть два режима работы: Layer2 и BGB.


  • BGP решает проблему маршрутизации при доставке IP-адреса на ноду, но требут поддержки BGP со стороны вашей сети.
    В нашем случае это плоская L2-сеть которая доставляется на ноду обычным VLAN.


  • Layer2 можно сравнить с тем, как работает VRRP. Но в отличии от VRRP преимущество данного подхода заключается в том, что изначально ноды не имеют назначенных публичных IP-адресов, а получают их только в момент присвоения адреса MetalLB.



Интересный факт, что полученный IP-адрес является виртуальным (его не видно в выводе ip addr). Но побочным эфектом от этого, что маршрутизация для этого IP-адреса осуществляется только в рамках запущенного workload.


То есть имея адрес, назначенный таким образом, попасть на ноду невозможно, а на ваш workload всегда пожалуйста. Это валидно для IPVS, но не уверен что это будет работать также, если kube-proxy настроен в режиме iptables, т.к. не проверял.

я про BGP и имел ввиду, моя оплошность. Помню ваше выступление на highload и вы описывали плоскую сеть без ротинга как примещуство. А получается, чтобы добиться гибкости маршрутизации в такой схеме, приходится добавлять статику, PBR, красить роуты на нодах. И это, ИМХО конечно, сложнее обслуживать чем динамику, где бгп за тебя анонсит маршруты соседям и дальше магия сетевиков (главное чтобы сеть не офигела от количества маршрутов которые бедет генерить куб). Я не критикую вас. Так мысли в слух. И понять для себя как лучше

Мне кажется, что нужно начать с того, что MetalLB — это вариант для бедных. От безысходности. Хотите нормально? Покупайте F5 и ставьте его впереди кластера. Или любое аналогичное решение с внешним LB

Не согласен, MetalLB — это просто софт который решает конкретную задачу, к слову он и BGP-роуты анонсить может.


Основная задача которую он решает: выдача ExternalIP-адресов LoadBalancer-сервисам.


Вариант с L2 более специфичен, но при должном подходе остаётся достаточно востребованным даже в больших кластерах как наш.

F5 предоставляет провайдер для кубернетс, что бы нормально отрабатывался type: loadBalancer?

Извините, пожалуйста, за встречный вопрос — а зачем вам вообще type: loadBalancer? Мы, например, смирились с тем, что мы в кубернетес затаскиваем или разработческие среды (и тогда не проблема telepresence/port-forward и затащить сервис на машину разработчика без публикации наружу), или кидать в кубернетес только http/rest приложения. Ну, действительно — там целая прорва проблем с БД и stateful нагрузками, так что решайте сами. А раз у нас все через ингресс, то теоретически можно внешних клиентов вменяемо отбалансировать по узлам даже без провайдера.


Отвечая на исходный вопрос — не оно — https://clouddocs.f5.com/containers/v2/kubernetes/ ?

Отвечая на исходный вопрос — не оно — clouddocs.f5.com/containers/v2/kubernetes ?

Нет, не оно. Там речь об ингресс, но тоже вариант.

А для тайп ЛБ есть куче кейсов, кроме стейтфула и бд

Давайте обсудим. Может я в этой жизни что-то не понимаю просто напросто ))) и Вы откроете мне какую-то очередную истину.

Истины не существует, а кейс — по выделенному ингресс контроллеру для каждого неймспейса, где хочется что бы svc для него был тайп лоадбалансер, это удобно для всяких автоматизаций и тестовых окружений.
по выделенному ингресс контроллеру для каждого неймспейса, где хочется что бы svc для него был тайп лоадбалансер, это удобно для всяких автоматизаций и тестовых окружений.

живем с единым ingress — брат жив. Могу представить, что разным пользователям кластера могут понадобиться разные ингресс-контроллеры (принципиально), но это странное решение. Если же речь за безопасность… То в любом случае нужно писать какие-то хуки или политики, которые будут запрещать возможность потрогать чужой неймспейс или забиндиться на чужое доменное имя (а с этим реально пока в кубе проблемы)

Это не странное решение, а в saas варианте сплошь и рядом. Плюс, тут как раз тот момент, когда пользователь должен сказать тайп лоадбалансер и все, в итоге увидеть айпи, желательно что бы оно еще автоматом создало запись в каком-то клоуд днс. в итоге получить окружение c точкой входа, для примера, api-tiket02.example.com, для тестирования и тп. После мерджа — это окружение так же, должно, легко быть выпилино одной командой.
Так что, покупка вендорской железки, это не решение проблемы, ибо, зачастую, у нее отсутствуют нужные интграции для кубера, а автоматизировать это с боку — лишняя работа.
Да, и вообще, кубер очень сильно завязан на то, что у него снизу есть слой IaaS, который ему и сторедж предоставит, и нетворкинг, и лб, и кучу другого
Да, и вообще, кубер очень сильно завязан на то, что у него снизу есть слой IaaS, который ему и сторедж предоставит, и нетворкинг, и лб, и кучу другого

+


Плюс, тут как раз тот момент, когда пользователь должен сказать тайп лоадбалансер и все, в итоге увидеть айпи, желательно что бы оно еще автоматом создало запись в каком-то клоуд днс. в итоге получить окружение c точкой входа, для примера, api-tiket02.example.com, для тестирования и тп.

самое простое решение — не строить свой кубер, а взять в облаке. К чему нас и толкают. Вы серьезно хотите он-премис куб? Касательно SaaS не совсем понял. Если Вы условный слак — Вы клиентам предоставляете единую точку входа вроде api.slack.com, а где .slack.com по большому счету не так важно.

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

Если *.example.com на нее направить, то проблемы будто нет (развожу руками)

я не люблю он-преймс, но они тоже присутствуют в моей жизни, часто, это связанно с рынком и законодательством на нем
Если *.example.com на нее направить, то проблемы будто нет (развожу руками)

Вы же понимаете это всего лишь пример, и доменов может быть больше, и не всегда вэйлкард прокатит)

Простой вопрос: как ещё можно пустить внешний трафик в кластер на ingress-контроллер, чтобы это было удобно и отказоусточиво?


А если таких ingress-контроллеров много?

Я же сказал вроде — внешний балансировщик, не? Вы же все равно наверняка с WAF там или Qrator интегрируетесь. Как минимум это "отказоустойчиво". Насчет "удобно" — зависит от наличия интеграций, как правильно заметил, P6i. Но тут варьируется от того насколько динамичная среда нужна и какие типы нагрузок (в условном кейсе только web-сервисов наружу — все ок).


К тому же в том же OpenShift как-то тоже ведь решали проблему отказоустойчивости "точки входа приложений в кластер", нет? Не могу, к сожалению, вспомнить точно, но вроде там магия с keepalived была и haproxy.

Это с оператором. А можно «внекуба», например поднять кластер haproxy, а тот уже будет балансировать на игнресс ноды, а те в свою очередь через ингресс балансировать на поды. Если для нужд хватает 80/443 портов. И будет очень даже отказоусточиво, да.
А можно даже заанонсить Service, и балансировать на них через haproxy (а дальше магия куба, но это тот еще костыль). Но в этом случае нужно отслеживать, чтобы при деплое не поменялся IP у сервиса или сообщать об этом haproxy, ну и фичи теряешь LB. Хотя можно поставить externalTrafficPolicy=Local и Affinity… крч много вариантов

Реквестирую обзор всех возможностей для нубов

Я хотел это сделать, даже начал писать статью. Но потом понял, что получается лонгрид, и никто это читать не будет))))
Ну не правада. Я бы дочитал
Не ингрессом единым, плюс, что будет автоматизировать конфигурацию haproxy? Но тут чуть лучше, вроде как, со вторйо версии он стал нативно поддерживать api kubernetes

Да я видел варианты, где народ юзает external ip, ввиде навешенных на всех нодах на lo интерфейсах пачкой апи и анонса их через ospf на бордер. Ну и табличка в конфлюенсе, какой айпи за каким сервисов закреплен)
на балансер повесить wildcard, а с него балансить на ingress nodes. Т.е. в этом случае автоматизация — добавить новые ingress nodes в ротацию. Но это любым ансиблом можно сделать. Если про первый вариант мной описаный. Если про второй — то это вообще не продовая вещь. там много костылей.
Вы не поняли кейса, у вас в каждом неймспейсе свой ингресс котроллер, со своей пачкой доменов, не все они домен третьего уровня, эти окружения (я про неймспейсы) — ондеманд, создаются динамически по запросу, так же удаляются. Помимо всего этого, еще через тот же external-dns плагин делает записи в днс провайдере.

Плюс, не должно быть доп телодвижений для конфигурации, или это должно быть спрятано от пользователя, и тп. Да это все прекрасно автоматизируется, оборачивается в ci/cd. Но зачем мне плюс одна сущность в виде ансибла или другой системы, когда я могу обойтись и без нее)

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

Да, если у вас прод кластер, в котором задеплоены сервисы, для них созданы ингресы и другие методы выставить их наружу, раз настроен хапрокси тот же, то да, можно и так жить, и так даже будет хорошо, меньше движущихся частей — меньше мест где сломается.
Но жизнь многогранна. Тем более, у автора поста, своего рода публичный «клауд» (ну почти так)

Я ничуть не спорю, BGP хорошо, и я с радостью юзал бы его, но так вышло, что у нас такая топология сети. Основное преимущество же заключалось в том что стоимость L2-оборудования значительно ниже чем умного L3.


И да, BGP решит проблему настройки роутинга, но не спасёт вас от маркировки пакетов. Вам всегда придётся как-то выкручиваться если на вашей ноде более одного default gateway.

Вот только зачем L2? Я, как сетевой инженер, постоянно вижу как люди постоянно пытаются использовать L2, там, где это на самом деле не требуется, потому что «так проще», но в итоге напарываются на кучу проблем.
Мой всем совет, используйте везде L3 где это возможно, нет острой необходимости в L2.

p.s. я пробовал MetalLB c BGP, мне понравилось.
согласен насчет л2. Но автор ответил на этот вопрос. А вы пробовали на ноде, например с CNI калико, которая установила пиринг по БГП со свичом/роутером поднять MetalLB спикера и подружить с темже свичем? (Необходимы костыли, пару из которых описаны здесь)
Как-то они там все усложнили, очевидный воркэрунд же — повесить второй ip на лупбек лифа и пирить metallb с ним.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий