Zacząłem pracować z cloudformation 4 lata temu. Od tego czasu złamałem wiele infrastruktury, nawet tej, która była już w produkcji. Ale za każdym razem, gdy coś zepsułem, uczyłem się czegoś nowego. Dzięki temu doświadczeniu podzielę się kilkoma z najważniejszych lekcji, które wyciągnąłem.

Lekcja 1: sprawdzaj zmiany przed ich wdrożeniem
Nauczyłem się tej lekcji szybko po tym, jak zacząłem pracować z cloudformation. Nie pamiętam, co dokładnie zepsułem wtedy, ale dokładnie pamiętam, że użyłem polecenia aws cloudformation update. To polecenie po prostu wdraża szablon bez jakiejkolwiek weryfikacji zmian, które zostaną wdrożone. Nie sądzę, że potrzebne są jakiekolwiek wyjaśnienia, dlaczego należy sprawdzić wszystkie zmiany przed ich wdrożeniem.
Po tej porażce natychmiast zmieniłem pipeline wdrożeniowy, zastępując polecenie update poleceniem 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" Gdy zestaw zmian zostanie utworzony, nie wpływa on w żaden sposób na istniejący stos. W przeciwieństwie do polecenia update, podejście z użyciem zestawu zmian nie powoduje faktycznego wdrożenia. Zamiast tego tworzy listę zmian, które można przeglądać przed wdrożeniem. Możesz przeglądać zmiany w interfejsie konsoli AWS. Ale jeśli wolisz zautomatyzować wszystko, co można, sprawdzaj je w 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 tableTo polecenie powinno zwrócić wynik podobny do poniższego:
--------------------------------------------------------------------
| DescribeChangeSet |
+---------+--------------------+----------------------+------------+
| Action | ReplacementNeeded | Resource | ResourceId |
+---------+--------------------+----------------------+------------+
| Modify | True | AWS::ECS::Cluster | MyCluster |
| Replace| True | AWS::RDS::DBInstance| MyDB |
| Add | None | AWS::SNS::Topic | MyTopic |
+---------+--------------------+----------------------+------------+Zwróć szczególną uwagę na zmiany, w których Action to Zamień, Usuń lub gdzie ReplacementNeeded — True. To są najniebezpieczniejsze zmiany i zazwyczaj prowadzą do utraty informacji.
Gdy zmiany zostaną przejrzane, mogą zostać wdrożone
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"Lekcja 2: używaj polityki stosu, aby zapobiec zastępowaniu lub usuwaniu zasobów przy zachowaniu stanu
Czasami samo przeglądanie zmian to za mało. Wszyscy jesteśmy ludźmi i wszyscy popełniamy błędy. Niedługo po tym, jak zaczęliśmy korzystać z zestawów zmian, mój kolega z zespołu nieświadomie przeprowadził wdrożenie, co doprowadziło do aktualizacji bazy danych. Nic poważnego się nie stało, ponieważ była to środowisko testowe.
Mimo że nasze skrypty wyświetlały listę zmian i prosiły o potwierdzenie, zmiana Replace została pominięta, ponieważ lista zmian była tak duża, że nie mieściła się na ekranie. A ponieważ była to zwykła aktualizacja w środowisku testowym, uwadze nie poświęcono zbyt wiele uwagi.
Są zasoby, których nigdy nie chcesz zastępować lub usuwać. To są usługi stateful, takie jak instancja bazy danych RDS czy klaster Elasticsearch itp. Byłoby dobrze, gdyby AWS automatycznie odmawiał wdrożenia, jeśli wykonywana operacja wymagałaby usunięcia takiego zasobu. Na szczęście CloudFormation ma wbudowany sposób, aby to osiągnąć. Nazywa się to polityką stosu i można o niej dowiedzieć się więcej w :
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"Lekcja 3: użyj UsePreviousValue, gdy aktualizujesz stos z tajnymi parametrami
Kiedy tworzysz instancję RDS MySQL, AWS wymaga podania MasterUsername i MasterUserPassword. Ponieważ lepiej nie przechowywać sekretów w kodzie źródłowym, a chciałem zautomatyzować wszystko, wprowadziłem „inteligentny mechanizm”, w którym przed wdrożeniem dane uwierzytelniające są pobierane z S3, a jeśli dane nie zostaną znalezione, generowane są nowe i przechowywane w S3.
Następnie te dane uwierzytelniające będą przekazane jako parametry do polecenia cloudformation create-change-set. Podczas eksperymentów ze skryptem zdarzyło się, że połączenie z S3 zostało utracone, a mój „inteligentny mechanizm” uznał to za sygnał do wygenerowania nowych danych uwierzytelniających.
Gdybym zaczął używać tego skryptu w środowisku produkcyjnym, a problem z połączeniem znów by wystąpił, zaktualizowałby stos nowymi danymi uwierzytelniającymi. W tej konkretnej sytuacji nic złego by się nie stało. Zrezygnowałem jednak z takiego podejścia i zacząłem używać innego, podając dane uwierzytelniające tylko raz — przy tworzeniu stosu. A później, gdy stos wymagałby aktualizacji, zamiast podawania tajnej wartości parametru, po prostu używałbym 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"Lekcja 4: użyj konfiguracji rollback
Inna komenda, z którą pracowałem, korzystała z funkcji cloudformation, zwanej rollback configuration. Nie spotkałem się z nią wcześniej i szybko zrozumiałem, że to uczyni moje wdrożenia jeszcze lepszymi. Używam tego teraz za każdym razem, gdy wdrażam mój kod w lambdzie lub ECS za pomocą cloudformation.
Jak to działa: wskazujesz CloudWatch alarm arn w parametrze —rollback-configuration, kiedy tworzysz zestaw zmian. Później, gdy wykonasz zestaw zmian, aws monitoruje alarm przez co najmniej jedną minutę. Cofnie wdrożenie, jeśli w tym czasie alarm zmieni stan na ALARM.
Poniżej znajduje się przykład fragmentu szablonu cloudformation, w którym tworzę cloudwatch alarm, monitorujący metrykę użytkową chmury w postaci liczby błędów w logach chmury (metryka tworzona przez MetricFilter):
Resources:
# ta metryka śledzi liczbę błędów w logach cloudwatch. W tym
# przypadku zakłada się, że logi są w formacie json i błędne logi są
# identyfikowane przez poziom "error". Zobacz 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 minuta
EvaluationPeriods: 1
Threshold: 0
TreatMissingData: notBreaching
ActionsEnabled: yesTeraz alarm może być używany jako rollback wyzwalacz podczas wykonywania zestawu narzędzi:
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"Lekcja 5: upewnij się, że wdrażasz najnowszą wersję szablonu
Łatwo jest wdrożyć starszą wersję szablonu CloudFormation, ale może to spowodować poważne problemy. Kiedyś mieliśmy taką sytuację: programista nie wysłał najnowszych zmian z Gita i niechcący wdrożył poprzednią wersję stosu. Doprowadziło to do przestoju aplikacji, która korzystała z tego stosu.
Coś prostego, na przykład dodanie sprawdzania, czy gałąź jest aktualna przed wykonaniem wdrożenia, będzie dobre (zakładając, że git to twoje narzędzie kontroli wersji):
git fetch
HEADHASH=$(git rev-parse HEAD)
UPSTREAMHASH=$(git rev-parse master@{upstream})
if [[ "$HEADHASH" != "$UPSTREAMHASH" ]] ; then
echo "Gałąź nie jest aktualna w stosunku do origin. Anulowanie"
exit 1
fiLekcja 6: nie wymyślaj koła na nowo
Może się wydawać, że wdrożenie z cloudformation to łatwe. Wystarczy kilka skryptów bash wykonujących polecenia aws cli.
4 lata temu zaczynałem od prostych skryptów, które nazywały komendę aws cloudformation create-stack. Wkrótce skrypt przestał być prosty. Każda wyciągnięta lekcja sprawiała, że skrypt był coraz bardziej skomplikowany. Było to nie tylko trudne, ale również pełne błędów.
Obecnie pracuję w małym dziale IT. Doświadczenie pokazuje, że każda drużyna ma swój sposób wdrażania stosów CloudFormation. I to jest złe. Byłoby lepiej, gdyby wszyscy używali jednego podejścia. Na szczęście istnieje wiele narzędzi, które pomagają wdrożyć i skonfigurować stosy CloudFormation.
Te lekcje pomogą ci uniknąć błędów.
Źródło: habr.com
