В этой статье я описал создание в AWS состоящего из трех нод кластера Docker Swarm и подключение к нему общего для всех нод реплицируемого тома GlusterFS.


Введение


Режим Docker Swarm используется для создания кластера Docker-хостов. При этом, если контейнер A с подключенным к нему именованным томом voldata запущен на node1, все изменения voldata будут сохранены локально на node1. Если контейнер A будет выключен и случится так, что он снова запустится, скажем, на node3, при подключении тома voldata это хранилище окажется пустым и не будет содержать изменений, сделанных на node1.


Как это обойти?


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


Для выполнения этого упражнения я использовал три AWS EC2-инстанса, к каждому из которых было подключено по одному EBS-тому.


Подготовка серверов


В качестве ОС будем использовать Ubuntu 16.04.


Сначала пропишем имена нод в /etc/hosts:


XX.XX.XX.XX    node1
XX.XX.XX.XX    node2
XX.XX.XX.XX    node3

Затем обновим систему:


$ sudo apt update
$ sudo apt upgrade

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


$ sudo apt install -y docker.io
$ sudo apt install -y glusterfs-server

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


$ sudo systemctl start glusterfs-server
$ sudo systemctl start docker

Создадим хранилище GlusterFS:


$ sudo mkdir -p /gluster/data /swarm/volumes

Настройка GlusterFS


На всех нодах подготовим файловую систему для хранилища Gluster:


$ sudo mkfs.xfs /dev/xvdb 
$ sudo mount /dev/xvdb /gluster/data/

На node1:


$ sudo gluster peer probe node2
peer probe: success. 
$ sudo gluster peer probe node3
peer probe: success.

Создадим реплицируемый том:


$ sudo gluster volume create swarm-vols replica 3 node1:/gluster/data node2:/gluster/data node3:/gluster/data force
volume create: swarm-vols: success: please start the volume to access data

Разрешим монтирование только с localhost:


$ sudo gluster volume set swarm-vols auth.allow 127.0.0.1
volume set: success

Запустим том:


$ sudo gluster volume start swarm-vols
volume start: swarm-vols: success

Затем подмонтируем его на каждой ноде Gluster:


$ sudo mount.glusterfs localhost:/swarm-vols /swarm/volumes

Настройка Docker swarm


Наша цель: создать 1 управляющий и 2 рабочих узла.


$ sudo docker swarm init
Swarm initialized: current node (82f5ud4z97q7q74bz9ycwclnd) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join     --token SWMTKN-1-697xeeiei6wsnsr29ult7num899o5febad143ellqx7mt8avwn-1m7wlh59vunohq45x3g075r2h     172.31.24.234:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

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


$ sudo docker swarm join-token worker

To add a worker to this swarm, run the following command:

```docker  
    docker swarm join     --token SWMTKN-1-697xeeiei6wsnsr29ult7num899o5febad143ellqx7mt8avwn-1m7wlh59vunohq45x3g075r2h     172.31.24.234:2377

На обоих рабочих узлах выполним:


$ sudo docker swarm join --token SWMTKN-1-697xeeiei6wsnsr29ult7num899o5febad143ellqx7mt8avwn-1m7wlh59vunohq45x3g075r2h 172.31.24.234:2377
This node joined a swarm as a worker.

Проверим swarm-кластер:


$ sudo docker node ls
ID                           HOSTNAME          STATUS  AVAILABILITY  MANAGER STATUS
6he3dgbanee20h7lul705q196    ip-172-31-27-191  Ready   Active        
82f5ud4z97q7q74bz9ycwclnd *  ip-172-31-24-234  Ready   Active        Leader
c7daeowfoyfua2hy0ueiznbjo    ip-172-31-26-52   Ready   Active

Тестирование


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


Поставим метки на узлы swarm:


$ sudo docker node update --label-add nodename=node1 ip-172-31-24-234
ip-172-31-24-234
$ sudo docker node update --label-add nodename=node3 ip-172-31-26-52
ip-172-31-26-52

Проверим метки:


$ sudo docker node inspect --pretty ip-172-31-26-52
ID:         c7daeowfoyfua2hy0ueiznbjo
Labels:
 - nodename = node3
Hostname:       ip-172-31-26-52
Joined at:      2017-01-06 22:44:17.323236832 +0000 utc
Status:
 State:         Ready
 Availability:      Active
Platform:
 Operating System:  linux
 Architecture:      x86_64
Resources:
 CPUs:          1
 Memory:        1.952 GiB
Plugins:
  Network:      bridge, host, null, overlay
  Volume:       local
Engine Version:     1.12.1

Создадим на node1 сервис Docker, который будет использоваться для тестирования работы с файлами в общем хранилище:


$ sudo docker service create --name testcon --constraint 'node.labels.nodename == node1' --mount type=bind,source=/swarm/volumes/testvol,target=/mnt/testvol /bin/touch /mnt/testvol/testfile1.txt
duvqo3btdrrlwf61g3bu5uaom

Проверим сервис:


$ sudo docker service ls
ID            NAME     REPLICAS  IMAGE    COMMAND
duvqo3btdrrl  testcon  0/1       busybox  /bin/bash

Убедимся, что он запущен на node1:


$ sudo docker service ps testcon
ID                         NAME           IMAGE          NODE              DESIRED STATE  CURRENT STATE           ERROR
6nw6sm8sak512x24bty7fwxwz  testcon.1      ubuntu:latest  ip-172-31-24-234  Ready          Ready 1 seconds ago     
6ctzew4b3rmpkf4barkp1idhx   \_ testcon.1  ubuntu:latest  ip-172-31-24-234  Shutdown       Complete 1 seconds ago

Также проверим подмонтированные тома:


$ sudo docker inspect testcon
[
    {
        "ID": "8lnpmwcv56xwmwavu3gc2aay8",
        "Version": {
            "Index": 26
        },
        "CreatedAt": "2017-01-06T23:03:01.93363267Z",
        "UpdatedAt": "2017-01-06T23:03:01.935557744Z",
        "Spec": {
            "ContainerSpec": {
                "Image": "busybox",
                "Args": [
                    "/bin/bash"
                ],
                "Mounts": [
                    {
                        "Type": "bind",
                        "Source": "/swarm/volumes/testvol",
                        "Target": "/mnt/testvol"
                    }
                ]
            },
            "Resources": {
                "Limits": {},
                "Reservations": {}
            },
            "RestartPolicy": {
                "Condition": "any",
                "MaxAttempts": 0
            },
            "Placement": {
                "Constraints": [
                    "nodename == node1"
                ]
            }
        },
        "ServiceID": "duvqo3btdrrlwf61g3bu5uaom",
        "Slot": 1,
        "Status": {
            "Timestamp": "2017-01-06T23:03:01.935553276Z",
            "State": "allocated",
            "Message": "allocated",
            "ContainerStatus": {}
        },
        "DesiredState": "running"
    }
]

Выключим сервис и создадим его заново на node3:


$ sudo docker service create --name testcon --constraint 'node.labels.nodename == node3' --mount type=bind,source=/swarm/volumes/testvol,target=/mnt/testvol ubuntu:latest /bin/touch /mnt/testvol/testfile3.txt
5y99c0bfmc2fywor3lcsvmm9q

Убедимся, что теперь он запущен на node3:


$ sudo docker service ps testcon
ID                         NAME           IMAGE          NODE             DESIRED STATE  CURRENT STATE           ERROR
5p57xyottput3w34r7fclamd9  testcon.1      ubuntu:latest  ip-172-31-26-52  Ready          Ready 1 seconds ago     
aniesakdmrdyuq8m2ddn3ga9b   \_ testcon.1  ubuntu:latest  ip-172-31-26-52  Shutdown       Complete 2 seconds ago

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


$ ls -l /swarm/volumes/testvol/
total 0
-rw-r--r-- 1 root root 0 Jan  6 23:59 testfile3.txt
-rw-r--r-- 1 root root 0 Jan  6 23:58 testfile1.txt
Поделиться с друзьями
-->

Комментарии (16)


  1. nazarpc
    07.02.2017 16:35

    $ sudo gluster peer probe node2
    peer probe: success.
    $ sudo gluster peer probe node3
    peer probe: success.

    А что с безопасностью? Должны же быть какие-то настройки шифрования (как в примере с Docker Swarm).


    1. ertaquo
      07.02.2017 19:23
      +1

      В Gluster для безопасности используется SSL с сертификатами.


  1. mcleod095
    07.02.2017 19:16

    и интересно было бы еще увидеть тесты на производительность
    Использую glusterfs для синхронизации файлов конфигурации, решил попробовать для синхронизации файлов веб интерфейса zabbix. Так вот, если просто с файловой системы читать тот же ab выдает 40 запросов в секунду, если файлы на glusterfs то ab уже выдает 4 запроса в секунду, в итоге имеем ситуацию когда интерфейс заббикса открывается секунд 20.


    1. SchmeL
      08.02.2017 16:46

      производительность fuse драйвера для glusterfs оставляет желать лучшего. Для улучшения производительности лучше использовать встроенный в нее же NFS сервер и монтировать через mount.nfs.


    1. o_serega
      08.02.2017 17:38

      У распределенных кластерных фс на dht, будут проблемы с большим колличеством мелких файлов. Улучшить положение можно настройками кеширования для файлов.
      Как дял примера, откуда гуглить:
      gluster volume set vol performance.cache-max-file-size 10MB
      gluster volume set vol performance.cache-refresh-timeout 5
      gluster volume set vol performance.write-behind-window-size 10MB
      gluster volume set vol performance.io-thread-count 64
      gluster volume set vol performance.cache-size 5G


  1. da45nik
    07.02.2017 21:55

    Так, на заметку, если использовать AWS, то можно просто подключить ко всем нодам, один EBS, где и будут сохранятся данные.


    1. kharkevich
      07.02.2017 23:37

      Хм, может EFS?


      1. da45nik
        08.02.2017 08:05

        Да, он, по привычке назвал :)


    1. gekk0
      08.02.2017 08:34

      Получаем таким образом привязку к AWS, и при желании включить в swarm docker-сервер, развёрнутый не в амазоне, получим дополнительные проблемы.


  1. SemperFi
    07.02.2017 22:10

    а с какими параметрами были AWS EC2 инстансы, если не секрет?


    1. gekk0
      08.02.2017 08:41

      О каких параметрах идёт речь? При создании использовались стандартные настройки для t2.micro в рамках free-tier.


  1. gekk0
    08.02.2017 08:40

    этот коммент нужно удалить


  1. SchmeL
    08.02.2017 16:47

    Про настройку glusterfs уже много где написано, но вот очень мало статей, по восстановлению отвалившегося «кирпича».
    Как-то упала одна нода, и проще было заново пересоздать кластер из двух кирпичей, чем добавить новый.


    1. stychos
      10.02.2017 17:48

      как я понял, у глюстры такая парадигма — отвалился кирпич? — добавляем новый и делаем ребалансировку


  1. stychos
    10.02.2017 17:46

    репликация без тома-арбитра чревата редкостным потенциальным гембелем со split-brain'ами


    1. stychos
      10.02.2017 17:50

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