Aplicando buenas prácticas en Terraform

Luciano Adonis
devsChile
Published in
6 min readAug 22, 2018

Tarde o temprano te pasará

Partamos utilizando un ejemplo simple; una misma instancia en GCP de dos formas: una sin buenas prácticas y otra utilizando algunos de estos + explicación, permitiendo así convertir la experiencia de emplear buenas prácticas en un proceso más amigable y menos traumático.

Si tomas la pastilla azul el post acaba, despiertas en tu cama y crees lo que tú quieras creer. Si tomas la roja, te quedas en Medium y te enseño qué tan genial se ponen las cosas. Recuerda, sólo te estoy ofreciendo la verdad, nada más.

“You get used to it”

Esta es mi forma de decir gracias y compartir parte de lo que aprendí sobre buenas prácticas al trabajar con el equipo SWAT de Terraform compuesto de Pablo Jiménez cuyas técnicas de automatización son simplemente 🤯 y Nicolás Neira, que facilitó enormemente el proceso de integración.

En base al ejemplo ya mencionado, dividiré el post en dos partes:

Método 1: El fin justifica los medios

  • instance.tf

Método 2: Te lo agradecerás, eventualmente

  • Estructura de una pequeña instancia
  • provider.tf
  • main.tf
  • variables.tf
  • terraform.tfvars
  • 00backend.tf
  • .gitignore
  • PRO TIP

Método 1: El fin justifica los medios

Si bien, no es la mejor forma, es la mas rápida. Tu service account en conjunto a un main.tf con todo lo necesario, desde variables (si es que), pasando por la definición de los recursos, hasta el provider.

.
├──instance.tf
└──se-me-pierde-el.json

instance.tf

Nada más maravilloso que poner las credenciales en duro dentro del mismo tf file. Puede parecer poco relevante, “uy, verán mi JSON” y sobre todo comenzando, pero en casos como con Oracle Cloud Infraestructure uno aprende a respetar el uso de terraform.tfvars

variable "name" {
default = "nombre-unico-y-diferente"
}
provider "google" {
credentials = "./se-me-pierde-el.json"
}
data "google_compute_zones" "available" {}

resource "google_compute_instance" "default" {
project = "no-muy-descriptivo"
zone = "us-east1-b"
name = "${var.name}"
machine_type = "una-de-esas-que-corren-minecraft"

boot_disk {
initialize_params {
image = "${var.machine_image}"
}
}

network_interface {
network = "default"
access_config = {}
}
}

Personalmente cuando solía ser joven, evadía la culpa usando variables rimbombantes y separándolos en instance.tf, provider.tf y vars.tf.

Método 2: Te lo agradecerás, eventualmente

Esto se pondrá bonito.

Estructura de una pequeña instancia

La estructura de todos los archivos que componen este ejemplo se vería algo así:

.
├── .gitignore
├── 00backend.tf
├── Makefile
├── main.tf
├── output.tf
├── provider.tf
├── terraform.tfvars
├── variables.tf
└── gcp-inspec
├── attributes.yml
└── gcp-example-profile
└── *

Definiciones

Makefile: bastante útil, sobre todo para la automatización para pruebas y seteo de ambiente. Lamentablemente esta utilidad no será cubierta dentro de este post.

provider.tf: en este segmento mágico se define la forma de acceso (credenciales) a los recursos del provider.

main.tf: aquí van las definiciones principales del recurso a implementar.

variables.tf: en este van las variables definidas con sus parámetros respectivos, las cuales son utilizadas en el resto de los tf files.

terraform.tfvars: este archivo especial es para esas variables más sensibles.

outputs.tf: opcional, te permite definir qué quieres mostrar al terminar. Se va de mi alcance explicar tal ciencia.

backend.tf: donde las abejas duermen.

gcp-inspec/*: JUST NO.

.gitignore: te ayuda a evitar conversaciones con seguridad sobre commits con credenciales incluidas.

Si bien, logras lo mismo con un instance.tf y un *.json, la vida no funciona así. Utilizando herramientas como make, se puede lograr la automatización de multiples tareas y bueno, tienes que verlo para entenderlo.

Never send a human to do machine’s job — More Matrix references

provider.tf

Dentro de este van las definiciones necesarias para establecer la comunicación con el provider, las requeridas por el recurso y versión.

provider "google" {
version = "~> 1.12.0"
credentials = "${var.gcp_account_path}"
project = "${var.gcp_project_id}"
region = "${var.region}"
}

Esto ultimo te evitará en gran medida, pillar “sorpresas” en producción, al definir una versión especifica del plugin del provider a utilizar.

Si te preguntan: ¿Por qué esa versión?, di: “es estable”, nunca falla.

¿Cómo sé si estoy usando una versión estable? -Anon.

No lo sabes, lo sientes.

main.tf

Para el manejo de variables dentro de este archivo, intenta utilizar nombres significativos y evitar el uso de nombre de variables como “NaMe_iNsTanCé777”. También por salud visual, utilizar terraform fmt.

data "google_compute_zones" "available" {}

resource "google_compute_instance" "default" {
project = "${var.gcp_organization_id}"
zone = "${var.region}"
name = "${var.name}"
machine_type = "${var.machine_type}"

boot_disk {
initialize_params {
image = "${var.machine_image}"
}
}

network_interface {
network = "default"
access_config = {}
}
}

A menos que utilices un súper IDE.

variables.tf

Ya a este punto no diferencio vars de variables, pero como ya mencioné anteriormente, en este se dejan las definiciones de las variables definidas en el main.tf, entregándole usualmente, las más generales. Relacionadas con credenciales o información sensible, pasa a ser problema de terraform.tfvars

variable "gcp_account_path" {}
variable "gcp_organization_id" {}
variable "region" {}

variable "region" {
default = "us-east1-b"
}

variable "name" {
default = "demo"
}

variable "machine_type" {
default = "f1-micro"
}

variable "machine_image" {
default = "centos-7-v20180227"
}

terraform.tfvars

Para evitar la situación en que tengas que mostrar tus variables y con ello las información sensible en medio de una presentación, es cuando este archivo nos permite de forma separada entregarle información a las variables definidas en variables.tfy no tendrás que preocuparte de revelar tu password. (Looking at you, Oracle)

id_json = "/path/to/se-me-pierde-el.json"
id_project = "party-parrot"
region = "us-east1-b"

00backend.tf

Este le permite a Terraform determinar cómo el state es cargado y cómo una operación es llevada a cabo al darle terraform apply.

Configurar un lindo backend es como el backend de las buenas prácticas, para esto, simplemente necesitas:

  • Un bucket 🗑☁️
  • Una o dos cervezas 🍺||🍺🍺

Para la configuración de este, debes lo siguiente:

terraform {
backend "gcs" {
bucket = "party-parrot-bucket"
project = "party-parrot"
}
}

Si le das terraform init, te fallará. Ya que se debe setear también la variable de entorno con la ruta al .json de tu service account:

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/se-me-pierde-el.json

TL;DR: no pierdes el state y puedes trabajar en equipo, sin mayores complicaciones, bueno sí, pero por algo se parte.

.gitignore

Probablemente muchos ya conocen este archivo, cuyo objetivo principal es evitar commitear las credenciales (y entre otros archivos sensibles). Dentro de este podemos excluir archivos como el .tfvars que es el cual contiene información “no pública”, el service account y otros archivos que no tienen nada que ver.

.idea/
.terraform/
terraform.tfvars
*tfstate*
*.txt
*.json

Si, el .idea/ es una referencia a Trabajando con estilo: Goland.

PRO TIP

Hablando de malas prácticas, es recomendable evitar que los git commit sean frases como “meh” o poco descriptivas. Darle un poco mas de afecto y descripción, te ahorrará levantarte en la madrugada por algún cambio que no tuvo nada que ver.

“meh”

— A Young DevOps

Un tema bastante importante el cual no se abordó, es sobre las buenas prácticas al manejar archivos como se-me-pierde-el.json, sobretodo si utilizas CI con herramientas como Jenkins.

Pero no se preocupen, Martin Chait y Juan Esteban Colcombet, nos deleitarán con un post sobre esto, Pipelines y mucho más (próximamente).

Reflexión final y última referencia a Matrix:

“Neo, sooner or later you’re going to realize, just as I did, that there’s a difference between knowing the path and walking the path.”

— Morpheus.

Lectura recomendada

--

--