Auto Scaling Lifecycle Hooks

AWS User Group São Paulo
awsugsp
Published in
6 min readJul 19, 2017

A alguns anos venho percebo que existem pouco conteúdo de AWS em português, mas alguns temas simplesmente não se acha absolutamente nada. Por isso estou escrevendo sobre Auto Scaling Lifecycle Hooks

Auto Scaling lifecycle hooks permite que sejam realizadas ações personalizadas quando o Auto Scaling inicia ou termina alguma instancia. Por exemplo, pode-se configurar ou instalar algo quando uma nova instancia é iniciada, ou fazer backup dos arquivos de log antes que uma instancia seja terminada.

Dito isso, vamos começar com os trabalhos. A ideia desse post é dar opções para utilizar essa função da forma que mais for útil em seu ambiente.

Para essas configurações, recomendo criar uma instancia simples com Amazon Linux na mesma região a ser configurado lifecycle hooks na AWS para servir de base de apoio (o qual chamaremos de “base_server” nesse post). Essa instancia precisará possuir ROLE com permissão manipulação de funções de autoscaling, SNS e Lambda.

Obs.: fique atento ao copiar um comando com caracteres quebrados, pois o “-” duplos normalmente sai quebrado.

Para essas configurações, precisamos ter o nome do AutoScaling que receberá essas configurações. Nesse caso, estou partindo de um principio que o nome dele é “autoscaling_group”. Mas estarão sendo representados por variáveis:

Logado no base_server, execute os comandos abaixo:

LC_HOOK_LAUNCHING=”lab-lifecycle-hook”
AS_NAME=”autoscaling_group”
REGIAO_AWS=$(curl -s http://instance-data/latest/dynamic/instance-identity/document | grep region | awk -F’”’ ‘{print $4}’)
aws — region $REGIAO_AWS autoscaling put-lifecycle-hook — lifecycle-hook-name $LIFECYCLE_HOOK_NAME_LAUNCHING — auto-scaling-group-name AUTOSCALING_NAME — lifecycle-transition autoscaling:EC2_INSTANCE_LAUNCHING

Depois de criar o lifecycle hook, toda instancia iniciada nesse AutoScaling ficará com o status de Pending:Wait até que alguém mude o status dela ou passe 60min por conta do time-out.
Nesse caso, no script de user-data de inicialização da instancia, após realizar tudo que for necessário para sua inicialização, colocaremos uma notificação para o Lifecycle Hooks liberar a instancia para que ela fique com o status de InService.

Segue um exemplo simples de script de update de SO e instalação e configuração do Apache. Esse script deve ficar no UserData da instancia que esta sendo iniciado pelo AutoScaling

#!/bin/bash
LC_HOOK_LAUNCHING=”devops-pro-hook”
AS_NAME=”autoscaling_group”
HOSTNAME=$(curl http://instance-data/latest/meta-data/local-hostname)
REGIAO_AWS=$(curl -s http://instance-data/latest/dynamic/instance-identity/document | grep region | awk -F’”’ ‘{print $4}’)
INSTANCE_ID=$(curl http://instance-data/latest/meta-data/instance-id/)
yum update -y && \
yum install -y httpd && \
service httpd start && \
chkconfig httpd on && \
echo “<h1>Bem vindo ao seu servidor WEB</h1> <h2>Instance-id $INSTANCE_ID</h2> <h2>hostname $HOSTNAME</h2>” > /var/www/html/index.html && \
chmod 644 /var/www/html/index.html && \
chown root:root /var/www/html/index.html && \
aws autoscaling complete-lifecycle-action — lifecycle-action-result CONTINUE — instance-id $INSTANCE_ID — lifecycle-hook-name $LC_HOOK_LAUNCHING — auto-scaling-group-name $AS_NAME — region $REGIAO_AWS || \
aws autoscaling complete-lifecycle-action — lifecycle-action-result ABANDON — instance-id $INSTANCE_ID — lifecycle-hook-name $LC_HOOK_LAUNCHING — auto-scaling-group-name $AS_NAME — region $REGIAO_AWS

Nesse caso, o script tenta instalar o Apache e configurar uma pagina WEB inicial, caso tudo ocorra bem, será enviado um result ao lifecycle de CONTINUE para ele deixar a instancia como InService.
Caso ocorra algo errado, será enviado um comando ABANDON, o que irá fazer com que a instancia seja terminada.

Esse procedimento é muito utilizado para ambientes com deploy mais elaborado e demorado, fazendo com que a instancia só entre em produção e seja apresentada ao ELB após o termino (com sucesso) do deploy, mitigando os riscos de uma instancia entrar em produção antes de estar 100% pronta.

Para esse LyfeCycle, utilizaremos, alem do AutoScaling, SNS e Lambda para manipular a instancia após durante o processo de terminate do AutoScaling

Primeiro, vamos criando um tópico de SNS

REGIAO_AWS=$(curl -s http://instance-data/latest/dynamic/instance-identity/document | grep region | awk -F’”’ ‘{print $4}’)
SNS_NAME=”sns-autoscaling”
SNS_ARN=$(aws — profile mandic sns — region $REGIAO_AWS create-topic — name $SNS_NAME — output text | tr -d ‘\r’)

Após essa criação, se atente a guardar o valor da variável $SNS_ARN que utilizaremos na sequência.

Agora criaremos uma função Lambda que irá realizar um snapshot da instancia antes de ela ser finalizada.

Antes, precisamos criar a role a ser usada pelo SNS.

echo “{
\”Version\”: \”2012–10–17\”,
\”Statement\”: [ {
\”Sid\”: \”\”,
\”Effect\”: \”Allow\”,
\”Principal\”: {
\”Service\”: \”autoscaling.amazonaws.com\”
},
\”Action\”: \”sts:AssumeRole\”
} ]
}” > SNS-Role-Trust-Policy.json

echo “{
\”Version\”: \”2012–10–17\”,
\”Statement\”: [ {
\”Action\”: \”sns:*\”,
\”Effect\”: \”Allow\”,
\”Resource\”: \”$SNS_ARN\”
}
]
}” > SNS-Role-Inline-Policy.json

SNS_PUBLISHER_ROLE=”sns_publisher_role”
SNS_POLICY_NAME=”sns_publisher_policy”
SNS_ROLE_ARN=$(aws iam — region $REGIAO_AWS create-role — role-name $SNS_PUBLISHER_ROLE — assume-role-policy-document file://SNS-Role-Trust-Policy.json — query “Role.Arn” — output text)
aws iam — region $REGIAO_AWS put-role-policy — role-name $SNS_PUBLISHER_ROLE — policy-name $SNS_POLICY_NAME — policy-document file://SNS-Role-Inline-Policy.json

Agora precisamos criar a role e policy que o Lambda irá utilizar.

echo “{
\”Version\”: \”2012–10–17\”,
\”Statement\”: [ {
\”Sid\”: \”\”,
\”Effect\”: \”Allow\”,
\”Principal\”: {
\”Service\”: \”lambda.amazonaws.com\”
},
\”Action\”: \”sts:AssumeRole\”
} ]
}” > LAMBDA-Role-Trust-Policy.json

echo “{
\”Version\”: \”2012–10–17\”,
\”Statement\”: [ {
\”Effect\”: \”Allow\”,
\”Resource\”: \”*\”,
\”Action\”: [
\”ec2:*\”,
\”autoscaling:*\”
]
},
{
\”Action\”: [
\”logs:CreateLogGroup\”,
\”logs:CreateLogStream\”,
\”logs:PutLogEvents\”
],
\”Resource\”: \”arn:aws:logs:*:*:*\”,
\”Effect\”: \”Allow\”
} ]
}” > LAMBDA-Role-Inline-Policy.json

LAMBDA_PUBLISHER_ROLE=”lambda_publisher_role”
LAMBDA_POLICY_NAME=”lambda_publisher_policy”
LAMBDA_ROLE_ARN=$(aws iam — region $REGIAO_AWS create-role — role-name $LAMBDA_PUBLISHER_ROLE — assume-role-policy-document file://LAMBDA-Role-Trust-Policy.json — query “Role.Arn” — output text)
aws iam — region $REGIAO_AWS put-role-policy — role-name $LAMBDA_PUBLISHER_ROLE — policy-name $LAMBDA_POLICY_NAME — policy-document file://LAMBDA-Role-Inline-Policy.json

Agora precisamos criar a função Lambda responsável pelo snapshot:

LAMBDA_FUNCION_JS=”LAMBDA_function.js”
echo “exports.handler = function(event, context) {

var AWS = require(‘aws-sdk’);
var ec2 = new AWS.EC2();
var as = new AWS.AutoScaling();

// Log the request
console.log(\”INFO: request Recieved.\nDetails\”);
var message = JSON.parse(event.Records[0].Sns.Message);

//console.log(\”DEBUG: SNS message contents. \nMessage:\n\”);
//console.log(\”DEBUG: Extracted Message Data\nData:\n\”);

// Pull out metadata
var instanceId = message.EC2InstanceId;
var createImageParams = {
InstanceId: instanceId,
Name: \”AMI LastSnapshot (\” + instanceId + \”) Time(\” + new Date().getTime() + \”)\”,
Description: \”AMI LastSnapshot — \” + instanceId ,
NoReboot: true,
DryRun: false
};
ec2.createImage(createImageParams, function(err, data) {
if (err) {
console.log(\”Failure creating image request for Instance: \” + instanceId);
console.log(err, err.stack);
}
else {
var imageId = data.ImageId;
console.log(\”Success creating image request for Instance: \” + instanceId + \”. Image: \” + imageId);
}
});
var lifecycleParams = {
\”AutoScalingGroupName\” : message.AutoScalingGroupName,
\”LifecycleHookName\” : message.LifecycleHookName,
\”LifecycleActionToken\” : message.LifecycleActionToken,
\”LifecycleActionResult\” : \”CONTINUE\”
};
console.log(\”Finalizando instancia — \” + lifecycleParams.LifecycleActionToken);
completeLifecycleAction(lifecycleParams);
function completeLifecycleAction(lifecycleParams, callback) {
as.completeLifecycleAction(lifecycleParams, function(err, data){
console.log(\”Parametros — \” + lifecycleParams );
if (err) {
console.log(\”CompleteLifecycleAction lifecycle completion failed.\nDetails:\n\”, err);
} else {
console.log(\”CompleteLifecycleAction Successful.\nReported:\n\”, data);
}
});
}
};” > $LAMBDA_FUNCION_JS

zip ${LAMBDA_FUNCION_JS}.zip $LAMBDA_FUNCION_JS
LAMBDA_FUNCION_NAME=”lambda-sns-function”

LAMBDA_ARN=$(aws — region $REGIAO_AWS lambda create-function — function-name $LAMBDA_FUNCION_NAME — zip-file fileb://${LAMBDA_FUNCION_JS}.zip — runtime nodejs6.10 — role $LAMBDA_ROLE_ARN — handler ${LAMBDA_FUNCION_NAME}-1–0.handler — timeout 30 | grep FunctionArn | awk -F’”’ ‘{print $4}’)

Criar subscription no tópico SNS para a função Lambda e dar permissão

aws sns — region $REGIAO_AWS subscribe — protocol lambda — topic-arn $SNS_ARN — notification-endpoint $LAMBDA_ARN
aws lambda — region $REGIAO_AWS add-permission — function-name $LAMBDA_FUNCION_NAME — statement-id 1 — action “lambda:InvokeFunction” — principal sns.amazonaws.com — source-arn $SNS_ARN

E para finalizar, configurar o Lifecycle Hook das instancias para entrarem em Terminating:Wait antes de ser terminada:

LC_HOOK_TERMINATING=”lab-lifecycle-hook-end”
AS_NAME=”autoscaling_group”
REGIAO_AWS=$(curl -s http://instance-data/latest/dynamic/instance-identity/document | grep region | awk -F’”’ ‘{print $4}’)
SNS_ROLE_ARN=$(echo $SNS_ROLE_ARN | tr -d ‘\r’)
aws — region $REGIAO_AWS autoscaling put-lifecycle-hook — lifecycle-hook-name $LC_HOOK_TERMINATING — auto-scaling-group-name $AS_NAME — notification-target-arn $SNS_ARN — role-arn $SNS_ROLE_ARN — lifecycle-transition autoscaling:EC2_INSTANCE_TERMINATING

Pronto, com essas configurações, todos as novas instancias entrarão em Pending:Wait e permanecerão até o termino da execução, no boot,do script que está no UserData das instancias, só após ficaram como InService.
Enquanto isso, quando uma instancia for ser terminada pelo processo do AutoScaling, a mesma entrará no status de Terminating:Wait, será realizado um ultimo Snapshot da instancia e só depois ela passará para o status de Terminated.

Resumindo, todo o ciclo de vida esta sendo contemplado nesse exemplo, lembrando que este é apenas um exemplo, podendo ser modificado a ordem,execuções ou funções descritas nesse post sem problemas.

Espero ter ajudado um pouco com essa experiência.

Até a próxima.

Originally published at medium.com on July 19, 2017.

--

--

AWS User Group São Paulo
awsugsp

Comunidade para discussões, palestras e networking de Tecnologias AWS no Brasil e no mundo.