Настройка основного и двух резервных операторов на Linux-роутере с NetGWM

    Задача резервирования основного шлюза — одна из самых популярных в сетевом администрировании. У нее есть целый ряд решений, которые реализуют механизмы приоритезации или балансировки исходящих каналов для абсолютного большинства современных маршрутизаторов, в том числе и маршрутизаторов на базе Linux.



    В статье об отказоустойчивом роутере мы вскользь упоминали свой корпоративный стандарт для решения этой задачи — Open Source-продукт NetGWM — и обещали рассказать об этой утилите подробнее. Из этой статьи вы узнаете, как устроена утилита, какие «фишки» можно использовать в работе с ней и почему мы решили отказаться от использования альтернативных решений.

    Почему NetGWM?


    Классическая схема резервирования основного шлюза в Linux, реализуемая средствами iproute2, выглядит практически во всех источниках примерно одинаково:

    • Дано: 2 провайдера.
    • Создаем для каждого оператора свою таблицу маршрутизации.
    • Создаем правила (ip rule), по которым трафик попадает в ту или иную таблицу маршрутизации (например, по источнику и/или по метке из iptables).
    • Метриками или ifupdown-скриптами определяем приоритет основного шлюза.

    Подробности этой схемы легко гуглятся по запросу «linux policy routing». На наш взгляд, схема имеет ряд очевидных недостатков, которые и стали основным мотиватором создания утилиты NetGWM:

    1. Сложность внесения изменений в схему, плохая управляемость.
    2. Если количество шлюзов 3 и более, логика скриптов усложняется, как и реализация выбора шлюза на основе метрик.
    3. Проблема обнаружения пропадания канала. Зачастую, физический линк и даже шлюз оператора могут быть доступны, при этом из-за проблем внутри инфраструктуры оператора или у его вышестоящего поставщика услуг реально сеть оказывается недоступной. Решение этой проблемы требует добавление дополнительной логики в ifupdown-скрипты, а в маршрутизации на основе метрик она нерешаема в принципе.
    4. Проблема «шалтай-болтай». Такая проблема проявляется, если на высокоприоритетном канале наблюдаются кратковременные частые перерывы в связи. При этом шлюз успешно переключается на резервный. Откуда здесь, казалось бы, может взяться проблема? Дело в том, что ряд сервисов, таких как телефония, видеосвязь, VPN-туннели и другие, требуют некоторого таймаута для определения факта обрыва и установления нового сеанса. В зависимости от частоты обрывов это приводит к резкому снижению качества сервиса или его полной недоступности. Решение этой проблемы также требует усложнения логики скриптов и тоже совершенно нерешаема метриками.

    Мы посмотрели, что поможет нам решить все 4 проблемы: простое и управляемое средство с поддержкой 2 и более шлюзов по умолчанию, умеющее диагностировать доступность канала и тестировать его на стабильность. И не нашли такого варианта. Именно так и появился NetGWM.

    Установка из GitHub и репозитория «Флант»


    NetGWM (Network GateWay Manager) — это небольшая утилита приоритезации основного шлюза, написанная на Python и распространяемая под свободной лицензией GNU GPL v3. Автор первоначальной версии — driusha (Андрей Половов).

    Исходный код и документация на английском языке доступны на GitHub, а краткая документация и описание на русском языке — здесь.

    Установка из GitHub:

    ## Предварительно в системе требуется установить:
    ##  iproute2, conntrack, модуль python-yaml
    ## После этого клонируем репозиторий:
    $ git clone git://github.com/flant/netgwm.git netgwm
    ## И устанавливаем (понадобятся права суперпользователя):
    $ cd netgwm && sudo make install
    ## Добавим служебную таблицу маршрутизации, которая будет использоваться NetGWM
    $ sudo sh -c "echo '100    netgwm_check' >> /etc/iproute2/rt_tables"
    ## Добавляем в cron пользователя root вызов netgwm с той частотой,
    ## с которой вам бы хотелось проверять доступность основного шлюза
    ## (например, раз в минуту):
    $ sudo crontab -e
    */1 * * * * /usr/lib/netgwm/newtgwm.py

    Кроме того, готовый DEB-пакет с NetGWM можно установить из репозитория для 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

    Добавлять служебную таблицу маршрутизации и настраивать cron в Ubuntu не потребуется. Таблица автоматически добавится при установке пакета. Кроме того, при установке будет зарегистрирована служба netgwm, init-скрипт которой стартует в качестве демона небольшой shell-скрипт /usr/bin/netgwm, который, в свою очередь, читает из файла /etc/default/netgwm значение параметра INTERVAL (в секундах) и с указанной периодичностью сам вызывает netgwm.py.

    Настройка


    В основе работы NetGWM также лежит policy-роутинг, и нам придется предварительно настроить таблицы маршрутизации для каждого оператора.

    Допустим, есть 3 оператора, и необходимо сделать так, чтобы основным оператором был оператор 1, в случае отказа — использовался оператор 2, а в случае отказа из обоих — оператор 3.

    Пусть первый оператор у нас подключен к интерфейсу eth1, второй — к eth2, третий — к eth3. Первый оператор имеет шлюз 88.88.88.88, второй оператор — шлюз 99.99.99.99, третий — 100.100.100.100.

    Мы почти всегда при настройке сети с несколькими основными шлюзами используем маркировку пакетов и модуль conntrack из NetFilter. Это хорошая практика, помогающая распределять пакеты по таблицам маршрутизации с учетом состояния, но не являющаяся обязательной.

    Настроим маркинг пакетов и conntrack:

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

    2. Добавим правила маршрутизации для промаркированных пакетов. Мы это делаем с помощью скрипта, который вызывается из /etc/network/interfaces при событии post-up на интерфейсе lo:

    #!/bin/bash
     
    /sbin/ip rule flush
     
    # operator 1
    /sbin/ip rule add priority 8001 iif eth1 lookup main
    /sbin/ip rule add priority 10001 fwmark 0x1/0x3 lookup operator1
    /sbin/ip rule add from 88.88.88.88 lookup operator1
     
    # operator 2
    /sbin/ip rule add priority 8002 iif eth2 lookup main
    /sbin/ip rule add priority 10002 fwmark 0x2/0x3 lookup operator2
    /sbin/ip rule add from 99.99.99.99 lookup operator2
    
    # operator 3
    /sbin/ip rule add priority 8002 iif eth3 lookup main
    /sbin/ip rule add priority 10002 fwmark 0x3/0x3 lookup operator3
    /sbin/ip rule add from 100.100.100.100 lookup operator3

    3. Объявим таблицы маршрутизации в /etc/iproute2/rt_tables:

    # Зарезервированные значения:
    255    local
    254    main
    253    default
    0    unspec
    # Служебная таблица, которую мы (или dpkg) добавили ранее:
    100  netgwm_check
    # Таблицы для операторов, которые мы должны добавить сейчас:
    101    operator1
    102    operator2
    103    operator3

    4. Настроим NetGWM. По умолчанию, netgwm.py будет искать конфигурационный файл по адресу /etc/netgwm/netgwm.yml, однако вы можете переопределить это с помощью ключа -c. Настроим работу утилиты:

    # Описываем маршруты по умолчанию для каждого оператора и приоритеты
    # Меньшее значение (число) имеет больший приоритет. 1 - самый высокий приоритет
    # Обратите внимание, что для смены шлюза по умолчанию на другой достаточно просто 
    # изменить значения приоритетов в этом файле. И уже при следующем запуске (через 
    # минуту в нашем случае) шлюз изменится на более приоритетный (если он доступен).
    # Наименование оператора должно совпадать с именем таблицы маршрутизации из
    # /etc/iproute2/rt_tables
    gateways:
      operator1: {ip: 88.88.88.88, priority: 1}
      operator2: {ip: 99.99.99.99, priority: 2}
      operator3: {ip: 100.100.100.100, priority: 3}
     
    # Этот параметр решает проблему «шалтай-болтай», когда приоритетного
    # оператора (из доступных) «штормит». 
    # Параметр определяет время постоянной доступности (в секундах),
    # после которого netgwm будет считать, что связь стабильна
    min_uptime: 900
     
    # Массив удаленных хостов, которые будут использоваться netgwm для
    # проверки работоспособности каждого оператора. Здесь стоит указать либо важные 
    # для вас хосты в интернете, либо общедоступные и стабильно работающие ресурсы, 
    # как сделано в примере. Шлюз считается недоступным, если netgwm НЕ СМОГ
    # установить через него связь до ВСЕХ (условие AND) указанных хостов
    check_sites:
      - 8.8.8.8 # Google public DNS
      - 4.2.2.2 # Verizon public DNS
    
    # По умолчанию netgwm проверяет доступность только для самого 
    # приоритетного шлюза. Если тот недоступен — до второго по приоритетности и т.д.
    # Данная опция, будучи установленной в true, заставит netgwm проверять
    # доступность всех шлюзов при каждом запуске
    check_all_gateways: false

    5. Настроим действия при переключении

    Если произойдет переключение, то после смены основного шлюза будут выполнены все исполняемые файлы из каталога /etc/netgwm/post-replace.d/*. При этом каждому файлу будут переданы 6 параметров командной строки:

    • $1 — наименование нового оператора;
    • $2 — IP вновь установленного шлюзе или NaN, если новый шлюз установить не удалось;
    • $3 — имя устройства нового шлюза или NaN, если шлюз установить не удалось;
    • $4 — наименование старого оператора или NaN, если шлюз устанавливается впервые;
    • $5 — IP старого оператора или NaN, если шлюз устанавливается впервые;
    • $6 — имя устройства старого оператора или NaN, если шлюз устанавливается впервые.

    В скрипте можно на основании этих переменных описать логику действий в зависимости от подключаемого оператора (добавить или удалить маршруты, отправить уведомления, настроить шейпинг и т.п.). Для примера приведу скрипт на shell, отправляющий уведомления:

    #!/bin/bash
    # Определяем, что произошло: переключение или старт netgwm
    if [ "$4" = 'NaN' ] && [ "$5" = 'NaN' ]
     then
      STATE='start'
     else
      STATE='switch'
    fi
    # Отправляем уведомление дежурным инженерам о произошедшем
    case $STATE in
     'start')
       /usr/bin/flant-integration --sms-send="NetGWM on ${HOSTNAME} has been started and now use gw: $1 - $2"
     ;;
     'switch')
       /usr/bin/flant-integration --sms-send="NetGWM on ${HOSTNAME} has switched to new gw: $1 - $2 from gw: $4 - $5"
     ;;
     *)
      /usr/bin/logger -t netgwm "Unknown NetGWM state. Try restarting service fo fix it."
     ;;
    esac
    exit

    6. Стартуем сервис netgwm в Ubuntu, если вы установили DEB-пакет:

    $ sudo service netgwm start

    Если вы получили NetGWM из GitHub, то установленное ранее задание в cron уже и так проверяет доступность вашего основного шлюза, дополнительных действий не требуется.

    Журналирование


    События по переключению NetGWM регистрирует в журнале /var/log/netgwm:

    $ tail -n 3 /var/log/netgwm.log
    2017-07-14 06:25:41,554 route replaced to: via 88.88.88.88
    2017-07-14 06:27:09,551 route replaced to: via 99.99.99.99
    2017-07-14 07:28:48,573 route replaced to: via 88.88.88.88

    Сохраненная история переключений помогает произвести анализ инцидента и определить причины перерыва в связи.

    Проверено в production


    Уже около 4 лет NetGWM используется в нашей компании на 30+ маршрутизаторах Linux разных масштабов. Надежность утилиты многократно проверена в работе. Для примера, на одной из инсталляций, с мая 2014 года NetGWM обработал 137 переключений операторов без каких-либо проблем.

    Стабильность, покрытие всех наших потребностей и отсутствие проблем в эксплуатации в течение длительного времени привели к тому, что мы практически не занимаемся развитием проекта. Код NetGWM написан на Python, поэтому отсутствует и необходимость в адаптации утилиты к новым версиям операционных систем. Тем не менее, мы будем очень рады, если вы решите принять участие в развитии NetGWM, отправив свои патчи в GitHub или просто написав feature request в комментариях.

    Заключение


    С NetGWM мы имеем стабильную, гибкую и расширяемую (с помощью скриптов) утилиту, которая полностью закрывает наши потребности в управлении приоритетом основного шлюза.

    Любые вопросы по использованию NetGWM также приветствуются — можно прямо здесь в комментариях.

    P.S. Читайте также в нашем блоге: «Наш рецепт отказоустойчивого Linux-роутера» — и подписывайтесь на него, чтобы не пропускать новые материалы!

    Флант

    338,40

    Специалисты по DevOps и высоким нагрузкам в вебе

    Поделиться публикацией
    Комментарии 15
      0
      Хорошо бы кроме резервирования сделать еще и балансировку трафика, чтобы каналы не простаивали.
        0
        согласен, ведь было бы неплохо, но это уже получается другой продукт.
        0
        Добрый день,
        А в чём состоит преимущество данной платформы относительно раутера?
          0
          Не совсем понял вопрос, поясните пожалуйста, что имеете в виду под раутером: железные решения?
            0
            VyOS например
              0
              Вопрос скорее в подходе, чем в конкретном решении. У нас есть свой стандарт дистрибутива, который мы повсеместно (тысячи железных/виртуальных серверов) используем, т.к. поддержка дополнительного специализированной платформы в долгосрочной перспективе получается «себе дороже» в нашем случае. Поэтому у нас утилита, которая подойдет для нужного нам (и, к слову, любого другого) дистрибутива, а не отдельный Linux-дистрибутив (который, впрочем, наверняка прекрасно решает и эту, и многие другие задачи — просто не наш вариант).
              0
              имеется в виду Cisco, Juniper etc (bare metal)
                +1
                Да, наверное, принципиальное преимущество в том, что зачастую роутер на Linux не только роутер. Он, как минимум, еще и DNS-сервер. То есть, выбирая решение на Linux, выбирают, в первую очередь, мультисервисность. Cisco, Juniper и т.д, конечно, тоже легко справляются с задачей, поставленной в статье.
                  0
                  Спасибо за разъяснение.
                  Просто меня в своё время учили что раутер должен быть только раутером и всё.
                  А все комбайны всё_в_одном, обычно не есть хорошо.
          • НЛО прилетело и опубликовало эту надпись здесь
              +3
              Надо полагать, серьезные pps возникают на серьезных проектах, под которые есть AS, и вся эта свистопляска со сменами маршрутов для хоумроутеров не нужна, так как работает BGP.
              К примеру, в сети провайдера с десятком тысяч абонентов и чуть менее 10Гбит внешнего канала чуть тюненый conntrack/mark работает (connmark не использую, сказать не могу) и не жужжит на обычном Xeon E3. Внешние маршруты строятся через BGP (поэтому такие вот штуки, как в статье не нужны), но используем NAT, так как адресов маловато.
              И уж тем более conntrack будет работать в сетке малого/среднего офиса, с сотней-другой сотрудников, для которого нет своей автономки. Но нужен резервный канал.
              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  да, верно, это решение для небольших проектов, имеющих высокие требования к надежности.
                +2
                Использую похожую схему, скрипт-тестер написан за 5-10 минут на пыхе, не претендует на красоту кода, но отлично работает тоже лет 5 безотказно
                кусок кода на PHP
                <?php
                function test_gw($gw) {
                
                    if ($gw=='') return 2;
                    $test_ip='4.2.2.4';
                    exec("ip ro del {$test_ip} >/dev/null 2>/dev/null");
                    exec("ip ro add {$test_ip} {$gw}");
                    exec("ping -c4 -i0.1 {$test_ip}", $output, $status);
                    exec("ip ro del {$test_ip}");
                    return $status;
                }
                
                function get_gw($table) {
                    $route=exec("ip route show table {$table}");
                    $route=str_replace("default ",'',$route,$count);
                    if (!$count) $route='';
                    return $route;
                }
                
                function change_default($default_gw,$new_gw) {
                    if ($default_gw==$new_gw) return;
                    exec("ip route replace default {$new_gw} table default");
                    exec("ip route flush cache");
                    mail('admin@example.com','GW change!',"Change gw:\n {$default_gw} {$new_gw}");
                    exit;
                }
                
                // main code
                
                    $gw_oper1=get_gw('oper1');
                    $status_oper1=test_gw($gw_oper1);
                    $gw_oper2=get_gw('oper2');
                    $status_oper2=test_gw($gw_oper2);
                    $gw_oper3=get_gw('oper3');
                    $status_oper3=test_gw($gw_oper3);
                
                    
                    $default_gw=get_gw('default');
                
                
                    if ($status_oper1==0)
                        change_default($default_gw,$gw_oper1);
                    if ($status_oper2==0)
                        change_default($default_gw,$gw_oper2);
                    if ($status_oper3==0)
                        change_default($default_gw,$gw_oper3);
                	//bla-bla oper12345
                ?>    
                    
                



                Суть похожая: используется несколько таблиц oper1 oper2 oper3, в которых есть default маршрут. И таблица default с маршрутом по умолчанию. В таблице main шлюза нет. В таблицы oper1/2/3 роутятся некоторые статические сети/абоненты/порты/маркированные соединения, то есть они в общем то используются, где-то для балансировки, где-то для других вещей.
                А вот когда основной канал «падает», то в default таблицу подгружается шлюз по умолчанию из более приоритетного, но при этом рабочего другого канала. А как только основной (или другой более приоритетный) маршрут «оживает» — маршрут по умолчанию тоже возвращается.

                При этом если оператор вышестоящий не режет на выходе «не свои» IP-адреса, то при обратном переключении даже сессии не рвутся (правда трафик асимметричный получается, уходит через одного провайдера, а возвращается на IP другого через другой канал)
                  +1
                  Классно! Хорошие идеи часто рождаются одновременно у нескольких человек, и это прекрасно. Выкладывайте на GitHub, оформится как конечное решение в итоге.

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

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