Comencé a trabajar con cloudformation hace 4 años. Desde entonces, he roto muchas infraestructuras, incluso aquellas que ya estaban en producción. Pero cada vez que rompía algo, aprendía algo nuevo. Gracias a esta experiencia, compartiré algunas de las lecciones más importantes que he aprendido.

Lección 1: verifica los cambios antes de desplegarlos
Aprendí esta lección poco después de comenzar a trabajar con cloudformation. No recuerdo exactamente qué rompí en ese momento, pero sí recuerdo que utilicé el comando aws cloudformation update. Este comando simplemente despliega la plantilla sin ninguna verificación de los cambios que se van a realizar. No creo que necesite explicaciones sobre por qué es necesario verificar todos los cambios antes de desplegarlos.
Después de ese fracaso, inmediatamente cambié mi pipeline de despliegue, reemplazando el comando update con el 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" Cuando se crea un conjunto de cambios, no afecta al stack existente. A diferencia del comando update, el enfoque de usar un conjunto de cambios no provoca un despliegue real. En su lugar, genera una lista de cambios, que puedes revisar antes del despliegue. Puedes ver los cambios en la interfaz de la consola de aws. Pero si prefieres automatizar todo lo que sea posible, revísalos en la 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 tableEste comando debería generar una salida similar a la siguiente:
--------------------------------------------------------------------
| DescribeChangeSet |
+---------+--------------------+----------------------+------------+
| Action | ReplacementNeeded | Resource | ResourceId |
+---------+--------------------+----------------------+------------+
| Modify | True | AWS::ECS::Cluster | MyCluster |
| Replace| True | AWS::RDS::DBInstance| MyDB |
| Add | None | AWS::SNS::Topic | MyTopic |
+---------+--------------------+----------------------+------------+Presta especial atención a los cambios donde Action es Replace, Eliminar o donde ReplacementNeeded — True. Estos son los cambios más peligrosos y generalmente conducen a la pérdida de información.
Una vez revisados los cambios, pueden ser desplegados
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"Lección 2: usa una política de stack para evitar la reemplazo o eliminación de recursos mientras se conserva el estado
A veces, simplemente revisar los cambios no es suficiente. Todos somos humanos y todos cometemos errores. Poco después de comenzar a usar los conjuntos de cambios, mi compañero de equipo realizó un despliegue sin darse cuenta, lo que llevó a una actualización de la base de datos. No pasó nada grave porque era un entorno de pruebas.
A pesar de que nuestros scripts mostraban una lista de cambios y pedían confirmación, el cambio Replace se pasó por alto porque la lista de cambios era tan extensa que no cabía en la pantalla. Y dado que era una actualización común en el entorno de pruebas, no se prestó mucha atención a los cambios.
Hay recursos que nunca querrías reemplazar o eliminar. Son servicios con estado, como una instancia de base de datos RDS o un clúster de Elasticsearch, etc. Sería bueno que AWS rechazara automáticamente el despliegue si la operación requiriera eliminar tal recurso. Afortunadamente, CloudFormation tiene una manera incorporada de hacer esto. Se llama política de pila, y se puede aprender más sobre ello en :
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"Lección 3: utiliza UsePreviousValue al actualizar la pila con parámetros secretos
Cuando creas una entidad RDS MySQL, AWS te solicita que proporciones MasterUsername y MasterUserPassword. Dado que es mejor no almacenar secretos en el código fuente, y quería automatizar absolutamente todo, implementé un "mecanismo inteligente" en el que, antes del despliegue, las credenciales se obtendrán de S3, y si no se encuentran, se generarán nuevas credenciales y se almacenarán en S3.
Luego, estas credenciales se pasarán como parámetros al comando cloudformation create-change-set. Durante los experimentos con el script, ocurrió que se perdió la conexión con S3, y mi "mecanismo inteligente" lo interpretó como una señal para generar nuevas credenciales.
Si comenzara a usar este script en un entorno de trabajo y el problema de conexión volviera a ocurrir, actualizaría la pila con nuevas credenciales. En este caso particular, no pasaría nada malo. Sin embargo, opté por un enfoque diferente y comencé a proporcionar las credenciales solo una vez, al crear la pila. Y más tarde, cuando la pila necesite una actualización, en lugar de especificar el valor secreto del parámetro, simplemente usaría 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"Lección 4: utiliza la configuración de rollback
Otro comando con el que trabajé usaba una función cloudformation, llamada rollback configuration. No me había encontrado con ella antes y rápidamente comprendí que haría que el despliegue de mis pilas fuera aún mejor. Ahora la uso cada vez que despliego mi código en Lambda o ECS utilizando CloudFormation.
Cómo funciona: especificas CloudWatch alarm arn en el parámetro —rollback-configuration, al crear un conjunto de cambios. Más tarde, cuando ejecutes el conjunto de cambios, AWS monitorea el alarm durante al menos un minuto. Revierte el despliegue si durante este tiempo el alarm cambia su estado a ALARM.
A continuación se muestra un ejemplo de fragmento de plantilla cloudformation, en el que creo cloudwatch alarm, que rastrea una métrica personalizada en la nube en forma de número de errores en los logs de la nube (la métrica se crea a través de MetricFilter):
Resources:
# esta métrica rastrea el número de errores en los logs de cloudwatch. En este
# caso particular se asume que los logs están en formato json y los logs de error son
# identificados por el nivel "error". Véase 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: yesAhora alarm puede ser utilizado como rollback disparador al ejecutar el conjunto de herramientas:
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"Lección 5: asegúrate de que estás desplegando la versión más reciente de la plantilla
Es fácil desplegar una versión de plantilla de CloudFormation que no sea la más reciente, pero esto puede causar grandes problemas. Una vez nos sucedió: un desarrollador no envió los últimos cambios de Git y, sin darse cuenta, desplegó una versión anterior de la pila. Esto provocó tiempo de inactividad en la aplicación que utilizaba esa pila.
Algo simple, como agregar una verificación para asegurarte de que la rama está actualizada antes de ejecutar el despliegue, sería beneficioso (asumiendo que git es tu herramienta de control de versiones):
git fetch
HEADHASH=$(git rev-parse HEAD)
UPSTREAMHASH=$(git rev-parse master@{upstream})
if [[ "$HEADHASH" != "$UPSTREAMHASH" ]] ; then
echo "La rama no está actualizada con origin. Abortando"
exit 1
fiLección 6: no inventes la rueda
Puede parecer que desplegar con cloudformation es fácil. Solo necesitas un montón de scripts bash que ejecuten comandos de aws cli.
Hace 4 años comencé con scripts simples que llamaban al comando aws cloudformation create-stack. Pronto el script dejó de ser simple. Cada lección aprendida hacía el script cada vez más complejo. No solo era complicado, sino que estaba lleno de errores.
Ahora trabajo en un pequeño departamento de TI. La experiencia muestra que cada equipo tiene su propio enfoque para desplegar pilas de CloudFormation. Y eso es malo. Sería mejor si todos usaran un enfoque cohesivo. Afortunadamente, hay muchas herramientas que ayudan a desplegar y configurar pilas de CloudFormation.
Estas lecciones te ayudarán a evitar errores.
Fuente: habr.com
