Detener e Iniciar instancias EC2 usando el AWS Systems Manager (SSM)

Hello World 🙂
Continuando con nuestra serie sobre la optimización de costos y el manejo eficiente de instancias EC2 en la nube, hoy exploraremos cómo detener e iniciar instancias en un horario predeterminado, esta vez utilizando AWS Systems Manager (SSM).
En artículos anteriores, ya hemos cubierto este proceso utilizando Lambda + Python3, y luego con la herramienta de código abierto Cloud Custodian. Hoy es el turno del Systems Manager, que a diferencia de los métodos anteriores, es completamente gratuito. Esta es una gran ventaja sobre los otros dos enfoques, aunque el uso de Lambda es extremadamente económico, con SSM no incurres en ningún costo.
Systems Manager es uno de los servicios más potentes y utilizados en AWS, ya que nos ofrece una amplia gama de opciones para gestionar nuestras instancias.
AWS Systems Manager es el centro de operaciones para las aplicaciones y los recursos de AWS y una solución de administración integral y segura para entornos híbridos y multinube que permite efectuar operaciones seguras a escala.
Tomado de AWS
En esta entrada, trabajaremos con la funcionalidad del SSM llamada Maintenance Windows (Periodos de Mantenimiento). Esta es una funcionalidad que permite planificar y automatizar tareas de mantenimiento en los recursos de AWS, como instancias EC2. Esto es útil para realizar acciones en horarios específicos sin intervención manual, como detener o iniciar instancias, aplicar parches, ejecutar scripts o comandos, y mucho más.
Aquí te explico sus características clave:
- Programación de Mantenimiento: Te permite definir ventanas de tiempo específicas durante las cuales se ejecutarán las tareas de mantenimiento, minimizando el impacto en la disponibilidad de tus aplicaciones.
- Automatización de Tareas: Puedes programar acciones como aplicar parches, ejecutar scripts, reiniciar instancias, realizar respaldos y más. Las tareas se ejecutan de manera automatizada dentro del periodo definido.
- Asignación de Recursos: Permite asociar las tareas de mantenimiento a grupos de instancias EC2, servidores locales (conectados a través de SSM) u otros recursos, utilizando etiquetas (tags) o IDs específicos.
- Control de Concurrencia y Éxito: Te da control sobre cuántos recursos participan en el mantenimiento simultáneamente y el nivel de éxito esperado para que las tareas se consideren completadas correctamente.
- Monitoreo y Reportes: AWS Systems Manager te permite realizar un seguimiento del éxito o fracaso de las tareas ejecutadas durante la ventana de mantenimiento a través de AWS CloudWatch Logs y EventBridge.
Requisitos:
- Tener una cuenta de AWS.
- Tener instalado Terraform.
La idea detrás de este post es simular un escenario real. Como en ejercicios anteriores, crearemos una VPC, que servirá como base para implementar un par de instancias EC2. Estas instancias se detendrán y encenderán automáticamente de lunes a viernes en un horario predefinido. Además, cada vez que una instancia sea detenida o iniciada, recibiremos una notificación por correo electrónico utilizando un tópico de Amazon SNS.
Las instancias que formarán parte de esta automatización serán filtradas utilizando un tag, en este caso, App1=True, aprovechando el servicio AWS Resource Groups. Esto nos permite hacer el proceso mucho más dinámico, ya que, si en algún momento necesitamos agregar más instancias al flujo automatizado, simplemente debemos asignarles ese tag y automáticamente se integrarán a la automatización sin necesidad de realizar cambios adicionales en el código.
Todo este flujo se puede crear manualmente desde la consola de AWS, pero en este caso será gestionado de manera automatizada con Terraform, lo que nos permitirá replicar y modificar el proyecto fácilmente en diferentes entornos.
1- Primero necesitamos exportar nuestras credenciales de AWS en nuestra consola local;
export AWS_ACCESS_KEY_ID="XXXXXXXXXX"
export AWS_SECRET_ACCESS_KEY="XXXXXXXXXX"- Comprobamos que estemos conectados de forma correcta
aws sts get-caller-identity
2- En este fichero 0-provider.tf, definiremos los proveedores que utilizaremos en este proyecto. Estos proveedores son esenciales, ya que permiten a Terraform interactuar con los distintos servicios de infraestructura en la nube necesarios para desplegar los recursos correctamente.
-
provider "aws" {region = local.region# Con esta opcion hacemos que todos los recursos creados tengan los "TAG" definidos en el fichero "locals"default_tags {tags = local.tags}}
3- En el fichero 1-vpc-module.tf, definiremos todo lo necesario para crear nuestra VPC, que servirá como la base de la infraestructura del proyecto. Aquí, definiremos subredes y demás componentes claves que permitirán una red segura y eficiente para los recursos que desplegaremos más adelante.
-
module "vpc" {source = "terraform-aws-modules/vpc/aws"name = "main-module"cidr = "10.0.0.0/16"azs = ["us-west-1a", "us-west-1b"]private_subnets = ["10.0.0.0/19", "10.0.32.0/19"]public_subnets = ["10.0.64.0/19", "10.0.96.0/19"]enable_dns_hostnames = trueenable_dns_support = trueenable_nat_gateway = truesingle_nat_gateway = trueone_nat_gateway_per_az = false}
4- En el fichero 2-ec2.tf, definiremos todo lo necesario para la creación de 2 instancias EC2.
-
resource "aws_instance" "demo_ssm" {count = 2ami = data.aws_ami.amazon_linux.idinstance_type = "t2.micro"subnet_id = module.vpc.public_subnets[0]associate_public_ip_address= truevpc_security_group_ids = [aws_security_group.demo_ssm.id]tags= {Name = "Demo-SSM"}}data "aws_ami" "amazon_linux" {most_recent = truefilter {name = "name"values = ["al202*-ami-202*"]}filter {name = "architecture"values = ["x86_64"]}owners = ["amazon"]}
5- En el fichero 3-security.tf, definiremos todo lo necesario para crear el SG que va a hacer usado por las instancias.
-
resource "aws_security_group" "demo_ssm" {name = "demo-ssm"description = "demo_ssm"vpc_id = module.vpc.vpc_idingress {cidr_blocks = ["0.0.0.0/0"]from_port = 22to_port = 22protocol = "tcp"}egress {from_port = 0to_port = 0protocol = "-1"cidr_blocks = ["0.0.0.0/0"]}tags = {Name = "demo_ssm"}}
6- En el fichero 4-aim-role.tf, definiremos todo lo necesario para crear el rol que será usado en las tareas de mantenimiento, este rol es vital para el funcionamiento de la automatización.
-
resource "aws_iam_role" "Start_Stop_EC2_SSM" {name = "Start-Stop-EC2-SSM"assume_role_policy = <<EOF{"Version": "2012-10-17","Statement": [{"Action": "sts:AssumeRole","Principal": {"Service": "ssm.amazonaws.com"},"Effect": "Allow","Sid": ""}]}EOF}resource "aws_iam_policy_attachment" "attach_AmazonSSMAutomationRole" {name = "attach-AmazonSSMAutomationRole"roles = [aws_iam_role.Start_Stop_EC2_SSM.name]policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole"}resource "aws_iam_policy_attachment" "attach_AmazonSSMMaintenanceWindowRole" {name = "attach-AmazonSSMMaintenanceWindowRole"roles = [aws_iam_role.Start_Stop_EC2_SSM.name]policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonSSMMaintenanceWindowRole"}resource "aws_iam_policy_attachment" "attach_AmazonSNSFullAccess" {name = "attach-AmazonSNSFullAccess"roles = [aws_iam_role.Start_Stop_EC2_SSM.name]policy_arn = "arn:aws:iam::aws:policy/AmazonSNSFullAccess"}
7- En el fichero 5-maintenance_window.tf, definiremos todo lo necesario para crear dos ventanas de mantenimiento, una para detener y otra para iniciar las instancias, cada una tendrá el horario en el que se ejecutará la ventana usando una expresión cron, también vamos a definir el tiempo que va a durar cada ventana de mantenimiento, en el caso de cutoff define el tiempo en horas antes del final de la ventana de mantenimiento en el que no se pueden agregar nuevas tareas. En este caso, se establece en 1 hora, lo que significa que no se pueden poner en marcha nuevas tareas una hora antes de que termine la ventana.
-
resource "aws_ssm_maintenance_window" "maintenance_window_stop_App1" {name = "maintenance_window_stop_App1"description = "Maintenance window to stop the group of EC2 instances App1"schedule = "cron(0 40 17 ? * * *)" # UTC Time, Ajustar el tiempoduration = 2cutoff = 1schedule_timezone = "UTC"enabled = trueallow_unassociated_targets = true}resource "aws_ssm_maintenance_window" "maintenance_window_start_App1" {name = "maintenance_window_start_App1"description = "Maintenance window to start the group of EC2 instances App1"schedule = "cron(0 50 17 ? * * *)" # UTC Time, Ajustar el tiempoduration = 2cutoff = 1schedule_timezone = "UTC"enabled = trueallow_unassociated_targets = true}
8- En el fichero 6-resourcegroups_group.tf, definiremos todo lo necesario para crear el grupo de instancias con las que vamos a trabajar en la automatización, en este caso vamos a buscar las instancias con el tag App1=True.
-
resource "aws_resourcegroups_group" "resourcegroups_App1" {name = "resourcegroups-App1"description = "Group of EC2 instances App1"resource_query {query = <<JSON{"ResourceTypeFilters": ["AWS::EC2::Instance"],"TagFilters": [{"Key": "App1","Values": ["True"]}]}JSON}}
9- En el fichero 7-maintenance_window_target.tf, definiremos todo lo necesario para crear el target con el que va a trabajar cada ventana de mantenimiento,
-
resource "aws_ssm_maintenance_window_target" "maintenance_window_target_stop_App1" {window_id = aws_ssm_maintenance_window.maintenance_window_stop_App1.idname = "maintenance_window_target_stop_App1"description = "This is a maintenance window target to STOP instances group APP1"resource_type = "RESOURCE_GROUP"targets {key = "resource-groups:Name"values = [aws_resourcegroups_group.resourcegroups_App1.name]}targets {key = "resource-groups:ResourceTypeFilters"values = ["AWS::EC2::Instance"]}}resource "aws_ssm_maintenance_window_target" "maintenance_window_target_start_App1" {window_id = aws_ssm_maintenance_window.maintenance_window_start_App1.idname = "maintenance_window_target_start_App1"description = "This is a maintenance window target to START instances group APP1"resource_type = "RESOURCE_GROUP"targets {key = "resource-groups:Name"values = [aws_resourcegroups_group.resourcegroups_App1.name]}targets {key = "resource-groups:ResourceTypeFilters"values = ["AWS::EC2::Instance"]}}
10- En el fichero 8-maintenance_window_task_start_sns.tf, definiremos todo lo necesario para crear las tareas a realizar en la ventana de mantenimiento, en nuestro caso cada ventana tendrá un par de tareas, en este caso iniciar y enviar el mail usando SNS.
-
resource "aws_ssm_maintenance_window_task" "maintenance_window_start_App1" {name = "maintenance_window_start_App1"description = "Task to start the group of EC2 instances App1"max_concurrency = 2max_errors = 1priority = 1task_arn = "AWS-StartEC2Instance"task_type = "AUTOMATION"window_id = aws_ssm_maintenance_window.maintenance_window_start_App1.idservice_role_arn = aws_iam_role.Start_Stop_EC2_SSM.arntargets {key = "WindowTargetIds"values = [aws_ssm_maintenance_window_target.maintenance_window_target_start_App1.id]}task_invocation_parameters {automation_parameters {document_version = "$LATEST"parameter {name = "InstanceId"values = ["{{RESOURCE_ID}}"]}}}}resource "aws_ssm_maintenance_window_task" "maintenance_window_task_start_sns" {name = "maintenance_window_task_start_sns"description = "Send a SNS msg start EC2"priority = 20task_arn = "AWS-PublishSNSNotification"task_type = "AUTOMATION"window_id = aws_ssm_maintenance_window.maintenance_window_start_App1.idservice_role_arn = aws_iam_role.Start_Stop_EC2_SSM.arntask_invocation_parameters {automation_parameters {document_version = "$LATEST"parameter {name = "TopicArn"values = [aws_sns_topic.topic_sns_ssm.arn]}parameter {name = "Message"values = ["The instances were started, msg from the ssm topic created with terraform"]}}}}
11- En el fichero 9-maintenance_window_task_stop_sns.tf, definiremos todo lo necesario para crear las tareas a realizar en la ventana de mantenimiento, en nuestro caso cada ventana tendrá un par de tareas, en este caso detener y enviar el mail usando SNS.
-
resource "aws_ssm_maintenance_window_task" "maintenance_window_stop_App1" {name = "maintenance_window_stop_App1"description = "Task to stop the group of EC2 instances App1"max_concurrency = 2max_errors = 1priority = 1task_arn = "AWS-StopEC2Instance"task_type = "AUTOMATION"window_id = aws_ssm_maintenance_window.maintenance_window_stop_App1.idservice_role_arn = aws_iam_role.Start_Stop_EC2_SSM.arntargets {key = "WindowTargetIds"values = [aws_ssm_maintenance_window_target.maintenance_window_target_stop_App1.id]}task_invocation_parameters {automation_parameters {document_version = "$LATEST"parameter {name = "InstanceId"values = ["{{RESOURCE_ID}}"]}}}}resource "aws_ssm_maintenance_window_task" "maintenance_window_task_stop_sns" {name = "maintenance_window_task_stop_sns"description = "Send a SNS msg stop EC2"priority = 20task_arn = "AWS-PublishSNSNotification"task_type = "AUTOMATION"window_id = aws_ssm_maintenance_window.maintenance_window_stop_App1.idservice_role_arn = aws_iam_role.Start_Stop_EC2_SSM.arntask_invocation_parameters {automation_parameters {document_version = "$LATEST"parameter {name = "TopicArn"values = [aws_sns_topic.topic_sns_ssm.arn]}parameter {name = "Message"values = ["The instances were stopped, msg from the ssm topic created with terraform"]}}}}
11- En el fichero 10-sns.tf, definiremos todo lo necesario para crear el topic SNS y suscribir nuestro mail.
-
resource "aws_sns_topic" "topic_sns_ssm" {name = "topic-sns-ssm"display_name = "Test From SSM"}resource "aws_sns_topic_subscription" "user_updates_mail_target" {topic_arn = aws_sns_topic.topic_sns_ssm.arnprotocol = "email"endpoint = "tu-email@gmail.com"}
12- En el fichero 11-locals.tf, definiremos las variables locales del proyecto. Estas variables ayudan a centralizar valores que se utilizarán en múltiples recursos, simplificando la configuración y facilitando el mantenimiento del código.
-
l
ocals {region = "us-west-1"tags = {Environment = "POC",Terraform ="True"App1 = "True"}}
13- En el fichero 12-output.tf, definiremos las salidas que Terraform nos mostrará tras ejecutar terraform apply. Estas salidas incluirán detalles de los recursos creados, que nos ayudarán a verificar rápidamente el estado del despliegue y a acceder fácilmente a los recursos configurados en el proyecto.
-
output "ssm_role_arn" {value = aws_iam_role.Start_Stop_EC2_SSM.arn}output "ec2_private_ip" {value = [for instance in aws_instance.demo_ssm : instance.private_ip]}
Una vez creado todos los ficheros ya estamos en condiciones de ejecutar Terraform;
terraform initterraform planterraform apply
Vamos a comprobar que todos recursos fueron generados correctamente.
- Instancias EC2 generadas.
- Si revisamos el correo debemos tener un mensaje del topic SNS generado, le damos clic para confirmar la subscripción.
- Ventanas de mantenimiento generadas.
- Aquí podemos ver el grupo de instancias
- Así llegan los correos de las ventanas de mantenimiento a nuestra bandeja de entrada.
Terraform destroy
- Como siempre, al finalizar tus pruebas, recuerda eliminar todos los recursos creados ejecutando
terraform destroy --auto-approve. Este paso es crucial para evitar sorpresas desagradables en tu factura a final del mes. No subestimes la importancia de esta tarea, ya que la acumulación de recursos no utilizados puede generar costos innecesarios. Tómate el tiempo para limpiar tu entorno, eso te ahorrará dolores de cabeza en el futuro 🙂
Conclusiones
Hemos llegado al final de esta entrada, donde exploramos una nueva forma de detener e iniciar instancias EC2 en un horario específico, utilizando un método que, como mencioné al inicio, tiene una gran ventaja: es completamente gratuito. En un entorno cloud, cada centavo cuenta, y tener soluciones que no generen costos adicionales es un punto clave en cualquier estrategia de optimización.
La optimización de costos en la nube no es una tarea de una sola vez; debe ser un proceso constante y continuo dentro de cualquier organización que utilice servicios cloud. Si no prestamos atención a estos detalles, podemos terminar pagando de más mes a mes.
Este proceso puede ser particularmente desafiante en organizaciones grandes, donde hay múltiples equipos y proyectos involucrados. Lograr que todos comprendan la importancia de este enfoque requiere un esfuerzo adicional, pero los resultados pueden ser muy significativos. Cada pequeña acción que tomemos para reducir costos contribuye a la eficiencia general.
Espero que esta serie de artículos sobre la optimización de costos en la nube te haya resultado útil y que puedas aplicar lo aprendido en tus entornos laborales. La clave está en la proactividad y en mantener siempre la mirada puesta en cómo mejorar la utilización de los recursos, sin sacrificar el rendimiento o la calidad de los servicios.
¡Nos vemos en la próxima entrada!
Nota: Todo el código lo pueden encontrar en el siguiente repositorio.

















