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:

mkdir -p terraform/docker && cd terraform/docker
Este comando creará en donde estés parado la carpeta terraform, subcarpeta docker y entrará a ese directorio.

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:

# Configure the Docker provider
provider "docker" {
    host = "unix:///var/run/docker.sock"
}
provider.tf > este archivo es esencial para que luego Terraform sepa donde conectarse.

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.

Cómo implementar Traefik 2.1.2 con Docker usando Terraform en DigitalOcean
¡Pa! que título, cuatro tecnologías en un solo tutorial. Les cuento estoy jugando mucho con Terraform en estos últimos días y con Traefik, así que arme una receta para desplegar este Reverse Proxy en DigitalOcean. Terraform, en mi opinión, es una herramienta maravillosa que te permite automatizar e…

Por último, estos archivos, los dejo disponible en este proyecto de GitLab para que puedan descargar y comenzar a jugar desde ahí.

Ignacio Van Droogenbroeck / Terraform Essentials
En este proyecto, encontrarás todos los ejemplos que uso en la seríe llamada Terraform Essentials. Espero que te sea de útilidad: Puedes encontrar estos artículos en: https://cduser.com/tag/terraform/

Nos vemos en el próximo artículo de esta serie que se llamará Terraform Essentials.