Índice de contenidos
- 1. Introducción
- 2. Para qué usar Terraform
- 3. Estructura de módulo de Terraform
- 3.1. Convenciones sobre la escritura de código de Terraform
- 3.2. Convenciones para facilitar el uso del repositorio
- 3.3. Convenciones para garantizar la consistencia
- 4. Mono repositorio de módulos vs multi repositorios
- 5. Conclusiones
- 6. Referencias
1. Introducción
A la hora de definir tu infraestructura en Terraform, tenemos varias opciones a la hora de definir nuestros módulos de Terraform.
Este tutorial se basa en mi experiencia trabajando con Terraform y los criterios utilizados para definir la estructura de módulos de Terraform de forma que sea usable y escalable en función de la magnitud del proyecto.
Para ello, nos centraremos en responder a las siguientes preguntas:
– ¿Para qué deberíamos usar Terraform?
– ¿Qué estructura debería tener la unidad del módulo de Terraform?
– ¿Creamos un repositorio por módulo de Terraform, o un repositorio que tenga todos los módulos al mismo tiempo?
2. Para qué usar Terraform
Con esto pretendemos definir la línea de qué contenido debería ir dentro de un módulo de terraform y cual no.
A continuación escribiremos para qué usaremos Terraform y para que no:
- Terraform define qué se despliega: El código de Terraform debería hacer referencia a qué se despliega, y gestionar la lógica para que desde la llamada del módulo (por medio de las variables) definas el comportamiento del mismo.
- Terraform NO tiene información contextual: Los módulos de Terraform (y toda la infraestructura) contienen información sobre qué recursos desplegar las opciones de configuración de los mismos, del mismo modo que en la programación orientada a objetos tienes una clase y sus respectivas instancias.
La configuración de instancia sería equivalente a la configuración de contexto y no debería estar dentro del código.
Por ejemplo, evitaríamos tener condiciones dentro de nuestro módulo que dependan de si estamos en producción o en desarrollo
Nuestro módulo de Terraform debería pedirnos cambiar si cambia la especificación de qué tiene que hacer (es decir, si necesita más recursos, o los que tiene necesitan ser capaces de ser configurados de otra forma). Si nuestro módulo tiene que cambiar porque ha cambiado el nombre del entorno, o porque queremos desplegar algo en otra cuenta diferente, es que no estamos definiendo bien los límites y está metiéndose en el CÓMO se tiene que desplegar.
3. Estructura de módulo de Terraform
Es importante (en general, no importa el tamaño del equipo) que el código tenga siempre la misma estructura.
A la hora de escribir código, la mayor parte del tiempo de desarrollo nos lo pasamos leyendo, y la parte de escritura suele ser bastante menor.
Una buena estructura debería:
- Ser consistente: Nuestro código debería parecer escrito por una única persona, lo que significa que todo el equipo debería seguir las mismas reglas de forma que se facilite el esperar donde está un código determinado, y saber dónde buscar cada uno de los elementos de un módulo. También, debería estar escrito en el mismo idioma.
- Ser intuitiva: Aunque tenemos que tener una serie de reglas, estas deberían ser intuitivas de forma que la curva de aprendizaje para un nuevo integrante del equipo sea sencilla.
- Ser simple: Todo se puede complicar tanto como queramos (os recomiendo echarle un vistazo a este hilo de stack overflow dónde se busca la creatividad de la gente para escribir un «Hola mundo» de la forma más compleja posible. Debido a este tipo de motivos, y por muy bien justificado que esté tu criterio, Simplifícalo, ¡idiota!
- Ser sencilla de usar: Que algo sea simple, no significa que sea sencillo de utilizar. Necesitamos que un nuevo miembro no tenga que saber cómo funcionan todas las cosas para empezar a trabajar, y en algunos casos donde la tecnología puede variar (como la salida de Open Tofu por la reciente compra de Hashicorp por IBM, la tecnología subyacente no debería cambiar lo que nosotros podemos hacer con el repositorio.
- Tener documentación: La documentación debería estar lo más cercana posible al código, de forma que sea más fácil que la cambies cuando se modifique el módulo. Además, esta debería ser generada automáticamente en base al código para garantizar que no se nos olvida.
- Ser replicable: Necesitamos que el entorno de trabajo para trabajar con el repositorio sea sencillo de configurar para todo el mundo. Para ello, necesitamos ser explícitos respecto a qué versiones necesita el repositorio para funcionar, así como los binarios que se necesiten para que funcione correctamente.
Una estructura que cumple los principios previos es:
. ├── .editorconfig ├── .gitignore ├── .pre-commit-config ├── .pre-commit-config.yaml ├── .releaserc.json ├── CHANGELOG.md ├── Makefile ├── Pipfile ├── README.md ├── data.tf ├── docs-source │ └── headers.md ├── locals.tf ├── main.tf ├── outputs.tf ├── tests ├── variables.tf └── versions.tf
A continuación explicaremos estos ficheros y cómo nos ayudan a mantenerlo simple:
3.1. Convenciones sobre la escritura de código de Terraform
- Los recursos del módulo se encuentran en
main.tf
. - Los datasources del módulo se encuentran en
data.tf
. - Los parámetros de entrada del módulo Terraform se definen en
variables.tf
. - Los atributos de salida del módulo se definen en
variables.tf
. - Las variables locales del módulo se definen en
locals.tf
. - Las especificaciones del módulo (versión de Terraform compatible con el módulo así como aliases internos en caso de existir) se definen en
versions.tf
. - La documentación del módulo (qué hace, detalles y limitaciones del módulo) se definen en
docs-source/headers.md
. - Nada de directorios internos a módulos internos como regla para separar los diferentes elementos que tiene el módulo. Si se te hace complicado leer los recursos que tiene un módulo solo teniendo en
main.tf
, es un indicativo de que el módulo es demasiado grande, y deba ser divido
Si solo has trabajado con Terraform, aquí te faltarán mínimo el contenido de dos bloques obligatorios para usar Terraform: provider
y backend
.
¿En qué fichero de los previos se encuentran estos recursos? En ninguno, porque opino que el código de Terraform siempre debería ir acompañado de Terragrunt para sacarle todo el partido, independientemente del tamaño del proyecto en el que estés. Esto es consecuencia de lo hablado en el apartado anterior sobre para qué utilizar Terraform.
En un futuro tutorial hablaré sobre la cómo definir la estructura de tu repositorio de Terragrunt, y la integración de ambos.
3.2. Convenciones para facilitar el uso del repositorio
- Uso de Makefile para definir las acciones dentro del repositorio: Los Makefiles serían el los métodos de la programación orientada a objetos. En este caso, definen los diferentes comportamientos que tiene el repositorio de forma que todas las acciones que se puedan realizar dentro del repositorio están definidas dentro de este fichero.
En un futuro haré un par de tutoriales sobre los Makefiles hablando de esto en detalle.
En el equipo, si entramos en un repositorio, y este tiene un Makefile, ejecutar el comando make help debería decirnos qué somos capaces de hacer dentro de ese repositorio. - Uso de Pre-commit para facilitar que se cumplan un conjunto de acciones antes de realizar un commit.
Estas tareas son tan personalizables como se quiera, y van desde asegurarnos que los commits tienen un formato determinado, a ejecutar los tests antes de cada commit o asegurarnos de que el código sigue las mismas reglas de formato.
Estas validaciones están solo del lado del cliente, de forma que necesitas que cada miembro del equipo las siga, pero una vez el equipo se acostumbra es muy cómodo saber que tu código sigue las convenciones del equipo si has sido capaz de realizar un commit y que no se te pasa nada a la hora de publicar código.
Estas dos directivas nos facilitan:
– Saber qué puedes hacer con ese repositorio (validar que todo sigue las reglas de consistencia, aplicar un linter para formato, ejecutar las pruebas automáticas)
– Ejecutar las validaciones pertinentes para garantizar la consistencia antes de hacer push del código de forma que todo lo que llegue al remoto tendrá cierta consistencia.
3.3. Convenciones para garantizar la consistencia
- .editorconfig: Es un fichero que nos ayuda a mantener la consistencia del estilo de código, que es ampliamente reconocido por los IDEs más habituales (hasta para VIM :))
- .gitignore: Para asegurarnos que no subimos ficheros no deseados al repositorio.
- Pipfile: Es importante que todos seamos capaces de replicar el mismo entorno. En este caso, usamos Pipfile para asegurarnos de que todos usamos la misma versión de pre-commit y Python (que se instala con Python) para evitar todo lo posible el en mi local funciona.
- CHANGELOG.md: Este fichero debería ser generado automáticamente, a ser posible a partir de los commits que se hagan en nuestro repositorio. De esta manera tienes un grado mayor de garantía de que los commits del equipo no pasan a ser copias los unos de los otros y de verdad explican los cambios que se han hecho, y más importante todavía, por qué o qué impacto tienen.
- README.md: Al igual que el CHANGELOG.md, en el caso de los módulos de Terraform este fichero se genera automáticamente gracias a terraform-docs
4. Mono repositorio de módulos vs multi repositorios
¿Deberíamos tener un único repositorio donde almacenar todos nuestro módulos, o un repositorio por cada módulo de Terraform?
Cada uno de estos alcances tiene sus pros y sus contras.
A lo largo de las discusiones de organización de módulos de Terraform he tenido la oportunidad de trabajar con ambos paradigmas, y he escuchado varios argumentos que apoyan los dos casos:
Tomemos el caso del repositorio único para ver sus ventajas y desventajas:
- Aumento de complejidad para versionar tus módulos. Es más sencillo configurar un versionado automático si cada repositorio tiene un único módulo, aunque realmente no importa mucho ya que una vez configurado tienes la misma flexibilidad que teniéndolo en varios repositorios.
- Facilidad para cambiar las convenciones a nivel general. Tenemos varias convenciones, y estas deberían ser adaptables a cada caso, de forma que puede que para tu caso de uso no apliquen todas, o falte alguna o simplemente evolucionan con el paso del tiempo. Puede ser más tedioso en función de la organización realizar cambios en varios repositorios a la vez que hacerlos dentro del mismo. Personalmente, para mi es un argumento débil ya que al final tanto el repositorio único como varios se reflejan en tu local como directorios, no importa los repositorios que sean.
- Ocupa más. Es evidente, pero esto puede ser un problema, ya que para querer utilizar un único módulo usando Terragrunt, este se descarga el repositorio por debajo, lo que puede ralentizar tus despliegues si tienes muchos módulos propios.
- No puedes gestionar a nivel de ruta. En los repositorios de Git no puedes restringir permisos a nivel de path de forma automática. Si tienes algún módulo sensible que esté restringido a un conjunto reducido de personas para garantizar que nadie es capaz de cambiar el código y preservar la automatización, no podrías evitarlo teniéndolo todo en el mismo repositorio.
En base a esto, el criterio que sigo para tenerlo junto o separado es hacerme las siguientes preguntas:
-
- ¿Hay algún requisito de seguridad que no nos permita tener todo junto? Si hay que restringir el acceso a ciertos repositorios a un grupo reducido de desarrolladores o equipos diferentes, deberíamos dividir el número de repositorios al menos en número mínimo de grupos que satisfagan el requisito.
Por ejemplo, si el equipo de seguridad necesita validación por medio de Pull Request en una serie de módulos, aunque sea infraestructura quien los desarrolle, es un buen indicativo de que esos módulos deberían estar en un repositorio diferente que tendrá unas reglas diferentes de aprobación para asegurarnos de que se cumple el requisito de seguridad - ¿Tenemos un número grande de módulos y estos ocupan mucho? Esto implica que nuestros despliegues serán más lentos, y es un problema acumulativo ya que si tenemos muchos módulos es un reflejo de que nuestra infraestructura tiene muchos elementos, por lo que además tendremos muchos despliegues. Si sumas el tiempo de descarga de todo el repositorio de módulos al número elevado de despliegues, puedes tener un ciclo de despliegue lento que te puede hacer perder mucho tiempo a largo plazo
- ¿Hay algún requisito de seguridad que no nos permita tener todo junto? Si hay que restringir el acceso a ciertos repositorios a un grupo reducido de desarrolladores o equipos diferentes, deberíamos dividir el número de repositorios al menos en número mínimo de grupos que satisfagan el requisito.
5. Conclusiones
Y esto sería todo de este primer tutorial sobre la estructura que debería tener tu repositorio de Terraform.
Esto debería servirnos como punto de partida o propuesta inicial para definir qué deberían hacer nuestros módulos de Terraform, así como saber el criterio de cada una de las decisiones que tomamos.
Por último, aquí tengo una plantilla que utilizo a la hora de crear módulos de terraform para un repositorio único para todos los módulos (la iré actualizando según vaya aprendiendo o cambiando mi criterio con el tiempo, junto a este tutorial) donde tienes un ejemplo concreto con el que puedes ver estas convenciones ya configuradas.