Comecei a trabalhar com cloudformation há 4 anos. Desde então, destruí muita infraestrutura, até mesmo aquelas que já estavam em produção. Mas a cada vez que estragava algo, aprendia algo novo. Com essa experiência, compartilharei algumas das lições mais importantes que aprendi.

Lição 1: verifique as alterações antes de implantá-las
Aprendi essa lição logo que comecei a trabalhar com cloudformation. Não me lembro exatamente o que estraguei, mas lembro perfeitamente que usei o comando aws cloudformation update. Esse comando simplesmente aplica o template sem qualquer verificação das alterações que serão implementadas. Não acho que precise de explicações sobre a importância de verificar todas as alterações antes de implantá-las.
Após esse fracasso, mudei imediatamente o pipeline de implantação, substituindo o comando update pelo comando 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" Quando o conjunto de alterações é criado, ele não afeta de forma alguma o stack existente. Ao contrário do comando update, a abordagem com um conjunto de alterações não provoca a implementação real. Em vez disso, cria uma lista de alterações que você pode revisar antes da implantação. Você pode visualizar as alterações na interface da console AWS. Mas se você prefere automatizar tudo que puder, verifique-as no 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 tableEsse comando deve resultar em uma saída semelhante à seguinte:
--------------------------------------------------------------------
| DescribeChangeSet |
+---------+--------------------+----------------------+------------+
| Ação | ReplacementNeeded | Recurso | ResourceId |
+---------+--------------------+----------------------+------------+
| Modificar | True | AWS::ECS::Cluster | MyCluster |
| Substituir| True | AWS::RDS::DBInstance| MyDB |
| Adicionar | Nenhum | AWS::SNS::Topic | MyTopic |
+---------+--------------------+----------------------+------------+Preste especial atenção nas alterações onde Ação é Replace, Excluir ou onde ReplacementNeeded — True. Essas são as alterações mais perigosas e geralmente resultam em perda de informação.
Quando as alterações forem revisadas, elas podem ser implementadas
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"Lição 2: use políticas de stack para evitar a substituição ou remoção de recursos mantendo o estado
Às vezes, apenas revisar as alterações não é suficiente. Todos somos humanos e cometemos erros. Logo depois de começarmos a usar os conjuntos de alterações, meu colega de equipe, sem perceber, realizou uma implantação que resultou na atualização do banco de dados. Não houve problemas, pois era um ambiente de teste.
Apesar de nossos scripts exibirem uma lista de alterações e solicitarem confirmação, a alteração 'Replace' foi ignorada porque a lista de alterações era tão extensa que não cabia na tela. E como era uma atualização comum em um ambiente de teste, não se deu tanta atenção às mudanças.
Existem recursos que você nunca quer substituir ou excluir. Esses são serviços stateful, como uma instância de banco de dados RDS ou um cluster Elasticsearch, etc. Seria interessante se o AWS automaticamente se recusasse a implantar se a operação executada exigisse a exclusão de tal recurso. Felizmente, o CloudFormation tem uma maneira embutida de fazer isso. Isso é chamado de política de stack, e você pode aprender mais sobre isso em :
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"Lição 3: use UsePreviousValue ao atualizar o stack com parâmetros secretos
Quando você cria uma entidade RDS MySQL, a AWS exige que você forneça MasterUsername e MasterUserPassword. Como é melhor não armazenar segredos no código-fonte, e eu queria automatizar absolutamente tudo, implementei um "mecanismo inteligente", onde, antes da implantação, as credenciais são obtidas do S3, e se as credenciais não forem encontradas, novas credenciais são geradas e armazenadas no S3.
Essas credenciais serão passadas como parâmetros para o comando cloudformation create-change-set. Durante os experimentos com o script, aconteceu de a conexão com o S3 ser perdida, e meu "mecanismo inteligente" considerou isso um sinal para gerar novas credenciais.
Se eu começasse a usar esse script em um ambiente de produção e o problema de conexão ocorresse novamente, ele atualizaria a pilha com novas credenciais. Nesse caso específico, nada de ruim aconteceria. No entanto, eu abandonei essa abordagem e comecei a usar outra, fornecendo as credenciais apenas uma vez — na criação da pilha. E depois, quando a pilha precisasse de atualização, eu em vez de especificar o valor secreto do parâmetro, simplesmente utilizaria 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"Aula 4: use a configuração de rollback
Outro comando com o qual trabalhei utilizava uma função cloudformation, chamada rollback configuration. Eu não tinha encontrado isso antes e rapidamente percebi que isso tornaria a implantação das minhas pilhas ainda mais impressionante. Agora eu uso isso sempre que implanto meu código no lambda ou ECS com o cloudformation.
Como isso funciona: você especifica CloudWatch alarm arn no parâmetro —rollback-configuration, ao criar o conjunto de alterações. Mais tarde, quando você executar o conjunto de alterações, a aws monitora o alarme por pelo menos um minuto. Ele reverte a implantação se, durante esse tempo, o alarme mudar o estado para ALARM.
Abaixo está um exemplo de trecho de modelo cloudformation, onde estou criando cloudwatch alarm, monitorando uma métrica personalizada da nuvem na forma de número de erros nos logs da nuvem (a métrica é criada através de MetricFilter):
Resources:
# esta métrica rastreia o número de erros nos logs do cloudwatch. Neste
# caso específico, presume-se que os logs estejam em formato json e que os logs de erro sejam
# identificados pelo nível "error". Veja 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 minuto
EvaluationPeriods: 1
Threshold: 0
TreatMissingData: notBreaching
ActionsEnabled: yesAgora alarme pode ser usado como rollback gatilho ao executar um conjunto de ferramentas:
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"Lição 5: certifique-se de que você está implantando a versão mais recente do modelo
É fácil implantar uma versão não mais recente do modelo cloudformation, mas isso pode causar grandes danos. Uma vez aconteceu conosco: um desenvolvedor não enviou as últimas alterações do Git e, sem querer, implantou uma versão anterior da pilha. Isso levou a um tempo de inatividade do aplicativo que utilizava essa pilha.
Algo simples, como adicionar uma verificação se o ramo está atualizado antes de executar a implantação, seria bom (supondo que o git seja sua ferramenta de controle de versão):
git fetch
HEADHASH=$(git rev-parse HEAD)
UPSTREAMHASH=$(git rev-parse master@{upstream})
if [[ "$HEADHASH" != "$UPSTREAMHASH" ]] ; then
echo "O ramo não está atualizado em relação ao origin. Abortando"
exit 1
fiLição 6: não reinvente a roda
Pode parecer que implantar com cloudformation é fácil. Você só precisa de um monte de scripts bash executando comandos aws cli.
Há 4 anos, eu começava com scripts simples que chamam o comando aws cloudformation create-stack. Em breve o script já não era simples. Cada lição aprendida tornava o script cada vez mais complicado. Não era apenas difícil, mas cheio de bugs.
Agora trabalho em um pequeno departamento de TI. A experiência mostra que cada equipe tem sua própria maneira de implantar pilhas cloudformation. E isso é ruim. Seria melhor se todos usassem uma abordagem única. Felizmente, existem várias ferramentas que ajudam a implantar e configurar pilhas cloudformation.
Essas lições ajudarão você a evitar erros.
Fonte: habr.com
