
Това е разшифровка на :
След второто комитване, всеки код става legacy, тъй като оригиналните намерения започват да се разминават с суровата реалност. Това не е добро и не е лошо, това е факт, с който е трудно да се спори и трябва да се научим да съжителстваме. Част от този процес е рефакторингът. Рефакторинг на Инфраструктурата като Код. Нека започне историята как да рефакторим Ansible за година и да не загубим ума си.
Раждането на Legacy
Ден № 1: Нулевият пациент

Имаше един условен проект. В него имаше Dev екип за разработка и Ops инженери. Те решаваха една и съща задача: как да разгръщат сървъри и да стартират приложение. Проблемът беше, че всеки екип решаваше тази задача по своему. В проекта беше прието решение да се използва Ansible за синхронизиране на знанията между екипите Dev и Ops.
Ден № 89: Раждането на Legacy

Без да го осъзнават, искаха да направят всичко възможно най-добро, а в крайна сметка се получи legacy. Как така стана?
- Имаме спешна задача, ще направим мръсен хак — после ще го оправим.
- Документацията не е необходима, така или иначе всичко е ясно какво става.
- Аз знам Ansible / Python / Bash / Terraform! Вижте как мога да се извивам!
- Аз съм Full Stack Overflow Developer, копирах това от stackoverflow, не знам как работи, но изглежда готино и решава задачата.
В крайна сметка може да получите код от непонятен вид, за който няма документация, не е ясно какво прави, нужен ли е, но проблемът е, че трябва да го развиете, да добавите храсти с подпори, правейки ситуацията само още по-лоша.
- hosts: localhost
tasks:
- shell: echo -n Z >> a.txt && cat a.txt
register: output
delay: 1
retries: 5
until: not output.stdout.find("ZZZ")Ден № 109: Осъзнаване на проблема

Първоначално замислената и реализирана модел на IaC спира да отговаря на реалността с исканията на потребителите / бизнеса / други екипи, времето за внасяне на промени в инфраструктурата спира да е приемливо. В този момент идва разбирането, че е време да се предприемат мерки.
Рефакторинг на IaC
Ден № 139: Наистина ли ви е нужен рефакторинг?

Преди да се хвърлите в рефакторинг, трябва да отговорите на редица важни въпроси:
- Защо ви е всичко това?
- Имате ли време?
- Достатъчно ли е знанието?
Ако не знаете как да отговорите на въпросите, рефакторингът ще завърши, преди да е започнал или може да стане само по-лошо. Тъй като имаше опит( ), проектът поиска помощ за корекция на ролите и покритие с тестове.
Ден № 149: Подготовка на рефакторинг

Основната задача е да се подготвим. Трябва да определим какво ще правим. Затова се свързваме, идентифицираме проблематичните точки и разглеждаме възможностите за решение. Записваме получените концепции, например в статия в confluence, за да не изгубим посоката при възникване на въпроса "как е най-добре?" или "как е правилно?" В нашия случай се придържахме към идеята разделяй и владей: разделяме инфраструктурата на малки парчета/кирпичи. Този подход позволява да вземем изолиран участък от инфраструктурата, да разберем какво прави, да го покрием с тестове и да го променим, без да се страхуваме, че нещо ще се срути.

В резултат на това, тестироването на инфраструктурата става краеуголният камък и тук следва да се спомене пирамидата на тестирование на инфраструктурата. Същата идея, която се прилага в разработката, но за инфраструктурата: започваме от евтини и бързи тестове, които проверяват основни неща, например отстъпи, до скъпи и пълноценни тестове, които разгръщат цялостна инфраструктура.
Опити за тестване на Ansible
Преди да продължим с описанието на това как покрихме с тестове Ansible на проекта, ще опиша опитите и подходите, които използвахме по-рано, за да разберем контекста на взетите решения.
Ден № -997: Provision на SDS

Първият път, когато тестирах Ansible, беше на проект за разработка на SDS (Програмирано хранилище). Има отделна статия по тази тема
, но накратко, получихме обърната пирамида на тестването и тестовете ни отнемаха 60-90 минути на една роля, което е дълго. Основата бяха e2e тестовете, т.е. разгръщахме пълна инсталация и после я тестваме. Допълнително усложнение беше създаването на собствено решение. Но трябва да призная, че това решение работеше и позволяваше стабилно разгръщане.
Ден № -701: Ansible и test kitchen

Развитието на идеята за тестване на Ansible стана чрез използването на готови инструменти, а именно test kitchen / kitchen-ci и inspec. Изборът беше обусловен от познанията ми по Ruby (повече в статия на хабра: ) работеше по-бързо около 40 минути за 10 роли. Създавахме партида виртуални машини и вътре стартирахме тестовете.

Като цяло решението работеше, но имаше неприятно усещане заради нееднородността. Когато увеличихме броя на тестваните до 13 основни роли и 2 мета роли, комбиниращи по-малки роли, изведнъж тестовете започнаха да отнемат 70 минути, което е почти два пъти повече. За XP (extreme programming) практиките беше трудно да се говори, тъй като никой не желае да чака 70 минути. Това стана основание за промяна на подхода.
Ден № -601: Ansible и molecule

Концептуално това е подобно на testkitchen, само че преместихме тестването на роли в docker и сменихме стека. В резултат, времето намаля до стабилни 20-25 минути за 7 роли.

Увеличавайки броя на тестваните роли до 17 и линтовката на 45 роли, ние изпълнихме това за 28 минути на 2 jenkins slave.
Ден № 167: Добавяме тестове Ansible към проекта

С една идея трудно ще се справим с рефакторинга. Задачата трябва да бъде измерима, за да може да я разделите на малки части и да изяждате слона по части с лъжичка. Трябва да има разбиране дали движението е в правилната посока и колко дълго ще продължи.

Като цяло не е важно как точно ще се направи, можете да запишете на хартия, да лепите стикери на шкафа, да създадете задачи в jira или да започнете google docs и там да запишете текущия статус. Всичко произтича от факта, че процесът не е мигновен, той ще бъде дълъг и труден. Малко вероятно е някой да иска в периода на рефакторинг да изгуби интерес, да се умори и да се отказва.
Рефакторингът е прост:
- Яж.
- Спи.
- Код.
- IaC тест.
- Повтаряй.
и така повтаряме, докато не постигнем поставената цел.

Не е сигурно, че можем да започнем да тестваме всичко наведнъж, така че нашата първа задача беше да започнем с линтовка и проверка на синтаксиса.
Ден № 181: Зелен Build Master

Линтовката е малка първа стъпка към Зелен Build Master. Това почти няма да счупи нещо, но ще позволи да се наложат процесите и да се направят зелени билдове в jenkins. Идеята е да се формират навици в екипа:
- Червените тестове са лоши.
- Ако дойдеш да поправиш нещо, направи кода малко по-добър, отколкото е бил преди теб.
Ден № 193: От линтовка до unit тестове

Построявайки процеса на извеждане на кода в master, можем да започнем процеса на постепенно подобряване — заменяйки линтовката с изпълнение на роли, дори и без идемпотентност. Необходимо е да разберем как да приложим ролите, как работят те.
Ден № 211: От unit тестове към интеграционни тестове

Когато повечето роли са покрити с unit тестове и всичко е линтувано, можем да преминем към добавяне на интеграционни тестове. С други думи, тестиране не на отделен елемент в инфраструктурата, а на техни комбинации, например пълна конфигурация на инстанс.

В Jenkins генерирахме множество стадии, които паралелно линтуваха роли/плейбуци, след което юнит тестовете в контейнери и накрая интеграционните тестове.
Jenkins + Docker + Ansible = Тестове

- Проверете репото и генерирайте етапи за изграждане.
- Стартирайте етапите на линт плейбука паралелно.
- Стартирайте етапите на линт ролите паралелно.
- Стартирайте етапите за проверка на синтаксиса на ролите паралелно.
- Стартирайте етапите за тестване на ролите паралелно.
- Линтинг на ролата.
- Проверка на зависимостите от други роли.
- Проверка на синтаксиса.
- Създайте Docker инстанция.
- Стартирайте molecule/default/playbook.yml.
- Проверка на идемпотентност.
- Стартиране на интеграционни тестове.
- Завърши
Ден № 271: Bus Factor

В началото рефакторингът се осъществяваше от малка група хора, в която имаше двама-трима души. Те правеха ревю на кода в основния клон. С времето в екипа се разви знание как да се пише код и ревюто на кода способстваше за разпространение на знанията за инфраструктурата и как тя е изградена. Изключителното тук беше, че ревюверите се избираха по ред, според графика, т.е. с определена вероятност да се сблъскат с нова част от инфраструктурата.

И тук трябва да е удобно. Удобно е да правите ревю, да видите в рамките на коя задача е направено, историята на дискусиите. Интегрирахме Jenkins + Bitbucket + Jira.
Но ревюто само по себе си не е панацея, понеже в един момент в основния клон проби код, който ни създаде проблеми с тестовете:
- get_url:
url: "{{ actk_certs }}\/{{ item.1 }}"
dest: "{{ actk_src_tmp }}\/"
username: "{{ actk_mvn_user }}"
password: "{{ actk_mvn_pass }}"
with_subelements:
- "{{ actk_cert_list }}"
- "{{ actk_certs }}"
delegate_to: localhost
- copy:
src: "{{ actk_src_tmp }}\/{{ item.1 }}"
dest: "{{ actk_dst_tmp }}"
with_subelements:
- "{{ actk_cert_list }}"
- "{{ actk_certs }}"След това го поправихме, но усещането остана.
get_url:
url: "{{ actk_certs }}\/{{ actk_item }}"
dest: "{{ actk_src_tmp }}\/{{ actk_item }}"
username: "{{ actk_mvn_user }}"
password: "{{ actk_mvn_pass }}"
loop_control:
loop_var: actk_item
with_items: "{{ actk_cert_list }}"
delegate_to: localhost
- copy:
src: "{{ actk_src_tmp }}\/{{ actk_item }}"
dest: "{{ actk_dst_tmp }}"
loop_control:
loop_var: actk_item
with_items: "{{ actk_cert_list }}"Ден № 311: Ускоряваме тестовете

С времето тестовете ставаха все повече, билдовете бяха по-бавни, достигайки до час в лошия случай. На едно от ретро събитията някой каза "добре е, че имаме тестове, но те са бавни". В крайна сметка се отказахме от интеграционните тестове на виртуални машини и ги адаптирахме за Docker, за да бъдат по-бързи. Също така заменихме Testinfra с Ansible Verifier, за да намалим броя на използваните инструменти.

Строго казано, тук имаше комплекс от мер:
- Преминаване на docker.
- Премахнете тестването на роли, което се дублира заради зависимостите.
- Увеличете броя на слейвовете.
- Ред на стартиране на тестовете.
- Възможност за линтинг ВСИЧКО локално с една команда.

В крайна сметка Pipeline на jenkins също беше унифициран
- Генерирайте етапи на сглобяване.
- Линтувайте всички паралелно.
- Стартирайте етапите за тестване на ролите паралелно.
- Завърши.
Уроци извлечени
Избягвайте глобални променливи
Ansible използва глобални променливи, има частичен обход в лицето на , но това не е панацея.
Ще дам пример. Нека имаме role_a и role_b
# cat role_a/defaults/main.yml
---
msg: a
# cat role_a/tasks/main.yml
---
- debug:
msg: role_a={{ msg }}# cat role_b/defaults/main.yml
---
msg: b
# cat role_b/tasks/main.yml
---
- set_fact:
msg: b
- debug:
msg: role_b={{ msg }}- hosts: localhost
vars:
msg: hello
roles:
- role: role_a
- role: role_b
tasks:
- debug:
msg: play={{msg}}
Интересното е, че резултатът от изпълнението на плейбуците ще зависи от не винаги очевидни неща, например реда на изброяване на ролите. За съжаление, това е естеството на Ansible и най-доброто, което можем да направим, е да използваме някакви споразумения, например в рамките на ролята да използваме само променливи, описани в тази роля.
ЛОШО: да се използва глобална променлива.
# cat roles/some_role/tasks/main.yml
---
debug:
var: java_homeДОБРЕ: В defaults определете необходимите променливи и след това използвайте само тях.
# cat roles/some_role/defaults/main.yml
---
r__java_home:
"{{ java_home | default('/path') }}"
# cat roles/some_role/tasks/main.yml
---
debug:
var: r__java_home
Префикс за променливи на роли
ЛОШО: да се използва глобална променлива.
# cat roles/some_role/defaults/main.yml
---
db_port: 5432ДОБРЕ: В ролята за променливи използвайте променливи с префикс на името на ролята, така че да можете по-лесно да разберете какво се случва при преглеждане на инвентара.
# cat roles/some_role/defaults/main.yml
---
some_role__db_port: 5432Използвайте променлива за управление на цикъла
ЛОШО: Използвайте в цикли стандартната променлива item, ако тази задача/плейбук бъде включена някъде, това може да доведе до неочаквано поведение
---
- hosts: localhost
tasks:
- debug:
msg: "{{ item }}"
loop:
- item1
- item2
ДОБРЕ: Препокривайте променливата в цикъла чрез loop_var.
---
- hosts: localhost
tasks:
- debug:
msg: "{{ item_name }}"
loop:
- item1
- item2
loop_control:
loop_var: item_name
Проверете входните променливи
Ние се споразумяхме да използваме префикси за променливите, не би било излишно да проверим, че те са определени, както очакваме и например не са били заменени с празна стойност
ДОБРЕ: Проверявайте променливите.
- name: "Проверете дали необходимите променливи от тип стринг са определени"
assert:
that: ahs_var is defined and ahs_var | length > 0 and ahs_var != None
fail_msg: "{{ ahs_var }} трябва да бъде зададена, за да работи ролята"
success_msg: "Необходими променливи {{ ahs_var }} са определени"
loop_control:
loop_var: ahs_var
with_items:
- ahs_item1
- ahs_item2
- ahs_item3Избягвайте хешове и речници, използвайте плоска структура
Ако ролята очаква хеш/речник като един от параметрите, то ако искаме да поправим един от дочерните параметри, ще трябва да препокриваме целия хеш/речник, което повишава сложността на конфигурирането.
ЛОШО: Използвайте хеш/речник.
---
user:
name: admin
group: adminДОБРЕ: Използвайте плоска структура на променливите.
---
user_name: admin
user_group: "{{ user_name }}"Създайте идемпотентни плейбукове и ролеви настройки
Ролите и плейбуците трябва да са идемпотентни, тъй като това намалява конфигурационното отклонение и избягва рисковете от повреда. Но ако използвате molecule, това е поведение по подразбиране.
Избягвайте използването на модули на команден шел
Използването на модулите на шел води до императивна парадигма на описание, вместо декларативна, която е основната за Ansible.
Тествайте ролите си чрез molecule
Molecule предлага много гъвкава опция, нека да разгледаме няколко сценария.
Molecule Множество инстанции
В molecule.yml в секция платформи може да опишете множество хостове, които да разгръщате.
---
driver:
name: docker
platforms:
- name: postgresql-instance
hostname: postgresql-instance
image: registry.example.com/postgres10:latest
pre_build_image: true
override_command: false
network_mode: host
- name: app-instance
hostname: app-instance
pre_build_image: true
image: registry.example.com/docker_centos_ansible_tests
network_mode: hostСъответно, тези хостове могат след това да се използват в converge.yml използвайте:
---
- name: Converge all
hosts: all
vars:
ansible_user: root
roles:
- role: some_role
- name: Converge db
hosts: db-instance
roles:
- role: some_db_role
- name: Converge app
hosts: app-instance
roles:
- role: some_app_roleAnsible проверител
В molecule има възможност да се използва ansible за проверка дали инстансът е настроен правилно, в допълнение е по подразбиране от трето издание. Това не е толкова гъвкаво, колкото testinfra/inspec, но можете да проверявате, че съдържанието на файл съвпада с нашите очаквания:
---
- name: Verify
hosts: all
tasks:
- name: copy config
copy:
src: expected_standalone.conf
dest: /root/wildfly/bin/standalone.conf
mode: "0644"
owner: root
group: root
register: config_copy_result
- name: Удостоверьте, че standalone.conf е променен
assert:
that: not config_copy_result.changedИли разгръща услуги, чакайте тяхната достъпност и направете smoke test:
---
- name: Verify
hosts: solr
tasks:
- command: /blah/solr/bin/solr start -s /solr_home -p 8983 -force
- uri:
url: http://127.0.0.1:8983/solr
method: GET
status_code: 200
register: uri_result
until: uri_result is not failed
retries: 12
delay: 10
- name: Постоянни документи към solr
command: /blah/solr/bin/post -c master /exampledocs/books.csvПоставете сложна логика в модули и приставки
Ansible проповядва декларативен подход, следователно, когато правите разделения на код, трансформация на данни, модулите на шел, кодът става трудно четим. За да се справите с това и да го оставите прост за разбиране, не е излишно да се борите с тази сложност чрез създаване на собствени модули.
Обобщете съвети и трикове
- Избягвайте глобалните променливи.
- Префикс на променливите на ролите.
- Използвайте променлива за контрол на цикъла.
- Проверете входните променливи.
- Избягвайте хешове, използвайте плоска структура.
- Създайте идемпотентни плейбукове и ролеви настройки.
- Избягвайте използването на модули на команден шел.
- Тествайте ролите си чрез molecule.
- Сложната логика се поставя в модули и плъгини.
Заключение

Не е възможно просто така да рефакторите инфраструктурата на проекта, дори ако имате IaC. Това е дълъг процес, изискващ търпение, време и знания.
Връзки
- Слайдове
- Видео
UPD1 2020.05.01 20:30 — За първоначално профилиране на плейбуковете можете да използвате callback_whitelist = profile_tasks за да разберете какво точно отнема дълго време. След това преминаваме през . Можете да опитате
UPD2 2020.05.03 16:34 —
Източник: habr.com
