Terraform Essentials I: Cómo conectarlo con Docker y desplegar un Hello World.
Una de las tecnologías con las que me gusta jugar mucho es con Terraform y se me ocurrió a hacer una serie de artículos para introducir, al que quiera, a este hermoso mundo de la infraestructura como código.
¿Qué les parece la idea? Me dicen que les parece con un comentario en la caja de abajo. ¿Arrancamos?
En este primer artículo la vamos a hacer bien fácil y no va a requerir que necesitemos de ningún Cloud Provider, aunque ya llegaremos a eso. Vamos a usar lo que tenemos y para este caso, algo bien simple como puede ser Docker instalado en tu máquina (MacOS, Windows o Linux).
Vamos a saltarnos la parte de la instalación de Docker y vamos con la de Terraform.
Instalación de Terraform
La instalación de esta solución es muy sencilla. Si estás en MacOS podés usar Brew.
brew install terraform
Si estás en Ubuntu y tenés instalado Brew podés usar el comando de arriba o bien usar:
sudo snap install terraform
Si estás en Windows, podés ir directamente a la página de Terraform a hacer la descarga (y a pensar en cambiarte a MacOS o Linux)
Una vez instalado, desde nuestra terminal podemos verificar que todo ande con el siguiente comando:
terraform version
Te debería devolver algo como esto:
Terraform v0.12.21
Vamos a crear la receta
Ya tenemos instalado Terraform, ahora vamos a crear la receta para que lo podamos conectar con nuestro Docker Engine que está corriendo de manera local.
Algo importante para entender que Terraform se maneja, lee y ejecuta todo lo que haya dentro de la carpeta en donde trabajamos y tengan una extensión tf. El resultado o lo que efectivamente ejecutó, lo va a guardar en un archivo con una extensión tfstate.
Más adelante les iré contando más detalles de cómo funciona, pero por ahora, solo tengan en mente lo de más arriba.
Para crear la receta y tener todo ordenadito, vamos a crear unas carpetas, la misma puede ser terraform/docker. Para crearla podés ejecutar esto:
Ahora bien, la estructura de la carpeta para este proyecto va a ser muy sencilla. Vamos a tener dos archivos, uno que se llame provider.tf y el otro que se llame container.tf.
nacho@nachos-air ~/terraform/docker ll
total 16
-rw-r--r-- 1 nacho staff 577B Feb 25 20:11 container.tf
-rw-r--r-- 1 nacho staff 95B Feb 25 20:03 provider.tf
En el provider.tf, vamos a especificar al proveedor que nos vamos a conectar, en este caso es el socket local de Docker, quedando de la siguiente manera:
Luego, vamos a especificar de esta manera en el archivo container.tf, el contenedor que se vamos a crear, el puerto que vamos a exponer y qué imagen va a usar desde el Docker Hub.
# Contenedor
resource "docker_container" "hello_world" {
name = "cduser_hello_world"
image = "tutum/hello-world"
# Especificamos el puerto del contenedor que vamos a exponer
ports {
internal = 80
external = 80
}
}
Una vez que estos dos archivos están guardados, podemos decir que mezclamos todos los ingredientes de la receta y así que...
A cocinar la receta
Para comenzar a cocinar la receta, lo primero que debemos ejecutar en el directorio terraform/docker es lo siguiente:
terraform init
Esto lo que va a hacer es leer el provider.tf y descargará el plugin para conectarse con Docker. En mi caso, como ya lo tengo instalado, me respondió con lo siguiente:
Initializing the backend...
Initializing provider plugins...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.docker: version = "~> 2.7"
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Fijense más arriba, que me especificó que la versión del proveedor de Docker esta en la 2.7.
Una vez instalado el plugin, vamos a validar si nuestra configuración está bien y no tiene ningún error. Para esto vamos a usar el siguiente comando:
terraform validate
Si esta todo bien y no hay error de sintaxis, nos devolverá algo como esto:
Success! The configuration is valid.
Lo siguiente es ejecutar un terraform plan donde nos mostrará todo lo que tenemos declarado y generará un plan para luego poder ejecutar. Si todo esta ok, nos devolverá algo como esto:
terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
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.hello_world will be created
+ resource "docker_container" "hello_world" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ gateway = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = "tutum/hello-world"
+ 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_hello_world"
+ 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: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
Fijense que hay un "+" al lado de todo lo que va a hacer, esto quiere decir esos recursos se van a crear, si hubiese un "-" quiere decir que se van a eliminar.
Si el plan esta ok, ejecutamos...
terraform apply
Nos devolverá algo similar a lo de arriba...
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.hello_world will be created
+ resource "docker_container" "hello_world" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ gateway = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = "tutum/hello-world"
+ 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_hello_world"
+ 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: 1 to add, 0 to change, 0 to destroy.
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
Fijense que yo "tipie" yes. Eso es para confirmar que todo esta ok y queremos que ejecute eso. Cuando le damos Enter, comenzará con la magia y veremos algo como esto:
docker_container.hello_world: Creating...
docker_container.hello_world: Still creating... [10s elapsed]
docker_container.hello_world: Creation complete after 18s [id=88247d9562379709c2409a259524c5718cb30abf5bec8d21be4dc6a106dbd69d]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Fijense que tardó 18 segundos en descargar la imágen y poner a correr el contenedor. Si la imágen hubiese estado descargada desde antes, ese proceso toma 1 segundo.
Ahora bien, si vamos a ver que tal salió todo... vamos a ejecutar lo siguiente:
docker ps
Y debería devolverte el contenedor creado, así me aparece a mi y a vos, excepto por el ID del contenedor, debería ser igual:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8a0b2b824bd5 tutum/hello-world "/bin/sh -c 'php-fpm…" 24 seconds ago Up 23 seconds 0.0.0.0:80->80/tcp cduser_hello_world
Si asi fue, vamos al navegador y ejecutamos http://localhost y debería aparece la imágen de Hello World del contenedor, tal como aparece acá abajo. Fijense que el ID del contenedor es el mismo en la página en el mismo que esta en el resultado del docker ps ;)
Para ir cerrando
Así que bueno, eso es todo, comenzaste a usar Terraform, con un ejemplo bastante sencillo pero que te dará pie para seguir investigando un poco más. Si querés algo un poco más avanzado, te recomiendo el siguiente artículo donde mezcle Terraform, Traefik, Docker en DigitalOcean.
Por último, estos archivos, los dejo disponible en este proyecto de GitLab para que puedan descargar y comenzar a jugar desde ahí.
Nos vemos en el próximo artículo de esta serie que se llamará Terraform Essentials.