Cómo desplegar Terraform de Azure Container Instances (ACI)
Vamos a explorar cómo desplegar Azure Container Instances (ACI) utilizando Terraform. Como ya hemos comentado en varias ocasiones en este blog, ACI es un servicio fundamental de Azure para ejecutar contenedores Docker sin necesidad de gestionar máquinas virtuales subyacentes. Ofrece una solución rápida, sencilla y escalable para aplicaciones basadas en contenedores.
En esta guía, detallaré los pasos necesarios para configurar y desplegar ACI de manera eficiente y eficaz con Terraform, maximizando los beneficios de la automatización y la reproducibilidad que ofrece la infraestructura como código.
Esta guía cubre todos los aspectos básicos y esenciales, desde la creación de un grupo de recursos y una red virtual, hasta el despliegue de las instancias de contenedores. En una futura publicación, profundizaremos en la incorporación de funcionalidades adicionales y configuraciones más avanzadas. No obstante, para un despliegue sencillo y funcional, esta guía ofrece todo lo necesario.
Requisitos Previos
Necesitas tener instalado Terraform CLI en tu máquina local. Si es la primera vez que utilizas Terraform para desplegar recursos en Microsoft Azure, te recomiendo consultar este enlace.
Un editor de texto o un IDE de tu preferencia (recomiendo Visual Studio Code con la extensión de Terraform).
Declarar el Proveedor de Azure en Terraform
El archivo provider.tf
en Terraform se utiliza para especificar y configurar los proveedores utilizados en tu configuración. Un proveedor es un servicio o plataforma donde se gestionarán los recursos. Esto puede ser un proveedor en la nube como Microsoft Azure, AWS, Google Cloud, etc.
Este archivo es importante porque indica a Terraform qué API de proveedor utilizar para crear, actualizar y eliminar recursos. Sin él, Terraform no sabría dónde gestionar tus recursos.
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
Importante: Terraform ahora requiere un ID de suscripción explícito en la configuración del proveedor para una gestión de infraestructura más clara, predecible y segura en entornos de nube complejos.
Despliegue de recursos de Azure usando Terraform
En el caso del despliegue de Azure Container Instances (ACI), el archivo main.tf
contiene los siguientes componentes clave:
azurerm_resource_group: Este bloque crea el grupo de recursos de Azure donde se desplegarán el resto de los recursos.
azurerm_virtual_network: Define la red virtual (VNet) de Azure, que proporciona un entorno de red aislado para tus recursos.
azurerm_subnet: Define las subredes dentro de la red virtual, permitiendo segmentar la red. Utiliza
local.flattened_subnets
para iterar sobre la configuración de las subredes.azurerm_container_group: Configura las instancias de contenedor de Azure, especificando parámetros como la imagen, CPU, memoria y configuración de red. Soporta múltiples contenedores dentro de un mismo grupo mediante
for_each
basado envar.aci_instances
.image_registry_credential: Es necesario cuando se extraen imágenes de contenedor desde el repositorio de Docker Hub. Ayuda a evitar los límites de extracción de imágenes públicas, ya que la autenticación incrementa esos límites
resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = var.resource_group_location
tags = var.tags
}
resource "azurerm_virtual_network" "vnet" {
for_each = var.networks
name = each.key
address_space = [each.value.address_space]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
tags = var.tags
}
locals {
flattened_subnets = flatten([
for vnet_key, vnet_value in var.networks : [
for subnet in vnet_value.subnets : {
vnet_name = vnet_key
subnet_name = subnet.name
address_prefix = subnet.address_prefix
}
]
])
}
resource "azurerm_subnet" "subnet" {
for_each = { for subnet in local.flattened_subnets : "${subnet.vnet_name}-${subnet.subnet_name}" => subnet }
name = each.value.subnet_name
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = each.value.vnet_name
address_prefixes = [each.value.address_prefix]
delegation {
name = "container-instance-delegation"
service_delegation {
name = "Microsoft.ContainerInstance/containerGroups"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
depends_on = [azurerm_virtual_network.vnet]
}
resource "azurerm_container_group" "aci" {
for_each = { for idx, aci in var.aci_instances : idx => aci }
name = each.value.name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
os_type = "Linux"
restart_policy = each.value.restart_policy
ip_address_type = each.value.is_public ? "Public" : "Private"
subnet_ids = each.value.is_public ? null : [azurerm_subnet.subnet["${each.value.vnet_name}-${each.value.subnet_name}"].id]
dns_name_label = each.value.is_public ? each.value.dns_label : null
dynamic "container" {
for_each = each.value.containers
content {
name = container.value.name
image = container.value.image
cpu = container.value.cpu
memory = container.value.memory
dynamic "ports" {
for_each = container.value.ports
content {
port = ports.value.port
protocol = ports.value.protocol
}
}
}
}
image_registry_credential {
server = "index.docker.io"
username = var.docker_username
password = var.docker_password
}
tags = var.tags
depends_on = [azurerm_subnet.subnet]
}
Declaración de variables de entrada
El archivo variables.tf
define las variables usadas en main.tf
. Estas variables permiten una mayor flexibilidad y reutilización del código.
En este ejemplo, las variables definidas incluyen:
subscription_id: Utilizado para pasar dinámicamente el ID de suscripción de Azure a Terraform, mejorando la flexibilidad y seguridad.
resource_group_name: Variable tipo cadena para especificar el nombre del grupo de recursos de Azure donde se desplegarán todos los recursos.
resource_group_location: Especifica la ubicación o región en la que se creará el grupo de recursos.
tags: Mapa de cadenas clave-valor para asignar etiquetas a los recursos creados en Azure. Por ejemplo, se puede usar
Terraform = true
para indicar que el recurso se desplegó con Terraform.networks: Es un mapa de objetos, cada uno representando la configuración de una red virtual, incluyendo su espacio de direcciones y una lista de subredes.
aci_instances: Mapa de objetos, cada uno representando la configuración de una instancia ACI. Incluye propiedades básicas como nombre, imagen, CPU, memoria, y otras más avanzadas como puertos, protocolo, accesibilidad pública, política de reinicio y etiqueta DNS. Soporta múltiples contenedores por instancia.
Credenciales de Docker Hub: Estas variables son opcionales. Aquí se pueden definir las credenciales de acceso a Docker Hub para evitar errores al acceder a imágenes públicas debido a los límites de extracción.
variable "subscription_id" {
type = string
description = "Azure Subscription ID"
}
variable "resource_group_name" {
description = "The name of the resource group"
type = string
}
variable "resource_group_location" {
description = "The location of the resource group"
type = string
}
variable "networks" {
description = "Map of virtual networks and their subnets"
type = map(object({
address_space = string
subnets = list(object({
name = string
address_prefix = string
}))
}))
}
variable "aci_instances" {
type = map(object({
name = string
vnet_name = string
subnet_name = string
containers = list(object({
name = string
image = string
cpu = number
memory = number
ports = list(object({
port = number
protocol = string
}))
}))
is_public = bool
restart_policy = string
dns_label = string
}))
validation {
condition = alltrue([for aci in var.aci_instances : alltrue([for container in aci.containers : alltrue([for port in container.ports : port.protocol == "TCP" || port.protocol == "UDP"])])])
error_message = "Protocol must be either 'TCP' or 'UDP'."
}
validation {
condition = alltrue([for aci in var.aci_instances : aci.restart_policy == "Always" || aci.restart_policy == "OnFailure" || aci.restart_policy == "Never"])
error_message = "Restart policy must be either 'Always', 'OnFailure', or 'Never'."
}
}
variable "docker_username" {
description = "Docker Hub username (optional)"
type = string
default = null
}
variable "docker_password" {
description = "Docker Hub password (optional)"
type = string
default = null
sensitive = true
}
variable "tags" {
description = "Common tags for all resources"
type = map(string)
default = {
Environment = "www.jorgebernhardt.com"
Terraform = "true"
}
}
Declaración de valores de salida
El archivo output.tf
en Terraform extrae y muestra información sobre los recursos creados o gestionados por tu configuración. Estos outputs se definen con la palabra clave output
y pueden ser útiles para el usuario, para otras configuraciones de Terraform o para su uso programático en scripts u otras herramientas.
En este ejemplo, el archivo output.tf
devuelve información sobre el grupo de recursos, redes virtuales, subredes e instancias de Azure Container Instances (ACI) creadas.
Una vez que Terraform haya aplicado tu configuración, mostrará los valores de salida definidos.
output "resource_group_name" {
description = "The name of the resource group"
value = azurerm_resource_group.rg.name
}
output "virtual_networks" {
description = "List of virtual networks with their name and address space"
value = [
for vnet in azurerm_virtual_network.vnet : {
name = vnet.name
address_space = vnet.address_space
}
]
}
output "subnets" {
description = "List of subnets with their name, address prefix, and associated virtual network name"
value = [
for subnet in azurerm_subnet.subnet : {
name = subnet.name
address_prefix = subnet.address_prefixes
vnet_name = subnet.virtual_network_name
}
]
}
output "aci_instances" {
description = "Details of the deployed ACI instances"
value = {
for aci in azurerm_container_group.aci : aci.name => {
name = aci.name
location = aci.location
restart_policy = aci.restart_policy
resource_id = aci.id
ip_address = aci.ip_address
fqdn = aci.fqdn
containers = [
for container in aci.container : {
name = container.name
image = container.image
cpu = container.cpu
memory = container.memory
ports = [
for port in container.ports : {
port = port.port
protocol = port.protocol
}
]
}
]
}
}
}
Ejecución del despliegue con Terraform
Ahora que has declarado correctamente los recursos, es momento de realizar los siguientes pasos para desplegarlos en tu entorno de Azure:
Inicialización: Ejecuta el comando
terraform init
. Esto inicializa el directorio de trabajo que contiene los archivos.tf
, descarga el proveedor especificado enprovider.tf
y configura el backend de Terraform. Si quieres saber más sobre este paso, consulta [este enlace].Planificación: A continuación, ejecuta
terraform plan
. Este comando genera un plan de ejecución y muestra las acciones que realizará Terraform para alcanzar el estado deseado. Te permite revisar los cambios antes de aplicarlos.Aplicación: Cuando estés satisfecho con el plan, ejecuta
terraform apply
. Este comando implementará las modificaciones necesarias para alcanzar el estado deseado de la infraestructura. Antes de aplicar cualquier cambio, se te pedirá confirmación.Inspección: Tras aplicar los cambios, puedes usar
terraform show
para ver el estado actual de tu infraestructura.Destrucción (opcional): Cuando ya no se necesita un proyecto o los recursos han quedado obsoletos, puedes usar el comando
terraform destroy
. Esto eliminará todos los recursos creados por Terraform.