La infraestructura como código (IaC, Infrastructure as Code) es un término para designar herramientas que gestionan recursos computacionales mediante ficheros estructurados. Nos permiten codificar la infraestructura que queremos y que este código sea ejecutado hasta lograr que esta sea aprovisionada y configurada.
Terraform es un programa para gestionar IaC. Define una estructura y sintaxis generales para el código y lo ejecuta para alcanzar el estado deseado. Terraform es, por tanto, declarativo: lo que se indica es el estado final que quieres para la plataforma, no los pasos para llegar a ese estado (lo que sería imperativo).
Al pensar en IaC, solemos asociarlo a los grandes proveedores de Cloud, como AWS, GCP y Azure. Lo que puede resultar algo más sorprendente es que Terraform también es capaz de gestionar otras herramientas como Kubernetes, Grafana, Chef, MySQL, RabbitMQ, Consul, Github, entre otras. Lo mejor es echar un ojo a la página de proveedores.
Con Terraform se pueden gestionar más de 147 clouds, plataformas, sistemas y herramientas. Para conseguirlo existen los providers. Estos consisten en partes software que se encargan de la comunicación con la plataforma o herramienta. Definen su propio conjunto de elementos, con sus propiedades y posibles valores.
Un error común con los providers consiste en pensar que existen recursos comunes que después son traducidos a recursos concretos. No es así. Cada proveedor tiene su propio conjunto de recursos y módulos y no existe ningún tipo de traducción entre ellos. Por ejemplo, si se escribe un código para desplegar algo en GCP, no puede ser directamente trasladado a AWS, es necesario reescribirlo. Se podría crear algún tipo de módulo de estandarización, que abstraiga de las diferencias entre ellos. Sin embargo, al existir conceptos fundamentalmente diferentes, probablemente se acabarían realizando multitud de ajustes hasta descartar la idea.
Existen cuatro elementos principales en Terraform:
· Providers: Definen los elementos data y resources para plataformas y herramientas específicas.
· Resources: Definen elementos de la infraestructura que pueden ser aprovisionados, junto con sus propiedades. Una vez desplegados, proporcionan salidas que pueden utilizarse como entrada de otros elementos o como salida final de la ejecución, para mostrar al usuario.
· Data: Recuperan información acerca del estado actual de un recurso u otro elemento y proporcionan salidas.
· Módulos: Son abstracciones para agrupar conjuntos de data y resources. Se pueden configurar entradas y salidas.
Al conectar salidas y entradas, se definen las dependencias, que son utilizadas para inferir el orden de ejecución. También es posible forzar las dependencias sin usar salidas.
Al ejecutar Terraform por primera vez, todos los recursos son creados en función de las entradas indicadas. Donde es posible, estos recursos se marcan con Tags que indican que son gestionados por Terraform.
Si ahora modificas el código y lo ejecutas, Terraform realizará todos los cambios necesarios para llegar al nuevo estado. Si algo no puede ser reemplazado en caliente, será recreado, provocando a su vez que se modifiquen todos los dependientes que sean necesarios. Si un recurso creado con Terraform ya no aparece en tu código, será eliminado. Incluso, si el estado actual no coincide con lo codificado (porque, por ejemplo, se ha modificado de forma externa), se actualizará.
Aquellos elementos que no fueron creados por Terraform y no han sido importados posteriormente, no serán gestionados por la herramienta y no se realizará ninguna acción con ellos. Es importante tener esto en cuenta y que, en algunas ocasiones, lo que parece un cambio manual, por ejemplo en una consola, en realidad implica la creación de nuevos recursos que no serían gestionados por Terraform, lo que podría derivar en problemas al modificar o eliminar el componente principal.
Es posible mezclar varios proveedores en el mismo proyecto de Terraform. Esto es muy útil para instalar y configurar herramientas creadas como infraestructura. Por ejemplo, se puede crear una base de datos y pasar las salidas con los datos de conexión al proveedor de bases de datos para crear usuarios o esquemas.
No existe ninguna manera de crear la estructura de una base de datos y realizar una carga inicial. Para ello, se debería utilizar Ansible. Sin embargo, existe una opción.
Existe otro elemento en Terraform, los provisioners. Con ellos se puede realizar cualquier tarea que pueda hacerse mediante comandos de consola. Estos elementos no están recomendados por Terraform (y por buenos motivos) excepto como último recurso. No son la solución más limpia, aunque pueden ayudar a resolver rápidamente un problema (de hecho serían necesarios para ejecutar Ansible, a no ser que se utilice algún tipo de pipeline).
Los provisioners se definen unidos a resources y son ejecutados después de la creación o actualización del recurso y solo si se desencadena mediante trigger. Por tanto, lo que podría hacerse es crear el esquema con el provider de la base de datos, que sería un recurso (al menos con mysql) y definir un provisioner que ejecute un comando de mysql con lo que necesitemos. Este comando solo sería ejecutado si la base de datos o su esquema son creados o actualizados. En cualquier caso, se debe ser extremadamente cuidadoso para asegurarse de no perder datos críticos.
Existen también providers especiales para generar contraseñas aleatorias, gestionar certificados, etc. Se debe tener en cuenta que las variables de Terraform no pueden almacenar datos binarios, es necesario utilizar base64 para ello. A pesar de ello existen funciones que pueden usar ficheros, leerlos y procesarlos. Un posible uso sería para crear un trigger en nuestro provisioner anterior para que se ejecute cada vez que se modifique un fichero. En este caso, probablemente quieras usar la función filesha1. También se pueden usar ficheros como plantillas, donde se pueden incluir variables que serán sustituidas por inputs.
A continuación, presentamos algunas buenas prácticas habituales con Terraform, junto con algunas que hemos aprendido al trabajar con esta herramienta:
· Modularización
Cada vez que crees un nuevo componente para tu infraestructura que pueda ser reutilizado, hazlo en un módulo. Intenta pensar en algunas entradas que puedan ser interesantes y estable un valor por defecto para aquellas que no sean necesarias explícitamente.
· Dependencias y triggers
No confíes en que el código se ejecutará en el orden en que aparece. Utiliza dependencias para controlar el orden de ejecución. Cuidado con las dependencias circulares.
· Utiliza variables
No utilices valores fijos, aunque pienses que no pueden cambiar. En su lugar, crea un fichero con variables y dales esos valores por defecto. Usar variables es especialmente importante en valores generados por otros recursos, aunque puedas saber el valor de salida que tendrá, utiliza la salida. Esto permitirá establecer la dependencia y evitará errores en caso de cambios inesperados.
· IaC es código, trátalo como código
Utiliza un gestor de control de versiones como git, se pagará solo. También puedes usar un gestor de incidencias, CI/CD, etc. la IaC es código!
· Descentraliza
Los ficheros de estado pueden ser almacenados en remoto. Esto permitirá a un grupo de programadores IaC trabajar y ejecutar Terraform de un modo consistente. Sin él, cada persona tendría su propio estado no sincronizado y no se podría lanzar contra la misma infraestructura (bueno, se podría, pero sería un desastre). También se debería establecer un mecanismo de bloqueos para evitar ejecuciones concurrentes.
· Descarta y crea frecuentemente mientras desarrollas
Al automatizar una infraestructura, es habitual realizar varios intentos antes de codificarlo. Esto puede llevar a que falten algunas partes o configuraciones en el código, que no sea del todo correcto, o que solo funcione en ciertas situaciones. Si eliminas por completo la infraestructura al final del día y la recreas al día anterior, sufrirás un poco más cada día, pero obtendrás un producto mucho más estable y maduro. Si el proceso tarda mucho, siempre puedes hacerlo con menos frecuencia o, mejor aún, automatizarlo para que todo esté correcto al comenzar la jornada.
Siempre finalizo señalando cuándo tiene sentido utilizar o no una tecnología, ya que todas tienen sus pros y sus contras. En esta ocasión diría: úsalo siempre. Incluso para ese pequeño proyecto que habitualmente termina siendo más grande. El único caso que se me ocurre para no utilizarlo es para pruebas rápidas (especialmente si son pequeñas).
El uso de IaC nos proporciona infraestructura repetible, mantenible, reutilizable, versionada y más estable, a la vez que nos hace más conscientes de lo que hacemos. Por supuesto, requiere algo más de tiempo para escribir el código, especialmente cuando utilizamos una guía donde todo se hace con comandos aunque, al final, los beneficios compensan con creces este tiempo extra.
Conoce al autor
Alejandro Ladera
Alejandro es Analista Senior del área de System Development&Integration, en la práctica de DxD de Deloitte. Está especializado en tecnología Java, en la que cuenta con 5 certificaciones de Oracle y 8 años de experiencia profesional. Ha trabajado, principalmente, en sistemas de venta del sector de transporte para grandes clientes, con los mayores volúmenes de transacciones en España. También ha llevado a cabo con éxito pruebas de stress automáticas en entornos grandes y complejos, así como mejoras de eficiencia con gran impacto.