Terraform AWS Cloud: Gestión de infraestructura sana

Publicado: 2022-03-11

Escribir una solicitud es solo una parte de la historia. Para que sea de valor, debe implementarse en algún lugar donde pueda escalar, debe ejecutarse con alta disponibilidad, necesita copias de seguridad, etc.

Se requiere que cada vez más desarrolladores tengan al menos una idea de este proceso de implementación. Esto se manifiesta, por ejemplo, en que DevOps se está convirtiendo en una función solicitada con frecuencia hoy en día, a medida que crece la complejidad del sistema. No podemos permitirnos ignorar esos cambios y debemos ser conscientes de cómo diseñar aplicaciones para que sean fácilmente implementables.

Esto también beneficia a nuestros clientes: nos contratan como expertos en nuestros campos y esperan que entreguemos el producto completo, a menudo de principio a fin. Tienen requisitos y, a menudo, ignoran la pila en la que se ejecuta su solución empresarial. Al final, lo que importa es el valor comercial del producto.

Presentamos Terraform

La implementación y la administración de la infraestructura no son un proceso simple. Además de una gran cantidad de experiencia en el dominio en constante cambio, necesitamos aprender otra herramienta más o un nuevo flujo de trabajo. Si ha estado posponiendo esto, este artículo es una oportunidad para que se familiarice con un enfoque para la gestión de infraestructura. Espero que al final tenga más confianza en el uso de Terraform y sepa más sobre los posibles enfoques y desafíos. Debería poder usar esta herramienta para al menos comenzar a administrar una parte de su infraestructura en la nube.

Terraform es una capa de abstracción, sí, y las abstracciones tienen fugas, estoy de acuerdo. Pero al final, estamos en el negocio de resolver problemas y gestionar abstracciones. Éste pretende aportarnos más cordura en nuestro día a día.

La meta

Explicaré qué es Terraform, cómo se adapta a todo el ecosistema y cómo se compara con otras herramientas similares. Luego, le mostraré los pasos necesarios para configurar una configuración de Terraform lista para producción y multientorno para un equipo. Explicaré los conceptos básicos para escribir la configuración de Terraform: cómo administrar la complejidad y el código duplicado con módulos compartibles.

Todos los ejemplos se centrarán en un proveedor de la nube: Amazon Web Services (AWS). Esta es solo una nube con la que tengo más experiencia, pero toda la información debería aplicarse también a otras nubes.

Terminaré con algunas notas que desearía haber sabido cuando comencé: algunos problemas de sintaxis, peculiaridades y casos en los que Terraform no sería mi herramienta preferida.

No me centraré en los detalles esenciales de la sintaxis. Puede ponerse al día rápidamente leyendo la fantástica documentación y las guías que HashiCorp proporciona para Terraform. Me gustaría centrarme en cosas que pueden no ser obvias desde el principio y que desearía haber sabido antes de comenzar a trabajar con Terraform. Con suerte, esto lo guiará en la dirección correcta y le permitirá considerar a Terraform como una herramienta para usar en proyectos futuros.

La gestión de la infraestructura es difícil

Hay varios pasos involucrados en la configuración de un entorno para una aplicación en la nube. A menos que los escriba todos como listas de verificación detalladas y los siga de cerca, todo el tiempo, cometerá errores; Somos humanos, después de todo. Esos pasos son difíciles de compartir. Necesita documentar una gran cantidad de procedimientos manuales y los documentos pueden quedar obsoletos rápidamente. Ahora multiplique todo eso por la cantidad total de entornos para una sola aplicación: dev , test/qa , stage y prod . También debe pensar en la seguridad de cada uno de ellos.

¿No tienen todas las herramientas una interfaz de usuario que puedo usar y olvidarme de la complejidad?

Tienen interfaces de usuario: la Consola de administración de AWS es un excelente ejemplo. Pero esas herramientas hacen mucho bajo el capó. Un clic en la interfaz de usuario puede invocar una cascada de cambios que son difíciles de comprender. Por lo general, no hay forma de deshacer lo que hizo en la interfaz de usuario (las preguntas habituales "¿Está seguro?" a menudo no son suficientes). Además, siempre es una buena idea tener un segundo par de ojos para verificar los cambios, pero cuando usamos interfaces de usuario, necesitamos sentarnos con esta persona o verificar nuestros cambios después de que se hayan realizado, lo cual es más una auditoría que un revisión. Cada proveedor de la nube tiene su propia interfaz de usuario que debe dominar. Las interfaces de usuario están diseñadas para ser fáciles de usar (¡no necesariamente simples!) y, como tales, son propensas a enfoques engañosos como "esto es solo un pequeño ajuste" o una revisión de producción rápida que olvidará en 48 horas. Tal clic manual también es muy difícil de automatizar.

¿Qué pasa con las herramientas CLI?

Serían mejores que las herramientas de interfaz de usuario para nuestros casos de uso. Sin embargo, aún es propenso a hacer cambios a mano o escribir scripts de bash que pueden salirse de control fácilmente. Además, cada proveedor tiene sus propias herramientas CLI. Con esas herramientas, no puede ver sus cambios antes de comprometerse con ellos. Afortunadamente, este no es un problema nuevo y existen herramientas que ayudan con esto.

Orquestación frente a gestión de la configuración

Definiré algunas categorías de herramientas y prácticas que se utilizan para administrar la infraestructura. Estos son la orquestación y la gestión de la configuración. Puede pensar en ellos aproximadamente como modelos de programación declarativos e imperativos (piense en Prolog vs. Python). Cada uno tiene sus pros y sus contras, pero es mejor conocerlos todos y aplicar la mejor herramienta para un trabajo determinado. Además, la orquestación suele implicar un nivel de abstracción más alto que la gestión de la configuración.

Orquestación

La orquestación se parece más a un paradigma de programación declarativa. Me gusta pensar en él como el director de una orquesta. Wikipedia (enlace) resume muy bien el trabajo de un director de orquesta como “el arte de dirigir la actuación simultánea de varios intérpretes o cantantes mediante el uso de gestos”. Los directores comunican el ritmo y el tempo, la dinámica y las indicaciones de la música, pero el trabajo real lo realizan músicos individuales que son expertos en tocar sus instrumentos musicales. Sin esa coordinación, no podrían realizar una pieza perfecta.

Aquí es donde encaja Terraform. Lo usa para llevar a cabo una parte de la infraestructura de TI: le dice qué implementar y Terraform lo vincula todo y realiza todas las llamadas API necesarias. Hay herramientas similares en este espacio; uno de los más conocidos es AWS Cloud Formation. Tiene mejor soporte para recuperación y reversión en caso de errores que Terraform, pero también, en mi opinión, una curva de aprendizaje más pronunciada. Tampoco es independiente de la nube: funciona solo con AWS.

Gestión de la configuración

El lado complementario de estas prácticas es el enfoque de gestión de la configuración. En este paradigma, usted especifica los pasos exactos que debe realizar una herramienta para llegar a una configuración determinada y deseada. Los pasos en sí mismos pueden ser pequeños y admitir múltiples sistemas operativos, pero debe pensar activamente en el orden de su ejecución. No tienen conciencia del estado actual y del entorno (a menos que los programes con eso) y, como tales, ejecutarán ciegamente cualquier paso que les des. Esto puede conducir a un problema conocido como cambio de configuración , donde sus recursos se desincronizarán lentamente con lo que pretendían representar inicialmente, especialmente si realizó algunos cambios manuales en ellos. Son excelentes para administrar y aprovisionar servicios en instancias individuales. Ejemplos de herramientas que sobresalen en este flujo de trabajo son Chef, Puppet, Ansible y Salt.

La orquestación impone un enfoque a su infraestructura en el que trata sus recursos como ganado, no como mascotas. En lugar de "nutrir" manualmente cada VPS, puede reemplazarlos con una copia exacta cuando algo sale mal. No quiero decir que simplemente no te importe y reinicies la cosa esperando lo mejor.

Meme del programa de televisión IT Crowd con su lema icónico: ¿Has probado a apagarlo y encenderlo de nuevo?

En su lugar, debe investigar y solucionar el problema en el código y luego implementarlo.

Ansible (y otras herramientas de CM) se pueden usar para administrar la infraestructura de AWS, pero esto implicaría mucho trabajo y es más propenso a errores, especialmente cuando la infraestructura cambia con frecuencia y crece en complejidad.

Una cosa importante que se debe recordar es que los enfoques de orquestación y gestión de la configuración no entran en conflicto entre sí. Son compatibles. Está perfectamente bien tener un grupo de instancias EC2 (VPS) en un grupo de AutoScaling administrado por Terraform pero ejecutando una imagen de aplicación de AWS (AMI), que es una instantánea del disco, que se preparó con pasos imperativos con, por ejemplo, Ansible . Terraform incluso tiene un concepto de "proveedores" que le permiten ejecutar herramientas de aprovisionamiento externas una vez que se inicia una máquina.

La documentación de Terraform hace un gran trabajo al explicar esto más detalladamente y ayudarlo a ubicar a Terraform en todo el ecosistema.

¿Qué es Terraform?

Es una herramienta de código abierto, creada por HashiCorp que le permite codificar su infraestructura como archivos de configuración declarativos que se versionan y comparten y se pueden revisar.

El nombre de HashiCorp debería sonarte: también hacen Nomad, Vault, Packer, Vagrant y Consul. Si ha utilizado alguna de esas herramientas, ya conoce la calidad de la documentación, la comunidad dinámica y la utilidad que puede esperar de sus soluciones.

Infraestructura como código

Terraform es independiente de la plataforma; puede usarlo para administrar servidores bare metal o servidores en la nube como AWS, Google Cloud Platform, OpenStack y Azure. En la jerga de Terraform, estos se denominan proveedores . Puede hacerse una idea de la escala leyendo una lista completa de proveedores admitidos. Se pueden usar varios proveedores al mismo tiempo, por ejemplo, cuando el proveedor A configura las VM por usted, pero el proveedor B configura y delega registros DNS.

¿Significa eso que uno puede cambiar de proveedor de nube con un cambio en un archivo de configuración? No, ni siquiera creo que quieras eso, al menos no de forma automatizada. El problema es que diferentes proveedores pueden tener diferentes capacidades, diferentes ofertas, flujos, ideas, etc. Esto significa que tendrá que usar diferentes recursos para que un proveedor diferente exprese el mismo concepto. Sin embargo, todo esto todavía se puede hacer en una sintaxis de configuración única y familiar y ser parte de un flujo de trabajo cohesivo.

Partes esenciales de una configuración de Terraform

  1. El propio binario de Terraform, que tienes que instalar
  2. Los archivos de código fuente, es decir, su configuración
  3. El estado (ya sea local o remoto) que representa los recursos que gestiona Terraform (más sobre esto más adelante)

Escribir la configuración de Terraform

El código de configuración de Terraform se escribe en archivos *.tf utilizando el lenguaje HCL. Hay una opción para usar el formato JSON ( *.tf.json ), pero está dirigido a máquinas y generación automática en lugar de humanos. Le recomiendo que se quede con HCL. No profundizaré en la sintaxis del lenguaje HCL; los documentos oficiales hacen un trabajo fantástico al describir cómo escribir HCL y cómo usar variables e interpolaciones. Solo mencionaré lo mínimo necesario para entender los ejemplos.

Dentro de los archivos de Terraform, se trata principalmente de recursos y fuentes de datos . Los recursos representan componentes de su infraestructura, por ejemplo, una instancia de AWS EC2, una instancia de RDS, un registro DNS de Route53 o una regla en un grupo de seguridad. Le permiten aprovisionarlos y cambiarlos dentro de la arquitectura de la nube.

Suponiendo que haya configurado Terraform, si emite una terraform apply , el siguiente código crearía una instancia EC2 completamente funcional (solo se muestran ciertas propiedades):

 resource "aws_instance" "bastion" { ami = "ami-db1688a2" # Amazon Linux 2 LTS Candidate AMI 2017.12.0 (HVM), SSD Volume Type - ami-db1688a2 instance_type = "t2.nano" key_name = "${var.key_name}" subnet_ vpc_security_group_ids = ["${aws_security_group.bastion.id}"] monitoring = "false" associate_public_ip_address = "true" disable_api_termination = "true" tags = { Name = "${var.project_tag}-bastion-${var.env}" Env = "${var.env}" Application ApplicationRole = "Bastion Host" Project = "${var.project_tag}" } }

Por otro lado, hay fuentes de datos que le permiten leer datos sobre componentes dados sin cambiarlos. ¿Quiere obtener el ID de AWS (ARN) de un certificado emitido por ACM? Usas una fuente de datos. La diferencia es que las fuentes de datos tienen el prefijo data_ cuando se hace referencia a ellas en los archivos de configuración.

 data "aws_acm_certificate" "ssl_cert" { domain = "*.example.com" statuses = ["ISSUED"] }

Lo anterior hace referencia a un certificado SSL de ACM emitido que se puede utilizar junto con los ALB de AWS. Sin embargo, antes de hacer eso, debe configurar su entorno.

Estructura de carpetas

Los entornos de Terraform (y sus estados) están separados por directorios. Terraform carga todos los archivos *.tf de un directorio en un espacio de nombres, por lo que el orden no importa. Recomiendo la siguiente estructura de directorios:

 /terraform/ |---> default_variables.tf (1) /stage/ (2) |---> terraform.tfvars (3) |---> default_variables.tf (4) |---> terraform.tf (5) |---> env_variables.tf (6) /prod/ /<env_name>/
  1. default_variables.tf : define todas las variables de nivel superior y, opcionalmente, sus valores predeterminados. Se pueden reutilizar en cada entorno (directorio anidado) con enlaces simbólicos.
  2. /stage/ : un directorio que contiene la configuración de un entorno completamente separado (aquí denominado stage , pero puede ser cualquier cosa). ¡Cualquier cambio realizado dentro de esta carpeta es totalmente independiente de otros entornos (env, como prod ), que es algo que desea para evitar estropear el entorno de producción con los cambios realizados en el stage !
  3. terraform.tfvars : define los valores de las variables. Los archivos .tfvars son similares a los archivos .env en que contienen los pares key=val para variables definidas. Por ejemplo, esto especifica el profile de AWS, el nombre_clave de AWS y la key_name de AWS que key_path . Se puede ignorar en Git.
  4. default_variables.tf : este es un enlace simbólico al archivo (2), que nos permite compartir variables independientes del entorno sin repetirnos.
  5. terraform.tf : esta es la configuración principal de cada entorno; contiene el bloque terraform {} que configura el back-end. También configuro proveedores aquí.
  6. env_variables.tf : este archivo contiene variables específicas de env. Etiqueto todos los recursos con Env=<env_name> en AWS, por lo que este archivo generalmente define solo una variable: env .

Por supuesto, esta no es la única forma de estructurar su entorno. Esto es algo que funcionó bien para mí al permitir una clara separación de preocupaciones.

Configuración de back-end

Ya mencioné el estado Terraform. Esta es una parte esencial del flujo de trabajo de Terraform. Quizás se pregunte si el estado es realmente obligatorio. ¿No podría Terraform simplemente consultar la API de AWS todo el tiempo para obtener el estado real de la infraestructura? Bueno, si lo piensa bien, Terraform necesita mantener un mapeo entre lo que administra en los archivos de configuración declarativos y a qué corresponden realmente esos archivos (en el entorno del proveedor de la nube). Tenga en cuenta que, al escribir archivos de configuración de Terraform, no le importan los ID de, por ejemplo, instancias EC2 individuales o los ARN que se crearán para los grupos de seguridad que publique. Sin embargo, internamente, Terraform necesita saber que un bloque de recursos determinado representa un recurso concreto con un ID/ARN. Esto es necesario para detectar cambios. Además, el estado se utiliza para realizar un seguimiento de las dependencias entre los recursos (¡también algo en lo que no tiene que pensar normalmente!). Se utilizan para construir un gráfico que puede (generalmente) paralelizarse y ejecutarse. Como siempre, recomiendo que lea la excelente documentación sobre el estado de Terraform y su propósito.

Dado que el estado es la única fuente de verdad para su arquitectura, debe asegurarse de que usted y su equipo estén siempre trabajando en su versión más actualizada y que no creen conflictos por el acceso no sincronizado al estado. No desea resolver conflictos de fusión en el archivo de estado, créame.

De forma predeterminada, Terraform almacena el estado en un archivo en el disco, ubicado en el directorio de trabajo actual (de cada entorno) como un archivo terraform.tfstate . Esto está bien si sabe que será el único desarrollador en el trabajo o simplemente está aprendiendo y experimentando con Terraform. Técnicamente, podría hacer que funcione en equipo porque puede enviar el estado a un repositorio de VCS. Pero entonces, ¡debería asegurarse de que todos estén trabajando siempre en la última versión del estado y que nadie haga cambios al mismo tiempo! Esto es generalmente un gran dolor de cabeza y lo desaconsejo enfáticamente. Además, si alguien se une a su operación de un solo desarrollador, aún tendría que configurar un lugar alternativo para el estado.

Afortunadamente, este es un problema con una buena solución integrada en Terraform: el llamado Estado Remoto . Para que funcione el estado remoto, debe configurar el back-end con uno de los proveedores de back-end disponibles. El siguiente ejemplo de back-end se basará en AWS S3 y AWS DynamoDB (base de datos AWS NoSQL). Puede usar solo S3, pero luego pierde el mecanismo de bloqueo de estado y verificación de coherencia (no recomendado). Si anteriormente usaba solo el estado local, la configuración de un back-end remoto le ofrecerá la opción de migrar su estado la primera vez, para que no pierda nada. Puede leer más sobre la configuración de back-end aquí.

Desafortunadamente, hay un problema del huevo y la gallina: el depósito S3 y la tabla de DynamoDB deben crearse manualmente. ¡Terraform no puede crearlos automáticamente ya que aún no hay un estado! Bueno, hay algunas soluciones como https://github.com/gruntwork-io/terragrunt que automatizan eso usando AWS CLI, pero no quiero desviarme del tema principal de esta publicación de blog.

Los aspectos importantes que debe saber sobre la configuración de back-end de S3 y DynamoDB son:

  1. Habilite el control de versiones en el depósito S3 para estar a salvo de errores humanos y la Ley de Murphy.
  2. La tabla de DynamoDB tiene un límite de tasa de lecturas y escrituras (llamado capacidad). Si realiza muchos cambios en el estado remoto, asegúrese de habilitar DynamoDB AutoScaling para esa tabla o configure límites de lectura y escritura lo suficientemente altos. De lo contrario, Terraform obtendrá errores HTTP 400 de la API de AWS al ejecutar muchas llamadas.

Para resumir todo, la siguiente configuración de back-end se puede colocar en terraform.tf para configurar el estado remoto en S3 y DynamoDB.

 terraform { # Sometimes you may want to require a certain version of Terraform required_version = ">= 0.11.7" # Stores remote state, required for distributed teams # Bucket & dynamoDB table have to be created manually if they do not exist # See: https://github.com/hashicorp/terraform/issues/12780 backend "s3" { bucket = "my-company-terraform-state" key = "app-name/stage" region = "eu-west-1" # 5/5 R/W Capacity might not be enough for heavy, burst work (resulting in 400s). Consider enabling Auto Scaling on the table. # See: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ProvisionedThroughput.html dynamodb_table = "terraform-state-lock-table" } }

Esto es mucho para asimilar a la vez, pero recuerda, lo haces una vez para cada entorno y luego puedes olvidarte de eso. Si necesita aún más control sobre el bloqueo de estado, está HashiCorp Terraform Enterprise, pero no lo cubriré aquí.

Proveedores

Para que este back-end sea accesible y poder comunicarse con nuestro proveedor de nube, necesitamos configurar el llamado proveedor . El siguiente bloque se puede colocar en el archivo terraform.tf (para cada entorno):

 provider "aws" { profile = "${var.profile}" region = "${var.region}" version = "~> 1.23.0" }

Las variables para el profile y la region se almacenan en el archivo terraform.tfvars , que puede ignorarse. La variable de profile hace referencia a un perfil con nombre que contiene credenciales de seguridad para la nube de AWS mediante el archivo de credenciales estándar. Tenga en cuenta que también estoy configurando algunas restricciones de versión. No desea que Terraform actualice los complementos de su proveedor sin su conocimiento en cada inicialización de back-end. Especialmente dado que hay una versión 2.x del proveedor de AWS que requiere una actualización cuidadosa.

Inicialización de back-end

Cada configuración de back-end requiere un paso de inicialización al principio y cada vez que se realiza un cambio. La inicialización también configura los módulos de Terraform (más sobre estos más adelante), por lo que cuando los agrega, también debe volver a ejecutar el paso de inicio. Es seguro ejecutar esta operación varias veces. Tenga en cuenta que, durante la inicialización del back-end, Terraform no puede leer todas las variables para configurar el estado, ni debe ser por razones de seguridad (por ejemplo, claves secretas). Para superar esto y, en nuestro caso, usar un perfil de AWS diferente al predeterminado, puede usar la opción -backend-config con acepta pares de variables k=v . Esto se llama configuración parcial.

 terraform init -backend-config=profile=<aws_profile_name>

Los archivos de Terraform comparten un ámbito que está restringido a un directorio determinado. Esto significa que una subcarpeta no está directamente conectada al código del directorio principal. Sin embargo, está definiendo un módulo que permite la reutilización de código, la gestión de la complejidad y el intercambio.

El flujo de trabajo

El flujo de trabajo general cuando se trabaja con el código de Terraform es el siguiente:

  1. Escriba la configuración para su infraestructura.
  2. Vea qué cambios reales hará ( terraform plan ).
  3. Opcionalmente, ejecute los cambios exactos que vio en el paso 2 ( terraform apply ).
  4. IR A 1

Plan de terraformación

El comando de plan de Terraform le presentará una lista de cambios que se realizarán en su infraestructura al emitir el comando de apply . Es seguro emitir el plan varias veces, ya que por sí solo no cambia nada.

Cómo leer un plan

Los objetos en Terraform (recursos y fuentes de datos) son fácilmente identificables por sus nombres completos.

  1. En el caso de los recursos, el ID podría tener este aspecto: <resource_type>.<resource_name> —p. ej., aws_ecs_service.this .
  2. En el caso de recursos dentro de módulos, tenemos un nombre de módulo adicional: module.<module_name>.<resource_type>.<resource_name> —p. ej., module.my_service_ecs_service_task.aws_ecs_service.this .
  3. Orígenes de datos (dentro y fuera de un módulo): (module.<module_name>).data.<resource_type>.<resource_name> —p. ej., module.my_service_ecs_service_task.data.aws_ecs_task_definition.this .

El tipo de recurso es específico de un proveedor determinado y normalmente incluye su nombre ( aws_… ). La lista completa de recursos disponibles para AWS se puede encontrar en los documentos.

Hay cinco acciones que un plan mostrará para recursos dados:

  1. [+] Agregar: se creará un nuevo recurso.
  2. [-] Destruir: un recurso se destruirá por completo.
  3. [~] Modificar en el lugar: se modificará un recurso y se cambiarán uno o más parámetros. Esto es generalmente seguro. Terraform le mostrará qué parámetros se modificarán y cómo (si es posible).
  4. [- / +] : el recurso se eliminará y luego se volverá a crear con nuevos parámetros. Esto sucede si se realizó un cambio en un parámetro que no se puede cambiar en el lugar. Terraform le mostrará qué cambios fuerzan una recreación de un recurso con el siguiente comentario en rojo: (forces new resource) . Esto es potencialmente peligroso, ya que hay un período en el que el recurso no existirá en absoluto. También puede romper otras dependencias conectadas. Recomendaría evitar tales cambios a menos que sepa cuáles serán las consecuencias o no le importe un tiempo de inactividad.
  5. [<=] : se leerá una fuente de datos. Esta es una operación de sólo lectura.

Terraform AWS: un "plan" de muestra

Arriba hay un plan de ejemplo. Los cambios que hice fueron:

  • Cambió el tipo de instance_type de la primera instancia de bastión
  • Se agregó una segunda instancia de bastión.
  • Cambió el nombre de un grupo de seguridad.

Tenga en cuenta que el último cambio es una regla del grupo de seguridad que detectó automáticamente un cambio en el nombre del grupo principal. En mi opinión, todo el plan es muy legible. Algunos valores de parámetros se muestran como <computed> ; no significa que se cambiarán, sino que no se pueden recuperar ni presentar en esta etapa (como el nombre de SG que aún no se ha creado). Recuerde que solo los recursos modificados se mostrarán durante el comando del plan . Todo lo que se omita no se tocará.

Por lo general, los comandos de Terraform aceptan parámetros adicionales. El parámetro más importante del comando del plan es la opción -out , que guardará su plan en el disco. Este plan guardado se puede ejecutar (exactamente como se guardó) mediante el comando de aplicación. Esto es muy importante, ya que de lo contrario podría haber un cambio realizado, por ejemplo, por colegas entre la emisión de un plan y la emisión de la apply . Ejemplo:

  1. Emita el plan y verifique que se vea bien.
  2. Alguien cambia el estado, sin que usted lo sepa.
  3. Problema apply y, ¡ups, hizo algo diferente de lo que estaba planeado!

Afortunadamente, este flujo de trabajo se ha mejorado en Terraform v0.11.0. Desde esta versión, el comando de apply le presenta automáticamente un plan que luego debe aprobar (escribiendo explícitamente yes ). La ventaja es que, si aplica este plan, se ejecutará exactamente como se presenta.

Otra opción útil es -destroy , que te mostrará el plan que resultará en la destrucción de todos los recursos que maneja Terraform. También puede apuntar a recursos específicos para su destrucción con la opción -target .

Aplicar Terraform

Cuando aplicamos un plan determinado, Terraform sale y bloquea nuestro estado para garantizar la exclusividad. Luego procede a cambiar los recursos y, al final, impulsa un estado actualizado. Una cosa a tener en cuenta es que algunos recursos tardan más en finalizar que otros. Por ejemplo, la creación de una instancia de AWS RDS puede tardar más de 12 minutos y Terraform esperará a que finalice. Obviamente, Terraform es lo suficientemente inteligente como para no bloquear todas las demás operaciones con esto. crea un gráfico dirigido de los cambios solicitados y, si no hay interdependencias, utiliza el paralelismo para acelerar la ejecución.

Importación de recursos

A menudo, Terraform y otras soluciones de "configuración como código" se introducen gradualmente en un entorno ya existente. La transición es realmente fácil. Básicamente, todo lo que no está definido dentro de Terraform se deja sin administrar y sin cambios. Terraform solo se preocupa por lo que gestiona. Por supuesto, es posible que esto presente problemas, por ejemplo, si Terraform se basa en algún punto final que existe fuera de su configuración y luego se destruye manualmente. Terraform no sabe nada de esto y, por lo tanto, no puede volver a crear este recurso durante los cambios de estado, lo que provocará errores de la API durante la ejecución del plan. Esto no es algo por lo que me preocuparía; los beneficios de introducir Terraform superan con creces los inconvenientes.

Para facilitar un poco el proceso de introducción, Terraform incluye el comando de import . Se utiliza para introducir recursos externos ya existentes en el estado de Terraform y permitirle administrar esos recursos. Desafortunadamente, Terraform no puede generar automáticamente configuraciones para esos módulos importados, al menos en el momento de escribir este artículo. Hay planes para esta funcionalidad, pero por ahora, primero debe escribir la definición del recurso en Terraform y luego importar este recurso para indicarle a Terraform que comience a administrarlo. Una vez importado al estado (y durante la planificación de la ejecución), verá todas las diferencias entre lo que escribió en los archivos .tf y lo que realmente existe en la nube. De esta manera, puede modificar aún más la configuración. Idealmente, no deberían aparecer cambios, lo que significaría que la configuración de Terraform refleja lo que ya está en la nube 1:1.

Módulos

Los módulos son una parte esencial de la configuración de Terraform y le recomiendo que los adopte y los use con frecuencia. Le proporcionan una forma de reutilizar ciertos componentes. Un ejemplo sería el clúster de AWS ECS, que se utiliza para ejecutar contenedores Docker. Para que un clúster de este tipo funcione, necesita configurar muchos recursos separados: el clúster en sí, la configuración de lanzamiento que administraría instancias EC2 separadas, repositorios de contenedores para imágenes, el grupo y la política de ajuste de escala automático, etc. Por lo general, necesita tener clústeres separados para entornos y/o aplicaciones separados.

Una forma de superar esto sería copiar y pegar la configuración, pero esta es obviamente una solución miope. Cometerías errores haciendo las actualizaciones más simples.

Los módulos le permiten encapsular todos esos recursos separados en un bloque de configuración (llamado módulo). Los módulos definen entradas y salidas que son las interfaces por las cuales se comunica con “el mundo exterior” (o el código de llamada). Además, los módulos se pueden anidar dentro de otros módulos, lo que le permite crear rápidamente entornos completamente separados.

Módulos Locales y Remotos

De manera similar al estado, puede tener módulos locales o módulos remotos. Los módulos locales se almacenan junto con su configuración de Terraform (en un directorio separado, fuera de cada entorno pero en el mismo repositorio). Esto está bien si tiene una arquitectura simple y no comparte esos módulos.

Esto, sin embargo, tiene limitaciones. Es difícil versionar esos módulos y compartirlos. El control de versiones es importante porque es posible que desee utilizar la versión 1.0.0 de su módulo ECS en producción, pero le gustaría experimentar con la versión 1.1.0 en un entorno de prueba. Si el módulo se almacenó junto con su código, cada cambio en el código del módulo se reflejaría en cada env (una vez que se ejecuta la apply ), lo que generalmente no es deseable.

Un enfoque útil para el control de versiones de los módulos es ponerlos todos en un repositorio separado, por ejemplo, your-company/terraform-modules. Luego, al hacer referencia a esos módulos dentro de su configuración de Terraform, puede usar un enlace VCS como fuente:

 module "my-module" { source = "[email protected]:your-company/terraform-modules.git//modules/my-module?ref=v1.1.0" ... }

Aquí hago referencia a una versión 1.1.0 de my-module (ruta específica) que puedo probar independientemente de otras versiones del mismo módulo en diferentes entornos.

Aparte de eso, está el problema de la capacidad de descubrimiento y compartibilidad de los módulos. Debe esforzarse por escribir módulos bien documentados y reutilizables. Por lo general, tendrá diferentes configuraciones de Terraform para diferentes aplicaciones y es posible que desee compartir el mismo módulo entre ellas. Sin extraerlos a un repositorio separado, esto sería muy difícil.

Usando módulos

Los módulos se pueden referenciar fácilmente en entornos de Terraform definiendo un bloque de módulo especial. Aquí hay un ejemplo de un bloque de este tipo para un módulo ECS hipotético:

 module "my_service_ecs_cluster" { source = "../modules/ecs_cluster" cluster_name = "my-ecs-service-${var.env}" repository_names = [ "my-ecs-service-${var.env}/api", "my-ecs-service-${var.env}/nginx", "my-ecs-service-${var.env}/docs", ] service_name = "my-ecs-service-${var.env}" ecs_instance_type = "t2.nano" min_size = "1" max_size = "1" use_autoscaling = false alb_target_group_arn = "${module.my_alb.target_group_arn}" subnets = "${local.my_private_subnets}" security_groups = "${aws_security_group.my_ecs.id}" key_name = "${var.key_name}" env_tag = "${var.env}" project_tag = "${var.project_tag}" application_tag = "${var.api_app_tag}" asg_tag = "${var.api_app_tag}-asg" }

Todas las opciones que se pasan (excepto algunas globales como source ) se definieron dentro de la configuración de este módulo como entradas (variables).

Registro de Módulos

Recientemente, HashiCorp lanzó un registro oficial del módulo Terraform. Esta es una gran noticia, ya que ahora puede aprovechar el conocimiento de la comunidad que ya ha desarrollado módulos probados en batalla. Además, algunos de ellos tienen la insignia "Módulo verificado de HashiCorp", lo que significa que están examinados y mantenidos activamente y le brindan una confianza adicional.

Anteriormente, tenía que escribir sus propios módulos desde cero (y aprender de sus errores) o usar módulos publicados en GitHub y otros lugares, sin ninguna garantía en cuanto a su comportamiento (¡aparte de leer el código!)

Compartir datos entre entornos

Idealmente, los entornos deberían estar totalmente separados, incluso utilizando diferentes cuentas de AWS. In reality, there are cases when one Terraform environment might use some information in another environment. This is especially true if you are gradually converting your architecture to use Terraform. One example might be that you have a global env that provides certain resources to other envs.

Let's say env global shares data with stage . For this to work, you can define outputs at the main level of the environment like so:

 output "vpc_id" { value = "${module.network.vpc_id}" }

Then, in the stage environment, you define a datasource that points to the remote state of global :

 data "terraform_remote_state" "global" { backend = "s3" config { bucket = "my-app-terraform-state" key = "terraform/global" region = "${var.region}" dynamodb_table = "terraform-state-lock-table" profile = "${var.profile}" } }

Now, you can use this datasource as any other and access all the values that were defined in global 's outputs:

 vpc_

Words of Caution

Terraform has a lot of pros. I use it daily in production environments and consider it stable enough for such work. Having said that, Terraform is still under active development. Thus, you will stumble on bugs and quirks.

Where to Report Issues and Monitor Changes

First of all, remember: Terraform has a separate core repo and repositories for each provider (eg, AWS). If you encounter issues, make sure to check both the core repo and the separate provider repositories for issues and/or opened pull requests with fixes. GitHub is really the best place to search for bugs and fixes as it is very active and welcoming.

This also means that provider plugins are versioned separately, so make sure you follow their changelogs as well as the core one. Most of the bugs I have encountered were resolved by upgrading the AWS provider which already had a fix.

Can't Cheat Your Way out of Cloud Knowledge

You cannot use Terraform to configure and manage infrastructure if you have no knowledge of how a given provider works. I would say this is a misconception and not a downside, since Terraform has been designed to augment and improve the workflow of configuration management and not to be some magic dust that you randomly sprinkle around and—poof! Environments grow! You still need a solid knowledge of a security model of each cloud, how to write, eg, AWS policies, what resources are available, and how they interact.

Prefer Separate Resources That Are Explicitly linked

There are certain resources—for example, the AWS security group or AWS route table—that allow you to configure the security rules and routes respectively, directly inside their own block. This is tempting, as it looks like less work but in fact will cause you trouble. The problems start when you are changing those rules on subsequent passes. The whole resource will be marked as being changed even if only one route/security rule is being introduced. It also gives implicit ordering to those rules and makes it harder to follow the changes. Thankfully, mixing those both approaches is not allowed now (see the note).

Best-practice example, with explicitly linked resources:

 resource "aws_security_group" "my_sg" { name = "${var.app_tag}-my-sg" ... } resource "aws_security_group_rule" "rule_one" { security_group_ ... } resource "aws_security_group_rule" "rule_two" { security_group_ ... }

Terraform plan Doesn't Always Detect Issues and Conflicts

I already mentioned this in the case where you were managing resources with Terraform that were relying on other, unmanaged infrastructure. But there are more trivial examples—for example, you will get an error if your EC2 instance has Termination Protection enabled, even though plan would show you it's OK to destroy it. You can argue that this is what Termination Protection has been designed for, and I agree, but there are more examples of things you can do in theory/on plan but when executed will deadlock or error out. For example, you cannot remove a network interface if something is using it—you get a deadlock without an option to gracefully recover.

Syntax Quirks

There are also quirks related to how HCLv1 (the syntax language Terraform uses) has been designed. It has a couple of frustrating quirks. There is work underway to provide an improved version of the parser for HCLv2. The best way to read on the current limitations and the plan to overcome them is this fantastic blog series. In the meantime, there are workarounds for most of those issues. They are not pretty and they will fail once v0.12 comes out, but hey, it is what it is.

When State Update Fails

It sometimes happens that Terraform is not able to correctly push an updated state. This is usually due to underlying network issues. The solution is to retry the state update instead of running apply again, which will fork the state .

Another issue might happen when state lock (the synchronization primitive that prevents multiple users to update the same state) fails to be taken down by Terraform. This involves running terraform force-unlock with the lock ID to take it down manually.

Thankfully, in case of such problems, Terraform provides you with a good description and steps you need to make to fix it.

Not Everything Is Fun to Manage Through Terraform

There are certain cases where Terraform is not my tool of choice. For example, configuring AWS CodePipeline and CodeBuild projects (AWS equivalent of CI/CD pipeline) is cumbersome when done through Terraform. You need to define each step through very verbose configuration blocks and things like “Login via GitHub” are a lot more complicated than using the UI. Of course, it's still possible if you prefer to have it codified. Well, I guess it's a good candidate for a well-written module!

Same thing goes for managing AWS API Gateway endpoints. In this case, using a dedicated serverless framework would be a better option.

When configuring AWS resources with Terraform, you will find yourself writing a lot of policies. Policies that would otherwise often be auto-generated for you (when using the UI). For those, I'd recommend the AWS Visual Editor and then copying the resulting policy JSON into Terraform.

Conclusión

Using Terraform has been fun and I'll continue doing so. Initial steps can be a bumpy ride, but there are more and more resources that help to ease you in.

I'd definitely recommend taking Terraform for a spin and simply playing with it. Remember, though—be safe and test it out on a non-essential account. If you are eligible for AWS Free Tier, use it as a 12-month free trial. Just be aware it has limitations as to what you can provision. Otherwise, just make sure you spin the cheapest resources, like t3.nano instances.

I highly recommend extensions for Terraform support in various code editors. For Visual Studio Code, there is one with syntax highlighting, formatting, validation and linting support.

It's always valuable to learn new things and evaluate new tools. I found that Terraform helped me immensely in managing my infrastructure. I think working with Terraform will only get easier and more fun, especially once v0.12.0 ships with a major upgrade to the HCL syntax and solve most of the quirks. The traction and community around Terraform are active and vibrant. You can find a lot of great resources on things I didn't manage to cover in a single blogs post, eg, a detailed guide on how to write modules.

Related: Terraform vs. CloudFormation: The Definitive Guide