Primeros pasos con modulos en terraform


terraform

Introducción

En este artículo vamos a ver una introducción sutil a los módulos de Terraform, cómo pasar datos a un módulo, obtener algo del módulo y crear un recurso (un clúster GKE). La idea es que sea lo más simple posible para entender de qué está compuesto un módulo y cómo podés crear los tuyos. A veces tiene sentido usar módulos para abstraer implementaciones que usás en varios proyectos o cosas que se repiten frecuentemente dentro del proyecto. Así que veamos qué se necesita para crear y usar un módulo. El código fuente para este artículo lo podés encontrar aquí. En este ejemplo estoy usando GCP, ya que te dan $300 USD por un año para probar sus servicios, y hasta ahora parece bastante bueno. Después de registrarte, tenés que ir a IAM, crear una cuenta de servicio y exportar la clave (esto es necesario para que el proveedor de Terraform pueda hablar con GCP).


Composición de un módulo

Un módulo puede ser cualquier carpeta que contenga un archivo main.tf. Sí, ese es el único archivo requerido para que un módulo sea usable, pero la recomendación es que también agregues un archivo README.md con una descripción del módulo si está pensado para ser utilizado por otras personas. Si es un submódulo, esto no es necesario. También vas a necesitar un archivo llamado variables.tf y otro outputs.tf. Por supuesto, si es un módulo grande que no se puede dividir en submódulos, podés dividir estos archivos por conveniencia o para mejorar la legibilidad. Las variables deberían tener descripciones para que las herramientas puedan mostrar para qué sirven. Podés leer más sobre los fundamentos de un módulo aquí.


Antes de avanzar, veamos la estructura de carpetas de nuestro proyecto:

 account.json
 LICENSE
 main.tf
 module
    main.tf
    outputs.tf
    variables.tf
 README.md
 terraform.tfvars

1 directory, 8 files

Bueno, basta de hablar, mostrame el código
El proyecto

Empecemos con el archivo main.tf que va a llamar a nuestro módulo. Fijate que agregué algunos comentarios adicionales, pero es bastante directo: configuramos el proveedor, definimos algunas variables, llamamos a nuestro módulo y mostramos una salida (la salida también se puede usar para pasar datos entre módulos).

# Configurar el proveedor para poder hablar con GCP
provider "google" {
  credentials = "${file("account.json")}"
  project     = "${var.project_name}"
  region      = "${var.region}"
}

# Definición de variables
variable "project_name" {
  default = "testinggcp"
  type    = "string"
}

variable "cluster_name" {
  default = "demo-terraform-cluster"
  type    = "string"
}

variable "region" {
  default = "us-east1"
  type    = "string"
}

variable "zone" {
  default = "us-east1-c"
  type    = "string"
}

# Llamar a nuestro módulo y pasar la variable zone, y obtener cluster_name
module "terraform-gke" {
  source = "./module"
  zone = "${var.zone}"
  cluster_name = "${var.cluster_name}"
}

# Imprimir el valor de k8s_master_version
output "kubernetes-version" {
  value = module.terraform-gke.k8s_master_version
}

Después, el archivo terraform.tfvars tiene algunos valores para sobrescribir los predeterminados que definimos:

project_name = "testingcontainerengine"
cluster_name = "demo-cluster"
region = "us-east1"
zone = "us-east1-c"

El módulo

Ahora veamos el propio módulo. Este módulo va a crear un clúster GKE, y aunque no es una buena práctica usar un módulo como un wrapper, para este ejemplo vamos a olvidar esa regla por un rato. Este es el archivo main.tf:

# Crear el clúster
resource "google_container_cluster" "gke-cluster" {
  name               = "${var.cluster_name}"
  network            = "default"
  zone               = "${var.zone}"
  initial_node_count = 3
}

El archivo variables.tf:

variable "cluster_name" {
  default = "terraform-module-demo"
  type    = "string"
}

variable "zone" {
  default = "us-east1-b"
  type    = "string"
}

variable "region" {
  default = "us-east1"
  type = "string"
}

Y finalmente el archivo outputs.tf:

output "k8s_endpoint" {
  value = "${google_container_cluster.gke-cluster.endpoint}"
}

output "k8s_master_version" {
  value = "${google_container_cluster.gke-cluster.master_version}"
}

output "k8s_instance_group_urls" {
  value = "${google_container_cluster.gke-cluster.instance_group_urls.0}"
}

output "k8s_master_auth_client_certificate" {
  value = "${google_container_cluster.gke-cluster.master_auth.0.client_certificate}"
}

output "k8s_master_auth_client_key" {
  value = "${google_container_cluster.gke-cluster.master_auth.0.client_key}"
}

output "k8s_master_auth_cluster_ca_certificate" {
  value = "${google_container_cluster.gke-cluster.master_auth.0.cluster_ca_certificate}"
}

Notá que tenemos muchos más outputs que el que decidimos mostrar, pero podés jugar con eso y experimentar si querés :)


Probándolo

Primero necesitamos inicializar nuestro proyecto para que Terraform pueda colocar los módulos, archivos de proveedor, etc. en su lugar. Es una buena práctica versionar las cosas y moverse entre versiones, de esa manera todo puede ser probado y, si algo no funciona como se espera, siempre podés hacer un rollback al estado anterior.

$ terraform init 
Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "google" (terraform-providers/google) 2.9.1...

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.google: version = "~> 2.9"

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.

Luego simplemente lo ejecutamos.

 $ terraform apply

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:

  # module.terraform-gke.google_container_cluster.gke-cluster will be created
  + resource "google_container_cluster" "gke-cluster" {
      + additional_zones            = (known after apply)
      + cluster_autoscaling         = (known after apply)
      + cluster_ipv4_cidr           = (known after apply)
      + enable_binary_authorization = (known after apply)
      + enable_kubernetes_alpha     = false
      + enable_legacy_abac          = false
      + enable_tpu                  = (known after apply)
      + endpoint                    = (known after apply)
      + id                          = (known after apply)
      + initial_node_count          = 3
      + instance_group_urls         = (known after apply)
      + ip_allocation_policy        = (known after apply)
      + location                    = (known after apply)
      + logging_service             = (known after apply)
      + master_version              = (known after apply)
      + monitoring_service          = (known after apply)
      + name                        = "demo-cluster"
      + network                     = "default"
      + node_locations              = (known after apply)
      + node_version                = (known after apply)
      + project                     = (known after apply)
      + region                      = (known after apply)
      + services_ipv4_cidr          = (known after apply)
      + subnetwork                  = (known after apply)
      + zone                        = "us-east1-c"

      + addons_config {
          + horizontal_pod_autoscaling {
              + disabled = (known after apply)
            }

          + http_load_balancing {
              + disabled = (known after apply)
            }

          + kubernetes_dashboard {
              + disabled = (known after apply)
            }

          + network_policy_config {
              + disabled = (known after apply)
            }
        }

      + master_auth {
          + client_certificate     = (known after apply)
          + client_key             = (sensitive value)
          + cluster_ca_certificate = (known after apply)
          + password               = (sensitive value)
          + username               = (known after apply)

          + client_certificate_config {
              + issue_client_certificate = (known after apply)
            }
        }

      + network_policy {
          + enabled  = (known after apply)
          + provider = (known after apply)
        }

      + node_config {
          + disk_size_gb      = (known after apply)
          + disk_type         = (known after apply)
          + guest_accelerator = (known after apply)
          + image_type        = (known after apply)
          + labels            = (known after apply)
          + local_ssd_count   = (known after apply)
          + machine_type      = (known after apply)
          + metadata          = (known after apply)
          + min_cpu_platform  = (known after apply)
          + oauth_scopes      = (known after apply)
          + preemptible       = (known after apply)
          + service_account   = (known after apply)
          + tags              = (known after apply)

          + taint {
              + effect = (known after apply)
              + key    = (known after apply)
              + value  = (known after apply)
            }

          + workload_metadata_config {
              + node_metadata = (known after apply)
            }
        }

      + node_pool {
          + initial_node_count  = (known after apply)
          + instance_group_urls = (known after apply)
          + max_pods_per_node   = (known after apply)
          + name                = (known after apply)
          + name_prefix         = (known after apply)
          + node_count          = (known after apply)
          + version             = (known after apply)

          + autoscaling {
              + max_node_count = (known after apply)
              + min_node_count = (known after apply)
            }

          + management {
              + auto_repair  = (known after apply)
              + auto_upgrade = (known after apply)
            }

          + node_config {
              + disk_size_gb      = (known after apply)
              + disk_type         = (known after apply)
              + guest_accelerator = (known after apply)
              + image_type        = (known after apply)
              + labels            = (known after apply)
              + local_ssd_count   = (known after apply)
              + machine_type      = (known after apply)
              + metadata          = (known after apply)
              + min_cpu_platform  = (known after apply)
              + oauth_scopes      = (known after apply)
              + preemptible       = (known after apply)
              + service_account   = (known after apply)
              + tags              = (known after apply)

              + taint {
                  + effect = (known after apply)
                  + key    = (known after apply)
                  + value  = (known after apply)
                }

              + workload_metadata_config {
                  + node_metadata = (known after apply)
                }
            }
        }
    }

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

module.terraform-gke.google_container_cluster.gke-cluster: Creating...
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [10s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [20s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [30s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [40s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [50s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m0s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m10s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m20s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m30s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m40s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [1m50s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [2m0s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [2m10s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [2m20s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Still creating... [2m30s elapsed]
module.terraform-gke.google_container_cluster.gke-cluster: Creation complete after 2m35s [id=demo-cluster]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

kubernetes-version = 1.12.8-gke.10
Notas finales

Como podés ver, crear un módulo es bastante simple y, con una buena planificación y práctica, te puede ahorrar mucho esfuerzo en proyectos grandes o cuando trabajás en varios proyectos al mismo tiempo. Contame qué te parece. Siempre acordate de destruir los recursos que no vas a usar con terraform destroy.


Erratas

Si encontrás algún error o tenés alguna sugerencia, por favor mandame un mensaje así lo arreglo.



No tienes cuenta? Regístrate aqui

Ya registrado? Iniciar sesión a tu cuenta ahora.

Iniciar session con GitHub
Iniciar sesion con Google
  • Comentarios

    Online: 0

Por favor inicie sesión para poder escribir comentarios.

by Gabriel Garrido