Світ не ідеальний

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. Спокійно їдемо в гори або потрапляємо під машину.

До речі, за рахунок гнучкості можна запрограмувати будь-яку поведінку системи і не тільки по системним, але і по бізнес-метрик.

Спасибі.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.