Автоматизация ( Автоматизация процедур сборки и разборки распределённой файловой системы. )

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".

Скрипты, конфигурационные и журнальные файлы нашей самодельной сетевой файловой системы будут располагаться одинаково на всех серверах, как ведущих, так и ведомых:

# mkdir -p /usr/local/etc/default/ /usr/local/etc/storage/fnc.d/ /usr/local/etc/storage/cnf.d/ /var/log/storage

Прежде всего создаём конфигурационный файл с параметрами для скрипта автоматизации:

# vi /usr/local/etc/default/storage-custom

# Default configuration file for /etc/init.d/storage-control

# Адрес директории динамически включаемых в тело скрипта управления описаний функций
FNCDIR="/usr/local/etc/storage/fnc.d"

# Адрес директории индивидуальных конфигураций сервисов хранилища
CNFDIR="/usr/local/etc/storage/cnf.d"

# Адрес директории файлов блокировок сервисов хранилища
LOCK="/tmp/storage/lck"

# Адрес файла журнала
LOG="/var/log/storage/default.log"

EMAIL=`cat /usr/local/etc/storage/email >/dev/null 2>&1`


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

# vi /etc/init.d/storage-control && chmod ugo+rx /etc/init.d/storage-control

#!/bin/bash

### BEGIN INIT INFO
# Provides:          storage-control
# Required-Start:    $local_fs $syslog $network
# Required-Stop:     $local_fs $syslog $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Script management of storage
# Description:       Script management of storage
### END INIT INFO

# Подключаем первичный конфигурационный файл
source "/usr/local/etc/default/storage-custom"
[ ${?} -ne 0 ] && { echo "Fatal error: missing default custom storage configuration file. Operation aborted."; exit 1; }

# Принимаем в переменные с "говорящими" именами входящие аргументы
INSTANCE=${0}
OPERATION=${1}
TARGET=${2}
DATE=`date +"%Y-%m-%d %H:%M:%S"`

# Перебираем в цикле все объекты (по маске) в целевой директории динамически подключаемых описаний функций
cd "${FNCDIR}"
for OBJECT in *\.fnc ; do
  # Включаем в тело скрипта текст обнаруженных фрагментов описаний функций
  [ -f "${FNCDIR}/${OBJECT}" ] && source "${FNCDIR}/${OBJECT}"
done

# Создаём директорию для файлов блокировки
mkdir -p "${LOCK}"

# Определяем функцию предварительной инициализации конфигурационных файлов хранилища
function start-init() {

  # Создаём временный файл журнала событий
  LOGT=$(mktemp /tmp/storage-start.log.XXXXXXXX)

  # Создаём временный файл глобальной конфигурации
  CNF=$(mktemp /tmp/storage.cnf.XXXXXXXX)

  cd "${CNFDIR}"
  for OBJECT in *\.cnf ; do
    # Включаем в тело временного глобального файла все обнаруженные фрагменты конфигураций
    [ -f "${CNFDIR}/${OBJECT}" ] && cat "${CNFDIR}/${OBJECT}" >> "${CNF}"
  done

  DATE=`date +"%Y-%m-%d %H:%M:%S"`
  start

  # Если запуск системы хранилища завершился неудачно, то отсылаем отчёт и "разбираем" его
  if [ "${?}" -ne "0" ] ; then
    send-report "Storage assembling error!" "$(cat ${LOGT})"
    return 1
  fi

  # Включаем временный журнал событий в основной и удаляем временный
  cat "${LOGT}" >> "${LOG}"
  rm --force "${LOGT}"

  # Удаляем временный файл глобальной конфигурации
  rm --force "${CNF}"

return ${?}
}

# Определяем функцию предварительной инициализации конфигурационных файлов хранилища
function stop-init() {

  # Создаём временный файл журнала событий
  LOGT=$(mktemp /tmp/storage-stop.log.XXXXXXXX)

  # Создаём временный файл глобальной конфигурации
  CNF=$(mktemp /tmp/storage.cnf.XXXXXXXX)

  cd "${CNFDIR}"
  for OBJECT in *\.cnf ; do
    # Включаем в тело временного глобального файла все обнаруженные фрагменты конфигураций
    [ -f "${CNFDIR}/${OBJECT}" ] && cat "${CNFDIR}/${OBJECT}" >> "${CNF}"
  done

  DATE=`date +"%Y-%m-%d %H:%M:%S"`
  stop

  # Если остановка системы хранилища завершилась неудачно, то отсылаем отчёт
  if [ "${?}" -ne "0" ] ; then
    send-report "Storage disassembling error!" "$(cat ${LOGT})"
  fi

  # Включаем временный журнал событий в основной и удаляем временный
  cat "${LOGT}" >> "${LOG}"
  rm --force "${LOGT}"

  # Удаляем временный файл глобальной конфигурации
  rm --force "${CNF}"

return ${?}
}

# Описываем функцию формирования опций хранилища и непосредственного её запуска
function start() {

  echo | tee -a "${LOGT}"
  echo "${DATE}: Procedure is running store is initialized." | tee -a "${LOGT}"
  echo | tee -a "${LOGT}"

  # Сборка массивов
  declare -f start-mdadm >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция сборки RAID." | tee -a "${LOGT}"
  else
    start-mdadm
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Assembly arrays failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Активация групп томов LVM
  declare -f start-lvm >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция сборки LVM." | tee -a "${LOGT}"
  else
    start-lvm
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Activating LVM volume groups failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Создание символической ссылки на заданный раздел целевого диска
  declare -f make-symlink >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция создания символической ссылки на раздел целевого диска." | tee -a "${LOGT}"
  else
    make-symlink
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Create a symbolic link with error. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Монтирование локальных файловых систем хранилища
  declare -f start-fs >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция монтирования локальных файловых систем хранилища." | tee -a "${LOGT}"
  else
    start-fs
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Mounting file systems store with error. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Публикация файловых систем хранилища посредством NFS
  declare -f start-nfs-export >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция публикации файловых систем посредством NFS." | tee -a "${LOGT}"
  else
    start-nfs-export
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Publication of file systems via NFS storage failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Сборка файловых систем хранилища из ресурсов NFS
  declare -f start-nfs-import >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция сборки файловых систем из ресурсов NFS." | tee -a "${LOGT}"
  else

    # Учитывая то, что нам неизвестно, когда станут доступными все импортируемые ресурсы, закручиваем бесконечный цикл в ожидании полностью безошибочного импорта всех ресурсов
    STERR=""
    while [ "${STERR}" != "0" ] ; do

      # Ждём некоторое время, перед повторением операции (кроме первой итерации)
      [ ! -z "${STERR}" ] && sleep 10

      start-nfs-import
      STERR="${?}"

    done

    # Проверяем успешность завершения функции импортирования
    if [ "${STERR}" -ne "0" ] ; then
      echo "${DATE}: The assembly of the storage file system NFS resource failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi

  fi

  # Сборка файловых систем в единую точку монтирования
  declare -f start-mhddfs >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция сборки файловых систем в единой точке монтирования." | tee -a "${LOGT}"
  else
    start-mhddfs
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Assembly file systems into a single mount point failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

return ${?}
}


# Определяем функцию остановки хранилища
function stop() {

  echo | tee -a "${LOGT}"
  echo "${DATE}: Running stop procedure storage." | tee -a "${LOGT}"
  echo | tee -a "${LOGT}"

  # Разборка файловых систем, смонтированных в единую точку монтирования
  declare -f stop-mhddfs >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция разборки файловых систем в единой точке монтирования." | tee -a "${LOGT}"
  else
    stop-mhddfs
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Disassembly of file systems mounted in a single mount point failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Разборка файловых систем хранилища из ресурсов NFS
  declare -f stop-nfs-import >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция разборки файловых систем из ресурсов NFS." | tee -a "${LOGT}"
  else
    stop-nfs-import
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Disassembly of the storage file system NFS resource failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Отключение публикации файловых систем хранилища посредством NFS
  declare -f stop-nfs-export >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция публикации файловых систем посредством NFS." | tee -a "${LOGT}"
  else
    stop-nfs-export
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Disabling Publishing file systems via NFS storage failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Демонтирование файловых систем хранилища
  declare -f stop-fs >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция демонтирования локальных файловых систем хранилища." | tee -a "${LOGT}"
  else
    stop-fs
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Unmounting file systems store with error. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Деактивация групп томов LVM
  declare -f stop-lvm >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция деактивации групп томов LVM хранилища." | tee -a "${LOGT}"
  else
    stop-lvm
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Deactivating LVM volume groups failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi

  # Остановка массивов
  declare -f stop-mdadm >/dev/null
  if [ ${?} -ne 0 ]; then
    echo "Notice. Не определена функция деактивации RAID." | tee -a "${LOGT}"
  else
    stop-mdadm
    if [ "${?}" -ne "0" ] ; then
      echo "${DATE}: Stop array failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi
  fi
return ${?}
}

# Блок выбора варианта исполнения скрипта
case "$1" in
  start)
    start-init

    # В случае ошибочного завершения сборки хранилища разбираем его
    if [ "${?}" -ne "0" ] ; then
      stop-init
    fi

  ;;
  stop)
    stop-init
  ;;
  check)

    echo | tee -a "${LOGT}"
    echo "${DATE}: Running the test procedure component." | tee -a "${LOGT}"
    echo | tee -a "${LOGT}"

    check-mdadm
    check-lvm
    check-fs
    check-nfs-export
    check-nfs-import
    check-mhddfs
  ;;
  *)
    echo "Usage $0 {start|stop|check}" >&2
    exit 1
  ;;
esac

exit $?

Прописываем наш скрипт для нужных уровней исполнения в системе:

# update-rc.d storage-control start 10 2 3 4 5 . stop 10 0 1 6 .

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

Устанавливаем приложение ротации текстовых файлов с одновременным их сжатием:

# aptitude install logrotate gzip

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

# mkdir -p /etc/logrotate.d
# vi /etc/logrotate.d/storage

# шаблон указывающий объекты подлежащие "ротации"
/var/log/storage/*.log {
  # размер журнального файла, после которого он обрабатывается утилитой
  size 10M
  # отсутствие файла не вызывает ошибку
  missingok
  # количество хранимых отработанных резервных копий
  rotate 10
  # указание сжимать отрабатываемые резервные копии
  compress
  # указание не сжимать первую резервную копию, делать это при повторном проходе
  delaycompress
  # указание не отрабатывать пустые файлы
  notifempty
  # добавлять к наименованию файла резервной копии даты в формате "-YYYYMMDD"
  dateext
  # задать права доступа, владельца и группу создаваемого журнального файла
  create 640 root root
}

Проверяем корректность конфигурационного файла:

# logrotate -d /etc/logrotate.d/storage

Переход к описанию вспомогательных функций.

Misc ( Вспомогательные функции. )


OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".


Пример опций конфигурационного файла хранилища:

# vi /usr/local/etc/storage/cnf.d/misc.cnf

....
# Перечень электронных почтовых адресов операторов подсистемы виртуализации
email=admin@example.com operator@example.net
....


Фрагмент кода со вспомогательными функциями:

# vi /usr/local/etc/storage/fnc.d/misc.fnc

#!/bin/bash
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell)
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell)

# Определяем функцию уведомления администратора о нештатном развитии ситуации
# Example use: send-report "Subject" "Body"
function send-report() {
  local MSUBJECT=$1
  local MREPORT=$2

  # Получаем перечень электронных почтовых адресов операторов подсистемы виртуализации
  EMAIL=`grep --ignore-case "^email=" "${CNF}" | awk -F = '{print $2}'`

  # Посылаем электронное письмо
  echo -e "Content-Type: text/plain; charset="utf-8"\nSubject: Storage warning: ${HOSTNAME}: ${MSUBJECT}\n${DATE}.\nHost: ${HOSTNAME}.\nSubject: ${MSUBJECT}.\n${MREPORT}" | sendmail -F${HOSTNAME} ${EMAIL}
return $?
}

Переход к настройке зеркалирования блочных устройств в рамках локальной системы.

MDADM ( Зеркалирование блочных устройств в рамках локальной системы. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".

Сохранность данных на диске можно повысить за счёт использования второго диска, работающего как зеркало первого. Этот режим называется RAID первого уровня (Redundant Array of Inexpensive Disks - избыточный массив недорогих дисков). Коротко - RAID-1.

Отмечу, что большую надёжность (но не производительность) даёт зеркалирование по сети посредством DRDB.


Устанавливаем пакет приложений MDADM:

# aptitude install mdadm

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

Надо понимать, что есть модуль ядра "md", обеспечивающий работу с MD-устройствами, а есть пакет утилит "mdadm", который позволяет управлять и мониторить состояние массива, работающего на модуле "md". В частности, утилиты часто запускаются в фоновом режиме для контроля состояния массивов и уведомления по почте. Кстати, файл "mdadm.conf" предназначен именно для работы этого пакета и не нужен для работы модуля "md".

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

Прежде всего останавливаем автоматически запущенные при инсталляции сервисы:

# /etc/init.d/mdadm stop
# /etc/init.d/mdadm-raid stop

# vi /etc/default/mdadm

....
INITRDSTART='none'
AUTOSTART=false
AUTOCHECK=false
START_DAEMON=false
....

Следует иметь в виду, что простая правка параметров вышеприведённого конфигурационного файла не окажет ожидаемого влияния на работу всех компонентов подсистемы "mdadm"; у таковой имеется как набор утилит, которые могут читать информацию их этого конфигурационного файла, так и модули ядра, параметры к которым нужно сформировать отдельно. Кроме того, упомянутые модули ядра подсистемы mdadm загружаются ещё и в составе образа "initramfs" (специальным образом подготовленный "Initial RAM Disk" - мини операционная система, загружающая целевую) изменить конфигурацию которой можно только обновив её содержимое путём создания нового образа с учётом произведённых изменений. Применения изменений можно добиться перезапустив конфигурацию пакета и в процессе диалога подтвердить выбранные значения параметров; во время переконфигурирования пакета все необходимые изменения будут применены, так же и обновлением "initramfs":

# dpkg-reconfigure mdadm

....
Generating array device nodes... done.
update-initramfs: Generating /boot/initrd.img-2.6.32-5-amd64
W: mdadm: /etc/mdadm/mdadm.conf defines no arrays.

В качестве последнего шага можно запретить загрузчику операционной системы (в нашем случае GRUB2) инициировать модуль "md" при обнаружении блочных устройств с мета-информацией RAID. Надо понимать, что это не позволит загрузить систему с устройств, объединённых в RAID-массив, но в нашем случае этого и не требуется. Зато, таким образом мы полностью исключим на этапе загрузке операционной системы возможность преждевременной автоматической инициализации наших массивов (и загрузка системы ускоряется за счёт пропуска операций сканирования всех блочных устройств):

# vi /etc/default

....
GRUB_CMDLINE_LINUX_DEFAULT="quiet raid=noautodetect"
....

# update-grub

После всех вышеприведённых манипуляций можно считать, что мы искоренили автоматическую инициализацию mdadm на всех уровнях загрузки: на уровне GRUB2, на уровне "Initial RAM Disk" и на уровне "init.rc".

При загрузке операционной системы модуль DM (Device Manager) по умолчанию автоматически сканирует все доступные блочные устройства на предмет наличия в них метаданных, свидетельствующих о том, что они части массива. В случае обнаружения - массив собирается автоматически.

Если модуль не загрузился, это можно сделать вручную:

# modprobe md
# modprobe raid1

Проверяем, загрузились ли нужные нам модули:

# lsmod | grep raid

Попробуем подготовить HDD (или любые другие блочные устройства) для ввода их в RAID-1.

Проверяем, имеется ли на блочном устройстве "мета-данные":

# mdadm --misc --query /dev/sdb

/dev/sdb: is not an md array

Если на HDD имеются "мета-данные" от предыдущих экспериментов - затираем их (или делаем это из профилактических соображений):

# dd if=/dev/zero of=/dev/sdb bs=1M count=10

Даём команду операционной системе перечитать обновлённую разметку HDD:

# blockdev --rereadpt /dev/sdb

В команде "dd" выше, в особо злостных случаях, когда на диске предварительно порезвились и по натыкали массу вариантов разметки (вложенные контейнеры, многослойные загрузчики, сжатые файловые системы и тому подобное), параметр "count" можно установить в "1000", что бы перезаписать не один килобайт информации в начале дискового устройства, а мегабайт, например. 

Для начала нужно подготовить разделы, которые хочется включить в RAID, присвоив им тип "fd (Linux RAID Autodetect)". Это не обязательно, но желательно. Для создания "программного массива" желательно использовать не весь аппаратный диск целиком, а лишь логические диски (желательно - одинакового объёма, в противном случае размер массива будет рассчитываться исходя из размера диска с минимальным объёмом), понятно, что желательно не растягивать раздел на всё доступное пространство, а оставить пару-пятёрку-десятку секторов свободными на возможность люфта объёма дисков:

# fdisk -cu /dev/sdb

Программный RAID в "Linux" ведёт журнал изменений блоков данных "bitmap". Он довольно активно используется и в случае медленного HDD ещё более замедляет его работу. Этот журнал по умолчанию сохраняется внутри самого блочного устройства RAID-а, но (режим "internal"), но можно вынести в файл на отдельный диск (системный в моём случае). Журнал вещь вспомогательная, массив восстанавливается и без "bitmap"-а, но медленнее.

Создаём директорию для выноса в неё журналов MDADM:

# mkdir -p /mnt/journal/mdadm

Первичную сборку массива осуществим ориентируясь на символические имена дисков, что автоматически создаются в каталоге устройств /dev. После будем ориентироваться на идентификаторы UUID.

Содаём массив из двух дисков:

# mdadm --create /dev/md0 --level=raid1 --chunk=512 --bitmap=/mnt/journal/mdadm/md0-bitmap --raid-devices=2 /dev/sdb1 /dev/sdc1

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

# mdadm --create /dev/md1 --level=raid1 --chunk=512 --bitmap=/mnt/journal/mdadm/md1-bitmap --raid-devices=2 /dev/sdd1 missing

Подключаем диск в массив, на место зарезервированное для него ранее:

# mdadm /dev/md1 --add /dev/sde1

Если что не так - массив можно деактивировать:

# mdadm --misc --stop /dev/md0

Наблюдаем за процессом зеркалирования:

# cat /proc/mdstat

Personalities : [raid1]
md0 : active raid1 sdc1[1] sdb1[0]
  976759343 blocks super 1.2 [2/2] [UU]
  [>.....] resync =  0.9% (9579008/976759343) finish=177.6min speed=90732K/sec
  bitmap: 462/466 pages [1848KB], 1024KB chunk, file: /var/spool/mdadm/md0-bitmap
unused devices: <none>

Следующим набором команд можно выяснить состояние RAID-массивов и компонентов таковых:

# mdadm --detail --scan --verbose
# mdadm --misc --query --examine /dev/sdb1
# mdadm --misc --query --detail /dev/md0

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

# blkid

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

# cat /proc/mdstat

Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
md0 : active (auto-read-only) raid1 sdb1[0] sdc1[1]
  976759343 blocks super 1.2 [2/2] [UU]
  resync=PENDING

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

# echo idle > /sys/block/md0/md/sync_action

Важно иметь в виду, что при обнаружении любой (!) ошибки при работе с RAID-1, RAID-4, RAID-5, RAID-6 и RAID-10 драйвер "mdadm" отключает устройство (помечает его как сбойное флагом "faulty") и продолжает работу на оставшихся. Если есть запасное (spare) устройство, то оно вводится в эксплуатацию вместо отключённого.

Переход к настройке автоматизации зеркалирования блочных устройств в рамках локальной системы.

Автоматизация MDADM ( Автоматизация зеркалирования блочных устройств в рамках локальной системы. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".


Обновим конфигурационный файл с перечнем и описанием характеристик ресурсов, готовых к инициализации и использованию.

Содержимое конфигурационного файла сформируем из вывода команды сканирования текущего состояния подсистемы, сразу после первичной сборки или изменения таковой. Примерный вывод:

# mdadm --detail --scan

ARRAY /dev/md0 metadata=1.2 bitmap=/mnt/journal/mdadm/md0-bitmap name=node1:0 UUID=0b03c852:5d850f6e:7635830d:1f8e0e8d
ARRAY /dev/md1 metadata=1.2 bitmap=/mnt/journal/mdadm/md1-bitmap name=node1:1 UUID=3786fb6e:b5746f9e:3bb90781:59bdd3a8
ARRAY /dev/md2 metadata=1.2 bitmap=/mnt/journal/mdadm/md2-bitmap name=node1:2 UUID=067b70c7:c9fa66b7:7fbb9927:39f4a423

Также все необходимые данные можно вычленить из вывода утилиты "blkid":

# blkid

/dev/sdb1: UUID="ed0c7d76-5d03-a1c1-6bda-02e4ae6a6d0c" TYPE="linux_raid_member" 
/dev/sdc1: UUID="ed0c7d76-5d03-a1c1-6bda-02e4ae6a6d0c" TYPE="linux_raid_member" 
/dev/sdd1: UUID="29e32e2d-07b5-a4a0-f0dd-b4d123eb9099" TYPE="linux_raid_member" 
/dev/sde1: UUID="29e32e2d-07b5-a4a0-f0dd-b4d123eb9099" TYPE="linux_raid_member" 
/dev/sdf1: UUID="1d7b49bc-163f-96f5-c68d-f65279db4da0" TYPE="linux_raid_member" 
/dev/sdg1: UUID="1d7b49bc-163f-96f5-c68d-f65279db4da0" TYPE="linux_raid_member"

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


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

# vi /usr/local/etc/storage/cnf.d/mdadm.cnf

....
mdadm.module=raid1

mdadm.array=md0
mdadm.array.md0.uuid=ed0c7d76-5d03-a1c1-6bda-02e4ae6a6d0c
mdadm.array.md0.journal=/mnt/journal/mdadm/md0-bitmap

mdadm.array=md1
mdadm.array.md1.uuid=1d7b49bc-163f-96f5-c68d-f65279db4da0
mdadm.array.md1.journal=/mnt/journal/mdadm/md1-bitmap

mdadm.array=md2
mdadm.array.md2.uuid=29e32e2d-07b5-a4a0-f0dd-b4d123eb9099
mdadm.array.md2.journal=/mnt/journal/mdadm/md2-bitmap
....

Фрагмент кода автоматизации функционала MDADM подсистемы хранения:

# vi /usr/local/etc/storage/fnc.d/mdmdm.fnc

#!/bin/bash
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell)
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell)

# Функция запуска (сборки) вручную подготовленных массивов
function start-mdadm() {

  # Перебираем все строки именований необходимых модулей
  for MODULES in `grep --ignore-case "^mdadm.module=" "${CONF}" | uniq`; do

    MODULE=`echo ${MODULES} | awk -F = '{print $2}'`

    # Проверяем, загружен ли необходимый для работы с массивами модуль
    if [ "`lsmod | grep --count --ignore-case ${MODULE}`" -eq "0" ] ; then
      echo "${DATE}: Probe module ${MODULE} load..." >> ${LOG}
      modprobe ${MODULE}

      # Проверяем успешность завершения операции
      if [ "${?}" != "0" ] ; then
        send-report "MDADM" "Error load module ${MODULE}. Aborting."
        echo "${DATE}: Error load module ${MODULE}. Aborting." >> ${LOG}
        return 1
      fi
    fi
  done

  # Перебираем все строки именований массивов
  for ARRAYS in `grep --ignore-case "^mdadm.array=" "${CONF}" | uniq`; do

    ARRAY=`echo ${ARRAYS} | awk -F = '{print $2}'`

    # Проверяем, не собран ли уже целевой массив
    STATE=`cat /proc/mdstat | grep --count --ignore-case "^${ARRAY}[ ]*:[ ]*"`
    if [ "${STATE}" -eq "0" ] ; then

      # Перебираем все строки параметров массивов
      for PARAMS in `grep --ignore-case "^mdadm.array.${ARRAY}." "${CONF}" | uniq` ; do

        BUFFER=`echo ${PARAMS} | grep --ignore-case ".uuid=" | awk -F = '{print $2}'`
        [ "${BUFFER}" != "" ] && UUID="${BUFFER}"

        BUFFER=`echo ${PARAMS} | grep --ignore-case ".journal=" | awk -F = '{print $2}'`
        [ "${BUFFER}" != "" ] && JOURNAL="${BUFFER}"

      done

      if [ "${UUID}" == "" ] || [ "${JOURNAL}" == "" ] ; then
        send-report "MDADM" "Parameters of the target array are not in full. Aborting."
        echo "${DATE}: Parameters of the target array are not in full. Aborting." >> "${LOG}"
        return 1
      fi

      # Собираем массив
      mdadm --assemble --bitmap="${JOURNAL}" --uuid="${UUID}" "/dev/${ARRAY}" >/dev/null 2>&1

      # Проверяем успешность завершения операции
      if [ "${?}" != "0" ] ; then
        send-report "MDADM" "Error assemble array ${ARRAY}. Aborting."
        echo "${DATE}: Error assemble array ${ARRAY}. Aborting." >> "${LOG}"
        return 1
      fi

      # Даём системе пару секунд на осознание места нового устройства
      sleep 2

      # Проверяем успешность отрабатывания операции сборки массива
      if [ -b "/dev/${ARRAY}" ] ; then
        echo "${DATE}: Array ${ARRAY} successfully built." >> "${LOG}"
      else
        send-report "MDADM" "Assembling the array ${ARRAY} failed. Aborting."
        echo "${DATE}: Assembling the array ${ARRAY} failed. Aborting." >> "${LOG}"
        return 1
      fi

    else

      # Проверяем состояние массива на базовом уровне
      STATE=`cat /proc/mdstat | grep --count --ignore-case "^${ARRAY}[ ]*:[ ]*active[ ]*"`
      if [ "${STATE}" -eq "0" ] ; then
        send-report "MDADM" "Array ${ARRAY} collected in error and is not active. Aborting."
        echo "${DATE}: Array ${ARRAY} collected in error and is not active. Aborting." >> "${LOG}"
        return 1
      else
        echo "${DATE}: Array ${ARRAY} is already assembled." >> "${LOG}"
      fi
    fi
  done

  # Создаём файл блокировки, показывающий на состояние работы подсистемы
  touch "${LOCK}/mdadm.lck"

return $?
}

# Функция остановки вручную подготовленных массивов
function stop-mdadm() {

  # Перебираем все строки именований массивов
  for ARRAYS in `grep --ignore-case "^mdadm.array=" "${CONF}" | uniq` ; do

    ARRAY=`echo ${ARRAYS} | awk -F = '{print $2}'`

    # Если массив доступен, то останавливаем его
    [ -b "/dev/${ARRAY}" ] && mdadm --stop "/dev/${ARRAY}" >/dev/null 2>&1
  done

  # Удаляем файл блокировки, показывающий на состояние работы подсистемы
  rm --force "${LOCK}/mdadm.lck"

return $?
}

# Функция проверки состояния вручную подготовленных массивов
function check-mdadm() {
  # Удостоверимся в том, что сборка массивов прошла успешно (в противном случае проверку не запускаем - бессмысленно)
  if [ -f "${LOCK}/mdadm.lck" ]; then
    echo "check"
  fi
return $?
}

Переход к настройке сведения блочных устройств в единое в рамках локальной системы.

LVM ( Сведение блочных устройств в единое в рамках локальной системы. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".

Устанавливаем пакет утилит для работы с LVM:

# aptitude install lvm2

Сканируем систему на предмет обнаружения групп томов LVM и активируем всё обнаруженное:

# vgscan && vgchange -ay

Перед использование в LVM активируем в роли "физического тома" RAID-массив:

# pvcreate /dev/md0


Просматриваем перечень имеющихся "физических томов" LVM:

# pvdisplay

Создаём группу томов LVM, включая в неё "физические тома" в качестве носителей:

# vgcreate vg0 /dev/md0

Дополняем группу томов новым "физическим томом":

# vgextend vg0 /dev/md1

Явно активируем новую группу томов:

# vgchange -a y vg0

Просматриваем перечень доступных групп томов LVM:

# vgdisplay

Создаём в группе томов LVM "логический том":

# lvcreate --size 930G --name lvstorage0 vg0

Просматриваем перечень доступных "логических томов" LVM:

# lvdisplay

При желании расширяем "логический том" LVM на указанный объём:

# lvextend --size +930G /dev/vg0/lvstorage0

Переход к настройке автоматизации сведения блочных устройств в единое в рамках локальной системы.

Автоматизация LVM ( Автоматизация сведения блочных устройств в единое в рамках локальной системы. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".


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

# vi /usr/local/etc/storage/cnf.d/lvm.cnf

....
lvm.physical=md0
lvm.physical=md1
lvm.physical=md2
lvm.group=vg0
lvm.group.vg0.volume=lvstorage0
....


Фрагмент кода автоматизации функционала LVM подсистемы хранения:

# vi /usr/local/etc/storage/fnc.d/lvm.fnc

#!/bin/bash
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell)
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell)

# Функция запуска (сборки) вручную подготовленных LVM-томов
function start-lvm() {

  # Сканируем все доступные блочные устройства на предмет обнаружения изменений "мета"-данных
  vgscan >/dev/null 2>&1

  # Перебираем все строки именований физических томов LVM
  for LPHYSICALS in `grep --ignore-case "^lvm.physical=" "${CONF}" | uniq` ; do

    LPHYSICAL=`echo ${LPHYSICALS} | awk -F = '{print $2}'`

    # Проверяем доступность физических томов LVM
    STATE=`pvs --noheadings | grep --count --ignore-case "^[ ]*/dev/${LPHYSICAL}[ ]*"`
    if [ ! -b "/dev/${LPHYSICAL}" ] || [ "${STATE}" -eq "0" ] ; then
      send-report "LVM" "Physical volume /dev/${LPHYSICAL} is unavailable. Aborting."
      echo "${DATE}: Physical volume /dev/${LPHYSICAL} is unavailable. Aborting." >> "${LOG}"
      return 1
    fi
  done

  # Перебираем все строки именований групп томов LVM
  for LGROUPS in `grep --ignore-case "^lvm.group=" "${CONF}" | uniq` ; do

    LGROUP=`echo ${LGROUPS} | awk -F = '{print $2}'`

    # Проверяем доступность группы томов LVM
    STATE=`vgs --noheadings | grep --count --ignore-case "^[ ]*${LGROUP}[ ]*"`
    if [ "${STATE}" -eq "0" ] ; then
      send-report "LVM" "Volume group LVM ${LGROUP} is unavailable. Aborting."
      echo "${DATE}: Volume group LVM ${LGROUP} is unavailable. Aborting." >> "${LOG}"
      return 1

    else

      # Проверяем, не инициирована ли уже целевая группа томов LVM
      if [ ! -d "/dev/${LGROUP}" ] ; then

        # Активируем целевую группу томов LVM
        vgchange --available y "${LGROUP}" >/dev/null 2>&1

        # Проверяем успешность завершения операции
        if [ "${?}" -ne "0" ] ; then
          send-report "LVM" "Error activating LVM group ${LGROUP}. Aborting."
          echo "${DATE}: Error activating LVM group ${LGROUP}. Aborting." >> "${LOG}"
          return 1
        fi

        # Даём системе пару секунд на осознание места нового устройства
        sleep 2

        # Проверяем успешность отрабатывания операции инициализации группы томов LVM
        if [ -d "/dev/${LGROUP}" ] ; then
          echo "${DATE}: Volume group LVM ${LGROUP} successfully activated." >> "${LOG}"
        else
          send-report "LVM" "Activating a volume group LVM ${LGROUP} failed. Aborting."
          echo "${DATE}: Activating a volume group LVM ${LGROUP} failed. Aborting." >> "${LOG}"
          return 1
        fi

      else
        echo "${DATE}: Volume group LVM ${LGROUP} is already active." >> "${LOG}"
      fi

      # Перебираем все строки именований логических томов LVM в рамках группы
      for LVOLUMES in `grep --ignore-case "^lvm.group.${LGROUP}.volume=" "${CONF}" | uniq` ; do

        LVOLUME=`echo ${LVOLUMES} | awk -F = '{print $2}'`

        # Проверяем доступность логических томов LVM
        STATE=`lvs --noheadings | grep --count --ignore-case "^[ ]*${LVOLUME}[ ]*${LGROUP}[ ]*"`
        if [ -b "/dev/${LGROUP}/${LVOLUME}" ] && [ "${STATE}" -ne "0" ] ; then
          echo "${DATE}: Logical volume /dev/${LGROUP}/${LVOLUME} active." >> "${LOG}"
        else
          send-report "LVM" "Logical volume /dev/${LGROUP}/${LVOLUME} unavailable. Aborting."
          echo "${DATE}: Logical volume /dev/${LGROUP}/${LVOLUME} unavailable. Aborting." >> "${LOG}"
          return 1
        fi
      done
    fi
  done

  # Создаём файл блокировки, показывающий на состояние работы подсистемы
  touch "${LOCK}/lvm.lck"

return $?
}

# Функция остановки вручную подготовленных LVM-томов
function stop-lvm() {

  # Перебираем все строки именований групп томов LVM
  for LGROUPS in `grep --ignore-case "^lvm.group=" "${CONF}" | uniq` ; do

    LGROUP=`echo ${LGROUPS} | awk -F = '{print $2}'`

    # Если группа томов LVM доступна, то деактивируем её
    [ -d "/dev/${LGROUP}" ] && vgchange --available n "${LGROUP}" >/dev/null 2>&1
  done

  # Удаляем файл блокировки, показывающий на состояние работы подсистемы
  rm --force "${LOCK}/lvm.lck"

return $?
}

# Функция проверки состояния вручную подготовленных LVM-томов
function check-lvm() {
  # Удостоверимся в том, что активация томов LVM прошла успешно (в противном случае проверку не запускаем - бессмысленно)
  if [ -f "${LOCK}/lvm.lck" ]; then
    echo "check"
  fi
return $?
}

Переход к подготовке символических ссылок, используемых при сборке XFS.

О симлинках ( О символических ссылках, используемых при сборке XFS. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".

В зависимости от очерёдности инициализации дисковых устройств они могут получать разные символические имена. В один раз системный диск может припарковаться на имя "sda", а в другой - на что-нибудь вроде "sdf". А мы желаем выделить на одном из дисков раздел для журналирования файловой системы хранилища; в случае невозможности получения доступа к журналу таковая просто не смонтируется. К сожалению, ориентироваться по UUID не получится, так как это сущность файловой системы, а не физического устройства как такового, так что ничего не остаётся, как перед запуском системы хранения искать целевое устройство по серийному номеру и создавать на один из его разделов символическую ссылку, с которой в дальнейшем и работать.


Для этого нам понадобится создать некий список соответствий серийных номеров целевых дисков, идентификаторов разделов, на которые нам нужно сослаться, и наименований символических ссылок (или ссылки, если более одной не нужно). Нужную информацию можно легко выяснить просмотрев вывод утилит "dmesg", "ls -l /dev/sd*" и "hdparm -i /dev/sdX". Автоматизацию процесса создания ссылки проработаем в одной из следующих заметок, а пока установим требуемое программное обеспечение:

# aptitude install hdparm

Для простоты договоримся о том, что мы будем использовать символическое имя "sd.sys.xfsjournal", в дальнейшем именно на него мы будем ориентировать драйвер XFS, чтобы тот размещал на указанном разделе журнал транзакций.

Замечу, к вышеприведённому, что обращаться с помощью "мягкой" ссылки в дальнейшем мы будем не к блочному устройству вроде "sda", например, а к разделу на указанном устройстве, например: "sda7". То есть, сопоставление будет вида: "sda7 => sd.sys.xfsjournal".

Учитывая то, что содержимое директории "/dev" с загрузкой операционной системы полностью обновляется, можно не опасаться загромождения её устаревшими ссылками; в связи с указанной особенностью директории очевидным является необходимость вызывать скрипт создания "жёстких" ссылок на устройства после каждой загрузки системы перед началом работы.

Переход к созданию на блочных устройствах LVM файловой системы XFS.


XFS ( Создание на блочных устройствах LVM файловой системы XFS. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".

Инсталлируем пакет утилит поддержки XFS:

# aptitude install xfsprogs

Создаем файловую систему XFS:

# mkfs.xfs -b size=4096 -d agcount=256 -l logdev=/dev/sd.sys.xfsjournal,size=128m /dev/vg0/lvstorage0

Где:

"-d agcount" (allocation groups) - параметр, влияющий на эффективность параллельной записи на устройство. В соответствии с рекомендациями разработчиков на каждые 4 (четыре) Гигабайта на блочном устройстве желательно иметь одну "allocation groups". Если пропустить назначение этого параметра, драйвер сам будет его определять и, все хором говорят, это не лучшим образом скажется на производительности;
"-l size" - параметр, определяющий размер журнала для "метаданных". Внятных рекомендаций я не нашёл, но подумал, что для "терабайтного" диска вполне должно хватить и 128 (ста двадцати восьми) Мегабайт:;
"-l logdev" - указатель на блочное устройство, в которое вынесены журналы транзакций XFS.


# mkdir -p /mnt/storage0

Монтируем новоприобретённую файловую систему:

# mount -t xfs -o noatime,nodiratime,logdev=/dev/sd.sys.xfsjournal /dev/vg0/lvstorage0 /mnt/storage0

Проверяем, смонтировалась ли таковая:

# mount

/dev/mapper/vg0-lvstorage0 on /mnt/storage0 type xfs (rw,noatime,nodiratime,logdev=/dev/sd.sys.xfsjournal)

При желании легко расширить файловую систему на всё доступное пространство.

Демонтируем файловую систему для проверки целостности данных:

# umount /dev/vg0/lvstorage0

Проверяем файловую систему:

# xfs_check -l /dev/sd.sys.xfsjournal -s /dev/vg0/lvstorage0

Монтируем файловую систему перед расширением таковой:

# mount -t xfs -o noatime,nodiratime,logdev=/dev/sd.sys.xfsjournal /dev/vg0/lvstorage0 /mnt/storage0

Расширяем файловую систему XFS на всё доступное пространство:

# xfs_growfs /dev/vg0/lvstorage0

Переход к автоматизации создания на блочных устройствах LVM файловой системы XFS.

Автоматизация XFS ( Автоматизация создания на блочных устройствах LVM файловой системы XFS. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".


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

# vi /usr/local/etc/storage/cnf.d/fs.cnf

....
hdd.target=hdd0
hdd.target.hdd0.serial=5LJ1J3HB
hdd.target.hdd0.partition=7
hdd.target.hdd0.symlink=sd.sys.xfsjournal

fs.target=storage0
fs.target.storage0.type=xfs
fs.target.storage0.device=/dev/vg0/lvstorage0
fs.target.storage0.mount.journal=/dev/sd.sys.xfsjournal
fs.target.storage0.mount.point=/mnt/storage0
....


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

# vi /usr/local/etc/storage/fnc.d/fs.fnc

#!/bin/bash
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell)
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell)

# Функция создания символической ссылки на заданный раздел целевого диска
function make-symlink() {

  # Перебираем все строки именований целевых дисков
  for HDDS in `grep --ignore-case "^hdd.target=" "${CONF}" | uniq` ; do

    HDD=`echo ${HDDS} | awk -F = '{print $2}'`

    # Перебираем все строки параметров HDD
    for PARAMS in `grep --ignore-case "^hdd.target.${HDD}." "${CONF}" | uniq` ; do

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".serial=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && SERIAL="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".partition=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && PARTITION="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".symlink=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && SYMLINK="${BUFFER}"

    done

    if [ "${SERIAL}" == "" ] || [ "${PARTITION}" == "" ] || [ "${SYMLINK}" == "" ] ; then
      send-report "Symlink" "Parameters of the target device are not in full. Aborting."
      echo "${DATE}: Parameters of the target device are not in full. Aborting." >> "${LOG}"
      return 1
    fi

    # Перебираем все строки именований HDD
    for OBJECT in `ls /dev | grep -i "^sd[a-z]$"` ; do

      # Ищем совпадения в выводе информации об устройстве с заданным серийным номером
      STATE=`hdparm -i /dev/${OBJECT} | grep --ignore-case "serial" | grep --count --ignore-case "${SERIAL}"`
      if [ "${STATE}" -ne "0" ] ; then

        # Ищем целевой раздел
        if [ "`ls /dev | grep --ignore-case ${OBJECT}${PARTITION}`" != "" ] ; then

          # Проверяем наличие корректной ссылки
          STATE=`ls -l /dev | grep --ignore-case "lrwx" | grep --ignore-case "${SYMLINK}" | grep --count --ignore-case "${OBJECTS}${PART}"`
          if [ "${STATE}" -eq "0" ] ; then
            ln --force --symbolic /dev/${OBJECT}${PARTITION} /dev/${SYMLINK}

            # Проверяем успешность завершения операции
            if [ "${?}" -eq "0" ] ; then
              echo "${DATE}: A symbolic link ${SYMLINK} successfully created for /dev/${OBJECT}${PARTITION}" >> "${LOG}"
              break
            else
              send-report "Symlink" "Error creating symlink ${SYMLINK}. Aborting."
              echo "${DATE}: Error creating symlink ${SYMLINK}. Aborting." >> "${LOG}"
              return 1
            fi

          else
            echo "${DATE}: Section on ${PARTITION} of the target device ${OBJECT} is already a symbolic link ${SYMLINK}." >> "${LOG}"
            break
          fi

        else
          send-report "Symlink" "The target device ${OBJECT} not specified partition ${PARTITION}. Aborting."
          echo "${DATE}: The target device ${OBJECT} not specified partition ${PARTITION}. Aborting" >> "${LOG}"
          return 1
        fi
      fi
    done
  done

return $?
}

# Функция монтирования вручную подготовленных файловых систем хранилища
function start-fs() {

  # Перебираем все строки именований целевых файловых систем
  for TFSS in `grep --ignore-case "^fs.target=" "${CONF}" | uniq` ; do

    TFS=`echo ${TFSS} | awk -F = '{print $2}'`

    # Перебираем все строки параметров целевой файловой системы
    for PARAMS in `grep --ignore-case "^fs.target.${TFS}." "${CONF}" | uniq` ; do

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".type=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && TFSTYPE="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".device=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && TFSDEVICE="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".mount.journal=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && TFSJOURNAL="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".mount.point=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && TFSPOINT="${BUFFER}"

    done

    if [ "${TFSTYPE}" == "" ] || [ "${TFSDEVICE}" == "" ] || [ "${TFSJOURNAL}" == "" ] || [ "${TFSPOINT}" == "" ] ; then
      send-report "FS" "Parameters of the target file system are not fully. Aborting."
      echo "${DATE}: Parameters of the target file system are not fully. Aborting." >> "${LOG}"
      return 1
    fi

    # Проверяем доступность целевых блочных устройств
    if [ -b "${TFSDEVICE}" ] && [ -b "${TFSJOURNAL}" ] ; then

      # Вычленяем из пути к монтируемому блочному устройству его имя
      TFSDEVICENAME=`basename "${TFSDEVICE}"`

      # Проверяем, не смонтирована ли уже целевая файловая система
      STATE=`mount | grep --ignore-case "${TFSDEVICENAME}" | grep --count --ignore-case "${TFSPOINT}"`
      if [ "${STATE}" -eq "0" ] ; then

        mount -t "${TFSTYPE}" -o noatime,nodiratime,logdev=${TFSJOURNAL} "${TFSDEVICE}" "${TFSPOINT}"

        # Проверяем успешность завершения операции
        if [ "${?}" -ne "0" ] ; then
          send-report "FS" "Error mount target FS ${TFSPOINT}. Aborting."
          echo "${DATE}: Error mount target FS ${TFSPOINT}. Aborting." >> "${LOG}"
          return 1
        fi

        # Даём системе пару секунд на осознание места новой файловой системы
        sleep 2

        # Проверяем успешность отрабатывания операции монтирования файловой системы
        STATE=`mount | grep --ignore-case "${TFSDEVICENAME}" | grep --count --ignore-case "${TFSPOINT}"`
        if [ "${STATE}" -ne "0" ] ; then
          echo "${DATE}: The file system storage successfully installed." >> "${LOG}"
        else
          send-report "FS" "Mount the file system storage with error. Aborting."
          echo "${DATE}: Mount the file system storage with error. Aborting." >> "${LOG}"
          return 1
        fi

      else
        echo "${DATE}: The target file system is already mounted to the point ${TFSPOINT}." >> "${LOG}"
      fi

    else
      send-report "FS" "Target block device ${TFSDEVICE} and ${TFSJOURNAL} unavailable. Aborting."
      echo "${DATE}: Target block device ${TFSDEVICE} and ${TFSJOURNAL} unavailable. Aborting." >> "${LOG}"
      return 1
    fi

  done

  # Создаём файл блокировки, показывающий на состояние работы подсистемы
  touch "${LOCK}/fs.lck"

return $?
}

# Функция размонтирования вручную подготовленных файловых систем хранилища
function stop-fs() {

  # Перебираем все строки именований целевых файловых систем
  for TFSS in `grep --ignore-case "^fs.target=" "${CONF}" | uniq` ; do

    TFS=`echo ${TFSS} | awk -F = '{print $2}'`

    # Перебираем необходимые строки параметров целевой файловой системы
    for PARAMS in `grep --ignore-case "^fs.target.${TFS}." "${CONF}" | uniq` ; do

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".mount.point=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && TFSPOINT="${BUFFER}"

    done

    if [ "${TFSPOINT}" == "" ] ; then
      send-report "FS" "Parameters of the target file system are not fully. Aborting."
      echo "${DATE}: Parameters of the target file system are not fully. Aborting." >> "${LOG}"
      return 1
    fi

    # Проверяем, смонтирована ли уже целевая файловая система
    STATE=`mount | grep --count --ignore-case "${TFSPOINT}"`
    if [ "${STATE}" -ne "0" ] ; then
      umount -f "${TFSPOINT}"
    fi
  done

  # Удаляем файл блокировки, показывающий на состояние работы подсистемы
  rm --force "${LOCK}/fs.lck"

return $?
}

# Функция проверки состояния вручную подготовленных файловых систем хранилища
function check-fs() {
  # Удостоверимся в том, что монтирование файловых систем хранилища прошло успешно (в противном случае проверку не запускаем - бессмысленно)
  if [ -f "${LOCK}/fs.lck" ]; then
    echo "check"
  fi
return $?
}

Переход к настройкам публикации и монтирования удалённых NFS-ресурсов.

NFS ( Публикация и монтирование удалённых NFS-ресурсов. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".

NFS подсистема древняя (в смысле богатая историей). Исторически сложилась ситуация, когда она считается базовым функционалом UNIX (а по наследству и Linux, и BSD). Несмотря на то, что NFS, по сути, является отдельной подсистемой вроде HTTP-сервера или иного сервера приложений, конфигурируемого отдельно от несущей операционной системы, ситуация с настройкой NFS несколько иная - для версий "2" и "3" запустить "просто NFS", без корректировки параметров смежных уровней абстракции нереально. С версией NFSv4 ситуация сильно улучшилась (теперь NFS практически автономный сервис), но разработчики излишне, на мой взгляд, озабочены поддержкой обратной совместимости и обеспечением работы подсистемы в условиях эксплуатации оной "дураком", запуская сразу все её составляющие, даже никому не нужные уже лет десять; очень неприятно, знаете ли, знать, что кроме нужного сервиса принимающего подключения по протоколу NFSv4, на сервере запущено ещё с десяток сервисов, обеспечивающих работу с протоколами NFSv1, NFSv2 и NFSv3. Мало того, что ненужные сервисы элементарно занимают неиспользуемыми ими ресурсы, так их наличие ещё и вносит путаницу в процесс первичной настройки и локализации неисправностей в последствии; мало где я читал столько чепухи, сколько в ветках обсуждения процедур настройки NFS разного рода интернет форумов - местами просто феерия непонимания связи параметров конфигурирования с целями такового.

Заинтересовавшимся рекомендую сразу начать читать здесь: http://wiki.linux-nfs.org/

Я буду использовать в работе только NFSv4 и только на транспорте TCP (главное преимущество TCP - более эффективный механизм повторной передачи, что компенсирует возможные сбои в работе, позволяя продолжать её даже на приходящем в неисправность оборудовании). Отключить поддержку NFSv2 и NFSv3 в Debian Linux Lenny/Squeeze/Wheezy исправлением значений пары-тройки переменных нельзя (так, что бы изменения сохранились бы при обновлении конфигурируемых пакетов) и здесь я даже не буду этого делать, оставив конфигурацию сервисов "по умолчанию"; далее мы будем управлять таковой "на лету", путём воздействия на неё из "командной строки". Для энтузиастов позже напишу отдельную заметку о запуске NFS с поддержкой только одной, нужной нам, версии "4".


Устанавливаем необходимые пакеты:

# aptitude install --without-recommends nfs-common nfs-server

APT-пакет "nfs-server" - виртуальный и объединяет собой как минимум "nfs-common", "nfs-kernel-server" и "portmap". Любителям точности можно устанавливать непосредственно целевые пакеты.

Надо заметить, что сборщик пакетов NFS в Debian совсем не заинтересован в детальной проработке их конфигурации; что-то можно настроить, а что-то нельзя, несмотря на то, что препятствий к этому с точки зрения воздействия на целевое программное обеспечение нет никаких. Отключить всё не получится, но на некоторые составляющие мы можем повоздействовать через предусмотренные конфигурационный файлы. Корректируем два файла, применяя значения указанным переменным:

# vi /etc/default/nfs-common

....
# Отключаем выделенный сервис "statd" (он уже включен в сервис nfsd NFSv4 и отдельно нужен только для поддержки NFSv2 и NFSv3)
NEED_STATD=no

# Влючаем выделенный сервис (предусмотренный для поддержки NFSv4) трансляции имён пользователей в идентификаторы (он полезен в системе с полноценной аутентификацией клиентов с помощью протоколов PAM или Kerberos)
NEED_IDMAPD=yes

# Отключаем выделенный сервис, авторизирующий подключающихся пользователей с помощью протокола Kerberos (сейчас нам это не нужно, так как серверы выделенные и подключения защищены на транспортном уровне)
NEED_GSSD=no
....

# vi /etc/default/nfs-kernel-server

....
# Зададим количество серверов принимающих запросы. Наш сервер будет обслуживать всего одного клиента, так что укажем запускать пару экземпляров (по умолчанию: 8)
RPCNFSDCOUNT=2

# Отключаем выделенный сервис, нужный только для публикации ресурсов, требующих авторизии подключающихся пользователей с помощью протокола Kerberos
NEED_SVCGSSD=no
....

В итоге мы чуть уменьшили количество задействуемых компонентов, в целом оставив конфигурацию "по умолчанию".

Перезапустим сервисы NFS с обновлёнными настройками:

# /etc/init.d/nfs-common restart
# /etc/init.d/nfs-kernel-server restart

Возможно попытка запуска ненастроенного NFS-сервера не будет успешной и сопровождаться следующим сообщением об ошибке:

[warn] Not starting NFS kernel daemon: no exports. ... (warning).

В "Linux Debian Lenny/Squeeze" можно было запустить NFS-сервер с пустым перечнем экспортируемых ресурсов - вхолостую, так сказать. Это удобно, когда сразу после старта системы в объявлении ресурсов нет необходимости или возможности. В "Debian Wheezy" эту возможность прикрыли, прерывая запуск NFS-сервера в случае пустых файлов "/etc/exports" и "/etc/exports.d/*exports". Глупо, надо заметить, и придётся с этим побороться.

Переделывать стартовый скрип не вижу смысла - мало ли что ещё поменяется в следующих реализациях, о чём я своевременно не узнаю? Потому просто подставим на публикацию в файле "/etc/exports" корень NFS, доступ к которому разрешим только на чтение:

# vi /etc/exports

....
/mnt/export 10.10.12.192/28(fsid=0,ro,nohide,root_squash,no_subtree_check,sync)

# /etc/init.d/nfs-kernel-server restart

[ ok ] Stopping NFS kernel daemon: mountd nfsd.
[ ok ] Unexporting directories for NFS kernel daemon....
[ ok ] Exporting directories for NFS kernel daemon....
[ ok ] Starting NFS kernel daemon: nfsd mountd.

Сразу можно будет посмотреть, опубликовался ли наш ресурс:

# showmount --exports

Export list:
/mnt/export 10.10.12.192/28

Нашему серверу достаточно иметь запущенными всего пару сервисов, удостоверимся в их наличии:

# ps wax | grep -v grep | grep nfs

....
2831 ... [nfsd4]
2832 ... [nfsd]
....

Опять же, в плане сетевых подключений для поддержания NFSv4 поверх TCP нам требуется прослушивание лишь одного порта "TCP:2049":

# netstat -apn | grep 2049

tcp ... 0 0.0.0.0:2049 0.0.0.0:* LISTEN
udp ... 0 0.0.0.0:2049 0.0.0.0:*

Как я уже упоминал выше, мы будем использовать только NFSv4 поверх TCP. По умолчанию запускается масса сервисов, обеспечивающих поддержку всей линейки протоколов NFS как на транспорте TCP, так и на UDP. Можете полюбоваться на них, запросив с помощью утилиты "rpcinfo" перечень зарегистрированных на сервере RPC-процессов:

# rpcinfo -p 127.0.0.1

На этом предварительную настройку подсистемы NFS будем считать завершённой.

Следующим, по сути первым, этапом будет оформление публикуемых (экспортируемых) ресурсов в должном виде.

Реализация NFSv4 не поддерживает публикацию произвольных веток файловых систем; теперь требуется предварительно создать некую отправную точку, которая объявляется условным корнем файловой системы, содержимое которой уже доступно для публикации (экспортирования). Получается своего рода "chroot"; надо полагать, это сделано в целях повышения уровня безопасности "от дурака", что бы случайно не раскрыть всем окружающим важные ресурсы.

Заведём себе такую точку сбора:

# mkdir -p /mnt/export

Непосредственно в директории выделенной для публикации можно разместить соответствующие ресурсы, а можно смонтировать их сюда с помощью функционала "--bind" (создавая синоним ресурса, а не ссылку на таковой, как некоторые неверно считают; преимуществом этого способа над символьными ссылками является возможность обходить ограничения доступа к файловой системе, возникающие перед процессами, запущенными в среде "chroot"):

# mkdir -p /mnt/export/storage0
# mount --bind /mnt/storage0 /mnt/export/storage0

Удостоверимся, что ресурс успешно смонтирован:

# mount

....
/mnt/storage0 on /mnt/export/storage0 type none (rw,bind)

Следующим шагом станет определение локальных прав собственности публикуемых (экспортируемых) ресурсов и правил доступа к таковым удалёнными клиентами.

Специфика NFS в том, что (до последнего времени, когда стала возможной аутентификация подключающихся пользователей с помощью Kerberos) сервер доверяет клиенту и принимает "на веру" заявленные им при доступе к ресурсам UID и GID, на основании которых определяется, имеет клиент право доступа к ресурсам или нет. Предполагается, что в пределах схемы существует общее пространство имен пользователей, то есть пользователь "storage" на любом клиентском (с точки зрения сервера NFS) компьютере имеет то же имя и тот же идентификатор, что и пользователь "storage" на сервере NFS; по умолчанию, если GID и UID клиента не обнаруживаются на сервере, их значения транслируются в значения пользователя "nobody:nogroup" сервера. В NFSv4 трансляцией имён и идентификаторов занимается сервис "idmapd".

Соответственно, самое простое, что мы можем сделать для обеспечения возможности операций чтения и записи с ресурсам, так это сделать их собственником пользователя "nobody:nogroup", разрешив ему всё необходимое. Однако, я считаю, что лучше завести специального пользователя как на сервере так и на клиенте, задав им при создании идентичные UID и GID, хотя бы и для того, что бы в последствии наладить полноценное удостоверение подлинности клиента при обращении к серверу, попутно избежав выдачи слишком высокого уровня доступа к ресурсам пользователю "nobody:nogroup", который по идее (в рамках которой он создавался) вообще не должен иметь возможности изменять что либо.

Добавляем пользователя, собственника ресурсов хранилища публикуемых с помощью NFS:

# groupadd --gid 1500 storage
# useradd --shell /bin/false --home-dir /var/lib/nfs --uid 1500 --gid storage storage

Естественно, что цифровые значения UID и GID необходимо подобрать несовпадающие с уже имеющимися во всех задействованных в схеме системах.

Обращаю внимание на то, что я лишил пользователя "storage" возможности работать в так называемой "оболочке", иначе говоря, от имени пользователя можно запустить приложение или обратится к ресурсам, но нельзя будет работать в "командной строке".

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

# chown -R storage:storage /mnt/storage0
# chmod -R ug+rw /mnt/storage0
# chmod -R o-rw /mnt/storage0

Пока не заведена система удостоверения подлинности подключающихся клиентов проще всего указать ключами "all_squash, anonuid=1500, anongid=1500" серверу NFS считать всех анонимными пользователями и преобразовывать их UID и GID в значения локального пользователя "storage:storage", собственника ресурсов хранилища. Тогда мы сможем обратится к ресурсам от имени любого пользователя; в данном упрощённом варианте пользователи с идентичными именами, UID и GID на всех серверах и клиентах схемы нужны более для того, что бы корректно отображать параметры доступа на удалённых узлах, явно показывая в выводе утилит вроде "ls" собственника ресурсов и права доступа к таковым.

# exportfs -i -o fsid=0,rw,nohide,all_squash,anonuid=1500,anongid=1500,no_subtree_check,sync 10.10.12.192/28:/mnt/export/storage0

Отменить экспортирование:

# exportfs -u 10.10.12.192/28:/mnt/export/storage0

Где:

"-i" - игнорируем содержимое "/etc/exports" руководствуясь только параметрами данной команды;
"-o fsid=0" - обозначает, что публикуется корневой для всех остальных экспортируемых каталогов (соответственно, все остальные каталоги, расположенные внутри него, публикуются с параметром "-o fsid=1").

Проверяем, успешно ли опубликованы ресурсы:

# showmount --exports

/mnt/export          10.10.12.192/28
/mnt/export/storage0 10.10.12.192/28

На стороне сборки опубликованных NFS-ресурсов потребуется провести несколько иные работы.

Установим утилиты подключения к NFS-ресурсам и монтирования их в локальную файловую систему:

# aptitude install --without-recommends nfs-common nfs-client

Чуть подправим конфигурацию NFS-клиента:

# vi /etc/default/nfs-common

....
# Отключаем выделенный сервис "statd" (он уже включен в сервис nfsd NFSv4 и отдельно нужен только для поддержки NFSv2 и NFSv3)
NEED_STATD=no

# Включаем выделенный сервис (предусмотренный для поддержки NFSv4) трансляции имён пользователей в идентификаторы
NEED_IDMAPD=yes

# Отключаем выделенный сервис, авторизирующий подключающихся пользователей с помощью протокола Kerberos (сейчас нам это не нужно, так как серверы выделенные и подключения защищены на транспортном уровне)
NEED_GSSD=no
....

# /etc/init.d/nfs-common restart

Добавляем пользователя, собственника ресурсов хранилища, публикуемых с помощью NFS:

# groupadd --gid 1500 storage
# useradd --shell /bin/false --home-dir /var/lib/nfs --uid 1500 --gid storage storage

Создаём директорию, в которую будем монтировать NFS-ресурсы:

# mkdir -p /mnt/import

Запрашиваем перечень доступных для монтирования удалённых NFS-ресурсов:

# showmount --exports 10.10.12.201

Export list for 10.10.12.201:
/mnt/export          127.0.0.1/32
/mnt/export/storage0 10.10.12.192/28

Ранее, в настройках NFSv4-сервера мы обозначили директорию "/mnt/export" корневой для клиентов с помощью опции "fsid=0". Теперь при монтировании с NFSv4-клиента следует указывать целевые ресурсы отталкиваясь не от корневой директории NFS-сервера, а от своеобразного "chroot" по адресу "/mnt/export". То есть, если требуется смонтировать ресурс "/mnt/export/resource", нужно монтировать "/resource":

# mount.nfs4 -o proto=tcp,port=2049,hard,intr,tcp,sync,noatime,nodiratime,nodev,noexec,nosuid 10.10.12.195:/storage0 /mnt/import

Иногда удобнее позволить NFS-клиенту уведомлять приложение использующее его ресурсы о проблемах со смонтированной файловой системой. Для этого монтируем файловую систему с опцией "soft", указываю количество попыток перемонтирования до вывода сообщения об ошибке опцией "retrans=5" (пять раз) и указываем таймаут для запроса опцией "timeo=600" (минута, в десятых долях секунды):

# mount.nfs4 -o soft,intr,retrans=5,timeo=600,tcp,sync,noatime,nodiratime,nodev,noexec,nosuid 10.10.12.195:/storage0 /mnt/import

За несколько лет эксплуатации я так и не пришёл к выводу, какой режим выгоднее - "мягкий", с некорректно отрабатывающими ответы NFS приложениями, или "жёсткий" - с зависающими в ожидании ответа приложениями. Пока остаюсь с "hard".

Иногда сбой в работе приложения обращающегося к файловой системе NFS и не получающего ответа приводит к его зависанию намертво (например MHDDFS сваливается до уровня "defunct" и "zombie" после длительного (десятки минут и более) ожидания ответа от файловой системы и множества дублирующих неудовлетворённых запросов к ресурсам) так, что его порой невозможно остановить без форсированной ("echo 1 > /proc/sys/kernel/sysrq; echo b > /proc/sysrq-trigger") перезагрузки системы (если зависшее приложение использует модуль ядра, драйвер файловой системы или что-то низкоуровневое в этом роде, то даже может не сработать программная перезагрузка, так как система будет ждать ответа от зависшего приложения или заблокированного им модуля бесконечно долго).

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

# blockdev --getbsz /dev/sda

В современных блочных устройствах это будет "4096" байт, скорее всего. Тогда подгоним размер пакета под это значение, установив соответствующие опции "rsize=4096,wsize=4096".

Переход к настройкам автоматизации публикации NFS-ресурсов.

Автоматизация экспорта NFS ( Автоматизация публикации NFS-ресурсов. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".


Заранее готовим корневую точку монтирования для публикуемых NFS-ресурсов:

# mkdir -p /mnt/export
# chown -R storage:storage /mnt/export
# chmod -R ug+rw /mnt/export

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

# vi /usr/local/etc/storage/cnf.d/nfs.export.cnf

....
nfs.export.server.chroot=/mnt/export
nfs.export.server.network=10.10.12.192/28

nfs.export=storage0
nfs.export.storage0.fsid=1
nfs.export.storage0.source=/mnt/storage0
nfs.export.storage0.point=/mnt/export/storage0
nfs.export.storage0.anonuid=1500
nfs.export.storage0.anongid=1500
....


Фрагмент кода автоматизации функционала экспорта NFS-ресурсов подсистемы хранения:

# vi /usr/local/etc/storage/fnc.d/nfs.export.fnc

#!/bin/bash
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell)
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell)

# Функция публикации файловых систем хранилища посредством NFS
function start-nfs-export() {

  # Вычленяем необходимые глобальные параметры сервера NFS
  GECHROOT=`grep --ignore-case "^nfs.export.server.chroot=" "${CNF}" | awk -F = '{print $2}'`
  GENETWORK=`grep --ignore-case "^nfs.export.server.network=" "${CNF}" | awk -F = '{print $2}'`

  # Проверяем наличие пути к корневой псевдофайловой системе NFS
  if [ ! -d "${GECHROOT}" ] ; then
    echo "${DATE}: There is no way to root pseudo file system NFS. Aborting." | tee -a "${LOGT}"
    return 1
  fi

  # Проверяем корректность формата NETWORK
  if [ "${GENETWORK}" == "" ] ; then
    echo "${DATE}: Not all global NFS server settings are in full. Aborting." | tee -a "${LOGT}"
    return 1
  fi

  # Проверяем, не опубликован ли уже корень псевдофайловой системы NFS
  STATE=`showmount --exports --no-headers | grep --count --ignore-case "^${GECHROOT}[ ]*${GENETWORK}*"`
  if [ "${STATE}" -eq "0" ] ; then

    exportfs -i -o fsid=0,root_squash ${GENETWORK}:${GECHROOT}

    # Проверяем успешность завершения операции
    if [ "${?}" != "0" ] ; then
      echo "${DATE}: Error export NFS pseudo file system. Aborting." | tee -a "${LOGT}"
      return 1
    fi

    # Даём системе пару секунд на завершение публикации
    sleep 2

    # Проверяем успешность публикации
    STATE=`showmount --exports --no-headers | grep --count --ignore-case "^${GECHROOT}[ ]*${GENETWORK}*"`
    if [ "${STATE}" -ne "0" ] ; then
      echo "${DATE}: NFS resource ${GECHROOT} published successfully." | tee -a "${LOGT}"
    else
      echo "${DATE}: Publication NFS resource ${GECHROOT} failed. Aborting." | tee -a "${LOGT}"
      return 1
    fi

  else
    echo "${DATE}: NFS root pseudo file system has already been published." | tee -a "${LOGT}"
  fi

  # Перебираем все строки именований публикуемых ресурсов
  for ENFSS in `grep --ignore-case "^nfs.export=" "${CNF}" | uniq`; do

    ENFS=`echo ${ENFSS} | awk -F = '{print $2}'`

    # Перебираем все строки параметров публикуемых ресурсов
    for PARAMS in `grep --ignore-case "^nfs.export.${ENFS}." "${CNF}" | uniq` ; do

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".fsid=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && EFSID="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".source=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && ESOURCE="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".point=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && EPOINT="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".anonuid=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && EANONUID="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".anongid=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && EANONGID="${BUFFER}"

    done

    # Проверяем наличие субьектов файловой системы
    if [ ! -d "${ESOURCE}" ] || [ ! -d "${EPOINT}" ] ; then
      echo "${DATE}: Not all mounted and exported resources are in full. Aborting." | tee -a "${LOGT}"
      return 1
    fi

    # Проверяем корректность параметров
    if [ "${EFSID}" == "" ] || [ "${EANONUID}" == "" ] || [ "${EANONGID}" == "" ] ; then
      echo "${DATE}: Not all resource settings are in full. Aborting." | tee -a "${LOGT}"
      return 1
    fi

    # Проверяем, не опубликованы ли уже ресурсы
    STATE=`showmount --exports --no-headers | grep --count --ignore-case "^${EPOINT}[ ]*${GENETWORK}"`
    if [ "${STATE}" -eq "0" ] ; then

      # Проверяем, не смонтирован ли уже публикуемый ресурс в псевдофайловую систему NFS
      STATE=`mount | grep --count --ignore-case "^${ESOURCE}[ ]*on[ ]*${EPOINT}"`
      if [ "${STATE}" -eq "0" ] ; then

        # Монтируем публикуемый ресурс
        mount --bind "${ESOURCE}" "${EPOINT}"

        # Проверяем успешность завершения операции
        if [ "${?}" != "0" ] ; then
          echo "${DATE}: Error bind mount ${ESOURCE} on ${EPOINT}. Aborting." | tee -a "${LOGT}"
          return 1
        fi
      fi

      # Публикуем целевой ресурс
      EOPT="fsid=${EFSID},rw,nohide,all_squash,anonuid=${EANONUID},anongid=${EANONGID},no_subtree_check,sync"
      exportfs -i -o ${EOPT} ${GENETWORK}:${EPOINT}

      # Проверяем успешность завершения операции
      if [ "${?}" != "0" ] ; then
        echo "${DATE}: Error export NFS resource ${EPOINT}. Aborting." | tee -a "${LOGT}"
        return 1
      fi

      # Даём системе пару секунд на завершение публикации
      sleep 2

      # Проверяем успешность публикации целевого ресурса
      STATE=`showmount --exports --no-headers | grep --count --ignore-case "^${EPOINT}[ ]*${GENETWORK}"`
      if [ "${STATE}" -ne "0" ] ; then
        echo "${DATE}: NFS resource ${EPOINT} published successfully." | tee -a "${LOGT}"
      else
        echo "${DATE}: Publication NFS resource ${EPOINT} failed. Aborting." | tee -a "${LOGT}"
        return 1
      fi

    else
      echo "${DATE}: Resource ${EPOINT} has already been published." | tee -a "${LOGT}"
    fi
  done

  # Создаём файл блокировки, показывающий на состояние работы подсистемы
  touch "${LOCK}/nfs-export.lck"

return $?
}

# Функция прекращения публикации файловых систем хранилища посредством NFS
function stop-nfs-export() {

  # Вычленяем необходимые параметры
  GECHROOT=`grep --ignore-case "^nfs.export.server.chroot=" "${CNF}" | awk -F = '{print $2}'`
  GENETWORK=`grep --ignore-case "^nfs.export.server.network=" "${CNF}" | awk -F = '{print $2}'`

  # Перебираем все строки именований публикуемых ресурсов
  for ENFSS in `grep --ignore-case "^nfs.export=" "${CNF}" | uniq`; do

    ENFS=`echo ${ENFSS} | awk -F = '{print $2}'`

    # Вычленяем необходимые параметры публикуемых ресурсов
    for PARAMS in `grep --ignore-case "^nfs.export.${ENFS}.point=" "${CNF}" | uniq` ; do

      EPOINT=`echo ${PARAMS} | awk -F = '{print $2}'`

      # Проверяем состояние публикации целевого ресурса
      STATE=`showmount --exports --no-headers | grep --count --ignore-case "^${EPOINT}[ ]*${GENETWORK}"`
      if [ "${STATE}" -ne "0" ] ; then

        # Останавливаем публикацию ресурса
        exportfs -u ${GENETWORK}:${EPOINT}

      fi
    done
  done

  # Останавливаем публикацию псевдофайловой системы NFS
  exportfs -u ${GENETWORK}:${GECHROOT}

  # Удаляем файл блокировки, показывающий на состояние работы подсистемы
  rm --force "${LOCK}/nfs-export.lck"

return $?
}

# Функция проверки состояния публикации файловых систем хранилища посредством NFS
function check-nfs-export() {
  # Удостоверимся в том, что публикация ресурсов прошла успешно (в противном случае проверку не запускаем - бессмысленно)
  if [ -f "${LOCK}/nfs-export.lck" ]; then
    echo "check"
  fi
return $?
}

Переход к настройке автоматизации импорта и монтирования NFS-ресурсов.

Автоматизация импорта NFS ( Автоматизация импорта и монтирования NFS-ресурсов. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".


Заранее готовим корневую точку монтирования для импортируемых NFS-ресурсов:

# mkdir -p /mnt/import
# chown -R storage:storage /mnt/import
# chmod -R ug+rw /mnt/import

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

# vi /usr/local/etc/storage/cnf.d/nfs.import.cnf

....
nfs.import=node0
nfs.import.node0.address=10.10.12.195
nfs.import.node0.root=/mnt/export
nfs.import.node0.from=/storage0
nfs.import.node0.to=/mnt/import/node0

nfs.import=node1
nfs.import.node1.address=10.10.12.196
nfs.import.node1.root=/mnt/export
nfs.import.node1.from=/storage0
nfs.import.node1.to=/mnt/import/node1

nfs.import=node2
nfs.import.node2.address=10.10.12.197
nfs.import.node2.root=/mnt/export
nfs.import.node2.from=/storage0
nfs.import.node2.to=/mnt/import/node2

nfs.import=node3
nfs.import.node3.address=10.10.12.198
nfs.import.node3.root=/mnt/export
nfs.import.node3.from=/storage0
nfs.import.node3.to=/mnt/import/node3
....


Фрагмент кода автоматизации функционала импорта NFS-ресурсов подсистемы хранения:

# vi /usr/local/etc/storage/fnc.d/nfs.import.fnc

#!/bin/bash
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell)
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell)

# Функция импорта файловых систем хранилища посредством NFS
function start-nfs-import() {

  # Перебираем все строки именований импортируемых ресурсов
  for INFSS in `grep --ignore-case "^nfs.import=" "${CNF}" | uniq`; do

    INFS=`echo ${INFSS} | awk -F = '{print $2}'`

    # Перебираем все строки параметров импортируемых ресурсов
    for PARAMS in `grep --ignore-case "^nfs.import.${INFS}." "${CNF}" | uniq` ; do

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".address=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && IADDRESS="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".root=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && IROOT="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".from=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && IFROM="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".to=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && ITO="${BUFFER}"

    done

    # Проверяем наличие субьектов файловой системы
    if [ ! -d "${ITO}" ] ; then
      echo "${DATE}: The mount point ${ITO} intended to import the resource is not available. Aborting." | tee -a "${LOGT}"
      return 1
    fi

    # Проверяем корректность параметров
    if [ "${IROOT}" == "" ] || [ "${IADDRESS}" == "" ] ; then
      echo "${DATE}: Not all resource settings are in full. Aborting." | tee -a "${LOGT}"
      return 1
    fi

    # Проверяем, не импортированы ли уже ресурсы
    STATE=`mount | grep --count --ignore-case "^${IADDRESS}:${IFROM}[ ]*on[ ]*${ITO}"`
    if [ "${STATE}" -eq "0" ] ; then

      # Проверяем доступность импортируемого ресурса
      STATE=`showmount --exports --no-headers ${IADDRESS} | grep --count --ignore-case "^${IROOT}${IFROM}"`
      if [ "${STATE}" -ne "0" ] ; then

        # # IOPT="hard,tcp,sync,noatime,nodiratime,nodev,noexec,nosuid"
        IOPT="soft, intr, retrans=5, timeo=600, rsize=4096, wsize=4096, tcp, async, noatime, nodiratime, nodev, noexec, nosuid"
        mount.nfs4 ${IADDRESS}:${IFROM} ${ITO} -o ${IOPT}

        # Проверяем успешность завершения операции
        if [ "${?}" != "0" ] ; then
          echo "${DATE}: Importing resource NFS ${IADDRESS}:${IFROM} completed with error. Aborting." | tee -a "${LOGT}"
          return 1
        fi

        # Даём системе пару секунд на завершение импортирования
        sleep 2

        # Проверяем успешность импортирования
        STATE=`mount | grep --count --ignore-case "^${IADDRESS}:${IFROM}[ ]*on[ ]*${ITO}"`
        if [ "${STATE}" -ne "0" ] ; then
          echo "${DATE}: Importing resource NFS ${IADDRESS}:${IFROM} completed successfully." | tee -a "${LOGT}"
        else
          echo "${DATE}: Importing resource NFS ${IADDRESS}:${IFROM} completed with error. Aborting." | tee -a "${LOGT}"
          return 1
        fi

      else
        echo "${DATE}: Imported resource NFS ${IADDRESS}:${IROOT}${IFROM} is not available. Aborting." | tee -a "${LOGT}"
        return 1
      fi

    else
      echo "${DATE}: Resource NFS ${IADDRESS}:${IFROM} has already been imported." | tee -a "${LOGT}"
    fi
  done

  # Создаём файл блокировки, показывающий на состояние работы подсистемы
  touch "${LOCK}/nfs-import.lck"

return $?
}

# Функция прекращения импорта файловых систем хранилища посредством NFS
function stop-nfs-import() {

  # Перебираем все строки именований импортируемых ресурсов
  for INFSS in `grep --ignore-case "^nfs.import=" "${CNF}" | uniq`; do

    INFS=`echo ${INFSS} | awk -F = '{print $2}'`

    # Перебираем все строки параметров импортируемых ресурсов
    for PARAMS in `grep --ignore-case "^nfs.import.${INFS}." "${CNF}" | uniq` ; do

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".address=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && IADDRESS="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".from=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && IFROM="${BUFFER}"

      BUFFER=`echo ${PARAMS} | grep --ignore-case ".to=" | awk -F = '{print $2}'`
      [ "${BUFFER}" != "" ] && ITO="${BUFFER}"

    done

    # Проверяем, импортированы ли ресурсы
    STATE=`mount | grep --count --ignore-case "^${IADDRESS}:${IFROM}[ ]*on[ ]*${ITO}"`
    if [ "${STATE}" -ne "0" ] ; then

      # Демонтируем ресурс NFS (сочетание ключей "force" и "lazy" позволяет отцепить даже задействованный ресурс)
      umount.nfs4 "${ITO}" -fl

    fi
  done

  # Удаляем файл блокировки, показывающий на состояние работы подсистемы
  rm --force "${LOCK}/nfs-import.lck"

return $?
}

# Функция проверки состояния импортированных файловых систем хранилища посредством NFS
function check-nfs-import() {
  # Удостоверимся в том, что публикация ресурсов прошла успешно (в противном случае проверку не запускаем - бессмысленно)
  if [ -f "${LOCK}/nfs-import.lck" ]; then
    echo "check"
  fi
return $?
}

Переход к настройке автоматизации сборки файловых систем в единую точку посредством MHDDFS.

Автоматизация MHDDFS ( Автоматизация сборки файловых систем в единую точку посредством MHDDFS. )

18 сентября 2012  (обновлено 17 июля 2019)
Эта публикация отнесена в архив. Она неактуальна.

OS: "Linux Debian 5/6/7 (Lenny/Squeeze/Wheezy)".


Устанавливаем пакет утилит файловой системы MHDDFS:

# aptitude install mhddfs

Заранее готовим точку монтирования:

# mkdir -p /mnt/storage-core0

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

# vi /usr/local/etc/storage/cnf.d/mhddfs.cnf

....
mhddfs.target=/mnt/storage-core0

mhddfs.source=/mnt/import/node0
mhddfs.source=/mnt/import/node1
mhddfs.source=/mnt/import/node2
mhddfs.source=/mnt/import/node3
....


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

# vi /usr/local/etc/storage/fnc.d/mhddfs.fnc

#!/bin/bash
# This file contains the code snippet for the shell Bash v.4 (Bourne again shell)
# Файл содержит фрагмент кода для командного интерпретатора Bash v.4 (Bourne again shell)

# Функция сбора разрозненых файловых систем хранилища в одну точку монтирования
function start-mhddfs() {

  # Вычленяем адрес точки сбора файловых систем
  MTARGET=`grep --ignore-case "^mhddfs.target=" "${CNF}" | awk -F = '{print $2}'`

  # Проверяем наличие пути к точку сбора файловых систем
  if [ ! -d "${MTARGET}" ] ; then
    echo "${DATE}: Collection point file system is not available. Aborting." | tee -a "${LOGT}"
    return 1
  fi

  # Проверяем, не смонтирована ли уже сборная файловая система
  STATE=`mount | grep --count --ignore-case "on[ ]*${MTARGET}[ ]*type[ ]*fuse.mhddfs"`
  if [ "${STATE}" -eq "0" ] ; then

    MHDDFS=""

    # Перебираем все строки адресов собираемых файловых систем
    for MSOURCES in `grep --ignore-case "^mhddfs.source=" "${CNF}" | uniq`; do

      MSOURCE=`echo ${MSOURCES} | awk -F = '{print $2}'`

      # Проверяем наличие пути к собираемым файловым системам
      if [ ! -d "${MSOURCE}" ] ; then
        echo "${DATE}: Mounted file system ${MSOURCE} is not available. Aborting." | tee -a "${LOGT}"
        return 1
      fi

      # Добавляем точку монтирования в список
      MHDDFS=${MHDDFS}",${MSOURCE}"

    done

    # Отрезаем лишнюю запятую впереди строки
    MHDDFS=`echo "${MHDDFS}" | cut -c 2-`

    # Собираем файловые системы в единой точке монтирования (-o logfile=...)
    nice --adjustment=-1 mhddfs -o mlimit=100G,direct_io,async,noatime,allow_other "${MHDDFS}" "${MTARGET}" >/dev/null 2>&1

    # Проверяем успешность завершения операции
    if [ "${?}" != "0" ] ; then
      echo "${DATE}: Mounting file systems into a single point with error. Aborting." | tee -a "${LOGT}"
      return 1
    fi

    # Даём системе пару секунд на завершение монтирования
    sleep 2

    # Проверяем успешность монтирования сборной файловой системы
    STATE=`mount | grep --count --ignore-case "on[ ]*${MTARGET}[ ]*type[ ]*fuse.mhddfs"`
    if [ "${STATE}" -ne "0" ] ; then
      echo "${DATE}: Team of the file system is successfully installed." | tee -a "${LOGT}"
    else
      echo "${DATE}: Mounting the file system team with error. Aborting." | tee -a "${LOGT}"
      return 1
    fi

  else
    echo "${DATE}: Team of the file system is already installed at ${MTARGET}." | tee -a "${LOGT}"
  fi

  # Создаём файл блокировки, показывающий на состояние работы подсистемы
  touch "${LOCK}/mhddfs.lck"

return $?
}

# Функция разбора разрозненых файловых систем хранилища из единой точки монтирования
function stop-mhddfs() {

  # Вычленяем адрес точки сбора файловых систем
  MTARGET=`grep --ignore-case "^mhddfs.target=" "${CNF}" | awk -F = '{print $2}'`

  [ -d "${MTARGET}" ] && fusermount -uz "${MTARGET}"

  # Удаляем файл блокировки, показывающий на состояние работы подсистемы
  rm --force "${LOCK}/mhddfs.lck"

return $?
}

# Функция проверки состояния сборной файловой системы
function check-mhddfs() {
  # Удостоверимся в том, что публикация ресурсов прошла успешно (в противном случае проверку не запускаем - бессмысленно)
  if [ -f "${LOCK}/mhddfs.lck" ]; then
    echo "check"
  fi
return $?
}



Комментарии

Популярные сообщения из этого блога

Права на папки и файлы (unix/chmod)

Автоматическое монтирование дисков и разделов в Linux или что такое fstab? Проблема Debian

Как подключить сетевой диск Windows в Linux