Сервер в кармане, или просто о сложном!

главная - Статьи - Виртуализация



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.

Удачи!



20.04.2020 20:12 Rufat Nuriyev
Огромное спасибо!

У меня не получалось с порт форвардингом для KVM, не мог понять почему-то правила переставали работать.

Ваш подход с Хуками помог!
24.04.2020 09:39 bzzz
Да, пришлось в свое время напрячь мозг. Удачи!

Авторизуйтесь для добавления комментариев!


    забыли пароль?    новый пользователь?