Table of Contents
Infraestructura como código en Azure, el ARM
Hoy en día, la mayoría de los desarrolladores están familiarizados con el concepto de tener la infraestructura de nuestras aplicaciones y sistemas definida en un código descriptivo y controlado por versiones que genera el mismo entorno cada vez que se aplica. IaC resuelve el problema de tener diferentes entornos como el de desarrollo, el de puesta en escena y el de producción desincronizados, ya que, sin tener nuestra infraestructura definida como código, cada entorno tiene que ser mantenido manualmente, lo que suele conducir a la introducción de inconsistencias accidentales entre ellos, debido al error humano y al olvido general de aplicar siempre el mismo cambio en todos los entornos.
Durante mucho tiempo, la opción nativa para IaC en Azure era utilizar plantillas de Azure Resource Manager (ARM): el propio Resource Manager es un servicio de despliegue y gestión en Azure. Cada una de las opciones para gestionar recursos en Azure interactúa con el ARM, ya sea directamente (Azure Portal, SDKs, clientes REST), o indirectamente (Azure CLI, PowerShell – ambos usando el SDK, que luego llama al ARM).
Las plantillas ARM son esencialmente archivos JSON, que describen la infraestructura que se espera obtener después de aplicar la plantilla. Estos archivos JSON pueden extenderse con expresiones de plantilla, que aprovechan las funciones proporcionadas por el propio ARM. Cada plantilla puede contener las siguientes secciones:
- Parámetros (por ejemplo, diferentes valores para diferentes entornos)
- Variables (por ejemplo, construir valores a partir de un parámetro para un nombre de recurso, para reutilizar en la plantilla)
- Funciones definidas por el usuario
- Recursos
- Outputs
Cuando se despliega una plantilla ARM, el archivo JSON se convierte en una llamada a la API REST, dirigida al proveedor real del recurso en la plantilla – por ejemplo, si la plantilla ARM pretende crear un Registro de Contenedores, el espacio de nombres Microsoft.ContainerRegistry aparecerá en la URL, llamada por el Administrador de Recursos: https://management.azure.com/subscriptions/:subscriptionId/resourceGroups/:resourceGroupName/providers/Microsoft.ContainerRegistry/registries
Al ser una opción madura y nativa para Azure, el soporte de día cero para las nuevas características está garantizado: todas las características y capacidades del ARM estarán inmediatamente disponibles para el SDK y los clientes REST, sin embargo, podrían aparecer sólo meses más tarde en el Portal, y en las herramientas de IaC de terceros cuando sus desarrolladores o colaboradores de código abierto se pongan al día y lo implementen.
Terraform para Azure
En mi proyecto diario, decidimos no usar plantillas ARM, sino ir con Terraform en su lugar. Aparte de los beneficios de facto de tener todos los aspectos positivos cuando la infraestructura se define como código, Terraform ofrecía algunas ventajas sobre las plantillas ARM que nos convencieron de darle una oportunidad, en un entorno empresarial.
La sintaxis de Terraform es mucho más amigable y legible para el desarrollador. Mientras que la plantilla de ARM es sólo un largo archivo JSON, su legibilidad es bastante difícil, especialmente después de introducir parámetros, variables, funciones, referenciando salidas entre los recursos y así sucesivamente. Mientras que Terraform utiliza el propio lenguaje de configuración de HashiCorp (HCL), su simplicidad hace que sea fácil ponerse al día con él rápidamente, y luego escribir infraestructuras bastante sofisticadas todavía puede terminar en un formato bastante legible y fácil de seguir.
También teníamos curiosidad por la gestión de estados de Terraform, un concepto que no está muy presente en las plantillas de ARM. Otros equipos de la empresa ya utilizaban Terraform en producción con éxito, así que no había dudas sobre la madurez de la herramienta.
Aunque para nuestro caso de uso, el punto de que Terraform sea agnóstico al host (una sola configuración para ser desplegada en toda la nube, no sólo en Azure, sino en otros proveedores como AWS o Google Cloud) no era de consideración, sin embargo, podría ser uno de los argumentos más poderosos para que otros equipos lo avalen.
Dado que Terraform no es una herramienta nativa de Microsoft, tuvimos que aceptar el hecho de que el apoyo a las nuevas características de la ARM podría tomar un tiempo para aparecer en Terraform (aunque siempre hay soluciones, con el despliegue de plantillas reales de ARM a través de Terraform o simplemente ejecutando fragmentos de Azure CLI – ambos de los cuales deben apoyar todas las últimas características de la ARM). No pensamos que esto nos causaría dolores de cabeza, ya que nuestro proyecto no estaba usando las últimas características de Azure, de todos modos. Sin embargo, terminamos enfrentándonos a un problema, relacionado con una característica de ARM que faltaba en Terraform:
Nuestro backend, que se ejecuta en un App Service, tenía que aceptar certificados entrantes, de forma opcional (un servicio externo que llamara a una de nuestras APIs expuestas por el App Service, utilizando autenticación basada en certificados SSL, mientras que el resto de nuestros endpoints utilizaban OAuth 2, sin esperar ni validar ningún certificado entrante en las llamadas HTTP, sólo Tokens Web JSON).
En el portal de Azure, en la configuración del servicio de aplicaciones, ajustes generales, esto se puede establecer a través de un control:
Certificados de cliente entrantes Modo de certificado de cliente: Requerir | Permitir | Ignorar
Sin embargo, en Terraform, la propiedad client_cert_enabled era un simple booleano, que indicaba si se requería (true), o se ignoraba (false, el valor por defecto) el certificado entrante – necesitábamos la tercera opción inexistente.
Otro dolor de cabeza que hemos encontrado, que no es inherentemente culpa de Terraform, es cómo terminamos con un estado inconsistente en el archivo .tfstate de Terraform, y en los recursos de Azure realmente aprovisionados. Esto fue causado por nuestro equipo que no fue lo suficientemente riguroso como para que cada miembro se adhiriera al uso de Terraform para implementar cambios en nuestros recursos (en lugar de hacer «arreglos rápidos» manualmente).
Bicep
Personalmente, para mí, la mayor fortaleza de Terraform sobre las plantillas ARM es su sintaxis más concisa y legible. Debido a esto, inmediatamente sentí curiosidad por Bicep al escuchar su lanzamiento: Bicep es un lenguaje declarativo de código abierto, específico del dominio, que actúa como una capa sobre la plantilla ARM (inicialmente comparé esta relación con TypeScript y JavaScript). Fue creado y es mantenido en su mayor parte por Microsoft – debido a su naturaleza nativa a Azure, todas las características actuales y entrantes deberían ser soportadas inmediatamente por Bicep.
Siguiendo la recomendación de Microsoft, podemos crear archivos .bicep con VS Code, para aprovechar el intellisense, el resaltado de sintaxis y la validación de la extensión oficial Bicep (sin embargo, aún está en Preview).
Los archivos .bicep deben transpilarse en archivos JSON, para lo cual hay que instalar el grupo de comandos bicep Azure CLI: az bicep install. Curiosamente, la CLI contiene un comando de descompilación, que intenta convertir una plantilla JSON de ARM en un archivo Bicep.
La gestión del estado de Bicep es conceptualmente diferente a la de Terraform: mientras que Terraform utiliza archivos .tfstate para realizar un seguimiento del estado, Bicep consulta directamente desde Azure lo que está actualmente aprovisionado y obtiene las diferencias entre los recursos ya existentes y el estado deseado de los archivos Bicep localmente. Expone un comando what-if, que es similar al comando terraform plan de Terraform, ambos prediciendo los cambios a aplicar, sin ejecutar realmente ninguna modificación en los recursos vivos.
Por supuesto, hay algunos contras a tener en cuenta. Después de jugar con Bicep por un tiempo, la sintaxis todavía no se siente tan natural y fácil de escribir como la de Terraform. Personalmente, para mí, la sintaxis de Bicep encaja en algún lugar entre ARM y Terraform, pesando más hacia el lado de ARM de la escala. Teniendo en cuenta, que también son los primeros días de Bicep, implica que partes de la herramienta están cambiando rápidamente y no se siente tan maduro como Terraform, todavía.
Aunque el soporte para múltiples proveedores de nube no es algo que considere durante mis actividades diarias en mi proyecto actual, es importante notar que a diferencia de Terraform, Bicep está limitado a Azure solamente.
Aprovisionamiento de un servicio de App: Bicep
Para comparar la experiencia de utilizar Bicep frente a Terraform, en los siguientes ejemplos se aprovisionará un App Service Plan y un App Service, a través de ambas herramientas.
El archivo .bicep simple tiene el siguiente aspecto:
param skuName string = 'F1'
param skuCapacity int = 1
param location string = resourceGroup().location
var appServicePlanName = 'asp-demo1'
var webSiteName = 'app-demo1'
resource appServicePlan 'Microsoft.Web/serverfarms@2020-06-01' = {
name: appServicePlanName
location: location
sku: {
name: skuName
capacity: skuCapacity
}
}
resource appService 'Microsoft.Web/sites@2020-06-01' = {
name: webSiteName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
siteConfig: {
minTlsVersion: '1.2'
}
}
}
Al echar un vistazo al contenido de la plantilla, me saltó inmediatamente una pequeña molestia: el espacio de nombres del recurso de Azure. Al escribir «resource appService», imagino que la mayoría de los desarrolladores esperarían que el proveedor de App Service Plan viviera bajo un espacio de nombres que contenga las palabras «app», «service» y «plan» – desafortunadamente, los espacios de nombres a menudo no coinciden con el nombre del recurso. En este caso, se encuentra bajo el espacio de nombres Web, y se llama serverfarms. Un escenario similar se puede observar en el recurso appService también, en ese caso, el espacio de nombres es Microsoft.Web/sites. Al menos por coherencia, la referencia dentro del App Service al App Service Plan se hace a través de una propiedad llamada serverFarmId. Desafortunadamente, el intellisense de la extensión Bicep no es tan útil para casos como este:
La aplicación de los archivos .bicep se puede hacer de forma similar a las plantillas ARM – mi forma preferida de hacerlo, a través del comando az deployment de la CLI de Azure. Usando la opción what-if de Bicep, podemos ejecutar lo siguiente, para tener una idea de los cambios que se aplicarán en nuestro grupo de recursos de Azure, sin ejecutar realmente el despliegue:
az deployment group what-if \
--name DemoDeployment1 \
--resource-group myResourceGroupName \
--template-file test.bicep
La salida del comando se parece bastante a la salida del comando terraform plan de Terraform:
Resource and property changes are indicated with these symbols:
+ Create
* Ignore
The deployment will update the following scope:
Scope: /subscriptions/:subscriptionId/resourceGroups/:resourceGroupId
+ Microsoft.Web/serverfarms/bicep-demo1-asp [2020-06-01]
apiVersion: "2020-06-01"
id: "/subscriptions/:subscriptionId/resourceGroups/:resourceGroupId/providers/Microsoft.Web/serverfarms/bicep-demo1-asp"
location: "westeurope"
name: "bicep-demo1-asp"
sku.capacity: 1
sku.name: "F1"
type: "Microsoft.Web/serverfarms"
+ Microsoft.Web/sites/bicep-demo1-app [2020-06-01]
apiVersion: "2020-06-01"
id: "/subscriptions/:subscriptionId/resourceGroups/:resourceGroupId/providers/Microsoft.Web/sites/bicep-demo1-app"
identity: "*******"
location: "westeurope"
name: "bicep-demo1-app"
properties.httpsOnly: true
properties.serverFarmId: "/subscriptions/:subscriptionId/resourceGroups/:resourceGroupId/providers/Microsoft.Web/serverfarms/bicep-demo1-asp"
properties.siteConfig: "*******"
type: "Microsoft.Web/sites"
Al eliminar la parte del comando what-if, el despliegue se lleva a cabo. Después, si cambiamos una configuración (por ejemplo, el nivel gratuito a un nivel estándar del plan de servicio de aplicaciones), el comando what-if genera lo siguiente:
Resource and property changes are indicated with these symbols:
+ Create
~ Modify
* Ignore
The deployment will update the following scope:
Scope: /subscriptions/:subscriptionId/resourceGroups/:resourceGroupId
~ Microsoft.Web/serverfarms/bicep-demo1-asp [2020-06-01]
~ sku.capacity: 0 => 1
~ sku.name: "F1" => "S1"
~ Microsoft.Web/sites/bicep-demo1-app [2020-06-01]
+ properties.siteConfig.localMySqlEnabled: false
+ properties.siteConfig.minTlsVersion: "1.2"
+ properties.siteConfig.netFrameworkVersion: "v4.6"
Dado que Bicep no rastrea el estado localmente, si cambiáramos el nombre del App Service después del despliegue, el App Service original seguiría en línea, y se aprovisionaría un segundo con el nuevo nombre – más adelante, veremos cómo en Terraform, esto generaría un plan «1 delete, 1 create», en lugar de un «1 create» solamente.
Aprovisionar un servicio de aplicación: Terraform
Un App Service Plan y App Service, con las mismas variables y extracciones de parámetros se vería así, como un archivo Terraform:
variable "skuTier" {
type = string
default = "Free"
}
variable "skuSize" {
type = string
default = "F1"
}
variable "location" {
type = string
default = "westeurope"
}
variable "resource_group_name" {
type = string
}
locals {
appServicePlanName = "bicep-demo1-asp"
webSiteName = "bicep-demo1-app"
}
resource "azurerm_app_service_plan" "AppServicePlan" {
name = local.appServicePlanName
location = var.location
resource_group_name = var.resource_group_name
kind = "app"
sku {
size = var.skuSize
tier = var.skuTier
}
}
resource "azurerm_app_service" "AppService" {
depends_on = [
azurerm_app_service_plan.AppServicePlan]
name = local.webSiteName
location = var.location
resource_group_name = var.resource_group_name
app_service_plan_id = azurerm_app_service_plan.AppServicePlan.id
https_only = true
site_config {
min_tls_version = "1.2"
}
identity {
type = "SystemAssigned"
}
}
La manera de Terraform de definir las variables es un poco más verbosa que la de Bicep, sin embargo, en un proyecto del mundo real, probablemente los parámetros de entrada se extraerían en un archivo variables.tf, mientras que las variables de salida en output.tf, como:
app_service_plan
→ main.tf
→ variables.tf
→ output.tf
Podemos ver que los nombres de los proveedores son un poco más fáciles de adivinar.
Para imitar el comando «what-if» de Bicep, podemos utilizar el plan de terraformación:
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# azurerm_app_service.AppService will be created
+ resource "azurerm_app_service" "AppService" {
+ app_service_plan_id = (known after apply)
+ app_settings = (known after apply)
+ location = "westeurope"
+ name = "bicep-demo1-app"
+ auth_settings {
+ additional_login_params = (known after apply)
+ allowed_external_redirect_urls = (known after apply)
+ active_directory { ... }
+ facebook { ... }
+ google { ... }
+ microsoft { ... }
+ twitter { ... }
...
}
+ identity {
+ principal_id = (known after apply)
+ tenant_id = (known after apply)
+ type = "SystemAssigned"
}
+ site_config {
+ dotnet_framework_version = "v4.0"
+ min_tls_version = "1.2"
...
}
...
}
# azurerm_app_service_plan.AppServicePlan will be created
+ resource "azurerm_app_service_plan" "AppServicePlan" {
+ id = (known after apply)
+ kind = "app"
+ location = "westeurope"
+ maximum_elastic_worker_count = (known after apply)
+ maximum_number_of_workers = (known after apply)
+ name = "bicep-demo1-asp"
+ sku {
+ capacity = (known after apply)
+ size = "F1"
+ tier = "Free"
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
Con terraform apply, podemos ejecutar los cambios y ver que los dos recursos se aprovisionan en nuestro grupo de recursos.
Como Terraform gestiona el estado localmente, si cambiamos el nombre del App Service, en lugar de dejar el actual como está e intentar crear sólo uno nuevo, Terraform muestra lo siguiente, durante el plan de terraformación:
Plan: 1 to add, 0 to change, 1 to destroy.
Conclusión
Después de pasar unos días investigando Bicep, leyendo la documentación y viendo demostraciones, siento que a partir de hoy, sería un fuerte contendiente para las personas que están familiarizadas con ARM ya, pero tal vez no con otras herramientas de IaC, como Terraform. Definitivamente se siente más fácil de usar que escribir plantillas puras de ARM, sin embargo, probablemente no voy a lanzar la idea a mi equipo para reescribir nuestro código Terraform a Bicep.
Dado que aún es pronto para la herramienta, también están presentes los retos habituales que conlleva la nueva tecnología: al ver hoy una presentación de hace seis meses, los comandos pueden haber cambiado un poco en ese corto periodo de tiempo.
La documentación de Bicep es fácil de seguir y bastante detallada, sin embargo, no tiene una sección de referencia tan completa como Terraform – el mejor lugar que pude encontrar fue el Bicep Playground, donde se pueden encontrar plantillas de ejemplo.
Referencias
Proveedores de recursos de Azure: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-providers-and-types
Lista de proveedores de recursos de Azure: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-services-resource-providers
Visión general de Bicep: https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview
Instalar Bicep: https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/install
Guía de inicio rápido de Bicep: https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/learn-bicep
Comando de bíceps what-if: https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/deploy-what-if?tabs=azure-powershell%2CCLI
Despliegue con Bicep y Azure CLI: https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/deploy-cli
Bicep Playground: https://bicepdemo.z22.web.core.windows.net/
Terraform para Azure: https://learn.hashicorp.com/collections/terraform/azure-get-started
Terraform proveedor de Azure: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
Author
-
Full stack developer, with 6+ years of experience, building back-, and frontends with various technologies, designing and developing scalable infrastructures on Microsoft Azure.
Ver todas las entradas