Вступление
Часто сетевая лаба представляет собой довольно сложную конструкцию, состоящую из множества устройств, соединенных между собой. В этом помогает виртуализация, благо всякие маршрутизаторы и т.п. могут запускаться в виде контейнеров (скажем, Quagga) или виртуальных машин (здесь я рассмотрю Mikrotik).
Тем не менее развернуть лабу из нескольких устройств с множеством сетевых интерфейсов и подключений, к примеру, для проверки BGP, в том числе между устройствами разных производителей, задача нетривиальная. Можно, конечно, писать плейбуки Ansible для развертывания виртуалок на ESXi и последующей конфигурации устройств, но это само по себе достойная задача. Может быть что-то с vagrant изобразить…
Но недавно я узнал о проекте ContainerLab.dev и весьма впечатлился. Попробовал, оценил, впечатлился еще больше. Полез на Хабр почитать тонкости и хитрости, но, к удивлению, не нашел ни единого поста на эту тему.
Решил исправить. Вдруг кому-то еще облегчит жизнь.
ContainerLab.dev
Как пишут создатели, Containerlab предоставляет простой интерфейс командной строки для оркестрации и управления сетевыми лабами на основе контейнеров. Запускает, строит между ними виртуальные линки. Причем все это богатство описывается в простом и понятном YAML файле.
«Containerlab provides a CLI for orchestrating and managing container-based networking labs. It starts the containers, builds a virtual wiring between them to create lab topologies of users choice and manages labs lifecycle».
Мой пример файла:
name: lab
prefix: ""
mgmt:
network: statics
ipv4_subnet: 172.20.20.0/24
topology:
kinds:
vr-ros:
# https://hub.docker.com/r/iparchitechs/chr
image: iparchitechs/chr:long-term # :7.5beta8 :long-term
nodes:
ubuntu:
kind: linux
image: ubuntu:latest
memory: 256MB
cpu: 0.5
mgmt_ipv4: 172.20.20.21
binds:
- setup_ubuntu.sh:/tmp/setup.sh
exec:
- bash /tmp/setup.sh
- ip addr add dev eth1 10.2.2.21/24
- ip route add 10.2.0.0/16 via 10.2.2.1
alpine1:
kind: linux
image: alpine:latest
memory: 64MB
cpu: 0.5
mgmt_ipv4: 172.20.20.31
binds:
- setup_alpine.sh:/tmp/setup.sh
exec:
- sh /tmp/setup.sh
- ip addr add dev eth1 10.2.3.31/24
- ip route add 10.2.0.0/16 via 10.2.3.1
ros1:
kind: vr-ros
mgmt_ipv4: 172.20.20.11
memory: 512MB
cpu: 1
ports:
- 10122:22
- 10180:80
# startup-config: ros1.cfg
binds:
- setup_mikrotik.sh:/tmp/setup.sh
exec:
- sh /tmp/setup.sh 192.168.122.1 10.2.2 10.2.3
links:
- endpoints: ["ubuntu:eth1","ros1:eth1"]
- endpoints: ["ros1:eth2","alpine1:eth1"]
Фактически задействованные устройства описываются в секции nodes, а связи между ними в links. Минимальное описание:
ubuntu:
kind: linux
image: ubuntu:latest
или
ros1:
kind: vr-ros
image: iparchitechs/chr:long-term # :7.5beta8 :long-term
А как задаются связи между ними и так понятно.
Дальше я более детально поясню важные моменты, в том числе начальную конфигурацию. А для затравки: этот файл с виртуалками Ubuntu, Alpine и маршрутизатором Mikrotik между ними разворачивается простой командой containerlab deploy (ну или с указанием файла топологии, если их несколько, как у меня).
На выходе получили три контейнера. Посмотреть их IP также можно с помощью команды containerlab inspect.
Для удобства я создал alias clab='sudo containerlab'
, так что на скриншотах будет clab
.
С помощью clab graph
можно построить топологию. При этом поднимется веб-сервер. Скриншт ниже именно с него, я только IP адреса добавил для понятности.
(Нюанс: при использовании бриджа топология рисуется кривовато, не знаю пока, что с этим делать)
Теперь можно подключаться к развернутым устройствам (docker exec, ssh…) и проверять, что душе угодно.
Подробности
Сеть
Коли вы заинтересовались описанным, подробнее освещу важные моменты.
Развертывается элементарно (bash -c "$(curl -sL https://get.containerlab.dev)"
).
IP адреса для контейнеров можно не указывать, получат автоматическию. Хотя я предпочитаю указывать явно (mgmt_ipv4, mgmt_ipv6
). Из какой подсети выдавать адреса настраивается: ipv4_subnet
.
Это речь об интерфейсе управления – eth0. По этому адресу вы можете обращаться к своим виртуальным устройствам. Через него же они могут выходить в интернет, получать апдейты…
Но для данных вы создадите дополнительные интерфейсы. И уже их будете соединять друг с другом. В моем случае
links:
- endpoints: ["ubuntu:eth1","ros1:eth1"]
- endpoints: ["ros1:eth2","alpine1:eth1"]
Т.е. у Ubuntu помимо интерфейса управления (eth0) будет eth1, подключенный к Mikrotik (eth1). Другой интерфейс Микротика (eth2) будет подключен к alpine (eth1). IP адреса для этих интерфейсов (eth1 и т.п.) задаются внутри виртуализированного устройства.
Подключить к одному интерфейсу (ros1:eth2) несколько контейнеров нельзя. Но можно создать бридж (сначала на хосте обычным образом, потом в YAML файле). Пример с дополнительным контейнером alpine2 также за eth2 Микротика есть в репозитории (ros-br.clab.yml).
Фактически описываем дополнительное устройство (бридж) br-clab и связываем его:
br-clab:
kind: bridge
links:
- endpoints: ["ubuntu:eth1","ros1:eth1"]
- endpoints: ["ros1:eth2","br-clab:eth1"]
- endpoints: ["alpine1:eth1","br-clab:eth2"]
- endpoints: ["alpine2:eth1","br-clab:eth3"]
Виртуальные машины
С Ubuntu все понятно. Берешь контейнер и наслаждаешься. В том числе есть контейнеризиванные маршрутизаторы и т.п. Но что, если хочется поиграть виртуальной копией аппаратного устройства, для которого нет контейнера, но есть виртуальная машина?
К счастью, имеются проекты, позволяющие «обернуть» виртуалку так, чтобы сделать ее совместимой по сетевой части со средой Containerlab.
Здесь я в качестве примера взял Mikrotik. Другие готовые решения можно посмотреть в разделе kinds. Juniper, Cisco…
Микротик был адаптирован с помощью проекта vrnetlab. Для недавно добавленного Check Point CloudGuard использовался более новый boxen. Если будет интерес, могу отдельно описать, как из скаченного с официального сайта qcow2 (образа для KVM) собрать контейнер.
Сравните описания:
https://containerlab.dev/manual/kinds/linux/ позволяет использовать ваш любимый образ. Я взял Ubuntu и Alpine с традиционного Docker Hub.
https://containerlab.dev/manual/kinds/vr-ros/: MikroTik RouterOS cloud hosted router is identified with vr-ros or vr-mikrotik_ros kind in the topology file. It is built using vrnetlab project and essentially is a Qemu VM packaged in a docker container format.
https://containerlab.dev/manual/kinds/checkpoint_cloudguard/: Check Point Cloudguard virtualized security appliance is identified with checkpoint_cloudguard kind in the topology file. It is built using boxen project and essentially is a Qemu VM packaged in a docker container format.
Понимание отличия «настоящего» контейнера от «не настоящего» очень важно. Команда docker exec для настоящего контейнера работает привычным образом. А вот в случае «не настоящего» вы подключитесь в «контейнер-обертку», а не в свою виртуальную машину с желаемым устройством. Я тут поначалу оторопел, почему не та ОС и куда делись ожидаемые команды.
Я в «обертке» (обычно это не требуется): docker exec --it ros1 bash
А вот ssh казалось бы на IP контейнера пробросит действительно внутрь искомой виртуальной машины: ssh admin@ros1
Магия!
И обратите внимание на IP адрес интерфейса управления ether1: 172.31.255.30/30, хотя SSH мы делали на 172.20.20.11. Это тоже особенность «обертки», которую просто нужно знать (и не портить этот IP при настройке Микротика или другой системы).
Сразу приведу скриншоты, как все работает, как с Ubuntu я могу пингануть Alpine через Микротик (и обратно).
Понятно, что для этого внутри Ubuntu я прописал дополнительный статический маршрут в сторону 10.2.2.1 (mikrotik:eth1), а на alpine в сторону 10.2.3.1 (mikrotik:eth2).
Как задать IP для дополнительных интерфейсов, маршруты и прочие настройки? Да любым привычным способом. Хоть CLI, хоть Web-интерфейс. Хоть автоматизировать :)
Настройки контейнеров
Во-первых, поддерживаются традиционные для контейнеров параметры: image, memory, cpu… Доступны bind, exec. Я использую их для автоматизации первоначальной настройки.
В данном примере я монтирую в контейнер скрипты с «универсальными» настройками и запускаю их через exec. Также через exec я сразу же на этапе развертывания лабы задаю параметры, специфичные для конкретного контейнера (IP адреса, маршруты…)
Можно опубликовать порты. Скажем, для Микротика:
ports:
- 10122:22
- 10180:80
Как в традиционном контейнерном мире: это позволит подключиться к основному хосту, на котором развернут Containerlab (http://CLab:10180) и увидеть web-интерфейс Микротика.
Хотя я предпочитаю прописать маршрут для сети управления (172.20.20.0 в моем случае) на CLab. Это позволит обращаться к Микротику напрямую, без публикации портов на хосте (http://172.20.20.11, как задано в YAML файле конфигурации лабы).
Настройки «не настоящих контейнеров»
Помните, docker exec пробрасывает не в виртуальную машину, а в «контейнер-обертку»? Так же и exec выполнит команды в контейнере vrnetlab или boxen, но не там, где хотелось бы. Так что придется действовать хитрее.
Итак,
ros1:
kind: vr-ros
mgmt_ipv4: 172.20.20.11
# startup-config: ros1.cfg
binds:
- setup_mikrotik.sh:/tmp/setup.sh
exec:
- sh /tmp/setup.sh 192.168.122.1 10.2.2 10.2.3
По идее Микротику можно «скормить» файл конфигурации, но я опишу более универсальный подход (строчка startup-config закомментирована).
Фактически я выполняю скрипт setup_mikrotik.sh в «обертке», и изнутри этого контейнера делаю ssh внутрь виртуальной машины с Микротиком.
#!/bin/bash
intIP=172.31.255.30
wait_ssh() {
printf "Waiting for ssh: "
SSH_UP=0
while [ $SSH_UP -eq 0 ]
do
printf "."
SSH_UP=$(wget --timeout=1 --tries=1 $intIP:22 2>&1 | grep -c Read);
done
printf " SSH UP"
}
apt-get update > /dev/null
apt-get install -y sshpass > /dev/null
wait_ssh
cat <<EOF | sshpass -p admin ssh -tt -o StrictHostKeyChecking=no admin@$intIP
/ip dns set servers=$1;
/ip address add address=$2.1/24 interface=ether2 network=$2.0;
/ip address add address=$3.1/24 interface=ether3 network=$3.0;
/ip route add distance=1 gateway=172.31.255.29;
EOF
Через ssh я выполню ряд команд Микротика (/ip dns set и т.п.). В качестве параметров передаю ему индивидуальные настройки (IP, DNS…). Для подстановки пароля я предварительно устанавливаю sshpass. (expect в «обертке» ожидаемо отсутствует). Потом можно и ключ ssh загрузить, но на первом этапе так.
Еще из интересного – после того, как контейнер стартовал, это не значит, что виртуальная машина с Микротиком сразу готова к работе, ей нужно немного времени. Поэтому я написал функцию wait_ssh, ожидающую готовности SSH в виртуальной машине. Проверку делаю через wget, поскольку он уже установлен. Во избежание зависания, я ограничиваю timeout. Ничего толкового я не получу, код возврата будет «Ошибка» в любом случае. Но с помощью grep я отличить Read в случае успешного подключения от полной ошибки подключения (не готовности виртуальной машины).
Заключение
Разумеется, так можно создавать весьма сложные топологии (скажем, много интерфейсов, BGP, VPN, переключения на резервные линки…).
Для Check Point CloudGuard вы получите традиционное:
В данном случае у меня Standalone конфигурация (Management + Gateway) и дополнительный шлюз.
Было бы очень интересно узнать, знакомы ли вы с ContainerLab, представляет ли это для вас интерес.
Упомянутые в статье и другие файлы можно найти на GitHub.