Всем привет.
Дисклеймер: сказу скажу, что пишу статью по ходу дела, "код" в ней рабочий, но не претендует на какие-либо best practices, поэтому не придирайтесь :) Цель статьи: донести до интересующейся русскоязычной части населения общие принципы, возможно разбудить интерес поразбираться самостоятельно и сделать что-то гораздо лучше и интереснее. Итак поехали!
Допустим Вы работаете с Terraform / Terragrunt (второе здесь непринципиально, но лучше изучайте, если ещё не используете) и автоматизируете инфраструктуру, например, в AWS (но совершенно необязательно AWS). Инфраструктура в коде репозитория, разворачивается из него же, казалось бы вот оно GitOps счастье :)
Всё идёт хорошо, пока какой-то пользователь не поменял что-то руками через консоль / UI и конечно забыл об этом кому-либо сказать. А то и сделал что-то нехорошее намеренно. И вот он ваш дрейф: код и инфраструктура больше не совпадают! :(
Для того, чтобы как минимум своевременно узнавать о подобном необходимо немного доработать автоматизацию.
Как обычно, есть много различных путей добиться желаемого. Например, недавно на горизонте появилась неплохо развивающаяся утилита https://github.com/cloudskiff/driftctl , которая может даже больше, чем предложу Вашему вниманию чуть ниже я, но на момент написания статьи driftctl как минимум не поддерживает работу с aws provider v2, а также не умеет в multi region, что делает его использование невозможным в большинстве серьёзных проектов. Но ребята обещают доделать её через месяц-два.
А пока что опишу и приведу пример небольшого количества кода для следующей очень простой схемы:
создаём pipeline, который или по расписанию (в Gitlab можно воспользоваться Pipeline schedules) или по кругу будет делать terraform plan
при нахождении дрейфа (diff в плане) pipeline будет, например, отправлять сообщение с его содержанием в Slack.
Аналогично можно реализовать и, например, создание issue в любом из используемых вами репозиториев, где поддерживается их создание через api и любое другое действие, например apply, который вернёт инфраструктуру к её эталонному состоянию. Или всё-таки импортировать изменение в state, если оно действительно необходимо.
Допустим есть репозиторий содержащий код для вашей live инфраструктуры, т.е. код, которому она должна соответствовать и откуда она и была развёрнута с такой структурой:
account_1/
+-- eu-central-1
¦ +-- dev
¦ ¦ +-- eks
¦ ¦ ¦ +-- terragrunt.hcl
¦ ¦ ¦ L-- values.yaml
¦ ¦ L-- s3-bucket
¦ ¦ +-- terragrunt.hcl
¦ ¦ L-- values.yaml
¦ +-- prod
¦ ¦ +-- eks
¦ ¦ ¦ +-- terragrunt.hcl
¦ ¦ ¦ L-- values.yaml
¦ ¦ L-- s3-bucket
¦ ¦ +-- terragrunt.hcl
¦ ¦ L-- values.yaml
¦ L-- staging
¦ +-- eks
¦ ¦ +-- terragrunt.hcl
¦ ¦ L-- values.yaml
¦ L-- s3-bucket
¦ +-- terragrunt.hcl
¦ L-- values.yaml
+-- us-east-1
¦ +-- dev
¦ ¦ +-- eks
¦ ¦ ¦ +-- terragrunt.hcl
¦ ¦ ¦ L-- values.yaml
¦ ¦ L-- s3-bucket
¦ ¦ +-- terragrunt.hcl
¦ ¦ L-- values.yaml
¦ +-- prod
¦ ¦ +-- eks
¦ ¦ ¦ +-- terragrunt.hcl
¦ ¦ ¦ L-- values.yaml
¦ ¦ L-- s3-bucket
¦ ¦ +-- terragrunt.hcl
¦ ¦ L-- values.yaml
¦ L-- staging
¦ +-- eks
¦ ¦ +-- terragrunt.hcl
¦ ¦ L-- values.yaml
¦ L-- s3-bucket
¦ +-- terragrunt.hcl
¦ L-- values.yaml
L-- terragrunt.hcl
В приведённом выше примере в папке account_1
находятся 2 папки: us-east-1
и eu-central-1
, по имени регионов AWS. Иногда удобно организовать структуру именно так и тогда имена папок можно использовать как значение для передачи в модуль с помощью Terragrunt функции/й, например, таких "${basename(get_terragrunt_dir())}"
Аналогичная логика с папками имеющими в названии окружение и далее идут названия самих компонентов, которых в этом примере 2: eks
и s3-bucket
Если смотреть от корня репозитория, то путь до каждого из файлов внутри папки компонента
<account_name>/<region>/<environment>/<component>/*
Т.е. "в общих чертах" */*/*/<component>/*
Выберем, например, компонент s3-bucket (на самом деле конечно можно реализовать это для всего сразу, но бывают нюансы и здесь интересно показать принцип).
Не забудьте подключить Incoming WebHooks в Slack и записать полученный Webhook URL. Делается это так: https://api.slack.com/messaging/webhooks
Тогда вот такой скрипт может выполнять требуемое планирование в pipeline и отправку в Slack diff'а при его нахождении:
#!/bin/bash
ROOT_DIR=$(pwd)
plan () {
echo -e "$(date +'%H-%M-%S %d-%m-%Y') $F"
CURRENT_DIR=$(pwd)
PLAN=$CURRENT_DIR/plan.tfplan
terragrunt run-all plan --terragrunt-non-interactive -lock=false -detailed-exitcode -out=$PLAN 2>/dev/null || ec=$?
case $ec in
0) echo "No Changes Found"; exit 0;;
1) printf '%s\n' "Command exited with non-zero"; exit 1;;
2) echo "Changes Found! Reporting!";
MESSAGE=$(terragrunt show -no-color ${PLAN} | sed "s/\"/'/g"); # let's replace the double quotes from the diff with single as double quotes "break" the payload
curl -X POST --data-urlencode "payload={\"channel\": \"#your-slack-channel-here\", \"username\": \"webhookbot\", \"text\": \"DRIFT DETECTED!!!\n ${MESSAGE}\", \"icon_emoji\": \":ghost:\"}" https://hooks.slack.com/services/YOUR/WEBHOOK/URL_HERE;;
esac
}
N="$(($(grep -c ^processor /proc/cpuinfo)*4))" # any number suitable for your situation goes here
for F in */*/*/s3-bucket/*; do
((i=i%N)); ((i++==0)) && wait # let's run only N jobs in parallel to speed up the process
cd $ROOT_DIR
cd $F
plan & # send the job to background to start the new one
done
Меняем что-нибудь руками, запускаем pipeline или ждём его выполнения и радуемся :)
На этом на сегодня всё!
Если Вы решали подобную задачу иначе, есть конкретные замечания/предложения, или просто хочется что-то спросить, то, по мере возможности, готов выслушать либо в комментариях, либо в личке, например, в телеграм @vainkop
P.S.: имхо проект https://github.com/cloudskiff/driftctl мне лично кажется действительно полезным и решающим правильную задачу и хороших аналогов ему нет, так что прошу поддержать ребят, а по возможности внести свою лепту ибо open source.
Всем хорошего настроения!
event1
А не логичнее ли просто запретить любые ручные изменения и разворачивать инфраструктура строго из гита в момент push'а с помощью хуков?
vainkop Автор
Запретить, конечно, было бы здорово, но, к сожалению, часто встречается, что некоторым запретить доступ через UI или какие-то другие инструменты нельзя.
oji
Так какой же это GitOps, если вносятся сторонние умышленные отклонения? На мой взгляд, нужно исправлять культуру, кнутом ли, пряником ли.
Что касается «аналогов нет», если работаете с Kubernetes, то я бы посмотрел в сторону Crossplane. В отличие от Terraform и Pulumi, в нём реализован встроенный механизм постоянного контроля и исправления несоответствий.
vainkop Автор
Ситуация конечно далека от идеальной как и этот мир и инструменты вроде driftctl и в целом такое понятие, как infra drift появились не просто так.
Но мне нравится как вы излагаете, потому что DevOps это как раз не про написание инструментов, а про продвижение правильной культуры в организации и про пробитие определённых в том числе административных барьеров. Много раз видел когда куча технически грамотных людей пилят какую-то чепуху даже не потому что продукт такой, а потому что нет воли, желания и т.п. менять культуру в организации и они очевидно хотят просто тихо сидеть на своём месте и спокойно получать зарплату не развиваясь. Имхо DevOps должен быть двигателем и спокойная жизнь это не про DevOps. Поэтому многие люди, которые пришли в DevOps из системного администрирования, но работаю просто как продвинутые системные администраторы, которые умеют немного программировать, на самом деле не являются DevOps'ами ещё и потому, что работают в разных с культурой DevOps, как её понимаю я, парадигмах.
В этом конкретном случае организация, к сожалению, никак не готова согласиться на запрет этих ручных изменений.
Crossplane отличный инструмент, знаю его, но я имел ввиду (возможно выразился не совсем точно) именно про работающие с Terraform инструменты, которые можно было бы использовать с этой задачей.