Terraform Essentials III: Cómo hacer nuestra Infraestructura escalable y reproducible gracias a las variables.
Llegamos al tercer capítulo de esta serie llamada #TerraformEssentials. En este caso, les voy a hablar de las variables y de como estás pueden ayudarnos a que nuestro código e infraestructura sea escalable y reproducible.
Hace unas semanas empecé con esta serie llamada #TerraformEssentials. Hasta ahora vimos dos ejemplos, uno bien sencillo y el otro un poco más completo que incluía la puesta en marcha de un Traefik, Wordpress, MariaDB y alguna que otra cosita más. Le dejo aquí los enlaces:
Lo que vamos a ver hoy es cómo hacemos que este código, en donde definimos nuestra infraestructura sea reproducible de una manera más, amigable", y que no nos exija editar los "tf" para cada ambiente.
Pensemos en el siguiente escenario: Imagínate el caso donde tengas que replicar la misma configuración para otro ambiente y al tiempo, tengas que hacerla para otro y que por supuesto, los parámetros sean diferentes, que los contenedores o máquinas virtuales tengan nombres y especificaciones diferentes, que se basen en uno u otro template o que incluso los usuarios y contraseñas sean diferentes. ¿Cómo hacemos para replicar todo eso y hacer los cambios? ¿Tengo que modificarlos archivos "TF" cada vez? ¿Al final voy a tener que tener decenas o cientos de archivos "TF" para toda mi infraestructura? y aquí es donde te digo que no. ¿Cómo? gracias a las variables o mejor dicho los archivos ".tfvars" donde vamos a especificarlas.
Estos archivos "tfvars" nos van a permitir especificar variables para que Terraform las consuma y "rellene" con esa información los argumentos en nuestros archivos ".tf" sin necesidad de modificarlos. Solamente veríamos diferentes resultados, cuando consultamos los archivos "tfstate".
Veámoslo con un ejemplo sencillo. Vamos a desplegar un Wordpress con una base de datos MariaDB en Docker y cada uno de los datos que necesitamos para configurar estos contenedores, los vamos a definir a través de un archivo de variables llamado terraform.tfvars.
Nuestro directorio, queda de la siguiente manera:
-rw-r--r-- 1 nacho staff 747B Mar 26 20:19 maria.tf
-rw-r--r-- 1 nacho staff 95B Mar 26 20:19 provider.tf
-rw-r--r-- 1 nacho staff 944B Mar 26 20:19 wordpress.tf
-rw-r--r-- 1 nacho staff 97B Mar 26 21:11 terraform.tfvars
Ahora bien, la sintaxis del archivo "tfvars" es bastante sencilla. Para este tutorial, quedaría de la siguiente manera:
#Vamos definir el proveedor. En este caso, el Docker es local, pero para hacerlo hacia a un Docker remoto, deberíamos especificar la conexión SSH hacia nuestro servidor.
local = "unix:///var/run/docker.sock"
#Vamos a especificar con variables el contenedor de mariadb
contenedor_maria = "cduser_mariadb"
imagen_maria = "mariadb:10.5.1"
password-root = "123456"
#Vamos a especificar con variables el contenedor del wordpress
contenedor = "cduser_wordpress"
imagen = "wordpress:php7.4"
#Vamos a especificar las variables de entorno del wordpress con variables de Terraform, pero también vamos a usar estás variables para la config del mariaDB, esto se llama ser eficientes.
mariadb = "cduser_mariadb"
db-user = "user"
password = "pass"
db = "wordpress"
Vean el código de arriba, comenzamos especificando el host al cual nos vamos a conectar para desplegar estos contenedores.
Luego, específico las variables de entorno de Docker del MariaDB y las "convierto" en variables de Terraform. Hago lo mismo con lo que necesito para el contenedor que va a correr Wordpress.
El archivo maria.tf, quedaría de la siguiente manera:
variable "contenedor_maria" {}
variable "imagen_maria" {}
variable "password-root" {}
# Contenedor
resource "docker_container" "mariadb" {
name = "${var.contenedor_maria}"
image = "${var.imagen_maria}"
# Especificamos variables de entorno para crear una base, generar un usuario root, una contraseña para ese usuario, el usuario de base de datos para wordpress y su contraseña.
env = ["MYSQL_DATABASE=${var.db}", "MYSQL_USER=${var.db-user}", "MYSQL_PASSWORD=${var.password}", "MYSQL_RANDOM_ROOT_PASSWORD=${var.password-root}"]
command = ["--default-authentication-plugin=mysql_native_password"]
ports {
external = 3306
internal = 3306
}
}
Noten en el "tf" de acá arriba, que lo que hago en el "root" del archivo es especificar las variables que voy a usar y que va a ir a buscar al "terraform.tfvars". Si, es lo que ustedes piensan, tiene que estar definidas en el archivo "tfvars" y en el "tf"
Y ahora, fijense en el wordpress.tf, que uso algunas variables definidas también en el maria.tf, esto es así porque voy a usar la base de datos que cree y no tiene sentido que haga dos variables para el mismo valor a excepción de la variable para el nombre del contenedor del MariaDB.
variable "contenedor" {}
variable "imagen" {}
variable "mariadb" {}
variable "db-user" {}
variable "password" {}
variable "db" {}
# Contenedor
resource "docker_container" "recurso" {
name = "${var.contenedor}"
image = "${var.imagen}"
env = ["WORDPRESS_DB=${var.mariadb}", "WORDPRESS_DB_USER=${var.db-user}", "WORDPRESS_DB_PASSWORD=${var.password}", "WORDPRESS_DB_NAME=${var.db}"]
ports {
internal = 80
external = 80
}
}
Lo mismo que el maria.tf, específico en el root del archivo las variables que voy a usar y las ubico en los espacios en donde efectivamente las voy a usar.
Cómo ven en los dos ejemplos, las variables a definir son fáciles de entender, tienen la siguiente sintaxis.
variable "contenedor" {}
Y para invocarlos, quedaría así: Cuando vamos a especificar el argumento entre comillas, ponemos un signo de pesos, abrimos la llave y lo que debemos especificar si o si es la palabra "var", agregamos un "punto", definimos la variable y cerramos la llave.
name = "${var.contenedor}"
Vamos a ver si todo esto funciona
Como siempre, la forma de ver si todo esto funciona es probandolo. así que vamos a ejecutar:
terraform apply
Nos devolverá lo siguiente:
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# docker_container.mariadb will be created
+ resource "docker_container" "mariadb" {
+ attach = false
+ bridge = (known after apply)
+ command = [
+ "--default-authentication-plugin=mysql_native_password",
]
+ container_logs = (known after apply)
+ entrypoint = (known after apply)
+ env = [
+ "MYSQL_DATABASE=wordpress",
+ "MYSQL_PASSWORD=pass",
+ "MYSQL_RANDOM_ROOT_PASSWORD=123456",
+ "MYSQL_USER=user",
]
+ exit_code = (known after apply)
+ gateway = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = "mariadb:10.5.1"
+ ip_address = (known after apply)
+ ip_prefix_length = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = "json-file"
+ logs = false
+ must_run = true
+ name = "cduser_mariadb"
+ network_data = (known after apply)
+ read_only = false
+ restart = "no"
+ rm = false
+ shm_size = (known after apply)
+ start = true
+ labels {
+ label = (known after apply)
+ value = (known after apply)
}
+ ports {
+ external = 3306
+ internal = 3306
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
# docker_container.recurso will be created
+ resource "docker_container" "recurso" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ entrypoint = (known after apply)
+ env = [
+ "WORDPRESS_DB=cduser_mariadb",
+ "WORDPRESS_DB_NAME=wordpress",
+ "WORDPRESS_DB_PASSWORD=pass",
+ "WORDPRESS_DB_USER=user",
]
+ exit_code = (known after apply)
+ gateway = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = "wordpress:php7.4"
+ ip_address = (known after apply)
+ ip_prefix_length = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = "json-file"
+ logs = false
+ must_run = true
+ name = "cduser_wordpress"
+ network_data = (known after apply)
+ read_only = false
+ restart = "no"
+ rm = false
+ shm_size = (known after apply)
+ start = true
+ labels {
+ label = (known after apply)
+ value = (known after apply)
}
+ ports {
+ external = 80
+ internal = 80
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
Warning: Interpolation-only expressions are deprecated
on maria.tf line 8, in resource "docker_container" "mariadb":
8: name = "${var.contenedor_maria}"
Terraform 0.11 and earlier required all non-constant expressions to be
provided via interpolation syntax, but this pattern is now deprecated. To
silence this warning, remove the "${ sequence from the start and the }"
sequence from the end of this expression, leaving just the inner expression.
Template interpolation syntax is still used to construct strings from
expressions when the template includes multiple interpolation sequences or a
mixture of literal strings and interpolations. This deprecation applies only
to templates that consist entirely of a single interpolation sequence.
(and 4 more similar warnings elsewhere)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
Tipeamos "yes" y veamos...
docker_container.mariadb: Creating...
docker_container.recurso: Creating...
docker_container.recurso: Creation complete after 1s [id=e5748cc303c057c7835fa5c74a6526347d14dc5d85a230a64f12ea9cbd00f92e]
docker_container.mariadb: Creation complete after 1s [id=9ac45903802c1686a15ca5b226a9f3965749b5986992fb6e791902e95b10b918]
Los contenedores se crearon así que vamos a ver que dice el "docker ps".
9ac45903802c mariadb:10.5.1 "docker-entrypoint.s…" 41 seconds ago Up 40 seconds 0.0.0.0:3306->3306/tcp cduser_mariadb
e5748cc303c0 wordpress:php7.4 "docker-entrypoint.s…" 41 seconds ago Up 40 seconds 0.0.0.0:80->80/tcp cduser_wordpress
Efectivamente están corriendo y así es como definimos infraestructura en código usando variables.
Para ir cerrando
Espero que este tutorial te haya enseñado algo nuevo, cualquier cosa me podés dejar un comentario acá abajo y lo vamos viendo.
Les dejo los archivos de este proyecto en Gitlab para que puedan reutilizarlos y estudiarlos.
Este tutorial también les deja un reto, hay un error, algo falta, no es sobre los variables, pero hay algo que falta definir en los archivos "tf" para que este Wordpress y el MariaDB puedan funcionar "en paz". Espero por sus soluciones aquí abajo. Pueden usar Pastebin para compartir su código.