Azure: Infrastructure as Code con Bicep
Tags: infrastructure, azure, bicep, iac
Published: 2022-03-08 16:04:41

## Introducción

En esta ocasión estoy inaugurando una nueva serie de posts que tratarán principalmente de Azure e infraestructura. Como primer post de esta serie hablaremos de Infrastructure as Code y que mejor con las herramientas provistas por Microsoft.

Bicep tiene sus competidores como Terraform (declarativo) o Pulumi (imperativo) y son muy buenos. Muchas empresas ya tienen experiencia con esta clase de productos, pero si eres nuevo en Azure y aun no te has decidido cual elegir, te recomiendo que veas este post y lo intentes.

Bicep es una muy buena herramienta y tiene el soporte 100% de Microsoft y la comunidad open source, integrado totalmente con Azure (y sí, solo para Azure).

## ¿Qué es Bicep?

Bicep es un lenguaje de dominio específico (significa que solo sirve para una cosa) que usa una sintaxis declarativa para crear específicamente recursos en Azure. En Bicep, se puede definir la infraestructura que queremos crear en Azure, y este mismo archivo de bicep lo podemos usar en todo nuestro ciclo DevOps como si fuera cualquier otro lenguaje, para al final terminar deployeado en Azure.

Es decir, así como creamos aplicaciones con C# y existe un CI/CD, con bicep también se puede hacer eso. Pruebas de integración, distintas validaciones y al final, se deployea en azure (lo cual significa, cambios en la infraestructura).

### Beneficios de usar Bicep

Bicep nos da las siguientes ventajas:

- **Soporte para todos los tipos de recursos en Azure**: Bicep inmediatamente soporta todos los recursos existentes en Azure, si lo puedes crear desde el portal, lo puedes crear con bicep. Con algunas otras herramientas IaC (Infrastructure as Code) a veces hay que esperar para que se soporte nuevas funcionalidades.
- **Sintaxis simple**: Al usar ARM templates, siempre se usan archivos JSON que suelen ser muy complicados de leer. Un template escrito en Bicep nos da una forma muy sencilla de leer. Bicep para nada requiere conocimientos en otros lenguajes, es declarativo y así se especifican los recursos que se quieren deployear en azure.

El siguiente ejemplo muestra la diferencia entre crear un recurso con un tradicional JSON y uno con Bicep.

JSON:

```json
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    },
    "storageAccountName": {
      "type": "string",
      "defaultValue": "[format('toylaunch{0}', uniqueString(resourceGroup().id))]"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-06-01",
      "name": "[parameters('storageAccountName')]", 
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "StorageV2",
      "properties": {
        "accessTier": "Hot"
      }
    }
  ]
}
```

Bicep:

```dart
param location string = resourceGroup().location
param storageAccountName string = 'toylaunch${uniqueString(resourceGroup().id)}'

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    accessTier: 'Hot'
  }
}
```

- **Buen intellisense**: Cuando usas VSCode y la extensión de [Bicep](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep) para crear tus templates, obtienes una experiencia de desarrollo de primer nivel igual que los lenguajes más populares. VS Code nos da un intellisense que nos librará de errores y por supuesto, de escribir mucho.
- **Idempotencia**: Repetidamente podemos deployear nuestra infraestructura en el ciclo de desarrollo y tendremos la seguridad de que nuestros recursos serán consistentes. Ya que Bicep realiza los deploy de una manera idempotente, quiere decir que podemos deployear el mismo archivo bicep varias veces y obtendremos los mismos recursos y en el mismo estado. Podemos desarrollar un solo bicep template que representa toda la infraestructura y el estado deseado, y no tener varios templates con actualizaciones de la infraestructura.
- **Orchestration**: No nos preocupamos en la complejidad de todas las operaciones que se tienen que realizar para deployear la infraestructura. El Resource Manager de Azure organiza todas las operaciones según las dependencias que estos tengan (por ejemplo, una base de datos depende de un servidor sql, o un Azure Function depende de un Storage Account). Cuando es posible, el Resource Manager deployea recursos de forma paralela y así puede terminar más rápido.
- **Es Modular**: Si nuestra infraestructura es enorme, podemos romper nuestro template en módulos. Un módulo puede deployear un conjunto de recursos relacionados. También los módulos nos permiten reutilizar código para simplificar el desarrollo.
- **Integración con Azure**: Bicep está totalmente integrado con Azure.
- **Previsualización de cambios**: Podemos utilizar la operación [what-if](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/deploy-what-if?tabs=azure-powershell%2CCLI) para poder ver un preview de los cambios que se van a realizar antes de ejecutar cualquier template bicep. Con *what-if,* podemos ver que recursos serán creados, actualizados, o eliminados, y hasta cualquier propiedad que se haya actualizado.
- **Sin costo y open source:** Bicep es totalmente gratis. No se tiene que pagar por algún plan premium. Totalmente mantenido por Microsoft.

### Instalando Bicep

Instalar y empezar a usar bicep es muy sencillo, esta [guía lo resume muy bien](https://docs.microsoft.com/es-mx/azure/azure-resource-manager/bicep/install), pero, en resumen:

- Tener Azure CLI instalado, con sesión iniciada a una suscripción de Azure
- Visual Studio Code
- La extensión de Bicep

## Creando infraestructura en Azure con Bicep

Al tener ya VS Code y Bicep instalado, creamos un archivo llamado `main.bicep` para comenzar a escribir código, comienza escribiendo solo `vnet` y te debe de salir el siguiente auto-complete:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kiklflimnnbmu10kueuy.png)

La idea es aprovechar al máximo las herramientas que VS Code nos ofrece para hacer más efectiva y sencilla la experiencia al escribir los templates con bicep.

Si seleccionamos `res-vnet` nos creará todo lo necesario para crear una Virtual Network.

> Nota 💡: Si no ves está opción de auto-complete en tu VS Code, revisa que tengas instalada la extensión correctamente. Si tienes todo instalado, al abrir VS Code y un archivo bicep, puede que tarde un poco en que cargue todo lo necesario para funcionar.

Por lo tanto, tendremos el siguiente código de una VNet:

```dart
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
  name: 'name'
  location: resourceGroup().location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'Subnet-1'
        properties: {
          addressPrefix: '10.0.0.0/24'
        }
      }
      {
        name: 'Subnet-2'
        properties: {
          addressPrefix: '10.0.1.0/24'
        }
      }
    ]
  }
}
```

Como comenté anteriormente, este snippet contiene todos los valores mínimos para poder crear una Virtual Network dentro de Azure. Pero sin problema, se puede modificar este fragmento para que cumpla con cualquier necesidad que tengamos. Por ejemplo, `name` no es un nombre muy adecuado para una virtual network. Lo cambiaremos por `examplevnet` (tampoco tan bueno, pero es un ejemplo 🤭).

```dart
name: 'examplevnet'
```

Este template ya se puede deployear en azure, pero es mejor dejar los templates configurables con parámetros y tal vez más legibles utilizando variables u otras cosas que Bicep provee. También agregaremos un Storage Account.

### Agregando parámetros

Ahora agregaremos un parámetro para un StorageAccount. Al inicio del archivo agregamos lo siguiente:

```dart
param storageName
```

Cuando agregamos un espacio después de `storageName`, fíjate que el intellisense nos está ayudando con los tipos de datos que bicep soporta. Selecciona `string`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovggll6i73lla5jq5y1s.png)

Entonces, tendremos el siguiente parámetro: `param storageName string`

Este parámetro, así como está, funciona bien, pero los storage accounts en azure tiene límites en su nombre. El nombre debe de tener mínimo 3 carácteres y máximo 24. Podemos agregar esas reglas utilizando decorators en el parámetro.

```dart
@minLength(3)
@maxLength(24)
param storageName string
```

Tambíen, podemos agregar una descripción al parámetro para que sea útil al deployear los recursos y saber que se está solicitando.

```dart
@minLength(3)
@maxLength(24)
@description('Ingresa un nombre para el Storage Account. Solo letras en minúsculas y números. El nombre debe ser único en todo Azure.')
param storageName string
```

### Agregando recursos

En lugar de usar un snippet como ahorita, vamos a utilizar el auto-complete del editor para ir agregando los valores necesarios para crear un storage account.

Para definir un recurso en general, se usa la palabra reservada `resource`. Debajo del virtual network creado, agregaremos el recurso `resource exampleStorage`.

Cuando escribes lo anterior, da un “espacio” después del nombre del recurso y automaticamente el intellisense nos dará las posibles opciones de recursos que podemos crear:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xvu1nyxidqdo4uri5zmq.png)

Después de seleccionar **Microsoft.Storage/storageAccounts**, se nos darán las posibles versiones de APIs. Siempre uso la última o la default.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ulqp13o1dquuruqrqj17.png)

Despues de seleccionar la versión, da un “espacio” y selecciona el simbolo igual `=` y te saldrá las siguientes opciones:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ulhm08rqtzi2sqcom0c9.png)

Selecciona la opción `required-properties` para que nos auto-complete todas las opciones mínimas requeridas para crear un Storage Account.

```dart
resource exampleStorage 'Microsoft.Storage/storageAccounts@2021-08-01' ={
  name: 
  location: 
  sku: {
    name: 
  }
  kind: 
}
```

Ya casi terminamos, solo hay que agregar los valores que nos pide y listo.

Nuevamente, usar intellisense es de vital importancia, si no, no estamos sacando el provecho completo a Bicep. Explora cómo funciona, hazlo lento y verás que intellisense lo hace muy, pero muy bien.

Terminando con el Storage Account, el archivo bicep se ve así:

```dart
param storageName string

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
  name: 'examplevnet'
  location: resourceGroup().location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
    subnets: [
      {
        name: 'Subnet-1'
        properties: {
          addressPrefix: '10.0.0.0/24'
        }
      }
      {
        name: 'Subnet-2'
        properties: {
          addressPrefix: '10.0.1.0/24'
        }
      }
    ]
  }
}

resource exampleStorage 'Microsoft.Storage/storageAccounts@2021-08-01' ={
  name: storageName
  location: 'eastus'
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
}
```

### Visualiza los recursos

Para visualizar los recursos, hay que dar click al siguiente botón que se encuentra en la esquina superior derecha:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uwk8j60qo6ip2waseg2b.png)

El visualizador nos permite ver las dependencias que existen entre recursos y si está organizado por módulos, se ven estos de una forma agrupada. Aunque este ejemplo no contiene ninguna dependencia ya que solo es un ejemplo.

### Deployea el archivo Bicep

Para deployear este archivo Bicep en nuestra suscripción de Azure, debemos de tener instalado Azure CLI y ya haber iniciado sesión.

Utilizando la consola, nos iremos a la carpeta donde se encuentra el template y escribimos lo siguiente:

```bash
az group create --name exampleRG --location eastus

az deployment group create --resource-group exampleRG --template-file main.bicep --parameters storageName=uniquename
```

Al ejecutar el segundo comando, si el `storageName` está bien, ya se debería de estar viendo el progreso del deployment de la infraestructura desde el portal de Azure.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxfn2nwxbue4se6bhqh4.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/362344cl087e8hyidris.png)

Una vez que la consola regresa un estatus exitoso, veremos que en el resource group creado anteriormente, está la infraestructura ya creada justo como se declaró en bicep.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4wwyetpgo3dad0789uuf.png)

Si este template lo volvemos a deployear, no habrá ninguna diferencia, ya que como se mencionó al inicio, estos deployments son idemponentes, es decir, no cambian o alteran el estado si este ya se encuentra como está definido.

Si actualizamos el template, como agregar más recursos y deployeamos el template, solo se agregarán los templates faltantes.

Esto es perfecto para tener una integración continua con algún pipeline como Azure DevOps o GitHub actions (que valdrá la pena hablar de eso más adelante).

### Limpia los recursos

Siempre es importante limpiar recursos cuando haces este tipo de pruebas, no queremos que existan cargos no deseados en nuestra suscripción de Azure.

Para eliminar los recursos, usamos el siguiente comando:

```bash
az group delete --name exampleRG
```

## Conclusión

Tener tu infraestructura “programada” tiene sus grandes ventajas, más cuando tu infraestructura está en constante crecimiento y evolución. Tener una seguridad de que los cambios se harán con certeza y tal como se diseñó con Bicep, es de gran valor para muchos proyectos.

## Contenido recomendado

- [Monitorea y diagnostica deployments con bicep](https://docs.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/quickstart-troubleshoot-bicep-deployment?tabs=azure-cli)
- [Crea y deployea template specs con bicep](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/template-specs?tabs=azure-powershell)
- [Configuración del linter para Bicep](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-config-linter)
- [Crea múltiples instancias de recursos con Bicep](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/quickstart-loops?tabs=CLI)

## Referencias

- [Bicep file structure and syntax - Azure Resource Manager | Microsoft Docs](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/file)
- [Deploy Bicep files by using GitHub Actions - Azure Resource Manager | Microsoft Docs](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/deploy-github-actions?tabs=CLI)
- [Exercise - Define resources in a Bicep template - Learn | Microsoft Docs](https://docs.microsoft.com/en-us/learn/modules/build-first-bicep-template/4-exercise-define-resources-bicep-template?pivots=cli)