Ці 6 уроків роботи з cloudformation я засвоїв на все життя

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

Ці 6 уроків роботи з cloudformation я засвоїв на все життя

Урок 1: перевіряйте зміни перед тим, як розгорнути їх

Я засвоїв цей урок незабаром як почав працювати з утворення хмар. Не пам'ятаю, що саме я тоді зламав, але точно пам'ятаю, що використав команду aws cloudformation update. Ця команда просто викочує шаблон без перевірки змін, які будуть розгорнуті. Не думаю, що потрібні пояснення, для чого потрібно перевірити всі зміни перед тим, як їх розгорнути.

Після цього провалу я відразу ж змінив трубопровід розгортання, замінивши команду 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 це заміщати, видаляти або де 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 була пропущена, тому що список змін був настільки великим, що не розміщувався на екрані. І оскільки це було звичайне оновлення в тестуванні, змінам приділялося не так багато уваги.

Є ресурси, які ви ніколи не захочете замінити чи видалити. Це стандартнісервіси, такі як екземпляр бази даних 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

Інша команда, з якою я працював, використовувала функцію утворення хмар, звану rollback configuration. Я не зустрічався з нею раніше і швидко зрозумів, що це зробить розгортання моїх стеків ще крутіше. Тепер я використовую щоразу, коли розгортаю свій код в lambda або ECS за допомогою cloudformation.

Як це працює: ви вказуєте CloudWatch alarm arn у параметрі -rollback-configuration, коли ви створюєте набір змін. Пізніше, коли ви здійсните набір змін, aws відстежує alarm не менше однієї хвилини. Він відкочує розгортання назад, якщо протягом цього часу alarm змінює стан ALARM.

Нижче наведено приклад уривка шаблону утворення хмар, в якому я створюю 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_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: не вигадуйте велосипед

Може здатися, що розгортання з утворення хмар - це легко. Вам просто потрібна купа скриптів bash, які виконують команди aws cli.

4 роки тому я починав із простих скриптів, які називають aws cloudformation create-stack командою. Незабаром скрипт не був простим. Кожен вивчений урок робив скрипт дедалі складнішим. Було не лише складно, а й з купою багів.

Зараз я працюю у невеликому ІТ-відділі. Досвід показує, що кожна команда має свій спосіб розгортання стеків cloudformation. І це погано. Було б краще, якби всі використовували єдиний підхід. На щастя, існує безліч інструментів, які допомагають розгорнути і налаштувати стеки cloudformation.

Ці уроки допоможуть вам уникнути помилок.

Джерело: habr.com

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