среда, 10 октября 2018 г.

Бездисковые станции на слоистой файловой системе OverlayFS

Идея реализации бездисковых станций на базе OverlayFS была не моя, а моих коллег по работе.
Вероятно, уже многие рассматривали варианты как сделать бездисковые станции на базе слоситых файловых систем. Например на базе AuFS.
Но это было не просто  - т.к. много работы по установке и настройке модулей ядра, которых нет по умолчанию.
А зачем вообще такие бездисковые станции? спросите вы.

Область применения:


Сразу скажу это решение для Linux систем.
А с другими я особо и не работаю :))

1. Самое понятное - Класс в учебном заведении.
на всех машинах должны стоять одинаковые системы, они должны обновляться одинаково. На них должны стоять одинаковые наборы программ с одинаковыми настройками и связями между собой и с принтерами например.
Ну а наработанные данные пользователя должны тоже сохраниться и они будут лежать в отдельных папках на сервере.
2. Разные операционисты в банках, в торговых заведениях. Если например все они должны работать в общем пространстве программ и все программы должны быть настроены одинаково на всех машинах.
3. Различные пультовые и компьютеры управления какими-нибудь системами.
Суть в том что в разных местах сложных больших по площади установок могут стоять бездисковые машины для контроля и управления этой установкой и при запуске эти машины будут работать совершенно одинаково и показывать одни и теже приложения которые настраиваются один раз для всех машин.

Ну и можно еще много всего перечислять.

Идея:


Есть один большой и мощный NFS сервер, который для каждого клиента (бездисковой машины) имеет свой ресурс выдачи.
Этот ресурс формируется из двух слоев, можно и больше если в этом есть производственная необходимость (позже расскажу какая может быть потребность).
Нижний или Базовый слой содержит всю основную систему со всеми пакетами программ и настройками драйверов для внешних и устройств. В этом плане все клиентские машины должны быть унифицированы и иметь одинаковые подключенные внешние и внутренние устройства. Ну или придется устанавливать драйвера для всех имеющихся на клиентах устройств.
На верхнем слое будет лежать только то, что наработал пользователь. Здесь имеется ввиду логи его действий, временные и рабочие каталоги или настройки программ для этой машины.
А вот данные пользователя и его домашний каталог можно держать совершенно в отдельном каталоге. Это можно было делать и без использования слоистой файловой системы.
В итоге мы получаем :
1. настроенная  система в одном экземпляре
2. пользовательские папки  - на каждого пользователя по одной папке, независимо от количества бездисковых машин. На всех них он может работать с одинаковыми программами и со своими данными
3. в верхних слоях для каждой машины лежат временные папки и папки логов. Эти папки кстати можно периодически чистить без особого урона.

За счет этого объединения слоев мы получаем значительную экономию места на nfs сервере
и унификацию всех настроек программ на всех машинах. Скорость внесения изменений в базовый слой - мгновенно!

Особенности:


В общем то все зависит как будут организованы слои.
Самый простой способ использования это два слоя базовый и верхний слой где лежат только пользовательские настройки. Пользователь не может устанавливать в этом случае программы и добавлять устройства в машину.
Можно организовать трехслойную структуру, в этом случае у администора конкретной машины будет возможность устанавливать пакеты в средний слой, который базируется на общий для всех машин. А пользователи по-прежнему будут сохранять свои настройки в верхний слой.
Но в любом случае нельзя исправлять или добавлять файлы в верхние слои если слои не смонтированы в overlayfs. Дело в том что если слои размонтировать , то в папках слоев будут лежать файлы, которые привязаны к файлам более нижнего слоя и поэтому если файл более верхнего слоя изменился а система overlayfs об этом не знает , это может привести к не предсказуемым результатам.
Также есть особенность в пересборке ядра. Ядро нужно будет пересобирать только в базовом слое, то есть нужно загрузить машину с этим слоем, собрать ядро с указанием аргументов с тем что при загрузке ядро подмонтирует рутовую директорию коорая находится  на nfs ресурсе. Затем нужно пересобрать iniramfs и скопировать ядро и initramfs на tftp сервер, для того чтобы следующая загрузка машины произошла уже с новым ядром.


Реализация:


Итак рассмотрим реализация системы с двумя слоями.
В качестве сервера будет выступать система Centos 7.4
Поддержка модуля  overlayfs  в ядре появилась только с версии 4.16. Поэтому обновляем ядро до 4.16 или выше.
Устанавливаем пакет с репозиторием elrepo:


# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
# rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm


Оставляем только ядерные ветки репозитория:


# yum --disablerepo="*" --enablerepo="elrepo-kernel" list available


Устанавливаем последнее ядро:


# yum --enablerepo=elrepo-kernel install kernel-ml


После этого перегружаем машину и смотрим какое ядро появилось в папке /boot

После этого нужно убедиться что в grub.conf - е указана правильная строка загрузки по умолчанию.
То есть смотрим файл /etc/default/grub и убеждаемся что там написано: 
GRUB_DEFAULT=0

Обычно после установки нового ядра оно прописывается в самый первый (нулевой) уровень загрузки.   Но если оно установилось в другой уровень то пропишите этот уровень в GRUB_DEFAULT

Создадим структуру папок для слоистой файловой системы:

Давайте подключим отдельный диск или раздел диска и подмонтируем его в папку /DATA
В ней создадим базовый слой:

# mkdir /DATA/base/centos7.4

можно в дальнейшем и базовые слои других операционных систем создать.

Создаем место для верхних слоев :


# mkdir /DATA/hosts/host1
# mkdir /DATA/hosts/host2
# mkdir /DATA/hosts/host3


И еще нужно  создать папки для временных рабочих файлов слоистой файловой системы:


# mkdir /DATA/works/host1
# mkdir /DATA/works/host2
# mkdir /DATA/works/host3


Устанавливаем систему в базовый слой :

# yum groups -y install "Server with GUI" --releasever=7 --installroot=/DATA/base/centos7.4

Здесь мы ставим например сервер с GUI.


Теперь устанавливаем nfs сервер:


# yum install nfs-utils


Создаем папки для выдачи соединенных слоев (соединять будем позже):


# mkdir  /export/hosts/host1
# mkdir  /export/hosts/host2
# mkdir  /export/hosts/host3


устанавливаем права на папки:


# chmod -R 755 /export/hosts/host1
# chown nfsnobody:nfsnobody  /export/hosts/host1


все то же самое для остальных папок.

Настраиваем файерволл:

firewall-cmd --permanent --zone=public --add-service=nfs
firewall-cmd --permanent --zone=public --add-service=mountd
firewall-cmd --permanent --zone=public --add-service=rpc-bind
firewall-cmd --reload


Запускаем все сервисы:


systemctl enable rpcbind
systemctl enable nfs-server
systemctl enable nfs-lock
systemctl enable nfs-idmap
systemctl start rpcbind
systemctl start nfs-server
systemctl start nfs-lock
systemctl start nfs-idmap


создаем файл /etc/exports и пишем туда примерно следующее:


/export/hosts/host1   192.168.0.1(rw,sync,no_root_squash,no_all_squash)
/export/hosts/host2   192.168.0.2(rw,sync,no_root_squash,no_all_squash)
/export/hosts/host3   192.168.0.3(rw,sync,no_root_squash,no_all_squash)


Теперь настроим монтирование слоев в overlayfs:

Во первых нужно настроить модуль ядра overlayfs, включаем возможность експортировать слои через NFS:


В файле /sys/module/overlay/parameters/nfs_export
 пишем YВ /sys/module/overlay/parameters/redirect_always_follow тоже пишем Y


Теперь подготовим файл /etc/fstab. Пишем для каждой машины:

overlay /exports/hosts/host1    overlay noauto,x-systemd.requires=/DATA,x-systemd.automount,lowerdir=/DATA/base/centos7.4/,upperdir=/DATA/hosts/host1/,workdir=/DATA/works/host1/,index=on 0 0

Здесь нужно отметить, что используется опция ядра index=on это важно для работы overlayfs через nfs. Иначе смонтированные по nfs верхние слои не будут видны на клиенте nfs.
А так же тут указаны важные опции noauto, x-systemd.requires и x-systemd.automount .
Это нужно для того чтобы монтирование папок происходило последовательно друг за другом.
То есть сначала монтируется диск с данными nfs ресурсов /DATA, а затем монтируются слои overlayfs. Чтобы такое произошло, нужно отключить автомонтирование слоев overlayfs. Кроме того монтирование будет производиться спомощью системы systemd.automount.

После перезагрузки сервера слои смонтируются и будут доступны по nfs.

Теперь нужно настроить выдачу загрузочного образа ядра для хостов через tftp сервер:


# yum install tftp-server


Меняем настройки по-умолчанию:

в файле /etc/xinetd.d/tftp меняем параметр disable  на  'no'

перегружаем xinetd:

# service xinetd restart

Устанавливаем основные загрузочные образы:

# yum install syslinux

Копируем нужные файлы в корень  (/var/lib/tftpboot/) tftp сервера:

# cp /usr/lib/syslinux/pxelinux.0 /var/lib/tftpboot
# cp /usr/lib/syslinux/menu.c32 /var/lib/tftpboot
# cp /usr/lib/syslinux/memdisk /var/lib/tftpboot
# cp /usr/lib/syslinux/mboot.c32 /var/lib/tftpboot
# cp /usr/lib/syslinux/chain.c32 /var/lib/tftpboot

Создаем директорию для загрузочного меню:

# mkdir /var/lib/tftpboot/pxelinux.cfg

Тут же создаем директорию для загрузочного образа базовой системы бездисковых станций:

# mkdir /var/lib/tftpboot/base-centos7.4

Туда же копируем vmlinuz и initrd.img.

в файле  /var/lib/tftpboot/pxelinux.cfg/default пишем примерно следующее:

default vesamenu.c32
prompt 0
timeout 300
ONTIMEOUT local

MENU TITLE PXE Menu
Local HHD0 Boot

LABEL Centos
    MENU LABEL Centos Install
        KERNEL base-centos7.4/vmlinuz
        APPEND initrd=base-centos7.4/initrd.img root=/dev/nfs rw nfsroot=nfs-server:/DATA/base/centos7.4 ip=eth0:dhcp selinux=0 ipv6.disable=1 console=tty0 plymouth.enable=0
       

Это настройки загрузки любой машины которую DHCP сервер перенаправил для загрузки ядра по сети на этот сервер. В данном случае загрузится базовый слой.

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

default vesamenu.c32
prompt 0
timeout 60
ONTIMEOUT Main

MENU TITLE PXE Boot Host1

LABEL Main
    MENU LABEL host1
        KERNEL base-centos7.4/vmlinus
        APPEND initrd=base-centos7.4/initrd.img root=/dev/nfs rw nfsroot=nfs-server:/exports/hosts/host1 ip=eth0:dhcp selinux=0 ipv6.disable=1 console=tty0 plymouth.enable=0
LABEL base-pult
    MENU LABEL base-centos7.4
        KERNEL base-centos7.4/vmlinuz
        APPEND initrd=base-centos7.4/initrd.img root=/dev/nfs rw nfsroot=nfs-server:/DATA/base/centos7.4  ip=eth0:dhcp ipv6.disable=1 console=tty0 plymouth.enable=0


Теперь перегружаем сервисы :


# service xinetd restart
# service tftpbootd restart


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

Прописываем в /etc/dhcp/dhcpd.conf

next-server nfs-server;
filename "pxelinux.0";

в секции описывающей сеть или сегмент сети.

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

Про особенности работы в слоистой файловой системе было написано выше.


Добавление нового хоста в слоистую файловую систему


Как можно понять из описанного выше добавление нового бездискового хоста потребует внести изменения как минимм в трех местах на nfs сервере:

1. Cоздаем структуру в /DATA/hosts/newhost и /DATA/works/newhost
2. Делаем соответствующие записи в /etc/fstab
3. Создаем файл соответствующий IP адресу новой машины в /var/lib/tft[pboot/pxelinux.cfg/
4. Перегружаем все нужные сервисы.

Ну конечно же можно просто написать скрипт для автоматизации всего этого дела:

#!/usr/bin/python
import os, sys
import socket

ROOT_PATH = '/DATA'
EXPORT_PATH = '/exports'
BASE = 'base-centos7.4'
WORK_NET = '192.168.0.0/24'
EXPORT_CONF_FILE = '/etc/exports'
ROOT_TFTP = '/var/lib/tftpboot/pxelinux.cfg/'
IP_NFS_SERVER = '192.168.0.254'
LOWER_DIR = os.path.join(ROOT_PATH,'base',BASE)
BASE_WORK_DIR = os.path.join(ROOT_PATH,'works')


def ip_to_hex(ip):
    out = ''
    hex_symbol = ''
    for i in ip.split('.'):
        hex_symbol = hex(int(i)).replace('0x','').upper()
        if len(hex_symbol) == 1:
            hex_symbol = '0'+hex_symbol
        out += hex_symbol
    return out


def make_export_conf(EXPORT_CONF_FILE, export_row):
    with open(EXPORT_CONF_FILE,'r') as expfile:
        text_file  = expfile.read()
    if text_file.find(export_row) == -1:
        with open(EXPORT_CONF_FILE,'a') as expfile:
            expfile.write(export_row)
    return


def make_tftp_file(hex_name,EXPORT_DIR,ROOT_TFTP,if_name):
    conf_file_text = ''
    dict_replace = {'hostname':hostname,
                     'export_dir':EXPORT_DIR,
                     'ip_nfs_server':IP_NFS_SERVER,
                     'if_name':if_name}
    text_tpl = open('tftpboot.tpl','r').read()
    text_next = text_tpl
    for k in dict_replace.keys():
        conf_file_text = text_next.replace('{'+k+'}',dict_replace[k])
        text_next = conf_file_text
    print conf_file_text
    conf_file = open(ROOT_TFTP+'/'+hex_name,'w')
    conf_file.write(conf_file_text)
    return


def make_fstab(mount_row):
    with open('/etc/fstab','r') as expfile:
        text_file  = expfile.read()
    if text_file.find(mount_row) == -1:
        with open('/etc/fstab','a') as expfile:
            expfile.write(mount_row)
        os.system('umount '+EXPORT_DIR)
        print 'mount -t overlay overlay -o lowerdir='+LOWER_DIR\
                  +'/,upperdir='+UPPER_DIR+'/,workdir='+WORK_HOST_DIR+'/,index=on '+EXPORT_DIR
        os.system('mount -t overlay overlay -o lowerdir='+LOWER_DIR\
                  +'/,upperdir='+UPPER_DIR+'/,workdir='+WORK_HOST_DIR+'/,index=on '+EXPORT_DIR)
    return


count_args = len(sys.argv)
name_interface = 'eth0'
ip = None
print count_args
if count_args < 2:
    print "hostname is needed. python create_layer.py {hostname} "
    sys.exit(1)
# get name of network interface of machine
if count_args >= 2:
    hostname = sys.argv[1] #get hostname
if count_args >= 3:
    ip = sys.argv[2] #get ip address
if count_args == 4:
    name_interface = sys.argv[3] #get name ethernet

if not ip:
    IPHOLDER = socket.gethostbyname(hostname)
else:
    IPHOLDER = ip
hex_name = ip_to_hex(IPHOLDER)
print hostname
print ip
print name_interface
print hex_name



Ну и вызов этого скрипта: python create_layer.py {hostname}
Необходим хотябы один аргумент для этого скрипта - это имя бездисковой станции который прописан в ДНС сервере и для которого IP адрес соответствует выдамаемомы dhcp сервером.
Можно так же указать еще два аргумента это ip адрес (на случай если нет записи в ДНС) и имя интерфейса, если хочется указать какой именно интерфейс будет подключаться к сети и к nfs серверу.

Вот так можно все это создавать и автоматизировать.

Все успехов и удачи!