В современном вебе, да и не только вебе, мы вынуждены создавать приложения, масштабируемые горизонтально. Как строятся такие решения всем давно хорошо известно. Чаще всего берется куча серверов, на которых разворачивается непосредственно приложение, притом все свое состояние оно держит не локально, а в каких-то СУБД. Перед этими серверами ставится балансировщик нагрузки в виде еще нескольких серверов с Nginx или HAProxy. Пользователи же направляются на балансировщик при помощи DNS. Но поддерживать все это хозяйство довольно хлопотно. И тут нам на помощь приходит Amazon!
Примечание: Со временем эта заметка может несколько устареть, так как консоль AWS меняется, но идея должна остаться прежней.
Проведем небольшой эксперимент. Все описанные шаги вы можете повторить самостоятельно. Amazon не возьмет с вас за это денег, если действовать быстро и все за собой подчистить.
Зайдем в консоль AWS , EC2 → Instances → Launch Instance. В появившемся списке операционных систем выбираем Ubuntu Server 14.04 LTS (HVM), SSD Volume Type, жмем Select. Тип интсанса возьмем t2.micro, так как нам для тестовых целей. Дополнительных настроек не требуется, поэтому жмем Review and Launch → Launch.
Нас спросят про ключи, при помощи которых мы хотим авторизоваться в EC2 инстансе. Выбираем Create a new key pair, вводим какое-нибудь имя, жмем Download Key Pair. Полученный .pem файл куда-нибудь сохраняем. Теперь жмем Launch Instances. Должны увидеть «Your instances are now launching». Ждем, пока инстанс изменит свое состояние в списке на зеленое «running».
Смотрим столбец Public IP. Зайти на хост можно при помощи полученного ранее pem файла:
Настроим Nginx , это мы с вами уже умеем. Он будет играть роль нашего тестового веб-приложения. Статику будем держать в /var/www. Создадим два файла.
Файл index.html:
Файл status.txt:
По умолчанию порт 80 наших инстансов не торчит наружу. В табличке с интансами находим столбец Security Groups (у меня он последний), жмем по ссылке, затем Create Security Group, заполняем как-то так:
… разрешая входящий HTTP трафик. Группу называем web-servers, жмем Create. В списке снова находим инстанс, Actions → Networking → Change Security Groups, ставим галочку рядом с web-servers, жмем Assign Security Groups. Открываем браузер, в адресной строке вводим IP инстанса, должны увидеть «hello».
Теперь у нас есть мастер-инстанс, который мы будем клонировать, поднимая больше или меньше инстансов в зависимости от текущей нагрузки. Actions → Image → Create Image. В качестве имени пишем nginx-image-v1, жмем Create Image. Должны увидеть «Create Image request received». В списке Images → AMIs скоро появится полученный образ. Инстанс при этом будет остановлен на время создания образа, а потом снова запущен. Итак, теперь у нас есть образ для копирования.
Далее нам понадобится балансировщик нагрузки. Идем в Network & Security → Load Balancers, жмем Create Load Balancer. Имя вводим test-load-balancer, жмем Continue. Выбираем протокол HTTP (также поддерживаются HTTPS, SSL и TCP). Далее требуется настроить health check. Вводим адрес «/status.txt». На шаге Assign Security Groups добавляем галочку напротив web-servers. На шаге Add EC2 Instances пока ничего не добавляем, а также снимаем галочку Enable Cross-Zone Load Balancing. В общем случае держать сервера в нескольких AZ — хорошая идея, но в контексте этой заметки балансировка нагрузки между несколькими AZ все только усложнит. Далее Continue → Continue → Create. Должны увидеть «Successfully created load balancer». Обратите внимание, что мы пропустили шаг указания каких-то там тэгов. В рамках этой заметки они нам не пригодятся, но вообще, это очень удобная штука, при помощи которой приложение через AWS SDK может определить, например, в каком окружении (dev/stage/prod) оно работает и не только.
Итак, есть балансер, но за ним пока что нет машин. При этом есть образ, из которого мы хотим создавать машины. Чтобы связать все это хозяйство воедино, требуется создать Auto Scaling Group. Идем в Auto Scaling → Auto Scaling Groups, жмем Create Auto Scaling Group, затем Create launch configuration. Launch Configuration определяет, какие машины мы будем запускать при увеличении нагрузки. Выбираем My AMIs → nginx-image-v1 → Select. Тип инстанса выбираем t2.micro, желательно использовать тот же, из которого создавался AMI. На следующем шаге вводим имя launch-configuration-v1, далее идем до шага Configure Security Group, добавляем разрешение ломиться на 80-ый порт откуда угодно. На самом деле, поскольку эти инстансы будут за балансировщиком, возможно, вам захочется запретить хождение на них напрямую по HTTP или SSH. Но вообще, это может оказаться удобным в случае возникновения проблем. Далее Preview → Create launch configuration. На вопрос о выборе ключей выбираем ранее созданный key pair. На шаге, где нужно ввести имя Launch Configuration, в разделе Advanced Details, вы могли заметить поле User data. Эта штука похожа на тэги у ELB, только может быть извлечена простым HTTP запросом без access key и secret key, а также предоставляет собой просто данные, не разделенные на ключи и значения. Опять таки, сейчас нам это не пригодится, но вообще штука очень удобная.
Теперь, когда мы создали Launch Configuration, Амазон наконец-то разрешит нам создать Auto Scaling Group. Вводим какое-то имя, начальное число инстансов пусть будет 2, выбираем одну из подсетей. В Advanced Details выбираем Receive traffic from Elastic Load Balancer(s), в списке находим ранее созданный балансер. На следующем шаге выбираем «Use scaling policies to adjust the capacity of this group», говорим держать от 2-х до 10-и инстансов. Настраиваем условия изменения размеров группы. Я поставил увеличивать на 1, если среднее использование CPU превышает 70% и уменьшать на 1, если CPU используется в среднем менее, чем на 30%:
Также можно изменять размер группы в зависимости от нагрузки на сеть или диск. Группу можно увеличивать и уменьшать не только на сколько-то инстансов, но и на какое-то число процентов. Переходим в Review, жмем Create Auto Scaling group. Должны увидеть «Successfully created Auto Scaling group». В свойствах группы вы увидите, что сейчас запускаются два новых инстанса. Очень скоро эти инстансы появятся и в свойствах балансера.
Если все было сделано правильно, то вбив в адресной строе браузера доменное имя балансера вы увидите «hello»:
Вы можете самостоятельно убедиться, зайдя на каждый из инстансов в группе и посмотрев логи, что запросы приходят от балансера то на один хост, то на другой. Также балансер дергает status.txt. Можете его удалить и посмотреть, как балансер погасит «вышедший из строя» инстанс и поднимет вместо него другой. Также можете попробовать нагрузить CPU на ваших инстансах с помощью sha512sum /dev/urandom
и посмотреть, как у вас в группе будут автоматически созданы новые инстансы.
Если вас интересует, как катить обновления на все это хозяйство, то это тоже не сложно. Выкатываем новую версию приложения на мастер инстанс, создаем из него новый AMI. Создаем новый Launch Configuration, использующий новый AMI. В свойствах нашей Auto Scaling Group указываем новый Launch Configuration. Теперь увеличиваем desired размер группы вдвое. Будут подняты инстансы, созданные из нового AMI. Затем возвращаем размер группы к прежнему значению. Будут потушены старые инстансы. Заметьте, что можно обновлять не сразу всю группу, а сначала подключить одну новую машину, посмотреть, не посыпались ли на ней ошибки, а потом постепенно заменить и все остальные. В случае возникновения проблем можно быстро откатиться к старой версии приложения. Еще ELB можно настроить так, чтобы, используя куки, он всегда направлял одного и того же пользователя на одну и ту же машину (так называемый stickness), поэтому вы можете раскатывать обновление на процент пользователей , используя только средства Амазона. Еще вас, возможно, интересует, как прикрутить к ELB нормальный домен. Это делается за 10 секунд через создание алиаса в Route 53. Можете считать проверку всех этих утверждений своим домашним заданием.
Остается ряд открытых вопросов. Как сделать, чтобы один и тот же AMI мог работать как на dev окружении, так и на prod, и при чем тут тэги и user data? Как рулить при помощи всего этого хозяйства, используя Java SDK? Можно ли, и если можно, то как масштабировать с помощью auto scaling groups приложение со стейтом, например, Riak ? Как настроить continuous delivery при условии, что размер и состав группы машин постоянно меняется? Как правильно катить и обновлять описанным образом приложение, использующее Akka Cluster ? Можно ли изолировать друг от друга две версии кластера и чтобы данные при этом не разъехались? Откуда брать seed nodes и как сообщать их Akka? Если у нас какая-то распределенная база, может ли она правильно определить размер кластера для корректной работы при нетсплите? Наконец, Amazon также поддерживает еще и внутренние балансировщики нагрузки, которые очень удобны, если ваши сервисы общаются между собой. Но все это уже темы для отдельных заметок.
А используете ли вы Auto Scaling Groups и Elastic Load Balancer? Если да, то для каких целей и каким образом выкатываете обновления?
Дополнение: Proxy protocol на практике при использовании Nginx и ELB