воскресенье, 22 ноября 2015 г.

Имя сетевого устройства

Старое название интерфейсов, такое как к примеру eth0, кануло в лету (ну почти, иногда старые имена можно встретить). В RHEL7 используются следующие варианты схем наименования сетевого интерфейса, в порядке применения:
  1. Имя, предоставляемое аппаратным обеспечением (Firmware или BIOS), включающее порядковый номер, для устройств, расположенных на материнской плате, к примеру eno1. Если выбор имени не произведен, используется схема 2.
  2. Имя, предоставляемое аппаратным обеспечением (Firmware или BIOS), включающее порядковый номер слота PCI Express, к примеру ens1. Если выбор не произведен, используется схема 3.
  3. Имя формируется из физического положения точки подключения устройства, к примеру enp2s0. Иначе используется схема 5.
  4. Имя включает MAC устройства, к примеру enx94de80a44f0c. Эта схема по умолчанию не используется.
  5. Старая схема, где имя присваивается ядром в порядке обнаружения интерфейса, к примеру eth0 и т.д.
Это поведение по умолчанию.

Новые имена сами по себе содержат полезную информацию. Принятое соглашение о наименовании следующее: первые два символа отражают тип интерфейса:
  1. en — Ethernet.
  2. wl — беспроводная локальная сеть, WLAN.
  3. ww — беспроводная глобальная сеть, WWAN.
Далее следует тип, отражающий схему наименования, описанную выше:
  1. o<index> индекс для устройств расположенных на материнской плате.
  2. s<slot>[f<function>][d<dev_id>] сначала идет номер PCI Express слота, далее f<function> - номер функции у многофункционального PCI устройства (я не встречал таких плат) и последним идет идентификатор устройства.
  3. p<bus>s<slot>[f<function>][d<dev_id>] расположение устройства на PCI шине. Домен PCI (p<bus>) указывается только если он отличен от нулевого (это можно увидеть в больших системах). Номер функции и номер устройства аналогично предыдущему примеру.
  4. p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>] расположение устройства на USB шине, причем учитывается вся цепочка портов.
  5. x<MAC> MAC адрес.
Нужно отметить, что имя не может быть более 15 символов, это ограничение ядра.

В случае использования VLAN добавляется следующее соглашение о имени интерфейса:
  1. vlan<vlanid> — имя с полным номером vlanid, к примеру vlan0012 или имя с сокращенным vlanid — vlan12
  2. device_name.<vlanid> - имя интерфейса и VLAN ID в полном или сокращенном виде, к примеру ens3.0012 или ens3.12
Как бы глупо это не звучало, но помимо описанных соглашений о наименовании, есть еще одно, используемое при помощи утилиты udev — biosdevname (насколько я понял, возникло это соглашение по инициативе DELL и используется на их серверах). Оно имеет силу только при наличии установленного пакета biosdevname и при указании biosdevname=1 при загрузке системы. Переименования будут производится только для встроенных интерфейсов. При этом:
  1. Встроенные интерфейсы будут иметь имя типа em[123...]
  2. Интерфейсы, подключенные по шине PCI — p<slot>p<ethernet port>, к примеру p3p4
Чтобы окончательно не запутаться в теоретической части, рассмотрим более глубоко процесс присваивания имени интерфейсу по шагам. Ниже пример того что происходит, когда в системе появляется новый интерфейс:

# udevadm monitor
...
KERNEL[27783.869667] add /devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7.4/1-7.4:1.0/net/eth0 (net)
...
KERNEL[27783.879674] move /devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7.4/1-7.4:1.0/net/enp0s20u7u4 (net)
UDEV [27783.892038] add /devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7.4/1-7.4:1.0/net/enp0s20u7u4 (net)
...

Т.е. сначала интерфейс появился со старым, знакомым именем eth0, а далее стал enp0s20u7u4. Нетрудно заметить, что новое имя сразу отображает что это адаптер Ethernet (en), подключенный к USB шине через usb хаб (в имени два usb порта u7u4). Достаточно удобно.

Посмотрим, что выполняет udev над интерфейсом:

  • Шаг первый:
# udevadm test /sys/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7.4/1-7.4:1.0/net/enp0s20u7u4
...
PROGRAM '/lib/udev/rename_device' /usr/lib/udev/rules.d/60-net.rules:1
starting '/lib/udev/rename_device'

Что происходит: правило udev /usr/lib/udev/rules.d/60-net.rules при помощи /lib/udev/rename_device, просматривает конфигурационные файлы сетевых интерфейсов - /etc/sysconfig/network-scripts/ifcfg-*, и если есть соответствие MAC адреса добавляемого интерфейса и адреса в директиве HWADDR, то имя интерфейса устанавливается в соответствии с директивой DEVICE.
Сейчас воспользуемся небольшой хитростью, чтобы процесс применения правил udev был более наглядным. Создадим конфигурационный файл ifcfg-tstif0 с именем tstif0 для нашего адаптера и вставим адаптер. Интерфейс в системе станет tstif0. Теперь удалим конфигурационный файл, и посмотрим на обработку правил еще раз, но уже для интерфейса tstif0, в конечном итоге мы должны будем получить имя enp0s20u7u4.
Запустим еще раз, первое правило не отработает, т.к. конфигурационный файл мы удалили:

PROGRAM '/lib/udev/rename_device' /usr/lib/udev/rules.d/60-net.rules:1
starting '/lib/udev/rename_device'
'/lib/udev/rename_device' [5023] exit with return code 0

  • Шаг второй, смотрим дальше:
PROGRAM '/sbin/biosdevname --policy physical -i tstif0' /usr/lib/udev/rules.d/71-biosdevname.rules:22
starting '/sbin/biosdevname --policy physical -i tstif0'
'/sbin/biosdevname --policy physical -i tstif0' [5024] exit with return code 2

Правило /usr/lib/udev/rules.d/71-biosdevname.rules реализует выше рассмотренное соглашение о именах при использовании biosdevname. Уточнение, это правило работает, если в системе установлен biosdevname, при загрузке не был использован параметр biosdevname=0, и если имя не было назначено на предыдущем шаге.

  • Шаг третий:
IMPORT builtin 'net_id' /usr/lib/udev/rules.d/75-net-description.rules:6
IMPORT builtin skip 'usb_id' /usr/lib/udev/rules.d/75-net-description.rules:8
IMPORT builtin 'hwdb' /usr/lib/udev/rules.d/75-net-description.rules:8

На этом этапе правилом /usr/lib/udev/rules.d/75-net-description.rules, заполняются следующие директивы описания устройства: ID_NET_NAME_ONBOARD, ID_NET_NAME_SLOT, ID_NET_NAME_PATH и ID_NET_NAME_MAC. Не все директивы могут быть заполнены, поскольку не все устройства могут иметь требуемую информацию.

  • Шаг четвертый:
IMPORT builtin 'path_id' /usr/lib/udev/rules.d/80-net-setup-link.rules:5
IMPORT builtin 'net_setup_link' /usr/lib/udev/rules.d/80-net-setup-link.rules:9
Config file /usr/lib/systemd/network/99-default.link applies to device tstif0
NAME 'enp0s20u7u4' /usr/lib/udev/rules.d/80-net-setup-link.rules:11

Вот оно правило, которое устанавливает имя для интерфейса в данном случае - /usr/lib/udev/rules.d/80-net-setup-link.rules. Что по этому правило происходит: если интерфейс не был переименован на предыдущих шагах, и если не задан параметр ядра net.ifnames=0, то имя интерфейса устанавливается в соответствии с порядком по приоритету из полученных ранее директив: ID_NET_NAME_ONBOARD, ID_NET_NAME_SLOT, ID_NET_NAME_PATH. Если ни одна из директив не определена, то интерфейс остается не переименованным, т.е. старый вариант именования — eth0.
Для понимания, посмотреть возможные имена можно так:

# udevadm info /sys/class/net/tstif0 | grep ID_NET_NAME
E: ID_NET_NAME=enp0s20u7u4
E: ID_NET_NAME_MAC=enx00101402bca7
E: ID_NET_NAME_PATH=enp0s20u7u4

Можно заметить, что почти все пункты (за исключением 5 — старого именования) из стандартного соглашения о наименовании реализуются в правилах udev: 75-net-description.rules и 80-net-setup-link.rules. Можно возразить, а где наименование по MAC адресам? Включение его можно произвести правкой правила 80-net-setup-link.rules. Скопируйте его:

# cp /usr/lib/udev/rules.d/80-net-name-slot.rules /etc/udev/rules.d

И добавьте строку выделенную курсивом на листинге:

# grep -v "^#\|^$" /etc/udev/rules.d/80-net-name-slot.rules
ACTION!="add", GOTO="net_name_slot_end"
SUBSYSTEM!="net", GOTO="net_name_slot_end"
NAME!="", GOTO="net_name_slot_end"
IMPORT{cmdline}="net.ifnames"
ENV{net.ifnames}=="0", GOTO="net_name_slot_end"
NAME=="", ENV{ID_NET_NAME_MAC}!="", NAME="$env{ID_NET_NAME_MAC}"
NAME=="", ENV{ID_NET_NAME_ONBOARD}!="", NAME="$env{ID_NET_NAME_ONBOARD}"
NAME=="", ENV{ID_NET_NAME_SLOT}!="", NAME="$env{ID_NET_NAME_SLOT}"
NAME=="", ENV{ID_NET_NAME_PATH}!="", NAME="$env{ID_NET_NAME_PATH}"
LABEL="net_name_slot_end"

Не уверен, что данный вариант именования удобен.

Итак, мы обсудили несколько соглашений о наименовании сетевых интерфейсов, рассмотрели по шагам, как происходит выбор имени, попутно узнали, что net.ifnames=0 отключает новую схему наименования, и последнее замечание: не используйте имена из пространства ядра - ethX в схеме с присвоением имен через файлы ifcfg-*.



воскресенье, 15 ноября 2015 г.

Перезагрузка сервера без нервов

Последнее поколение серверов с большим количеством аппаратных ресурсов, тратят много времени на проверку оборудования при старте системы. Порой это может занять 20 минут и более. Для систем, где окно техобслуживания составляет не больше часа, обновление ОС может быть очень затруднено. Выйти из положения позволяет kexec. Kexec - это системный вызов, который загружает в память новое ядро и производит загрузку ОС с этим ядром. Фактически, kexec выполняет функцию загрузчика ОС. Естественно данный процесс загрузки отличается от нормального тем, что не производится инициализация оборудования на аппаратном уровне (UEFI), за счет этого и достигается выигрыш времени.
Синтаксис команды для загрузки нового ядра:

kexec -l kernel-image --append=command-line-options --initrd=initrd-image

где:

  • -l - путь к новому ядру
  • --append - параметры, используемые при загрузке, можно использовать текущие параметры, содержащиеся в /proc/cmdline, для этого вместо этого параметра укажите --reuse-cmdline
  • --initrd - путь к initrd/initramfs образу.

После чего необходимо дать команду на запуск нового ядра:

kexec -e

Пример:

# kexec -l /boot/vmlinuz-3.10.0-229.20.1.el7.x86_64 --reuse-cmdline --initrd=/boot/initramfs-3.10.0-229.20.1.el7.x86_64.img
# kexec -e

четверг, 5 ноября 2015 г.

Имя хоста

Имена бывают трех типов:

  • статическое (static) — то, которое задает пользователь, хранится в /etc/hostname;
  • временное (transient) — имя,  полученное посредством DHCP или mDNS, если статическое имя установлено и оно отлично от localhost, то будет использоваться статическое имя;
  • прелестное (pretty) — имя, которое можно использовать для описания хоста, к примеру «Super duper server», хранится в /etc/machine-info. Может быть полезно, когда имена машин однотипны. Больше всего умиляет, что названия как в физике, типа «прелестный кварк».

Статическое и временное имена должны состоять из символов: a-z, A-Z, 0-9, дефиса, подчеркивания и точки, длина не должна превышать 64 символа.
Посмотреть текущие имена можно следующим образом:

$ hostnamectl status

Установить все имена сразу можно командой:

# hostnamectl set-hostname имя

Если нужно изменить определенное имя, то нужно добавить --pretty, --static или --transient, к примеру:

# hostnamectl set-hostname "Super duper server" --pretty

Заметки о RHEL7



Друзья/коллеги/читатели, пишу ряд статей об использовании RHEL7. Я сознательно не рассматриваю абсолютно все возможные пути конфигурирования или выполнения базовых операций. К примеру, в статьях точно не будут рассмотрены варианты конфигурирования с помощью графической оболочки NetworkManager. Источники данных, в большинстве своем, официальные документы с сайта вендора.
Ниже будут ссылки на статьи для удобства.

Имя хоста
Имя сетевого устройства

пятница, 10 июля 2015 г.

Балансировщик трафика для Exchange 2013

Продолжим рассматривать балансировку, отказоустойчивость и масштабирование приложений. Поскольку используется все тот же keepalived вместе с ipvs, то я не буду расписывать все подробно и рекомендую обратится к предыдущим статьям 1 и 2.
Цель задачи состоит в замене Windows Network Load Balancing(WNLB) для балансировки соединений к Exchange. Почему не устраивает WNLB?
  1. Не отслеживает доступность сервиса.
  2. Не обеспечивает равномерную балансировку
  3. Повышенная нагрузка на сетевую инфраструктуру
  4. Работает не устойчиво при нагрузке на сервера Exchange.
IP адреса нод кластера балансировки пусть будут 192.168.3.10 и 192.168.3.11. Для минимизации изменений в существующей инфраструктуре наш балансир должен иметь IP ранее принадлежащий WNLB (192.168.3.110). Следовательно, чтобы соединение прошло от клиента к кластеру Exchange (четыре сервера 192.168.3.111 - 192.168.3.114), необходимо осуществить трансляцию адреса назначения - Destination NAT. А чтобы ответные пакеты от Exchange серверов не ушли напрямую к клиенту, нужен Source NAT. Последняя ремарка: в отличие от уже упомянутых статей - это исключительно выделенный кластер для балансировки, который состоит из двух серверов (активный и резервный), в моем случае ОС это OEL6 с ядром UEK R2, это важно, поскольку более старое ядро не позволит реализовать задуманное, т.е. ядро из rhel6 не подойдет.

Итак, конфигурационный файл keepalived:

! Configuration File for keepalived

global_defs {
   notification_email {
      unix_adm@domain.ru
   }
   notification_email_from exchange_balancer01.domain.ru
   smtp_server 192.168.3.1
   smtp_connect_timeout 30
   router_id exchange_balancer01.domain.ru
}

vrrp_instance exchange_relay {
    state MASTER
    interface eth0
    virtual_router_id 117
    priority 100
    lvs_sync_daemon_interface eth0
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass mrelay
    }
    virtual_ipaddress {
        192.168.3.110
    }
    preempt
    notify_master "/etc/keepalived/snat.py -a 192.168.3.111 -a 192.168.3.112 -a 192.168.3.113 -a 192.168.3.114 -s 192.168.3.110"
    notify_backup "/etc/keepalived/snat.py -r 192.168.3.111 -r 192.168.3.112 -r 192.168.3.113 -r 192.168.3.114 -s 192.168.3.110"
    notify_fault "/etc/keepalived/snat.py -r 192.168.3.111 -r 192.168.3.112 -r 192.168.3.113 -r 192.168.3.114 -s 192.168.3.110"
}

virtual_server 192.168.3.110 443 {
    delay_loop 20
    lb_algo wlc
    lb_kind NAT
    protocol TCP

    real_server 192.168.3.111 443 {
        weight 1
          MISC_CHECK {
            misc_path "/etc/keepalived/check_exchange.sh 192.168.3.111"
            misc_timeout 18
          }
        }
    
    #Идентичные описания Exchange серверов
}

Что здесь нового:
  • lvs_sync_daemon_interface eth0 - На резервную ноду передается информация об открытых соединениях. Это позволяет оставить рабочими все текущие соединения в случае выхода из строя master ноды. Проверить работоспособность механизма передачи  на резервной ноде можно путем просмотра состояния текущих соединений:
         # ipvsadm -Lnc
  • Скрипт snat.py - в отличие от предыдущих статей, скрипт добавляет правила iptables на активную ноду, чтобы реализовать SNAT, соответственно он же убирает правила на резервной ноде. Правила в виде:
        -A POSTROUTING -d 192.168.3.111/32 -p tcp -j SNAT --to-source 192.168.3.110
  •  lb_kind NAT - Метод передачи пакетов, DNAT.
Чуть подробнее взглянем на скрипт проверки доступности приложения - check_exchange.sh.

#!/bin/sh

error=0
for i in `seq 1 3`
  do
    echo "GET /rpc/healthcheck.htm" | openssl s_client -connect $1:443 -crlf -quiet 2>/dev/null | grep "200 OK" || let error=$error+1
  done
[ $error -lt 3 ] && exit 0 || exit 1

Производится запрос состояния Exchange 2013 Outlook Anywhere путем обращения к странице https://exchange_server/rpc/healthcheck.htm. Если страница содержит 200 OK - состояние рабочее. Скрипт запрашивает три раза, и если хотя бы одна проверка верная, считается что сервис функционирует штатно. Почему так? Сервера exchange, в моем случае, работают под нагрузкой и не всегда успевают выдать страницу проверки. Если есть желание, можете дополнить скрипт на проверку других служб:

owa - Exchange 2013 Outlook Web App
ews - Exchange 2013 Web Services
microsoft-Server-ActiveSync - Exchange 2013 ActiveSync
ecp - Exchange 2013 Control Panel
autodiscover - Exchange 2013 Autodiscover Service
oab - Exchange 2013 Offline Address Book

И последнее, установите переменную ядра:

net.ipv4.vs.conntrack = 1

Без нее не будет работать контроль состояния соединения - это приведет к тому, что правила SNAT и DNAT будут работать не корректно.




воскресенье, 1 марта 2015 г.

Отказоустойчивый, масштабируемый кластер приложения с балансировкой нагрузки, часть 2 (про масштабирование и про ARP Flux)

Возвращаясь к статье Отказоустойчивый, масштабируемый кластер приложения с балансировкой нагрузки, может создаться впечатление, что масштабировать кластер можно, просто добавляя дополнительные ноды. Масштабируемость, на первый взгляд, будет ограничена количеством адресов в сетевом сегменте. Но внимание: чем больше нод, тем больше будет утилизация конечного приложения, и тем больше будет не целевого трафика в сегменте. Этому виной будут перекрестные проверки нод на доступность ресурсов. Следовательно, количество нод, которые могут выполнять функции балансировки, должно быть разумно ограниченным. По моему мнению четыре вполне достаточно. Рассмотрим, как можно преодолеть эту проблему.
Создадите сервер приложения аналогично предыдущим, но keepalived не ставьте. Добавьте интерфейс loopback с общим ip кластера:

# cat /etc/sysconfig/network-scripts/ifcfg-lo:0
DEVICE=lo:0
IPADDR=192.168.3.100
NETMASK=255.255.255.255
ONBOOT=yes

Не спешите применять настройки. Поговорим вот еще о каком аспекте: по умолчанию linux сервер c несколькими интерфейсами в одной сети будет отвечать на ARP запрос, пришедший на любой локальный интерфейс и содержащий любой целевой локальный ip адрес. Звучит запутанно, покажу на примере: предположим на хосте есть два интерфейса с ip 192.168.3.200 и 192.168.3.201, тогда:

# arping -I eth0 192.168.3.200
ARPING 192.168.3.4 from 192.168.3.102 eth0
Unicast reply from 192.168.3.200 [52:54:00:BD:DF:C2]  1.362ms
Unicast reply from 192.168.3.200 [52:54:00:10:11:35]  1.209ms

Мы получаем два разных ARP ответа, эта проблема называется ARP Flux. Если мы сейчас включим интерфейс lo:0 это, в свою очередь, может стать причиной ARP Poisoning. Для того, чтобы решить эту проблему, необходимо поменять эту модель поведения работы ARP (host based model) на интерфейсо-зависимую (interface based model). Выставите следующие переменные ядра:

# echo -e "net.ipv4.conf.all.arp_ignore = 1 \nnet.ipv4.conf.all.arp_announce = 2" >> /etc/sysctl.d/99-sysctl.conf

Что эти переменные означают:
net.ipv4.conf.all.arp_ignore - определяет различные режимы ответа, на входящие ARP запросы, которые производят разрешение локальных IP адресов. Возможные значения:
  • 0 - установлено по умолчанию. Ответ производится на ARP запрос, пришедший на любой сетевой интерфейс системы, на разрешение любого целевого локального IP адреса.
  • 1 - ответ производится в случае, если ARP запрос на разрешение IP адреса пришел на интерфейс, содержащий этот IP адрес.
  • 2 - ответ производится в случае, если ARP запрос на разрешение IP адреса пришел на интерфейс, содержащий этот IP адрес, причем IP адрес отправителя должен находится в одной подсети целевым IP адресом.
  • 3 - не отвечать, если IP адрес источника находится в той же сети.
  • 4-7 - зарезервированы.
  • 8 - не отвечать на запросы.
net.ipv4.conf.all.arp_announce - определяет, каким образом будет анонсироваться локальный IP адрес в исходящих ARP запросах:
  • 0 - установлено по умолчанию. Используется любой локальный адрес, настроенный на любом интерфейсе.
  • 1 - попытаться исключить локальные адреса, которые не принадлежат к подсети. в которой находится целевой IP адрес. В случае отсутствия такого локального IP адреса будет использоваться поведение за номером 2.
  • 2 - Всегда использовать лучший локальный адрес, с которого нам удобно посылать запрос в целевую подсеть. Такой IP адрес выбирается из первичных IP адресов, находящихся в той же подсети, что и целевой IP адрес, на всех локальных интерфейсах. Если ничего подходящего нет, то будет использован первый ip адрес на интерфейсе, с которого будет произведен запрос или на всех других интерфейсах.
Теперь можно применить изменения и добавить новый сервер приложения в конфигурацию keepalived на всех нодах балансировщиках.

четверг, 26 февраля 2015 г.

Курсы Java/Android

Всем интересующимся программированием на java: проводятся курсы, доп. информация по ссылке. Автор курса мой знакомый - Лигай Виталий, гуру программирования, как я считаю.

понедельник, 23 февраля 2015 г.

Отказоустойчивый, масштабируемый кластер приложения с балансировкой нагрузки, часть 1

Заголовок броский, я бы даже сказал, слегка желтоват (желтая IT пресса - более унылой вещи трудно представить). Но, тем не менее, это правда, хотя, как известно, все скрывается в деталях. В данном случае детали - это, собственно, возможность самого ПО делить нагрузку или обеспечивать отказоустойчивость. Но хватит лирики, перейдем к постановке задачи.

Мне нужен почтовый релей, забегая вперед, скажу, что с таким же успехом это может быть любое другое приложение, например, web сервер. Могу показаться банальным занудой, но хочу еще раз повторить: каждый сервис требует отдельного анализа, и не факт, что желаемое удастся реализовать. Далее отразим заголовок статьи: релей должен быть отказоустойчивым и иметь возможность масштабирования. Читатель статьи может возразить, что для почтового релея все вышесказанное можно реализовать, добавив несколько MX записей в доменной зоне. К сожалению, я ограничен одним, уже существующим ip адресом релея, таким образом, вариант с MX не годится. Подведем итог: нужно несколько почтовых релеев, обеспечивающих отказоустойчивость сервиса, разделяющих нагрузку и использующих один ip адрес.

В плане выбора ПО все просто и традиционно - я буду использовать дистрибутив oracle linux 7 (почти аналогичны ему centos 7 или rhel 7), в качестве smtp сервиса - postfix. Для балансировки будет использоваться решение LVS, поскольку ее частью является подсистема linux ядра - ipvs, быстрый и надежный вариант балансировки трафика на 4-м уровне сетевой модели OSI. 

В качестве управляющего ПО кластером используется keepalived. Данное ПО использует протокол VRRP для обеспечения высокой доступности (high avialability) нод кластера, а также позволяет организовать мониторинг доступности приложения.

Оставшиеся вводные данные:
Используемая сеть: 192.168.3.0/24
IP серверов по порядку: 192.168.3.[101-105]
Общий IP: 192.168.3.100

Обычно в стандартной практике делают отдельный кластер балансировки и группу серверов приложения, к примеру, рекомендуемое решение от Red Hat. Особенность данной реализации заключается в том, что каждый сервер является сервером приложения и балансировщиком трафика одновременно. Один из балансировщиков является активным, остальные резервные. В моем случае используются четыре релея, расположенные по два в двух географически разнесенных ЦОДах. Ниже приведен рисунок, иллюстрирующий прохождение трафика в кластере, к которому мы будем возвращаться при описании процесса работы.


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

Перейдем к установке и настройке. Все написанное ниже справедливо для всех нод кластера, если не указано обратное. Установку и настройку postfix рассматривать не будем, убедитесь только что postfix использует все доступные сетевые интерфейсы:

# grep "inet_interfaces" /etc/postfix/main.cf | grep -v "^#"
inet_interfaces = all

Отключим firewalld, его можно будет настроить позднее, после того как будете твердо уверены, что кластер функционирует должным образом:

# systemctl disable firewalld.service
# systemctl stop firewalld.service

Установим keepalived, ipvsadm и модуль python ipaddr, модуль нужен для скрипта:

# yum -y install keepalived ipvsadm python-ipaddr

Разберем конфигурационный файл keepalived:

# Секция определения общих параметров
global_defs {
   notification_email {
      # Список почтовых адресов, по которым будут приходить оповещения о
      # недоступности нод или сервисов
      unix_adm@domain.ru
   }
   # Email от которого будет рассылаться почта
   notification_email_from keepalived@domain.ru
   # Адрес smtp сервера и время, в течение которого будет производится попытка
   # отправки почты
   smtp_server 192.168.3.1
   smtp_connect_timeout 30
   # Идентификатор сервера
   router_id server01.domain.ru
}

# Секция описания экземпляров VRRP (перемещаемые IP)
vrrp_instance mail_relay {
    # Начальное состояние при запуске, на остальных серверах должно быть BACKUP
    state MASTER
    # Интерфейс, на котором будет работать экземпляр VRRP
    interface eth0
    # Идентификатор экземпляра VRRP, должен быть идентичен на всех нодах.
    virtual_router_id 11
    # Приоритет при выборе MASTER'а, нода с большим приоритетом становится
    # активной.
    priority 100
    # Интервал уведомлений о состоянии, которые рассылает MASTER нода.
    # При отсутствии уведомлений произойдут перевыборы.
    advert_int 2
    # Секция аутентификации, пароль должен быть идентичен на всех нодах,
    # используется первые 8 символов.
    authentication {
        auth_type PASS
        auth_pass mrelay
    }
    # Список адресов, которые будут добавлены на ноду при выборе ее MASTER
    virtual_ipaddress {
        192.168.3.100
    }
    # Произвести миграцию состояния MASTER при появлении ноды с более высоким
    # приоритетом. В случае установки опции в nopreempt при появлении ноды с 
    # более высоким приоритетом MASTER нодой останется прежняя нода.
    preempt
    # Скрипт, который надо выполнить при событии, когда нода становится MASTER
    notify_master "/etc/keepalived/bypass_ipvs.py -r 192.168.3.100"
    # Скрипт, который надо выполнить при событии, когда нода становится SLAVE
    notify_backup "/etc/keepalived/bypass_ipvs.py -a 192.168.3.100"
    # Скрипт, который надо выполнить при событии ошибки смены роли ноды
    notify_fault "/etc/keepalived/bypass_ipvs.py -a 192.168.3.100"
}

Что выполняет скрипт: при установке состояния BACKUP на ноде он добавляет правило в iptables, которое заменяет (NAT) общий IP в пакетах входящего трафика на локальный IP ноды, и наоборот - для ответного трафика:

-A PREROUTING -t nat -d 192.168.3.100/32 -p tcp -j REDIRECT

Таким образом реализуется совместная обработка трафика всеми нодами, посмотрите еще раз рисунок вверху. В случае, когда нода становится MASTER, скрипт убирает правило натирования, поскольку общий IP принадлежит этой ноде. Скачать скрипт можно здесь.

Несколько замечаний: 
  • state - если вы уверены в качестве сети, можно на всех нодах кластера выставить BACKUP, в этом случае мастер определится в результате выборов. 
  • priority - в случае равных приоритетов побеждает нода с большим IP.
  • nopreempt - при данной опции state должен быть установлен в BACKUP.
  • если установить опции state BACKUP, одинаковый priority и nopreempt на все ноды, мы получим идентичный конфигурационный файл, что очень удобно в системах централизованного сопровождения серверов. Но вместе с тем шансы возникновения ситуации "split brain" в результате каких либо проблем также увеличиваются.
Продолжим далее рассматривать keepalived.conf:

# Секция конфигурации политики балансировки (LVS) и описание серверов приложения
virtual_server 192.168.3.100 25 {
    # Период опроса серверов приложения
    delay_loop 2
    # Планировщик балансировки, wlc - Weighted Least-Connections Scheduling.
    lb_algo wlc
    # Метод передачи пакетов на сервера приложений, dr - Direct Routing
    lb_kind DR
    protocol TCP

    # Секция описание сервера приложения 192.168.3.101
    real_server 192.168.3.101 25 {
        # Вес сервера
        weight 1
        # Секция проверки доступности приложения, в данном случае выполняется
        # проверка доступности smtp приложения. 
        SMTP_CHECK {
            connect_timeout 5
            retry 3
            # Имя в команде smtp helo
            helo_name smtpchecker.domain.ru
        }
    }
    # Далее однотипные описания серверов для 192.168.3.102 и т.д.
}

Еще несколько замечаний:
  • lb_algo wlc - распределяет большее количество запросов серверам с наименьшим количеством активных подключений в соответствии с их весом.
  • lb_kind DR - маршрутизация пакета, в этом случае пакет с активной ноды передается на резервную следующим образом: производится замена MAC адреса в поле destination MAC в ethernet заголовке пакета с MAC адреса активной ноды на MAC адрес сервера приложения. IP адрес остается без изменения. В свою очередь сервер приложения (нода в состоянии BACKUP) получив пакет, натирует общий IP адрес в поле destination IP в свой собственный. 
  • со всеми вариантами балансировки, передачи пакетов, методах проверки доступности приложения можно ознакомиться на сайте проекта LVS.
Ссылка на файл keepalived.conf

Запустим keepalived и проверим что получилось:

# systemctl enable keepalived.service
# systemctl start keepalived.service

$ telnet 192.168.3.100 25
Trying 192.168.3.100...
Connected to 192.168.3.100.
Escape character is '^]'.
220 server01.domain.ru ESMTP Postfix
quit
221 2.0.0 Bye
Connection closed by foreign host.
[valentine@server ~]$ telnet 192.168.3.100 25
Trying 192.168.3.100...
Connected to 192.168.3.100.
Escape character is '^]'.
220 server02.domain.ru ESMTP Postfix
quit
221 2.0.0 Bye
Connection closed by foreign host.

Итак все работает, это замечательно. Что делать, когда не работает? Ниже варианты команд, которые помогут разобраться в проблеме.

Посмотреть логи keepalived:

# journalctl -u keepalived.service

Посмотреть, на какой ноде находится общий IP, привычная команда ifconfig его не показывает:

# ip addr show
...
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 52:54:00:0d:5a:1a brd ff:ff:ff:ff:ff:ff
    inet 192.168.3.101/24 brd 192.168.3.255 scope global dynamic eth0
       valid_lft 70403sec preferred_lft 70403sec
    inet 192.168.3.100/32 scope global eth0
...

Или более современный вариант:

# nmcli device show eth0
...
IP4.ADDRESS[1]:                         ip = 192.168.3.101/24, gw = 192.168.3.1
IP4.ADDRESS[2]:                         ip = 192.168.3.100/32, gw = 192.168.3.1
...

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

# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.3.100:25 wlc
  -> 192.168.3.101:25             Route   1      2          14         
  -> 192.168.3.102:25             Route   1      3          9      
...

Посмотреть текущие соединения:

# ipvsadm -Lnc
IPVS connection entries
pro expire state       source             virtual            destination
TCP 13:20  ESTABLISHED 192.168.3.3:59527  192.168.3.100:25   192.168.3.102:25
...

Если в сети несколько vrrp экземпляров, можно проверить номера идентификаторов

# tcpdump -nnn ip proto 112
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
21:11:36.244824 IP 192.168.3.101 > 224.0.0.18: VRRPv2, Advertisement, vrid 11, prio 100, authtype simple, intvl 1s, length 20

Удачных внедрений!

Upd: продолжение, для тех кому мало будет 4 ноды.