
По заверениям разработчиков на сегодняшний день Docker Swarm легко и просто позволяет масштабировать ваши приложения на множество хостов с десятков до тысяч контейнеров. Но выполняется ли это обещание в реальности?
Давайте проверим запуск 1000 контейнеров на трех виртуальных серверах в K2 Cloud и узнаем об этом наверняка.
Весь эксперимент мы будем проводить на трех виртуальных серверах Ubuntu 16.04 размером m1.large (2 vCPU, 8 Gb RAM). Все серверы подключены к единой виртуальной сети и имеют следующие имена:
Разрешаем доступ к виртуальной сети по SSH (22/TCP) и 2376/TCP для управления хостами с Docker Machine.
Установим Docker на каждый из серверов следующими командами:
docker-machine create -d generic
--generic-ssh-user ec2-user
--generic-ssh-key ~/.ssh/id_rsa
--generic-ip-address 185.12.28.100
master.swarm-demo.avmaksimov.ru
docker-machine create -d generic
--generic-ssh-user ec2-user
--generic-ssh-key ~/.ssh/id_rsa
--generic-ip-address 185.102.122.120
node-1.swarm-demo.avmaksimov.ru
docker-machine create -d generic
--generic-ssh-user ec2-user
--generic-ssh-key ~/.ssh/id_rsa
--generic-ip-address 185.12.28.39
node-2.swarm-demo.avmaksimov.ru
Проверим установку:
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
master.swarm-demo.avmaksimov.ru - generic Running tcp://185.12.28.100:2376 v17.03.1-ce
node-1.swarm-demo.avmaksimov.ru - generic Running tcp://185.102.122.120:2376 v17.03.1-ce
node-2.swarm-demo.avmaksimov.ru - generic Running tcp://185.12.28.39:2376 v17.03.1-ce
Инициализируем Swarm кластер. На master узле выполним команду:
eval $(docker-machine env master.swarm-demo.avmaksimov.ru) &&
docker swarm init
Команда для выполнения на node-1 и node-2 будет в выводе предыдущей команды. Не забудьте переключить переменные окружения для клиента docker:
eval $(docker-machine env node-1.swarm-demo.avmaksimov.ru) &&
docker swarm join
--token SWMTKN-1-5alztmf6vhq9sbxrlgd6fysdg1xasdfkoxahl7by9q6vt621u-aso816y7t5qk1n5l9qboxfi0g
192.168.1.11:2377
This node joined a swarm as a worker.
eval $(docker-machine env node-2.swarm-demo.avmaksimov.ru) &&
docker swarm join
--token SWMTKN-1-5alztmf6vhq9sbxrlgd6fysdg1xasdfkoxahl7by9q6vt621u-aso816y7t5qk1n5l9qboxfi0g
192.168.1.11:2377
This node joined a swarm as a worker.
Проверим состояние Swarm кластера:
docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
co835i5lt8ojqi8av39euuvdp node-1.swarm-demo.avmaksimov.ru Ready Active
jokrnk2o566g6flcim6hlmaow node-2.swarm-demo.avmaksimov.ru Ready Active
o6b0kelc4n6u57ep6xt7bacyj * master.swarm-demo.avmaksimov.ru Ready Active Leader
Создадим для запуска тестовых контейнеров отдельную сеть:
eval $(docker-machine env master.swarm-demo.avmaksimov.ru) &&
docker network create --driver overlay --subnet 10.0.0.0/20
--attachable swarm-scale-test-net
bx2idzv2629ed8mlfnvi4v6pj
Эта сеть будет использоваться всеми контейнерами для коммуникации друг с другом. Сетевая маска /20 даст нам возможность запускать в этой сети до 4 тысяч контейнеров. Ключ —attachable дает нам возможность запускать в этой сети не управляемые Docker Swarm (обычные) контейнеры, запускаемые при помощи команды docker run.
Из-за того, что на каждом хосте будет запущено очень большое количество контейнеров (а это в свою очередь означает запуск очень большого количества системных процессов Docker), нам нужно немного настроить некоторые sysctl параметры, чтобы избежать нестабильности работы нашего сервиса.
Самое первое сообщение, которое вы увидите в выводе dmesg, если оставить sysctl настройки по умолчанию — neighbour: arp_cache: neighbor table overflow!. Эта проблема вызвана слишком маленьким ARP кешом по умолчанию, который будет не успевать очищаться.
Каждый запускаемый вами контейнер подключается к трем сетям:
Т.к. в этих сетях для каждого контейнера будут назначены несколько IP-адресов, то суммарно получается порядка 5000 IP адресов для работы запускаемого нами сервиса.
ARP кешем управляют следующие настройки:
sysctl -w net.ipv4.neigh.default.gc_thresh1=8096
sysctl -w net.ipv4.neigh.default.gc_thresh2=12288
sysctl -w net.ipv4.neigh.default.gc_thresh3=16384
Вообще говоря, работа с ARP кешем документирована слабо, поэтому обычно эти значения подбирают, умножая предыдущие значения на 2, до тех пор, пока система не начнет чувствовать себя нормально.
Также вам определенно понадобятся и другие sysctl настройки, которые касаются главным образом того, сколько соединений может быть открыто в данный момент времени, какие порты используются для соединений, как быстро перерабатываются соединения и т.д.
# Определить большее количество портов (диапазон) для подключения
net.ipv4.ip_local_port_range=1024 65000
# Быстрее переиспользовать закрытые сокеты
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_fin_timeout=15
# Максимальное количество "накопившихся" сокетов. По умолчанию 128.
net.core.somaxconn=4096
net.core.netdev_max_backlog=4096
# 16MB на сокет - скорее всего не будет
# использоваться так много
net.core.rmem_max=16777216
net.core.wmem_max=16777216
# Общий сетевой тюнинг
net.ipv4.tcp_max_syn_backlog=20480
net.ipv4.tcp_max_tw_buckets=400000
net.ipv4.tcp_no_metrics_save=1
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_syn_retries=2
net.ipv4.tcp_synack_retries=2
net.ipv4.tcp_wmem=4096 65536 16777216
# Отслеживание соединений для предотвращения отбрасывания пакетов
# (обычно проблема на балансировщиках)
net.netfilter.nf_conntrack_max=262144
net.ipv4.netfilter.ip_conntrack_generic_timeout=120
net.netfilter.nf_conntrack_tcp_timeout_established=86400
# Настройки ARP кеша для высоко нагруженного docker swarm-а
net.ipv4.neigh.default.gc_thresh1=8096
net.ipv4.neigh.default.gc_thresh2=12288
net.ipv4.neigh.default.gc_thresh3=16384
После того, как был произведен тюнинг хостов, давайте попробуем запустить наш сервис на 1000 контейнеров в Docker Swarm кластере. В качестве сервиса будет использоваться очень маленький образ titpetric/sonyflake, предназначенный для генерации ID-шников и отдачи их по HTTP. Исходный код сервиса доступен на GitHub
docker service create
--replicas 1000
--network swarm-scale-test-net
--update-parallelism 5
--name sonyflake
-p 80:80
titpetric/sonyflake
Пара минут и все контейнеры запустились!
docker service ls
ID NAME MODE REPLICAS IMAGE
uxtfzjy7q203 sonyflake replicated 1000/1000 titpetric/sonyflake:latest
В качестве теста будет использоваться Wrk — современный инструмент тестирования производительности HTTP, способный генерировать значительную нагрузку при работе даже с одним многоядерным процессором. Он сочетает в себе многопоточный дизайн с масштабируемыми системами уведомления о событиях, такими как epoll и kqueue.
docker run
--net=swarm-scale-test-net
--rm williamyeh/wrk
-t 6
-c 30
http://sonyflake
Running 10s test @ http://sonyflake
6 threads and 30 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.69ms 1.87ms 45.42ms 91.81%
Req/Sec 2.55k 0.88k 8.27k 80.97%
152568 requests in 10.08s, 29.79MB read
Requests/sec: 15137.45
Transfer/sec: 2.96MB
Уменьшим количество запущенных контейнеров до 30.
docker service scale sonyflake=30
Проверить статус операции можно командой
docker ps -a --format "{{.Status}}" | sort | uniq -c
470 Dead
181 Removal In Progress
8 Exited (2) About a minute ago
8 Up 56 minutes
3 Up 58 minutes
3 Up 59 minutes
8 Up About an hour
Как только количество контейнеров в статусе «Removal in Progress» станет равно нулю и docker service ls будет показывать 30/30, проведем тест еще раз
docker run
--net=swarm-scale-test-net
--rm williamyeh/wrk
-t 6
-c 30
http://sonyflake
Running 10s test @ http://sonyflake
6 threads and 30 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.36ms 1.06ms 18.20ms 80.58%
Req/Sec 2.64k 1.26k 7.63k 83.67%
157952 requests in 10.09s, 30.84MB read
Requests/sec: 15651.65
Transfer/sec: 3.06MB
Результаты теста показывают примерно 30% увеличение времени ответа каждого отдельного контейнера при увеличении количества запускаемых контейнеров в 30 раз (с 30 до 1000), обуславливаемое контекстным переключением CPU каждого сервера кластера, затрачиваемое на обслуживание работы каждого отдельного docker процесса.
Мы успешно протестировали возможности Docker Swarm кластера и запустили в нем 1000 контейнеров. Создать и запустить для этого теста виртуальную инфраструктуру в K2 Cloud заняло у меня считанные минуты, а производительности даже таких небольших виртуальных серверов с лихвой хватило на обслуживание работы такого количества контейнеров. Само собой, чтобы запустить еще большее количество микросервисных контейнеров на одном виртуальном сервере, нужно еще чуть-чуть увеличить параметры sysctl.