Aplicando buenas prácticas en Terraform
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.
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.
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.tf
y 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
- Terraform Recommended Practices — Official Docs.
- Terraform Best Practices — Para AWS pero sigue la misma logica.
- How to use Terraform as a Team — Otro buen post de Gruntwork.