ICM / terraform / Terraform. Tfstate remoto para trabajo en equipo.

Terraform. Tfstate remoto para trabajo en equipo.

6 octubre 2022 | Jesús Martínez

Terraform es un herramienta de gestión y orquestación de infraestructura. Está desarrollada por Hashicorp y nos permite definir y gestionar nuestra infraestructura como código.

Utilidad de terraform

En el pasado hemos hablado de terraform en múltiples entradas. Es una herramienta fantástica que llevamos utilizando en ICM, para el despliegue y la administración de infraestructura, desde hace algunos años y que se complementa de una forma fantástica con ansible. De esta forma tenemos toda nuestra infraestructura documentada, descrita claramente en ficheros .tf y mediante la utilización de un repositorio git podemos ver como ha ido evolucionando a lo largo del tiempo.

Funcionamiento habitual de Terraform

El funcionamiento habitual de terraform es utilizando un fichero de estado, el famoso tfstate. Este fichero es un json en el que se almacena toda la infraestructura desplegada a través de los apply que se vayan lanzando. Este fichero es comparado con la infraestructura desplegada en la cuenta o subscripción en la que se esté trabajando y así terraform sabe qué altas, bajas o modificaciones tiene que hacer en el proceso de apply.

Pero ¿Y si trabajamos en equipo?

Cuando existe un equipo que trabaje en una infraestructura, el fichero tfstate debe ser global y no puede generarse por defecto en el disco local, ya que podrían producirse discordancias o generar conflictos entre dos deploys diferentes. Para evitarlo debemos asegurarnos que el tfstate es remoto y global para trabajo en equipo. En este artículo os mostraremos como poder configurar un tfstate externo sobre una cuenta de almacenamiento de Azure.

Cómo crear un tfsate remoto para trabajo en equipo

Vamos a preparar en una subscripción de Azure una nueva storage account:

# Hacemos login en el nuestro tenant
az login --tenant XXXXXXXXXXXXXXXX

# Seleccionamos la subscripción que queramos utilizar para crear la storage account
az account set --name YYYYYYYYYYYYYYYYYYY

# Creamos un resource group para almacenar la storage account
az group create --name rg_global_tfstate --location westeurope

# Creamos la storage account
az storage account create --resource-group rg_global_tfstate --name satfstateforall

# Creamos un container donde alojar el tfstate
az storage container create --name containerfortfstate --account-name satfstateforall

# Obtenemos las keys de acceso a la storage account para poder realizar la configuración
az storage account keys list --resource-group rg_global_tfstate --account-name satfstateforall

[
  {
    "creationTime": "2022-09-03T13:37:03.700642+00:00",
    "keyName": "key1",
    "permissions": "FULL",
    "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
  },
  {
    "creationTime": "2022-09-03T13:37:03.700642+00:00",
    "keyName": "key2",
    "permissions": "FULL",
    "value": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
  }
]

Con la cuenta de almacenamiento de Azure preparada, debemos configurar un proyecto de terraform para que utilice esa cuenta de almacenamiento.

En este ejemplo vamos a crear un proyecto que trabaje con Azure (podría ser AWS, GCE, Nutanix, …) y que guarde el tfstate en una storage account de Azure (no es necesario que sean ni del mismo tenant ni subscripción), aquí tenemos un main.tf de ejemplo que utiliza azure blob storage como repositorio para alojar el tfstate del proyecto:

terraform {
    required_providers {
        azurerm = {
            source  = "hashicorp/azurerm"
            version = "=3.0.0"
        }
    }
    backend "azurerm" {
        storage_account_name = "satfstateforall"
        container_name       = "containerfortfstate"
        key                  = "terraform.tfstate"
        access_key           = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    }
}

provider "azurerm" {
    features {}
}

resource "azurerm_resource_group" "rg_example" {
  name     = "rg_example"
  location = "westeurope"
}

Para inicializar el proyecto hay que realizar (en este caso en PowerShell):

$Env:ARM_CLIENT_ID = "XXXXXXXXXXXXXXXXXXXXXX"
$Env:ARM_CLIENT_SECRET = "YYYYYYYYYYYYYYYYYYYYY"
$Env:ARM_SUBSCRIPTION_ID = "ZZZZZZZZZZZZZZZZZZZZZZZZZ"
$Env:ARM_TENANT_ID = "WWWWWWWWWWWWWWWWWWWWWWWWWW"

.\terraform.exe init

Initializing the backend...

Successfully configured the backend "azurerm"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Installing hashicorp/azurerm v3.0.0...
- Installed hashicorp/azurerm v3.0.0 (signed by HashiCorp)

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.


.\terraform.exe plan
azurerm_resource_group.rg_example: Refreshing state... [id=/subscriptions/XXXXXXXXXXXXXXXXXXXXXX/resourceGroups/rg_example]

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_resource_group.rg_example will be created
  + resource "azurerm_resource_group" "rg_example" {
      + id       = (known after apply)
      + location = "westeurope"
      + name     = "rg_example"
    }

Plan: 1 to add, 0 to change, 0 to destroy.


.\terraform.exe apply
azurerm_resource_group.rg_example: Refreshing state... [id=/subscriptions/XXXXXXXXXXXXXXXXXXXXXX/resourceGroups/rg_example]

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_resource_group.rg_example will be created
  + resource "azurerm_resource_group" "rg_example" {
      + id       = (known after apply)
      + location = "westeurope"
      + name     = "rg_example"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_resource_group.rg_example: Creating...
azurerm_resource_group.rg_example: Creation complete after 1s [id=/subscriptions/XXXXXXXXXXXXXXXXXXXXXX/resourceGroups/rg_example]

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

Es recomendable no almacenar access_key o passwords en los ficheros tf de terraform, para evitar tener configurada ese access_key en el main.tf debería utilizarse una variable de entorno para informar de dicha access_key. Otros métodos de autenticación se pueden encontrar en el siguiente enlace: Backend Type: azurerm | Terraform by HashiCorp

Si accedemos a la cuenta de almacenamiento donde alojamos los ficheros de estado, podremos ver el fichero con contenido:

Espero que os haya sido interesante y os ayude a crear vuestros ficheros Tfstate remoto para trabajo en equipo. Recordar que si necesitáis ayuda en orquestración de infraestrucutras cloud o on-premise, no dudéis de poneros en contacto con ICM 😉