AWS VPC, Route 53, RDS MariaDB, EC2 usando Ansible y Terraform (1/5)

Configuración de Terraform y creación de la subred del VPC

Terraform logo

En esta demo de Terraform y Ansible para AWS podrá encontrar todo el código que se necesita para crear un VPC (Cloud Privado Virtual) en AWS (Amazon Web Services) con una instancia EC2 (Elastic Compute) conectada a una base de datos MariaDB ejecutándose en RDS (Servicio de Base de Datos Relacional) utilizando un plan de Terraform 0.12.

Además se utiliza Ansible para instalar el sowftare y configurar un servidor Nginx, con PHP, y Let’s Encrypt para ejecutar WordPress.

Para una introducción a AWS, lea AWS VPC Basic Elements.

Este VPC puede ser utilizado para instalar una aplicación web sencilla como WordPress utilizando Ansible, tal como se mostrará en otro tutorial.

La demo aplica algunas de las buenas prácticas de IT Wonder Lab y utiliza exclusivamente* recursos gratuitos disponibles en el plan “free tier” de AWS.

Una infraestructura real debería hacer uso de algunos recursos de infraestructura que no están disponibles de forma gratuita en el plan “free tier” de AWS, como las instancias NAT y las VPNs IPSec. Así mismo deberían usarse diferentes VPCs o al menos sub-redes diferentes para cada entorno, reutilizar módulos de Terraform y almacenar el estado de Terraform (tfstate) en una ubicación compartida entre otros.

* La zona DNS no está incluída en la capa gratuita de AWS.

Cambios:

  • 23 Jun 2020: Actualizado a Terraform 0.12

Diagrama General

Este diagrama muestra los elementos principales de la infraestructura que será creada mediante Terraform:

AWS Public and Private Network in a VPC

1 VPC
4 sub-redes
4 grupos de seguridad
3 tablas de rutas
1 instancia EC2
1 Puerta de salida a Internet (gateway)
1 Route 53 Hosted zone
1 Grupo de sub-red de base de datos
1 Base de datos RDS MariaDB

Aplicar

El siguiente asciicast muestra como Terraform crea todos los recursos necesarios en AWS. La ejecución del plan completo tarda unos 10 minutos, de los cuales más de 7 son esperando la creación de la base de datos AWS RDS MariaDB.

Pulse el botón play para ver la ejecución del plan de Terraform.

* El asciicast ha sido editado para mostrar la ejecución 2 minutos.

Prerrequisitos

Una equipo ejecutando software compatible con Terraform y Ansible. Para esta demo usaremos Ubuntu.

  1. Crear una nueva cuenta en AWS para demos.
  2. Crear un usuario IAM de AWS para demos.
  3. Entender los Elementos básicos de AWS.
  4. Instalar el software necesario para ejecutar la demo::
    1. Terraform 0.12
    2. GIT
    3. Atom, Visual Studio Code o su editor de código favorito
  5. Leer Utilice su dirección IP pública en Terraform.
  6. Leer Evitando destruir instancias AWS con Terraform.
  7. Descargar el código fuente de la demo desde el Repositorio público en GitHub de IT Wonder Lab
  8. Hacer whatismyip.sh ejecutable usando:
chmod 764 whatismyip.sh

Estructura de Ficheros

Los ficheros de configuración de Terraform se utilizan para describir la infraestructura y asignar valores a las variables, la mayoría de los ejemplos de Internet utilizan un solo fichero o como mucho unos pocos para configurar toda la infraestructura.

Mejores Prácticas: Estructura de ficheros Terraform

En este ejemplo para la creación de un AWS VPC con una instancia EC2 y una base de datos MariaDB sobre RDS, encontrará la estructura de ficheros que recomendamos y que aplica las mejores prácticas para definir varios entornos en un solo VPC.

El ejemplo tiene los siguientes ficheros:

NombreDescripción
modulesDirectorio con los módulos de Terraform
aws_ds_aws_ami.tfFuente de datos para obtener el ID de los últimos AMI para el sistema operativo seleccionado
aws_ec2_pro_wp.tfServidor WordPress, grupos de seguridad asociados, registro en DNS (Route 53)
aws_ec2_pro_wp_vars.tfVariables del servidor WordPress
aws_internet_gateway.tfInternet Gateway
aws_internet_gateway_vars.tfVariables del Internet Gateway
aws_rds_pro_mariadb_01.tfRDS MariaDB y grupos de seguridad asociados
aws_rds_pro_mariadb_01_vars.tfRDS MariaDB variables
aws_rds_sn_pro.tfRDS subred
aws_rds_sn_pro_vars.tfVariables de RDS subred
aws_route53.tfRoute 53 (DNS)
aws_route53_vars.tfVariables de Route 53 (DNS)
aws_sec_group_ec2_default.tfGrupos de seguridad asignados por defecto a todas las instancias EC2
aws_sec_group_ec2_default_vars.tfVariables grupos de seguridad EC2 por defecto
aws_sec_group_rds_mariadb_default.tfGrupos de seguridad asignados por defecto a todas las instancias RDS MariaDB
aws_sec_group_rds_mariadb_default_vars.tfVariables por defecto para los grupos de seguridad de RDS
aws_vpc_routing.tfTablas de rutas de las sub-redes
aws_vpc_routing_vars.tfVariables para las tablas de rutas de las sub-redes
aws_vpc_subnets.tfSub-redes de VPC
aws_vpc_subnets_vars.tfVariables sub-redes de VPC
aws_vpc.tfVPC
aws_vpc_vars.tfVPC variables
external_whatismyip.tfObtiene su IP pública de Internet para usarla en el firewall de AWS
provider_aws.tfDefine el proveedor AWS
provider_aws_vars.tfVariables para la definición del proveedor AWS
terraform.tfDefine Terraform
terraform.tfvarsValores para todas las variables del tutorial
terraform_vars.tfDefinición de todas las variables
whatismyip.shScript para obtener su IP pública de Internet para usarla en el firewall de AWS
.gitignoreExcluye ficheros y directorios del repositorio GIT
.terraformDirectorio de trabajo creado por Terraform

Configurar el proveedor AWS en Terraform

Terraform necesita el ID de cuenta y las credenciales para el usuario que interactuará con el API de AWS.

Las credenciales de AWS se almacenarán fuera de Terraform, en el fichero ~/.aws/credentials, utilizando la opción Shared Credentials file de Terraform.

Mejores Practicas: Credenciales en AWS y Terraform

Las credenciales de acceso a AWS y en general cualquier contraseña deberá ser almacenada fuera la configuración de Terraform. Esto permite que cada usuario / sistema ejecute Terraform con sus propias credenciales y reduce el riesgo de accesos no autorizados.

Cree o añada al fichero de credenciales ~/.aws/credentials el siguiente contenido:

[ditwl_infradmin]
aws_access_key_id=A1B2C3D4E5F6G7H8I9J0
aws_secret_access_key=QwertYuiopASDFGHJKL123456789sadfghjkvcbn

El nombre del perfil está rodeado por corchetes cuadrados  [ditwl_infradmin] y será el nombre utilizado en la configuración de Terraform para identificar las credenciales a utilizar.

El valor de aws_access_key_id y aws_secret_access_key se ha obtenido al crear un usuario AWS IAM par demos.

Además deberá introducir otra información relacionada con las credenciales de AWS, como el ID de cuenta en el fichero terraform.tfvars de Terraform, ubicado en la raíz de los ficheros extraídos del repositorio git:

#------------------------
# PROVIDERS
#------------------------

# DEFAULT AWS
provider_default_aws_profile = "ditwl_infradmin"
provider_default_aws_region = "us-east-1"
provider_default_aws_account_id = ["134567891011"] 
provider_default_aws_shared_credentials_file = "~/.aws/credentials"
provider_default_aws_key_file = "~/keys/ditwl_kp_infradmin.pem"
  • provider_default_aws_profile: Corresponde al perfil utilizado en el fichero de credenciales.
  • provider_default_aws_region: us-east-1 corresponde a US East (N. Virginia) y es la región de AWS más barata. Lea el artículo Choose your AWS Region wisely.
  • provider_default_aws_account_id: es el ID de la cuenta de AWS, vea Crear una nueva cuenta de AWS para la demo.
  • provider_default_aws_shared_credentials_file: la ruta completa al fichero de credenciales. Se utiliza el valor por defecto “~/.aws/credentials”.
  • provider_default_aws_key_file: la ruta completa a la clave privada que usará Terraform para aprovisionar instancias y que posteriormente usará Ansible para conectar por SSH y continuar con las instalación del software.

Los valores de terraform.tfvars se utilizan en provider_aws.tf para configuración el proveedor AWS de Terraform.

Fijar el valor de allowed_account_ids evitar que Terraform realice cambios en una cuenta de AWS incorrecta.

Mejores Prácticas: Redundancia en la Configuración

En IT Wonder Lab recomendamos realizar algunas configuraciones redundantes cuando tratamos con infraestructura.
La redundancia actúa como una doble comprobación que evita errores catastróficos.

Especifique el valor de la cuenta de AWS en la variable provider_default_aws_account_id del fichero terraform.tfvars. Esta variable se usará para fijar ese mismo valor en allowed_account_ids del AWS provider.

La variables aws_access_key_id de un usuario IAM se utiliza por AWS para identificar el ID de cuenta del usuario, todas las operaciones de ese usaurio serán aplicadas a esa cuenta, por lo que no es estrictamente necesario especificar de nuevo el ID de cuenta, lo hacemos por redundancia y seguridad.

provider "aws" {
  shared_credentials_file = pathexpand(var.provider_default_aws_shared_credentials_file)
  profile                 = var.provider_default_aws_profile
  region                  = var.provider_default_aws_region
  allowed_account_ids     = var.provider_default_aws_account_id
  version                 = "~> 2.0"
}

Creación de sub-redes VPC

AWS crea un VPC (Cloud Privado Virtual) en cada una de las regiones de AWS en la que se ha creado una cuenta.

La eliminación del VPC por defecto es irreversible y en caso de querer recuperarlo se debe contactar con soporte o recrear la cuenta.

Es posible adoptar en Terraform cualquier recurso ya existente en AWS, incluido el VPC por defecto, sin embargo no los consideramos una buena práctica.

Mejores Prácticas: Crear el AWS VPC desde Terraform

Debemos controlar totalmente la infraestructura de la que somos responsables por lo que se recomienda eliminar el VPC por defecto y crear uno controlado por Terraform en el que especifiquemos el rango de IPs y todos los detalles.

El VPC se creará usando el rango privado 172.17.32.0/19 IPv4 (8192 IPs) dividido en varias sub-redes mas pequeñas de 512 máquinas (máscara /23). Vea ipv4 Subnet Calculator.

terraform.tfvars

#------------------------
# VPC
#------------------------

aws_vpc_tag_name = "ditwl-vpc"
aws_vpc_block = "172.17.32.0/19" #172.17.32.1 - 172.16.67.254

Para el tutorial solo crearemos 4 sub-redes.

Mejores Prácticas: Múltiples Zonas de Disponibilidad AWS

Las sub-redes deben estar distribuidas entre varias “availabilities zones” para mejorar la alta disponibilidad y además deberán tener zonas privadas y zonas públicas donde ubicar los recursos según su uso reduciendo su exposición a Internet.

3 AWS availability zones
VPC que utiliza varias zonas de disponibilidad con redes públicas y privadas

La ubicación de las instancias en diferentes zonas de disponibilidad supone un coste adicional por la transferencia de datos entre zonas.

CIDRHost Address RangeNombre en TerraformDescripción
172.17.32.0/23172.17.32.1 – 172.17.33.254aws_sn_za_pro_pub_32Zona: A Entorno: PRO Tipo: PUBLICA Código: 32
172.17.34.0/23172.17.34.1 – 172.17.35.254aws_sn_za_pro_pri_34Zona: A Entorno: PRO Tipo: PRIVADA Código: 34
172.17.36.0/23172.17.36.1 – 172.17.37.254aws_sn_zb_pro_pub_36Zona: B Entorno: PRO Tipo: PUBLICA Código: 36
172.17.38.0/23172.17.38.1 – 172.17.39.254aws_sn_zb_pro_pri_38Zona: B Entorno: PRO Tipo: PRIVADA Código: 38
172.17.40.0/23172.17.40.1 – 172.17.41.254aws_sn_z?_pr?_p??_40Sin uso
172.17.42.0/23172.17.42.1 – 172.17.43.254aws_sn_z?_pr?_p??_42Sin uso
172.17.44.0/23172.17.44.1 – 172.17.45.254aws_sn_z?_pr?_p??_44Sin uso
172.17.46.0/23172.17.46.1 – 172.17.47.254aws_sn_z?_pr?_p??_46Sin uso
172.17.48.0/23172.17.48.1 – 172.17.49.254aws_sn_z?_pr?_p??_48Sin uso
172.17.50.0/23172.17.50.1 – 172.17.51.254aws_sn_z?_pr?_p??_50Sin uso
172.17.52.0/23172.17.52.1 – 172.17.53.254aws_sn_z?_pr?_p??_52Sin uso
172.17.54.0/23172.17.54.1 – 172.17.55.254aws_sn_z?_pr?_p??_54Sin uso
172.17.56.0/23172.17.56.1 – 172.17.57.254aws_sn_z?_pr?_p??_56Sin uso
172.17.58.0/23172.17.58.1 – 172.17.59.254aws_sn_z?_pr?_p??_58Sin uso
172.17.60.0/23172.17.60.1 – 172.17.61.254aws_sn_z?_pr?_p??_60Sin uso
172.17.62.0/23172.17.62.1 – 172.17.63.254aws_sn_z?_pr?_p??_62Sin uso

Mejores Prácticas: Patrones de nombrado en AWS y Terraform

Nombrar cada variables y recurso utilizando un patrón común que indica el proveedor, recurso, entorno, visibilidad y un identificador único.

Patrones de nombrado en variables de Terraform

Para las sub-redes se utiliza un patrón de nombrado en minúscula y separado por guión bajo compuesto por:

  • Proveedor: un prefijo que identifica al proveedor, en este caso AWS.
  • Recurso: un nombre corto que identifica el recurso, en este caso “sn” significa subnet o subred.
  • Entorno: para recursos que no son compartidos entre varios entornos, se utiliza un acrónimo del entorno compuesto por tres letras:
    • pro: producción
    • pre: preproducción
    • dev: desarrollo
  • Visibilidad: para recursos que pueden ser públicos o privados, se utiliza un acrónimo de tres letras:
    • pub: para recursos públicos
    • pri: para recursos privados
  • ID único: un identificado único que está relacionado con el recursos. En este caso se ha utilizado el tercer octeto de la dirección IP. (172.17.NN.0) ya que es diferente en todas las sub-redes. El objetivo es que el ID único permita detectar errores cometidos al realizar “copia y pega”.

Ejemplo:

aws_sn_za_pro_pub_32 = { …}

Patrones en los valores de Terraform

El nombre de los recursos (ej. un servidor) sigue un patrón muy simular al usado en las variables, aunque utilizando un guion medio como separador y el nombre del cloud en lugar del proveedor.

  • Cloud: un prefijo único que identifica este cloud entre todos los utilizados por la empresa.

    En este caso el prefijo utilizado es: ditwl acrónimo extraído de Demo IT Wonder Lab en minúscula.

El uso de este patrón permite que los sistemas externos, como el de monitorización, identifiquen unívocamente cada elemento.

Ejemplo:

name =”ditwl-sn-za-pro-pub-32″

El uso de un patrón constante permite que el nombre tenga toda la información necesaria para entender su uso sin que una descripción más exhaustiva sea necesaria.

Definición de la sub-red en Terraform

terraform.tfvars

#------------------------
# SUBNETS
#------------------------

  #------------------------
  # For EC2 instances
  #------------------------

    #Zone: A, Env: PRO, Type: PUBLIC, Code: 32
    aws_sn_za_pro_pub_32={
      cidr   ="172.17.32.0/23" #172.17.32.1 - 172.17.33.254
      name   ="ditwl-sn-za-pro-pub-32"
      az     ="us-east-1a"
      public = "true"
    }

    #Zone: A, Env: PRO, Type: PRIVATE, Code: 34
    aws_sn_za_pro_pri_34={
      cidr   = "172.17.34.0/23" #172.17.34.1 - 172.17.35.254
      name   = "ditwl-sn-za-pro-pri-34"
      az     = "us-east-1a"
      public = "false"
    }

    #Zone: B, Env: PRO, Type: PUBLIC, Code: 36
    aws_sn_zb_pro_pub_36={
      cidr   = "172.17.36.0/23" #172.17.36.1 - 172.17.37.254
      name   = "ditwl-sn-zb-pro-pub-36"
      az     = "us-east-1b"
      public = "false"
    }

    #Zone: B, Env: PRO, Type: PRIVATE, Code: 38
    aws_sn_zb_pro_pri_38={
      cidr   = "172.17.38.0/23" #172.17.38.1 - 172.17.39.254
      name   = "ditwl-sn-zb-pro-pri-38"
      az     = "us-east-1b"
      public = "false"
    }

Plan

Ejecutar el plan de Terraform de forma periódica es la mejor forma de comprobar que todo es correcto. Algunos errores son difíciles de identificar por lo que recomendamos realizar pequeños cambios en la configuración de Terraform y comprobar el plan.

El siguiente asciicast muestra los elementos que serán creados por Terraform en AWS.

Pulse el botón play para ver la ejecución del plan de Terraform.

Depurar Terraform

Terraform utiliza el valor de la variable de entorno TF_LOG para definir su nivel de LOG. Los valores permitos son TRACE, DEBUG, INFO, WARN o ERROR. Además puede enviar el LOG a un fichero fijando la variable de entorno TF_LOG_PATH con la ruta completa del fichero.

Fije la variable en DEBUG y a continuación ejecute Terraform

$export TF_LOG=DEBUG
$#Opcional export TF_LOG_PATH=~/debug.txt
$terraform plan
2020/07/31 20:19:01 [WARN] Log levels other than TRACE are currently unreliable, and are supported only for backward compatibility.
  Use TF_LOG=TRACE to see Terraform's internal logs.
  ----
2020/07/31 20:19:01 [INFO] Terraform version: 0.12.26  
2020/07/31 20:19:01 [INFO] Go runtime version: go1.12.13
2020/07/31 20:19:01 [INFO] CLI args: []string{"/usr/bin/terraform", "plan"}
2020/07/31 20:19:01 [DEBUG] Attempting to open CLI config file: /home/jruiz/.terraformrc
2020/07/31 20:19:01 [DEBUG] File doesn't exist, but doesn't need to. Ignoring.
2020/07/31 20:19:01 [INFO] CLI command args: []string{"plan"}
2020/07/31 20:19:01 [WARN] Log levels other than TRACE are currently unreliable, and are supported only for backward compatibility.
  Use TF_LOG=TRACE to see Terraform's internal logs.
  ----
2020/07/31 20:19:01 [DEBUG] New state was assigned lineage "cc420085-6d12-cb64-c041-a0ed135c58ed"
2020/07/31 20:19:01 [DEBUG] checking for provider in "."
2020/07/31 20:19:01 [DEBUG] checking for provider in "/usr/bin"
2020/07/31 20:19:01 [DEBUG] checking for provider in ".terraform/plugins/linux_amd64"
2020/07/31 20:19:01 [DEBUG] found provider "terraform-provider-aws_v2.67.0_x4"
2020/07/31 20:19:01 [DEBUG] found provider "terraform-provider-external_v1.2.0_x4"
2020/07/31 20:19:01 [DEBUG] found valid plugin: "aws", "2.67.0", "/home/jruiz/git/github/terraform-aws-ec2-rds-basic-free/.terraform/plugins/linux_amd64/terraform-provider-aws_v2.67.0_x4"
2020/07/31 20:19:01 [DEBUG] found valid plugin: "external", "1.2.0", "/home/jruiz/git/github/terraform-aws-ec2-rds-basic-free/.terraform/plugins/linux_amd64/terraform-provider-external_v1.2.0_x4"
2020/07/31 20:19:01 [DEBUG] checking for provisioner in "."
2020/07/31 20:19:01 [DEBUG] checking for provisioner in "/usr/bin"
2020/07/31 20:19:01 [DEBUG] checking for provisioner in ".terraform/plugins/linux_amd64"
2020/07/31 20:19:01 [INFO] backend/local: starting Plan operation
...
2020/07/31 20:19:30 [DEBUG] ProviderTransformer: "module.aws_sg_rds_mariadb_pro_pub_01.aws_security_group.default" (*terraform.NodeValidatableResource) needs provider.aws
6.aws_subnet.default - *terraform.NodeValidatableResource
    provider.aws - *terraform.NodeApplyableProvider
  module.aws_sn_zb_pro_pub_36.output.id - *terraform.NodeApplyableOutput
  module.aws_sn_zb_pro_pub_36.var.az - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.cidr - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.description - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.name - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.public - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.vpc_id - *terraform.NodeApplyableModuleVariable
  module.aws_sr_ec2_default_internet_to_ssh.aws_security_group_rule.default - *terraform.NodeValidatableResource

....
2020/07/31 20:19:30 [DEBUG] ProviderTransformer: "module.aws_sg_rds_mariadb_pro_pub_01.aws_security_group.default" (*terraform.NodeValidatableResource) needs provider.aws
6.aws_subnet.default - *terraform.NodeValidatableResource
    provider.aws - *terraform.NodeApplyableProvider
  module.aws_sn_zb_pro_pub_36.output.id - *terraform.NodeApplyableOutput
  module.aws_sn_zb_pro_pub_36.var.az - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.cidr - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.description - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.name - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.public - *terraform.NodeApplyableModuleVariable
  module.aws_sn_zb_pro_pub_36.var.vpc_id - *terraform.NodeApplyableModuleVariable
  module.aws_sr_ec2_default_internet_to_ssh.aws_security_group_rule.default - *terraform.NodeValidatableResource
...

Continúe los tutoriales de Terraform y Ansible, vea:

AWS VPC, Route 53, RDS MariaDB, EC2 usando Ansible y Terraform (1/5)

Leave a Reply

Your email address will not be published.