Prácticas recomendadas para usar Terraform

En este documento, se proporcionan lineamientos y recomendaciones para un desarrollo efectivo con Terraform en varios miembros del equipo y transmisiones de trabajo.

Esta guía no es una introducción a Terraform. Para obtener una introducción al uso de Terraform con Google Cloud, consulta Comienza a usar Terraform.

Lineamientos generales de estilo y estructura

Las siguientes recomendaciones abarcan el estilo y la estructura básicos de tus opciones de configuración de Terraform. Las recomendaciones se aplican a los módulos reutilizables de Terraform y a las configuraciones raíz.

Sigue una estructura de módulo estándar

  • Los módulos de Terraform deben seguir la estructura de módulo estándar.
  • Inicia cada módulo con un archivo main.tf, en el que los recursos se ubican de forma predeterminada.
  • En cada módulo, incluye un archivo README.md en formato Markdown. En el archivo README.md, incluye la documentación básica sobre el módulo.
  • Coloca los ejemplos en una carpeta examples/, con un subdirectorio separado para cada ejemplo. Para cada ejemplo, incluye un archivo README.md detallado.
  • Crea grupos lógicos de recursos con sus propios archivos y nombres descriptivos, como network.tf, instances.tf o loadbalancer.tf.
    • Evita darle a cada recurso su propio archivo. Agrupa los recursos según su propósito compartido. Por ejemplo, combina google_dns_managed_zone y google_dns_record_set en dns.tf.
  • En el directorio raíz del módulo, incluye solo Terraform (*.tf) y los archivos de metadatos del repositorio (como README.md y CHANGELOG.md).
  • Coloca cualquier documentación adicional en un subdirectorio docs/.

Adopta una convención de nombres

  • Asigna nombres a todos los objetos de configuración con guiones bajos para delimitar varias palabras. Esta práctica garantiza la coherencia con la convención de nombres para los tipos de recursos, los tipos de fuente de datos y otros valores predefinidos. Esta convención no se aplica a los argumentos del nombre.

    Recomendado:

    resource "google_compute_instance" "web_server" {
      name = "web-server"
    }
    

    No se recomienda:

    resource "google_compute_instance" "web-server" {
      name = "web-server"
    }
    
  • A fin de simplificar las referencias a un recurso que es el único de su tipo (por ejemplo, un solo balanceador de cargas para un módulo completo), nombra el recurso main.

    • Se necesita trabajo mental adicional para recordar some_google_resource.my_special_resource.id en comparación con some_google_resource.main.id.
  • Para diferenciar los recursos del mismo tipo entre sí (por ejemplo, primary y secondary), proporciona nombres de recursos significativos.

  • Haz que los nombres de recursos sean singulares.

  • En el nombre del recurso, no repitas el tipo de recurso. Por ejemplo:

    Recomendado:

    resource "google_compute_global_address" "main" { ... }
    

    No se recomienda:

    resource "google_compute_global_address" "main_global_address" { … }
    

Usa las variables con cuidado

  • Declara todas las variables en variables.tf.
  • Proporciona nombres descriptivos de variables que sean relevantes para su uso o propósito:
    • Las entradas, las variables locales y los resultados que representan valores numéricos (como los tamaños de disco o de RAM) deben nombrarse con unidades (como ram_size_gb). Las APIs de Google Cloud no tienen unidades estándar, por lo que nombrar variables con unidades hace que la unidad de entrada esperada sea clara para los encargados de mantener la configuración.
    • Para las unidades de almacenamiento, usa prefijos de unidades binarias (potencias de 1,024): kibi, mebi y gibi. Para todas las demás unidades de medida, usa prefijos de unidades decimales (potencias de 1,000): kilo, mega y giga. Este uso coincide con el de Google Cloud.
    • Para simplificar la lógica condicional, asigna nombres positivos a las variables booleanas (por ejemplo, enable_external_access).
  • Las variables deben tener descripciones. Las descripciones se incluyen automáticamente en la documentación generada automáticamente de un módulo publicado. Las descripciones agregan contexto adicional para los desarrolladores nuevos que los nombres descriptivos no pueden proporcionar.
  • Proporciona tipos definidos de variables.
  • Cuando corresponda, proporciona valores predeterminados:
    • Para las variables que tienen valores independientes del entorno (como el tamaño del disco), proporciona valores predeterminados.
    • Para las variables que tienen valores específicos del entorno (como project_id), no proporciones valores predeterminados. De este modo, el módulo que realiza la llamada debe proporcionar valores significativos.
  • Usa valores predeterminados vacíos para las variables (como strings o listas vacías) solo cuando dejar la variable vacía es una preferencia válida que las API subyacentes no rechazan.
  • Sé prudente cuando uses las variables. Solo parametriza los valores que deben variar para cada instancia o entorno. Cuando decidas si debes exponer una variable, asegúrate de tener un caso de uso concreto para cambiar esa variable. Si solo hay una pequeña probabilidad de que se necesite una variable, no la expongas.
    • La adición de una variable con un valor predeterminado es compatible con versiones anteriores.
    • La eliminación de una variable es incompatible con versiones anteriores.
    • En los casos en que un literal se vuelve a usar en varios lugares, puedes usar un valor local sin exponerlo como una variable.

Expón los resultados

  • Organiza todos los resultados en un archivo outputs.tf.
  • Proporciona descripciones significativas para todos los resultados.
  • Documenta las descripciones de salida en el archivo README.md. Genera descripciones automáticas en la confirmación con herramientas como terraform-docs.
  • Genera todos los valores útiles a los que los módulos raíz podrían necesitar hacer referencia o que podrían necesitar compartir. En especial para los módulos de código abierto o de uso intensivo, expón todos los resultados que tengan potencial para su consumo.
  • No pases los resultados directamente a través de variables de entrada, ya que esto evita que se agreguen de forma correcta al grafo de dependencia. Para garantizar que se creen dependencias implícitas, asegúrate de que se generen atributos de referencia de los recursos. En lugar de hacer referencia a una variable de entrada para una instancia directamente, pasa el atributo como se muestra a continuación:

    Recomendado:

    output "name" {
      description = "Name of instance"
      value       = google_compute_instance.main.name
    }
    

    No se recomienda:

    output "name" {
      description = "Name of instance"
      value       = var.name
    }
    

Usa fuentes de datos

  • Coloca fuentes de datos junto a los recursos que hacen referencia a estas. Por ejemplo, si recuperas una imagen que se usará para iniciar una instancia, colócala junto con la instancia en lugar de recopilar recursos de datos en su propio archivo.
  • Si la cantidad de fuentes de datos es grande, considera moverlas a un archivo data.tf dedicado.
  • Para recuperar datos relacionados con el entorno actual, usa la interpolación de variables o recursos.

Limita el uso de secuencias de comandos personalizadas

  • Usa las secuencias de comandos solo cuando sea necesario. El estado de los recursos creados a través de secuencias de comandos no se tiene en cuenta ni se administra en Terraform.
    • De ser posible, evita las secuencias de comandos personalizadas. Úsalas solo cuando los recursos de Terraform no admitan el comportamiento deseado.
    • Cualquier secuencia de comandos personalizada que se use debe tener un motivo documentado con claridad para un plan existente y, idealmente, un plan de baja.
  • Terraform puede llamar a las secuencias de comandos personalizadas mediante aprovisionadores, incluido el aprovisionador local-exec.
  • Coloca las secuencias de comandos personalizadas que llama Terraform en un directorio scripts/.

Incluye secuencias de comandos auxiliares en un directorio separado

  • Organiza las secuencias de comandos auxiliares a las que Terraform no llama en un directorio helpers/.
  • Documenta las secuencias de comandos auxiliares en el archivo README.md con explicaciones y, además, invocaciones de ejemplo.
  • Si las secuencias de comandos auxiliares aceptan argumentos, proporciona la verificación de argumentos y el resultado --help.

Coloca los archivos estáticos en un directorio separado

  • Los archivos estáticos a los que Terraform hace referencia, pero que no ejecuta (como las secuencias de comandos de inicio cargados en instancias de Compute Engine), deben estar organizados en un directorio files/.
  • Coloca los documentos HereDocs largos en archivos externos, separados de su HCL. Haz referencia a ellos con la función file().
  • Para los archivos que se leen con la función templatefile de Terraform, usa la extensión de archivo .tftpl.
    • Las plantillas se deben colocar en un directorio templates/.

Protege los recursos con estado

Para los recursos con estado, como las bases de datos, asegúrate de que la protección contra la eliminación esté habilitada. Por ejemplo:

resource "google_sql_database_instance" "main" {
  name = "primary-instance"
  settings {
    tier = "D0"
  }

  lifecycle {
    prevent_destroy = true
  }
}

Usa el formato integrado

Todos los archivos de Terraform deben cumplir con los estándares de terraform fmt.

Limita la complejidad de las expresiones

  • Limita la complejidad de cualquier expresión interpolada individual. Si se necesitan muchas funciones en una sola expresión, considera dividirla en varias expresiones mediante valores locales.
  • Nunca tengas más de una operación ternaria en una sola línea. En su lugar, usa varios valores locales para compilar la lógica.

Usa count para valores condicionales

Para crear una instancia de un recurso de forma condicional, usa el metaargumento count. Por ejemplo:

variable "readers" {
  description = "..."
  type        = list
  default     = []
}

resource "resource_type" "reference_name" {
  // Do not create this resource if the list of readers is empty.
  count = length(var.readers) == 0 ? 0 : 1
  ...
}

Ten cuidado cuando uses variables especificadas por el usuario a fin de establecer la variable count para los recursos. Si se proporciona un atributo de recurso para una variable de este tipo (como project_id) y ese recurso aún no existe, Terraform no puede generar un plan. En su lugar, Terraform informa el error value of count cannot be computed. En esos casos, usa una variable enable_x independiente para calcular la lógica condicional.

Usa for_each para recursos iterados

Si deseas crear varias copias de un recurso según un recurso de entrada, usa el metaargumento for_each.

Publica módulos en un registro

Módulos reutilizables

Para los módulos que están destinados a la reutilización, usa los siguientes lineamientos además de los lineamientos anteriores.

Activa las API necesarias en módulos

Los módulos de Terraform pueden activar cualquier servicio obligatorio mediante el recurso google_project_service o el módulo project_services. Incluir la activación de API facilita las demostraciones.

  • Si la activación de la API se incluye en un módulo, la activación de la API debe inhabilitarse mediante la exposición de una variable enable_apis que se establece de forma predeterminada en true.
  • Si la activación de la API se incluye en un módulo, la activación de la API debe establecerse disable_services_on_destroy en false, ya que este atributo puede causar problemas cuando se trabaja con varias instancias del módulo.

    Por ejemplo:

    module "project-services" {
      source  = "terraform-google-modules/project-factory/google//modules/project_services"
      version = "~> 12.0"
    
      project_id  = var.project_id
      enable_apis = var.enable_apis
    
      activate_apis = [
        "compute.googleapis.com",
        "pubsub.googleapis.com",
      ]
      disable_services_on_destroy = false
    }
    

Incluye un archivo de propietarios

Para todos los módulos compartidos, incluye un archivo OWNERS (o CODEOWNERS en GitHub), que documente quién es responsable del módulo. Antes de que se combine cualquier solicitud de extracción, un propietario debe aprobarla.

Lanza versiones con etiquetas

A veces, los módulos requieren cambios rotundos y necesitas comunicar los efectos a los usuarios para que puedan fijar sus configuraciones en una versión específica.

Asegúrate de que los módulos compartidos sigan a SemVer v2.0.0 cuando se etiqueten o lancen versiones nuevas.

Cuando hagas referencia a un módulo, usa una restricción de versión para fijar la versión principal. Por ejemplo:

module "gke" {
  source  = "terraform-google-modules/kubernetes-engine/google"
  version = "~> 20.0"
}

No configures proveedores ni backends

Los módulos compartidos no deben configurar proveedores ni backends. En su lugar, configura proveedores y backends en los módulos raíz.

Para los módulos compartidos, define las versiones mínimas de los proveedores que se requieren en un bloque required_providers de la siguiente manera:

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 4.0.0"
    }
  }
}

A menos que se pruebe lo contrario, supongamos que las nuevas versiones de los proveedores funcionarán.

Expón etiquetas como una variable

Permite la flexibilidad en el etiquetado de los recursos a través de la interfaz del módulo. Considera proporcionar una variable labels con un valor predeterminado de un mapa vacío, de la siguiente manera:

variable "labels" {
  description = "A map of labels to apply to contained resources."
  default     = {}
  type        = "map"
}

Expón resultados para todos los recursos

Las variables y los resultados te permiten inferir dependencias entre módulos y recursos. Sin ningún resultado, los usuarios no pueden ordenar de forma correcta tu módulo en relación con sus opciones de configuración de Terraform.

Para cada recurso definido en un módulo compartido, incluye al menos un resultado que haga referencia al recurso.

Usa submódulos intercalados para la lógica compleja

  • Los módulos intercalados te permiten organizar módulos complejos de Terraform en unidades más pequeñas y anular la duplicación de recursos comunes.
  • Coloca los módulos intercalados en modules/$modulename.
  • Trata los módulos intercalados como privados, a fin de que los módulos externos no los usen, a menos que la documentación del módulo compartido indique lo contrario.
  • Terraform no realiza un seguimiento de los recursos refactorizados. Si comienzas con varios recursos en el módulo de nivel superior y, luego, los envías a submódulos, Terraform intenta volver a crear todos los recursos refactorizados. Para mitigar este comportamiento, usa bloques moved cuando refactorices.
  • Los resultados que definen los módulos internos no se exponen de manera automática. Para compartir los resultados de los módulos internos, vuelve a exportarlos.

Módulos raíz de Terraform

Las configuraciones raíz (módulos raíz) son los directorios de trabajo desde los que ejecutas la CLI de Terraform. Asegúrate de que las opciones de configuración raíz cumplan con los siguientes estándares (y con los lineamientos anteriores de Terraform, cuando corresponda). Las recomendaciones explícitas para los módulos raíz sustituyen los lineamientos generales.

Minimiza la cantidad de recursos en cada módulo raíz

Es importante evitar que una sola opción de configuración raíz sea demasiado grande y cuente con demasiados recursos almacenados en el mismo directorio y estado. Todos los recursos de una opción de configuración raíz en particular se actualizan cada vez que se ejecuta Terraform. Esto puede causar una ejecución lenta si se incluyen demasiados recursos en un solo estado. Una regla general: no incluyas más de 100 recursos (lo ideal sería solo unas pocas docenas) en un solo estado.

Usa directorios separados para cada aplicación

Para administrar las aplicaciones y los proyectos de forma independiente, coloca recursos para cada aplicación y proyecto en sus propios directorios de Terraform. Un servicio puede representar una aplicación particular o un servicio común, como las redes compartidas. Anida todo el código de Terraform para un servicio en particular en un directorio (incluidos los subdirectorios).

Divide las aplicaciones en subdirectorios específicos del entorno

Cuando implementes servicios en Google Cloud, divide la configuración de Terraform para el servicio en dos directorios de nivel superior: un directorio modules que contiene la configuración real del servicio, y un directorio environments que contiene las configuraciones raíz para cada entorno.

-- SERVICE-DIRECTORY/
   -- OWNERS
   -- modules/
      -- <service-name>/
         -- main.tf
         -- variables.tf
         -- outputs.tf
         -- provider.tf
         -- README
      -- ...other…
   -- environments/
      -- dev/
         -- backend.tf
         -- main.tf

      -- qa/
         -- backend.tf
         -- main.tf

      -- prod/
         -- backend.tf
         -- main.tf

Usa directorios del entorno

Para compartir código entre entornos, consulta los módulos. Por lo general, este podría ser un módulo de servicio que incluya la configuración base compartida de Terraform para el servicio. En los módulos de servicio, codifica las entradas comunes de forma hard-coded y solo requiere entradas específicas del entorno como variables.

Cada directorio del entorno debe contener los siguientes archivos:

  • Un archivo backend.tf que declara la ubicación del estado del backend de Terraform (por lo general, Cloud Storage)
  • Un archivo main.tf que crea una instancia del módulo de servicio

Cada directorio del entorno (dev, qa, prod) corresponde a un lugar de trabajo de Terraform predeterminado y, luego, implementa una versión del servicio en ese entorno. Estos lugares de trabajo aíslan los recursos específicos del entorno en sus propios contextos. Usa solo el lugar de trabajo predeterminado.

No se recomienda tener varios lugares de trabajo de la CLI en un entorno por los siguientes motivos:

  • Puede ser difícil inspeccionar la configuración en cada lugar de trabajo.
  • No se recomienda tener un solo backend compartido para varios lugares de trabajo, ya que el backend compartido se convierte en un punto único de fallo si se usa para la separación del entorno.
  • Si bien es posible volver a usar el código, se vuelve más difícil leer el código debiendo cambiar según la variable de lugar de trabajo actual (por ejemplo, terraform.workspace == "foo" ? this : that).

Para obtener más información, consulta lo siguiente:

Expón resultados mediante el estado remoto

Asegúrate de exponer los resultados útiles de las instancias del módulo desde un módulo raíz.

Por ejemplo, el siguiente fragmento de código pasa por el resultado del ID del proyecto desde la instancia del módulo de fábrica del proyecto como resultado del módulo raíz.

# Project root module
terraform {
  backend "gcs" {
    bucket  = "BUCKET"
  }
}

module "project" {
  source  = "terraform-google-modules/project-factory/google"
  ...
}

output "project_id" {
  value       = module.project.project_id
  description = "The ID of the created project"
}

Otros entornos y aplicaciones de Terraform solo pueden hacer referencia a los resultados a nivel de módulo raíz.

Si usas el estado remoto, puedes hacer referencia a los resultados del módulo raíz. Para permitir que otras apps dependientes usen la configuración, asegúrate de exportar al estado remoto la información relacionada con los extremos de un servicio.

# Networks root module
data "terraform_remote_state" "network_project" {
  backend = "gcs"

  config = {
    bucket = "BUCKET"
  }
}

module "vpc" {
  source  = "terraform-google-modules/network/google"
  version = "~> 9.0"

  project_id   = data.terraform_remote_state.network_project.outputs.project_id
  network_name = "vpc-1"
  ...
}

A veces, como cuando se invoca un módulo de servicio compartido desde directorios de entornos, es apropiado volver a exportar todo el módulo secundario de la siguiente manera:

output "service" {
  value       = module.service
  description = "The service module outputs"
}

Fija en las versiones secundarias del proveedor

En los módulos raíz, declara cada proveedor y fija en una versión secundaria. Esto permite la actualización automática a nuevas versiones de parches y, al mismo tiempo, mantiene un objetivo sólido. Para mantener la coherencia, nombra el archivo de versiones versions.tf.

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0.0"
    }
  }
}

Almacena variables en un archivo tfvars

Para los módulos raíz, proporciona variables con un archivo de variables .tfvars. Para mantener la coherencia, nombra los archivos de variables terraform.tfvars.

No especifiques variables mediante opciones de línea de comandos alternativas de var-files o var='key=val'. Las opciones de línea de comandos son efímeras y fáciles de olvidar. El uso de un archivo de variables predeterminado es más predecible.

Revisa el archivo .terraform.lock.hcl

Para los módulos raíz, el archivo .terraform.lock.hcl de bloqueo de dependencia se debe registrar en el control de origen. Esto permite realizar un seguimiento y revisar los cambios en las selecciones de proveedores para una configuración determinada.

Comunicación entre configuraciones

Un problema común que surge cuando se usa Terraform es cómo compartir información entre diferentes configuraciones de Terraform (posiblemente mantenidas por diferentes equipos). En general, la información se puede compartir entre las configuraciones sin necesidad de que se almacenen en un solo directorio de configuración (o incluso en un solo repositorio).

La forma recomendada de compartir información entre diferentes configuraciones de Terraform es mediante el estado remoto para hacer referencia a otros módulos raíz. Cloud Storage o Terraform Enterprise son los backends de estado preferidos.

Para consultar recursos que no administra Terraform, usa fuentes de datos del proveedor de Google. Por ejemplo, la cuenta de servicio predeterminada de Compute Engine se puede recuperar mediante una fuente de datos. No uses fuentes de datos para consultar recursos administrados por otra configuración de Terraform. Esto puede crear dependencias implícitas en los nombres de recursos y las estructuras que las operaciones normales de Terraform podrían romper por accidente.

Trabaja con recursos de Google Cloud

Las prácticas recomendadas para aprovisionar recursos de Google Cloud con Terraform están integradas en los módulos de Cloud Foundation Toolkit que mantiene Google. En esta sección, se reiteran algunas de estas prácticas recomendadas.

Prepara imágenes de máquinas virtuales

En general, te recomendamos preparar imágenes de máquinas virtuales con una herramienta como Packer. Terraform solo necesita iniciar las máquinas con las imágenes ya preparadas.

Si las imágenes ya preparadas no están disponibles, Terraform puede transferir máquinas virtuales nuevas a una herramienta de administración de configuración con un bloque provisioner. Te recomendamos que evites este método y solo lo uses como último recurso. Para limpiar el estado anterior asociado con la instancia, los aprovisionadores que requieren lógica de eliminación deben usar un bloque provisioner con when = destroy.

Terraform debe proporcionar información sobre la configuración de la VM a la administración de configuración con metadatos de la instancia.

Gestiona la administración de identidades y accesos

Cuando se aprovisionan asociaciones de IAM con Terraform, hay varios recursos diferentes disponibles:

  • google_*_iam_policy (por ejemplo, google_project_iam_policy)
  • google_*_iam_binding (por ejemplo, google_project_iam_binding)
  • google_*_iam_member (por ejemplo, google_project_iam_member)

google_*_iam_policy y google_*_iam_binding crean asociaciones de IAM autorizadas, en las que los recursos de Terraform sirven como única fuente de información para los permisos que se pueden asignar al recurso relevante.

Si los permisos cambian fuera de Terraform, en su siguiente ejecución, Terraform reemplaza todos los permisos para representar la política como se define en la configuración. Esto puede tener sentido para recursos que son completamente administrados por una configuración de Terraform en particular, pero significa que se quitan los roles que administra Google Cloud de forma automática, lo que puede interrumpir la funcionalidad de algunos servicios.

Para evitar esto, te recomendamos usar los recursos de google_*_iam_member directamente o el módulo sobre IAM de Google.

Control de versiones

Al igual que con otras formas de código, almacena el código de la infraestructura en el control de versión para preservar el historial y permitir reversiones sencillas.

Usa una estrategia de ramificación predeterminada

Para todos los repositorios que contienen un código de Terraform, usa la siguiente estrategia de forma predeterminada:

  • La rama main es la rama de desarrollo principal y representa el código aprobado más reciente. La rama main está protegida.
  • El desarrollo ocurre en ramas de funciones y corrección de errores que se bifurcan de la rama main.
    • Nombra las ramas de funciones feature/$feature_name.
    • Nombra las ramas de corrección de errores fix/$bugfix_name.
  • Cuando se complete una función o una corrección de errores, vuelve a combinarla en la rama main con una solicitud de extracción.
  • Para evitar conflictos de combinación, modifica las ramas antes de combinarlas.

Usa ramas de entorno para la configuración raíz

Para los repositorios que incluyen configuraciones raíz que se implementan directamente en Google Cloud, se requiere una estrategia segura de lanzamiento. Recomendamos tener una rama separada para cada entorno. Por lo tanto, los cambios en la configuración de Terraform se pueden promover mediante la combinación de cambios entre las diferentes ramas.

Rama separada para cada entorno

Permite una visibilidad amplia

Haz que el código fuente y los repositorios de Terraform sean visibles y accesibles en todas las organizaciones de ingeniería para los propietarios de la infraestructura (por ejemplo, SRE) y las partes interesadas de la infraestructura (por ejemplo, desarrolladores). Esto garantiza que las partes interesadas de la infraestructura puedan comprender mejor la infraestructura de la que dependen.

Incentiva a las partes interesadas de la infraestructura para que envíen solicitudes de combinación como parte del proceso de solicitud de cambio.

Nunca confirmes objetos Secret

Nunca confirmes objetos Secret en el control de origen, incluso en la configuración de Terraform. En su lugar, súbelos a un sistema como Secret Manager y haz referencia a ellos mediante las fuentes de datos.

Ten en cuenta que estos valores sensibles aún pueden terminar en el archivo de estado y que también pueden exponerse como resultados.

Organiza los repositorios según los límites del equipo

Si bien puedes usar directorios separados para administrar los límites lógicos entre los recursos, los límites organizacionales y la logística determinan la estructura del repositorio. En general, usa el principio de diseño de que las configuraciones con diferentes requisitos de aprobación y administración se separen en diferentes repositorios de control de origen. Para ilustrar este principio, se incluyen algunas opciones de configuración de repositorio posibles:

  • Un repositorio central: en este modelo, un solo equipo de plataforma administra todo el código de Terraform de forma central. Este modelo funciona mejor cuando hay un equipo de infraestructura dedicado responsable de toda la administración de la nube y aprueba cualquier cambio que soliciten otros equipos.

  • Repositorios de equipos: en este modelo, cada equipo es responsable de su propio repositorio de Terraform en el que administra todo lo relacionado con la infraestructura que posee. Por ejemplo, el equipo de seguridad puede tener un repositorio en el que se administran todos los controles de seguridad, y los equipos de aplicaciones tienen su propio repositorio de Terraform para implementar y administrar su aplicación.

    Organizar repositorios a través de límites de equipos es la mejor estructura para la mayoría de las situaciones empresariales.

  • Repositorios desacoplados: en este modelo, cada componente lógico de Terraform se divide en su propio repositorio. Por ejemplo, las herramientas de redes pueden tener un repositorio dedicado, y puede que haya un repositorio de fábrica del proyecto separado para la creación y administración de proyectos. Esto funciona mejor en entornos muy descentralizados donde las responsabilidades cambian con frecuencia entre los equipos.

Estructura del repositorio de muestra

Puedes combinar estos principios para dividir la configuración de Terraform en diferentes tipos de repositorios:

  • Básica
  • Específica de la aplicación y el equipo
Repositorio básico

Un repositorio básico que contiene los principales componentes centrales, como las carpetas o la IAM de la organización. El equipo central de la nube puede administrar este repositorio.

  • En este repositorio, incluye un directorio para cada componente principal (por ejemplo, carpetas, redes, etcétera).
  • En los directorios de componentes, incluye una carpeta separada para cada entorno (que refleje la orientación de estructura del directorio que se mencionó antes).

Estructura básica del repositorio

Repositorios específicos de la aplicación y el equipo

Implementa repositorios específicos de la aplicación y el equipo por separado para que cada equipo administre su configuración de Terraform única y específica de la aplicación.

Estructura del repositorio específico de la aplicación y el equipo

Operations

Mantener la infraestructura segura depende de tener un proceso estable y seguro para aplicar actualizaciones de Terraform.

Siempre planifica primero

Siempre genera primero un plan para las ejecuciones de Terraform. Guarda el plan en un archivo de salida. Después de que un propietario de la infraestructura lo apruebe, ejecuta el plan. Incluso cuando los desarrolladores prototipan cambios de forma local, deben generar un plan y revisar los recursos que se agregarán, modificarán y destruirán antes de aplicar el plan.

Implementa una canalización automatizada

Para garantizar un contexto de ejecución coherente, ejecuta Terraform a través de herramientas automatizadas. Si un sistema de compilación (como Jenkins) ya está en uso y se adopta de forma amplia, úsalo para ejecutar los comandos de terraform plan y terraform apply automáticamente. Si no hay un sistema existente disponible, adopta Cloud Build o Terraform Cloud.

Usa las credenciales de la cuenta de servicio para la integración continua

Cuando Terraform se ejecuta desde una máquina en una canalización de CI/CD, debe heredar las credenciales de la cuenta de servicio del servicio que ejecuta la canalización. Siempre que sea posible, ejecuta canalizaciones de CI en Google Cloud porque Cloud Build, Google Kubernetes Engine o Compute Engine insertan credenciales sin descargar claves de cuenta de servicio.

Para las canalizaciones que se ejecutan fuera de Google Cloud, prefiere la federación de Workload Identity a fin de obtener credenciales sin descargar claves de cuenta de servicio.

Evita importar recursos existentes

Siempre que sea posible, evita importar recursos existentes (mediante terraform import), ya que esto puede dificultar la comprensión de la procedencia y la configuración de los recursos creados de forma manual. En su lugar, crea recursos nuevos a través de Terraform y borra los recursos anteriores.

En los casos en los que borrar recursos anteriores crearía un trabajo repetitivo significativo, usa el comando terraform import con aprobación explícita. Después de importar un recurso a Terraform, adminístralo de forma exclusiva con Terraform.

Google proporciona una herramienta que puedes usar para importar tus recursos de Google Cloud al estado de Terraform. Para obtener más información, consulta Importa tus recursos de Google Cloud al estado de Terraform.

No modifiques el estado de Terraform de forma manual

El archivo de estado de Terraform es fundamental para mantener la asignación entre la configuración de Terraform y los recursos de Google Cloud. Los daños pueden generar problemas importantes de infraestructura. Cuando se necesiten modificaciones en el estado de Terraform, usa el comando terraform state.

Revisa los marcadores de versión de forma periódica

Fijar versiones garantiza la estabilidad, pero evita que se incorporen correcciones de errores y otras mejoras en tu configuración. Por lo tanto, revisa de forma periódica los marcadores de versión para Terraform, los proveedores de Terraform y los módulos.

Para automatizar este proceso, usa una herramienta como Dependabot.

Usa credenciales predeterminadas de la aplicación cuando ejecutes de forma local

Cuando los desarrolladores realizan iteraciones de forma local en la configuración de Terraform, deben autenticarlas mediante la ejecución de gcloud auth application-default login para generar las credenciales predeterminadas de la aplicación. No descargues claves de cuenta de servicio, ya que las claves descargadas son más difíciles de administrar y proteger.

Establece alias para Terraform

Para facilitar el desarrollo local, puedes agregar alias a tu perfil de shell del comando:

  • alias tf="terraform"
  • alias terrafrom="terraform"

Security

Terraform requiere acceso datos sensibles en tu infraestructura de nube para operar. Seguir estas prácticas recomendadas de seguridad puede ayudarte a minimizar los riesgos asociados y mejorar la seguridad general de la nube.

Usa el estado remoto

Para los clientes de Google Cloud, recomendamos usar el backend de estado de Cloud Storage. Este enfoque bloquea el estado para permitir la colaboración como equipo. También separa el estado y toda la información potencialmente sensible del control de versión.

Asegúrate de que solo el sistema de compilación y los administradores con un gran cantidad de privilegios puedan acceder al bucket que se usa para el estado remoto.

Para evitar la confirmación accidental del estado de desarrollo en el control de origen, usa gitignore para los archivos de estado de Terraform.

Encripta el estado

Si bien los buckets de Google Cloud están encriptados en reposo, puedes usar las claves de encriptación proporcionadas por el cliente para brindar una capa adicional de protección. Para ello, usa la variable de entorno GOOGLE_ENCRYPTION_KEY. Aunque no haya objetos Secret en el archivo de estado, siempre encripta el estado como una medida adicional de defensa.

No almacenes objetos Secret en el estado

Existen muchos recursos y proveedores de datos en Terraform que almacenan valores de Secret en texto simple en el archivo de estado. Siempre que sea posible, evita almacenar los objetos Secret en el estado. Los siguientes son algunos ejemplos de proveedores que almacenan objetos Secret en texto simple:

Marca los resultados sensibles

En lugar de intentar encriptar valores sensibles de forma manual, recurre a la compatibilidad integrada de Terraform para la administración de estado sensible. Cuando exportes valores sensibles al resultado, asegúrate de que los valores estén marcados como sensibles.

Asegúrate de la separación de obligaciones

Si no puedes ejecutar Terraform desde un sistema automatizado en el que ningún usuario tiene acceso, cumple con la separación de obligaciones mediante la separación de permisos y directorios. Por ejemplo, un proyecto de red correspondería a una cuenta de servicio o un usuario de Terraform de red cuyo acceso se encuentra limitado a este proyecto.

Ejecuta verificaciones previas a la aplicación

Cuando ejecutes Terraform en una canalización automatizada, usa una herramienta como gcloud terraform vet para verificar el resultado del plan con las políticas antes de realizar la ejecución. Esto puede detectar regresiones de seguridad antes de que ocurran.

Ejecuta auditorías continuas

Después de ejecutar el comando terraform apply, ejecuta controles de seguridad automatizados. Estas verificaciones pueden ayudar a garantizar que la infraestructura no se desvíe a un estado no seguro. Las siguientes herramientas son opciones válidas para este tipo de verificación:

Prueba

La prueba de módulos y configuraciones de Terraform a veces sigue diferentes patrones y convenciones a partir de la prueba del código de la aplicación. Si bien probar el código de la aplicación implica principalmente probar la lógica empresarial de las aplicaciones en sí, para probar el código de la infraestructura en su totalidad es necesario implementar recursos en la nube reales para minimizar el riesgo de errores de producción. Ten en cuenta las siguientes consideraciones cuando ejecutes pruebas de Terraform:

  • La ejecución de una prueba de Terraform crea, modifica y destruye la infraestructura real, por lo que las pruebas pueden llevar mucho tiempo y ser costosas.
  • No puedes probar la unidad solo de una arquitectura de extremo a extremo. El mejor enfoque consiste en dividir la arquitectura en módulos y probarlos de forma individual. Los beneficios de este enfoque incluyen un desarrollo iterativo más rápido debido al tiempo de ejecución de prueba más rápido, la reducción de costos para cada prueba y la reducción de las posibilidades de pruebas fallidas a partir de factores más allá de tu control.
  • Evita la reutilización del estado, si es posible. Puede haber situaciones en las que realices pruebas con configuraciones que compartan datos con otras opciones de configuración, pero idealmente cada prueba debería ser independiente y no debería reutilizar el estado en todas las pruebas.

Usa primero métodos de prueba menos costosos

Existen varios métodos que puedes usar para probar Terraform. En orden ascendente según el costo, el tiempo de ejecución y la profundidad, incluyen lo siguiente:

  • Análisis estático: prueba la sintaxis y la estructura de tu configuración sin implementar ningún recurso mediante herramientas, como compiladores, lints y pruebas de validación. Para hacerlo, usa terraform validate.
  • Prueba de integración del módulo: para garantizar que los módulos funcionen de forma correcta, prueba los módulos individuales de forma aislada. La prueba de integración para los módulos implican implementar el módulo en un entorno de pruebas y verificar que se creen los recursos esperados. Hay varios frameworks de prueba que facilitan la escritura de pruebas, de la siguiente manera:
  • Prueba de extremo a extremo: si extiendes el enfoque de prueba de integración a un entorno completo, puedes confirmar que varios módulos funcionen juntos. En este enfoque, implementa todos los módulos que conforman la arquitectura en un entorno de pruebas nuevo. Lo ideal es que el entorno de pruebas sea lo más similar posible a tu entorno de producción. Esto es costoso, pero proporciona la mayor confianza de que los cambios no interrumpen tu entorno de producción.

Empieza de a poco

Asegúrate de que las pruebas se compilen de forma iterativa entre sí. Considera ejecutar primero pruebas más pequeñas y, luego, trabajar en pruebas más complejas mediante un enfoque de falla rápida.

Aleatoriza ID de proyectos y nombres de recursos

Para evitar conflictos de nombres, asegúrate de que tus opciones de configuración tengan un ID del proyecto único a nivel global y nombres de recursos no superpuestos en cada proyecto. Para hacerlo, utiliza espacios de nombres en tus recursos. Terraform tiene un proveedor aleatorio integrado para esto.

Usa un entorno independiente para realizar pruebas

Durante la prueba, se crean y borran muchos recursos. Asegúrate de que el entorno esté aislado de los proyectos de desarrollo o producción para evitar las eliminaciones accidentales durante la limpieza de los recursos. El mejor enfoque es hacer que cada prueba cree un proyecto o una carpeta nuevos. A fin de evitar una configuración incorrecta, considera crear cuentas de servicio específicas para cada ejecución de prueba.

Limpia todos los recursos

Probar el código de infraestructura significa que estás implementando recursos reales. Para evitar que se apliquen cargos, considera implementar un paso de limpieza.

Para destruir todos los objetos remotos que administra una configuración específica, usa el comando terraform destroy. Algunos frameworks de prueba tienen un paso de limpieza integrado. Por ejemplo, si usas Terratest, agrega defer terraform.Destroy(t, terraformOptions) a tu prueba. Si usas Kitchen-Terraform, borra tu lugar de trabajo con terraform kitchen delete WORKSPACE_NAME.

Después de ejecutar el comando terraform destroy, también ejecuta procedimientos de limpieza adicionales para quitar cualquier recurso que Terraform no haya destruido. Para eso, borra los proyectos que se usaron en la ejecución de prueba o con una herramienta como el módulo project_cleanup.

Optimiza el entorno de ejecución de pruebas

Para optimizar el tiempo de ejecución de prueba, usa los siguientes enfoques:

  • Ejecuta pruebas en paralelo. Algunos frameworks de prueba admiten la ejecución de varias pruebas de Terraform de forma simultánea.
    • Por ejemplo, con Terratest, puedes agregar t.Parallel() después de la definición de la función de prueba.
  • Realiza pruebas en etapas. Separa tus pruebas en configuraciones independientes que se pueden probar por separado. Este enfoque quita la necesidad de pasar por todas las etapas cuando se ejecuta una prueba y acelera el ciclo de desarrollo iterativo.
    • Por ejemplo, en Kitchen-Terraform, divide las pruebas en conjuntos separados. Cuando iteras, ejecuta cada conjunto de forma independiente.
    • Del mismo modo, mediante Terratest, une cada etapa de la prueba con stage(t, STAGE_NAME, CORRESPONDING_TESTFUNCTION). Configura variables de entorno que indiquen qué pruebas ejecutar. Por ejemplo, SKIPSTAGE_NAME="true".
    • El framework de prueba de plano admite la ejecución por etapas.

¿Qué sigue?