image
Мир не идеален. В любой момент что-то может пойти не так. К счастью, большинство из нас не запускает ракеты в космос и не строит самолеты.

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

Люди не идеальны. Мы постоянно делаем ошибки. Делаем опечатки, мы можем забыть что-то или поддаться лени. Человек может банально забухать или попасть под машину.

Железо не идеально. Жесткие диски умирают. Датацентры теряют каналы. Процессоры перегреваются и электрические сети выходят из строя.

Софт не идеален. Память течёт. Коннекты рвутся. Реплики ломаются и данные уходят в небытие.

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)


  1. zharikovpro
    01.09.2016 19:26
    +7

    > Serverless — в первую очередь, то, что не запущено

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

    > 3d-party — Не нужно следить за AWS Lambda и поддерживать

    В теории-то да. В целом AWS за своей инфраструктурой следит. Только если вы от нее зависите, вам придется следить за состоянием этой инфраструктуры. Иначе может состояться примерно такой диалог:

    Клиент: а почему у меня деньги списались со счета а заказ не пришел?
    Саппорт: вы не волнуйтесь, у нас с заказами работает AWS Lambda, они сами следят за тем чтобы все было классно.

    А в это время у AWS какой-нибудь глобальный даунтайм. Дайнтаймы бывают у любого сервиса, в т.ч. 3rd-party. Это значит что вам лично возможно меньше придется рвать волос и просыпаться по ночам для восстановления этого сервиса. Но вам все равно придется быть в курсе работоспособности этого сервиса, если от него зависит что-то мало-мальски важное.

    Вывод — сервера, алерты и мониторинг остаются. Просто для каких-то задач кое-что упрощается. В первую очередь AWS Lambda для некоторых задач позволяет легче делать более масштабируемые системы.


    1. Ipeacocks
      01.09.2016 22:31

      Просто для каких-то задач кое-что упрощается

      Причем ценой не таких уж малых денег.


    1. 8ll
      02.09.2016 10:15

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

      Эти сказочные гномики работают в AWS :) Любой сервис может сломаться, в том числе и Lambda. Но нам не нужно тратить силы, на maintenance и прочие радости operations.

      >В теории-то да. В целом AWS за своей инфраструктурой следит.

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

      >Но вам все равно придется быть в курсе работоспособности этого сервиса, если от него зависит что-то мало-мальски важное.

      Мир не идеален да. Согласен.

      >Вывод — сервера, алерты и мониторинг остаются.

      Я не говорил, что от них нужно полностью отказаться. Я лишь показал, как можно это улучшить и добавить гибкости. Кстати, нам никто не мешает организовать этот механизм без AWS Lambda. Просто нужно больше писать.


  1. vladoos
    01.09.2016 20:41
    +3

    Мир совершенен в своем несовершенстве.


  1. aml
    01.09.2016 23:53
    +2

    Добро пожаловать в site reliability engineering :)


  1. Sylar
    02.09.2016 10:05
    +1

    Это специальный надуманный пример? Иначе зачем самому прибивать unhealthy инстансы, если за вас это может сделать автоскейлинг группа?


    1. 8ll
      02.09.2016 10:34

      Да это пример, который показывает, как можно расширить функционал, добавить гибкости и удобства. Любое сообщение в SNS topic вызовет выполнение лямбды. Что будет делать лямбда уже зависит от нас.


  1. rombell
    02.09.2016 10:18

    Финансовый кризис и «чёрный лебедь» дали понимание того, что риск — штука неотъемлемая от жизни, от него нельзя избавиться, можно только поднять уровень ставок — если при низких ставках ломается конкретный сервис (или банк, пирамида), то при высоких — ломается вся система. В финансовом мире после изобретения CDO считалось, что риски изжиты. Однако в результате риск перешёл на системный уровень, в 2008м году посыпались сами CDO и финансовый мир еле-еле устоял. Так и тут. Для конкретного проекта надёжность повышается. Зато если заглючит сам AWS Lamda и начнёт, например, прибивать всё, до чего дотянется, то эпичность факапа резко возрастёт.


    1. 8ll
      02.09.2016 10:36

      >Зато если заглючит сам AWS Lamda и начнёт, например, прибивать всё, до чего дотянется, то эпичность факапа резко возрастёт.

      Значит наша система построена плохо. :) К слову http://techblog.netflix.com/2012/07/chaos-monkey-released-into-wild.html


  1. Vlad_fox
    02.09.2016 10:54
    +3

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


    1. 8ll
      02.09.2016 11:18
      +2

      Потому что «жизнь — боль» :)