Наш рецепт отказоустойчивого Linux-роутера


    В высоконагруженных проектах всегда повышенные требования к избыточности и надежности. Одним из важнейших звеньев инфраструктуры является маршрутизатор, потому что от его устойчивости зависит доступность сети в целом. Именно на таких узлах мы используем одну из схем реализации отказоустойчивого виртуального роутера на базе GNU/Linux с использованием iproute2, NetGWM, keepalived, ISC DHCPD, PowerDNS. Как мы всё это настраиваем, читайте в этой статье.

    Компоненты


    В идеальной схеме отказоустойчивого роутера мы резервируем все элементы, которые могут привести к недоступности сети, то есть:

    • каналы связи,
    • коммутаторы,
    • маршрутизаторы.

    В общем виде схема (на уровне L2) выглядит так:



    Как видно из схемы, нам нужны 2 коммутатора с поддержкой 802.1Q VLAN. Оператор 1 коммутируется в Коммутатор 1 и ему выделяется отдельный VLAN (например, 110). Оператор 2 коммутируется в Коммутатор 2, в другой VLAN (например, 120). Отдельный VLAN (в нашем случае — 200), выделяется под локальную сеть. Между коммутаторами организуется транк, и транком же линкуем оба маршрутизатора, которые и будут «сердцем» нашего виртуального роутера (схема router-on-a-stick).

    Такая компоновка позволяет оставлять работоспособной сеть при выходе из строя любого компонента: роутера, коммутатора или оператора.

    Стек базовых компонентов, которые мы используем в работе роутеров:

    • Ubuntu Linux;
    • NetGWM — утилита приоритезации основного шлюза в решении. Это наша Open Source-разработка, о которой мы готовим отдельную статью (пока предлагаю ознакомиться с базовой документацией) [Обновлено 08.08.2017: статья опубликована как «Настройка основного и двух резервных операторов на Linux-роутере с NetGWM»];
    • iproute2 — для создания нескольких таблиц маршрутизации;
    • keepalived — для реализации протокола VRRP в Linux;
    • ISC DHCPD — как горизонтально масштабируемый DHCP-сервер;
    • PowerDNS — как DNS-сервер для локальной сети.

    Маршрутизаторы настраиваются примерно одинаково, за исключением конфигурации IP-адресов и keepalived.

    Настройка интерфейсов


    Настраиваем VLAN. Конфигурация /etc/network/interfaces будет выглядеть примерно так:

    auto lo
    iface lo inet loopback
            post-up bash /etc/network/iprules.sh
            post-up ip route add blackhole 192.168.0.0/16
            dns-nameservers 127.0.0.1
            dns-search dz
     
    # lan, wan: trunk dot1q
    auto eth0
    iface eth0 inet manual
     
    # lan
    auto vlan200
    iface vlan200 inet static
            vlan_raw_device eth0
            address 192.168.1.2
            netmask 255.255.255.0
     
    # Operator1
    auto vlan110
    iface vlan110 inet static
            vlan_raw_device eth0
            address 1.1.1.2
            netmask 255.255.255.252
            post-up   ip route add default via 1.1.1.1 table oper1
            post-up   sysctl net.ipv4.conf.$IFACE.rp_filter=0
            post-down ip route flush table oper1
     
    # Operator2
    auto vlan120
    iface vlan120 inet static
            vlan_raw_device eth0
            address 2.2.2.2
            netmask 255.255.255.252
            post-up   ip route add default via 2.2.2.1 table oper2
            post-up   sysctl net.ipv4.conf.$IFACE.rp_filter=0
            post-down ip route flush table oper2
    

    Основные моменты:

    • настраиваем blackhole — хорошая практика для того, чтобы локальные пакеты не улетали по маршруту по умолчанию в сторону провайдера;
    • net.ipv4.conf.$IFACE.rp_filter=0 — необходим для корректной работы multi-wan;
    • для каждого провайдера настраиваем отдельную таблицу маршрутизации с единственным маршрутом по умолчанию.

    Настроим маркинг пакетов для направления в определенные таблицы — добавим в iptables правила:

    iptables -t mangle -A PREROUTING -i vlan110 -m conntrack --ctstate NEW,RELATED -j CONNMARK --set-xmark 0x1/0x3
    iptables -t mangle -A PREROUTING -i vlan120 -m conntrack --ctstate NEW,RELATED -j CONNMARK --set-xmark 0x2/0x3
    iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff
    iptables -t mangle -A OUTPUT -o vlan110 -m conntrack --ctstate NEW,RELATED -j CONNMARK --set-xmark 0x1/0x3
    iptables -t mangle -A OUTPUT -o vlan120 -m conntrack --ctstate NEW,RELATED -j CONNMARK --set-xmark 0x2/0x3
    iptables -t mangle -A OUTPUT -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff
    iptables -t mangle -A POSTROUTING -o vlan110 -m conntrack --ctstate NEW,RELATED -j CONNMARK --set-xmark 0x1/0x3
    iptables -t mangle -A POSTROUTING -o vlan120 -m conntrack --ctstate NEW,RELATED -j CONNMARK --set-xmark 0x2/0x3
    

    И настроим правила маршрутизации для промаркированных пакетов — мы это делаем вызовом скрипта iprules.sh при выполнении ifup lo (смотри выше в /etc/network/interfaces). Внутри скрипта:

    #!/bin/bash
     
    /sbin/ip rule flush
     
    #operator 1
    /sbin/ip rule add priority 8001 iif vlan110 lookup main
    /sbin/ip rule add priority 10001 fwmark 0x1/0x3 lookup oper1
    /sbin/ip rule add from 1.1.1.2 lookup oper1
     
    #operator 2
    /sbin/ip rule add priority 8002 iif vlan120 lookup main
    /sbin/ip rule add priority 10002 fwmark 0x2/0x3 lookup operator2
    /sbin/ip rule add from 2.2.2.2 lookup operator2
    

    Эти таблицы маршрутизации необходимо объявить в /etc/iproute2/rt_tables:

    # reserved values
    255    local
    254    main
    253    default
    0    unspec
    # local
    110    oper1
    120    oper2
    

    Балансировщик основного шлюза


    Настроим NetGWM — утилиту для приоритезации основного шлюза. Она будет устанавливать маршрут по умолчанию, выбирая операторов в соответствии с двумя правилами: а) установленным нами приоритетом, б) статусом оператора (жив или нет).

    Чтобы установить NetGWM, можно воспользоваться исходниками на GitHub или нашим репозиторием для Ubuntu. Второй способ в случае Ubuntu 14.04 LTS выглядит так:

    # Установим репозиторий
    $ sudo wget https://apt.flant.ru/apt/flant.trusty.common.list -O /etc/apt/sources.list.d/flant.common.list
    # Импортируем ключ
    $ wget https://apt.flant.ru/apt/archive.key -O- | sudo apt-key add -
    # Понадобится HTTPS-транспорт — установите его, если не сделали это раньше
    $ sudo apt-get install apt-transport-https
    # Обновим пакетную базу и установим netgwm
    $ sudo apt-get update && sudo apt-get install netgwm

    Укажем в конфиге /etc/netgwm/netgwm.yml, что у нас 2 оператора, маршруты по умолчанию для каждого из них, приоритезацию и настройки для контроля доступности:

    # Описываем маршруты по умолчанию для каждого оператора и приоритеты
    # Меньшее значение (число) имеет больший приоритет
    gateways:
      oper1: {ip: 1.1.1.1, priority: 1}
      oper2: {ip: 2.2.2.1, priority: 2}
     
    # В ситуации, когда более приоритетного оператора начинает «штормить»
    # и он то online, то offline, хочется избежать постоянных переключений
    # туда-сюда. Этот параметр определяет время (в секундах), после которого
    # netgwm будет считать, что связь стабильна
    min_uptime: 900
     
    # Массив удаленных хостов, которые будут использоваться netgwm для
    # проверки работоспособности каждого оператора
    check_sites:
      - 192.5.5.241
      - 198.41.0.4


    Обратите внимание на имена oper1 и oper2 — это названия таблиц маршрутизации из /etc/iproute2/ip_tables. Рестартнем сервис netgwm, чтобы он начал управлять шлюзом по умолчанию для системы:

    $ sudo service netgwm restart

    Настройка keepalived


    Keepalived — реализация протокола VRRP для Linux. Этот протокол позволяет реализовать схему с отказоустойчивой маршрутизацией, создавая виртуальный IP, который будет использоваться в качестве маршрута по умолчанию для обслуживаемой сети. Виртуальный IP автоматически передается резервному серверу при выходе из строя основного сервера.

    На этом этапе мы определяемся, что Маршрутизатор 2 будет играть роль Backup, а Маршрутизатор 1 — роль Master. Настраиваем keepalived, изменяя конфигурационный файл /etc/keepalived/keepalived.conf:

    ! Глобальный блок можно настраивать под себя
    ! Configuration File for keepalived
    global_defs {
      notification_email {
        admin@fromhabr.ru
      }
      notification_email_from keepalived@example.com
      smtp_server 127.0.0.1
      smtp_connect_timeout 30
      router_id MY_ROUTER
    }
     
    vrrp_instance VI_1 {
      interface vlan200  # VRRP обслуживает VLAN локальной сети
      virtual_router_id 17 # поставьте любое число, но одинаковое на Master и Backup
      nopreempt # важный параметр, подробнее см. ниже
      state MASTER # на резервном сервере установите state BACKUP
      priority 200 # на резервном сервере число должно быть ниже, например 100
      advert_int 1 # интервал рассылки сообщения “Я жив”
      garp_master_delay 1 
      garp_master_refresh 60 
      authentication {
        auth_type PASS
        auth_pass qwerty # не забудьте поменять пароль
      }
      virtual_ipaddress {
        # Виртуальный адрес, который будет шлюзом по умолчанию в локальной сети и 
           # заодно широковещательный адрес, в который будут рассылаться VRRP-анонсы
        192.168.1.1/24 broadcast 192.168.1.255 dev vlan200
      }
      # Скрипты вызываются при переходе в соответствующее состояние Master, Backup, Fault и 
      # при остановке keepalived; используются нами для передачи уведомлений
      notify_master /etc/keepalived/scripts/master.sh
      notify_backup /etc/keepalived/scripts/backup.sh
      notify_stop   /etc/keepalived/scripts/stop.sh
      notify_fault /etc/keepalived/scripts/fault.sh
    }

    Так как наш отказоустойчивый роутер — многокомпонентный, мы решили использовать режим, в котором переключение keepalived режимов Backup → Master происходит только в случае отказа Master-сервера. За это как раз отвечает параметр nopreempt.

    Настройка ISC DHCPD


    ISC DHCPD был выбран нами, так как позволяет масштабировать DHCP на несколько серверов. Он прост в конфигурировании и хорошо зарекомендовал себя на практике. Кроме того, нам понравилось, что разработчики этого DHCP-сервера придумали изящное решение для организации реплики между серверами. Для основного и второстепенного серверов выделяются разные пулы адресов и на запросы отвечает сервер, который успел сделать это первым, выдавая адрес из своего пула. При этом база арендованных IP синхронизируется. В случае, если один из серверов отказывает, второй как ни в чем не бывало продолжает выдачу адресов из своего пула. При возврате отказавшего сервера он начинает выдачу из своего пула, при этом не возникает коллизий.

    Правим конфиг /etc/dhcp/dhcpd.conf:

    # Настраиваем DDNS для локальных адресов
    ddns-updates on;
    ddns-update-style interim;
    do-forward-updates on;
    update-static-leases on;
    deny client-updates;    # ignore, deny, allow
    update-conflict-detection false;
    update-optimization false;
     
    key "update-key" {
            algorithm hmac-md5;
            secret "КЛЮЧ"; # про генерацию ключа см. ниже
    };
     
    zone    1.168.192.in-addr.arpa. {
            primary 192.168.1.1;
            key "update-key";
    }
     
    zone    mynet. {
            primary 192.168.1.1;
            key "update-key";
     
    # Настраиваем масштабирование
    failover peer "failover-partner" {
            primary; # на резервном сервере укажите здесь secondary
            address 192.168.1.3; # адрес резервного сервера
            port 519;
            peer address 192.168.1.2; # адрес основного сервера
            peer port 520;
            max-response-delay 60;
            max-unacked-updates 10;
            load balance max seconds 3;
    }
     
    default-lease-time 2400;
    max-lease-time 36000;
     
    log-facility local7;
    authoritative;
     
    option ntp-servers 192.168.1.1, ru.pool.ntp.org;
     
    # Настраиваем пул для нашего сервера
    subnet 192.168.1.0 netmask 255.255.255.0 {
      range 192.168.1.51 192.168.1.150; # на 100 адресов, например
      option subnet-mask 255.255.255.0;
      option broadcast-address 192.168.1.255;
      option domain-name-servers 192.168.1.1;
      option routers 192.168.1.1;
      ddns-domainname "mynet.";
    # … и пул для резервного. Они не должны пересекаться
      pool {
           failover peer "failover-partner";
           range 192.168.1.151 192.168.1.250;
      }
    # … и статические leases
      host printer { hardware ethernet 00:26:73:47:94:d8; fixed-address 192.168.1.8; }
    }
    

    Нам понадобится сгенерировать ключ update_key, с помощью которого мы будем обновлять зону mynet. Сгенерируем его и выведем на экран:

    $ dnssec-keygen -r /dev/urandom -a HMAC-MD5 -b 64 -n HOST secret_key
    Ksecret_key.+157+64663
    $ cat Ksecret_key.+*.private | grep ^Key | awk '{print $2}'
    bdvkG1HcHCM=

    Скопируйте сгенерированный ключ и вставьте в конфигурационный файл вместо слова КЛЮЧ.

    Настройка PowerDNS


    В качестве DNS-сервера мы предпочли PowerDNS, так как он имеет возможность хранить зоны в СУБД MySQL, которую удобно реплицировать между первым и вторым сервером. Кроме того, PoweDNS — это производительное решение, хорошо функционирующее в высоконагруженном роутере.

    Настройку PowerDNS начнем с подготовки базы данных.

    # Заходим в MySQL CLI
    $ mysql -u root -p
    # Создаем БД и назначаем права для пользователя, которым будем к ней подключаться
    mysql> CREATE DATABASE IF NOT EXIST powerdns;
    mysql> GRANT ALL ON powerdns.* TO 'pdns_admin'@'localhost' IDENTIFIED BY 'pdns_password';
    mysql> GRANT ALL ON powerdns.* TO 'pdns_admin'@'localhost.localdomain' IDENTIFIED BY 'pdns_password';
    mysql> FLUSH PRIVILEGES;
    # Переходим в только что созданную БД и создаем структуру данных
    mysql> USE powerdns;
    

    mysql> CREATE TABLE IF NOT EXIST `domains` (
    id INT auto_increment,
    name VARCHAR(255) NOT NULL,
    master VARCHAR(128) DEFAULT NULL,
    last_check INT DEFAULT NULL,
    type VARCHAR(6) NOT NULL,
    notified_serial INT DEFAULT NULL,
    account VARCHAR(40) DEFAULT NULL,
    primary key (id)
    );
    mysql> CREATE TABLE `records` (
    id INT auto_increment,
    domain_id INT DEFAULT NULL,
    name VARCHAR(255) DEFAULT NULL,
    type VARCHAR(6) DEFAULT NULL,
    content VARCHAR(255) DEFAULT NULL,
    ttl INT DEFAULT NULL,
    prio INT DEFAULT NULL,
    change_date INT DEFAULT NULL,
    primary key(id)
    );
    mysql> CREATE TABLE `supermasters` (
    ip VARCHAR(25) NOT NULL,
    nameserver VARCHAR(255) NOT NULL,
    account VARCHAR(40) DEFAULT NULL
    );
    mysql> CREATE INDEX `domain_id` ON `records`(`domain_id`);
    mysql> CREATE INDEX `rec_name_index` ON `records`(`name`);
    mysql> CREATE INDEX `nametype_index` ON `records`(`name`,`type`);
    mysql> CREATE UNIQUE INDEX name_index` ON `domains`(`name`);
    

    quit;

    Теперь нужно настроить PowerDNS и научить его работать с БД. Для этого требуется установить пакет pdns-backend-mysql и изменить конфиг /etc/powerdns/pdns.conf:

    # Настраиваем доверенные сети
    allow-axfr-ips=127.0.0.0/8,192.168.1.0/24
    allow-dnsupdate-from=127.0.0.0/8,192.168.1.0/24
    allow-recursion=127.0.0.0/8,192.168.1.0/24
    # Базовые настройки демона
    config-dir=/etc/powerdns
    daemon=yes
    disable-axfr=no
    dnsupdate=yes
    guardian=yes
    local-address=0.0.0.0
    local-address-nonexist-fail=no
    local-port=53
    local-ipv6=::1
    # На втором маршрутизаторе такие же значения этих параметров
    master=yes
    slave=no
     
    recursor=127.0.0.1:5353
    setgid=pdns
    setuid=pdns
    socket-dir=/var/run
    version-string=powerdns
    webserver=no
    # Настраиваем MySQL
    launch=gmysql
    # Хост с БД - тот самый, которым управляет keepalived
    gmysql-host=192.168.1.1
    gmysql-port=3306
    # Имя и пароль пользователя, которого мы создавали в БД
    gmysql-user=pdns_admin
    gmysql-password=pdns_password
    gmysql-dnssec=yes

    На этом базовая конфигурация PowerDNS закончена. Нам же ещё потребуется настроить рекурсор — обработчик рекурсивных DNS-запросов, который позволяет значительно повысить производительность DNS-сервера. Правим файл /etc/powerdns/recursor.conf:

    daemon=yes
    forward-zones-file=/etc/powerdns/forward_zones
    local-address=127.0.0.1
    local-port=5353
    quiet=yes
    setgid=pdns
    setuid=pdns
    

    В файл forward_zones вносим зоны intranet, которые обслуживают соседние серверы:

    piter_filial.local=192.168.2.1
    2.168.192.in-addr.arpa=192.168.2.1

    По окончании настройки перезапускаем сервисы pdns и pdns-recursor.

    После запуска настраиваем реплику MySQL между серверами.

    Заключение


    Мы используем данное решение не только в чистом виде. В большинстве случаев оно усложняется добавлением туннелей VTun, OpenVPN или IPSec через основного и резервного оператора связи и динамической маршрутизацией, которая реализуется с помощью Quagga. Поэтому схему, предложенную в статье, предлагаю воспринимать как фундамент для создания более сложных решений.

    Будем рады, если вы зададите свои вопросы в комментариях или укажете на места в схеме, которые можно улучшить. И, конечно, подписывайтесь на наш хаб, чтобы не пропускать новые полезные материалы! )
    Метки:
    Флант 353,22
    Специалисты по DevOps и высоким нагрузкам в вебе
    Поделиться публикацией
    Комментарии 28
    • +1
      при использовании conntrack, хм, и без тюнинга, сколько оно держит cps/qps/pps, mb/s e.t.c.?
      • 0
        Специально не замеряли производительность серверов. Единожды только уперлись в производительность роутера с процессором Atom, при этом на нем еще в userspace крутился VPN.
      • 0
        conntrackd не пробовали? и сколько у вас ядер в каждом из серверов? Если больше одного и скорости будут под гигабит — надо будет еще smp_affinity на интерфейсах покурить и физических карт доставить.
        • 0
          conntrackd пробовали. Для себя увидели только один плюс — возможность репликации контраков между серверами, в наших текущих задачах обрыв соединений при переключении между роутерами не критичен.

          В разных проектах разные роутеры, и количество ядер разное. Спасибо за совет по smp_affinity. По поводу физических карт: мы используем карты, поддерживающие несколько очередей.
        • 0
          а можно примерный список по железу, на которой будет жить такая конфигурация...?
          • 0
            На какую нагрузку?
            • 0
              200 Мб/с с NAT и QoS, с двумя блоками питания.

              1 Гб/с просто роутиг стабильно без проседаний.
              • +1
                Исходя из нашей практики, на вашу нагрузку достаточно будет роутера на процессоре типа Xeon E3.
              • 0
                собственно ответ ниже по процессору понятен, а вот какие коммутаторы с VALN кроме Cisco есть смысл использовать исходя из варианта Small Business?
                • 0
                  Если нагрузка вам позволит, то можно. С учетом того, что, как заметили ниже, надежность системы равна надежности самой слабой компоненты.
                  • 0
                    сорри, таки похоже вы ответили на на мой вопрос… интересовали ваши рекомендации по выбору данных коммутаторов кроме Cisco, какие вы можете рекомендовать?
                    • 0
                      Например HP серии Aruba или Huawei серий S и CE
            • 0
              А я взял pfSense и всё
              • 0
                или VyOS, если нужна производительность
              • 0

                С какой целью наружу выставляются коммутаторы?

                • 0

                  Дейчтвительно. Такая схема часто применялась в cisco, причина доровизна интерфейсов. А сейчас интернет часто бывает гигабитным. А роутеров с одним стандартным гигабитным интерфейсом я не припомню.


                  Если это учебная задача, то я бы добавил агрегацию каналов от свитча у роутеру. Если практическая, то зачем?


                  Теперь о надежности. А рабочие компы у вас тоже с двумя сетевухами? Надежность системы равна надежности самого слабого участка.

                  • +1
                    Коммутаторы в данной схеме позволяют включить не 2, а 3, 4 или 15 провайдеров и управлять ими с помощью netgwm. Если вопрос был про безопасность, то, конечно, Managment vlan не смотрит наружу.

                    По поводу локалки: мы используем схему коммутации, когда хосты из LAN коммутируются поровну в первый и второй коммутаторы и при выходе коммутатора из строя часть LAN оказывается без связи. Перекоммутацию выполняет администратор на месте руками.
                • 0
                  vlan_raw_device eth0
                  везде стоит eth0, так и нужно?
                  Использовал только вашу утилиту NetGWM, при переключении на резерв теряется пинг с хостом на пару минут, так и должно быть?
                  • 0
                    Да, все так и должно быть. Подразумевается, что в eth0 от коммутатора приходит транк, который мы и разбираем на vlan этими конструкциями.
                    • 0
                      не подскажите куда нужно добавить строчку, что б при смене шлюза так же запускался скрипт с правилами iptables, что б для каждого шлюза были свои правила?
                      И еще вопрос наверное ближе к самой системе Windows, при смене шлюза уже установленные соединения «тыкается» в linkdown, и если буквально пару минут подаждать и снова попробовать зайти в интернет пакет уже идут в linkup как и положено. Может есть какая та комманда сброса кэша в Windows?
                      • 0
                        Скрипт нужно просто положить в /etc/netgwm/post-replace.d и он будет запускаться при каждой смене провайдера. В качестве параметров скрипту передаются:
                        $1 — new gateway identifier
                        $2 — new gateway IP or NaN
                        $3 — new gateway device or NaN
                        $4 — old gateway identifier or NaN
                        $5 — old gateway IP or NaN
                        $6 — old gateway device or NaN

                        По поводу Windows: при смене шлюза выполните conntrack -F на роутере. Если проблема в контраках, их сброс ее решит. Если же проблема в таймаутах на win-машине, то нет.
                        • 0
                          Не совсем пойму как сделать смену /root/ipt.sh на /root/ipt2.sh и обратно при переключении между провайдерами, их нужно прописать в $1 и $4?
                          • +1
                            Нужен всего один скрипт, в котором обрабатывается переключение с case по параметрам, как-то так:
                            case $1 in
                             operator1)
                                 #тут пишем правила при переключении на оператора 1
                             ;;
                             operator2)
                                #тут пишем правила для оператора 2
                             ;;
                             *)
                               #тут выводим сообщение об ошибке или отправляем в мониторинг
                            esac
                            
                            • 0
                              Спасибо. Можете сделать расширеный мануал по вашей утилите, это было бы очень хорошо, что б меньше задавать вопросов в комментариях
                              • 0
                                Да, ее автор сейчас готовит материал, скоро будет отдельная статья на эту тему.
                              • 0
                                Добрый день.

                                Обещанная статья по NetGWM https://habrahabr.ru/company/flant/blog/335030/
                  • 0
                    Может, кто-то мне подскажет утилиту-обертку над iproute2, которая бы сама создавала таблицы на каждый интерфейс, и настраивала rule'ы? Чтобы не писать отдельные правила для каждого интерфейса вручную, а они бы настраивались типовым образом, автоматически?

                    Из более-менее работающих знаю только:
                    https://github.com/kristrev/multi
                    https://github.com/kristrev/multihomed-routing
                    https://github.com/ncopa/pingu

                    Может, кто-то что-то еще подскажет?

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое