Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

підхід IaC (Infrastructure as Code) складається не тільки з коду, що зберігається в репозиторії, але ще людей і процесів, які оточують цей код. Чи можна перевикористовувати підходи з розробки програмного забезпечення в управління та опис інфраструктури? Буде не зайвим пам'ятати цю ідею, поки читатимете статтю.

Англійська версія

Це розшифровка мого виступи на DevopsConf 2019-05-28.

Slides and videos

Infrastructure як bash history

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Припустимо, приходьте ви на новий проект, а вам кажуть: «у нас Інфраструктура як код«. Насправді виявляється, Infrastructure як bash history або наприклад Documentation as bash history. Це цілком реальна ситуація, наприклад, подібний випадок описував Денис Лисенко у виступі Як замінити всю інфраструктуру та почати спати спокійно, він розповів як з bash history вони отримали струнку інфраструктуру на проекті.

За деякого бажання, можна сказати, що Infrastructure як bash history це як код:

  1. відтворюваністьВи можете взяти bash history, виконати команди звідти, можливо, до речі, ви отримаєте робочу конфігурацію на виході.
  2. версіонування: ви знаєте хто заходив і що робив, знову ж таки не факт, що це вас призведе до робочої конфігурації на виході.
  3. історія: історія хто і що зробив тільки ви не зможете користуватися ним, якщо втратите сервер.

Що ж робити?

Інфраструктура як код

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Навіть такий дивний випадок як Infrastructure як bash history можна притягнути за вуха до Інфраструктура як кодАле коли ми захочемо зробити щось складніше, ніж старий добрий LAMPовий сервер, ми прийдемо до того, що цей код необхідно якось модифікувати, змінювати, доопрацьовувати. Далі хотілося ми будемо розглядати паралелі між Інфраструктура як код та розробкою ПЗ.

DRY

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

На проекті з розробки СГД було підзавдання періодично налаштовувати SDS: випускаємо новий реліз - його необхідно розкотити, для подальшого тестування. Завдання гранично просте:

  • сюди зайди по ssh та виконай команду.
  • туди скопіюй файлик.
  • тут підправ конфіг.
  • там запусти сервіс
  • ...
  • PROFIT!

Для описаної логіки досить достатньо bash, особливо на ранніх стадіях проекту, коли він тільки стартує. Це непогано що ви використовуєте bash, Але згодом з'являються запити розгорнути щось схоже, але трохи відрізняються. Перше що спадає на думку: copy-paste. І ось у нас вже два дуже схожі скрипти, які роблять майже те саме. Згодом кількість скриптів зросла, і ми зіткнулися з тим, що є якась бізнес логіка розгортання інсталяції, яку необхідно синхронізувати між різними скриптами, це досить складно.

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Виявляється, є така практика DRY (Do not Repeat Yourself). Ідея полягає в тому, щоб перевикористовувати існуючий код. Звучить просто, але дійшли цього не відразу. У нашому випадку то була банальна ідея: відокремити конфіги від скриптів. Тобто. логіка бізнес як розгортається інсталяція окремо, конфіги окремо.

SOLID for CFM

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Згодом проект зростав і природним продовженням стала поява Ansible. Основна причина появи його - це наявність експертизи в команді і що bash не призначений для складної логіки. Ansible теж став містити складну логіку. Для того щоб складна логіка не перетворювалася на хаос, у розробці ПЗ існують принципи організації коду SOLID Так само, наприклад, Григорія Петров у доповіді «Навіщо айтішнику особистий бренд» торкнувся питання, що людина так влаштована, що їй простіше оперувати якимись соціальними сутностями, у розробці ПЗ це об'єкти. Якщо об'єднати ці дві ідеї продовжити розвивати їх, можна помітити, що у описі інфраструктури також можна використовувати SOLID щоб надалі було простіше підтримувати і модифікувати цю логіку.

Принцип єдиної відповідальності

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Кожен клас виконує лише одне завдання.

Не треба змішувати код і робити монолітні божественні макаронні монстри. Інфраструктура повинна складатися з простої цегли. Виявляється, якщо роздробити Ansible playbook на невеликі шматочки, читай Ansible ролі, то їх простіше підтримувати.

The Open Closed Principle

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Принцип відкритості/закритості.

  • Відкрито для розширення: означає, що поведінка сутності може бути розширена шляхом створення нових типів сутностей.
  • Закриті для зміни: в результаті розширення поведінки сутності, не повинні вносити зміни до коду, який використовує ці сутності.

Спочатку ми розгортали тестову інфраструктуру на віртуальних машинах, але за рахунок того, що бізнес логіка розгортання була окремо від реалізації, ми без проблем додали розкочування на baremetall.

The Liskov Substitution Principle

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Принцип підстановки Барбари Лисків. об'єкти у програмі повинні бути замінені на екземпляри їх підтипів без зміни правильності виконання програми

Якщо подивитися ширше, то не особливість якогось конкретного проекту, що там можна застосувати SOLID, Воно в цілому про CFM, наприклад, на іншому проекті необхідно розгортати коробкове Java додаток поверх різних Java, серверів додатків, баз даних, OS, ітд. На цьому прикладі я розглядатиму подальші принципи SOLID

У нашому випадку в рамках інфраструктурної команди є домовленість, що якщо ми встановили роль imbjava або oraclejava, то у нас є бінарний файл java. Це необхідно т.к. Вища роль залежать від цієї поведінки, вони очікують на наявність java. У той же час це дозволяє заміняти одну реалізацію/версію java на іншу при цьому не змінюючи логіку розгортання програми.

Проблема тут у тому, що у Ansible не можна реалізувати таке, як наслідок у межах команди з'являються якісь домовленості.

The Interface Segregation Principle

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Принцип поділу інтерфейсу «багато інтерфейсів, спеціально призначених для клієнтів, краще ніж один інтерфейс загального призначення.

Спочатку ми пробували складати всю варіативність розгортання програми в один Ansible playbook, але це було складно підтримувати, а підхід, коли у нас специфікований інтерфейс назовні (клієнт чекає 443 порт), то під конкретну реалізацію можна компонувати інфраструктуру з окремих цеглинок.

Принцип інверсії залежності

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Принцип інверсії залежностей Модулі верхніх рівнів не повинні залежати від нижніх модулів. Обидва типи модулів мають залежати від абстракцій. Абстракції не повинні залежати від деталей. Деталі мають залежати від абстракцій.

Тут приклад буде заснований на антипаттерні.

  1. Один із замовників мав приватну хмару.
  2. Усередині хмари ми замовляли віртуальні машини.
  3. Але через особливості хмари, розгортання програми було прив'язане до того, який гіпервізор потрапила ВМ.

Тобто. високорівнева логіка розгортання програми, залежностями протікала на рівні гіпервізору, і це означало проблеми при перевикористанні цієї логіки. Не треба так.

Взаємодія

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Інфраструктура як код — це не тільки про код, а й про відносини між кодом і людиною, про взаємодію між розробниками інфраструктури.

Bus factor

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Припустимо, що у вас на проекті є Вася. Вася все знає про вашу інфраструктуру, що буде, якщо Вася раптом пропаде? Це цілком реальна ситуація, адже його може збити автобус. Іноді таке трапляється. Якщо таке трапиться і знання про код, його структуру, як він працює, явки та паролі, не розподілені в команді, то можна зіткнутися з низкою неприємних ситуацій. Щоб мінімізувати ці ризики та розподілити знання в рамках команди можна використовувати різні підходи

Pair Devopsing

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Це не як жартома, Що адміни пили пиво, паролі змінювали, а аналог парного програмування. Тобто. два інженери сідають за один комп'ютер, одну клавіатуру і починають разом налаштовувати вашу інфраструктуру: налаштовувати сервер, Ansible роль писати, ітд. Звучить гарно, але в нас не спрацювало. Але окремі випадки цієї практики працювали. Прийшов новий співробітник, його наставник разом із ним бере реальне завдання, працює – передає знання.

Інший окремий випадок, це incident call. Під час проблеми збирається група чергових та причетних, призначається один ведучий, який розшарує свій екран та озвучує хід думки. Інші учасники йдуть за думкою ведучого, підглядають трюки з консолі, перевіряють, що не пропустив рядок у лозі, дізнаються нове про систему. Такий підхід скоріше працював, ніж ні.

Code Review

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Суб'єктивно, ефективніше поширення знань про інфраструктуру і те, як вона влаштована проходило за допомогою code review:

  • Інфраструктура описана кодом у репозиторії.
  • Зміни відбуваються у окремій гілці.
  • При мердже реквесті можна побачити дельту змін інфраструктури.

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

Стиль коду

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Згодом почали з'являтися чвари під час ревью, т.к. у рев'юверів був свій стиль і ротованість рев'юверів стикала їх з різними стилями: 2 пробіли або 4, camelCase або snake_case. Впровадити це вдалося не відразу.

  • Першою ідеєю було рекомендовано порадити використовувати linter, адже все ж таки інженери, всі розумні. Але різні редактори, ОС, не зручно
  • Це еволюціонувало в роботі, який по кожному проблемному комміту писав у slack, і прикладав висновок linter. Але здебільшого перебували важливіші справи і код залишався не виправленим.

Green Build Master

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Час іде, і дійшли того, що не можна пускати в майстер комміти, які не проходять якісь тести. Вуаль! ми винайшли Green Build Master який вже давно практикується в розробці ПЗ:

  • Розробка йде в окремій гілці.
  • Цією гілкою ганяються тести.
  • Якщо тести не проходять, код не потрапить у майстер.

Ухвалення цього рішення було дуже болючим, т.к. викликало безліч суперечок, але це того коштувало, т.к. на рев'ю стали приходити запити на злиття без розбіжностей за стилем і з часом кількість проблемних місць стала зменшуватися.

IaC Testing

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Крім перевірки стилю можна використовувати й інші речі, наприклад, перевіряти, що ваша інфраструктура дійсно може розвернутися. Або перевіряти, що зміни в інфраструктурі не призведуть до втрати грошей. Навіщо це може знадобитися? Питання складне і філософське, відповісти краще байкою, що якось був auto-scaler на Powershell який, не перевіряв прикордонні умови => створилося більше ВМ ніж треба => клієнт витратив грошей більше ніж планував. Приємного мало, але цю помилку цілком реально було б відловити на ранніх стадіях.

Можна спитати, а навіщо робити складну інфраструктуру ще складніше? Тести для інфраструктури, як і для коду, це не про спрощення, а про знання як ваша інфраструктура має працювати.

IaC Testing Pyramid

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

IaC Testing: Static Analysis

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

Bash is tricky

Ось розглянемо банальний приклад. вибрати всі файли в поточній директорії та скопіювати в інше місце. Перше що спадає на думку:

for i in * ; do 
    cp $i /some/path/$i.bak
done

А якщо в імені файлу пробіл є? Ну ок, ми ж розумні, вміємо користуватися лапками:

for i in * ; do cp "$i" "/some/path/$i.bak" ; done

Молодці? ні! Якщо в директорії немає нічого, тобто. глобінг не спрацює.

find . -type f -exec mv -v {} dst/{}.bak ;

Тепер то молодці? неа… Забули що в імені файлу може бути n.

touch x
mv x  "$(printf "foonbar")"
find . -type f -print0 | xargs -0 mv -t /path/to/target-dir

Static analysis tools

Проблему з попереднього кроку можна було відловити, коли ми забули лапки, для цього в природі існує безліч засобів. Shellcheckвзагалі їх багато, і швидше за все ви зможете знайти під свою IDE лінтер для вашого стеку.

Language
Інструмент

бити
Shellcheck

рубін
RuboCop

пітон
Пілінт

ansible
Ansible Lint

IaC Testing: Unit Tests

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Як ми переконалися з попереднього прикладу, лінтери не всемогутні та не можуть вказати на всі проблемні місця. Далі за аналогією з тестуванням у розробці ПЗ можна згадати про unit tests. Тут відразу на думку спадають shunit, джуніт, rspec, пітест. Але що робити з ansible, chef, saltstack та що з ними?

На самому початку ми говорили про SOLID і те, що наша інфраструктура повинна складатися з маленьких цеглинок. Настав їхній час.

  1. Інфраструктура дробиться на дрібні цеглинки, наприклад, Ansible ролі.
  2. Розгортається якесь оточення, чи то docker, чи ВМ.
  3. Для цього тестове оточення застосовуємо нашу Ansible роль.
  4. Перевіряємо що все відпрацювало як ми очікуємо (проганяємо тести).
  5. Вирішуємо ок чи не ок.

IaC Testing: Unit Testing tools

Запитання, а що таке тести для CFM? можна банально запускати скрипт, а можна використовувати готові рішення для цього:

CFM
Інструмент

Неможливо
Тестинфра

шеф-кухар
Інспектор

шеф-кухар
Serverspec

солі
Госс

Приклад для testinfra, перевіряємо що користувачі test1, test2 існують і перебувають у групі sshusers:

def test_default_users(host):
    users = ['test1', 'test2' ]
    for login in users:
        assert host.user(login).exists
        assert 'sshusers' in host.user(login).groups

Що вибрати? питання складне і не однозначне, ось приклад зміни в проектах на github за 2018-2019 роки:

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

IaC Testing frameworks

Виникає як це все зібрати разом та запустити? Можна, можливо взяти і зробити все самому за наявності достатньої кількості інженерів. А можна взяти готові рішення, правда їх не дуже багато:

CFM
Інструмент

Неможливо
Молекула

шеф-кухар
Тестова кухня

Terraform
Терратест

Приклад зміни у проектах на github за 2018-2019 роки:

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Molecule vs. Testkitchen

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Спочатку ми пробували використовувати testkitchen:

  1. Створити ВМ у паралель.
  2. Застосувати Ansible ролі.
  3. Проганяти inspec.

Для 25-35 ролей це працювало 40-70 хвилин, що тривало.

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Наступним кроком став перехід на jenkins/docker/ansible/molecule. Ідіологічно все те саме

  1. Пролінтувати плейбуки.
  2. Пролінтувати ролі.
  3. Запустити контейнер
  4. Застосувати Ansible ролі.
  5. Проганяти testinfra.
  6. Перевірити ідемопотентність.

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Лінтівка для 40 ролей і тести для десятка почали займати близько 15 хвилин.

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Що вибрати залежить від безлічі факторів, як використовується стек, експертиза в команді ітд. тут кожен вирішує сам, як закривати питання Unit тестування

IaC Testing: Integration Tests

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

На наступному щаблі піраміди тестування інфраструктури з'являться інтеграційні тести. Вони схожі на Unit тести:

  1. Інфраструктура дробиться на дрібні цеглинки, наприклад Ansible ролі.
  2. Розгортається якесь оточення, чи то docker, чи ВМ.
  3. На це тестове оточення застосовується безліч Відмінні ролі.
  4. Перевіряємо що все попрацювало як ми очікуємо (проганяємо тести).
  5. Вирішуємо ок чи не ок.

Грубо кажучи, ми не перевіряємо працездатність окремого елемента системи як у unit тестах, ми перевіряємо як сервер налаштований загалом.

IaC Testing: End to End Tests

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

На вершині піраміди нас зустрічають End to End тести. Тобто. ми не перевіряємо працездатність окремого сервера, окремого скрипту, окремої цегли нашої інфраструктури. Ми перевіряємо, що безліч серверів, об'єднаних воєдино, наша інфраструктура працює, як ми цього очікуємо. На жаль, готових коробкових рішень, мені не доводилося бачити, напевно, т.к. інфраструктура часто унікальна і її складно шаблонізувати і зробити фреймворк для її тестування. Як результат усі створюють свої власні рішення. Попит є, а відповіді немає. Тому розповім, що є, щоб наштовхнути інших на здорові думки або тицьнути мене носом, що все давно винайдено до нас.

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Проект із багатою історією. Використовується у великих організаціях і певно кожен із вас побічно перетинався. Додаток підтримує безліч баз даних, інтеграцій тощо. Знання про те, як інфраструктура може виглядати це безліч файлів docker-compose, а знання того, які тести в якому оточенні запускати - це jenkins.

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Ця схема досить довго працювала, поки в рамках дослідження ми не спробували це перенести до Openshift. Контейнери залишилися теж, а от середа запуску змінилася (привіт DRY знову).

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Думка дослідження пішла далі, і в Openshift знайшлася така штука APB (Ansible Playbook Bundle), яка дозволяє в контейнер запакувати знання як розгортати інфраструктуру. Тобто. є точка знання, що відтворюється, тестується, як розгорнути інфраструктуру.

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Все це звучало добре, доки не уткнулися в гетерогенну інфраструктуру: нам для тестів потрібна Windows. У результаті знання про те, де, як розгорнути, і протестувати сидить у jenkins.

Висновок

Що я дізнався, протестувавши 200 000 рядків інфраструктурного коду

Infrastructure as Code це

  • Код у репозиторії.
  • Взаємодія людей.
  • Тестування інфраструктури.

зв'язку

Джерело: habr.com

Додати коментар або відгук