Docker: Docker Swarm кластер в AWS step-by-step

Автор: | 12/08/2016

docker_lxcПодготовка инфраструктуры (создание VPC, Master и Slave хостов и т.д.) – описаны в посте AWS: VPC – EC2 в public и private подсетях, NAT и Internet Gateway.

Ниже описан запуск Swarm-кластера по “старой” схеме, до выхода Docker 1.12 пару недель тому, в котором Docker Swarm был включен в Docker как “swarm mode“.

Отличное описание Docker Swarm есть на whatis.com.

“Старый” Swarm – документация тут>>>.

“Новый” Swarm – документация тут>>>.

Создание инстансов EC2

Нам требуется 2 Swarm-мастера, 2 Swarm-ноды и один EC2 – для Consul в роли service discovery.

Итого – два EC2 в публичной сети под мастер, 2 EC2 – под Swarm-ноды в приватной сети, 1 EC2 – для Consul, тоже в приватной сети.

Создаём ещё один Master в public сети:

$ aws ec2 run-instances --image-id ami-7615c901 --count 1 --instance-type t1.micro --key-name my-cluster --security-group-ids sg-cbbb9eac --subnet-id subnet-f6c35d92

И ещё две ноды в private сети:

$ aws ec2 run-instances --image-id ami-7615c901 --count 2 --instance-type t1.micro --key-name my-cluster --security-group-ids sg-cbbb9eac --subnet-id subnet-c3c35da7

Получаем новый Public IP для второго Master:

$ aws ec2 allocate-address --domain vpc
{
    "PublicIp": "52.210.30.69", 
    "Domain": "vpc", 
    "AllocationId": "eipalloc-268dd343"
}

Подключаем его к i-d8f93353:

$ aws ec2 associate-address --public-ip 52.210.30.69 --instance-id i-d8f93353
{
    "AssociationId": "eipassoc-397f0a5f"
}

Проверяем:

$ aws ec2 describe-instances --filters Name=vpc-id,Values=vpc-24279540 --query '[Reservations[*].Instances[*].{PrivateIP:PrivateIpAddress,InstanceId:InstanceId,PrivateDnsName:PrivateDnsName,PublicIpAddress:PublicIpAddress}]'
[
    [
        [
            {
                "InstanceId": "i-46e228cd", 
                "PrivateDnsName": "ip-10-0-1-103.eu-west-1.compute.internal", 
                "PublicIpAddress": "52.210.51.198", 
                "PrivateIP": "10.0.1.103"
            }
        ], 
        [
            {
                "InstanceId": "i-f9e32972", 
                "PrivateDnsName": "ip-10-0-2-33.eu-west-1.compute.internal", 
                "PublicIpAddress": null, 
                "PrivateIP": "10.0.2.33"
            }
        ], 
        [
            {
                "InstanceId": "i-d8f93353", 
                "PrivateDnsName": "ip-10-0-1-245.eu-west-1.compute.internal", 
                "PublicIpAddress": "52.210.30.69", 
                "PrivateIP": "10.0.1.245"
            }
        ], 
        [
            {
                "InstanceId": "i-a3ff3528", 
                "PrivateDnsName": "ip-10-0-2-185.eu-west-1.compute.internal", 
                "PublicIpAddress": null, 
                "PrivateIP": "10.0.2.185"
            }, 
            {
                "InstanceId": "i-a2ff3529", 
                "PrivateDnsName": "ip-10-0-2-186.eu-west-1.compute.internal", 
                "PublicIpAddress": null, 
                "PrivateIP": "10.0.2.186"
            }
        ]
    ]
]

swarm-cluster-1

Установка Docker Engine

Т.к. доступ к Swarm-нодам и Consul есть только из VPC – логинимся на один из swarm-master, и продолжаем оттуда.

Копируем ключ на master (если ещё не сделано):

$ scp -i my-cluster.pem my-cluster.pem [email protected]:/home/ubuntu
swarm-cluster.pem                            100% 1671     1.6KB/s   00:00

Подключаемся и продолжаем с него:

$ ssh [email protected] -i my-cluster.pem 
...
Last login: Thu Aug 11 07:40:42 2016 from 194.105.145.45
ubuntu@ip-10-0-1-103:~$

Устанавливаем Docker:

# apt-get update && curl -sSL https://get.docker.com/ | sh

Проверяем:

# docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Server Version: 1.12.0
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 0
 Dirperm1 Supported: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: host bridge null overlay
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Security Options: apparmor
Kernel Version: 3.13.0-33-generic
Operating System: Ubuntu 14.04.1 LTS
OSType: linux
Architecture: x86_64
CPUs: 1
Total Memory: 588.6 MiB
Name: ip-10-0-1-103
ID: JDX3:OYHG:LQER:CGX5:5LKQ:ICCG:YEIM:NB4V:4YCS:Y2C3:4I23:A42U
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
WARNING: No swap limit support
Insecure Registries:
 127.0.0.0/8

Для корректной работы Docker нам потребуется изменить на всех хостах ещё два файла – /etc/default/docker и создать файл /etc/profile.d/docker_host.sh.

По умолчанию Docker слушает только локальный сокет:

# ps aux | grep docker
root     29260  0.0  3.6 492500 21976 ?        Ssl  08:37   0:01 /usr/bin/dockerd --raw-logs
root     29269  0.0  0.8 199676  5356 ?        Ssl  08:37   0:00 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --shim docker-containerd-shim --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --runtime docker-runc

Нам требуется слушать внешний TCP порт.

Редактируем /etc/default/docker и добавляем DOCKER_OPTS:

...
DOCKER_OPTS="-H 10.0.1.103:2375"
...

Второй файл – /etc/profile.d/docker_host.sh, потребуется для того, что бы не задавать переменную $DOCKER_HOST каждый раз:

# cat /etc/profile.d/docker_host.sh`
export DOCKER_HOST=10.0.1.103:2375
# . /etc/profile.d/docker_host.sh

Перезапускаем, проверяем:

# service docker restart
docker stop/waiting
docker start/running, process 29833
# netstat -anp | grep 2375
tcp        0      0 10.0.1.103:2375          0.0.0.0:*               LISTEN      29833/dockerd   

Проверяем как работает сам Docker:

# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c04b14da8d14: Pull complete 
Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

Настройка нод

Находим Private IP остальных инстансов (с рабочей машины, где есть AWS CLI):

$ aws ec2 describe-instances --filters Name=vpc-id,Values=vpc-24279540 --query '[Reservations[*].Instances[*].PrivateIpAddress]' --output text
10.0.1.103
10.0.2.33
10.0.1.245
10.0.2.185
10.0.2.186

10.0.1.103 – Private IP нашего Master0, на который мы только что установили Docker Engine.

С помощью простого bash-скрипта docker_setup_multi_hosts.sh – устанавливаем на все остальные ноды, добавив внутренние IP (кроме первого мастера) в переменную $IPS:

#!/usr/bin/env bash

IPS="10.0.2.33 10.0.1.245 10.0.2.185 10.0.2.186"
USER="ubuntu"
RSA_KEY="my-cluster.pem"

docker_update () {
    echo -e "\nInstalling Docker to $1\n"
    ssh -t -t -oStrictHostKeyChecking=no -i "$RSA_KEY" "$USER@$1" "bash -c '
        sudo apt-get update && curl -sSL https://get.docker.com/ | sh
    '"
}

copy_opts () {

    echo -e "\nCopy /etc/profile.d/docker_host.sh to $1\n"
    # subsctitute Master's IP to Node's IP
    sed -e 's/10.0.1.103/'"$1"'/g' /etc/profile.d/docker_host.sh > docker_host.sh
    scp -i "$RSA_KEY" docker_host.sh "$USER@$1":/home/$USER
    rm docker_host.sh

    echo -e "\nCopy /etc/default/docker to $1\n"
    # subsctitute Master's IP to Node's IP
    sed -e 's/10.0.1.103/'"$1"'/g' /etc/default/docker > docker
    scp -i "$RSA_KEY" docker "$USER@$1":/home/$USER
    rm docker
}

move_opts () {
    ssh -t -t -oStrictHostKeyChecking=no -i "$RSA_KEY" "$USER@$1" "bash -c '
        sudo mv /home/$USER/docker /etc/default/docker
        sudo mv /home/$USER/docker_host.sh /etc/profile.d/docker_host.sh
    '"
}

docker_restart () {
    echo -e "\nRestarting Docker daemon.\n"
    ssh -t -t -oStrictHostKeyChecking=no -i "$RSA_KEY" "$USER@$1" "bash -c '
        sudo service docker restart
    '"
}

for host in $IPS; do
    docker_update $host
    copy_opts $host
    move_opts $host
    docker_restart $host
done

Запускаем  и идём пить чай:

# ./docker_setup_multy_hosts.sh 

Installing Docker to 10.0.2.33

sudo: unable to resolve host ip-10-0-2-33
Ign http://eu-west-1.ec2.archive.ubuntu.com trusty InRelease
Get:1 http://eu-west-1.ec2.archive.ubuntu.com trusty-updates InRelease [65.9 kB]
...
Remember that you will have to log out and back in for this to take effect!

Connection to 10.0.2.186 closed.

Готово.

Проверяем на любом хосте:

# ssh -i my-cluster.pem [email protected]
...
$ docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Server Version: 1.12.0
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs

Запуск Consul

Consul будет играть роль service discovery (см. тут>>> и тут>>>).

Ручная установка Consul описана в посте Consul: установка и базовые операции.

Подключаемся на ноду Consul0:

$ ssh [email protected] -i my-cluster.pem
...
ubuntu@ip-10-0-2-186:~$ 

Запускаем Consul на порту 8500:

$ sudo -s
# docker run -d -p 8500:8500 --name=consul progrium/consul -server -bootstrap
d1c0b41ca61f7276b79f58b675d2980dd8f04b4207dd501c6049c45c6bcc7053

Проверяем:

# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                            NAMES
d1c0b41ca61f        progrium/consul     "/bin/start -server -"   18 seconds ago      Up 17 seconds       53/tcp, 53/udp, 8300-8302/tcp, 8400/tcp, 8301-8302/udp, 0.0.0.0:8500->8500/tcp   consul

Проверяем Consul – логинися в контейнер и выполняем consul members:

# docker exec -ti d1c0b41ca61f consul members
Node          Address          Status  Type    Build  Protocol  DC
d1c0b41ca61f  172.17.0.2:8301  alive   server  0.5.2  2         dc1

Запуск Swarm-master0

Возвращаемся к первому мастеру:

bash-4.3# exit
ubuntu@ip-10-0-2-186:~$ logout
Connection to 10.0.2.186 closed.

(хотя можно оставить октрытым во вкладке для дебага позже)

Запускаем первый Swarm-manager:

# docker run -d -p 4000:4000 swarm manage -H :4000 --replication --advertise 10.0.1.103:4000 consul://10.0.2.186:8500
Unable to find image 'swarm:latest' locally
latest: Pulling from library/swarm
148ee29ca1ff: Pull complete 
6059dedfee26: Pull complete 
64a5b64b008f: Pull complete 
Digest: sha256:5ff2c5e085320680a1094fdd279d3420a9b240887287d4610810903e46a3ee50
Status: Downloaded newer image for swarm:latest
b5c8f53123aa763998213383b901914d29353a03cd85a51397bdbcf8cf341655

Тут: 10.0.1.103 – IP мастера, 10.0.2.186 – адрес Consul-ноды в приватной подсети.

Проверяем:

# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
37e262e19cab        swarm               "/swarm manage -H :40"   19 seconds ago      Up 18 seconds       2375/tcp, 0.0.0.0:4000->4000/tcp   reverent_tesla

Повторяем на втором мастере:

# ssh [email protected] -i my-cluster.pem
$ sudo -s
# docker run -d -p 4000:4000 swarm manage -H :4000 --replication --advertise 10.0.1.245:4000 consul://10.0.2.186:8500
12a85ee872e4324c4605e23ee19fff78b3f119377729286880f4a4104757c56b

Возвращаемся на Мастер0:

root@ip-10-0-1-245:~# exit
ubuntu@ip-10-0-1-245:~$ logout
Connection to 10.0.1.245 closed.
root@ip-10-0-1-103:~#

Добавление нод в кластер

Подключаем обе ноды.

Node0:

# ssh [email protected] -i my-cluster.pem
$ sudo -s
# docker run -d swarm join --advertise=10.0.2.33:2375 consul://10.0.2.186:8500
Unable to find image 'swarm:latest' locally
latest: Pulling from library/swarm
148ee29ca1ff: Pull complete 
6059dedfee26: Pull complete 
64a5b64b008f: Pull complete 
Digest: sha256:5ff2c5e085320680a1094fdd279d3420a9b240887287d4610810903e46a3ee50
Status: Downloaded newer image for swarm:latest
3725c67a839bb13d5847dd97941b55107be94a69c4b03272073f7ef52570880e
root@ip-10-0-2-33:~# exit
ubuntu@ip-10-0-2-33:~$ logout
Connection to 10.0.2.33 closed.

Node1:

# ssh [email protected] -i my-cluster.pem
$ sudo -s
# docker run -d swarm join --advertise=10.0.2.185:2375 consul://10.0.2.186:8500
Unable to find image 'swarm:latest' locally
latest: Pulling from library/swarm
148ee29ca1ff: Pull complete 
6059dedfee26: Pull complete 
64a5b64b008f: Pull complete 
Digest: sha256:5ff2c5e085320680a1094fdd279d3420a9b240887287d4610810903e46a3ee50
Status: Downloaded newer image for swarm:latest
85febf58b870f10171a2406341f3f657e10c74c1f5bfa43bf637ba7b1e3b2822
root@ip-10-0-2-185:~# exit
ubuntu@ip-10-0-2-185:~$ logout
Connection to 10.0.2.185 closed.

Вернувшись в консоль Master0 – проверяем статус:

# docker -H :4000 info
Containers: 10
 Running: 2
 Paused: 0
 Stopped: 8
Images: 2
Server Version: swarm/1.2.4
Role: primary
Strategy: spread
Filters: health, port, containerslots, dependency, affinity, constraint
Nodes: 2
 ip-10-0-2-33: 10.0.2.33:2375
  └ ID: 75JN:6IUX:VFEH:7WRA:LBUU:UZIU:P34I:VAJP:AALI:2G4I:QJXB:ETUE
  └ Status: Healthy
  └ Containers: 5 (1 Running, 0 Paused, 4 Stopped)
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 618 MiB
  └ Labels: kernelversion=3.13.0-33-generic, operatingsystem=Ubuntu 14.04.1 LTS, storagedriver=aufs
  └ UpdatedAt: 2016-08-11T15:39:37Z
  └ ServerVersion: 1.12.0
 ip-10-0-2-185: 10.0.2.185:2375
  └ ID: MR63:UDFV:IH65:2ZWE:CR3B:UCLP:4YGC:FEY3:4ESN:WU2E:3MUJ:67DG
  └ Status: Healthy
  └ Containers: 5 (1 Running, 0 Paused, 4 Stopped)
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 618 MiB
  └ Labels: kernelversion=3.13.0-33-generic, operatingsystem=Ubuntu 14.04.1 LTS, storagedriver=aufs
  └ UpdatedAt: 2016-08-11T15:40:04Z
  └ ServerVersion: 1.12.0
Plugins:
 Volume: 
 Network: 
Swarm: 
 NodeID: 
 Is Manager: false
 Node Address: 
Security Options:
Kernel Version: 3.13.0-33-generic
Operating System: linux
Architecture: amd64
CPUs: 2
Total Memory: 1.207 GiB
Name: 80e595613215
Docker Root Dir: 
Debug Mode (client): false
Debug Mode (server): false
WARNING: No kernel memory limit support

И напрямую из Consul – смотрим ноды:

# docker run swarm list consul://10.0.2.186:8500
time="2016-08-11T09:48:54Z" level=info msg="Initializing discovery without TLS" 
10.0.2.185:2375
10.0.2.33:2375

На сообщение “Initializing discovery without TLS” можно пока забить, т.к. всё работает по внутренней приватной сети без доступа снаружи, но потом лучше настроить TLS.

Обращаем внимание на строку:

...
Role: primary
...

Если выполнить на втором мастере – то увидим:

# ssh -i my-cluster.pem [email protected] "bash -c 'sudo docker -H :4000 info | grep Role:'"
sudo: unable to resolve host ip-10-0-1-245
WARNING: No kernel memory limit support
Role: replica

Запускаем на ферме NGINX:

# docker -H :4000 run -d nginx
dec406413b21052c5ce305c3f8485603dd951cb0487775c1027c716f591e899c

Проверяем:

# docker -H :4000 ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
dec406413b21        nginx               "nginx -g 'daemon off"   4 seconds ago       Up 3 seconds        80/tcp, 443/tcp     ip-10-0-2-33/elegant_jepsen

Ссылки по теме:

Build a Swarm cluster for production (какой production…)

The Docker Ecosystem: Service Discovery and Distributed Configuration Stores

Set up Docker Swarm Cluster Using Consul

Running a Small Docker Swarm Cluster