Terraform: Despliegue automatizado de infraestructura con Azure DevOps

Article available only in Spanish

1. Introducción

Este post muestra paso a paso cómo desplegar infraestructura básica de forma automatizada en Azure usando Terraform y pipelines de CI/CD en Azure DevOps.

Dicho de forma simple, se parte de un fichero básico de configuración de Terraform donde se define la infraestructura a desplegar y se configura Azure DevOps para habilitar pipelines de CI/CD que despliegan los cambios de infraestructura definidos en el fichero de Terraform cada vez que se produce un commit en el repositorio que los contiene.

Además, se explica de forma previa cómo desplegar el mismo fichero de Terraform usando diferentes interfaces cliente con Azure. En concreto, se muestra cómo desplegar los ficheros de Terraform desde Azure Cloud Shell y desde cualquier máquina Windows configurada con los componentes y software necesario. Saber interactúar o realizar pruebas básicas desde nuestra máquina local o Azure Cloud Shell es obligatorio como paso previo a automatizar los despliegues con Azure DevOps

Así pues, los objetivos principales que se pretenden mostrar en este post son:
 
Una vez fijados los objetivos que pretende cubrir este post se asume que el lector posee conocimientos básicos de Azure, Terraform y Azure DevOps, si bien a lo largo del post se irán recordando la mayor parte de conceptos o proporcionando enlaces para obtener la ayuda necesaria.

2. Consideraciones Generales

Las soluciones mostradas a lo largo de este post han tenido en cuenta las siguientes consideraciones:
 
  • La versión de Terraform utilizada para ejecutar y verificar los ejemplos es v0.12.29.
  • Se manejará el mismo fichero de Terraform de forma independiente al interfaz utilizado para desplegarlo. Se pretende evitar modificaciones en los ficheros según el interfaz desde el que se va a desplegar.
  • El código de Terraform se incluye en un único fichero denominado main.tf. Existen formas más correctas para organizar dicho código en multiples ficheros aún cuando alguno de ellos se cree sin contenido (fichero de definición de variables, fichero de asignación de variables, outputs, etc.) pero se maneja un único fichero a fin de hacer este post lo más simple posible. Si lo deseas puedes consultar las best practices recomendadas por Terraform al respecto.
  • Se asume un entorno colaborativo, es decir, varios miembros de un mismo equipo trabajando de forma paralela. Como se verá posteriormente, esto obliga a guardar el "estado" de los despliegues en Azure vía Terraform de forma centralizada y remota. Se evitará el uso de ficheros de "estado" locales.
  • La información sensible, como contraseñas, secretos o claves nunca serán añadidas en formato plano a los ficheros de Terraform de modo que no apareceran en los repositorios de código. Se planteará una solución sencilla en cada caso y se propondrán otras soluciones más óptimas como mejoras a implementar a lo descrito en este post. Se mostrarán dos modos de asignar estas claves o secretos vía variables de entorno con prefijo TF_VAR_ o ARM_ siguiendo las convenciones definidas por Terraform. Si lo deseas puedes consultar las best practices recomendadas por Terraform al respecto.
  • De forma habitual se referencian nombres e identificadores específicos de mi entorno de desarrollo de modo que en caso de seguir este post será necesario modificarlos por aquellos que se ajusten al entorno del lector.

3. ¿Por qué Terraform?

Terraform crea, modifica o elimina recursos en cloud a partir de código. Esto es lo que se denomina Infrastructure as Code (IaS). Las principales ventajas que ofrece Terraform son las siguientes:

  • Permite automatizar la creación de infraestructura vía código
  • Permite estandarizar, replicar y versionar recursos evitando errores derivados de interacciones manuales.
  • Visualización de los cambios de infraestructura antes de ser aplicados.
  • Implementación de infraestructura en diferentes clouds con un lenguaje declarativo común y sencillo.
  • El código es desplegado y convertido en infraestructura de forma óptima aprovechando paralelismos siempre que es posible.
  • Posibilidad de generar infrastructura de forma rápida y automática ante desastres (Disaster Recovery)
  • Mayor sencillez en el lenguaje y menos líneas de código para desplegar los mismos recursos respecto al empleado en proveedores soportados como Azure Resource Manager Templates u otros.

4. Configuración de Azure

Este post asume la existencia de una suscripción activa en Azure. Si todavía no dispones de una de ellas puedes ver en el siguiente enlace cómo crear una cuenta gratuita en Azure. Una vez dispongas de una cuenta en Azure estarás preparado para configurar todos los componentes necesarios a los que se hace referencia a lo largo de este post

4.1 Azure Cloud Shell

Azure Cloud Shell requiere una suscripción activa en Azure. Como se menciona en la documentación oficial de Microsoft, Azure Cloud Shell es un shell interactivo, autenticado y al que se puede acceder desde un explorador para administrar recursos de Azure. Ofrece la flexibilidad de poder elegir la experiencia de shell que mejor se adapte a la forma de trabajar de cada uno, Bash o PowerShell. Puedes acceder a Azure Cloud Shell vía http://shell.azure.comEn lo que a este post concierne, éstos son los puntos importantes a tener en cuenta:

- No es necesaria la instalación de Terraform, ya que Azure Cloud Shell ya proporciona, en teoría, la última versión de dicha aplicación. Puedes ver la versión instalada ejecutando desde el shell el comando terraform -version:

Azure Cloud Shell utiliza un Fileshare incluido en una Storage Account para persistir datos y ficheros. Esto implica que, si no lo has hecho ya, tendrás que crear dichos componentes. Puedes ver de forma detallada cómo hacerlo usando la configuración básica por defecto o la avanzada en Persistencia de archivos en Azure Cloud Shell. Mi recomendación es crear por anticipado una Storage Account y un Fileshare con nombres que sigan la convención del resto de nuestros recursos y posteriormente referenciar dichos componentes desde la configuración avanzada del shell o dicho de otro modo, crear el "mount" sobre ellos. De una forma u otra, al final el directorio clouddrive de nuestra home apuntará al Fileshare anteriormente configurado. Puedes ver el Fileshare utilizado mediante el comando df:

En mi caso jmstgacc es el nombre de la Storage Account y jm-cs-fs el nombre del FileShare. En la columna Mounted on puede verse que dicho Fileshare se encuentra "montado" sobre el directorio clouddrive de nuestra home (/home/jmuro en mi caso).

En la imagen posterior puede verse como queda mi arbol de directorios una vez abierto desde el editor de ficheros. En clouddrive he creado un directorio jm77 para contener mis pruebas de concepto, entre ellas jm-inn-terraform-intro que será el directorio sobre el cual almacenaré los ficheros de Terraform a ejecutar vía cloud shell:

También puedes visualizar y acceder a los ficheros contenidos en el Fileshare por medio del Portal de Azure

O mediante otras herramientas muy recomendables como Microsoft Azure Storage Explorer:

4.2 Azure Service Principal

La creación de recursos en Azure debe realizarse en el contexto de seguridad de una cuenta de usuario o de sistema con los permisos necesarios. Cuando se utiliza Terraform es buena práctica crear un Service Principal específico con el rol contributor a nivel de suscripción como vía de autenticación y asignación de permisos. La asignación del rol contributor a nivel de suscripción permitirá crear toda la infraestructura necesaria en el ámbito de dicha suscripción. No son necesarios permisos adicionales.

Desde los ficheros con código HCL de Terraform se configura el proveedor de Azure (azurerm) de modo que este Service Principal sea referenciado y todos los recursos desplegados se ejecuten en su contexto de seguridad. La forma de asignar los valores de contraseña o client secret serán diferentes según el interfaz que estemos utilizando en cada momento (Azure Cloud Shell, Azure DevOps, etc.), ya que no vamos a guardar información sensible de este tipo en los ficheros de Terraform.

A continuación se explica cómo crear el Service Principal desde el shell de Azure:

1. Abrir una sesión de Azure Cloud Shell y autenticarse con az login.

2. En caso de tener varias suscripciones de Azure activas, seleccionar la suscripción sobre la que vamos a crear nuestros nuevos recursos:

az account list --output table

az account set --subscription "YOUR_SUBSCRIPTION_ID"

3. Puedes verificar la suscripción seleccionada con:

az account show

4. Crear el Service Principal con permisos de contributor a nivel de suscripción:

az ad sp create-for-rbac --name "YOUR_SERVICE_PRINCIPAL_NAME" --role="Contributor" --scopes="/subscriptions/YOUR_SUBSCRIPTION_ID"

Si todo funciona de forma correcta se generará nuestro Service Principal con el nombre asignado precedido de http://, un appId de tipo guid (será referenciado como client_id en Terraform) y un password por defecto (será referenciado como client_secret en Terraform) en el tenant o directorio (referenciado como tenant_id en Terraform) asociado a nuestra suscripción.

5. Si lo deseas, puedes modificar el password creado por defecto con el siguiente comando:

az ad sp credential reset --name "YOUR_SERVICE_PRINCIPAL_NAME" --password "YOUR_NEW_PASSWORD"

El password no queda almacenado en Azure en formato plano así que es importante recordarlo. No obstante, siempre se puede resetear en caso de olvidarlo con el comando que acabamos de utilizar.

6. Puedes verificar la creación del Service Principal desde el Portal de Azure accediendo a Subscription -> Access control (IAM) -> Role Assignments:

4.3 Azure Container: Contenedor Remoto de Estado de Terraform

Terraform utiliza un Fichero de Configuración de Estado para almacenar información o metadatos sobre la infraestructura anteriormente desplegada en cloud de modo que la sincronización de cambios de infraestructura respecto a los cambios de código en los ficheros de Terraform con código HCL (HashiCorp Configuration Language) puedan efectuarse correctamente.

En caso de trabajar en modo colaborativo es buena práctica almacenar este fichero de configuración de estado de forma remota y centralizada, por ejemplo en un container de Azure creado desde una Storage Account. Además, el hecho de guardar la información de estado de forma centralizada nos permitirá desplegar infraestructura desde multiples interfaces de forma sincronizada, por ejemplo desde pipelines de Azure DevOps, Azure Cloud Shell o nuestra máquina local vía Azure CLI.

En el apartado sobre Azure Cloud Shell vimos que necesitabamos crear una Storage Account para persistir ficheros. Por tanto, ya deberíamos disponer de una Storage Account. No obstante, queda a gusto del lector el crear una nueva cuenta de almacenamiento específica. En mi caso, así lo prefiero, la nueva cuenta se denomina jminnstgacc. Si todavía no estás familiarizado, puedes consultar en el siguiente enlace los Pasos a seguir para crear una Cuenta de Almacenamiento

A continuación se muestran los pasos básicos para crear el Azure Container que almacenará nuestros ficheros de configuración de estado de Terraform:

1. En el portal de Azure, tras seleccionar nuestra Storage Account, acceder a la opción de menú Blob service -> Containers.

2. Pulsar en Container para añadir un nuevo container.

3. Completar los datos solicitados, nombre del container (referenciado como container_name en Terraform) y nivel de acceso:

4. Una vez creado el container, accedemos a la sección de access keys desde la cuenta de almacenamiento para obtener las claves de acceso:

Pulsando en el botón Show keys aparecen las claves de acceso key1 y key2. Ambas son válidas para acceder a la Storage Account. En la imagen posterior se muestra la key1 en modo hidden:

Esta información de claves será usada desde los ficheros de configuración de Terraform (sección backend) para gestionar de forma remota la información de estado de creación de recursos e infraestructura en Azure. El nombre del blob en el que almacenaré la información de estado será terraform-intro.tfstate (referenciado como key en Terraform).

5. El blob almacenado en el container de Azure será utilizado en modo colaborativo de manera que debe bloquear el acceso a otros usuarios cuando se está utilizando de forma previa. Este proceso es realizado automáticamente por Terraform en Azure por medio de la información de Lease State:

Si el fichero está disponible, el campo Lease State tendrá el valor Available. Si el fichero está siendo modificado mostrará el valor Leased. Si la liberación del fichero falla por algún motivo podemos liberar el lease de forma manual mediante la opción de menú Break Lease. Simplemente tener en cuenta esta información para saber de antemano los motivos de posibles errores cuando se ejecutan comandos de Terraform como plan o apply. Si el contenedor de estado no está disponible Terraform mostrará un error y abortará el comando a ejecutar.

5. Archivos de Configuración de Terraform

Terraform utiliza ficheros planos de texto para describir la infraestructura a crear en entornos cloud soportados (Microsoft Azure, AWS, GCP, etc.). El lenguaje utilizado en estos ficheros se denomina HashiCorp Configuration Language (HCL). Los archivos se crean con la extensión ".tf".

A medida que la configuración de infraestructura se hace más compleja tiene sentido el uso de varios Módulos personalizados de Terraform para organizar el código HCL. Dicho simple, un módulo personalizado de Terraform es un conjunto de archivos de configuración HCL en un único directorio. El módulo más alto en la jerarquía se denomina módulo raíz. Los módulos referenciados pueden ser locales o remotos y se denominan módulos hijos. En este post tan solo usaremos un único módulo con un único archivo de configuración.

Así pues, partiremos del archivo de configuración de Terraform mostrado más abajo explicando cada bloque de código HCL. Queda pospuesto para otros posts la generación de ficheros más complejos. Este post se centra en el despliegue de estos archivos de configuración desde diferentes interfaces y en los comandos básicos proporcionados por Terraform (init, validate, plan, apply).

A continuación se muestra nuestro archivo de configuración de Terraform, denominado main.tf, que crea un simple grupo de recursos en la suscripción seleccionada de Azure. Para hacerlo lo más simple posible no se incluye ningún otro componente dentro del grupo de recursos a desplegar:

 
# It will be set via environment variable TF_VAR_sp_client_secret
variable "sp_client_secret" {
  type = string
  description = "Contains the client secret to authenticate in Azure via service principal"
}

provider "azurerm" {  
  # Non-beta version >= 2.5.0 and < 3.0.0
  version = "~>2.5"
 
  # Configure Service Principal
  subscription_id = "YOUR SUBSCRIPTION ID"
  tenant_id       = "YOUR TENANT ID"
  client_id       = "YOUR SERVICE PRINCIPAL CLIENT ID"
  client_secret   = var.sp_client_secret
 
  # Required. Leave it empty if non-used. Used to set up some properties for new resources.
  features {}
}

# Reference to container holding the terraform state
terraform {
  backend "azurerm" {
 
    # Not allowed to use variables in this block
    resource_group_name  = "jm-inn-core-rg"
    storage_account_name = "jminnstgacc"
    container_name       = "terraform"
    key                  = "terraform-intro.tfstate"
 
    # It is a best practice not include this key in plain text here.
    # It will be set via environment variable ARM_ACCESS_KEY
    # access_key = "YOUR_ACCESS_KEY"
 
  }
}
 
# Creation of Resource Group in Azure
resource "azurerm_resource_group" "rg" {
        name = "jm-inn-terraform-intro-rg"
        location = "westeurope"
        tags =  {
           Topic = "Innovation"
           Category = "Terraform"
           Subcategory = "Introduction"
   }
}

 

Este archivo tan sólo contiene código HCL para crear un grupo de recursos denominado jm-inn-terraform-intro-rg en la region West Europe en la suscripción de Azure seleccionada. También asigna una colección de tags al grupo de recursos. En definitiva, lo suficiente para cumplir de forma simple y rápida con los objetivos de este post.

Ahora vamos a explicar cada bloque del fichero:

1. El primer bloque iniciado con la keyword variable sirve para definir variables. Estas variables pueden ser referenciadas posteriormente mediante la sintaxis var.nombre_variable. Se puede declarar y asignar directamente un valor a las variables en el propio fichero, en ficheros separados con las declaraciones tipo variables.tf y con las asignaciones de valores tipo *.tfvars, utilizando múltiples parámetros (-var) en la CLI de Terraform o inyectando dichos valores mediante la creación de variables de entorno de sesión, usuario o sistema con nombres que siguen convenciones predeterminadas. En este post, por simplicidad, quedarán definidas en el mismo fichero main.tf. Puedes ampliar esta información en la Documentación oficial de Terraform sobre Variables.

variable "sp_client_secret" {
  type = string
  description = "Contains the client secret to authenticate in Azure via service principal"
}
 

2. El segundo bloque iniciado con la keyword provider "azurerm" configura Azure como proveedor destino para la creación de infraestructura. Además fija los atributos necesarios para:

  • Indicar las versiones de Terraform aceptadas por nuestro fichero que serán descargadas como plugins con el uso del comando terraform init.
  • Configurar el uso del Service Principal que creamos anteriormente en el capítulo 4.
  • Asignar propiedades avanzadas sobre recursos de Azure.
 
provider "azurerm" {  
  # Non-beta version >= 2.5.0 and < 3.0.0
  version = "~>2.5"
 
  # Configure Service Principal
  subscription_id = "YOUR SUBSCRIPTION ID"
  tenant_id       = "YOUR TENANT ID"
  client_id       = "YOUR SERVICE PRINCIPAL CLIENT ID"
  client_secret   = var.sp_client_secret
 
  # Required. Leave it empty if non-used. Used to set up some properties for new resources.
  features {}
}
 
 
subscription_id: Opcional. Identificador de la suscripción de Azure utilizada para desplegar recursos. Si no es asignada se utilizará la suscripción por defecto configurada en Azure. También puede ser asignada de forma externa mediante la creación de la variable de entorno ARM_SUBSCRIPTION_ID.
 
client_id: Opcional. Opcional. Identificador del usuario o service principal utilizado para autenticarse en Azure. Puede ser asignado de forma externa mediante la creación de la variable de entorno ARM_CLIENT_ID.
 
client_secret: Opcional. Cuando se usa Service Principal como modo de autenticación tiene sentido definir este atributo. Puede ser asignado mediante la variable de entorno ARM_CLIENT_SECRET. En nuestro caso el valor es asignado de forma indirecta mediante la asignación de una variable. Está hecho de este modo para mostrar otra posible asignación de variables de entorno a variables internas. Terraform buscará una variable de entorno denominada TF_VAR_ seguida del nombre de la variable interna para asignar el correspondiente valor. En este caso deberiamos definir una variable de entorno llamada TF_VAR_sp_client_secret.
 
tenant_id: Opcional. El tenant o directorio asociado a la suscripción. Se puede asignar mediante la variable de entorno ARM_TENANT_ID.
 
features: Obligatorio. Puede tener contenido vacío. Se usa para personalizar el comportamiento de determinados recursos a desplegar en Azure.
 

3. El tercer bloque iniciado con la keyword backend "azurerm" contiene la configuración del container remoto que almacenará la información de estado de la infraestructura desplegada con Terraform. Sin información de estado disponible o incorrecta, Terraform generará errores y abortará la ejecución de comandos. 

 
# Reference to container holding the terraform state
terraform {
  backend "azurerm" {
 
    # Not allowed to use variables in this block
    resource_group_name  = "jm-inn-core-rg"
    storage_account_name = "jminnstgacc"
    container_name       = "terraform"
    key                  = "terraform-intro.tfstate"
 
    # It is a best practice not include this key in plain text here.
    # It will be set via environment variable ARM_ACCESS_KEY
    # access_key = "YOUR_ACCESS_KEY"
 
  }
}
 
Recomiendo revisar la Documentación oficial de Terraform para el backend azurerm antes de continuar con la explicación de cada atributo utilizado:
 
resource_group_name: Obligatorio. Nombre del grupo de recursos que contiene la Storage Account.
 
storage_account_name: Obligatorio. Nombre de la Storage Account.
 
container_name: Obligatorio. Nombre del container en la Storage Account.
 
key: Obligatorio. Nombre del blob utilizado para almacenar el fichero de configuración de estado de Terraform en el container.
 
access_key: Opcional. La clave de acceso a la Storage Account. Puede ser asignada mediante la variable de entorno ARM_ACCESS_KEY. En este caso y con objeto de que el fichero no almacene esta información tan sensible, la asignación de la clave se hará vía variables de entorno. En el fichero puedes ver que este atributo está comentado. 
 

4. El cuarto bloque iniciado con la keyword resource "azurerm_resource_group" "rg" define la configuración del grupo de recursos a desplegar en Azure.

resource "azurerm_resource_group" "rg" {
        name = "jm-inn-terraform-intro-rg"
        location = "westeurope"
        tags =  {
           Topic = "Innovation"
           Category = "Terraform"
           Subcategory = "Introduction"
   }
}
 
azurerm_resource_group: Tipo de recurso a generar en Azure, en este caso identifica a un grupo de recursos.
 
rg: Nombre local a nivel de módulo de Terraform utilizado para referenciar este recurso en dicho módulo.
 
name: Nombre del grupo de recursos.
 
location: Región donde se crea el grupo de recursos.
 
tags: Etiquetas que categorizan al grupo de recursos.
 

6. Uso de Terraform desde Azure Cloud Shell

En este capítulo vamos a ver cómo ejecutar nuestro archivo de configuración de Terraform, visto en el capítulo 5, desde Azure Cloud Shell. Como ya se explicó en el capítulo 4, al usar el shell de Azure ya tenemos disponible Terraform con la última versión por lo que no hará falta ninguna instalación adicional.

1. Accedemos a Azure Cloud Shell vía http://shell.azure.com, nos autenticamos y seleccionamos el directorio o tenant sobre el que deseamos trabajar.

2. Creamos una estructura de directorios para almacenar nuestro archivo de configuración de Terraform.

Bajo el directorio clouddrive, sobre el que se monta la Storage Account de persistencia de ficheros (visto en el capítulo 4) crear una estructura de directorios similar a la mostrada en la imagen anterior. En mi caso jm-inn-terraform-intro será el directorio o módulo raíz que contendrá el archivo de Terraform main.tf. Puedes crear directorios directamente desde el shell de Azure usando comandos de PowerShell o Bash. También puedes hacerlo de forma externa vía Microsoft Azure Storage Explorer.

3. Nos movemos al directorio donde se encuentra nuestro módulo raíz de Terraform y creamos las variables de entorno de sesión que fijarán los valores de la variable interna sp_client_secret asociada a la clave del Service Principal utilizado para autenticar Terraform en Azure y el atributo access_key del bloque de backend destinado a almacenar de forma remota la configuración de estado de Terraform. En la imagen posterior se puede ver cómo crear las variables de entorno con Powershell:

4. Ejecutamos terraform init. Deberías ver una lista de mensajes similar a:

5. Ejecutamos terraform plan. Terraform consulta la información de estado existente en el backend y traza un plan informando de los cambios a efectuar en la infraestructura Azure, tanto para añadir, modificar o eliminar. 

Si es la primera vez que despliegas la infraestructura, en el plan trazado deberías ver sólo componentes a añadir. Si ya has desplegado anteriormente y modificas alguna configuración deberías ver algo similar a lo mostrado en la imagen anterior. En mi caso ya había desplegado la infraestructura y el único cambio introducido fue la inclusión de un nuevo tag denominado CreatedFrom. Este nuevo tag es marcado con un signo + y simplemente lo uso como método de verificación rápida del origen desde el que se ejecutó Terraform.

6. Ejecutamos terraform apply. Tras confirmar la ejecución, si todo funciona bien deberiamos ver un mensaje similar a:

7. Verificamos la creación del grupo de recursos en el portal de Azure:

8. Como comprobación adicional también podemos visualizar el blob terraform-intro.tfstate que guarda la información de estado de Terraform para este proyecto, verificar que ha sido modificado según la fecha de ejecución de nuestros comandos (columna Modified) y que ha sido liberado correctamente (Columna Lease State debería ser Available):

7. Uso de Terraform con Windows

La ejecución de Terraform desde máquinas con Windows es bastante similar a lo ya visto en el capítulo anterior, si bien es necesaria la instalación de los siguientes componentes:

1. Instalación de Terraform. Una vez realizada la descarga, se debe extraer el archivo ejecutable Terraform.exe y guardarlo en un directorio a elección del usuario. Posteriormente se debe crear una nueva entrada en la variable de entorno de sistema PATH para añadir la ruta a dicho ejecutable:

  • Ir a Mi PC
  • Botón derecho del ratón y click en Properties
  • Click en Advanced system settings
  • Click en Environment Variables
  • Seleccionar variable Path y a continuación, Edit
  • Añadir la ruta al ejecutable de Terraform

2. Para que Terraform pueda autenticarse en el proveedor de Azure debe instalarse Azure CLI. Una vez instalado se puede utilizar Azure CLI vía Powershell o CMD de Windows.

3. Comprobar que Azure CLI está instalado correctamente. Desde CMD o PowerShell ejecutar az --version. Si el CLI está bien instalado mostrará la versión correspondiente:

4. Comprobar que la configuración de la variable de entorno Path es correcta para Terraform. Abrir una consola CMD o Powershell y ejecutar el comando terraform. Si Path está bien configurada se mostrarán mensajes de ayuda sobre la ejecución de los comandos:

Una vez instalados todos los componentes anteriores estamos preparados para ejecutar nuestros archivos de código HCL de Terraform.

El siguiente paso es crear un directorio para almacenar nuestro módulo de Terraform con el fichero main.tf. Podemos hacerlo tan simple como copiar y pegar ese fichero en la ruta que deseemos y empezar a ejecutar los comandos o bien conectar dicho directorio con un repositorio remoto usando Git, Visual Studio Code, etc. En mi caso utilizaré Visual Studio Code, si bien todos los comandos ejecutados desde el terminal de VS Code podrían ser utilizados de igual modo desde PowerShell o CMD de Windows. Si al final decides usar Visual Studio Code como entorno integrado de desarrollo recomiendo utilizar las siguientes extensiones:

En la imagen de abajo puede verse mi árbol de directorios local. Tan solo contiene un módulo de Terraform con el fichero main.tf ya visto anteriormente y un fichero README.MD. Esta conectado a un repositorio Git en Azure DevOps para tener control del código fuente, si bien no influye en la interacción con Terraform:

Previo a la ejecución de los comandos de Terraform es necesario crear las variables de entorno para asignar valores a las claves o secretos asociados con el Service Principal de autenticación en Azure y con la Storage Account usada para almacenar la información de estado. Basicamente lo mismo que ya hicimos desde Azure Cloud Shell pero en este caso tenemos la posibilidad de crear variables de entorno de sistema en lugar de variables de entorno de sesión. Por tanto una opción es añadir las siguientes variables de entorno de sistema:

- ARM_ACCESS_KEY con el valor de key1 o key2 cuyos valores pueden visualizarse en Azure en la sección Access Keys de la Storage Account.

- TF_VAR_sp_client_secret con el valor del client_secret o password asignado al Service Principal.

Una vez creadas las variables de entorno ya estamos listos para ejecutar exactamente la misma secuencia de comandos de Terraform que vimos en el apartado correspondiente relativo a Azure Cloud Shell:

1. terraform init

2. terraform plan

En este caso, ya que estamos utilizando el fichero de código HCL original en el que no existe la tag CreatedFrom, Terraform detecta está diferencia respecto a lo anteriormente desplegado y nos informa que dicho tag será eliminado (con un signo -) en la ejecución del comando apply. Recuerda que la última vez que modificamos el grupo de recursos jm-inn-terraform-intro-rg desde Azure Cloud Shell incluimos dicha tag adicional.

3. terraform apply

Si todo va bien Terraform nos informa con un mensaje del tipo:

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

Unicamente nos quedaría por comprobar que el grupo de recursos ha sido modificado de tal modo que solo contiene las tags Topic, Category y Subcategory:

Así pues, como se puede ver en la imagen anterior, la ejecución funcionó correctamente.

8. Automaticación con Azure DevOps y Terraform

8.1 Creación de nueva Organización en Azure DevOps

A continuación se detallan los pasos a seguir para la creación de una nueva organización:

1. Acceder a Azure DevOps y pulsar en New Organization:

2. Introducir el nombre de la nueva organización, en mi caso jm77, la localización donde almacenar los proyectos incluidos, en mi caso West Europe y presionar en Continue:

3. Una vez creada la nueva organización, Azure DevOps nos presenta un asistente en el que se nos insta a empezar a trabajar en la nueva organización a través de la creación de un nuevo proyecto.

8.2 Creación de nuevo Proyecto

A continuación se detallan los pasos para la creación de un nuevo proyecto en la nueva organización jm77. En mi caso, el proyecto se llamará jm-inn-terraform-intro y será de ámbito privado:

1. Una vez introducido el nombre del nuevo proyecto y seleccionada la visibilidad o ámbito, presionar el botón + Create Project.

2. Nos aparecerá una pantalla similar a la mostrada más abajo en la que podemos empezar a trabajar en el proyecto, incluidos repos y pipelines:

8.3 Servicio de Conexión de Azure DevOps con Azure Cloud

En este apartado se explica como configurar nuestro proyecto jm-inn-terraform-intro para crear un servicio de conexión desde Azure DevOps a nuestra suscripción de Azure Cloud. Este servicio nos permite acceder a Azure Cloud desde Azure DevOps en el contexto de seguridad de un Service Principal, de modo que sea posible la ejecución de comandos de creación o modificación de infraestructura de Terraform según los permisos y roles asignados al Service Principal.

La creación del Service Principal ya fue explicada anteriormente en el apartado 4.2. Usaremos el mismo Service Principal ya creado anteriormente. 

Estos son los pasos para crear el servicio de conexión:

1. Acceder al proyecto y presionar en Project Settings: