Todos ouvimos que recursos em nuvem são ilimitados, que podemos usar o quanto quisermos, que o céu é o limite, que na nuvem nada se acaba e outras coisas. Mas tudo isto tem limite sim, o “bolso” da empresa.
Eu vi e vejo muito algumas empresas contratando o EKS (Kubernetes da AWS) e na sequência subindo instâncias EC2 com máquinas pesadas, e depois RDS’s como se não houvesse amanhã. A nuvem nos proporciona isso, tudo é muito fácil e atrativo. O problema é a conta no fim do mês.
Uma das formas de economizarmos e reduzir os custos da infraestrutura é simplesmente desligá-la quando não precisamos usar.
Ambientes como desenvolvimento e homologação (ou qualquer outra que não seja o de produção) pode ser desligado em determinados períodos e com isto reduzimos o billing no final do mês.
Neste artigo vou mostrar como desligar as instâncias do RDS fora do horário de expediente e nos finais de semana. Com isto já conseguimos reduzir bastante no custo com os recursos de infraestrutura na nuvem.
Iniciando o projeto
Não vamos abordar os detalhes de como se cria um um projeto e como se configura os arquivos do Terraform. Para início, vamos criar o arquivo main.tf e vamos declarar o provider da AWS que vamos utilizar.
provider "aws" {
region = "us-east-1"
}
Vamos configurar o arquivo vars.tf com as seguintes variáveis que vamos utilizar no nosso projeto:
variable "policy_name" {
default = "ScheduleRDSPolicy"
}
variable "policy_description" {
default = "Policy que permite o Lambda a desligar e ligar as instâncias do RDS"
}
variable "role_name" {
default = "ScheduleRDSRole"
}
variable "role_description" {
default = "Role que permite o Lambda a desligar e ligar as instâncias do RDS"
}
variable "lambda_function_name" {
default = "ScheduleRDSLambda"
}
variable "lambda_handler" {
default = "lambda_function.lambda_handler"
}
variable "lambda_timeout" {
default = "10"
}
variable "lambda_dbinstances" {
default = "eventstore-db,kong-db"
}
variable "cloudwatch_name" {
default = "ScheduleRDSCloudwatch"
}
variable "cloudwatch_description" {
default = "Liga e desliga os recursos do RDS"
}
A organização do projeto deve ficar da seguinte forma:

Criando a Policy
No arquivo main.tf, vamos criar a Policy para dar permissão de Start e Stop no RDS para o Lambda, além de dar as permissões de salvar os logs no CloudWatch.
resource "aws_iam_policy" "schedule_rds_policy" {
name = "${var.policy_name}"
description = "${var.policy_description}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"rds:Stop*",
"rds:Start*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
EOF
}
Criando a Role
Em seguida criaremos a Role para dar permissão ao Lambda de executar os comandos no RDS.
resource "aws_iam_role" "schedule_rds_role" {
name = "${var.role_name}"
description = "${var.role_description}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement":
[
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
Na sequência vamos anexar a Role na Policy.
resource "aws_iam_role_policy_attachment" "schedule_iam_attachment" {
role = "${aws_iam_role.schedule_rds_role.name}"
policy_arn = "${aws_iam_policy.schedule_rds_policy.arn}"
}
Criando a função Python
Vamos configurar o Lambda para executar uma função em Python. Esta função recebe como parâmetro os nomes dos bancos de dados RDS.
A função está configurada para manter as instâncias de RDS ligadas durante o horaŕio de expediente, ou seja, das 8h às 19h. Fora do expediente e aos finais de semana ele fica desligado.
Caso seja necessário outros períodos é só ajustarmos as variáveis da função.
import boto3
import datetime
import calendar
import os
from datetime import date, datetime, timedelta
rds = boto3.client('rds')
def lambda_handler(event, context):
dateNow = date.today()
dayOfWeek = calendar.day_name[dateNow.weekday()]
dbinstances = os.environ['dbinstances'].split(',')
time = datetime.now()
now = (time - timedelta(hours = 3))
hour = now.hour
print('Iniciando o lambda de schedule RDS em ' + str(dayOfWeek) + ' às ' + str(hour))
if len(dbinstances) == 0:
print('Nenhuma instância RDS foi cadastrada.')
return
if dayOfWeek == 'Saturday' or dayOfWeek == 'Sunday':
print('Final de semana.')
return
if hour > 7 and hour < 19:
print('Iniciando as instâncias de RDS')
for instance in dbinstances:
rds.start_db_instance(DBInstanceIdentifier = instance)
else:
print('Parando as instâncias de RDS')
for instance in dbinstances:
rds.stop_db_instance(DBInstanceIdentifier = instance)
print('Finalizando o lambda de schedule RDS.')
Criando a função Lambda
Agora vamos criar o resource Lambda que executará a função lambda_handler do arquivo lambda_function.py.
resource "aws_lambda_function" "schedule_rds_lambda" {
filename = "${data.archive_file.file_lambda.output_path}"
function_name = "${var.lambda_function_name}"
role = "${aws_iam_role.schedule_rds_role.arn}"
handler = "${var.lambda_handler}"
timeout = "${var.lambda_timeout}"
source_code_hash = "${data.archive_file.file_lambda.output_base64sha256}"
runtime = "python3.8"
environment {
variables = {
dbinstances = "${var.lambda_dbinstances}"
}
}
}
data "archive_file" "file_lambda" {
type = "zip"
source_file = "lambda_function.py"
output_path = "lambda_function.zip"
}
No resource schedule_rds_lambda temos a variável dbinstances. Nesta variável colocaremos os nomes das instâncias RDS que queremos fazer o agendamento de Start/Stop.
Junto com a criação do Lambda criarmos também uma função data. Esta função é responsável por pegar o arquivo Python, fazer o zip e prepará-lo para o upload no Lambda.
Criando o CloudWatch
Por último criamos o resource para o CloudWatch. Neste resource configuramos o CloudWatch para ser executado a cada 60 minutos e em seguida anexamos o CloudWatch ao Lambda que criamos anteriormente.
resource "aws_cloudwatch_event_rule" "schedule_cloudwatch" {
name = "${var.cloudwatch_name}"
description = "${var.cloudwatch_description}"
schedule_expression = "rate(60 minutes)"
}
resource "aws_cloudwatch_event_target" "schedule_cloudwatch_target" {
rule = "${aws_cloudwatch_event_rule.schedule_cloudwatch.name}"
target_id = "lambdaRDS"
arn = "${aws_lambda_function.schedule_rds_lambda.arn}"
}
Executando o projeto
Com tudo configurado agora podemos executar os comandos para o Terraform escrever nossa infra as a code na AWS.
terraform init

terraform plan

terraform apply

Resultado
Agora vamos conferir para ver se realmente o Terraform criou tudo o que precisamos para agendar o Start/Stop das nossas instâncias RDS.
Policy

Role

Lambda

CloudWatch

Conclusão
Eu fiz isto aqui na empresa onde trabalho e o resultado foi bastante considerável. Desligar os recursos quando não estão sendo utilizados trouxe uma boa economia e este processo pode ser aplicado em todos os ambientes, exceto produção, claro.
Este mesmo projeto também pode ser usado para desligar instâncias do EC2, por exemplo. Basta alterar as funções do arquivo Python para desligar o EC2 ou qualquer outro recurso que queira.
Os fontes deste artigo podem ser encontrados no meu GitHub.