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:

2. Desplazarse hasta la sección de menú Pipelines y seleccionar Service connections:

3. Pulsar en el botón Create service connection:

4. Vamos a crear una nueva conexión de tipo Azure Resource Manager de modo que puedes introducir dicho texto en el filtro de búsqueda de tipos de conectores, seleccionar Azure Resource Manager y pulsar en Next:

5. Ahora se requiere seleccionar el tipo de autenticación que usaremos para configurar nuestro servicio de conexión. Como puede verse en la figura de abajo hay varias opciones:

  • La opción recomendada es la creación de un nuevo Service principal (automatic), es decir, Azure DevOps creará el service principal de forma automática por nosotros y le concederá los roles necesarios (contributor a nivel de suscripción).
  • La opción Service principal (manual) es la más adecuada en nuestro caso ya que hemos creado y utilizado previamente un Service Principal destinado precisamente a servir como objeto de autenticación y concesión de permisos vía RBAC para gestionar recursos de Azure.
  • El resto de opciones quedan fuera del alcance de este artículo. 

Así pues, seleccionamos Service principal (manual) y pulsamos en Next

6. Configuramos las distintas secciones del servicio de conexión:

  • Dejar las selecciones por defecto en Environment y Scope Level 
  • Introducir el identificador y nombre de la suscripción sobre la cual se creó el Service Principal y sobre la cual desplegaremos los recursos en Azure Cloud

7. Completar los datos de autenticación con los datos del Service Principal. Puedes ver cómo obtener estos datos en el apartado 4.2. No obstante, a modo recordatorio puedes acceder a los datos del Service Principal en Azure Cloud de los siguientes modos:

- A través del portal de Azure:

  • Selecciona Home -> Azure Active Directory -> App Registrations
  • Filtra por el nombre del Service Principal en mi caso, jm-inn-terraform-sp y pulsa en él
  • Accederás a una pantalla similar a la siguiente donde puedes ver toda la información requerida:

A través de Azure Cloud Shell:

  • az account list te permite visualizar la lista de tus subscripciones activas.
  • az account set --subscription "YOUR_SUBSCRIPTION_NAME", en mi caso, az account set --subscription "Visual Studio Enterprise Subscription" te permite fijar la suscripción por defecto sobre la cual ejecutar el resto de comandos.
  • az ad sp show --id "YOUR_SERVICE_PRINCIPAL_NAME", en mi caso,  az ad sp show --id "http://jm-inn-terraform-sp" te permite visualizar toda la información asociada al Service Principal.
  • Si además quieres resetear el password puedes ejecutar el siguiente comando: az ad sp credential reset --name "YOUR_SERVICE_PRINCIPAL_NAME" --password "YOUR_PASSWORD".

Una vez recopilados los datos anteriores de una forma u otra, completar la sección de datos de autenticación y pulsar en Verify (*):

(*) Service Principal Id debe completarse con la información de Application Id.

(*) Service principal key debe completarse con el password asociado al Service Principal.

Si la conexión pudo establecerse de forma correcta deberías ver un mensaje similar al siguiente:

8. Rellenar los datos de la sección Details con el nombre que queremos dar a nuestra conexión (Service connection name), en mi caso jm-inn-terraform-sp-azure-cnn

9. Chequear Grant access permission to all pipelines en Security para conceder acceso a todas las pipelines

10. Por último, pulsar en Verify and Save:

Este será el servicio de conexión que usaremos en nuestras pipelines de Azure DevOps para conectar con Azure Cloud y ejecutar los comandos de Terraform.

8.4 Creación de nuevo Repositorio en Azure DevOps

El siguiente paso antes de crear nuestras pipelines de build y release es la creación de un nuevo repositorio Git, sobre el cual ejercer el control de las versiones de nuestro código HCL de Terraform.

El objetivo final es que nuestras pipelines sean capaz de detectar cualquier commit en el repositorio de modo que se desencadenen de forma automática los procesos de build y release para desplegar los cambios de infraestructura introducidos en el fichero de configuración de Terraform main.tf.

Si accedemos al menú de Repos en nuestro proyecto de Azure DevOps podemos ver como existen diferentes modos de crear nuestro repositorio:

1. Clonar directamente a nuestro equipo 

Puedes obtener ayuda adicional en Fundamentos de Git - Obtener repositorios GIT

2. Generar el repositorio desde la línea de comandos de Git mediante un Push:

Puedes obtener ayuda adicional en Fundamentos de Git - Trabajar con repositorios remotos

3. Importar un repositorio existente

4. Inicialización básica mediante la creación de un fichero README o gitignore

En mi caso, he utilizado la primera opción mediante el botón Clone in VS Code para generar el repositorio ya que también he usado Visual Studio Code para trabajar en modo "local" en mi máquina Windows tal y cómo se explicó en el Capítulo 7. Una vez ejecutamos esa opción, Azure DevOps solicita permisos de acceso mediante un popup y nos pide el directorio sobre el cual crear nuestro repositorio Git local. Una vez creado el repositorio local podemos añadir un fichero README.MD con una breve descripción sobre nuestro proyecto y el fichero main.tf:

Incluidos estos dos ficheros, ya podemos hacer un commit y un push a nuestro repositorio remoto:

Con esto quedaría concluida la creación y configuración de nuestro repositorio en Azure DevOps.

8.5 Extensiones Terraform en Azure DevOps 

La única extensión que utilizaré en este proyecto de Azure DevOps es Terraform Build & Release Tasks by Charles Zipp:

Puedes utilizar el botón Marketplace (vía Organization Settings -> Extensions) para buscar esta extensión e instalarla de forma gratuita.

8.6 Azure DevOps Build Pipeline

En este apartado vamos a crear nuestra primera Azure DevOps Build Pipeline

1. Seleccionamos el menú Pipelines -> Pipelines en nuestro proyecto jm-inn-terraform-intro y a continuación el botón New Pipeline situado en la esquina superior derecha. Nos aparece una pantalla en la que se muestran los diferentes pasos para configurar la pipeline:

2. En el primer paso Connect se debe seleccionar donde está el código sobre el cual generar la build. Seleccionamos Use the classic editor to create a pipeline without YAML situado al final de la lista de opciones.

3. Seleccionamos Azure Repos Git como source y rellenamos el resto de campos con la información de nuestro proyecto, repositorio y rama. Una vez completos, pulsamos en Continue:

4. Como template para generar la build, seleccionamos Empty job.

5. De forma opcional pulsamos en Agent job 1 y modificamos el Display name del agente.

6. Pulsamos en + para añadir tareas al job.

7. Añadimos Copy files como primera tarea:

8. Configuramos la tarea Copy files del modo mostrado a continuación. Copiaremos en el subdirectorio Output todos los archivos con extension ".tf" y ".md":

9. Añadimos una nueva tarea de tipo Publish Artifact: drop y la configuramos del modo siguiente:

Una vez creada la build el artefacto drop será almacenado en Azure Pipelines y tendrá un contenido similar al siguiente:

10. En el menú Triggers habilitar Enable continuous integration:

11. Click en Save & queue para guardar y lanzar nuestra Build Pipeline. Si todo va bien, deberías ver una pantalla similar a la siguiente:

12. De forma opcional puedes pulsar en Agent job 1 en la sección Jobs para acceder a los logs de la Build y obtener los detalles del proceso así como al artifact generado:

Estos serían todos los pasos para generar nuestra Build Pipeline y por tanto, ya estamos preparados para generar la Release Pipeline.

8.7 Azure DevOps Release Pipeline

En este apartado crearemos nuestra Release Pipeline usando como entrada el artifact generado en la Build Pipeline del apartado anterior 8.6.

Nuestra Release Pipeline estará formada por las siguientes tasks:

  • Terraform Installer -> Instalación de la versión requerida de Terraform
  • Terraform CLI -> Ejecución del comando Terraform Init
  • Terraform CLI -> Ejecución del comando Terraform Validate
  • Terraform CLI -> Ejecución del comando Terraform Plan
  • Terraform CLI -> Ejecución del comando Terraform Apply

Todas las tareas están soportadas en la extensión de Azure DevOps Terraform Build & Release Tasks by Charles Zipp explicada en el apartado 8.5 de este post.

En este punto vamos a empezar mostrando los diagramas finales de nuestra Release Pipeline jm-inn-terraform-intro-CD.

1. Diagrama general final:

2. Diagrama de tareas del Stage denominado Deployment with Terraform que contiene la instalación y los comandos de Terraform:

3. Definición de variables de la Release Pipeline. Serán inyectadas como variables de entorno en los comandos de Terraform:

Antes de continuar, tan sólo mencionar que la variable TF_VAR_sp_client_secret podría ser sustituida por otra de igual valor con nombre ARM_Client_Secret. En este último caso no haría falta emplear la variable interna sp_client_secret de nuestro fichero main.tf. El valor del atributo client_secret asociado al Service Principal sería tomado directamente de la variable ARM_Client_Secret. No obstante, he preferido seguir con la definición de variable interna como en el resto de escenarios anteriores y así mostrar cómo asignar valores a dichas variables.

Configuración de la sección Artifacts

Ahora mostraremos cómo crear cada uno de los componentes anteriormente mostrados en la sección Artifacts:

1. Crear nueva Release Pipeline haciendo click en el botón New Pipeline vía menú Pipeline -> Release y dar un nombre adecuado, en mi caso jm-inn-terraform-intro.CD.

2. Seleccionar Empty Job como template.

3. Pulsar en Add an artifact en la sección Artifacts, seleccionar el botón Build y configurar la fuente o Source con nuestra Build Pipeline.

4. Pulsar en el icono de lighting (en verde en la imagen de abajo) y chequear la opción Creates a release every time a new build is available. De este modo activamos la opción de Continuous Deployment (CD).

 

5. Pulsar en el menú Save para grabar los cambios en la Release Pipeline.

Configuración de la sección Stages

Ahora vamos a mostrar los pasos para configurar las tareas incluidas en nuestro único Stage de la sección Stages.

1. Click en Stage1 y asigna un nuevo nombre más descriptivo como por ejemplo, Deploying with Terraform.

2. Pulsa en el link 1 job, 0 task para comenzar a añadir tareas al stage.

3. Pulsa en el icono + para añadir la tarea Terraform Installer. Deja los valores por defecto que aparecen en el formulario. Esta tarea instalará la versión específica de Terraform antes de iniciar la Release Pipeline.

4. Pulsa en el icono + para añadir la tarea Terraform CLI. Configura la tarea para ejecutar el comando terraform init del modo siguiente:

 

Fíjate en la asignación de los siguientes campos:

  • Command: init. Comando de Terraform a ejecutar.
  • Configuration Directory: directorio donde se encuentra nuestro módulo de Terraform dentro del artefacto generado por la Build Pipeline.
  • Backend Type: azurerm. En nuestro caso Azure Resource Manager o azurerm.
  • Backend Azure Subscription: servicio de conexión de Azure DevOps con Azure Cloud descrito en el apartado 8.3. Utiliza el Service Principal como contexto de seguridad para la creación o modificación de infraestructura.
  • Resource Group Name: grupo de recursos que contiene la Storage Account del recuadro inferior.
  • Storage Account Name: cuenta de almacenamiento en Azure del container del recuadro inferior.
  • Container Name: contenedor que almacena en un blob el fichero de configuración de estado de Terraform.
  • Key: nombre del blob que almacena el fichero de configuración de estado de Terraform.

Los valores secretos de access_key para acceder al container y client_secret para hacer uso del Service Principal son pasados en las variables ARM_Access_Key y TF_VAR_sp_client_secret respectivamente.

5. Pulsa en el icono + para añadir la tarea Terraform CLI. Configura la tarea para ejecutar el comando terraform validate del modo siguiente:

6. Pulsa en el icono + para añadir la tarea Terraform CLI. Configura la tarea para ejecutar el comando terraform plan del modo siguiente:

La variable interna sp_client_secret es asignada a la variable externa con scope de tipo pipeline vía línea de comandos (parámetro -var). Sin esta línea, parece que la extensión instalada de Azure DevOps no es capaz de resolver la asignación automática según la convención de nombres de Terraform. No obstante, puedes enviarme un comentario si dispones de información adicional. 

7. Pulsa en el icono + para añadir la tarea Terraform CLI. Configura la tarea para ejecutar el comando terraform apply del modo siguiente:

Ejecución de la Release Pipeline

Llegados a este punto, ya estamos preparados para ejecutar nuestra Release Pipeline. 

1. Pulsa en el botón Create Release y a continuación en el botón Create. La Release Pipeline se iniciará e irá informando sobre la ejecución de cada tarea en cada stage.

2. Si el despliegue funcionó de forma correcta deberías ver una imagen similar a la siguiente en la sección Stage de la Release Pipeline:

Si pulsas en el recuadro puedes acceder a la información de log del Stage Deployment with Terraform:

Pulsando en cada tarea puedes acceder a la información de log de detalle de la misma.

3. Finalmente puedes probar a modificar el fichero main.tf, hacer commit y push al repositorio remoto y verificar que nuestras pipelines funcionan correctamente.

Referencias

 

Add comment