Docker – виртуализация сети. Часть 2
О технологиях

Docker – виртуализация сети. Часть 2

924
14 минут

В первой статье, посвященной виртуализации сети в Docker, мы рассмотрели, как Docker создает выделенное пространство имен для оверлей сети и соединяет контейнеры с этим пространством имен. Мы также увидели, что для оверлейной связи между хостами Docker используется VXLAN. В этой статье мы рассмотрим протокол VXLAN более подробно, а также его использование Docker-ом.

VXLAN

Virtual Extensible LAN (VXLAN) является технологией сетевой виртуализации, созданной для решения проблем масштабируемости в больших системах облачных вычислений.

VXLAN — это технология или протокол туннелирования, который инкапсулирует L2 кадры внутри UDP-пакетов, обычно отправляемых на порт 4789. Этот протокол первоначально был разработан компаниями Arista и Cisco. Основная задача VXLAN заключалась в упрощении развертывания облачных сред, требующих изоляции на уровне L2 для своих пользователей. Другими словами, для того, чтобы изолировать трафик каждого отдельного пользователя в облачной среде. Он обеспечивает:

  • Туннелирование L2 поверх L3, чтобы избежать необходимость организовывать L2 связность между всеми хостами в кластере
  • Более 4096 изолированных сетей (идентификаторы VLAN ограничены 4096)

VXLAN нативно поддерживается OpenvSwitch в ядре Linux, начиная с версии 3.7. Кроме того, VXLAN работает с сетевыми пространствами имен с версии ядра 3.16.

Вот как выглядит пакет VXLAN:

Docker-Networking-Part-2-VXLAN-packet.png

«Исходящий» IP-пакет используется для организации связи между Docker хостами, а исходный L2 кадр, отправляемый контейнером, инкапсулируется в пакет UDP с дополнительным заголовком VXLAN, содержащим метаданные (в частности, VXLAN ID).

Мы можем проверить, что трафик между нашими хостами ходит по VXLAN при помощи tcpdump. Давайте выполним ping до контейнера X-1 из контейнера на docker-2:


$ docker run -it --rm --net demo-network busybox ping 192.168.10.100
PING 192.168.10.100 (192.168.10.100): 56 data bytes
64 bytes from 192.168.10.100: seq=0 ttl=64 time=0.700 ms
64 bytes from 192.168.10.100: seq=1 ttl=64 time=0.370 ms
64 bytes from 192.168.10.100: seq=2 ttl=64 time=0.398 ms
^C
--- 192.168.10.100 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.370/0.489/0.700 ms

И прослушаем трафик на docker0 на хосте docker-1:


$ sudo tcpdump -pni eth0 "port 4789"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
10:11:00.694745 IP 192.168.1.5.45031 > 192.168.1.4.4789: VXLAN, flags [I] (0x08), vni 256
IP 192.168.10.2 > 192.168.10.100: ICMP echo request, id 256, seq 0, length 64
10:11:00.694994 IP 192.168.1.4.49474 > 192.168.1.5.4789: VXLAN, flags [I] (0x08), vni 256
IP 192.168.10.100 > 192.168.10.2: ICMP echo reply, id 256, seq 0, length 64
10:11:01.694855 IP 192.168.1.5.45031 > 192.168.1.4.4789: VXLAN, flags [I] (0x08), vni 256
IP 192.168.10.2 > 192.168.10.100: ICMP echo request, id 256, seq 1, length 64

Каждый пакет генерирует две строки вывода в tcpdump:

  • «Внешний» кадр (192.168.1.4 и 192.168.1.5, это хосты Docker)
  • «Внутренний» кадр (IP-адреса 192.168.10.100 и 192.168.10.2, это наши контейнеры) с полезной нагрузкой ICMP. Мы также можем видеть MAC-адреса наших контейнеров.
Docker-Networking-Part-2-Overlay-network-Detailed-VXLAN-1.png

Определение имен и местоположения контейнеров

Мы видели, что мы можем пинговать контейнеры на docker-1 из контейнеров на docker-2, используя протокол VXLAN, однако, мы пока не знаем, как контейнеры на каждом хосте могут сопоставлять IP-адреса MAC-адресам и как L2-фреймы пересылаются на соответствующий хост.

Давайте создадим контейнер на docker-2 и посмотрим на его таблицу ARP:


	 $ docker run -it --rm --net demo-network debian bash
	 root@3b10b104b467:/# ip neighbor show

В контейнере нет информации ARP. Если мы выполним ping до контейнера X-1, контейнер сгенерирует ARP-трафик. Давайте сначала посмотрим, как этот трафик просматривается в пространстве имен оверлей сети на хосте docker-1:


	 $ sudo nsenter --net=/var/run/docker/netns/7-0f222fcb18 tcpdump -pni any "arp"

Возвращаемся к нашему контейнеру и попытаемся выполнить ping X-1, который будет генерировать ARP-пакет:


	 root@309a2c8ba35d:/# ping 192.168.10.100
	 PING 192.168.10.100 (192.168.10.100): 56 data bytes
	 64 bytes from 192.168.10.100: icmp_seq=0 ttl=64 time=0.662 ms
	 ^C--- 192.168.10.100 ping statistics ---
	 1 packets transmitted, 1 packets received, 0% packet loss
	 round-trip min/avg/max/stddev = 0.662/0.662/0.662/0.000 ms

В tcpdump на docker-1 ничего нет, т.е. трафик ARP не отправляется в туннель VXLAN (вы можете увидеть какие-то ARP запросы, но не для хоста 192.168.10.100). Давайте пересоздадим контейнер на docker-2 и выполним tcpdump в пространстве имен оверлей сети на docker-2, чтобы убедиться, что мы получаем ARP-запросы.


	 $ docker run -it --rm --net demo-network debian bash

Давайте запустим tcpdump в другом окне. Далее посмотрим список всех пространств имен Docker для определения пространства имен, связанного с нашей оверлей сетью. Это пространство имен при перезапуске контейнера может измениться, поскольку пространство имен оверлей сети удаляется, если в данной сети на хосте больше нет контейнеров. Все так же на docker-2:


	 $ sudo ls -1 /var/run/docker/netns
	 36fd4ac0a9c6
	 9-0f222fcb18
	 $ sudo nsenter --net=/var/run/docker/netns/9-0f222fcb18 tcpdump -peni any "arp"

Когда мы выполняем ping из окна с контейнером на docker-2, вот что мы видим в tcpdump:


	 11:42:21.005002 Out 02:42:c0:a8:0a:02 ethertype ARP (0x0806), length 44: Request who-has 192.168.10.100 tell 192.168.10.2, length 28
	 11:42:21.004977   B 02:42:c0:a8:0a:02 ethertype ARP (0x0806), length 44: Request who-has 192.168.10.100 tell 192.168.10.2, length 28
	 11:42:21.005006 In 02:42:c0:a8:0a:64 ethertype ARP (0x0806), length 44: Reply 192.168.10.100 is-at 02:42:c0:a8:0a:64, length 28
	 11:42:21.005016 Out 02:42:c0:a8:0a:64 ethertype ARP (0x0806), length 44: Reply 192.168.10.100 is-at 02:42:c0:a8:0a:64, length 28

Мы можем увидеть ARP-запросы и ответы, что означает, что в пространстве имен оверлей сети есть вся необходимая информация, а также что оно действует как ARP прокси. Мы легко можем проверить это, выполнив команду на docker-2:


	 $ sudo nsenter --net=/var/run/docker/netns/9-0f222fcb18 ip neigh show
	 192.168.10.100 dev vxlan0 lladdr 02:42:c0:a8:0a:64 PERMANENT
 192.168.10.2 dev vxlan0 lladdr 02:42:c0:a8:0a:02 PERMANENT

Запись помечена как ПОСТОЯННАЯ, что означает, что она является статической и была добавлена «вручную», а не в результате обнаружения ARP. Что произойдет, если мы создадим второй контейнер на docker-1?


	 $ docker run -d --ip 192.168.10.200 --net demo-network --name X-2 busybox sleep 3600

Смотрим с docker-2:


	 $ sudo nsenter --net=/var/run/docker/netns/9-0f222fcb18 ip neigh show
	 192.168.10.200 dev vxlan0 lladdr 02:42:c0:a8:0a:c8 PERMANENT
	 192.168.10.100 dev vxlan0 lladdr 02:42:c0:a8:0a:64 PERMANENT
	 192.168.10.2 dev vxlan0 lladdr 02:42:c0:a8:0a:02 PERMANENT

Запись была добавлена автоматически, даже если в этот новый контейнер еще не был отправлен трафик. Это означает, что Docker автоматически заполняет записи ARP в пространстве имен оверлей сети, а также что интерфейс vxlan действует как прокси для ответа на ARP запросы.

Если мы посмотрим на конфигурацию интерфейса vxlan, то мы увидим, что в нем установлен флаг прокси-сервера, который объясняет это поведение. С хоста docker-2:


	 46: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UNKNOWN mode DEFAULT
link/ether e6:63:36:70:3d:15 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 1
vxlan id 256 srcport 0 0 dstport 4789 proxy l2miss l3miss ageing 300
bridge_slave addrgenmode eui64

Кстати, а на каком хосте находится MAC 02:42:c0:a8:0a:64? Давайте посмотрим базу данных переадресации bridge в пространстве имен оверлей сети. Также на хосте docker-2:


	 $ sudo nsenter --net=/var/run/docker/netns/9-0f222fcb18 bridge fdb show
	 e6:63:36:70:3d:15 dev vxlan0 master br0 permanent
	 6e:dd:a3:b1:59:83 dev veth0 master br0 permanent
	 02:42:c0:a8:0a:c8 dev vxlan0 dst 192.168.1.4 link-netnsid 0 self permanent
	 02:42:c0:a8:0a:02 dev vxlan0 dst 192.168.1.5 link-netnsid 0 self permanent
	 02:42:c0:a8:0a:64 dev vxlan0 dst 192.168.1.4 link-netnsid 0 self permanent
	 33:33:00:00:00:01 dev veth0 self permanent
	 01:00:5e:00:00:01 dev veth0 self permanent

Мы видим, что MAC-адреса для наших двух контейнеров на docker-1 находятся в базе данных с постоянным флагом. Эта информация также динамически заполняется Docker.

Docker-Networking-Part-2-Overlay-network-Detailed-VXLAN-2.png

Распространение информации MAC/FDB

Мы только что обнаружили, что Docker автоматически заполняет информацию MAC/FDB. Как это делается?

Сначала мы можем взглянуть на содержание ключей, хранящихся в Consul.

Docker-Networking-Part-2-Overlay-network-Detailed-Consul-1.png

Ключ network, который был пуст, когда мы наши эксперименты, теперь содержит информацию, и мы можем легко распознать в ней идентификатор созданной нами оверлей сети: 0f222fcb18197027638550fd3c711c57c0ab6c16fd20456aed0ab38629884b37.

Web-интерфейс Consul не отображает ключи, когда они слишком длинные, однако, мы можем использовать curl для просмотра содержимого нужного нам ключа. Единственное, что нужно иметь в виду, это что Docker хранит информацию в формате JSON, которая закодирована в base64, а Consul в JSON формате отвечает на запросы):


$ network=$(docker network inspect demo-network -f {{.Id}})
$ curl -s http://192.168.1.6:8500/v1/kv/docker/network/v1.0/network/${network}/ | jq -r ".[0].Value"  |  base64 -d | jq .
{
  "addrSpace": "GlobalDefault",
  "attachable": false,
  "configFrom": "",
  "configOnly": false,
  "created": "2017-08-28T14:26:37.986950821Z",
  "enableIPv6": false,
  "generic": {
    "com.docker.network.enable_ipv6": false,
    "com.docker.network.generic": {}
  },
  "id": "0f222fcb18197027638550fd3c711c57c0ab6c16fd20456aed0ab38629884b37",
  "inDelete": false,
  "ingress": false,
  "internal": false,
  "ipamOptions": {},
  "ipamType": "default",
  "ipamV4Config": "[{\"PreferredPool\":\"192.168.10.0/24\",\"SubPool\":\"\",\"Gateway\":\"\",\"AuxAddresses\":null}]",
  "ipamV4Info": "[{\"IPAMData\":\"{\\\"AddressSpace\\\":\\\"GlobalDefault\\\",\\\"Gateway\\\":\\\"192.168.10.1/24\\\",\\\"Pool\\\":\\\"192.168.10.0/24\\\"}\",\"PoolID\":\"GlobalDefault/192.168.10.0/24\"}]",
  "labels": {},
  "name": "demo-network",
  "networkType": "overlay",
  "persist": true,
  "postIPv6": false,
  "scope": "global"
}

Мы можем найти все метаданные нашей сети:

  • Имя: demo-network
  • ID: 0f222fcb18197027638550fd3c711c57c0ab6c16fd20456aed0ab38629884b37
  • Диапазон подсети: 192.168.10.0/24

Мы также можем получить информацию о конечных точках подключения. Правда такой вывод curl трудно читается, поэтому мы будем использовать этот небольшой python скрипт для получения этой информации:


import consul
import json

c=consul.Consul(host="192.168.1.6",port=8500)

(idx,endpoints)=c.kv.get("docker/network/v1.0/endpoint/",recurse=True)
epdata=[ ep['Value'] for ep in endpoints if ep['Value'] is not None]

for data in epdata:
    jsondata=json.loads(data.decode("utf-8"))
    print("Endpoint Name: %s" % jsondata["name"])
    print("IP address: %s" % jsondata["ep_iface"]["addr"])
    print("MAC address: %s" % jsondata["ep_iface"]["mac"])
    print("Locator: %s\n" % jsondata["locator"])

Установка Python-модуля для доступа к Consul:


$ sudo yum -y install epel-release
$ sudo yum -y install python-pip
$ sudo pip install python-consul

Сценарий отображает основные части информации о конечных точках контейнера:

  • Имя контейнера
  • IP-адрес контейнера
  • MAC-адрес контейнера
  • Хост, на котором находится контейнер

Вот что нам теперь известно о нашей конфигурации:


Endpoint Name: X-1
IP address: 192.168.10.100/24
MAC address: 02:42:c0:a8:0a:64
Locator: 192.168.1.4

Endpoint Name: X-2
IP address: 192.168.10.200/24
MAC address: 02:42:c0:a8:0a:c8
Locator: 192.168.1.4

Consul используется как хранилище-справочник для всей статической информации. Однако, для корректной работы сети в Docker недостаточно простого динамического уведомления всех хостов в процессе создания контейнеров. Docker дополнительно использует Serf и Gossip. Мы можем легко проверить это, если подписавшемся на события serf на хосте docker-1 и создадим контейнер на хосте docker-2:


$ serf agent -bind 0.0.0.0:17946 -join 192.168.1.5:7946 -node demo-node -log-level=debug -event-handler=./event_handler.sh

Серф запускается со следующими параметрами:

  • bind: слушать порт 17946, (порт 7946 уже используемый Docker)
  • join: присоединиться к serf-кластеру
  • node: дать альтернативное имя узлу (т.к. docker-1 уже занят)
  • event-handler: простой скрипт для отображения serf-событий
  • log-level: требуется для просмотра вывода сценария обработчика событий

Содержимое event_handler.sh:


#!/usr/bin/bash

echo "New event: ${SERF_EVENT}"
while read line; do
    printf "${line}\n"

Давайте создадим контейнер на хосте docker-2 и посмотрим на вывод на docker-1:


	 $ docker run -it --rm --net demo-network debian sleep 10

При этом на хосте docker-1 в выводе получим:


	 $ New event: user
	 join 192.168.10.3 255.255.255.0 02:42:c0:a8:00:02

И через 10 секунд:


	 $ New event: user
	 leave 192.168.10.3 255.255.255.0 02:42:c0:a8:00:02

Демон Docker подписывается на эти события для создания и удаления записей в таблицах ARP и FDB хоста.

Docker-Networking-Part-2-Overlay-network-Detailed-VXLAN-3.png

В режиме Swarm Docker не полагается на Serf для синхронизации информации между узлами. В этом режиме он полагается на собственную реализацию протокола Gossip, которая работает точно так же.

Альтернативные варианты обнаружения VXLAN

Демон Docker автоматически заполняет таблицы ARP и FDB на основе информации, полученной от протокола Gossip через Serf, и полагается на проксирование ARP через интерфейс VLXAN. Однако, VXLAN тоже дает нам возможности для обнаружения.

Обнаружение ТОЧКА-ТОЧКА

Когда VXLAN настроен с опцией «remote», он отправляет весь неизвестный трафик на этот IP-адрес. Эта настройка очень проста, но ограничена только туннелями между двумя хостами.
Docker-Networking-Part-2-Point-to-point-resolution.png

Обнаружение мультикаст

Когда VXLAN настроен с опцией «group», он отправляет весь неизвестный трафик в мультикаст группу. Такая настройка очень эффективна, но требует мальтикаст связи между всеми хостами в сети, что не всегда возможно, особенно при использовании публичного Облака.

Docker-Networking-Part-2-Multicast-resolution.png

Для получения более подробной информации о конфигурации VXLAN в Linux.


В этой статье были рассмотрены особенности совместной работы VXLAN и Docker, а также управление таблицами MAC и FDB при помощи Serf и Grossip.

17 сентября 2019
Docker и InSpec: проверка контейнерной инфраструктуры на безопасность
Cis-docker-benchmark – это ни что иное как профиль соответствия InSpec, который содержит в себе набор тестов для запуска в автоматическом режиме. По итогам тестирования вы получаете детальное описание того, что у вас сделано хорошо, а что требует улучшения.
0 минут
684
6 сентября 2019
Docker: решения для хранения образов
На сегодняшний день ни одно решение, построенное на основе или просто использующее Docker, не обходится без хранилища Docker образов (images). Такие хранилища делятся на публично доступные и приватные.
1 минута
1146
23 августа 2019
Docker: проверка контейнеров на безопасность
Насколько безопасен Docker? В первые дни, ответ на этот вопрос у профессионалов был бы “не очень”. По справедливости, когда Docker впервые был запущен в 2013 году, ему определенно не хватало надежных функций безопасности и инструментов, позволивших бы ему обеспечить безопасность контейнеров на достаточном для Enterprise предприятий уровне.
1 минута
698
scrollup