Terraform: Dando forma a nuestra infraestructura

La imagen muestra el logo de terraform y el de azure

Acabamos de terminar la serie sobre CI/CD, y en ella hemos hablado de lo importante que es mantener unas estrategias de trabajo que nos permitan la detección de errores rápidamente. Como colofón final, vimos como aprovisionar y desplegar en Azure Web Apps desde el Azure Pipelines (o desde otros sitios) y así despreocuparnos de esa parte utilizando las plantillas ARM.

Utilizar ARM es muy potente, pero quizás es algo tedioso de utilizar… ahí es donde entra terraform. ¿Y que es terraform podrás preguntarte? Pues es una herramienta multiplataforma hecha en Go que nos permite una gran abstracción sobre ARM, pero no solo funciona con ARM, admite una lista de proveedores bastante larga y que puedes consultar aquí.

Además de permitirnos trabajar de forma declarativa, sin tener que bajar al fango, aporta una gran ventaja respecto a usar ARM, y es que terraform mantiene el estado. Es decir, la herramienta es capaz de almacenar el estado de las cosas que ya están desplegadas, evitando volver a desplegarlas si no hay cambios. Pero hablar es fácil, y que yo te cuente que algo es mejor o peor no sirve de mucho, así que vamos a probarlo para ver lo sencillo que es.

Instalando terraform

Vamos a ir a su web y descargarnos el binario para nuestro sistema operativo. En la web nos indican que tenemos que añadir la ruta donde hayamos puesto el binario al PATH. (En mi caso, «C:/Terraform»):

La imagen muestra la ruta de terraform en el PATH

Una vez que lo hayamos hecho, desde una terminal vamos a comprobar que todo esta bien ejecutando:

terraform version

Si todo está bien, debería devolvernos la versión actual. Si es así, ya tenemos terraform instalado. ¿A qué no ha sido tan difícil?

Aunque trabaja con ficheros *.tf que son texto plano, yo recomiendo utilizar Visual Studio Core con la extensión «Terraform«. Si tienes dudas sobre como instalar cualquiera de las dos, te dejo un enlace de CampusMVP donde explico cómo preparar un entorno de trabajo para .Net Core, al que solo hay que añadir la nueva extensión. Esto solo es una sugerencia, si prefieres utilizar cualquier otro entorno, o simplemente el block de notas y la terminal, todo funcionará igual de bien.

Creando nuestro código terraform

Para esta entrada, vamos a hacer un aprovisionamiento sencillo de una Azure Web App Windows, junto a su plan y su grupo de recursos desde terraform (para la siguiente vamos a hacer un escenario algo más complejo añadiendo un Sql Server a nuestra web). Para ello, vamos a crear un fichero al que llamaremos «main.tf» (el nombre no es importante). De hecho, terraform buscará todos los ficheros «*.tf» desde el nivel en el que se encuentre y unificará todos los archivos, por lo que es una buena idea separar los archivos por finalidades. En esta entrada vamos a utilizar un solo fichero para el código, pero una estructura más clara podría ser:

  • webapp.tf para el código de la web.
  • resourcegroup.tf para el código de grupo de recursos.
  • variables.tf para las variables que vamos a necesitar.

Definiendo el proveedor

Lo primero que vamos a hacer, es definir el proveedor junto a las 4 variables que necesita (para utilizar las variables, se hace con el formato «${var.NOMBRE_VARIABLE}» ):

provider "azurerm" {
  version         = ">= 1.6.0"
  subscription_id = "${var.ID_Suscripcion}"
  client_id       = "${var.ID_Aplicacion}"
  client_secret   = "${var.Password_Aplicacion}"
  tenant_id       = "${var.ID_Tenant}"
}
variable "ID_Suscripcion" {
  description = "ID de la suscripción de Azure"
}
variable "ID_Aplicacion" {
  description = "ID del la aplicación"
}
variable "Password_Aplicacion" {
  description = "Secreto de la aplicación"
}
variable "ID_Tenant" {
  description = "ID del directorio activo de Azure"
}

¿Y de dónde sacamos estos datos? Pues dos de ellas simplemente debemos consultarlas, para ello escribimos en la PowerShell:

az account list

Esto nos devolverá el ID de la suscripción y el directorio activo:

La imagen muestra el ID_Suscripcion y el ID_Tenant

Para los dos campos de aplicación, lo que vamos a hacer es registrar una aplicación en nuestro directorio activo. Esto se puede hacer desde el portal de Azure, o desde la propia terminal ejecutando:

az ad sp create-for-rbac -n "NombreAplicacion"

Eso nos va a devolver los datos que nos faltan:

La imagen muestra los datos de ID_Aplicacion y Password_Aplicacion

Si tienes problemas porque no se reconoce el comando «az», sigue este enlace para instalar Azure CLI.

Definiendo el Grupo de Recursos

Una vez que lo tenemos, vamos a añadir un grupo de recursos para nuestra Web App:

resource "azurerm_resource_group" "webapp" {
  name     = "${var.resource_group_name}"
  location = "${var.location}"
}
variable "location" {
  description = "Region donde queremos que se cree"
}
variable "resource_group_name" {
  description = "Nombre del grupo de recursos"
}

Definiendo el Service Plan

Ahora vamos a añadir el service plan:

resource "azurerm_app_service_plan" "webserviceplan" {
  name                = "${var.service_plan_name}"
  location            = "${azurerm_resource_group.webapp.location}"
  resource_group_name = "${azurerm_resource_group.webapp.name}"

  kind = "${var.plan_settings["kind"]}"

  sku {
    tier     = "${var.plan_settings["tier"]}"
    size     = "${var.plan_settings["size"]}"
    capacity = "${var.plan_settings["capacity"]}"
  }
}
variable "plan_settings" {
  type        = "map"
  description = "Definimos el tipo de plan que vamos a utilizar"

  default = {
    kind     = "Windows"  # Linux or Windows
    size     = "S1"
    capacity = 1
    tier     = "Standard"
  }
}
variable "service_plan_name" {
  description = "Nombre del service plan"
}

Una de las ventajas que podemos ver, es que terraform nos permite utilizar datos de los propios recursos desplegados utilizando el formato «${TIPO_DE_RECURSO.NOMBRE_RECURSO.DATO}». Por ejemplo, en:

location  = "${azurerm_resource_group.webapp.location}"

Le estamos indicando que la localización del service plan, queremos que sea la misma que la del recurso «azurerm_resource_group» con el nombre «webapp». Trabajando de esta manera, terraform es capaz de calcular el grafo de dependencias para que todo se pueda aprovisionar sin problemas.

Definiendo la Web App

Por último, solo nos queda añadir el Web App:

resource "azurerm_app_service" "webapp" {
  name                = "${var.name}"
  location            = "${azurerm_resource_group.webapp.location}"
  resource_group_name = "${azurerm_resource_group.webapp.name}"
  app_service_plan_id = "${azurerm_app_service_plan.webserviceplan.id}"
}
variable "name" {
  description = "Nombre de la Web App"
}

Hay muchos parámetros, que podríamos poner en el propio código, pero entonces no sería reutilizable… Terarform nos permite indicarle todos estos parámetros cuando vayamos a hacer el despliegue en el propio comando, pero como puedes intuir, es muy tedioso. Así que vamos a utilizar otra de las opciones que nos da terraform, que es cargar tener un segundo fichero de variables con sus valores.

Si usas git, hay que añadirlo en el .gitignore o nuestras claves se irán al repositorio.

Para poder tener este fichero, creamos un segundo fichero con el formato «*.auto.tfvars», por ejemplo, «variables.auto.tfvars», donde iremos rellenando los valores de esta forma:

ID_Suscripcion = "----"
ID_Aplicacion = "----"
Password_Aplicacion = "----"
ID_Tenant = "----"
name = "postterraformbasic"
service_plan_name = "planpostterraformbasic"
resource_group_name = "postterraform"
location = "westeurope"

A la vista está que este código es más fácil de seguir y mantener que si fuese ARM directamente, aunque hay algunas cosas que no están soportadas aun, y puede hacer falta incluir algo de ARM en algunos casos…

Desplegando con terraform

Una vez que tenemos todo el código, y las variables listas, vamos a desplegar, para ello vamos a inicializar la herramienta con:

terraform init
La imagen muestra la salida del comando init

Una vez hecho esto, vamos a comprobar si existe algún error con:

terraform validate

En caso de que no exista ningún error, lo siguiente es calcular los cambios (aunque este paso se puede omitir):

terraform plan

La salida de este comando nos va a mostrar algo como esto:

La imagen muestra la salida del comando terraform plan

Como salida, nos da las acciones que va a ejecutar en detalle, pero solo son a nivel informativo, para ejecutar el despliegue, utilizamos:

terraform apply

Esto nos devuelve algo parecido al comando plan, donde nos dice que es lo que va a hacer, pero ahora, nos pide que confirmemos escribiendo «yes» si queremos que despliegue:

La imagen muestra la petición de confirmación de terraform

Tras escribir «yes», se inicia el proceso. Una vez termine, la salida será algo asi:

La imagen muestra el resultado de finalización

Como terraform almacena el estado, si volvemos a intentar desplegar lo mismo, volviendo a ejecutar:

terraform apply

Obtenemos el resultado esperado, y nos dice que no hay ningún cambio pendiente:

La imagen muestra como el comando apply termina con 0 cambios

Si por el contrario hubiésemos cambiado algo en el código, el resultado hubiese sido un cambio en nuestra infraestructura. Para terminar con las opciones que tenemos, existe una manera de eliminar los recursos desplegados:

terraform destroy

Volverá a pedirnos confirmación, y basta con que volvamos a escribir «yes»:

La imagen muestra las acciones que va a hacer el comando terraform destroy
La imagen muestra el final del comando terraform destroy

Conclusión

Aunque ha sido un ejemplo simple, se puede ver que terraform es una herramienta muy potente que nos quita mucho trabajo a la hora de desplegar infraestructura desde código al ser declarativo, persistir el estado y encima calcular las dependencias automáticamente. Existe una lista detallada de las opciones disponibles en la documentación oficial, a la cual recomiendo echarle un ojo.

En la próxima entrada vamos a ver un ejemplo más en profundidad creando una web en Linux conectada a un Sql Server, de modo que tengamos una infraestructura funcional, y además desplegaremos la web desde Azure Pipelines para poder comparar el proceso en el caso de ARM y en el caso de Terraform.

Si quieres ver el código completo, dejo un gist. También os dejo el enlace a un repo muy interesante de un workshop sobre terraform de un compañero.

Deja un comentario