Cómo desplegar una aplicación en Kubernetes con Terraform (en 10 segundos)

Publicando contenedores en Kubernetes con Terraform

Terraform es una herramienta ideal para definir infraestructura de forma programática (Infraestructura como código). Dado que las aplicaciones de Kubernetes están contenerizadas (Dockers), su despliegue se puede realizar fácilmente con un pequeño fichero de configuración de Terraform que define los recursos a crear en Kubernetes.

En este breve tutorial, mostramos cómo publicar una aplicación y crear un NodePort en Kubernetes usando Terraform. Su ejecución solo lleva 10 segundos.

La aplicación será obtenida desde el registro público de Docker y Terraform instruirá a Kubernetes para crear 3 réplicas y publicar sus servicios en un NodePort.

Pulse para ver la ejecución:

Atención: Esto no es una buena práctica

Las buenas prácticas para infraestructura de IT Wonder Lab incluyen modularizar la configuración de Terraform.
Sin embargo, en este ejemplo se define todo en un único fichero. Puede ver otros de nuestros tutoriales que sí aplican buenas prácticas para el despliegue con Kubernetes.

Prerrequisitos

  • Disponer de acceso a un clúster de Kubernetes.En el ejemplo usaremos el creado siguiendo el tutorial Creación de un Clúster de Kubernetes con Vagrant y Ansible.
  • Disponer de un fichero de configuración de acceso a Kubernetes local y conocer el contexto.
  • Descargar el código fuente desde el repositorio de Git Hub de IT Wonder Lab o crear el fichero con el código mostrado.

Comprobación del contexto de conexión al clúster de Kubernetes

Para el ejemplo usaremos la configuración de conexión a un clúster de Kubernetes existente en la ubicación por defecto ~/.kube/config

El fichero ~/.kube/config puede tener diferentes contextos, un contexto define un clúster, un usuario y el nombre asociado al contexto.

Comprueben los contextos usando el comando kubectl config view:

$ kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.50.11:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: [email protected]
current-context: [email protected]
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

En el siguiente ejemplo se muestra el contenido del fichero ~/.kube/config que contiene el contexto con nombre [email protected]:

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lH....Q0VSVElGSUNBVEUtLS0tLQo=
    server: https://192.168.50.11:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: [email protected]
current-context: [email protected]
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJWTJsaU5WWjVrZ1V3....U4rbW9qL1l6V0NJdURnSXZBRU1NZDVIMnBOaHMvcz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
    client-key-data: LS0tLS1XRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpWSUlFcFFJQkFBS0NBUUVBK295RXBYVTZu.....BLRVktLS0tLQo=

Utilizaremos el contexto kubern[email protected] en la definición de nuestro proveedor de Terraform para Kubernetes.

Si el contexto es diferente, puede actualizar el nombre en el fichero de Terraform o renombrar el contexto utilizando los comandos kubectl config get-contexts y kubectl config rename-context

En el siguiente ejemplo, se renombra un contexto existente llamado [email protected] a [email protected]

$ kubectl config get-contexts
CURRENT   NAME                            CLUSTER      AUTHINFO           NAMESPACE
*         [email protected]   kubernetes   kubernetes-admin

$ kubectl config rename-context [email protected] [email protected]
Context "[email protected]" renamed to "[email protected]".

$ kubectl config get-contexts
CURRENT   NAME                            CLUSTER      AUTHINFO           NAMESPACE
*         [email protected]   kubernetes   kubernetes-admin

Comprobar la conectividad del Clúster de Kubernetes

Por favor compruebe que su fichero de configuración de conexión a Kubernetes tiene las credenciales correctas, la comprobación se realiza conectando al clúster con el comando kubectl:

[email protected]:~/git/github/terraform-kubernetes-deploy-app$ kubectl get nodes
NAME      STATUS   ROLES    AGE     VERSION
k8s-m-1   Ready    master   3h33m   v1.18.6
k8s-n-1   Ready    <none>   3h30m   v1.18.6
k8s-n-2   Ready    <none>   3h27m   v1.18.6

Fichero de Terraform para despliegue de aplicación en Kubernetes

Cree o descargue el fichero terraform.tf desde GitHub:

# Copyright (C) 2018 - 2020 IT Wonder Lab (https://www.itwonderlab.com)
#
# This software may be modified and distributed under the terms
# of the MIT license.  See the LICENSE file for details.

# -------------------------------- WARNING --------------------------------
# IT Wonder Lab's best practices for infrastructure include modularizing 
# Terraform configuration. 
# In this example, we define everything in a single file. 
# See other tutorials for Terraform best practices for Kubernetes deployments.
# -------------------------------- WARNING --------------------------------

terraform {
  required_version = "~> 0.12" #cannot contain interpolations. Means requiered version >= 0.12 and < 0.13
}

#-----------------------------------------
# Default provider: Kubernetes
#-----------------------------------------

provider "kubernetes" {
  #Context to choose from the config file.
  config_context = "[email protected]"
  version = "~> 1.12"
}


#-----------------------------------------
# KUBERNETES DEPLOYMENT COLOR APP
#-----------------------------------------

resource "kubernetes_deployment" "color" {
    metadata {
        name = "color-blue-dep"
        labels = {
            app   = "color"
            color = "blue"
        } //labels
    } //metadata
    
    spec {
        selector {
            match_labels = {
                app   = "color"
                color = "blue"
            } //match_labels
        } //selector

        #Number of replicas
        replicas = 3

        #Template for the creation of the pod
        template { 
            metadata {
                labels = {
                    app   = "color"
                    color = "blue"
                } //labels
            } //metadata

            spec {
                container {
                    image = "itwonderlab/color"   #Docker image name
                    name  = "color-blue"          #Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL).
                    
                    #Block of string name and value pairs to set in the container's environment
                    env { 
                        name = "COLOR"
                        value = "blue"
                    } //env
                    
                    #List of ports to expose from the container.
                    port { 
                        container_port = 8080
                    }//port          
                    
                    resources {
                        limits {
                            cpu    = "0.5"
                            memory = "512Mi"
                        } //limits
                        requests {
                            cpu    = "250m"
                            memory = "50Mi"
                        } //requests
                    } //resources
                } //container
            } //spec
        } //template
    } //spec
} //resource

#-------------------------------------------------
# KUBERNETES DEPLOYMENT COLOR SERVICE NODE PORT
#-------------------------------------------------

resource "kubernetes_service" "color-service-np" {
  metadata {
    name = "color-service-np"
  } //metadata
  spec {
    selector = {
      app = "color"
    } //selector
    session_affinity = "ClientIP"
    port {
      port      = 8080 
      node_port = 30085
    } //port
    type = "NodePort"
  } //spec
} //resource

Las líneas 13 a 15 definen la versión requerida de Terraform. Usaremos Terraform 0.12.

Las líneas 21 a 25 definen el config_context (contexto de configuración) del fichero file ~/.kube/config que será utilizado, usaremos [email protected], también se define la versión requerida del proveedor de Kubernetes para Terraform.

Las líneas 32 a 91 definen un recurso de despliegue en Kubernetes por parte de Terraform con el nombre «color» y las siguientes propiedades:

  • Nombre = «color-blue-dep»
  • Etiquetas
    • app=»color»
    • color=»blue»
  • Réplicas: 3
  • Todas las réplicas usarán la plantilla de contenedor para crear los PODS que:
    • Obtiene la imagen Docker «itwonderlab/color» desde el repositorio público de Docker (https://hub.docker.com/u/itwonderlab)
    • Fija una variable de entorno en el contenedor de Docker con nombre COLOR y valor «blue»
    • Publica el puerto 8080 del contenedor (El puerto HTTP usado por la aplicación)
    • Fija límites en memoria y CPU.

Dado que estamos usando el clúster local del tutorial Kubernetes usando Vagrant y Ansible, se debe crear un puerto NodePort para exponer el puerto de la aplicación color fuera de la red de VirtualBox

Las líneas 97 a 112 crean un NodePort que publica el puerto 8080 de la aplicación «color» como Node Port 30085 en todos las IPs públicas de los nodos de Kubernetes. Vea Using a NodePort in a Kubernetes Cluster on top of VirtualBox para más información.

Inicializar los proveedores de Terraform

Si es la primera vez que se ejecuta el plan, se deben inicializar los proveedores de Terraform (Proveedor Terraform para Kubernetes) con el comando terraform init que descargará los plugins necesarios.

~/git/github/terraform-kubernetes-deploy-app$ terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "kubernetes" (hashicorp/kubernetes) 1.12.0...

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, othercommands will detect it and remind you to do so if necessary 

Publicar la aplicación en Kubernetes junto con el NodePort usando Terraform

Ya estamos listos para publicar la aplicación definida en la configuración del fichero de Terrafom.

Ejecute terraform plan para ver los cambios a realizar sobre el clúster:

[email protected]:~/git/github/terraform-kubernetes-deploy-app$ 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:

  # kubernetes_deployment.color will be created
  + resource "kubernetes_deployment" "color" {
      + id               = (known after apply)
      + wait_for_rollout = true

      + metadata {
          + generation       = (known after apply)
          + labels           = {
              + "app"   = "color"
              + "color" = "blue"
            }
          + name             = "color-blue-dep"
          + namespace        = "default"
          + resource_version = (known after apply)
          + self_link        = (known after apply)
          + uid              = (known after apply)
        }

      + spec {
          + min_ready_seconds         = 0
          + paused                    = false
          + progress_deadline_seconds = 600
          + replicas                  = 3
 ...
      + spec {
          + cluster_ip                  = (known after apply)
          + external_traffic_policy     = (known after apply)
          + publish_not_ready_addresses = false
          + selector                    = {
              + "app" = "color"
            }
          + session_affinity            = "ClientIP"
          + type                        = "NodePort"

          + port {
              + node_port   = 30085
              + port        = 8080
              + protocol    = "TCP"
              + target_port = (known after apply)
            }
        }
    }

Plan: 2 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.

Ejecute terraform applypara ejecutar los cambios en Kubernetes:

[email protected]:~/git/github/terraform-kubernetes-deploy-app$ terraform plan

[email protected]:~/git/github/terraform-kubernetes-deploy-app$ 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:

  # kubernetes_deployment.color will be created
  + resource "kubernetes_deployment" "color" {
      + id               = (known after apply)
      + wait_for_rollout = true

      + metadata {
          + generation       = (known after apply)
          + labels           = {
              + "app"   = "color"
              + "color" = "blue"
            }
          + name             = "color-blue-dep"
          + namespace        = "default"
          + resource_version = (known after apply)
          + self_link        = (known after apply)
          + uid              = (known after apply)
        }

      + spec {
          + min_ready_seconds         = 0
          + paused                    = false
          + progress_deadline_seconds = 600
          + replicas                  = 3
...

          + template {
              + metadata {
                  + generation       = (known after apply)
                  + labels           = {
                      + "app"   = "color"
                      + "color" = "blue"
                    }
                  + name             = (known after apply)
                  + resource_version = (known after apply)
                  + self_link        = (known after apply)
                  + uid              = (known after apply)
                }

              + spec {
...

                  + container {
                      + image                    = "itwonderlab/color"
                      + image_pull_policy        = (known after apply)
                      + name                     = "color-blue"
                      + stdin                    = false
                      + stdin_once               = false
                      + termination_message_path = "/dev/termination-log"
                      + tty                      = false

                      + env {
                          + name  = "COLOR"
                          + value = "blue"
                        }

                      + port {
                          + container_port = 8080
                          + protocol       = "TCP"
                        }
 ...
    }

  # kubernetes_service.color-service-np will be created
  + resource "kubernetes_service" "color-service-np" {
      + id                    = (known after apply)
  ...
      + spec {
          + cluster_ip                  = (known after apply)
          + external_traffic_policy     = (known after apply)
          + publish_not_ready_addresses = false
          + selector                    = {
              + "app" = "color"
            }
          + session_affinity            = "ClientIP"
          + type                        = "NodePort"

          + port {
              + node_port   = 30085
              + port        = 8080
              + protocol    = "TCP"
              + target_port = (known after apply)
            }
        }
    }

Plan: 2 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

kubernetes_service.color-service-np: Creating...
kubernetes_service.color-service-np: Creation complete after 0s [id=default/color-service-np]
kubernetes_deployment.color: Creating...
kubernetes_deployment.color: Creation complete after 8s [id=default/color-blue-dep]

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

Acceda a la aplicación usando el NodePort

Abra la URL http://192.168.50.11:30085/ para acceder a la aplicación Color (Todos los nodos de Kubernetes exponen el mismo NodePort, puede usar cualquier IP del clúster para acceder tal como se explica en Using a NodePort in a Kubernetes Cluster on top of VirtualBox.

Application deployed in Kubernetes using Terraform
Color App Publicada en Kubernetes con Terraform

Ha finalizado la publicación y ha accedido correctamente a una de las réplicas.

A continuación se muestran otras opciones para comprobar el desligue y modificar la configuración.

Uso del Kubernetes Dashboard para comprobar el despliegue

Acceda al Dashboard de Kubernetes, si ha utilizado el tutorial de Kubernetes local será la URL https://192.168.50.11:30002/

Cómo desplegar una aplicación en Kubernetes con Terraform (en 10 segundos) 1
Kubernetes Dashboard moestrando los recursos de la aplicación Color (PODs y Servicios)

En el Dashboard de Kubernetes se observan las tres réplicas o PODS de la aplicación color, un set de réplica y el servicio de NodePort.

Modifique el número de replicas a 1

Es muy sencillo modificar el número de replicas de la aplicación desplegadas en Kubernetes usando Terraform sin afectar las conexiones existentes usando una estrategia de rolling update.

Edite el fichero terraform.tf para indicar un número diferente de réplicas:

replicas = 1

Ejecute terraform plan

[email protected]:~/git/github/terraform-kubernetes-deploy-app$ terraform plan
Refreshing Terraform state in-memory prior to plan...
...
  ~ update in-place
Terraform will perform the following actions:
  # kubernetes_deployment.color will be updated in-place
  ~ resource "kubernetes_deployment" "color" {
        id               = "default/color-blue-dep"
        wait_for_rollout = true

        metadata {
            annotations      = {}
            generation       = 1
            labels           = {
                "app"   = "color"
                "color" = "blue"
            }
...

      ~ spec {
            min_ready_seconds         = 0
            paused                    = false
            progress_deadline_seconds = 600
          ~ replicas                  = 3 -> 1
            revision_history_limit    = 10
...
            strategy {
                type = "RollingUpdate"

                rolling_update {
                    max_surge       = "25%"
                    max_unavailable = "25%"
                }
            }

 ...
    }

Plan: 0 to add, 1 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.

Ejecute terraform apply para ver los cambios:

[email protected]:~/git/github/terraform-kubernetes-deploy-app$ terraform apply
...
Resource actions are indicated with the following symbols:
  ~ update in-place
Terraform will perform the following actions:
  # kubernetes_deployment.color will be updated in-place
  ~ resource "kubernetes_deployment" "color" {
        id               = "default/color-blue-dep"
        wait_for_rollout = true
...
      ~ spec {
            min_ready_seconds         = 0
            paused                    = false
            progress_deadline_seconds = 600
          ~ replicas                  = 3 -> 1
            revision_history_limit    = 10
...
    }

Plan: 0 to add, 1 to change, 0 to destroy.
...
kubernetes_deployment.color: Modifying... [id=default/color-blue-dep]
kubernetes_deployment.color: Modifications complete after 1s [id=default/color-blue-dep]

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Cómo desplegar una aplicación en Kubernetes con Terraform (en 10 segundos)

2 comentarios

    1. Muchas gracias Alfonso, nos alegra mucho que te sea útil. Kubernetes facilita mucho los despliegues de aplicaciones y su infraestructura asociada.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *