Я начал работать с cloudformation 4 года назад. С тех пор я сломал много инфраструктур, даже те, которые уже были в продукции. Но каждый раз, когда я что-то портил, узнавал новое. Благодаря этому опыту, я поделюсь некоторыми из самых важных уроков, которые я выучил.
Урок 1: проверяйте изменения перед тем, как развернуть их
Я усвоил этот урок вскоре как начал работать с cloudformation. Не помню, что именно я тогда сломал, но точно помню, что использовал команду aws cloudformation update. Эта команда просто выкатывает шаблон без какой-либо проверки изменений, которые будут развернуты. Не думаю, что требуются объяснения, для чего нужно проверить все изменения перед тем, как развернуть их.
После этого провала, я сразу же изменил deployment pipeline, заменив команду update командой create-change-set
# OPERATION is either "UPDATE" or "CREATE"
changeset_id=$(aws cloudformation create-change-set
--change-set-name "$CHANGE_SET_NAME"
--stack-name "$STACK_NAME"
--template-body "$TPL_PATH"
--change-set-type "$OPERATION"
--parameters "$PARAMETERS"
--output text
--query Id)
aws cloudformation wait
change-set-create-complete --change-set-name "$changeset_id"
Когда набор изменений создан, он никак не влияет на существующий стек. В отличие от команды update, подход с использованием набора изменений не вызывает фактического развертывания. Вместо этого он создает список изменений, которые вы можете просмотреть до развертывания. Вы можете просмотреть изменения в интерфейсе консоли aws. Но если вы предпочитаете автоматизировать всё, что только можно, то проверяйте их в CLI:
# this command is presented only for demonstrational purposes.
# the real command should take pagination into account
aws cloudformation describe-change-set
--change-set-name "$changeset_id"
--query 'Changes[*].ResourceChange.{Action:Action,Resource:ResourceType,ResourceId:LogicalResourceId,ReplacementNeeded:Replacement}'
--output table
Эта команда должна выдать вывод, похожий на следующий:
--------------------------------------------------------------------
| DescribeChangeSet |
+---------+--------------------+----------------------+------------+
| Action | ReplacementNeeded | Resource | ResourceId |
+---------+--------------------+----------------------+------------+
| Modify | True | AWS::ECS::Cluster | MyCluster |
| Replace| True | AWS::RDS::DBInstance| MyDB |
| Add | None | AWS::SNS::Topic | MyTopic |
+---------+--------------------+----------------------+------------+
Обратите особое внимание на изменения, где Action это Replace, Delete или где ReplacementNeeded — True. Это самые опасные изменения и обычно они приводят к потере информации.
Когда изменения просмотрены, они могут быть развернуты
aws cloudformation execute-change-set --change-set-name "$changeset_id"
operation_lowercase=$(echo "$OPERATION" | tr '[:upper:]' '[:lower:]')
aws cloudformation wait "stack-${operation_lowercase}-complete"
--stack-name "$STACK_NAME"
Урок 2: используйте stack policy для предотвращения замены или удаления ресурсов с сохранением состояния
Иногда простого просмотра изменений недостаточно. Все мы люди и все допускаем ошибки. Вскоре после того, как мы начали использовать наборы изменений, мой товарищ по команде неосознанно выполнил развертывание, что привело к обновлению базы данных. Ничего страшного не произошло, потому что это была среда тестирования.
Несмотря на то, что наши скрипты отображали список изменений и просили подтверждения, изменение Replace было пропущено, потому что список изменений был настолько большим, что не помещался на экране. И поскольку это было обычное обновление в среде тестирования, изменениям уделялось не так много внимания.
Есть ресурсы, которые вы никогда не захотите заменить или удалить. Это statefull сервисы, такие как экземпляр базы данных RDS или кластер elastichsearch и т. д. Было бы хорошо, если бы aws автоматически отказывал в развертывании, если выполняемая операция потребует удаления такого ресурса. К счастью, cloudformation имеет встроенный способ сделать это. Это называется stack policy, и об этом можно больше узнать в
STACK_NAME=$1
RESOURCE_ID=$2
POLICY_JSON=$(cat <<EOF
{
"Statement" : [{
"Effect" : "Deny",
"Action" : [
"Update:Replace",
"Update:Delete"
],
"Principal": "*",
"Resource" : "LogicalResourceId/$RESOURCE_ID"
}]
}
EOF
)
aws cloudformation set-stack-policy --stack-name "$STACK_NAME"
--stack-policy-body "$POLICY_JSON"
Урок 3: используйте UsePreviousValue, когда обновляете стек с секретными параметрами
Когда вы создаете сущность RDS mysql AWS требует от вас предоставить MasterUsername и MasterUserPassword. Поскольку лучше не хранить секреты в исходном коде, и я хотел автоматизировать абсолютно все, я реализовал «умный механизм», при котором перед развертыванием учетные данные будут получены из s3, и если учетные данные не будут найдены, новые учетные данные генерируются и хранятся в s3.
Затем эти учетные данные будут переданы в качестве параметров команде cloudformation create-change-set. Во время экспериментов со скриптом случилось, что соединение с s3 было потеряно, и мой «умный механизм» рассматривал его как сигнал для генерации новых учетных данных.
Если бы я начал использовать этот скрипт в рабочей среде, и проблема с подключением возникла бы снова, он бы обновил стек новыми учетными данными. В этом конкретном случае ничего плохого не произойдет. Однако я отказался от такого подхода и начал использовать другой, предоставляя учетные данные только один раз — при создании стека. И позже, когда стек потребует обновления, я бы вместо указания секретного значения параметра просто использовал UsePreviousValue=true:
aws cloudformation create-change-set
--change-set-name "$CHANGE_SET_NAME"
--stack-name "$STACK_NAME"
--template-body "$TPL_PATH"
--change-set-type "UPDATE"
--parameters "ParameterKey=MasterUserPassword,UsePreviousValue=true"
Урок 4: используйте rollback configuration
Другая команда, с которой я работал, использовала функцию cloudformation, называемую rollback configuration. Я не встречался с ней раньше и быстро понял, что это сделает развертывание моих стеков еще круче. Теперь я использую каждый раз, когда разворачиваю свой код в lambda или ECS с помощью cloudformation.
Как это работает: вы указываете CloudWatch alarm arn в параметре —rollback-configuration, когда создаете набор изменений. Позже, когда вы выполните набор изменений, aws отслеживает alarm не менее одной минуты. Он откатывает развертывание обратно, если в течение этого времени alarm изменяет состояние на ALARM.
Ниже приведен пример отрывка шаблона cloudformation, в котором я создаю cloudwatch alarm, отслеживающий пользовательскую метрику облака в виде числа ошибок в журналах облака (метрика создается через MetricFilter):
Resources:
# this metric tracks number of errors in the cloudwatch logs. In this
# particular case it's assumed logs are in json format and the error logs are
# identified by level "error". See FilterPattern
ErrorMetricFilter:
Type: AWS::Logs::MetricFilter
Properties:
LogGroupName: !Ref LogGroup
FilterPattern: !Sub '{$.level = "error"}'
MetricTransformations:
- MetricNamespace: !Sub "${AWS::StackName}-log-errors"
MetricName: Errors
MetricValue: 1
DefaultValue: 0
ErrorAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${AWS::StackName}-errors"
Namespace: !Sub "${AWS::StackName}-log-errors"
MetricName: Errors
Statistic: Maximum
ComparisonOperator: GreaterThanThreshold
Period: 1 # 1 minute
EvaluationPeriods: 1
Threshold: 0
TreatMissingData: notBreaching
ActionsEnabled: yes
Сейчас alarm может быть использован как rollback триггер при выполнении набора инструментов:
ALARM_ARN=$1
ROLLBACK_TRIGGER=$(cat <<EOF
{
"RollbackTriggers": [
{
"Arn": "$ALARM_ARN",
"Type": "AWS::CloudWatch::Alarm"
}
],
"MonitoringTimeInMinutes": 1
}
EOF
)
aws cloudformation create-change-set
--change-set-name "$CHANGE_SET_NAME"
--stack-name "$STACK_NAME"
--template-body "$TPL_PATH"
--change-set-type "UPDATE"
--rollback-configuration "$ROLLBACK_TRIGGER"
Урок 5: убедитесь, что вы разворачиваете самую последнюю версию шаблона
Легко развернуть не самую последнюю версию шаблона cloudformation, но это нанесет большой ущерб. Однажды так было у нас: разработчик не отправил последние изменения из Git и неосознанно развернул предыдущую версию стека. Это привело к простою приложения, которое использовало этот стек.
Что-то простое, например, добавление проверки, актуальна ли ветка, прежде чем выполнять развертывание, будет хорошо (если предположить, что git — это ваш инструмент контроля версий):
git fetch
HEADHASH=$(git rev-parse HEAD)
UPSTREAMHASH=$(git rev-parse master@{upstream})
if [[ "$HEADHASH" != "$UPSTREAMHASH" ]] ; then
echo "Branch is not up to date with origin. Aborting"
exit 1
fi
Урок 6: не изобретайте велосипед
Может показаться, что развертывание с cloudformation — это легко. Вам просто нужна куча скриптов bash, выполняющих команды aws cli.
4 года назад я начинал с простых скриптов, которые называют aws cloudformation create-stack командой. Вскоре скрипт уже не был простым. Каждый выученный урок делал скрипт все более и более сложным. Было не только сложно, но и с кучей багов.
Сейчас я работаю в небольшом ИТ-отделе. Опыт показывает, что у каждой команды есть свой способ развертывания стеков cloudformation. И это плохо. Было бы лучше, если бы все использовали единый подход. К счастью, существует множество инструментов, которые помогают развернуть и настроить стеки cloudformation.
Эти уроки помогут вам избежать ошибок.
Источник: habr.com