Crear VM-Apache2 con Terraform en Azure [ES]
Tags: terraform, azure, apache
Published: 2021-01-12 20:41:48

Luego de conocer ciertos principios de DevOps y lo que es Infraestructura como Código se procederá a la creación de recursos en un proveedor Cloud en este caso Azure usando Terraform.

Por que Terraform? 

Es agnóstico a la plataforma, funciona con proveedores de nubes publicas muy usados en la actualidad, automatización del proceso de la creación de infraestructura 

Terraform trabaja en torno a estos archivos *.tf  *.tfvars y  *.tfstate 

**Archivo * .tf:**

Contienen realmente los recursos que se crearán en el proveedor de la nube . Estos contienen todas las configuraciones que debe tener el recurso creado.

**Archivo * .tfvars:**

Este archivo contiene las variables que se pueden reemplazar en archivos * .tf 

**Archivo * .tfstate:**

Esta es la fuente de verdad para terraform. Este archivo contiene los ID y las propiedades de los recursos que creó Terraform para que pueda administrar o destruir esos recursos en el futuro. 

Ejemplo introductorio crear un recurso:


Recursos:
-Cuenta de Azure


>   [1. Instalar Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli?in=terraform/azure-get-started) 

Esta herramienta esta disponible para Windows, Mac, Linux


>  [2. Instalar el CLI de Azure ](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) 

Para instalar la línea de comandos de Azure si está en Windows recomiendo usar el MSI(Microsoft Installer) de la documentación oficial


> Autenticarse en CLI de Azure

Usar el comando 

`az login` 

Iniciar sesión con las credenciales que tenga una suscripción activa en la Cloud



> Crear un directorio

Le puede dar cualquier nombre a este directorio en este caso "learn-terraform-azure" dentro de el crear el archivo `main.tf` en este archivo estará el código escrito en HCL(Hashi Corp Language) 

```
# Configure the Azure provider
terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = ">= 2.26"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg" {
  name     = "myTFResourceGroup"
  location = "westus2"
}

```

Aquí hay dos declaraciones importantes esta `provider` donde se declara el proveedor de la nube a usar y `resource` donde se indica el tipo de recurso a elegir de todos los que se encuentra disponibles en Azure en este caso un grupo de recursos con una región de despliegue


> Inicializar terraform

Dentro del proyecto ejecutar el comando `terraform init` el cual leera la configuracion inicial del archivo escrito para preparar el entorno de terraform para su posterior despliegue


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1610477721350/ikZRJBodh.png)


> Generar el plan de ejecución

Ejecutar el comando `terraform plan` con lo cual se obtendrá un plan detallado de todo lo que se creara con respecto al código 

>Aplicar la configuracion

Una vez revisado el plan y saber que recursos se crearan se ejecuta `terraform apply` luego aceptar la ejecucion con `yes`  y se tendran creado los recursos 

> Inspeccionar el estado
Ver el grupo de recursos que se han creado mediante terraform
`terraform show`

> Ver en la consola de la nube de Azure


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1610478746461/1tKNo8kIr.png)

>Destruir el recurso

Usando el comando `terraform destroy` destruimos lo creado en el proveedor de la nube luego de aceptar la ejecucion con escribiendo `yes`


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1610479232296/3ike8Opgo.png)



![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1610479265777/FSgGIG0ar.png)

> Cambios en la infraestructura

Se puede agregar distintos cambios al codigo sin afectar el plan previamente creado una vez realizado esto se ejecutará `terraform plan -out=newplan` para obtener el plan de salida de los nuevos y luego `terraform apply newplan`

**Segunda parte**

*En el siguiente ejemplo se creará una máquina virtual en Azure que tendrá instalado un servidor web de apache, todo esto desde código sin acceder al servidor *

> Crear un archivo `variables.tf` en que se declara el uso de variables en la creación de los recursos 

```
variable "location" {}

variable "admin_username" {
  type        = string
  description = "Administrator user name for virtual machine"
}

variable "admin_password" {
  type        = string
  description = "Password must meet Azure complexity requirements"
}

variable "prefix" {
  type    = string
  default = "my"
}

variable "tags" {
  type = map

  default = {
    Environment = "Terraform GS"
    Dept        = "Engineering"
  }
}

variable "sku" {
  default = {
    westus2 = "16.04-LTS"
    eastus  = "18.04-LTS"
  }
}


```

> Crear el archivo `terraform.tfvars` en donde se establecen los valores de las varibles 

```
location       = "xxxxx"
prefix         = "xx"
admin_username = "xxxxx"
admin_password = "xxxxxxxx"
```
> Crear un archivo `azure-user-data.sh` que tendrá los script iniciales que se ejecutaran en la maquina virtual creada en Azure

```
#! /bin/bash
sudo apt-get update
sudo apt-get install -y apache2
sudo systemctl start apache2
sudo systemctl enable apache2
echo "<h1>Demp Apache2 from Azure Virtual Machine</h1>" | sudo tee /var/www/html/index.html

```
> Las siguientes configuraciones se realizaran en el archivo `main.tf` 

**Agregar una regla de red que habilite el puerto 80**

```
    security_rule {
    name                       = "allow-http"
    description                = "allow-http"
    priority                   = 110
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "Internet"
    destination_address_prefix = "*"
  }
```

Dentro del recurso de red

```
resource "azurerm_network_security_group" "nsg" {
  name                = "myTFNSG"
  location            = "westus2"
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

    security_rule {
    name                       = "allow-http"
    description                = "allow-http"
    priority                   = 110
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "Internet"
    destination_address_prefix = "*"
  }
}
```

> Declarar la `data` para leer el archivo de configuracion inicial tambien llamado `cloud-init`

```
data "template_file" "linux-vm-cloud-init" {
  template = file("azure-user-data.sh")
}
```

>Cargar la `data` en sistema operativo de la maquina virtual, decodificando el archivo anteriormente declarado

```
  os_profile {
    computer_name  = "myTFVM-kg"
    admin_username = var.admin_username
    admin_password = var.admin_password
    custom_data = base64encode(data.template_file.linux-vm-cloud-init.rendered)
  }
```

> Configuración completa del archivo `main.tf`

Tener en cuenta usar   `vm_size               = "Standard_B1s"` para asegurarnos usar la capa gratis de Azure

> Declarar una salida por consola de la ip de la vm creada 

```
output "public_ip_address" {
  value = data.azurerm_public_ip.ip.ip_address
}
```

> `main.tf`

```
# Configure the Azure provider
terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = ">= 2.26"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg" {
  name     = "myTFResourceGroup"
  location = "westus2"

  tags = {
      Environment = "Terraform Getting Started"
      Team = "DevOps"
  }
}

# Create a virtual network
resource "azurerm_virtual_network" "vnet" {
    name                = "myTFVnet"
    address_space       = ["10.0.0.0/16"]
    location            = "westus2"
    resource_group_name = azurerm_resource_group.rg.name
}

# Create subnet
resource "azurerm_subnet" "subnet" {
  name                 = "myTFSubnet"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.1.0/24"]
}

# Create public IP
resource "azurerm_public_ip" "publicip" {
  name                = "myTFPublicIP"
  location            = "westus2"
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Static"
}

# Create Network Security Group and rule
resource "azurerm_network_security_group" "nsg" {
  name                = "myTFNSG"
  location            = "westus2"
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

    security_rule {
    name                       = "allow-http"
    description                = "allow-http"
    priority                   = 110
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "Internet"
    destination_address_prefix = "*"
  }
}

# Create network interface
resource "azurerm_network_interface" "nic" {
  name                      = "myNIC"
  location                  = "westus2"
  resource_group_name       = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "myNICConfg"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "dynamic"
    public_ip_address_id          = azurerm_public_ip.publicip.id
  }
}

data "template_file" "linux-vm-cloud-init" {
  template = file("azure-user-data.sh")
}

# Create a Linux virtual machine
resource "azurerm_virtual_machine" "vm" {
  name                  = "myTFVM"
  location              = "westus2"
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic.id]
  vm_size               = "Standard_B1s"


  storage_os_disk {
    name              = "myOsDisk"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Premium_LRS"
  }

  storage_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = lookup(var.sku, var.location)
    version   = "latest"
  }

  os_profile {
    computer_name  = "myTFVM-kg"
    admin_username = var.admin_username
    admin_password = var.admin_password
    custom_data = base64encode(data.template_file.linux-vm-cloud-init.rendered)
  }

  os_profile_linux_config {
    disable_password_authentication = false
  }
}

data "azurerm_public_ip" "ip" {
  name                = azurerm_public_ip.publicip.name
  resource_group_name = azurerm_virtual_machine.vm.resource_group_name
  depends_on          = [azurerm_virtual_machine.vm]
}

output "public_ip_address" {
  value = data.azurerm_public_ip.ip.ip_address
}


```

**Estrucutra de archivos **

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1610480552296/T9diNq4pB.png)

Ejecutar 
`terraform plan`

`terraform apply` - yes




![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1610480920715/OSaX0xFLv.png)


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1610480951214/9BMg3lK2i.png)


Se ha creado un servidor web-apache2  desde el código en minutos 👏🏼👏🏼

`terraform destroy`

Se destruye el recurso

En minutos se crea y destruye un servidor web en la nube con un gasto de minimo de apenas centavos 


> Almacenar el estado remoto en Terraform Cloud


Los backends remotos de Terraform permiten que Terraform utilice un espacio de almacenamiento compartido para los datos de estado. El backend remoto de Terraform Cloud también permite a los equipos versionar, auditar y colaborar fácilmente en cambios de infraestructura.

>Registrarse en Terraform Cloud

>Configurar el backend remoto

```
terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = ">= 2.26"
    }
  }

+ backend "remote" {
+   organization = "<ORG_NAME>"
+   workspaces {
+     name = "Example-Workspace"
+   }
+ }
}

provider "azurerm" {
  features {}
}
```

> Iniciar sesion en local `terraform login`

> Ver la cuenta de azure iniciado` az account list`

> Ver informacion necesaria de la cuenta de azure`az account set --subscription="SUBSCRIPTION_ID"`

>Copiar las ID en la cloud de terraform
```
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/SUBSCRIPTION_ID"
```



![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1610482017272/dKPGzIFlI.png)

> Aplicar las configuraciones `terraform apply`


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1610482103778/zQgpirbN5.png)

>Destruir los recursos creados `terraform destroy`

Terraform Cloud ofrece un entorno de ejecución compartido predecible y confiable con herramientas para ayudarlo a trabajar juntos en configuraciones y módulos de Terraform.

Aunque Terraform Cloud puede actuar como un backend remoto estándar para admitir las ejecuciones de Terraform en máquinas locales, funciona incluso mejor como un entorno de ejecución remota.