Ho iniziato a lavorare con cloudformation 4 anni fa. Da allora ho distrutto molte infrastrutture, anche quelle che erano già in produzione. Ma ogni volta che rovinavo qualcosa, imparavo qualcosa di nuovo. Grazie a questa esperienza, condividerò alcune delle lezioni più importanti che ho appreso.

Lezione 1: controlla le modifiche prima di distribuirle
Ho imparato questa lezione subito dopo aver iniziato a lavorare con cloudformation. Non ricordo esattamente cosa abbia rotto, ma ricordo perfettamente che ho usato il comando aws cloudformation update. Questo comando distribuisce semplicemente il modello senza alcun controllo delle modifiche che verranno applicate. Non credo siano necessarie spiegazioni sul perché sia importante controllare tutte le modifiche prima di distribuirle.
Dopo quel fallimento, ho subito modificato deployment pipeline, sostituendo il comando update con il 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 il set di modifiche è creato, non influisce sullo stack esistente. A differenza del comando update, l'approccio con il set di modifiche non provoca una distribuzione reale. Invece, crea un elenco di modifiche che puoi esaminare prima della distribuzione. Puoi visualizzare le modifiche nell'interfaccia della console aws. Ma se preferisci automatizzare tutto ciò che è possibile, controllale nel 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 tableQuesto comando dovrebbe restituire un output simile al seguente:
--------------------------------------------------------------------
| DescribeChangeSet |
+---------+--------------------+----------------------+------------+
| Action | ReplacementNeeded | Resource | ResourceId |
+---------+--------------------+----------------------+------------+
| Modify | True | AWS::ECS::Cluster | MyCluster |
| Replace| True | AWS::RDS::DBInstance| MyDB |
| Add | None | AWS::SNS::Topic | MyTopic |
+---------+--------------------+----------------------+------------+Presta particolare attenzione alle modifiche in cui l'Action è Sostituisci, Elimina o dove ReplacementNeeded — True. Queste sono le modifiche più pericolose e di solito portano a perdite di dati.
Una volta esaminate le modifiche, possono essere distribuite
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"Lezione 2: usa la stack policy per prevenire la sostituzione o la rimozione delle risorse mantenendo lo stato
A volte semplicemente visualizzare le modifiche non è sufficiente. Siamo tutti esseri umani e commettiamo errori. Poco dopo aver iniziato a utilizzare i set di modifiche, un mio compagno di squadra ha inavvertitamente eseguito il deployment, il che ha portato a un aggiornamento del database. Non è successo niente di grave, poiché si trattava di un ambiente di test.
Anche se i nostri script mostravano un elenco di modifiche e chiedevano una conferma, la modifica Replace è stata saltata, poiché l'elenco delle modifiche era così lungo da non entrare nello schermo. E poiché si trattava di un aggiornamento ordinario nell'ambiente di test, le modifiche non hanno ricevuto molta attenzione.
Ci sono risorse che non vorresti mai sostituire o eliminare. Si tratta di servizi stateful, come un'istanza di database RDS o un cluster Elasticsearch, ecc. Sarebbe utile se AWS rifiutasse automaticamente il deployment se l'operazione in corso richiedesse di eliminare tale risorsa. Fortunatamente, CloudFormation ha un modo integrato per farlo. Si chiama stack policy, e puoi saperne di più su :
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"Lezione 3: utilizza UsePreviousValue quando aggiorni lo stack con parametri segreti
Quando crei un'entità RDS MySQL, AWS richiede che tu fornisca MasterUsername e MasterUserPassword. Poiché è meglio non memorizzare segreti nel codice sorgente, e volevo automatizzare tutto, ho implementato un "meccanismo intelligente", in base al quale le credenziali vengono recuperate da S3 prima del deployment, e se le credenziali non vengono trovate, ne vengono generate di nuove e memorizzate in S3.
Queste credenziali verranno quindi passate come parametri al comando cloudformation create-change-set. Durante gli esperimenti con lo script, è capitato che la connessione a S3 fosse persa, e il mio "meccanismo intelligente" lo interpretava come un segnale per generare nuove credenziali.
Se avessi iniziato a utilizzare questo script in un ambiente di lavoro e il problema di connessione si fosse ripresentato, avrebbe aggiornato lo stack con nuove credenziali. In questo caso specifico, non succederebbe nulla di male. Tuttavia, ho rinunciato a questo approccio e ho iniziato a utilizzare un altro metodo, fornendo le credenziali solo una volta, al momento della creazione dello stack. E in seguito, quando lo stack richiederà un aggiornamento, invece di indicare il valore segreto del parametro, utilizzerei semplicemente 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"Lezione 4: utilizza la configurazione di rollback
Un altro comando con cui ho lavorato utilizzava una funzione cloudformation, chiamata rollback configuration. Non l'avevo mai incontrata prima e ho capito rapidamente che avrebbe reso l'implementazione dei miei stack ancora più efficace. Ora la utilizzo ogni volta che distribuisco il mio codice in lambda o ECS tramite cloudformation.
Come funziona: specifichi CloudWatch alarm arn nel parametro —rollback-configuration, quando crei un set di modifiche. In seguito, quando esegui il set di modifiche, aws monitora l'allarme per almeno un minuto. Ripristina l'implementazione se durante questo tempo l'allarme cambia stato in ALARM.
Di seguito è riportato un esempio di frammento di template cloudformation, in cui creo cloudwatch alarm, che monitorizza una metrica personalizzata del cloud sotto forma di numero di errori nei log del cloud (la metrica è creata tramite MetricFilter):
Resources:
# questa metrica tiene traccia del numero di errori nei log di cloudwatch. In questo
# caso particolare si presume che i log siano in formato json e i log degli errori siano
# identificati dal livello "error". Vedi 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: yesAttualmente allarme può essere utilizzato come rollback trigger durante l'esecuzione del set di strumenti:
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"Lezione 5: assicurati di implementare l'ultima versione del modello
È facile implementare una versione di CloudFormation che non è l'ultima, ma questo può causare grandi danni. Una volta è successo a noi: un programmatore non ha inviato le ultime modifiche da Git e ha inavvertitamente implementato una versione precedente dello stack. Questo ha portato a un'interruzione dell'applicazione che utilizzava quello stack.
Qualcosa di semplice, come aggiungere un controllo per verificare se il ramo è aggiornato prima di eseguire l'implementazione, sarebbe utile (ammesso che git sia il tuo strumento di controllo della versione):
git fetch
HEADHASH=$(git rev-parse HEAD)
UPSTREAMHASH=$(git rev-parse master@{upstream})
if [[ "$HEADHASH" != "$UPSTREAMHASH" ]] ; then
echo "Il ramo non è aggiornato con l'origine. Interruzione dell'operazione"
exit 1
fiLezione 6: non reinventare la ruota
Potrebbe sembrare che implementare con cloudformation sia semplice. Ti servono solo un sacco di script bash che eseguono comandi aws cli.
Quattro anni fa ho iniziato con semplici script che chiamano il comando aws cloudformation create-stack. Presto lo script non era più semplice. Ogni lezione appresa rendeva lo script sempre più complesso. Non era solo complicato, ma anche pieno di bug.
Ora lavoro in un piccolo dipartimento IT. L'esperienza dimostra che ogni team ha il proprio modo di implementare gli stack di CloudFormation. E questo è un problema. Sarebbe meglio se tutti seguissero un approccio unico. Fortunatamente, esistono molti strumenti che aiutano a implementare e configurare gli stack di CloudFormation.
Queste lezioni ti aiuteranno a evitare errori.
Fonte: habr.com
