image
Поиск оптимального хранилища — это довольно сложный процесс, у всего есть плюсы и минусы. Разумеется, лидером в данной категории является CEPH, но это довольно сложная система, хотя и с очень богатым функционалом. Для нас такая система избыточна, учитывая то, что нам нужно было простое реплицируемое хранилище в режиме master-master на пару терабайт. Изучив много материала, было принято решение протестировать наиболее модный на рынке продукт для интересующей нас схемы. В связи с тем, что готового решения подобного плана найдено не было, хочется поделиться своими наработками по данной теме и описать проблемы, с которыми мы столкнулись в процессе развертывания.

Цели


Что мы ждали от нового хранилища:

  • Возможность работы с четным числом нод для репликации.
  • Простая установка, настройка, поддержка
  • Система должна быть взрослой, проверенной временем и пользователями
  • Возможность расширения места на хранилище без простоя системы
  • Хранилище должно быть совместимо с Kubernetes
  • Должен быть автоматический Failover при падениях одного из узлов

Именно по последнему пункту у нас и возникло много вопросов.

Развертывание


Для развертывания было создано две виртуалки на CentOs 8. К каждой подключено по дополнительному диску с СХД.

Предварительная подготовка


Для GlusterFS нужно выделить отдельный диск с XFS, чтобы он никак не влиял на систему.

Выделяем партицию:

$ fdisk /dev/sdb
Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1):  1
First sector (2048-16777215, default 2048): 
Last sector, +sectors or +size{K,M,G,T,P} (2048-16777215, default 16777215): 
 
Created a new partition 1 of type ‘Linux’ and of size 8 GiB.
Command (m for help): w 

The partition table has been altered.
Calling ioctl() to re-read partition table. Syncing disks.

Форматируем в XFS и монтируем:

$ mkfs.xfs /dev/sdb1
$ mkdir /gluster
$ mount /dev/sdb1 /gluster

И в довершении закидываем запись в /etc/fstab для автоматического монтирования директории при запуске системы:

/dev/sdb1       /gluster        xfs     defaults        0       0

Установка


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

На обеих нодах ставим и запускаем glusterfs последней версии:

$ wget -P /etc/yum.repos.d  https://download.gluster.org/pub/gluster/glusterfs/LATEST/CentOS/glusterfs-rhel8.repo
$ yum -y install yum-utils
$ yum-config-manager --enable PowerTools
$ yum install -y glusterfs-server
$ systemctl start glusterd

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

$ gluster peer probe gluster-02.example.com

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

$ gluster peer status
Number of Peers: 1

Hostname: gluster-02.example.com
Uuid: a6de3b23-ee31-4394-8bff-0bd97bd54f46
State: Peer in Cluster (Connected)
Other names:
10.10.6.72

Теперь можно создать Volume в который мы будем писать.

gluster volume create main replica 2 gluster-01.example.com:/gluster/main gluster-02.example.com:/gluster/main force

Где:


Запускаем Volume и проверяем его работоспособность:

gluster volume start main
gluster volume status main

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

$ gluster volume set main network.ping-timeout 5
$ gluster volume set main cluster.quorum-type fixed
$ gluster volume set main cluster.quorum-count 1
$ gluster volume set main performance.quick-read on

Этими простыми действиями мы собрали кластер GlusterFS. Осталось подключиться к нему и проверить работоспособность. На клиентской машине установлена Ubuntu, для монтирования нужно установить клиент:

$ add-apt-repository ppa:gluster/glusterfs-7
$ apt install glusterfs-client
$ mkdir /gluster
$ mount.glusterfs gluster-01.example.com:/main /gluster

Gluster при подключении к одной из нод, отдает адреса всех нод и автоматически подключается ко всем. Если клиент уже подключился, то отказ одной из нод не приведет к остановке работы. Но вот если будет недоступен первый узел, подключиться в случае разрыва сессии уже не получится. Для этого при монтировании можно передать параметр backupvolfile с указанием второй ноды.
mount.glusterfs gluster-01.example.com:/main /gluster -o backupvolfile-server=gluster-02.example.com

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

Подключение к Kubernetes


На этом этапе и начались вопросы: «А как это подключить?». И здесь есть несколько вариантов. Рассмотрим их.

Heketi


Самый популярный и рекомендуемый — использовать внешний сервис: heketi. heketi — это прослойка между kubernetes и gluster, которая позволяет управлять хранилищем через http и работать с ним. Но heketi и будет той самой единой точкой отказа, т.к. сервис не кластеризуется. Второй инстанс данного сервиса не сможет самостоятельно работать, т.к. любые изменения хранятся в локальной БД. Запуск в kubernetes данного сервиса также не подходит, т.к. ему нужен статичный диск, на котором будет хранится его БД. В связи с этим данный вариант оказался самым не подходящим.

Endpoint в Kubernetes


Если у вас Kubernetes на системах с пакетными менеджерами, то это очень удобный вариант. Смысл заключается в том, что для всех серверов GlusteFS в Kubernetes создается общий Endpoint. На этот Endpoint вешается сервис и к этому сервису мы уже будем монтироваться. Для работы данного варианта необходимо на каждой ноде Kubernetes установить glusterfs-client и убедиться в возможности монтирования. В Kubernetes деплоим следующий конфиг:

apiVersion: v1
kind: Endpoints
metadata: 
  name: glusterfs-cluster
subsets:
  - addresses:
      #Тут указываем ip наших серверов
      - ip: 10.10.6.71
    ports:
      #Тут просто можно оставить 1, порт роли не играет
      - port: 1
  - addresses:
      - ip: 10.10.6.72
    ports:
      - port: 1

---
apiVersion: v1
kind: Service
metadata:
  name: glusterfs-cluster
spec:
  ports:
  - port: 1

Теперь мы можем создать простой тестовый деплоймент и проверить как происходит монтирование. Ниже пример простого тестового деплоймента:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gluster-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gluster-test
  template:
    metadata:
      labels:
        app: gluster-test
    spec:
      volumes:
      - name: gluster
        glusterfs:
          endpoints: glusterfs-cluster
          path: main
      containers:
      - name: gluster-test
        image: nginx
        volumeMounts:
        - name: gluster
          mountPath: /gluster

Данный вариант нам не подошел, потому что у нас на всех нодах Kubernetes стоит container-linux. Пакетного менеджера там нет, поэтому установить gluster-client для монтирования не удалось. В связи с этим был найден третий вариант, который и было решено использовать.

GlusterFS + NFS + keepalived


GlusterFS до недавнего времени предлагал собственный NFS сервер, но теперь для NFS используется внешний сервис nfs-ganesha. Об этом написано довольно немного, в связи с этим разберем как это настроить.

Репозиторий нужно прописать вручную. Для этого в файле /etc/yum.repos.d/nfs-ganesha.repo вносим:

[nfs-ganesha]
name=nfs-ganesha
baseurl=https://download.nfs-ganesha.org/2.8/2.8.0/RHEL/el-8/$basearch/
enabled=1
gpgcheck=1
[nfs-ganesha-noarch]
name=nfs-ganesha-noarch
baseurl=https://download.nfs-ganesha.org/2.8/2.8.0/RHEL/el-8/noarch/
enabled=1
gpgcheck=1

И устанавливаем:

yum -y install nfs-ganesha-gluster --nogpgcheck

После установки проводим базовую конфигурацию в файле /etc/ganesha/ganesha.conf.

# create new
NFS_CORE_PARAM {
    # possible to mount with NFSv3 to NFSv4 Pseudo path
    mount_path_pseudo = true;
    # NFS protocol
    Protocols = 3,4;
}
EXPORT_DEFAULTS {
    # default access mode
    Access_Type = RW;
}
EXPORT {
    # uniq ID
    Export_Id = 101;
    # mount path of Gluster Volume
    Path = "/gluster/main";
    FSAL {
        # any name
        name = GLUSTER;
        # hostname or IP address of this Node
        hostname="gluster-01.example.com";
        # Gluster volume name
        volume="main";
    }
    # config for root Squash
    Squash="No_root_squash";
    # NFSv4 Pseudo path
    Pseudo="/main";
    # allowed security options
    SecType = "sys";
}
LOG {
    # default log level
    Default_Log_Level = WARN;
}

Нужно стартовать сервис, включить nfs для нашего volume и проверить то, что он включился.

$ systemctl start nfs-ganesha
$ systemctl enable nfs-ganesha
$ gluster volume set main nfs.disable off
$ gluster volume status main

В результате статус должен показать что запустился nfs сервер для нашего volume. Нужно сделать mount и проверить.

mkdir /gluster-nfs
mount.nfs gluster-01.example.com:/main /gluster-nfs

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

Установка keepalived в CentOs производится сразу через пакетный менеджер.

$ yum install -y keepalived

Производим конфигурацию сервиса в файле /etc/keepalived/keepalived.conf:

global_defs {
    notification_email {
        admin@example.com
    }
    notification_email_from alarm@example.com
    smtp_server mail.example.com
    smtp_connect_timeout 30

    vrrp_garp_interval 10
    vrrp_garp_master_refresh 30
}

#Cкрипт для проверки того, что гластер запущен. Если проверка не пройдет, VIP переедет.
vrrp_script chk_gluster {
    script "pgrep glusterd"
    interval 2
}

vrrp_instance gluster {
    interface ens192
    state MASTER #Для второй ноды тут будет BACKUP
    priority 200 #Для второй ноды тут будет число меньше, например 100
    virtual_router_id 1
    virtual_ipaddress {
        10.10.6.70/24
    }

    unicast_peer {
        10.10.6.72 #на второй ноде тут надо указать будет адрес первой
    }

    track_script {
        chk_gluster
    }
}

Теперь мы можем запустить сервис и проверить что VIP на ноде появился:

$ systemctl start keepalived
$ systemctl enable keepalived
$ ip addr
1: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:97:55:eb brd ff:ff:ff:ff:ff:ff
    inet 10.10.6.72/24 brd 10.10.6.255 scope global noprefixroute ens192
       valid_lft forever preferred_lft forever
    inet 10.10.6.70/24 scope global secondary ens192
       valid_lft forever preferred_lft forever

Если у нас все заработало, то осталось внести PersistentVolume в Kubernetes и создать тестовый сервис для проверки работы.

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: gluster-nfs
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    server: 10.10.6.70
    path: /main

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: gluster-nfs
spec:
 accessModes:
 - ReadWriteMany
 resources:
   requests:
     storage: 10Gi
 volumeName: "gluster-nfs"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gluster-test
  labels:
    app: gluster-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gluster-test
  template:
    metadata:
      labels:
        app: gluster-test
    spec:
      volumes:
      - name: gluster
        persistentVolumeClaim:
          claimName: gluster-nfs
      containers:
      - name: gluster-test
        image: nginx
        volumeMounts:
        - name: gluster
          mountPath: /gluster

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

Итоги


В данной статье мы рассмотрели 3 возможных варианта подключения GlusterFS к Kubernetes, в нашем варианте возможно добавить provisioner в Kubernetes, но он пока нам не нужен. Осталось добавить результаты тестов производительности между NFS и Gluster на одних и тех же нодах.

Файлы по 1Mb:

sync; dd if=/dev/zero of=tempfile bs=1M count=1024; sync
Gluster: 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 2.63496 s, 407 MB/s
NFS: 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 5.4527 s, 197 MB/s

Файлы по 1Kb:

sync; dd if=/dev/zero of=tempfile bs=1K count=1048576; sync
Gluster: 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 70.0508 s, 15.3 MB/s
NFS: 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 6.95208 s, 154 MB/s

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