\documentclass[a4paper,12pt,draft]{article}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\usepackage{indentfirst}
\usepackage{graphicx}
\usepackage{url}
\usepackage{framed}
\title{Кластеризация + виртуализация : Linux HA + OpenVZ}

\author{Евгений Прокопьев}
% \city{Ростов-на-Дону}
\begin{document}
\maketitle
\setcounter{tocdepth}{3}

\tableofcontents{}

\clearpage 

\begin{abstract}
  Как одновременно уменьшить количество физических серверов, требующих
  обслуживания, и при этом продублировать их, обеспечив автоматическое
  переключение на резервный сервер при отказе основного
\end{abstract}

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

\begin{itemize}
\item \emph{Отказоустойчивость} "--- от простого резервного копирования
  важных данных до систем высокой готовности (High Availability),
  когда при выходе из строя основного сервера его функции
  автоматически берет на себя резервный сервер.

\item \emph{Консолидация сервисов и сокращение количества физических
    серверов, требующих обслуживания} "--- от простого размещения
  нескольких сервисов на одном физическом сервере до использования
  различных систем виртуализации, изолирующих сервисы друг от друга.
\end{itemize}

На первый взгляд эти задачи кажутся прямо прямо противоположными,
однако их вполне можно совместить, если обеспечить отказоустойчивость
не каждого сервиса в отдельности, а \emph{группы сервисов}, работающих в
виртуальных средах.

\section{Инструменты}

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

Если требуется гарантировать отказоустойчивость сервису, который сам
по себе отказоустойчивым не является и встроенных механизмов
кластеризации не имеет, то особых альтернатив проекту Linux HA
(\url{http://linux-ha.org}) нет. Основным компонентом проекта является
менеджер кластера Heartbeat, предоставляющий средства коммуникации
между узлами кластера (в том числе механизм оповещения об
отключении/""включении узла) и управления ресурсами кластера. Для
сервисов, решающих исключительно вычислительные задачи, этого может
быть достаточно, но большинство сервисов имеют привычку также хранить
свои настройки и данные на диске. Чтобы они были доступны сервису
независимо от того, на каком узле кластера он в данный момент
работает, требуются внешние по отношению к проекту Linux HA средства
организации распределенного файлового хранилища. В их качестве могут
выступать:

\begin{itemize}
\item \emph{классические распределенные файловые системы (OCFS, CODA,
    GFS, Lustre)}, позволяющие организовать параллельный доступ к
  файлам с нескольких компьютеров одновременно;

\item \emph{средства автоматического зеркалирования разделов жесткого
    диска на двух компьютерах}, обеспечивающие доступ к файлам на
  чтение и запись только с одного компьютера (программные "--- DRBD и
  аппаратные "--- IBM ServeRAID);

\item \emph{прочие средства}, которые в принципе использовать можно,
  но либо их надежность (NFS, CIFS), либо стоимость (SAN) в данном
  случае являются неприемлемыми.
\end{itemize}

Чаще всего в связке с Heartbeat используется DRBD (\url{http://drbd.org}),
которая в версии 7 обеспечивает более высокую производительность и
надежность по сравнению с классическими распределенными файловыми
системами. DRBD версии 8 движется в сторону своих конкурентов, пытаясь
сохранить собственные преимущества, но к промышленной эксплуатации она
пока еще не готова.

Что же касается виртуализации, то здесь выбор гораздо шире, и
определяется он тем, что именно мы собираемся виртуализировать, и
сколько аппаратных ресурсов мы готовы потратить. Поскольку нам не
требуется запуск в разных виртуальных средах различных операционных
систем, разумно будет в целях экономии аппаратных ресурсов
остановиться на технологиях виртуализации на уровне операционной
системы. Из трех открытых проектов такого рода, предназначенных для
Linux, наиболее функциональным и быстрее всего развивающимся выглядит
OpenVZ, который также является основой для проприетарного продукта
Virtuozzo фирмы SWSoft. Более того, на wiki проекта довольно коротко
уже описана удачная попытка <<скрестить>> его с проектом Linux HA "---
\url{http://wiki.openvz.org/HA_cluster_with_DRBD_and_Heartbeat}. Наконец,
OpenVZ работает в ALT Linux Sisyphus <<из коробки>>. Поэтому пойдем
уже проторенной дорогой и попытаемся построить аналогичное решение,
использующее все преимущества кластеризации.

\section{Что нам предстоит сделать}

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

\begin{enumerate}
\item \emph{Традиционный}\label{method:traditional} "--- для каждой
  задачи выделяется и конфигурируется отдельный физический сервер. В
  случае сбоя одного из серверов мы лишемся как минимум тех сервисов,
  которые этот сервер предоставляет. В худшем случае, учитывая, что
  сервисы могут зависеть друг от друга (а конфигурации, в которых
  почтовый сервер интенсивно использует сервер БД, не так уж редки),
  мы можем лишиться гораздо большего.
\item \emph{Кластеризация}\label{method:clusterization} "---
  физические сервера (точнее, сервисы, работающие на этих серверах)
  дублируются средствами Linux HA. В этом случае надежность
  значительно увеличивается, но возрастает также количество серверов и
  затраты на их обслуживание.
\item \emph{Виртуализация}\label{method:virtualization} "--- для
  каждого сервера выделяется отдельная виртуальная среда на общем
  физическом сервере. В этом случае увеличивается гибкость, теперь мы
  не ограничены в количестве серверов и можем, например, выделить
  отдельную виртуальную среду для организации
  маршрутизатора/брандмауэра, ограничивающего доступ к почтовому
  серверу и серверу БД. Количество физических серверов и затраты на их
  обслуживание уменьшаются, но вместе с ними уменьшается и надежность,
  которая может оказаться даже ниже, чем в случае (\ref{method:traditional}),
  если работоспособность серверов зависит друг от друга.
\item \emph{Кластеризация + виртуализация} "--- общий физический
  сервер (точнее, один единственный сервис "--- система виртуализации)
  дублируется средствами Linux HA, тем самым мы совмещаем преимущества
  подходов (\ref{method:clusterization}) и
  (\ref{method:virtualization}) ценой увеличения нагрузки на сервера
  по сравнению с подходом (\ref{method:traditional}).
\end{enumerate}

Если не углубляться в детали, схема выбранного нами последнего способа
построения системы изображена на
рисунке~\ref{fig:cluster+virtualization}.

\begin{figure}[h]
  \centering
  \includegraphics[width=10cm]{cluster+virtualization}
  
  \caption{Кластеризация + виртуализация}
\label{fig:cluster+virtualization}
\end{figure}

\section{Строим кластер}

Более подробная схема кластера, который нам необходимо построить,
изображена на рисунке~\ref{fig:cluster}. Каждый узел кластера имеет по 3 сетевые
карты:

\begin{figure}[h]
  \centering
  \includegraphics[width=13cm]{cluster}
  \caption{Схема кластера}
  \label{fig:cluster}
\end{figure}

\begin{itemize}
\item \texttt{eth0} используется для подключения к внешней сети;
\item \texttt{eth1} будет использована далее при создании виртуального
  сервера router, поэтому IP"~адрес ей не присвоен;
\item \texttt{eth2} специально выделена для коммуникации между узлами
  кластера.
\end{itemize}

\subsection{Синхронизированные разделы: DRBD}

Сначала настроим общие разделы узлов кластера средствами
DRBD. Установка DRBD производится штатным для ALT Linux
способом. Cмотрим, какие пакеты имеются в наличии:

\begin{verbatim}
[root@m1 ~]# apt-cache search drbd
drbd-tools - Distributed Redundant Block Device utilities
kernel-modules-drbd-ovz-smp - Linux drbd kernel modules for DRBD.
kernel-modules-drbd-std-smp - Linux drbd kernel modules for DRBD.
kernel-modules-drbd-std-up - Linux drbd kernel modules for DRBD.
kernel-modules-drbd-std26-smp - Linux drbd kernel modules for DRBD.
kernel-modules-drbd-std26-up - Linux drbd kernel modules for DRBD.
kernel-modules-drbd-vs26-smp - Linux drbd kernel modules for DRBD.
kernel-modules-drbd-wks26-smp - Linux drbd kernel modules for DRBD.
kernel-modules-drbd-wks26-up - Linux drbd kernel modules for DRBD.
kernel-source-drbd-0.7.21 - Kernel source for DRBD
\end{verbatim}

\begin{framed}
  В репозиториях ALT Linux ядро имеется в скомпилированном и
  упакованном в RPM"~пакеты виде в нескольких вариантах. Каждый
  вариант оптимизирован под свою схему применения Такие пакеты
  называются по стандартной схеме \texttt{kernel-image-тип-сборки}.
  
  Практика ALT Linux показывает, что пользователю никогда не требуется
  самостоятельно компилировать ядро, достаточно выбрать один из
  имеющихся вариантов.
  
  Часть модулей ядра не включается в общий пакет с образом ядра, они
  распространяются в отдельных пакетах
  (\texttt{kernel-modules-тип-сборки}), что позволяет пользователю
  устанавливать и удалять их независимо. Однако при этом модули должны
  быть согласованы с ядром по версии и по типу сборки. 
\end{framed}

Затем определяем, какое у нас ядро:

\begin{verbatim}
[root@m1 ~]# uname -a
Linux m1.mydomain.com 2.6.16-std26-up-alt10 #1 Wed Sep 13 20:06:02 MSD 2006 i686 GNU/Linux
\end{verbatim}

На основании этой информации (\texttt{std26-up}) решаем, какие именно пакеты нам нужны, и
устанавливаем их:

\begin{verbatim}
[root@m1 ~]# apt-get install kernel-modules-drbd-std26-up drbd-tools
\end{verbatim}

Первый установленный нами пакет содержит модуль ядра, необходимый для
работы DRBD, второй "--- userspace"=инструменты для управления
DRBD. Конфигурирование DRBD сводится к редактированию файла
/etc/drbd.conf. Минимальная, но вполне работоспособная конфигурация
будет выглядеть так:

\begin{verbatim}
resource r0 {
    
    # протокол передачи
    # C: запись считается завершенной, если данные записаны на локальный и удаленный диск
    #    подходит для критически важных транзакций и в большинстве остальных случаев
    # B: запись считается завершенной, если данные записаны на локальный диск 
    #    и удаленный буферный кэш
    # A: запись считается завершенной, если данные записаны на локальный диск 
    #    и локальный буфер tcp для отправки
    #    подходит для для сетей с высокой задержкой   
    protocol B;
    
    # описание узла m1
    on m1.mydomain.com {
        device     /dev/drbd0;           # имя drbd-устройства
        disk       /dev/hda3;            # раздел диска, поверх которого оно работает 
        address    192.168.200.1:7788;   # адрес и порт демона drbd
        meta-disk  internal;             # хранить метаданные drbd на том же разделе диска
    }
    
    # описание узла m2
    on m2.mydomain.com {
        device    /dev/drbd0;            # имя drbd-устройства
        disk      /dev/hda3;             # раздел диска, поверх которого оно работает 
        address   192.168.200.2:7788;    # адрес и порт демона drbd
        meta-disk internal;              # хранить метаданные drbd на том же разделе диска
    }
}
\end{verbatim}

В комплекте с DRBD поставляется очень детальный пример
конфигурационного файла, в котором все параметры прокомментированы.

Для работы DRBD необходим специальный файл устройста (/dev/drbd0).
Если в системе используется udev, то такой файл будет создаваться
автоматически, в противном случае придется создать его вручную:

\begin{verbatim}
[root@m1 ~]# mknod /dev/drbd0 b 147 0
\end{verbatim}

Последний шаг настройки "--- обеспечить запуск сервиса drbd при
загрузке узла:

\begin{verbatim}
[root@m1 ~]# chkconfig --level 2345 drbd on
\end{verbatim}

Эти операции необходимо повторить на узле m2, а затем на обоих узлах
запустить сервис drbd:

\begin{verbatim}
[root@m1 ~]# service drbd start
Starting Starting DRBD resources:     service: [ d0 s0 n0 ].
...
\end{verbatim}

После чего на обоих узлах кластера мы должны увидеть следующее:

\begin{verbatim}
[root@m1 ~]# service drbd status
drbd driver loaded OK; device status:
version: 0.7.21 (api:79/proto:74)
SVN Revision: 2326 build by builder@xeon.office.altlinux.ru, 2006-09-22 23:46:26
 0: cs:Connected st:Secondary/Secondary ld:Inconsistent
    ns:0 nr:0 dw:0 dr:0 al:0 bm:0 lo:0 pe:0 ua:0 ap:0
\end{verbatim}

Итак, устройство /dev/drbd0 уже работает, но содержимое
разделов /dev/hda3, лежащих уровнем ниже, еще не
синхронизировано. Первый раз синхронизацию необходимо произвести
вручную, выполнив на узле m1:

\begin{verbatim}
[root@m1 ~]# drbdadm -- --do-what-I-say primary all
\end{verbatim}

После выполнения этой команды мы можем проследить за процесом
синхронизации:

\begin{verbatim}
[root@m1 ~]# service drbd status
drbd driver loaded OK; device status:
version: 0.7.21 (api:79/proto:74)
SVN Revision: 2326 build by builder@xeon.office.altlinux.ru, 2006-09-22 23:46:26
 0: cs:SyncSource st:Primary/Secondary ld:Consistent
    ns:1972 nr:0 dw:0 dr:10164 al:0 bm:0 lo:0 pe:30 ua:2048 ap:0
       [>...................] sync'ed:  0.2% (3158696/3160552)K
       finish: 0:26:19 speed: 1,856 (1,856) K/sec
\end{verbatim}

После окончания синхронизации на узле m1 мы увидим:

\begin{verbatim}
[root@m1 ~]# service drbd status
drbd driver loaded OK; device status:
version: 0.7.21 (api:79/proto:74)
SVN Revision: 2326 build by builder@xeon.office.altlinux.ru, 2006-09-22 23:39:33
 0: cs:Connected st:Primary/Secondary ld:Consistent
    ns:731068 nr:1052796 dw:1782732 dr:193337 al:20 bm:356 lo:0 pe:0 ua:0 ap:0
\end{verbatim}

По строке Primary/Secondary можно определить, что сейчас узел m1
является ведущим. На ведомом узле m2 в выводе той же самой команды мы
увидим Secondary/Primary. Все операции с устройством /dev/drbd0
необходимо выполнять только на ведущем узле, при этом все изменения
будут автоматически применены к разделам /dev/hda3 на обоих узлах
кластера.

Создадим на устройстве /dev/drbd0 файловую систему и смонтируем ее:

\begin{verbatim}
[root@m1 ~]# mkfs.ext3 /dev/drbd0
mke2fs 1.39 (29-May-2006)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
395200 inodes, 790138 blocks
39506 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=809500672
25 block groups
32768 blocks per group, 32768 fragments per group
15808 inodes per group
Superblock backups stored on blocks: 
        32768, 98304, 163840, 229376, 294912

Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 25 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
[root@m1 ~]# mkdir /d0
[root@m1 ~]# mount /dev/drbd0 /d0
\end{verbatim}

Если теперь мы попытаемся смонтировать устройство /dev/drbd0 на узле
m2, мы получим следующее сообщение об ошибке:

\begin{verbatim}
[root@m2 ~]# mkdir /d0
[root@m2 ~]# mount /dev/drbd0 /d0
/dev/drbd0: Input/output error
mount: block device /dev/drbd0 is write-protected, mounting read-only
/dev/drbd0: Input/output error
mount: /dev/drbd0 already mounted or /d0 busy
\end{verbatim}

Такое (вполне разумное) поведение гарантируется только для ядер 2.6, в
2.4 была возможность смонтировать устройство /dev/drbd0 на обоих
узлах кластера одновременно, что, в свою очередь, могло привести к
повреждению файловой системы.

Чтобы все-таки смонтировать раздел /dev/drbd0 на узле m2, необходимо
сделать его ведущим, выполнив на узлах m1 и m2 следующие команды:

\begin{verbatim}
[root@m1 ~]# umount /d0
[root@m1 ~]# drbdadm disconnect all
[root@m2 ~]# drbdadm disconnect all
[root@m1 ~]# drbdadm secondary all
[root@m2 ~]# drbdadm -- --human primary all
[root@m1 ~]# drbdadm connect all
[root@m2 ~]# drbdadm connect all
[root@m2 ~]# mount /dev/drbd0 /d0 
\end{verbatim}

В случае отказа узла m1 достаточно выполнить только те команды,
которые приведены выше для узла m2. По большому счету, любители
велосипедостроения (или оптимальных решений "--- иногда трудно отличить
одно от другого) могут этим и ограничиться: написать скрипт, который
будет следить на доступностью соседнего узла и монтировать раздел
/dev/drbd0, если соседний узел перестал отвечать, можно самостоятельно. Другой
вопрос: зачем, если Heartbeat умеет делать и это, и многое другое?

\subsection{Автоматическое отслеживание узлов: Heartbeat}

Устанавливаем Heartbeat штатным для ALT Linux способом:

\begin{verbatim}
[root@m1 heartbeat]# apt-get install heartbeat
\end{verbatim}

Установка Heartbeat для других дистрибутивов Linux описана на сайте проекта
(\url{http://www.linux-ha.org/DownloadSoftware}). На главной странице проекта
также сказано, что кроме Linux Heartbeat собирается и запускается на
FreeBSD, Solaris и даже MacOS X.

После установки Heartbeat на оба узла кластера на каждом узле
необходимо создать файл /etc/ha.d/authkeys с правами доступа 600 и
случайной строкой (ее можно придумать самому, а лучше сгенерировать с
помощью apg), которая будет использоваться узлами для авторизации друг
друга:

\begin{verbatim}
auth 1
1 sha1 RcBkJzU8ClnrjWVRLv5EDsdRFQP1j1C
\end{verbatim}

Также необходимо создать конфигурационный файл Heartbeat, который на
узле m1 будет выглядеть так:

\begin{verbatim}
Logfacility     local0
ucast eth2 192.168.200.2
auto_failback on
node m1.mydomain.com m2.mydomain.com
\end{verbatim}

а на m2 "--- так:

\begin{verbatim}
Logfacility     local0
ucast eth2 192.168.200.1
auto_failback on
node m1.mydomain.com m2.mydomain.com
\end{verbatim}

Затем необходимо описать ресурсы кластера в файле
/etc/ha.d/haresources на каждом узле:

\begin{verbatim}
m1.mydomain.com drbddisk Filesystem::/dev/drbd0::/d0::ext3
\end{verbatim}

Такая запись означает, что кластер в штатном режиме будет использовать
ресурсы drbddisk и Filesystem узла m1, а в случае его <<смерти>> "---
аналогичные ресурсы узла, оставшегося в живых, то есть m2. Для ресурса
Filesystem заданы параметры: имя drbd-устройства, каталог, в который
оно должно быть смонтировано (этот каталог мы должны создать
самостоятельно на каждом узле кластера), и тип файловой
системы. Узнать больше о ресурсах, поддерживаемых heartbeat, можно
заглянув в каталог /etc/ha.d/resource.d "--- каждый ресурс представлен
там соответствующим скриптом.

Теперь можно запустить сервис heartbeat (не забыв перед этим
размонтировать устройство /dev/drbd0, если оно было смонтировано):

\begin{verbatim}
[root@m1 ~]# service heartbeat start
logd is already running
Starting High-Availability services:       [ DONE ]
\end{verbatim}

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

\begin{verbatim}
m1 heartbeat: [3372]: info: Status update for node m2.mydomain.com: status active
m1 harc[3395]: info: Running /etc/ha.d/rc.d/status status
m1 heartbeat: [3406]: info: Local Resource acquisition completed.
m1 harc[3431]: info: Running /etc/ha.d/rc.d/ip-request-resp ip-request-resp
m1 ip-request-resp[3431]: received ip-request-resp drbddisk OK yes
m1 ResourceManager[3446]: info: Acquiring resource group: m1.mydomain.com drbddisk Filesystem::/dev/drbd0::/d0::ext3
m1 ResourceManager[3446]: info: Running /etc/ha.d/resource.d/drbddisk  start
m1 Filesystem[3569]: INFO: Running status for /dev/drbd0 on /d0
m1 Filesystem[3569]: INFO: /d0 is unmounted (stopped)
m1 Filesystem[3505]: INFO: Filesystem Resource is stopped
m1 ResourceManager[3446]: info: Running /etc/ha.d/resource.d/Filesystem /dev/drbd0 /d0 ext3 start
m1 Filesystem[3678]: INFO: Running start for /dev/drbd0 on /d0
m1 kernel: kjournald starting.  Commit interval 5 seconds
m1 kernel: EXT3 FS on drbd0, internal journal
m1 kernel: EXT3-fs: mounted filesystem with ordered data mode.
m1 Filesystem[3614]: INFO: Filesystem Success
\end{verbatim}

В логах ведомого узла можно будет увидеть следующее:

\begin{verbatim}
m2 heartbeat: [3739]: info: Status update for node m1.mydomain.com: status up
m2 harc[3752]: info: Running /etc/ha.d/rc.d/status status
m2 heartbeat: [3739]: info: remote resource transition completed.
m2 heartbeat: [3773]: info: No local resources [/usr/lib/heartbeat/ResourceManager listkeys m2.mydomain.com] to acquire.
\end{verbatim}

После старта heartbeat на обоих узлах кластера устройство /dev/drbd0
будет смонтировано на ведущем узле. Если остановить сервис heartbeat
на ведущем узле m1, ведомый m2 возьмет на себя функции ведущего, и
устройство /dev/drbd0 будет смонтировано на нем, при этом в логах мы
увидим:

\begin{verbatim}
m2 kernel: drbd0: Secondary/Primary --> Secondary/Secondary
m2 heartbeat: [3739]: info: Received shutdown notice from 'm1.mydomain.com'.
m2 heartbeat: [3739]: info: Resources being acquired from m1.mydomain.com.
m2 heartbeat: [3850]: info: acquire local HA resources (standby).
m2 heartbeat: [3851]: info: No local resources [/usr/lib/heartbeat/ResourceManager listkeys m2.mydomain.com] to acquire.
m2 heartbeat: [3850]: info: local HA resource acquisition completed (standby).
m2 heartbeat: [3739]: info: Standby resource acquisition done [all].
m2 harc[3870]: info: Running /etc/ha.d/rc.d/status status
m2 mach_down[3880]: info: Taking over resource group drbddisk
m2 ResourceManager[3900]: info: Acquiring resource group: m1.mydomain.com drbddisk Filesystem::/dev/drbd0::/d0::ext3
m2 ResourceManager[3900]: info: Running /etc/ha.d/resource.d/drbddisk start
m2 kernel: drbd0: Secondary/Secondary --> Primary/Secondary
m2 Filesystem[4023]: INFO: Running status for /dev/drbd0 on /d0
m2 Filesystem[4023]: INFO: /d0 is unmounted (stopped)
m2 Filesystem[3959]: INFO: Filesystem Resource is stopped
m2 ResourceManager[3900]: info: Running /etc/ha.d/resource.d/Filesystem /dev/drbd0 /d0 ext3 start
m2 Filesystem[4132]: INFO: Running start for /dev/drbd0 on /d0
m2 kernel: kjournald starting.  Commit interval 5 seconds
m2 kernel: EXT3 FS on drbd0, internal journal
m2 kernel: EXT3-fs: mounted filesystem with ordered data mode.
m2 Filesystem[4068]: INFO: Filesystem Success
m2 mach_down[3880]: info: /usr/lib/heartbeat/mach_down: nice_failback: foreign resources acquired
m2 mach_down[3880]: info: mach_down takeover complete for node m1.mydomain.com.
m2 heartbeat: [3739]: info: mach_down takeover complete.
\end{verbatim}

Если просто выключить питание узла m1, чтобы он не успел оповестить m2
о том, что он завершает свою работу, m2 обнаружит отсутствие m1 и
точно так же возьмет на себя функции ведущего узла и примонтирует
раздел /dev/drbd0:

\begin{verbatim}
m2 kernel: drbd0: PingAck did not arrive in time.
m2 kernel: drbd0: drbd0_asender [3566]: cstate Connected --> NetworkFailure
m2 kernel: drbd0: asender terminated
m2 kernel: drbd0: drbd0_receiver [3510]: cstate NetworkFailure --> BrokenPipe
m2 kernel: drbd0: short read expecting header on sock: r=-512
m2 kernel: drbd0: worker terminated
m2 kernel: drbd0: drbd0_receiver [3510]: cstate BrokenPipe --> Unconnected
m2 kernel: drbd0: Connection lost.
m2 kernel: drbd0: drbd0_receiver [3510]: cstate Unconnected --> WFConnection
m2 heartbeat: [3739]: WARN: node m1.mydomain.com: is dead
m2 heartbeat: [3739]: WARN: No STONITH device configured.
m2 heartbeat: [3739]: WARN: Shared disks are not protected.
m2 heartbeat: [3739]: info: Resources being acquired from m1.mydomain.com.
m2 heartbeat: [3739]: info: Link m1.mydomain.com:eth2 dead.
m2 harc[4383]: info: Running /etc/ha.d/rc.d/status status
m2 heartbeat: [4384]: info: No local resources [/usr/lib/heartbeat/ResourceManager listkeys m2.mydomain.com] to acquire.
m2 mach_down[4403]: info: Taking over resource group drbddisk
m2 ResourceManager[4423]: info: Acquiring resource group: m1.mydomain.com drbddisk Filesystem::/dev/drbd0::/d0::ext3
m2 ResourceManager[4423]: info: Running /etc/ha.d/resource.d/drbddisk  start
m2 kernel: drbd0: Secondary/Unknown --> Primary/Unknown
m2 Filesystem[4546]: INFO: Running status for /dev/drbd0 on /d0
m2 Filesystem[4546]: INFO: /d0 is unmounted (stopped)
m2 Filesystem[4482]: INFO: Filesystem Resource is stopped
m2 ResourceManager[4423]: info: Running /etc/ha.d/resource.d/Filesystem /dev/drbd0 /d0 ext3 start
m2 Filesystem[4655]: INFO: Running start for /dev/drbd0 on /d0
m2 kernel: kjournald starting.  Commit interval 5 seconds
m2 kernel: EXT3 FS on drbd0, internal journal
m2 kernel: EXT3-fs: recovery complete.
m2 kernel: EXT3-fs: mounted filesystem with ordered data mode.
m2 Filesystem[4591]: INFO: Filesystem Success
m2 mach_down[4403]: info: /usr/lib/heartbeat/mach_down: nice_failback: foreign resources acquired
m2 mach_down[4403]: info: mach_down takeover complete for node m1.mydomain.com.
m2 heartbeat: [3739]: info: mach_down takeover complete.
\end{verbatim}

При повторном запуске сервиса heartbeat на m1 или при включении узла
m1 он снова станет ведущим.

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

\section{Строим контейнер для виртуальных серверов и кластеризуем его}

Традиционно после конфигурирования общего файлового хранилища файл
/etc/ha.d/haresources дополняется списком сервисов, для которых
необходимо гарантировать отказоустойчивость, а конфигурационные файлы
и файлы данных этих сервисов перемещаются на файловую систему,
созданную поверх drbd-устройства. Часто также конфигурируется алиас
для сетевого интерфейса, который автоматически создается на ведущем
узле "--- тем самым все сервисы оказываются доступными по одному адресу.

Недостатки такого подхода очевидны "--- чем больше сервисов, тем более
запутанной становится такая конфигурация, а установка новых версий
сервисов становится очень неудобной. Про общие проблемы использования
различных сервисов на одном сервере я и не говорю "--- это специфично не
только для Linux HA.

Самым простым и изящным выходом в данной ситуации является
виртуализация. Если использовать этот подход, нам потребуется
обеспечить отказоустойчивость только для одного сервиса "--- контейнера,
в котором будут жить виртуальные сервера, причем последние можно
конфигурировать, не задумываясь о сложностях, связанных с
кластеризацией.

Приступим к установке выбранной нами системы виртуализации "---
OpenVZ. Она представляет собой набор патчей к стандартному ядру Linux
и userspace-инструменты для управления. В ALT Linux Sisyphus ядро с
поддержкой OpenVZ собрано отдельно, поэтому его необходимо установить
в дополнение к существующему ядру, установить модуль поддержки DRBD в
этом ядре и userspace-инструменты, представленные пакетами vzctl и
vzquota:

\begin{verbatim}
[root@m1 ~]# apt-get install kernel-image-ovz-smp kernel-modules-drbd-ovz-smp vzctl vzquota
\end{verbatim}

После установки нового ядра необходимо проверить конфигурацию
загрузчика и удостовериться в том, что после перезапуска будет
загружено ovz-ядро, а если это не так, предпринять для загрузки
требуемого ядра необходимые действия. Например, если в качестве
загрузчика используется lilo, то его конфигурационный файл может
выглядеть так:

\begin{verbatim}
boot=/dev/hda
map=/boot/map
default=linux
image=/boot/vmlinuz
    label=linux
    root=/dev/hda2
    initrd=/boot/initrd.img
    read-only
\end{verbatim}

При этом /boot/vmlinuz и /boot/initrd.img должны ссылаться на образ
ядра и образ initrd соответственно:

\begin{verbatim}
[root@m1 ~]# ls -l /boot/
total 6892
-rw-r--r-- 1 root root  755493 Sep 22 22:45 System.map-2.6.16-ovz-smp-alt7
-rw-r--r-- 1 root root  686841 Oct  9 23:54 System.map-2.6.16-std26-up-alt10
-rw-r--r-- 1 root root     512 Oct  9 23:54 boot.0300
-rw-r--r-- 1 root root     512 Oct  9 23:54 boot.0800
-rw-r--r-- 1 root root   65929 Sep 22 22:39 config-2.6.16-ovz-smp-alt7
-rw-r--r-- 1 root root   66100 Oct  9 23:54 config-2.6.16-std26-up-alt10
-rw------- 1 root root  321240 Oct 10 23:34 initrd-2.6.16-ovz-smp-alt7.img
-rw------- 1 root root  204992 Oct  9 23:54 initrd-2.6.16-std26-up-alt10.img
lrwxrwxrwx 1 root root      30 Oct 10 23:34 initrd-smp.img -> initrd-2.6.16-ovz-smp-alt7.img
lrwxrwxrwx 1 root root      32 Oct  9 23:54 initrd-up.img -> initrd-2.6.16-std26-up-alt10.img
lrwxrwxrwx 1 root root      30 Oct 10 23:34 initrd.img -> initrd-2.6.16-ovz-smp-alt7.img
-rw------- 1 root root   31744 Oct 10 23:38 map
lrwxrwxrwx 1 root root      27 Oct 10 23:34 vmlinuz -> vmlinuz-2.6.16-ovz-smp-alt7
-rw-r--r-- 1 root root 1246789 Sep 22 22:45 vmlinuz-2.6.16-ovz-smp-alt7
-rw-r--r-- 1 root root 1132834 Oct  9 23:54 vmlinuz-2.6.16-std26-up-alt10
lrwxrwxrwx 1 root root      27 Oct 10 23:34 vmlinuz-smp -> vmlinuz-2.6.16-ovz-smp-alt7
lrwxrwxrwx 1 root root      29 Oct  9 23:54 vmlinuz-up -> vmlinuz-2.6.16-std26-up-alt10
\end{verbatim}

Также с помощью chkconfig --list необходимо удостовериться, что сервис
vz не будет запущен после перезагрузки, и затем перезагрузиться.

После перезагрузки необходимо переместить файлы OpenVZ в каталог /d0,
куда уже должно быть смонтировано устройство /dev/drbd0, а на старом
месте создать символьные ссылки:

\begin{verbatim}
[root@m1 ~]# mkdir /d0/vz
[root@m1 ~]# mkdir /d0/vz/etc
[root@m1 ~]# mkdir /d0/vz/etc/sysconfig
[root@m1 ~]# mkdir /d0/vz/var
[root@m1 ~]# mkdir /d0/vz/var/lib
[root@m1 ~]# cp -r /etc/vz /d0/vz/etc
[root@m1 ~]# cp -r /etc/sysconfig/vz-scripts /d0/vz/etc/sysconfig
[root@m1 ~]# cp -r /var/lib/vz /d0/vz/var/lib
[root@m1 ~]# cp -r /var/lib/vzquota /d0/vz/var/lib
[root@m1 ~]# rm -rf /etc/vz
[root@m1 ~]# rm -rf /etc/sysconfig/vz-scripts
[root@m1 ~]# rm -rf /var/lib/vz
[root@m1 ~]# rm -rf /var/lib/vzquota
[root@m1 ~]# ln -s /d0/vz/etc/vz /etc/vz
[root@m1 ~]# ln -s /d0/vz/etc/sysconfig/vz-scripts /etc/sysconfig/vz-scripts
[root@m1 ~]# ln -s /d0/vz/var/lib/vz /var/lib/vz
[root@m1 ~]# ln -s /d0/vz/var/lib/vzquota /var/lib/vzquota
\end{verbatim}

После остановки сервиса heartbeat на узле m1 и монтирования
drbd-устройства на узле m2 на нем необходимо аналогичным образом
удалить каталоги OpenVZ и создать вместо них ссылки на /d0/vz.

После того, как OpenVZ перенесен на drbd-раздел, необходимо указать
сервису heartbeat, что сервис vz должен работать на узле m1, для чего
отредактировать файл /etc/ha.d/haresources на обоих узлах:

\begin{verbatim}
m1.mydomain.com drbddisk Filesystem::/dev/drbd0::/d0::ext3 vz
\end{verbatim}

После перезапуска heartbeat на обоих узлах необходимо смоделировать отказ
узла m1 и убедиться в том, что сервис vz запускается на узле m2.

\section{Строим первый виртуальный сервер}

%%FIXME(LATER): ссылки на документацию подлежат замене на ссылки на раздел
%%той же книги
Теперь мы можем забыть о том, что OpenVZ работает в кластере, и
конфигурировать его, опираясь на оригинальную дукументацию с
\url{http://openvz.org/documentation} и выжимку из нее, ориентированную на
ALT Linux и доступную здесь "---
\url{http://www.freesource.info/wiki/AltLinux/Dokumentacija/OpenVZ}.

Существуют уже готовые шаблоны виртуальных серверов, построенные на
основе некоторых общедоступных дистрибутивов Linux: CentOS, Debian,
Fedora Core, Gentoo, Mandriva, openSUSE и ALT Linux. Коммерческие RHEL
и SUSE SLES в этом списке отсутствуют.

%%FIXME: необходимо обоснование, зачем менять шаблон
%%FIXME: не следует ли использовать вместо spt spt3 ?
Но мы построим шаблон для виртуального сервера на основе ALT Linux
самостоятельно с помощью spt. Если spt уже установлен (его удобнее
держать на выделенном физическом либо виртуальном сборочном сервере),
то можно использовать содержимое каталога /usr/share/spt/profile-ovz/
как образец для создания образа, который затем послужит нам шаблоном
для создания виртуального сервера. Нет никаких препятствий к тому,
чтобы использовать этот образец как есть, но мне показалось более
правильным скопировать его в ~/ovz и изменить список пакетов в
шаблоне, отредактировав файл ~/ovz/packages/main так:

\begin{verbatim}
basesystem
passwd
apt
apt-conf-sisyphus
etcnet
glibc
sysklogd
mc
openssh-server
openssh-clients
\end{verbatim}

%%FIXME: при описании общего случая нужно ли про пакеты из локального
%%apt-репозитория? выкинуть эту часть?
Также мне показалось разумным изменить конфигурацию apt по умолчанию,
чтобы сразу иметь возможность устанавливать пакеты из моего локального
репозитория. Для этого я создал файл ~/ovz/postinstall/setup.d/01apt c
таким содержимым:

\begin{verbatim}
cat >> /etc/apt/sources.list.d/sisyphus.local.list <<END

# Local Sisyphus

rpm [alt] ftp://192.168.46.1/distrib/linux/alt-linux-sisyphus i586 classic
rpm-src [alt] ftp://192.168.46.1/distrib/linux/alt-linux-sisyphus i586 classic
rpm [alt] ftp://192.168.46.1/distrib/linux/alt-linux-sisyphus noarch classic
rpm-src [alt] ftp://192.168.46.1/distrib/linux/alt-linux-sisyphus noarch classic

END
\end{verbatim}

Затем я выполнил команду:

\begin{verbatim}
spt -v --noiso --image-type=tgz --maketty ~/ovz/
\end{verbatim}

и получил файл ~/ovz/out/altlinux, который можно использовать как
шаблон виртуального сервера для OpenVZ. Теперь файл ~/ovz/out/altlinux
необходимо скопировать на ведущий узел кластера с именем
/var/lib/vz/template/cache/altlinux-sisyphus.tar.gz и выполнить
следующее:

\begin{verbatim}
[root@m1 ~]# vzctl create 101 --ostemplate altlinux-sisyphus --config vps.basic
Creating VE private area: /var/lib/vz/private/101
Performing postcreate actions
VE private area was created
[root@m1 ~]# vzctl set 101 --name router --save
Name router assigned
Saved parameters for VE 101
[root@m1 ~]# vzctl set router --onboot yes --save
Saved parameters for VE 101
[root@m1 ~]# vzctl set router --hostname router.mydomain.com --save
Set hostname: router.mydomain.com
Saved parameters for VE 101
\end{verbatim}

Таким образом, мы создали виртуальный сервер, задали для него имя,
указали, что он должен загружаться при старте OpenVZ, и присвоили ему
FQDN. Везде мы использовали ключ -save, чтобы сохранить внесенные
изменения после перезапуска виртуального сервера.

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

\begin{verbatim}
[root@m1 ~]# vzctl set router --netdev_add eth1 --save
Saved parameters for VE 101
\end{verbatim}

Теперь можно запустить виртуальный сервер, войти в него,
сконфигурировать сетевой интерфейс eth1 и проверить доступность
физических серверов из виртуального и наоборот:

\begin{verbatim}
[root@m1 ~]# vzctl start router                         
Starting VE ...
VE is mounted
Setting CPU units: 1000
Set hostname: router.mydomain.com
VE start in progress...
[root@m1 ~]# vzctl enter router
entered into VE 101
[root@router /]# ip address add 192.168.46.200/24 dev eth1
[root@router /]# ip link set eth1 up
[root@router /]# 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=64 time=5.37 ms

--- 192.168.46.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 5.376/5.376/5.376/0.000 ms
\end{verbatim}

Команды внутри виртуального сервера можно также выполнять 
не переключаясь <<вовнутрь>> виртуального сервера (vzctl enter),
с помощью vzctl exec:

\begin{verbatim}
[root@m1 ~]# vzctl exec router 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=64 time=6.34 ms
\end{verbatim}

Для сохранения сетевых настроек после перезагрузки в ALT Linux 
используется система etcnet, о которой много
написано на \url{http://etcnet.org/} и
\url{http://wiki.sisyphus.ru/admin/etcnet}:

\begin{verbatim}
[root@router /]# mkdir /etc/net/ifaces/eth1
[root@router /]# echo 192.168.46.200/24 > /etc/net/ifaces/eth1/ipv4address
[root@router /]# echo default via 192.168.46.1 dev eth1 > /etc/net/ifaces/eth1/ipv4route
[root@router /]# echo "BOOTPROTO=static
> ONBOOT=yes
> TYPE=eth" > /etc/net/ifaces/eth1/options
[root@router /]# service network restart
Computing interface groups: ... 3 interfaces found
Processing /etc/net/vlantab: empty.
Stopping group 1/realphys (1 interfaces)
        Stopping eth1: ..OK
Stopping group 0/virtual (2 interfaces)
        Stopping lo: .OK
        Stopping venet0: .OK
error: setting key "net.ipv4.icmp_echo_ignore_broadcasts": Operation not permitted
error: setting key "net.ipv4.tcp_syncookies": Operation not permitted
error: setting key "net.ipv4.tcp_timestamps": Operation not permitted
Computing interface groups: ... 3 interfaces found
Starting group 0/virtual (2 interfaces)
        Starting lo: ....OK
        Starting venet0: ......OK
Starting group 1/realphys (1 interfaces)
        Starting eth1: ......OK
Processing /etc/net/vlantab: empty.
\end{verbatim}

Сообщения <<Operation not permitted>> связаны с ограничениями для
виртуального сервера, определенными по умолчанию, и в нашем случае на
функционировании сети отрицательно не сказываются. Можно
закомментировать соответствующие строки в файле /etc/net/sysctl.conf,
чтобы эти сообщения больше не появлялись.

Созданный нами виртуальный сервер будет использовать сетевой интерфейс
eth1 того узла, на котором он в данный момент работает, поэтому в
случае отказа узла m1 сервер переместится на узел m2 и будет доступен
там по тому же самому адресу. Можно смоделировать эту ситуацию и
увидеть с внешнего физического сервера следующее:

\begin{verbatim}
$ ping 192.168.46.200
PING 192.168.46.200 (192.168.46.200) 56(84) bytes of data.
64 bytes from 192.168.46.200: icmp_seq=1 ttl=64 time=0.549 ms
...
From 192.168.46.1 icmp_seq=83 Destination Host Unreachable
...
64 bytes from 192.168.46.200: 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
\end{verbatim}

\section{Строим виртуальную сеть}

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

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

\begin{itemize}
\item \emph{venet} "--- соединения типа точка"--~точка между физическим сервером
  и виртуальным, которые создаются автоматически для каждого
  виртуального сервера и конфигурируются c помощью vzctl. Они не имеют
  MAC-адресов, не поддерживают широковещательные сообщения (broadcast), на них не работают снифферы
  и средства учета трафика, использующие libicap (например, tcpdump),
  на них нельзя строить бриджи.
\item \emph{veth} "--- соединения типа точка"--~точка между физическим сервером и
  виртуальным, которые нужно создавать и конфигурировать вручную
  средствами того дистрибутива Linux, который работает на физическом и
  виртуальном сервере. Они лишены ограничений venet (которые можно
  рассматривать как достоинства с точки зрения безопасности и
  эффективности) и выглядят как полноценные ethernet-интерфейсы.
\end{itemize}

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

\begin{figure}[h]
  \centering
  \includegraphics[width=\textwidth]{veth}
  \caption{Схема соединения виртуальных серверов}
  \label{fig:veth}
\end{figure}

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

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

\begin{verbatim}
[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...
\end{verbatim}

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

\begin{verbatim}
[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
\end{verbatim}

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

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

\begin{verbatim}
[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
\end{verbatim}

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

\begin{verbatim}
[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
\end{verbatim}

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

\begin{verbatim}
[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
\end{verbatim}

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

\begin{verbatim}
[root@m1 ~]# vzctl exec router sysctl -w net.ipv4.ip_forward=1
\end{verbatim}

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

\begin{verbatim}
[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
\end{verbatim}

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

\begin{verbatim}
[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
\end{verbatim}

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

\begin{verbatim}
brctl addif br0 ${dev}
\end{verbatim}

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

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

\begin{verbatim}
[root@m1 ~]# mkdir /etc/net/ifaces/br0
[root@m1 ~]# echo TYPE=bri > /etc/net/ifaces/br0/options
\end{verbatim}

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

\begin{verbatim}
[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 
\end{verbatim}

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

\begin{verbatim}
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 
}
\end{verbatim}

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

\begin{verbatim}
[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
\end{verbatim}

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

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

\begin{verbatim}
$ 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
\end{verbatim}

\section{Что дальше}

Итак, мы выполнили базовую настройку двухузлового кластера, подняли
систему виртуализации OpenVZ, кластеризовали ее, а затем настроили
несколько виртуальных серверов, виртуальную сеть между ними, и связали
ее с внешней физической сетью. При этом мы покзаали преимущества такого
способа построения систем как с точки зрения надежности, так и с точки
зрения снижения затрат на оборудование и его обслуживание.  Теперь
можно углубляться в детали: садиться и вдумчиво читать OpenVZ Users
Guide "--- \url{http://download.openvz.org/doc/OpenVZ-Users-Guide.pdf}\footnote{Кстати,
перевод этого руководства доступен на
\url{http://www.opennet.ru/docs/RUS/virtuozzo/}, однако он довольно плохого
качества "--- лучше все-таки читать оригинал}.

Для полноценного использования OpenVZ нам потребуется настроить:

\begin{itemize}
\item квоты на процессорное время для виртуальных серверов;
\item квоты потребление системных ресурсов виртуальными серверами
  (количество процессов, количество сокетов, объем виртуальной памяти,
  различных буферов и т.\,д.);
\item дисковые квоты;
\item доступ к физическим устройствам и файловым системам физического
  сервера, если в этом есть необходимость.
\end{itemize}

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

И наконец, после более тщательной настройки OpenVZ можно переходить к
настройке самих виртуальных серверов (либо к миграции существующих
физических в OpenVZ) "--- но те, кто все-таки дочитал статью до конца,
думаю, сумеют это сделать.

\section{Ссылки и источники}

\begin{itemize}
\item \url{http://linux-ha.org}
\item \url{http://drbd.org}
\item \url{http://openvz.org}
\item \url{http://altlinux.ru}
\item \url{http://sisyphus.ru}
\item \url{http://freesource.info}
\end{itemize}

\end{document}

%%% Local Variables: 
%%% mode: latex
%%% TeX-master: t
%%% End: 
