Принцип работы Flannel и реализация исходного кода сетевого анализа Kubernetes

Kubernetes

Flannel — это сетевой подключаемый модуль CNI с открытым исходным кодом для cereos.На следующем рисунке показана схема упаковки, передачи и распаковки пакета данных на официальном веб-сайте flannel.Из этого рисунка видно, что docker0 из двух машины находятся в разных сегментах: 10.1.20.1/24 и 10.1.15.1/24, если из пода Web App Frontend1 (10.1.15.2) подключиться к поду Backend Service2 (10.1.20.3) на другом хосте, сетевой пакет отправляется с хоста 192.168.0.100 на 192.168.0.200. Пакеты данных внутреннего контейнера инкапсулируются в UDP хоста, а IP-адрес и mac-адрес хоста инкапсулируются во внешнем уровне. Это классическая оверлейная сеть.Поскольку IP-адрес контейнера является внутренним IP-адресом, он не может обмениваться данными через хост, поэтому сеть контейнера должна быть перенесена в сеть хоста.

flannel поддерживает различные сетевые режимы, обычно используются vxlan, UDP, hostgw, ipip, gce и Alibaba Cloud и т. д. Разница между vxlan и UDP заключается в следующем: vxlan — это пакет ядра, а UDP — программный пакет пользовательского режима flanneld. , поэтому производительность UDP будет несколько хуже, режим hostgw - это режим хост-шлюза, а шлюз из контейнера в контейнер на другом хосте настроен на адрес сетевой карты хоста, на котором он находится. очень похоже на calico, за исключением того, что calico декларируется через BGP, а hostgw — через раздачу center.etcd, поэтому hostgw — это режим прямого подключения, который не нужно упаковывать и распаковывать через оверлей, а производительность относительно высока, но самый большой недостаток режима hostgw в том, что он должен быть в сети уровня 2. Ведь маршрут следующего хопа должен быть в таблице соседей, иначе он не может пройти.

В реальной производственной среде наиболее часто используемым режимом является режим vxlan.Сначала мы рассмотрим принцип работы, а затем реализуем процесс посредством анализа исходного кода.

Процесс установки очень прост, в основном разделен на два этапа:

Первым шагом является установка фланель

yum установить flannel или запустить через метод daemonset kubernetes, настроить адрес etcd для flannel

Второй шаг — настройка сети кластера

curl -L http://etcdurl:2379/v2/keys/flannel/network/config -XPUT -d value="{\"Network\":\"172.16.0.0/16\",\"SubnetLen\":24,\"Backend\":{\"Type\":\"vxlan\",\"VNI\":1}}"

Затем запустите запланированную программу для каждого узла.

1. Принцип работы

1. Как выделить адрес контейнера

При запуске контейнера Docker через docker0 выделяется IP-адрес, а flannel назначает каждой машине сегмент IP, настроенный на docker0.После запуска контейнера в этом сегменте выбирается незанятый IP.Так как же flannel изменить сетевой сегмент docker0?

Сначала взгляните на файл запуска фланели /usr/lib/systemd/system/flanneld.service.

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/flanneld
ExecStart=/usr/bin/flanneld-start $FLANNEL_OPTIONS
ExecStartPost=/opt/flannel/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker

В файле указана переменная среды фланель и сценарий запуска, а также mk-docker-opts.sh, заданные сценарием выполнения после запуска ExecStartPost. Функция этого сценария заключается в создании /run/flannel/docker.Содержимое файла следующим образом:

DOCKER_OPT_BIP="--bip=10.251.81.1/24"
DOCKER_OPT_IPMASQ="--ip-masq=false"
DOCKER_OPT_MTU="--mtu=1450"
DOCKER_NETWORK_OPTIONS=" --bip=10.251.81.1/24 --ip-masq=false --mtu=1450"

И этот файл связан с файлом запуска докера /usr/lib/systemd/system/docker.service,

[Service]
Type=notify
NotifyAccess=all
EnvironmentFile=-/run/flannel/docker
EnvironmentFile=-/etc/sysconfig/docker

Это настроит мост для docker0.

В среде разработки есть три машины, которым соответственно назначены следующие сегменты сети:

host-139.245 10.254.44.1/24

host-139.246 10.254.60.1/24

host-139.247 10.254.50.1/24

2. Как взаимодействуют контейнеры

Выше описано, как назначить IP каждому контейнеру, так как контейнеры на разных хостах обмениваются данными?В качестве примера мы используем самый распространенный vxlan.Здесь есть три ключевых момента, один маршрут, один arp и один FDB. Мы анализируем функции вышеперечисленных трех элементов один за другим в соответствии с процессом отправки пакетов из контейнера.Сначала пакеты данных из контейнера будут проходить через docker0.Затем следующие будут отправлены непосредственно из хост-сети или перенаправлены через vxlan пакеты? Это настройки маршрутизации на каждой машине.

 #ip route  show dev flannel.1
10.254.50.0/24 via 10.254.50.0 onlink
10.254.60.0/24 via 10.254.60.0 onlink

Вы можете видеть, что у каждого хоста есть маршрут к двум другим машинам. Этот маршрут является маршрутом onlink. Параметр onlink указывает, что шлюз принудительно находится «на канале» (хотя маршрут канального уровня отсутствует), в противном случае Linux Нет возможности добавлять маршруты к разным сегментам сети. Таким образом, пакет данных может быть известен, и если к нему напрямую обращается контейнер, он будет передан устройству flannel.1 для обработки.

Виртуальное сетевое устройство flannel.1 будет пакетировать данные, но снова возникает следующий вопрос, какой mac-адрес у этого шлюза? Поскольку этот шлюз установлен через onlink, фланель выдаст этот mac-адрес, проверьте таблицу arp.

# ip neig show dev flannel.1
10.254.50.0 lladdr ba:10:0e:7b:74:89 PERMANENT
10.254.60.0 lladdr 92:f3:c8:b2:6e:f0 PERMANENT

Вы можете увидеть MAC-адрес, соответствующий этому шлюзу, чтобы внутренние пакеты данных были инкапсулированы.

Или последний вопрос, каков IP-адрес назначения исходящих пакетов данных? Другими словами, на какую машину следует отправить этот инкапсулированный пакет? Трудно, чтобы каждый пакет транслировался. Реализация vxlan по умолчанию действительно впервые осуществляется через широковещательную рассылку, но Flannel снова использует метод взлома для прямой выдачи таблицы пересылки FDB.

# bridge fdb show dev flannel.1
92:f3:c8:b2:6e:f0 dst 10.100.139.246 self permanent
ba:10:0e:7b:74:89 dst 10.100.139.247 self permanent

Таким образом, можно получить IP-адрес назначения переадресации, соответствующий MAC-адресу.

Здесь следует отметить еще один момент,Является ли это таблицей arp или таблицей FDB, она постоянна., что указывает на то, что запись записи поддерживается вручную.Традиционный способ получения соседей arp через широковещательную рассылку.Если соответствующий arp от однорангового конца получен, одноранговый конец будет помечен как достижимый.После превышения достижимого времени настройки, если одноранговый конец окажется недействительным, он будет помечен как устаревший, а задержка и зонд, которые будут переданы позже, перейдут в состояние обнаружения.Если обнаружение не удалось, оно будет помечено как состояние сбоя. Причина, по которой вводится базовое содержимое arp, заключается в том, что старая версия flannel не использует вышеуказанный метод, а использует временную схему arp.Выпущенный в это время arp указывает на достижимое состояние, что означает, что если flannel выйдет из строя, если превышено достижимое время таймаута, сеть контейнера на этой машине будет прервана.Давайте кратко рассмотрим предыдущую (0.7.x) версию.Для того, чтобы получить arp-адрес пира, ядро ​​сначала отправит arp-консульт, если попытается

/proc/sys/net/ipv4/neigh/$NIC/ucast_solicit

В этот момент в пространство пользователя будет отправлен запрос arp.

/proc/sys/net/ipv4/neigh/$NIC/app_solicit

Предыдущая версия фланели использовала эту функцию, устанавливая

# cat   /proc/sys/net/ipv4/neigh/flannel.1/app_solicit
3

Таким образом, flanneld может получить L3MISS, отправленный ядром в пространство пользователя, и сотрудничать с etcd, чтобы вернуть MAC-адрес, соответствующий этому IP-адресу, и сделать его доступным. Из анализа видно, что в случае выхода из программы flanneld связь между контейнерами будет прервана, что здесь требует внимания. Процесс запуска Flannel показан на следующем рисунке:

Flannel запускает и выполняет newSubnetManager, и создает через него фоновое хранилище данных.В настоящее время поддерживается два типа бэкэндов.По умолчанию используется хранилище etcd.Если для запуска flannel указан параметр "kube-subnet-mgr", интерфейс kubernetes используется для хранения данных.

Конкретный код выглядит следующим образом:

func newSubnetManager() (subnet.Manager, error) {
    if opts.kubeSubnetMgr {
       return kube.NewSubnetManager(opts.kubeApiUrl, opts.kubeConfigFile)
    }
  
    cfg := &etcdv2.EtcdConfig{
       Endpoints: strings.Split(opts.etcdEndpoints, ","),
       Keyfile:   opts.etcdKeyfile,
       Certfile:  opts.etcdCertfile,
       CAFile:    opts.etcdCAFile,
       Prefix:    opts.etcdPrefix,
       Username:  opts.etcdUsername,
       Password:  opts.etcdPassword,
    }
  
    // Attempt to renew the lease for the subnet specified in the subnetFile
    prevSubnet := ReadCIDRFromSubnetFile(opts.subnetFile, "FLANNEL_SUBNET")
  
    return etcdv2.NewLocalManager(cfg, prevSubnet)
 }

С помощью SubnetManager в сочетании с данными etcd, сконфигурированными во время описанного выше развертывания, вы можете получить информацию о конфигурации сети, в основном касающуюся информации о бэкэнде и сегментах сети.Если это vxlan, создайте соответствующий сетевой менеджер с помощью NewManager.Здесь используется простой инженерный режим , Прежде всего, каждый менеджер сетевого режима будет зарегистрирован через инициализацию init,

такие как vxlan

func init() {
    backend.Register("vxlan", New)

если удп

  func init() {
    backend.Register("udp", New)
 }

Другие аналогичны, зарегистрируйте методы построения на карте, чтобы включить соответствующий сетевой менеджер в соответствии с режимом сети, настроенным etcd.

3. Зарегистрируйте сеть

RegisterNetwork сначала создаст сетевую карту с flannel.vxlanID. По умолчанию vxlanID равен 1. Затем он зарегистрирует аренду с помощью etcd и получит соответствующую информацию о сегменте сети. Есть деталь. Старая версия flannel получает новый сегмент сети каждый Когда он запустится, новая версия flannel будет проходить через информацию etcd, которая была зарегистрирована в etcd, чтобы получить ранее выделенный сегмент сети и продолжить его использование.

Наконец, запишите файл локальной подсети через WriteSubnetFile,

    # cat /run/flannel/subnet.env 
FLANNEL_NETWORK=10.254.0.0/16
FLANNEL_SUBNET=10.254.44.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true

Настройте сеть докеров через этот файл. Внимательные читатели могут обнаружить, что MTU здесь не равен 1500, как указано в Ethernet, потому что внешний пакет vxlan также занимает 50 байт.

Конечно, после того, как фланель запущена, необходимо продолжить данные в watch etcd Это три таблицы, которые другие фланельные узлы могут динамически обновлять при добавлении или изменении нового фланельного узла. Основные методы обработки находятся в handleSubnetEvents

    func (nw *network) handleSubnetEvents(batch []subnet.Event) {
 . . .
  
       switch event.Type {//如果是有新的网段加入(新的主机加入)
       case subnet.EventAdded:
  . . .//更新路由表
if err := netlink.RouteReplace(&directRoute); err != nil {
    log.Errorf("Error adding route to %v via %v: %v", sn, attrs.PublicIP, err)
    continue
 } 
//添加arp表
log.V(2).Infof("adding subnet: %s PublicIP: %s VtepMAC: %s", sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC))
             if err := nw.dev.AddARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                log.Error("AddARP failed: ", err)
                continue
             }
 //添加FDB表
             if err := nw.dev.AddFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                log.Error("AddFDB failed: ", err)
  
                              if err := nw.dev.DelARP(neighbor{IP: event.Lease.Subnet.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                   log.Error("DelARP failed: ", err)
                }
  
                continue
             }//如果是删除实践
      case subnet.EventRemoved:
//删除路由
             if err := netlink.RouteDel(&directRoute); err != nil {
                log.Errorf("Error deleting route to %v via %v: %v", sn, attrs.PublicIP, err)
             
          } else {
             log.V(2).Infof("removing subnet: %s PublicIP: %s VtepMAC: %s", sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC))
  
           //删除arp            if err := nw.dev.DelARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                log.Error("DelARP failed: ", err)
             }
 //删除FDB
             if err := nw.dev.DelFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {
                log.Error("DelFDB failed: ", err)
             }
  
             if err := netlink.RouteDel(&vxlanRoute); err != nil {
                log.Errorf("failed to delete vxlanRoute (%s -> %s): %v", vxlanRoute.Dst, vxlanRoute.Gw, err)
             }
          }
       default:
          log.Error("internal error: unknown event type: ", int(event.Type))
       }
    }
 }

Таким образом, добавление и удаление любого хоста во фланеле могут восприниматься другими узлами, тем самым обновляя локальную таблицу переадресации ядра.

Автор: Чэнь Сяоюй

Источник: Технологический институт CreditEase.