Строим виртуальную сеть

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

Посмотрим, какие средства для этого предлагает OpenVZ. Он предлагает 2 типа виртуальных сетевых интерфейсов:

Если в качестве маршрутизатора/брандмауэра для доступа к виртуальным серверам использовать физический сервер, стоит, несомненно, предпочесть venet. Поскольку такая конфигурация более распространена, то и venet-интерфейсы используются чаще. Однако чем сложнее конфигурация маршрутизатора/брандмауэра, тем больше оснований появляется для вынесения его в отдельный виртуальный сервер, чтобы не перегружать физический сервер лишними задачами и лишним ПО. В нашем случае дополнительным поводом стало желание иметь один и тот же адрес виртуального сервера, доступный извне, независимо от адреса узла кластера, на котором он в данный момент работает. Эта конфигурация в некоторых случаях может оказаться еще более сложной, если, например, на проброшенном физическом интерфейсе организовать поддержку IEEE 802.1Q VLAN, чтобы принять несколько vlan-ов, но с точки зрения настройки OpenVZ эта конфигурация не будет ничем отличаться от того, что было рассмотрено выше "-- разница будет только в настройке проброшенного сетевого интерфейса. Более важным является то, что в случае использования в качестве маршрутизатора/брандмауэра виртуального сервера более удобным будет построить виртуальную сеть на veth-интерфейсах. Выглядеть это будет так, как показано на рисунке 3.

.: Схема соединения виртуальных серверов
Image veth

Итак, для каждого виртуального сервера мы создаем veth-интерфейс, а концы этих интерфейсов со стороны физического сервера объединяем в бридж "-- в результате получается аналог хаба, в который включены все виртуальные сервера. Один из них уже имеет проброшенный в него физический интерфейс "-- этот виртуальный сервер и будет играть роль маршрутизатора/брандмауэра.

Создаем и стартуем виртуальные сервера:

[root@m1 ~]# vzctl create 102 --ostemplate altlinux-sisyphus --config vps.basic
Creating VE private area: /var/lib/vz/private/102
Performing postcreate actions
VE private area was created
[root@m1 ~]# vzctl set 102 --name mail --save
Name ve1 assigned
Saved parameters for VE 102
[root@m1 ~]# vzctl set mail --onboot yes --save
Saved parameters for VE 102
[root@m1 ~]# vzctl set mail --hostname mail.mydomain.com --save
Saved parameters for VE 102
[root@m1 ~]# vzctl start mail
Starting VE ...
VE is mounted
Adding IP address(es): 192.168.199.2
Setting CPU units: 1000
Set hostname: mail.mydomain.com
VE start in progress...

[root@m1 ~]# vzctl create 103 --ostemplate altlinux-sisyphus --config vps.basic
Creating VE private area: /var/lib/vz/private/103
Performing postcreate actions
VE private area was created
[root@m1 ~]# vzctl set 103 --name dbms --save
Name ve2 assigned
Saved parameters for VE 103
[root@m1 ~]# vzctl set dbms --onboot yes --save
Saved parameters for VE 103
[root@m1 ~]# vzctl set dbms --hostname dbms.mydomain.com --save
Saved parameters for VE 103
[root@m1 ~]# vzctl start dbms
Starting VE ...
VE is mounted
Adding IP address(es): 192.168.199.3
Setting CPU units: 1000
Set hostname: dbms.mydomain.com
VE start in progress...

Создаем и конфигурируем veth-интерфейсы внутри виртуальных серверов:

[root@m1 ~]# vzctl set router --veth_add veth1,00:12:34:56:78:9A,eth0,00:12:34:56:78:9B --save
Processing veth devices
Saved parameters for VE 101
[root@m1 ~]# vzctl exec router ip address add 192.168.199.1/24 dev eth0
[root@m1 ~]# vzctl exec router ip link set eth0 up

[root@m1 ~]# vzctl set mail --veth_add veth2,00:12:34:56:78:9C,eth0,00:12:34:56:78:9D --save
Processing veth devices
Saved parameters for VE 102
[root@m1 ~]# vzctl exec mail ip address add 192.168.199.2/24 dev eth0
[root@m1 ~]# vzctl exec mail ip link set eth0 up

[root@m1 ~]# vzctl set dbms --veth_add veth3,00:12:34:56:78:9E,eth0,00:12:34:56:78:9F --save
Processing veth devices
Saved parameters for VE 103
[root@m1 ~]# vzctl exec dbms ip address add 192.168.199.3/24 dev eth0
[root@m1 ~]# vzctl exec dbms ip link set eth0 up

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

Объединяем концы veth-интерфейсов со стороны физического сервера в бридж:

[root@m1 ~]# ip link set veth1 up
[root@m1 ~]# ip link set veth2 up
[root@m1 ~]# ip link set veth3 up
[root@m1 ~]# brctl addbr br0
[root@m1 ~]# brctl addif br0 veth1
[root@m1 ~]# brctl addif br0 veth2
[root@m1 ~]# brctl addif br0 veth3
[root@m1 ~]# ip link set br0 up

Проверяем работоспособность внутренней виртуальной сети:

[root@m1 ~]# vzctl exec router ping 192.168.199.2
PING 192.168.199.2 (192.168.199.2) 56(84) bytes of data.
64 bytes from 192.168.199.2: icmp_seq=1 ttl=64 time=15.9 ms

[root@m1 ~]# vzctl exec router ping 192.168.199.3
PING 192.168.199.3 (192.168.199.3) 56(84) bytes of data.
64 bytes from 192.168.199.3: icmp_seq=1 ttl=64 time=3.71 ms

Теперь на виртуальных серверах описываем маршрут во внешнюю физическую сеть:

[root@m1 ~]# vzctl exec mail ip route add 192.168.0.0/16 via 192.168.199.1
[root@m1 ~]# vzctl exec dbms ip route add 192.168.0.0/16 via 192.168.199.1

На виртуальном маршрутизаторе включаем пересылку пакетов между физической и виртуальной сетями:

[root@m1 ~]# vzctl exec router sysctl -w net.ipv4.ip_forward=1

Теперь если на машине из физической сети 192.168.46.0/24 описать маршрут в сеть 192.168.199.0/24 (или настроить NAT на виртуальном маршрутизаторе), мы получим то, чего и добивались:

[root@m1 ~]# vzctl exec mail ping 192.168.46.1
PING 192.168.46.1 (192.168.46.1) 56(84) bytes of data.
64 bytes from 192.168.46.1: icmp_seq=1 ttl=63 time=0.982 ms

Желательно, чтобы все описанные выше настройки сохранялись при перезапуске виртуальных серверов, сервиса vz и ведущего узла кластера. С настройками виртуальных серверов проще всего "-- их можно сохранить в etcnet:

[root@m1 ~]# vzctl enter mail
[root@mail /]# mkdir /etc/net/ifaces/eth0
[root@mail /]# echo 192.168.199.2/24 > /etc/net/ifaces/eth0/ipv4address
[root@mail /]# echo 192.168.0.0/16 via 192.168.199.1 dev eth0 > /etc/net/ifaces/eth0/ipv4route
[root@mail /]# echo "BOOTPROTO=static
> ONBOOT=yes
> TYPE=eth" > /etc/net/ifaces/eth0/options

[root@m1 ~]# vzctl enter dbms
[root@dbms /]# mkdir /etc/net/ifaces/eth0
[root@dbms /]# echo 192.168.199.3/24 > /etc/net/ifaces/eth0/ipv4address
[root@dbms /]# echo 192.168.0.0/16 via 192.168.199.1 dev eth0 > /etc/net/ifaces/eth0/ipv4route
[root@dbms /]# echo "BOOTPROTO=static
> ONBOOT=yes
> TYPE=eth" > /etc/net/ifaces/eth0/options

Для автоматического добавления конца veth-интерфейсов со стороны физического сервера в бридж при старте соответствующего виртуального сервера потребуется исправить скрипт /usr/sbin/vznetcfg, добавив в конец функции init_veth() строку (сделать это нужно на двух узлах кластера):

brctl addif br0 ${dev}

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

Наконец, бридж тоже нужно создать, и сделать это необходимо еще до старта всех виртуальных серверов. Лучше всего добавить его создание в конфигурацию etcnet на обеих узлах кластера:

[root@m1 ~]# mkdir /etc/net/ifaces/br0
[root@m1 ~]# echo TYPE=bri > /etc/net/ifaces/br0/options

Есть одна неприятная деталь. В конфигурацию виртуальных серверов мы добавили маршрут в сеть 192.168.0.0/16, но во многих случаях нам потребуется добавить туда маршрут по уполчанию. Сделать этого мы не сможем, так как такой маршрут, созданный OpenVZ заранее для собственных нужд, уже есть:

[root@m1 ~]# vzctl exec mail ip route
192.168.199.0/24 dev eth0  proto kernel  scope link  src 192.168.199.2 
192.0.2.0/24 dev venet0  scope host 
192.168.0.0/16 via 192.168.199.1 dev eth0 
default via 192.0.2.1 dev venet0

Он создается и автоматически привязывается к интерфейсу venet0. Ни эта довольно навязчивая автоматика, ни venet-интерфейсы вообще нам сейчас не нужны, мы используем только veth. Поэтому чтобы такого не происходило, потребуется исправить скрипт, выполняющий настройку сетевых интерфейсов виртуального сервера. В случае ALT Linux Sisyphus это /etc/vz/dists/scripts/etcnet-add_ip.sh. В нем нам нужно модифицировать функцию add_ip() таким образом, чтобы она выполнялась только при наличии присвоенного venet-интерфейсу адреса:

add_ip()
{
    local i ip
    
    if [ -n "$IP_ADDR" ]; then

        if [ "$VE_STATE" = "starting" ]; then
            setup_network
        fi
    
        backup_configs "$IPDELALL"

        i=0
        for ip in ${IP_ADDR}; do
            i="$(find_unused_alias "$((i+1))")"
            create_alias "$ip" "$i"
        done

        move_configs

        if [ "$VE_STATE" = "running" ]; then
            # synchronyze config files & interfaces
            ifdown "$VENET_DEV"
            ifup "$VENET_DEV"
        fi

    fi 
}

Затем в виртуальных серверах необходимо удалить из etcnet настройки интерфейса venet0:

[root@m1 ~]# vzctl exec router rm -rf /etc/net/ifaces/venet0
[root@m1 ~]# vzctl exec mail rm -rf /etc/net/ifaces/venet0
[root@m1 ~]# vzctl exec dbms rm -rf /etc/net/ifaces/venet0

Теперь можно перезапустить сервис vz "-- в конфигурации виртуальных серверов останутся только те маршруты, которые мы указали явно.

Таким образом мы добились того, чего хотели: в штатном режиме виртуальные сервера mail и dbms работают на узле m1, а в случае его отказа автоматически переезжают на узел m2, при этом с точки зрения внешнего наблюдателя из физической сети 192.168.46.0/24 наблюдается лишь кратковременный перерыв в обслуживании:

$ ping 192.168.199.2
PING 192.168.199.2 (192.168.199.2) 56(84) bytes of data.
64 bytes from 192.168.199.2: icmp_seq=1 ttl=64 time=0.549 ms
...
From 192.168.46.1 icmp_seq=83 Destination Host Unreachable
...
From 192.168.46.200 icmp_seq=83 Destination Host Unreachable
...
64 bytes from 192.168.199.2: icmp_seq=179 ttl=64 time=1.05 ms

--- 192.168.46.200 ping statistics ---
179 packets transmitted, 25 received, +93 errors, 86% packet loss, time 178149ms
rtt min/avg/max/mdev = 0.298/193.970/1702.783/397.285 ms, pipe 3



Eugene 2012-05-28