главная - Статьи - Виртуализация
Libvirt. Проброс портов в гостевую vm.
Теги: libvirt Linux Iptables NAT
Задача: сделать проброс порта с хоста kvm на выбранный порт на виртуальной машине, подключенной к сети default (forward mode=nat). Например, сделать доступным снаружи веб-сервер, запущенный на виртуальной машине с именем vm5. Имя интересующей машины уточните с помощью virsh list --all.
Проблема: libvirt загружает свои правила iptables для виртуальных сетей. Причем libvirt стартует после iptables, поэтому он затирает/дополняет восстановленные из /etc/sysconfig/iptables правила и вы на этот процесс никак не влияете. Т.е. вы внесли изменения в iptables, сохранили текущие правила в /etc/sysconfig/iptables, все хорошо, а потом перезагрузили хост и увидели, что ваши сохраненные правила либо затерты либо дополнены правилами для виртуальных сетей. Ничего приятного, т.к. перестают работать правила проброса портов и др.
Например, ваш основной скрипт iptables (пусть будет /root/iptables.sh), суть которого заключается в пробросе порта 8041 с хоста на 80-й порт гостевой vm 192.168.122.40, выглядит как-то так:
#!/bin/sh IF_EXT="enp1s0" IPT="/sbin/iptables" IPT6="/sbin/ip6tables" # flush $IPT --flush $IPT -t nat --flush $IPT -t mangle --flush $IPT -X $IPT6 --flush # loopback $IPT -A INPUT -i lo -j ACCEPT $IPT -A OUTPUT -o lo -j ACCEPT # default $IPT -P INPUT DROP $IPT -P OUTPUT DROP $IPT -P FORWARD DROP $IPT6 -P INPUT DROP $IPT6 -P OUTPUT DROP $IPT6 -P FORWARD DROP # allow forwarding echo 1 > /proc/sys/net/ipv4/ip_forward # NAT CHAIN $IPT -t nat -D PREROUTING -d 192.168.122.40 -p tcp --dport 8041 -j DNAT --to 192.168.122.40:80 # INPUT CHAIN $IPT -A INPUT -p tcp ! --syn -m state --state NEW -j DROP $IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # ssh $IPT -A INPUT -p tcp --dport 1624 -j ACCEPT # OUTPUT CHAIN $IPT -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT # FORWARD CHAIN $IPT -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT $IPT -A FORWARD -i $IF_EXT -d 192.168.122.40/32 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
После выполнения скрипта вы сможете, набрав http://1.2.3.4:8041, увидеть веб-сайт, запущенный на виртуальной машине 192.168.122.40:80. Но при этом вы затираете правила для виртуальных сетей, которых может быть множество. Но при этом работа других виртуальных машин бедет нарушена, сеть перестанет быть им доступной (мы же все --flush, по дефолту drop и вообще мы - молодцы). Libvirt умеет с этим бороться и при перезагрузке хоста (или при перезапуске libvirt или др.) libvirt восстановит дефолтные правила для сетей типа nat, bridge, isolated и др., которые у вас настроены. При этом ваши правила из вашего скрипта, относящиеся к пробросу порта 80 на виртуальную машину, перестанут работать (хотя и не будут удалены), т.к. выше них будет стоять правило reject, которое безотказный libvirt любезно восстановит для сети virbr0, что вы наглядно можете созерцать (iptables -L FORWARD -n -v). Как бороться с этой напастью? Ведь не всегда есть возможность или потребность использовать бридж в сети хоста.
Как вариант - можно создать службу, которая выполняла бы ваши правила (скажем, из /scripts/custom-iptables.sh) после запуска libvirt. Про создание такой службы можно посмотреть здесь.
А можно использовать т.н. hooks libvirt - дополнительные правила для виртуальных сетей и гостевых машин. Много интересного можно почерпнуть на https://www.libvirt.org/hooks.html и http://wiki.libvirt.org/page/Networking.
Libvirt hooks
Чтобы в дальнейшем не возникало проблем со сменой ip-адресов, рекомендуется использовать статические ip, а не выданные dhcp. Или делать привязку mac-ip. Также рекомендуется сначала выключить гостевую машину. Я не выключал (забыл просто) и все прошло успешно.
Файл с хуком должен находиться в директории /etc/libvirt/hooks. Если ее нет, создадим:
# mkdir /etc/libvirt/hooks
# chmod 700 /etc/libvirt/hooks
Создадим файл /etc/libvirt/hooks/qemu, в котором укажем, что входящие на 8041 и 8042 порты соответственно должны быть перенаправлены на 80 и 443 порты гостевой vm5. В моем примере 192.168.88.2 - внешний ip-адрес хоста KVM, а 192.168.122.40 - ip-адрес виртуальной машины vm5 на этом хосте.
#!/bin/bash # used some from advanced script to have multiple ports: use an equal number of guest and host ports # Update the following variables to fit your setup Guest_name=vm5 Guest_ipaddr=192.168.122.40 Host_ipaddr=192.168.88.2 Host_port=( '8041' '8042' ) Guest_port=( '80' '443' ) length=$(( ${#Host_port[@]} - 1 )) if [ "${1}" = "${Guest_name}" ]; then if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then for i in `seq 0 $length`; do iptables -t nat -D PREROUTING -d ${Host_ipaddr} -p tcp --dport ${Host_port[$i]} -j DNAT --to ${Guest_ipaddr}:${Guest_port[$i]} iptables -D FORWARD -d ${Guest_ipaddr}/32 -p tcp -m state --state NEW -m tcp --dport ${Guest_port[$i]} -j ACCEPT done fi if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then for i in `seq 0 $length`; do iptables -t nat -A PREROUTING -d ${Host_ipaddr} -p tcp --dport ${Host_port[$i]} -j DNAT --to ${Guest_ipaddr}:${Guest_port[$i]} iptables -I FORWARD -d ${Guest_ipaddr}/32 -p tcp -m state --state NEW -m tcp --dport ${Guest_port[$i]} -j ACCEPT done fi fi
Первый if - при остановке сервиса правила удаляются (-D), второй if - при старте сервиса правила добавляются (-I, -A).
Вы можете добавить сюда любые правила, команды iptables или другие. Главное, не городите лишнего и постарайтесь не переносить сюда все правила iptables, включая защиту хоста. Лучше использовать этот инструмент по назначению и в этом месте прописать только правила для работы маршрутизации пакетов из внешнего мира к виртуальным машинам и обратно или других задач, но связанных исключительно с libvirt и теми правилами, которые затираются при его перезапуске.
Делаем файл исполняемым:
# chmod 700 /etc/libvirt/hooks/qemu
Перезапускаем libvirtd:
# systemctl restart libvirtd.service
Результат можно посмотреть сразу, без перезагрузки:
# iptables -t nat -L -v -n Chain PREROUTING (policy ACCEPT 92 packets, 12344 bytes) pkts bytes target prot opt in out source destination 15 764 DNAT tcp -- * * 0.0.0.0/0 192.168.88.2 tcp dpt:8041 to:192.168.122.40:80 0 0 DNAT tcp -- * * 0.0.0.0/0 192.168.88.2 tcp dpt:8042 to:192.168.122.40:443 ...
После перезагрузки хоста, или если вы выполните какой-то свой скрипт с кастомными правилами iptables и даже сохраните их в /etc/sysconfig/iptables, то рестарт службы libvirtd возвратит все на свои места.
Чтобы не нарушать работу виртуальных машин при изменениях iptables вашим скриптом, особенно если он включает в себя --flush
, -t nat --flush
и тому подобное, то сразу после выполнения вашего скрипта него выполните рестарт libvirtd.
Удачи!
Авторизуйтесь для добавления комментариев!
У меня не получалось с порт форвардингом для KVM, не мог понять почему-то правила переставали работать.
Ваш подход с Хуками помог!