Providers, Variables y Modulos en Terraform

En Terraform, los providers (proveedores) son plugins que permiten a Terraform interactuar con diferentes plataformas y servicios, como AWS, Azure, Google Cloud, Kubernetes, GitHub, y muchos más. (ChatGPT).
Un provider en Terraform es como el controlador que le dice a Terraform cómo comunicarse y gestionar recursos en una plataforma específica.

El otro día, cuando comenzamos a escribir nuestro código de Terraform lo primero que hicimos fue crear el archivo main.tf, y dentro de este escribimos fue el bloque provider.

provider “aws” {
region = “us-east-1”
}

Escribimos la palabra reservada “provider” seguido de un proveedor de nube “aws”, y entre las llaves escribimos un parámetro requerido “region = “us-east-1”.
Tambien le indicamos que debe crear un recurso en la nube de AWS también con los parámetros requeridos por Terraform.

resource “aws_instance” “mi-instancia-ec2” {
ami = “ami-0c55b159cbfafe1f0”
instance_type = “t2.micro”
}

Ahora. Que pasa si no le indicamos a Terraform cual es el provider. Si omitimos ese bloque de código. Simplemente dejamos el bloque de codigo de la creación del recurso?
Y seguidamente ejecutamos un terraform init. Luego ejecutamos un terraform apply.
La pregunta obligada es. ¿Como sabe Terraform donde tiene que crear ese recurso y como autenticarse para ese proveedor de nube?, (en este caso AWS).
Esa información, Terraform la obtiene de ese bloque de código:
provider “aws” {
region = “us-east-1”
}
Provider, no es mas que es un plugin que le dice a Terraform donde tiene que crear la infraestructura. Sin el bloque de código provider, esto no seria posible.

Por otra parte, si en vez de que el provedor sea AWS, ¿que sucede si queremos desplegar infraestructura en Azure o GCP etc?
Terraform tiene un listado de casi todos los proveedores de nube calsificados en tres tipos, Official Providers, Partner Providers y Comunity Providers.
Allí podemos encontrar la documentación especifica para cada proveedor de nube.

https://registry.terraform.io/browse/providers

Hasta ahora hemos creado recursos para una region en AWS. Una pregunta importante que surge cuando queremos usar Terraform como IaC es, ¿Que pasa queremos desplegar infraestructura en varias regiones?
La respuesta es sencilla. Lo que se hace es mencionarlo en otro bloque de código, especificando la o las regiones en las que queremos desplegar infraestructura.

provider “aws” {
alias = “us-east-1”
region = “us-east-1”
}
provider “aws” {
alias = “us-west-2”
region = “us-west-2”
}

Observa que hemos especificado un alias para cada región.
Entonces, cuando vamos a crear nuestros recursos, simplemente especificamos en que región queremos que se despliegue ese recurso en especifico.
resource “aws_instance” “mi-instancia-ec2” {
ami = “ami-0c55b159cbfafe1f0”
instance_type = “t2.micro”
provider = “aws.us-east-1”
}

resource “aws_instance” “mi-instancia-ec2” {
ami = “ami-0c55b159cbfafe1f0”
instance_type = “t2.micro”
provider = “aws.us-west-2”
}

Al ver esto, Terraform comprende que debe crear 2 recursos (en este caso, instancias EC2) en 2 regiones diferentes identificadas particularmente con un alias

Hay organizaciones que manejan su infraestructura en varios proveedores de nube. Otra pregunta es, ¿Que pasa si queremos desplegar infraestructura en varias nubes? Por ejemplo AWS y Azure.
La respuesta es muy similar a la anterior.
En Terraform se pude configurar providers con diferentes nombres.
Por ejemplo AWS tiene su nombre de proveedor como “aws” y Azure tiene el suyo como “azurerm”
Cada uno con sus parámetros específicos.

provider “aws” {
region = “us-east-1”
}

provider “azurerm” {
subscription_id = “your-azure-subscription-id”
client_id = “your-azure-client-id”
client_secret = “your-azure-client-secret”
tenant_id = “your-azure-tenant-id”
}

Nota: Se debe tener en cuenta las credencias de acceso a cada proveedor de nube.

Los recursos también se especifican para cada provider:
resource “aws_instance” “mi-instancia-ec2” {
ami = “ami-0c55b159cbfafe1f0”
instance_type = “t2.micro”
provider = “aws.us-east-1”
}
resource “azurerm_virtual_machine” “example” {
name = “example-vm”
location = “eastus”
size = “Standard_A1”
}

Otro concepto de mucha importancia, son:
Las Variables en Terraform:
Las variables se usan para parametrizar, para pasar valores al proyecto.
En Terraform es buena practica usar variables.
Digamos que tenemos como en el ejemplo anterior codificado la ami y el tipo de instancia para la creación de un recurso en AWS
resource “aws_instance” “mi-instancia-ec2” {
ami = “ami-0c55b159cbfafe1f0”
instance_type = “t2.micro”
}

Si en algún momento, otro equipo requiere un proyecto para crear una instancia EC2, no tenemos necesidad de escribir el código una y otra vez. O copiar y pegar, reemplazando los valores de ami e instance_type. Tal vez requiera otro tipo de instancia u otra ami.
Para resolver esto en lugar de proporcionar un valor rígido, podemos reemplazarlo con una variable.
En Terraform hay 2 tipos de variables.
Variables input y las variables output. o entrada y salida
Si queremos pasar información a Terraform, esas son variables de entrada.
Si queremos que Terraform imprima un valor especifico (output). Por ejemplo la IP pública de una instancia EC2 luego de haberla creado. Esta se mostrará en la consola.
¿Como definimos una variable en Terraform?
Con la palabra reservada var seguido de un punto y el nombre de la variable:

resource “aws_instance” “mi-instancia-ec2” {
ami = var.ami_id
instance_type = var.instance_type
}

Luego, ¿donde Terraform buscará el valor de la variable?
Eso lo hace desde el archivo variables.tf
No necesriamente estos valores de las variables deben estar en un archivo separado. Pueden estar definidos dentro del mismo archivo main.tf.

archivo variables.tf

variable “instance_type” {
description = “EC2 instance type”
type = string
default = “t2.micro”
}

variable “ami_id” {
description = “EC2 AMI ID”
type = string
}

Declaramos la variable con el mismo nombre
main.tf -> instance_type = var.instance_type
variables.tf -> variable “instance_type

Un aspecto importante es definir el tipo de variable. Entraremos en mas detalles mas adelante.
Tambien podemos definir un valor por defecto.
En este caso le hemos dicho que el valor por defecto de la variable instance_type es “t2.micro”. Entonces Terraform tomará el parametro instance_type = var.instance_type por “t2.micro”

Existen otra opcion para pasar el valor de la variable.
Al ejecutar -> terraform apply -var=”region=us-west-2″ por ejemplo.

Con esto podriamos reutilizar el codigo en varios proyectos de Terraform.

Ahora, para las variables de salida la sintaxis es mas sencilla:
output “public_ip” {
description = “Public IP address of the EC2 instance”
value = aws_instance.mi-instancia-ec2.public_ip
}
Comenzará con la palabra reservada output. Esto le indica a Terraform que debe devolver un valor una vez que el comando terraform apply se haya ejecutado exitosamente.
En el ejemplo queremos saber la dirección IP pública que asigna AWS a las Instancias EC2.
En los parámetros le podemos asignar una descripcion -> description = “Public IP address of the EC2 instance”, y el valor que queremos que nos traiga desde AWS. -> value = aws_instance.mi-instancia-ec2.public_ip

Ahora bien, ¿Qué pasa si el equipo de devs hace una nueva solicitud y pide cambiar el tipo de instancia, por ejemplo t2.large?
¿Cómo cambiaríamos ese parámetro especifico dinámicamente?
En Terraform existe un concepto llamado tfvars.
Los archivos con extension .tfvars son usados para especificar un conjunto de valores para las variables de entrada definidas en la configuracion de Terraform.
Podemos crear el archivo terraform.tfvars y dentro asignamos los valores a variables automáticamente.
Por ejemplo:
En el archivo variables.tf ->

variable “region” {
description = “Región de AWS”
type = string
}

variable “instance_type” {
description = “Tipo de instancia EC2”
type = string
}
En el archivo terraform.tfvars

region = “us-east-1”
instance_type = “t2.micro”

Cuando ejecutamos terraform apply, Terraform lee automáticamente el archivo terraform.tfvars y usa esos valores.
Para que sirve esto?
Si tenemos varios entornos o configuraciones diferentes como Dev o Prod, podríamos tener los archivos dev.tfvars y prod.tfvars con parámetros distintos, y lo que haríamos sería llamarlos o ejecutarlos cada uno por separado -> terraform apply -var-file=”prod.tfvars”. Debemos especificar el nombre de el archivo .tfvars, de lo contrario tomará los valores que tenga el terraform.tfvars.

Condicionales en Terraform

Las condicionales en Terraform te permiten tomar decisiones en tiempo de ejecución para determinar qué valor usar dependiendo de una condición. Esto es útil, por ejemplo, para elegir valores distintos según el entorno, o para habilitar/deshabilitar recursos.
Su sintaxis es muy parecida a muchos lenguajes de programación.
condición ? valor_si_verdadero : valor_si_falso

Un ejemplo sencillo seria al crear un Security Group de AWS.

variable “environment” {
description = “Environment type”
type = string
default = “development”
}

variable “production_subnet_cidr” {
description = “CIDR block for production subnet”
type = string
default = “10.0.1.0/24”
}

variable “development_subnet_cidr” {
description = “CIDR block for development subnet”
type = string
default = “10.0.2.0/24”
}
resource “aws_security_group” “example” {
name = “example-sg”
description = “Example security group”

ingress {
from_port = 22
to_port = 22
protocol = “tcp”
cidr_blocks = var.environment == “production” ? [var.production_subnet_cidr] : [var.development_subnet_cidr]
}
}

Proporcionamos el nombre, y la descripción. En los parámetros de ingress le decimos desde el puerto 22 hasta el puerto 22 para permitir ssh pero solo para un bloque cidr especifico. Es decir, si el entorno es producción, entonces asigna el bloque cidr especifico para producción. Si el entorno es development entonces asigna el bloque cidr especifico para development, los cuales hemos definido previamente.

Módulos en Terraform

¿Qué es y para que sirve un módulo en Terraform?
Son bloques reutilizables de configuración que te permiten organizar, reutilizar y mantener tu infraestructura como código de forma más limpia y escalable. Sirve como una plantilla. Defines una vez y puedes usarla muchas veces, incluso en diferentes proyectos.

Ventajas:
Modularidad: Permiten dividir la infraestructura en partes pequeñas y manejables.
Reusabilidad: Se pueden reutilizar como plantillas en distintos proyectos, evitando duplicación. En lugar de tener todo el código de nuestra infraestructura en un solo archivo, podemos separarlo y modificarlo particularmente.
Colaboración: Facilitan que distintos equipos trabajen por separado y luego integren sus partes.
Versionamiento: Se pueden versionar para actualizar con control y sin romper despliegues existentes.
Abstracción: Ocultan la complejidad de los recursos, permitiendo usar parámetros simples.
Pruebas: Pueden probarse por separado antes de usarlos en producción.
Documentación: Se documentan solos gracias a variables y salidas bien definidas.
Escalabilidad: Ayudan a mantener el código organizado a medida que crece la infraestructura.
Seguridad: Permiten aplicar configuraciones seguras y consistentes en cada despliegue.

Terraform State

Cuando estamos en un proyecto de terraform e iniciamos con terraform init, terraform plan, terraform apply, en el directorio del proyecto se crea un archivo terraform.tfstate. Este es un archivo clave que guarda el estado actual de tu infraestructura gestionada por Terraform. Esta en formato JSON y dentro de el guarda los recursos reales creados, como instancias EC2, Redes, buckets S3, etc. También guarda sus atributos actuales como IPs, IDs, nombres, etc. Además, guarda la relación entre lo que tenemos en el código, en los archivos .tf y lo que realmente esta en la nube.
Para que nos sirve? Terraform compara el estado actual (real) con el deseado (lo que escribiste en los .tf). Nos sirve para saber qué cambiar sin afectar lo que ya está funcionando, y para aplicar comandos como terraform plan, terraform apply o terraform destroy con precisión.

Este archivo es mejor no tocarlo si no sabemos lo que estamos haciendo. Además puede contener datos sensibles y por eso debe estar bien protegido. Es recomendable almacenarlo remotamente como en un bucket S3 por ejemplo.

Un ejemplo practico seria. Tenemos desplegada una infraestructura en AWS con una instancia EC2, una VPC, una subred etc, algo simple. Digamos que por instrucciones se le debe agregar una etiqueta a la instancia EC2. Podemos ir a nuestro proyecto, vamos al código y agregamos la línea para agregar la etiqueta a la instancia EC2.
Hacemos un terraform plan y luego un terraform apply.
Y el cambio se realiza con exito.
Ahora bien, ¿Como sabe terraform que debe sólo actualizar esa instancia y agregarle solo esa etiqueta?, O, por que no elimina la instancia y crea una nueva con los cambios?, O, por que no crea una instancia adicional en la infraestructura con un id distinto?
Esto se debe al terraform.tfstate.
Que está creado y que tiene que crear.
Simplemente compara.
Si hay alguna diferencia hará los cambios y el archivo terraform. tfstate se actualiza.
Por esta razón el terraform.tfstate es el corazon de Terraform.
Si no existe, Terraform nunca sabra que tiene que actualizar una infraestructura en lugar de crearla.
Esto también aplica a la hora de eliminar la infraestructura.
Como lo mencioné, el archivo terraform.tfstate guarda información sensible. Contraseñas, cadenas de conexión a bases de datos, credenciales, Token de APIs, etc. Lo que significa que el que pueda tener acceso a ese archivo en particular puede ver toda esa información.
Un ingeniero DEVOPS puede estar creando infraestructura para los desarrolladores de un proyecto X, y le pasas el proyecto de Terrafrom al equipo de desarrolladores para que lo ejecuten, bien sea, para correr una instancia EC2 o un Bucket S3 etc. Ese equipo de desarrolladores tendrá acceso al .tfstate y podrán leer todo. Y con esto le damos acceso a información que no deben manejar.
Algo similar sucede cuando pasamos el proyecto Terrafrom a un sistema de control de versiones como Github. Una vez hecho esto, significa que el sistema de control de versiones queda comprometido.
También puede ocurrir que alguien descargue el proyecto, haga cambios en local y luego olvide subir el tfstate. Si esto pasa, podría arruinar toda la configuración.
Estos inconvenientes se pueden solucionar con un concepto de Terraform llamado backend remoto. Nos permite almacenar el archivo terraform.tfstate en un lugar remoto y compartido, en lugar de guardarlo localmente en local.
Podemos guardar el archivo de estado en servicios más seguros (como S3, GCS, Azure Blob, etc.).
No dependes del archivo local, lo que facilita CI/CD, backups y recuperación.
Usando un backend remoto, se puede clonar el repositorio, trabajar localmente para hacer cambios, verificar los cambios con terraform plan y luego solicitar un PR (pull request) al repositorio de control de versiones. Una vez que alguien apruebe el PR, el proyecto de Terraform estará actualizado con los cambios, y también se actualizará el archivo terraform.tfstate en el bucket de S3.
Para hacer uso de backend remoto, podemos crear un archivo en la raiz de nuestro proyecto llamado backend.tf y dentro le agreagamos lo siguiente:

terraform {
  backend "s3" {
    bucket         = "mi-bucket-terraform"
    key            = "estado/infraestructura.tfstate"
    region         = "us-east-1"
    
  }
}

El bucket S3 deberá existir o se puede crear desde terraform previamente.
Solo puedes definir un backend por proyecto y una vez configurado, debes correr terraform init para inicializarlo y migrar el estado.
Cada vez que se hace un terraform apply, Terraform bloqueará el tfstate, y lo hace a través de DynamoDB para poder implementar el mecanismo de bloqueo. Esto evita que dos ejecuciones de terraform apply entren en conflicto entre Terraform y AWS.
DynamoDB previene esto bloqueando el archivo de estado mientras alguien lo está usando.
Crea una tabla especial donde guarda un registro del lock (bloqueo), y cuando alguien corre terraform apply, entonces Terraform revisa si el archivo de estado está bloqueado, si no lo está, crea un bloqueo (una entrada en la tabla DynamoDB). Luego, aplica los cambios y libera el bloqueo al finalizar.
Si alguien más intenta aplicar cambios al mismo tiempo, verá un mensaje como:
“Error acquiring the state lock”.

Podemos crear el recurso DynamoDB:

resource "aws_dynamodb_table" "terraform-state-lock" {
  name         = "terraform-state-lock"
  billing_mode = "PAY_PER_REQUEST"
  attribute {
    name = "LockID"
    type = "S"
  }
  hash_key = "LockID"
  
}

Debeos tomar en cuenta que DynamoDB tiene un costo por solicitud.

Y en el archivo backend.tf agregamos la siguiente línea:
dynamodb_table = "terraform-state-lock"

El backend no gestiona recursos, solo el archivo de estado (terraform.tfstate).

Agregar un comentario

Tu dirección de correo electrónico no será publicada. Los campos requeridos están marcados *