Мир не идеален. В любой момент что-то может пойти не так. К счастью, большинство из нас не запускает ракеты в космос и не строит самолеты.
Современный человек зависит от приложения в его телефоне и наша задача, сделать так, что бы в любой момент времени при любом стечении обстоятельств, он мог открыть приложеньку и посмотреть картинки с котиками.
Люди не идеальны. Мы постоянно делаем ошибки. Делаем опечатки, мы можем забыть что-то или поддаться лени. Человек может банально забухать или попасть под машину.
Железо не идеально. Жесткие диски умирают. Датацентры теряют каналы. Процессоры перегреваются и электрические сети выходят из строя.
Софт не идеален. Память течёт. Коннекты рвутся. Реплики ломаются и данные уходят в небытие.
Shit happens — как говорят наши заокеанские друзья. Что же мы можем со всем этим сделать? А ответ банален до простоты — ничего. Мы можем вечно тестировать, поднимать тонну окружений, копировать продакшн и держать сто тысяч резервных серверов, но это все равно не спасет: мир не идеален.
Единственный верное решение здесь — это смириться. Нужно принять мир таким какой он есть и минимизировать потери. Каждый раз настраивая новый сервис нужно помнить — он сломается в самый неподходящий момент.
Он обязательно сломается. Ты обязательно сделаешь ошибку. Железо обязательно выйдет из строя. Кластер обязательно рассыпется. И по законам этого неидеального мира — это случится именно тогда, когда ты этого меньше всего ожидаешь.
Что делает большинство из нас что бы обмануть всех (в том числе и себя)? — Мы настраиваем алерты. Мы пишем хитрые метрики, собираем логи и создаем алерты, тысячи, сотни тысяч алертов. Наши почтовые ящики переполнены. Наши телефоны разрываются от смс и звонков. Мы сажаем целые этажи людей смотреть на графики. А когда в очередной раз мы теряем доступ к сервису, начинаются разборы: что же мы забыли замониторить.
Все это лишь видимость надежности. Никакие алерты, метрики и мониторинги не помогут.
Сегодня тебе позвонили, и ты починил сервис — никто и не заметил, что что-то сломалось. А завтра ты уехал в горы. А послезавтра забухал. Люди не идеальны. К счастью мы инженеры, живем в неидеальном мире и учимся его побеждать.
Так почему же надо просыпаться по ночам или утром вместо кофе читать почту. Почему бизнес должен зависеть от одного человека и от его работоспособности. Почему. Я не понимаю.
Я лишь только понимаю, что так жить нельзя, и я не хочу так жить. А ответ прост: Автоматизируй это (да, именно с большой буквы). Нам нужны не просто алерты и звонки по ночам. Нам нужны автоматические реакции на эти сообщения. Мы должны быть уверены, что система может починить себя сама. Система должна быть гибкой и уметь изменяться.
К сожалению, у нас пока нет достаточно умного ИИ. К счастью, все наши проблемы формализуемы.
У меня нет серебрянной пули, но зато у меня есть Proof of Concept для AWS.
AWS Lambda
Serverless — в первую очередь, то, что не запущено сломаться не может.
Event based — получили событие, обработали, выключились.
Умеет JVM — а значит, можно использовать весь опыт из Java мира (и значит, что я могу использовать Clojure).
3d-party — Не нужно следить за AWS Lambda и поддерживать.
Pipeline выглядит следующим образом:
Событие -> SNS Topic -> AWS Lambda -> Реакция
К слову, SNS topic может иметь несколько endpoints. Значит, можно банально добавить почту и получать так же уведомления. А можем расширить lambda функцию и сделать уведомления намного полезнее: например, слать алерты сразу вместе с графиками или добавить отправку SMS.
Целиком пример одной Lambda функции можно найти по ссылке: github.com/lowl4tency/aws-lambda-example
Лямбда функция прибивает все ноды в ELB не в состоянии inService.
Разбор кода
В данном примере мы будем убивать все ноды которые не находятся в состоянии InService. К слову, вся Lambda функция занимает ~50 строк кода в одном файле, а значит простота поддержки и легкость входа.
Любой проект на Clojure начинается с project.clj
Я использовал официальный Java SDK и прекрасную библиотечку Amazonica, которая является враппером для этого SDK. Ну и что бы не тащить много лишнего, исключаем те части SDK, которые нам не понадобится
[amazonica "0.3.52" :exclusions [com.amazonaws/aws-java-sdk]]
[com.amazonaws/aws-java-sdk-core "1.10.62"]
[com.amazonaws/aws-lambda-java-core "1.1.0"]
[com.amazonaws/aws-java-sdk-elasticloadbalancing "1.11.26"
:exclusions [joda-time]]
[com.amazonaws/aws-java-sdk-ec2 "1.10.62"
:exclusions [joda-time]]
[com.amazonaws/aws-lambda-java-events "1.1.0"
:exclusions [com.amazonaws/aws-java-sdk-dynamodb
com.amazonaws/aws-java-sdk-kinesis
com.amazonaws/aws-java-sdk-cognitoidentity
com.amazonaws/aws-java-sdk-sns
com.amazonaws/aws-java-sdk-s3]]]
Для большей гибкости каждой Lambda функции я использую конфигурационный файл с самым обычным edn. Для того что бы получить возможность обрабатывать события нам нужно немного изменить объявление функции
(ns aws-lambda-example.core
(:gen-class :implements [com.amazonaws.services.lambda.runtime.RequestStreamHandler])
Точка входа. Читаем событие на входе, обрабатываем данное событие с помощью handle-event и пишем в поток JSON в качестве результата.
(defn -handleRequest [this is os context]
"Parser of input and genarator of JSON output"
(let [w (io/writer os)]
(-> (io/reader is)
json/read
(-> (io/reader is)
json/read
walk/keywordize-keys
handle-event
(json/write w))
(.flush w))))
Рабочая лошадка:
(defn handle-event [event]
(let [instances (get-elb-instances-status
(:load-balancer-name
(edn/read-string (slurp (io/resource "config.edn")))))
unhealthy (unhealthy-elb-instances instances)]
(when (seq unhealthy)
(pprint "The next instances are unhealthy: ")
(pprint unhealthy)
(ec2/terminate-instances :instance-ids unhealthy))
{:message (get-in event [:Records 0 :Sns :Message])
:elb-instance-ids (mapv :instance-id instances)}))
Получаем список нод в ELB и фильтруем их по статусу. Все ноды, которые в состоянии InService удаляем из списка. Остальные терминейтим.
Все что мы печатаем через pprint попадет в логи CloudWatch. Это может быть полезно для дебага. Так как у нас нет постоянно запущенной лямбды и нет возможности подключиться к REPL это может быть довольно полезно.
{:message (get-in event [:Records 0 :Sns :Message])
:instance-ids (mapv :instance-id instances)}))
В данном месте вся структура, которуя сгенерим и возвратим из этой функции будет записана в JSON и увидим в результате выполнения в Web интерфейсе Lambda.
В функции unhealthy-elb-instances фильтруем наш список и получаем instance-id только для тех нод, которые ELB посчитал нерабочими. Получаем список инстансев и фильтруем их по тегам.
(defn unhealthy-elb-instances [instances-status]
(->>
instances-status
(remove #(= (:state %) "InService"))
(map :instance-id)))
В функции get-elb-instances-status вызываем АПИ метод и получаем список всех нод со статусами для одного определенного ELB
(defn get-elb-instances-status [elb-name]
(->>
(elb/describe-instance-health :load-balancer-name elb-name)
:instance-states
(map get-health-status )))
Для удобства убираем лишнее и генерируем список только с информацией которая нам интересна. Это instance-id и status каждого instance.
(defn get-health-status [instance]
{:instance-id (:instance-id instance)
:state (:state instance)})
И фильтруем наш список, убирая те ноды, что находятся в состоянии InService.
(defn unhealthy-elb-instances [instances-status]
(->>
instances-status
(remove #(= (:state %) "InService"))
(map :instance-id)))
И это всё: 50 строк, которые позволят не просыпаться по ночам и спокойно ехать в горы.
Deployment
Для простоты деплоймента я использую простой bash-script
#!/bin/bash
# Loader AWS Lambda
aws lambda create-function --debug --function-name example --handler aws-lambda-example.core --runtime java8 --memory 256 --timeout 59 --role arn:aws:iam::611066707117:role/lambda_exec_role --zip-file fileb://./target/aws-lambda-example-0.1.0-SNAPSHOT-standalone.jar
Настраиваем алерт и прикручиваем его к SNS topic. SNS topic прикручиваем к лямбде как endpoint. Спокойно едем в горы или попадаем под машину.
К слову, за счет гибкости можно запрограммировать любое поведение системы и не только по системным, но и по бизнес-метрикам.
Спасибо.
Комментарии (11)
Sylar
02.09.2016 10:05+1Это специальный надуманный пример? Иначе зачем самому прибивать unhealthy инстансы, если за вас это может сделать автоскейлинг группа?
8ll
02.09.2016 10:34Да это пример, который показывает, как можно расширить функционал, добавить гибкости и удобства. Любое сообщение в SNS topic вызовет выполнение лямбды. Что будет делать лямбда уже зависит от нас.
rombell
02.09.2016 10:18Финансовый кризис и «чёрный лебедь» дали понимание того, что риск — штука неотъемлемая от жизни, от него нельзя избавиться, можно только поднять уровень ставок — если при низких ставках ломается конкретный сервис (или банк, пирамида), то при высоких — ломается вся система. В финансовом мире после изобретения CDO считалось, что риски изжиты. Однако в результате риск перешёл на системный уровень, в 2008м году посыпались сами CDO и финансовый мир еле-еле устоял. Так и тут. Для конкретного проекта надёжность повышается. Зато если заглючит сам AWS Lamda и начнёт, например, прибивать всё, до чего дотянется, то эпичность факапа резко возрастёт.
8ll
02.09.2016 10:36>Зато если заглючит сам AWS Lamda и начнёт, например, прибивать всё, до чего дотянется, то эпичность факапа резко возрастёт.
Значит наша система построена плохо. :) К слову http://techblog.netflix.com/2012/07/chaos-monkey-released-into-wild.html
zharikovpro
> Serverless — в первую очередь, то, что не запущено
Т.е. вы хотите сказать, что всю работу в AWS Lamda выполняют сказочные гномики, и она не запускается на серверах AWS?
> 3d-party — Не нужно следить за AWS Lambda и поддерживать
В теории-то да. В целом AWS за своей инфраструктурой следит. Только если вы от нее зависите, вам придется следить за состоянием этой инфраструктуры. Иначе может состояться примерно такой диалог:
Клиент: а почему у меня деньги списались со счета а заказ не пришел?
Саппорт: вы не волнуйтесь, у нас с заказами работает AWS Lambda, они сами следят за тем чтобы все было классно.
А в это время у AWS какой-нибудь глобальный даунтайм. Дайнтаймы бывают у любого сервиса, в т.ч. 3rd-party. Это значит что вам лично возможно меньше придется рвать волос и просыпаться по ночам для восстановления этого сервиса. Но вам все равно придется быть в курсе работоспособности этого сервиса, если от него зависит что-то мало-мальски важное.
Вывод — сервера, алерты и мониторинг остаются. Просто для каких-то задач кое-что упрощается. В первую очередь AWS Lambda для некоторых задач позволяет легче делать более масштабируемые системы.
Ipeacocks
Причем ценой не таких уж малых денег.
8ll
>Т.е. вы хотите сказать, что всю работу в AWS Lamda выполняют сказочные гномики, и она не запускается на серверах AWS?
Эти сказочные гномики работают в AWS :) Любой сервис может сломаться, в том числе и Lambda. Но нам не нужно тратить силы, на maintenance и прочие радости operations.
>В теории-то да. В целом AWS за своей инфраструктурой следит.
Точно такой же диалог может случиться и при собственной инфраструктуре и сервисах. Дверью можно прищемить, это не значит что не надо пользоваться дверьми.
>Но вам все равно придется быть в курсе работоспособности этого сервиса, если от него зависит что-то мало-мальски важное.
Мир не идеален да. Согласен.
>Вывод — сервера, алерты и мониторинг остаются.
Я не говорил, что от них нужно полностью отказаться. Я лишь показал, как можно это улучшить и добавить гибкости. Кстати, нам никто не мешает организовать этот механизм без AWS Lambda. Просто нужно больше писать.