Resumen
En este tutorial se muestra el uso de una herramienta como Terraform para construir y modificar infraestructura cloud mediante código. Se estudiarán casos de uso en OpenStack, Google Cloud y Microsoft Azure.
-
Conocer el papel de la Infraestructura como código en la cultura DevOps.
-
Entender la utilidad y potencia de la Infraestructura como código.
-
Inicializar un proveedor cloud en Terraform.
-
Desplegar infraestructura en OpenStack, Google Cloud y Microsoft Azure.
-
Inicializar las máquinas virtuales en el momento de su creación.
Tip
|
Disponible el repositorio usado en este tutorial. |
1. Introducción
Desde hace un tiempo se oye hablar mucho de DevOps. Se trata de una fusión que combina las áreas de:
-
Desarrollo
-
Operaciones
-
Control de calidad
Es una extensión natural de metodologías Agile y es habitual el uso de los principios CAMS, cuyas siglas vienen de:
-
Cultura relacionada con comunicación humana, procesos y herramientas
-
Automatización de procesos
-
Monitorización
-
Sharing feedback, buenas prácticas y conocimiento
En DevOps son habituales las prácticas siguientes:
-
Planificación ágil
-
Despliegue continuo (CI/CD). La subida de cambios al repositorio de código desencadena la ejecución de pruebas automatizadas que finalmente realizan el despliegue de los cambios tras superarse las pruebas.
-
Infraestructura como código (Infrastructure as Code). Se trata del desarrollo de scripts para las tareas de despliegue y gestión de la infraestructura
-
Contenedorización. Combinada con la Infraestructura como código, permite el despliegue instantáneo de aplicaciones en contenedores.
-
Microservicios. Desarrollo de aplicaciones como un conjunto de servicios independientes. Cada servicio se despliega de forma independiente lo que facilita la escalabilidad y la actualización de los servicios.
-
Infraestructura cloud. Favorece el despliegue, la disponibilidad y la automatización.
En esta asignatura ya hemos tratado las prácticas de Infraestructura cloud y de Contenedorización. En este tema nos centraremos en la Infraestructura como código, a la que podríamos caracterizar de esta forma:
-
Uso de scripts para configurar de forma automática el entorno (redes, máquinas virtuales, volúmenes, …) con independencia de su estado inicial.
-
Versionado y desarrollo colaborativo del código de la infraestructura mediante sistemas de control de versiones.
-
Infraestructura repetible.
-
Evita errores humanos.
-
Se especifica el estado de lo que se quiere.
Actualmente, existen varias propuestas para hacer Infraestructura como código. Hay algunas que son específicas de un proveedor, como AWS CloudFormation y Azure Resource Manager. Otras son más genéricas y permiten trabajar con varios proveedores, como Terraform, Pulumi o Ansible.
En este tema estudiaremos Terraform, una herramienta para construir, modificar y versionar infraestructura de forma segura y eficiente.
2. Terraform
Terraform es una herramienta para construir, modificar y versionar infraestructura de forma segura y eficiente. Es un proyecto Open Source desarrollado por HashiCorp, surgido en 2014. Genera un plan de ejecución (preview) indicando qué hará para conseguir el estado deseado. Si hay cambios en la configuración, Terraform detecta los cambios y crea un plan incremental para alcanzar el nuevo estado.
2.1. Instalación
La instalación de Terraform es muy sencilla. Se descarga como un binario que hay que descoprimir. Luego se coloca en un directorio incluido en el PATH del sistema. Probamos su funcionamiento desde la terminal con terraform
Usage: terraform [global options] <subcommand> [args]
The available commands for execution are listed below.
The primary workflow commands are given first, followed by
less common or more advanced commands.
Main commands:
init Prepare your working directory for other commands
validate Check whether the configuration is valid
plan Show changes required by the current configuration
apply Create or update infrastructure
destroy Destroy previously-created infrastructure
All other commands:
console Try Terraform expressions at an interactive command prompt
fmt Reformat your configuration in the standard style
force-unlock Release a stuck lock on the current workspace
get Install or upgrade remote Terraform modules
graph Generate a Graphviz graph of the steps in an operation
import Associate existing infrastructure with a Terraform resource
login Obtain and save credentials for a remote host
logout Remove locally-stored credentials for a remote host
metadata Metadata related commands
output Show output values from your root module
providers Show the providers required for this configuration
refresh Update the state to match remote systems
show Show the current state or a saved plan
state Advanced state management
taint Mark a resource instance as not fully functional
test Experimental support for module integration testing
untaint Remove the 'tainted' state from a resource instance
version Show the current Terraform version
workspace Workspace management
Global options (use these before the subcommand, if any):
-chdir=DIR Switch to a different working directory before executing the
given subcommand.
-help Show this help output, or the help for a specified subcommand.
-version An alias for the "version" subcommand.
2.2. Sintaxis de los archivos
Hashicorp usa su propio lenguaje de configuración para la descripción de la infraestructura.
Los archivos Terraform se pueden escribir en dos formatos:
-
HashiCorp Configuration Language (HCL). La extensión de los archivos es
.tf
-
JSON. La extensión de los archivos es
.tf.json
El formato preferido es HCL, ya que es más legible y fácil de escribir. No obstante, el lenguaje HCL es un poco complicado y puede ser confuso al principio, especialmente si se quieren hacer bucles o condicionales.
Note
|
Pulumi, una herramienta similar a Terraform, permite escribir la configuración en varios lenguajes de programación como Python, TypeScript, Go, … Sin embargo, Terraform es más popular y tiene una comunidad más grande. Esto, unido a que el estado en Terraform se almacena en local de forma predeterminada, mientras que en Pulumi se almacena en la nube, hace que Pulumi pueda despertar recelos en entornos corporativos. Puedes obtener más información en el tutorial Infraestructura como código con Pulumi. |
2.3. Recursos y módulos
El objetivo de Terraform es declarar recursos. Todas las características del lenguaje giran en torno a hacer que la definición de recursos sea más flexible y conveniente.
Los recursos puede agruparse en módulos, que crean una unidad de configuración de nivel más alto. Un recurso describe un objeto básico de infraestructura, mientras que un módulo describe un conjunto de objetos y sus relaciones para crear un sistema mayor.
externa
resource "openstack_networking_floatingip_v2" "tf_vm_ip" {
pool = "externa"
}
Una configuración Terraform consta de un módulo raíz donde comienza la evaluación. El módulo puede contener módulos hijo que se van llamando unos a otros. La configuración más sencilla de módulo contendría sólo un archivo .tf
(main.tf
) aunque se recomienda una organización como la siguiente:
-
main.tf
: Configuración de lo recursos del módulo -
providers.tf
: Proveedor de los recursos del módulo -
variables.tf
: Variables de entrada -
terraform.tfvars
: Valores de las variables de entrada -
output.tf
: Variables de salida
Tip
|
El archivo |
Ejemplo de organización:
├── README.md
├── main.tf
├── providers.tf
├── variables.tf
├── terraform.tfvars
├── outputs.tf
├── ...
├── modules/
│ ├── moduleA/
│ │ ├── README.md
│ │ ├── main.tf
│ │ ├── providers.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ ├── moduleB/
│ ├── .../
2.4. Providers
Terraform puede crear stacks de infraestructura en varios proveedores. Por ejemplo, una configuración podría crear infraestructura en Google Cloud Platform y en OpenStack-DI.
Hay gran cantidad de proveedores Terraform, tanto oficiales, mantenidos por Hashicorp, (AWS, Azure, Google Cloud Platform, Heroku, Kubernetes, MongoDB Atlas, OpenStack, VMware Cloud, VMware vSphere, …) como de la comunidad y terceros (OpenShift, Trello, Telegram, …)
2.5. Variables
2.5.1. Variables de entrada
Las variables de entrada se usan como parámetros para los módulos. Se crean mediante bloques variable
variable "openstack_user_name" {
type = string
description = "The username for the Tenant."
default = "mtorres" (1)
}
variable "security_groups" {
type = list(string)
default = ["default"]
}
-
Valor por defecto. Esto es opcional y se usa si no se especifica un valor en el archivo
terraform.tfvars
.
Las variables se usan siguiendo esta sintaxis var.<variable>
.
provider "openstack" {
user_name = var.openstack_user_name (1)
....
}
-
Uso de la variable
openstack_user_name
Más información sobre la declaración, uso de variables y constructores de tipos en la documentación oficial.
2.5.2. Configuración de variables
Las variables se pueden configurar de varias formas:
-
En el archivo
variables.tf
con un valor por defecto. -
En el archivo
terraform.tfvars
con un valor específico.
Declaración de variables sin valor por defecto
Por ejemplo, si define una variable user_name
en variables.tf
, se puede configurar en terraform.tfvars
con un valor específico.
variables.tf
variable "user_name" {
type = string
description = "The username for the Tenant."
}
terraform.tfvars
user_name = "mtorres"
Declaración de variables con valor por defecto
Si se define una variable user_name
en variables.tf
con un valor por defecto, se puede configurar en terraform.tfvars
con un valor específico o dejar el valor por defecto.
variables.tf
variable "user_name" {
type = string
description = "The username for the Tenant."
default = "mtorres" (1)
}
-
Valor por defecto
terraform.tfvars
user_name = "mtorres"
Si se define una variable en variables.tf
con un valor por defecto y no se configura en terraform.tfvars
, se usará el valor por defecto. En cambio, si se configura en terraform.tfvars
, se usará el valor específico, independientemente del valor por defecto.
2.5.3. Variables de entorno
Terraform permite el uso de variables de entorno para la configuración. Se definen con la sintaxis TF_VAR_<variable>
.
Por ejemplo, si se define una variable PASSWORD
en Terraform, se puede acceder a ella en la shell como TF_VAR_PASSWORD
. Terraform la reconocerá como PASSWORD
.
$ export TF_VAR_PASSWORD=xxxx
Posterirmente, se accede a la variable en Terraform como var.PASSWORD
.
provider "openstack" {
user_name = var.openstack_user_name
tenant_name = var.openstack_tenant_name
password = var.PASSWORD (1)
auth_url = var.openstack_auth_url
}
-
Uso de la variable
La variable PASSWORD
no tiene por qué estar definida en el archivo variables.tf
. Terraform la reconocerá como PASSWORD
. Además, Terraform no la incluirá en el archivo de estado. Esto es muy útil para almacenar valores sensibles como contraseñas o claves de acceso.
Warning
|
Configurar contraseñas en variables de entorno es una buena práctica de seguridad. Por contra, almacenar contraseñas en archivos de configuración es una mala práctica, ya que si se suben al repositorio de código quedan expuestas y además se almacenan en el archivo de estado de Terraform, lo que puede ser un problema de seguridad. |
2.5.4. Variables de salida
Las variables de salida se usan para pasar valores a otros módulos o para mostrar en el CLI un resultado tras un despliegue con terraform apply
.
Las variables de salida se definen con bloques output
y un identificador único. Normalmente, toman como valor una expresión (p.e. una IP generada para una instancia creada).
output tf_vm_Floating_IP {
value = openstack_networking_floatingip_v2.tf_vm_ip.address (1)
depends_on = [openstack_networking_floatingip_v2.tf_vm_ip] (2)
}
-
Expresión que devuelve la dirección IP de un recurso previamente creado.
-
Argumento opcional que establece una dependencia con un recurso creado.
2.6. Archivo de estado
Terraform guarda la información de la infraestructura creada en un archivo de estado Terraform (terraform.tfstate
). Este archivo se usa al ejecutar los comandos terraform plan
o terraform apply
para determinar los cambios a aplicar. Gracias a esto se puede:
-
Seguir la pista de los cambios en la infraestructura
-
Actualizar sólo los componentes necesarios
-
Eliminar componentes
Una característica muy interesante de Terraform es la idempotencia, así como la facilidad para aplicar cambios. Si volvemos a ejecutar un despliegue con terraform apply
y no ha habido cambios en los archivos de configuración tras el último despliegue (cuyo estado quedó almacenado en el archivo .tfstate
), el despliegue quedará intacto. Es decir, no se volverá a crear infraestructura repetida, ni se reemplazará la infraestructura creada por una nueva si no hay cambios en los archivos de configuración.
Sin embargo, si modificamos la configuración modificando los archivos Terraform estaremos indicando un nuevo estado al que queremos llegar. En este caso, al aplicar terraform apply
sí se desplegarán los cambios realizados en la configuración. Sin embargo, sólo se desplegarán los recursos correspondientes a los cambios realizados, manteniendo intacta la configuración no modificada.
2.7. Gestión de la infraestructura
Normalmente, estos son los pasos que se deben seguir para construir, mantener y eliminar una infraestructura con Terraform.
-
Inicializar el directorio del proyecto Terraform (
terraform init
). El comando descarga todos los componentes necesarios, incluyendo módulos y plugins. La inicialización crea un archivo.terraform
en el directorio de trabajo con los plugins necesarios. La información necesaria sobre los plugins y proveedores a descargar se suele encontrar en el archivoproviders.tf
. -
Crear un plan de ejecución (
terraform plan
). El comando determina las acciones necesarias para alcanzar el estado deseado especificado en los archivos de configuración (p.e.main.tf
). -
Crear o modificar la infraestructura (
terraform apply
). Terraform es idempotente. Al usar este comando sólo se despliegan los recursos correspondientes a los cambios que se hayan realizado en los archivos de configuración (p.e.main.tf
), sin volver a crear lo que ya existe y no se ha modificado. Para esto, Terraform se basa en lo almacenado en los archivos de estado, que guardan la información de la infraestructura creada en el último despliegue. -
Mostrar las variables de salida de un despliegue (
terraform output
). -
Eliminar la infraestructura (
terraform destroy
). Se usa para eliminar la infraestructura creada.
Note
|
Es posible que en algún momento se produzca un fallo en un despliegue. Por ejemplo, se realiza un despliegue de una infraestructura y se produce un error por falta de recursos. En una situación como esta, Terraform no puede deshacer los cambios realizados y quizá no pueda eliminar los recursos creados. En este caso, se puede usar el comando |
3. Despliegue en OpenStack
El provider OpenStack permite crear configuraciones Terraform para desplegar infraestructura en OpenStack. Entre los recursos que podemos gestionar están:
-
Instancias
-
Credenciales
-
Imágenes
-
Redes
-
Almacenamiento de bloques
-
Almacenamiento NFS
-
Balanceadores de carga
3.1. Configuración del provider
Para usarlo hay que configurar sus parámetros de acceso (p.e. usuario, proyecto, endpoint, …). Lo haremos en un archivo providers.tf
. El archivo providers.tf
se usa para definir y configurar los proveedores de los recursos del módulo.
providers.tf
terraform {
required_version = ">= 0.14.0"
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "~> 1.53.0"
}
}
}
provider "openstack" {
user_name = var.openstack_user_name
tenant_name = var.openstack_tenant_name
password = var.PASSWORD (1)
auth_url = var.openstack_auth_url
}
-
La contraseña se accede a través de la variable de entorno
TF_VAR_PASSWORD
para evitar almacenarla en el archivo de configuración y en el archivo de estado. Esto es una buena práctica de seguridad.
Se usan las variables definidas en el archivo variables.tf
variable "openstack_user_name" {
description = "The username for the Tenant."
default = "your-openstack-user"
}
variable "PASSWORD" {
description = "The user password."
}
variable "openstack_tenant_name" {
description = "The name of the Tenant."
default = "your-openstack-project"
}
variable "openstack_auth_url" {
description = "The endpoint url to connect to OpenStack."
default = "https://openstack.di.ual.es:5000/v3"
}
variable "openstack_keypair" {
description = "The keypair to be used."
default = "your-openstack-keypair-name"
}
3.2. Inicializar el provider
Para inicializar ejecutar terraform init
.
terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of terraform-provider-openstack/openstack from the dependency lock file
- Using previously-installed terraform-provider-openstack/openstack v1.53.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, other
commands will detect it and remind you to do so if necessary.
(base) MacBook-Pro-de-Manuel:00-pruebas-carga manolo$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding terraform-provider-openstack/openstack versions matching "~> 1.53.0"...
- Installing terraform-provider-openstack/openstack v1.53.0...
- Installed terraform-provider-openstack/openstack v1.53.0 (self-signed, key ID 4F80527A391BEFD2)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
...
Terraform has been successfully initialized!
...
Esto creará una carpeta .terraform
con en plugin de OpenStack instalado y disponible para ser usado en el proyecto. También crea un archivo .terraform.lock.hcl
que registra las selecciones de proveedores realizadas. Este archivo se debe incluir en el repositorio de control de versiones para garantizar que Terraform haga las mismas selecciones por defecto cuando se ejecute terraform init
en el futuro.
3.3. Despliegue de una instancia
La creación de una instancia se realiza con openstack_compute_instance_v2.
A continuación, crearemos una instancia denominada tf_vm
. Cada recurso creado en Terraform se identifica con un nombre. En este caso, el nombre del recurso es tf_vm
. Es el nombre que se use en resource
, no el nombre asignado en name
, es el que referencia al objeto resource
creado. Esto permite tratar el recurso creado (p.e. para asignarle una dirección IP flotante, para conectarle un volumen, …).
En el ejemplo siguiente se ilustra la creación de una máquina virtual, una dirección IP flotante (openstack_networking_floatingip_v2
) y la asignación de la IP flotante a la máquina virtual creada (openstack_compute_floatingip_associate_v2
).
#Crear nodo tf_vm
resource "openstack_compute_instance_v2" "tf_vm" {(1)
name = "tf_vm"
image_name = "jammy"
availability_zone = "nova"
flavor_name = "medium"
key_pair = var.openstack_keypair
security_groups = ["default"]
network {
name = var.openstack_network_name (2)
}
}
resource "openstack_networking_floatingip_v2" "tf_vm_ip" { (3)
pool = "ext-net"
}
resource "openstack_compute_floatingip_associate_v2" "tf_vm_ip" { (4)
floating_ip = openstack_networking_floatingip_v2.tf_vm_ip.address (5)
instance_id = openstack_compute_instance_v2.tf_vm.id (6)
}
output tf_vm_Floating_IP {
value = openstack_networking_floatingip_v2.tf_vm_ip.address (7)
depends_on = [openstack_networking_floatingip_v2.tf_vm_ip] (8)
}
-
Creación de un recurso instancia (máquina virtual) en OpenStack. El objeto recurso creado es asignado a la variable
tf_vm
. -
Red a la que se conectará la instancia creada. Usamos una variable de entrada almacenada en
variables.tf
con el nombre de la red. -
Creación de un recurso dirección IP flotante. El objeto recurso creado es asignado a la variable
tf_vm_ip
. -
Asociación de la IP flotante a la instancia
-
Acceso a la dirección del recurso IP flotante creado
-
Acceso al
id
la instancia creada -
Acceso a la dirección del recurso IP flotante creado
-
Esperar a que esté creado el recurso de la IP flotante
La creación de la instancia, igual que los demás recursos, tiene un configuración específica. En este caso, se crea una instancia con las siguientes características destacadas:
-
Nombre
tf_vm
-
Imagen
jammy
. Así es como se conoce a la imagen de Ubuntu 22.04 en OpenStack-DI. -
Zona de disponibilidad
nova
. Es el nombre de la zona de disponibilidad en OpenStack-DI. Una zona de disponibilidad es un conjunto de recursos de cómputo y almacenamiento que se encuentran en un solo centro de datos o en varios centros de datos cercanos. -
Sabor
medium
. Es el tamaño de la instancia. En OpenStack-DI, el tamañomedium
es una instancia con 2 vCPUs y 4 GB de RAM.
Para desplegar la infraestructura, ejecutar terraform apply
. Terraform mostrará un resumen de los cambios a realizar y pedirá confirmación para aplicarlos. Si la variable de entorno TF_VAR_PASSWORD
no está definida, Terraform la solicitará. Tras confirmar, Terraform creará la infraestructura. Como resultado, se mostrará la dirección IP flotante asignada a la instancia creada.
La figura siguiente ilustra la instancia creada en OpenStack-DI con la dirección IP flotante asignada.
Si ya no necesitamos la infraestructura creada, podemos eliminarla con terraform destroy
. Terraform mostrará un resumen de los cambios a realizar y pedirá confirmación para aplicarlos. Tras confirmar, Terraform eliminará la infraestructura.
3.4. Modificar el despliegue
La modificación de un despliegue se realiza modificando los archivos de configuración Terraform y ejecutando terraform apply
. Terraform detectará los cambios y mostrará un resumen de los cambios a realizar. Tras confirmar, Terraform aplicará los cambios.
A modo de ilustración, este ejemplo muestra cómo aplicar cambios a una configuración desplegada previamente. En este caso se trata de:
-
Cambiar el sabor de la instancia desplegada.
-
Crear un volumen de 1GB (
openstack_blockstorage_volume_v3
). -
Conectar el volumen a la máquina virtual (
openstack_compute_volume_attach_v2
).
resource "openstack_compute_instance_v2" "tf_vm" {
name = "tf_vm"
image_name = "jammy"
availability_zone = "nova"
flavor_name = "large" (1)
key_pair = var.openstack_keypair
security_groups = ["default"]
network {
name = var.openstack_network_name
}
}
...
resource "openstack_blockstorage_volume_v3" "tf_vol" { (2)
name = "tf_vol"
description = "first test volume"
size = 1 (3)
}
resource "openstack_compute_volume_attach_v2" "va_1" { (4)
instance_id = "${openstack_compute_instance_v2.tf_vm.id}" (5)
volume_id = "${openstack_blockstorage_volume_v3.tf_vol.id}" (6)
}
-
Modificación del sabor de la imagen
-
Creación de un recurso volumen
-
Especificación del tamaño del volumen
-
Conexión del volumen a la instancia
-
Acceso al
id
la instancia -
Acceso al
id
del volumen creado
Al ejecutar con terraform apply
, Terraform nos informará de los cambios detectados y de la nueva configuración. La nueva configuración se aplicará si confirmamos la operación. Una vez aplicados desplegados los cambios, los recursos creados se mostrarán en el panel de control de OpenStack-DI, mostrando la instancia modificada y el volumen creado y conectado a la instancia. La figura siguiente ilustra el volumen creado y conectado a la instancia.
3.5. Ejecutar un script de inicialización
Una característica muy interesante en el despliegue de una instancia es la posibilidad de ejecutar un script de inicialización durante su creación. Esto permite la creación de instancias con paquetes instalados y configurados.
Terraform permite esta operación en OpenStack pasando un script en el parámetro user_data
al crear la instancia.
Note
|
Si se modifica el valor de |
A continuación se muestra un script install_mysql.sh
que configura una base de datos MySQL inicializada con una base de datos de ejemplo. El script realiza las siguientes operaciones:
-
Actualizar el repositorio de paquetes.
-
Instalar un servidor MySQL con el password
my_password
. -
Descargar un archivo con un script SQL para inicializar una base de datos de ejemplo.
-
Ejecutar el archivo SQL para inicializar la base de datos. La inicialización consiste en la creación de una base de datos denominada
SG (Sporting Goods)
, la creación de una tabla denominadas_customers
, la inserción de datos en la tabla y la creación de un usuarioSG
con permisos sobre la base de datos. -
Modificar el archivo de configuración de MySQL (
mysqld.cnf
) para que admita conexiones desde cualquier lugar.
install_mysql.sh
#!/bin/bash
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password password my_password'
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password my_password'
sudo apt update
sudo apt -y install mysql-server
wget https://gist.githubusercontent.com/ualmtorres/f8d0e5ea79a0e570f495087724288c6d/raw/0a894b23466bb6eea520a05559372e148e6e5803/sginit.sql -O /home/ubuntu/sginit.sql
mysql -h "localhost" -u "root" "-pmy_password" < "/home/ubuntu/sginit.sql"
sudo sed -i 's/127.0.0.1/0.0.0.0/g' /etc/mysql/mysql.conf.d/mysqld.cnf
sudo service mysql restart
Para crear la instancia con Terraform basta con crear el recurso pasando a la propiedad user_data
el nombre y la ruta del script de inicialización. En este caso, se supone que el script de inicialización está en el mismo directorio que el script Terraform.
#Crear nodo mysql
resource "openstack_compute_instance_v2" "mysql" {
name = "mysql"
image_name = "jammy"
availability_zone = "nova"
flavor_name = "medium"
key_pair = var.openstack_keypair
security_groups = ["default"]
network {
name = var.openstack_network_name
}
user_data = file("install_mysql.sh") (1)
}
-
Pasar el script de inicialización de la instancia
Tras ejecutar terraform apply
, Terraform creará la instancia con el script de inicialización. El script se ejecutará durante la creación de la instancia. La instancia creada tendrá un servidor MySQL instalado y configurado con la base de datos SG
inicializada.
3.6. Archivos de plantilla
Una característica muy interesante de Terraform es la posibilidad de definir scripts con contenido dinámico. Se trata de archivos que interpolan el valor de variables generadas durante el proceso de despliegue.
El procedimiento es el siguiente:
-
Generar variables de salida
-
Crear archivos de plantilla con extensión
.tpl
que obtengan los valores de dichas variables con la sintaxis siguiente${nombre-variable}
. -
Interpolar mediante la función
templatefile
donde sea necesario los archivos plantilla con la sintaxis siguientedata.template_file.objeto-template-file.rendered
.
Para ilustrar su uso:
-
Crearemos una plantilla que obtenga (también llamado interpole) la dirección IP de un servidor MySQL creado en el despliegue (almacenada en una variable
output
). Dicha variable se usará para definir una variable de entorno en la instancia definida y para cambiar las variables de entorno de Apache. -
Crearemos una instancia inicializada con el archivo de la plantilla. La instancia será un servidor web inicializado con una aplicación PHP sencilla. La aplicación usará la variable de entorno inicializada por el script. La variable de entorno contiene la dirección IP del servidor MySQL al que accede la aplicación para mostrar sus datos.
install_appserver.tpl
#!/bin/bash
echo "export MYSQL_SERVER=${mysql_ip}" >> /home/ubuntu/.profile (1)
sudo apt-get update
sudo apt-get install -y apache2 php php-mysql libapache2-mod-php php-mcrypt
sudo chgrp -R www-data /var/www
sudo chmod -R 775 /var/www
sudo chmod -R g+s /var/www
sudo useradd -G www-data ubuntu
sudo chown -R ubuntu /var/www/
sudo rm /var/www/html/index.html
wget https://gist.githubusercontent.com/ualmtorres/1c833f9b471fa7351e2725731596f45e/raw/a66b26d90b5f75c3a37cfe12a2370b57d2768132/sginit.php -O /var/www/html/index.php
echo "export MYSQL_SERVER=${mysql_ip}" >> /etc/apache2/envvars (2)
sudo service apache2 restart
-
Inicialización de una variable de entorno con el valor de la variable
mysql_ip
. -
Inicialización de una variable de entorno Apache con el valor de la variable
mysql_ip
.
#Crear nodo appserver
resource "openstack_compute_instance_v2" "appserver" {
name = "appserver"
image_name = "Ubuntu 16.04 LTS"
availability_zone = "nova"
flavor_name = "medium"
key_pair = "mtorres_ual"
security_groups = ["default"]
network {
name = "desarrollo-net"
}
user_data = templatefile("${path.module}/install_appserver.tpl", { mysql_ip = openstack_compute_instance_v2.mysql.network.0.fixed_ip_v4 }) (1)
depends_on = [openstack_compute_instance_v2.mysql]
}
-
Interpolación del archivo plantilla
El proceso de interpolación con la función templatefile
se realiza en el momento de la creación de la instancia. Terraform sustituye las variables de la plantilla por los valores de las variables de salida generadas durante el despliegue. La función toma dos argumentos: la ruta del archivo plantilla y un mapa con las variables a interpolar.
3.7. Ejemplo completo
En este apartado crearemos un escenario más complejo que combine creación de recursos de red e instancias aprovisionadas durante su creación.
Se trata de crear lo siguiente:
-
Red denominada
desarrollo-net
. Contendrá una subred denominadadesarrollo-subnet
con direcciones10.2.0.0./24
y estos servidores DNS:150.214.156.2 8.8.8.8
. -
Router denominado
desarrollo-router
que conecte la red exteriorext-net
con la reddesarrollo-net
creada anteriormente. -
Un servidor MySQL inicializado con el script
install_mysql.sh
-
Un servidor Web con PHP inicializado con el script
install_appserver.tpl
La figura siguiente ilustra el diagrama de la infraestructura.
Tras finalizar el despliegue tendremos la configuración de red realizada, un servidor MySQL con una base de datos inicializada y servidor web con aplicación PHP de catálogo de productos desplegada. Terraform nos informará con las variables de salida.
Apply complete! Resources: 10 added, 0 changed, 0 destroyed.
Outputs:
Appserver_Floating_IP = 192.168.68.112
MySQL_Floating_IP = 192.168.68.135
Si accedemos a la dirección IP del servidor web veremos la aplicación de catálogo mostrando los productos almacenados en la base de datos.
4. Despliegue en Google Cloud
El provider Google Cloud permite crear configuraciones Terraform para desplegar configuraciones en el gran conjunto de servicios de Google Cloud. Entre los recursos que podemos gestionar están:
-
Infraestructura (Instancias, Imágenes, Redes, …)
-
App Engine
-
Bases de datos (Cloud SQL, Big Query, Firebase, …)
-
Kubernetes
-
Cloud Storage
-
…
4.1. Crear una clave para la Cuenta de servicio
-
Seleccionar el proyecto Google Cloud.
-
En el menú de navegación seleccionar
IAM y administración | Cuentas de servicio
. -
Seleccionar
Crear cuenta de servicio
. -
Darle un nombre (p.e.
terraform
) -
Seleccionar
Crear y continuar
. -
En el paso
Otorga a esta cuenta de servicio acceso al proyecto
del asistente, seleccionar el rolProyecto → Editor
. -
Pulsar el botón
Listo
. No es necesario configurar nada más en este asistente. -
Editar la Cuenta de servicio. En la sección
Claves
seleccionarAgregar clave | Crear clave nueva
. -
Dejar
JSON
en el tipo de clave.. -
Seleccionar
Crear
. A continuación se descargará a nuestro equipo la clave privada. -
En el menú de navegación seleccionar
IAM y adminsitración | IAM
, en la pestaña dePermisos
localizar la cuenta de servicio creada paraterraform
y pulsar sobreEditar cuenta principal
. -
Pulsar sobre
Agregar otro rol
. SeleccionarServicios de red - Administrador de extensiones del servicio
. -
Guardar los cambios.
4.2. Configuración del provider
Para usarlo hay que configurar sus parámetros de acceso. Lo haremos en un archivo providers.tf
providers.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "6.5.0"
}
}
}
provider "google" {
credentials = file("../gcp-identity.json") (1)
project = var.gcp-project
region = "us-central1"
zone = "us-central1-c"
}
-
Ruta al archivo de credenciales de la cuenta de servicio descargadas en el paso anterior.
Se usan las variables definidas en el archivo variables.tf
variable "gcp-username" {
description = "GCP user name"
default = "mtorres"
}
variable "gcp-project" {
description = "GCP project"
default = "cc2025-mtorres"
}
variable "gcp-network" {
description = "GCP network"
default = "terraform-network"
}
4.3. Inicializar el provider
Para inicializar ejecutar terraform init
.
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/google versions matching "6.5.0"...
- Installing hashicorp/google v6.5.0...
- Installed hashicorp/google v6.5.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
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.
Esto creará una carpeta .terraform
con en plugin de Google Cloud instalado y disponible para ser usado en el proyecto. También crea un archivo .terraform.lock.hcl
que registra las selecciones de proveedores realizadas. Este archivo se debe incluir en el repositorio de control de versiones para garantizar que Terraform haga las mismas selecciones por defecto cuando se ejecute terraform init
en el futuro.
4.4. Configuración de la red
Para crear una red en Google Cloud usaremos el recurso google_compute_network. En el siguiente ejemplo se crea una red denominada terraform-network
.
resource "google_compute_network" "vpc_network" {
name = var.gcp-network
}
También crearemos las reglas de firewall para permitir el tráfico de entrada y salida en la red. Para ello usaremos el recurso google_compute_firewall. En este ejemplo veremos cómo añadir una regla ICMP que permita el tráfico PING desde cualquier origen, una regla SSH que permita el tráfico SSH desde cualquier origen y una regla que permite todo el tráfico interno. El tráfico interno lo entenderemos dentro de la región us-central1
con máscara de red 10.128.0.0/20
.
resource "google_compute_network" "vpc_network" {
name = var.gcp-network
}
resource "google_compute_firewall" "firewall-icmp" {
name = "terraform-allow-icmp"
network = google_compute_network.vpc_network.name
allow {
protocol = "icmp"
}
source_ranges = ["0.0.0.0/0"]
}
resource "google_compute_firewall" "firewall-ssh" {
name = "terraform-allow-ssh"
network = google_compute_network.vpc_network.name
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["0.0.0.0/0"]
}
resource "google_compute_firewall" "firewall-internal" {
name = "terraform-allow-internal"
network = google_compute_network.vpc_network.name
allow {
protocol = "tcp"
ports = ["0-65535"]
}
allow {
protocol = "udp"
ports = ["0-65535"]
}
allow {
protocol = "icmp"
}
source_ranges = ["10.128.0.0/20"]
}
4.5. Despliegue de una instancia
La creación de una instancia se realiza con google_compute_instance.
A continuación, crearemos una instancia denominada tf-vm
. El nombre que se use en resource
, no el nombre asignado en name
, es el que referencia al objeto resource creado. Esto permite tratar el recurso creado (p.e. para asignarle una dirección IP externa, para conectarle un volumen, …).
En el ejemplo siguiente se ilustra la creación de una máquina virtual con una dirección IP efímera.
Note
|
De forma predeterminada, si no se indica ninguna dirección IP fija, Google Cloud creará una efímera para la máquina virtual. |
resource "google_compute_instance" "tf-vm" { (1)
name = "tf-vm"
zone = "us-central1-c"
machine_type = "n1-standard-1"
boot_disk {
initialize_params {
image = "debian-cloud/debian-11"
}
}
# Add SSH access to the Compute Engine instance
metadata = {
ssh-keys = "${var.gcp-username}:${file("~/.ssh/id_rsa.pub")}"
}
# Startup script
# metadata_startup_script = "${file("update-docker.sh")}"
network_interface { (2)
network = var.gcp-network
subnetwork = var.gcp-network
access_config {} (3)
}
}
output "tf-vm-internal-ip" { (4)
value = google_compute_instance.tf-vm.network_interface.0.network_ip
depends_on = [google_compute_instance.tf-vm]
}
output "tf-vm-ephemeral-ip" { (5)
value = google_compute_instance.tf-vm.network_interface.0.access_config.0.nat_ip
depends_on = [google_compute_instance.tf-vm]
}
-
Creación de un recurso instancia (máquina virtual) en Google Cloud. El objeto recurso creado es asignado a la variable
tf-vm
. -
Red a la que se conectará la instancia creada.
-
Dejar
access_config
sin configurar hará que se genere una dirección IP efímera. -
Dirección IP interna de la instancia
-
Dirección IP efímera de la instancia
4.6. Modificar el despliegue
A modo de ilustración este ejemplo muestra cómo aplicar cambios a una configuración desplegada previamente. En este caso se trata de:
-
Cambiar el tipo de máquina de la instancia desplegada a
n1-standard-2
. -
Crear un volumen de 1GB (
google_compute_disk
). -
Conectar el volumen a la máquina virtual (
google_compute_attached_disk
).
resource "google_compute_instance" "tf-vm" {
name = "tf-vm"
zone = "us-central1-c"
machine_type = "n1-standard-2" (1)
boot_disk {
initialize_params {
image = "debian-cloud/debian-11"
}
}
...
resource "google_compute_disk" "tf-disk" { (2)
name = "tf-disk"
type = "pd-ssd" (3)
size = 1 (4)
}
resource "google_compute_attached_disk" "attached-tf-disk" {(5)
disk = google_compute_disk.tf-disk.id (6)
instance = google_compute_instance.tf-vm.id (7)
}
-
Modificación del tamaño de la imagen
-
Creación de un recurso volumen
-
Tipo SSD
-
Especificación del tamaño del volumen
-
Conexión del volumen a la instancia
-
Acceso al id del volumen creado
-
Acceso al id de la instancia
Al ejecutar con terraform apply
, Terraform nos informará de los cambios detectados y de la nueva configuración. La nueva configuración se aplicará si confirmamos la operación. Una vez aplicados desplegados los cambios, los recursos creados se mostrarán en el panel de control de Google Cloud, mostrando la instancia modificada y el volumen creado y conectado a la instancia. Si nos conectamos a la instancia con ssh
podremos ver el volumen creado con lsblk
.
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 10G 0 disk (1)
├─sda1 8:1 0 9.9G 0 part /
├─sda14 8:14 0 3M 0 part
└─sda15 8:15 0 124M 0 part /boot/efi
sdb 8:16 0 1G 0 disk (2)
-
Disco de la instancia
-
Volúmen creado y conectado a la instancia
4.7. Creación de una instancia con una dirección IP estática
De forma predeterminada, Google Cloud crea una dirección IP efímera para las instancias. Si queremos una dirección IP estática, debemos crearla y asignarla a la instancia. Para ello, usaremos el recurso google_compute_address
. En el siguiente ejemplo se crea una dirección IP estática denominada tf-vm-ip
. A la hora de crear la instancia, una forma de asignar la dirección IP estática creada es a través de la configuración access_config
de la tarjeta de red de la instancia.
resource "google_compute_address" "tf-vm-ip" { (1)
name = "ipv4-address-tf-vm"
}
resource "google_compute_instance" "tf-vm" { (2)
name = "tf-vm"
machine_type = "n1-standard-1"
boot_disk {
initialize_params {
image = "debian-cloud/debian-11"
}
}
...
network_interface {
network = var.gcp-network
subnetwork = var.gcp-network
access_config {
nat_ip = google_compute_address.tf-vm-ip.address (3)
}
}
}
output "tf-vm-ip" { (4)
value = google_compute_address.tf-vm-ip.address
depends_on = [google_compute_instance.tf-vm]
}
-
Creación de un recurso dirección IP estática
-
Creación de un recurso instancia (máquina virtual) en Google Cloud
-
Asignación de la dirección IP estática a la instancia
-
Dirección IP estática de la instancia
4.8. Ejecutar un script de inicialización
Una característica muy interesante en el despliegue de una instancia es la posibilidad de ejecutar un script de inicialización durante su creación. Esto permite la creación de instancias con paquetes instalados y configurados.
Terraform permite esta operación en GCP pasando un script en el parámetro metadata_startup_script
al crear la instancia.
Note
|
Si se modifica el valor de |
En este apartado veremos cómo crear una instancia Ubuntu aprovisionada con Docker. Además, la instancia se inicializará con un archivo docker-compose.yml
que despliega dos contenedores: un contenedor MySQL con una base de datos inicializada y otro contenedor con una aplicación PHP que muestra un catálogo de productos almacenados en el contenedor MySQL.
Note
|
El script de instalación es válido para Ubuntu. Si se usan otras otras distribuciones Linux será necesario adaptar el script de instalación a las peculiaridades de la distribución utilizada. |
La aplicación deberá ser accesible en Internet. Por tanto, hay que definir una regla en el cortafuegos que permita la comunicación HTTP. La regla tendrá una etiqueta asociada. Las instancias que deseen aplicar la regla incluirán la etiqueta correspondiente en su definición.
network-firewall.tf
# allow http traffic
resource "google_compute_firewall" "allow-http" {
name = "tf-fw-allow-http" (1)
network = var.gcp-network (2)
allow {
protocol = "tcp"
ports = ["80"] (3)
}
target_tags = ["http"] (4)
source_ranges = ["0.0.0.0/0"] (5)
}
-
Nombre de la regla del firewall
-
Red a la que se aplica la regla definida
-
Puerto abierto
-
Etiqueta para poder usar la regla
-
Rango de direcciones IP permitidas. En este caso, cualquier dirección IP
main.tf
resource "google_compute_instance" "tf-vm" {
name = "tf-vm"
zone = "us-central1-c"
machine_type = "n1-standard-1"
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2204-lts" (1)
}
}
# Add SSH access to the Compute Engine instance
metadata = {
ssh-keys = "${var.gcp-username}:${file("~/.ssh/id_rsa.pub")}"
}
# Add http tag to the instance to identify it in the firewall rule
tags = ["http"] (2)
# Startup script
metadata_startup_script = file("setup-docker.sh") (3)
network_interface {
network = var.gcp-network
subnetwork = var.gcp-network
access_config {}
}
}
output "tf-vm-internal-ip" {
value = google_compute_instance.tf-vm.network_interface.0.network_ip
depends_on = [google_compute_instance.tf-vm]
}
output "tf-vm-ephemeral-ip" {
value = google_compute_instance.tf-vm.network_interface.0.access_config.0.nat_ip
depends_on = [google_compute_instance.tf-vm]
}
-
Imagen de la instancia
-
Etiqueta para identificar la instancia en la regla del cortafuegos
-
Script de inicialización de la instancia
setup-docker.sh
de inicialización de la instancia#!/bin/bash
echo "Instalando Docker"
# Add Docker's official GPG key:
apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable docker
git clone https://github.com/ualmtorres/docker_customer_catalog.git (1)
cd docker_customer_catalog
docker compose up -d (2)
exit 0
-
Clonado del repositorio con el archivo de despliegue, la aplicación y el script de inicialización de la base de datos
-
Despliegue del entorno (Base de datos + Aplicación)
Para crear la instancia con Terraform basta con crear el recurso pasando a la propiedad metadata_startup_script
el nombre y la ruta del script de inicialización. En este caso, se supone que el script de inicialización está en el mismo directorio que el script Terraform.
La figura siguiente ilustra el resultado tras unos minutos que se necesitan para la creación e inicialización de la instancia y despliegue de la base de datos y la aplicación de catálogo.
Note
|
Si estamos interesados en mostrar el log de arranque de la instancia para comprobar que el script de inicialización se ha ejecutado correctamente, podemos hacerlo desde la propia instancia ejecutando |
5. Despliegue en Microsoft Azure
El provider Microsoft Azure permite crear configuraciones Terraform para desplegar configuraciones en el gran conjunto de servicios de Azure. Entre los recursos que podemos gestionar están:
-
Infraestructura (Instancias, Imágenes, Redes, …)
-
App Service
-
Bases de datos (SQL, CosmosDB, …)
-
Kubernetes
-
Storage
-
…
5.1. Autenticación en Azure
Para autenticarse en Azure, Terraform necesita que se haya iniciado la sesión con el CLI de Azure y proporcionar las credenciales de la suscripción y del proyecto en Azure. Las credenciales se obtendrán a través del CLI de Azure.
Note
|
Azure CLI es una herramienta de línea de comandos que proporciona una experiencia unificada para administrar los servicios de Azure. Para instalarlo, seguir las instrucciones en Instalación de la CLI de Azure. |
Para obtener las credenciales necesarias, seguir los siguientes pasos:
-
Iniciar sesión en Azure con
az login
. Esto abrirá un navegador para autenticarse en Azure. Tras la autenticación, se mostrará un mensaje de confirmación en la terminal y devolverá los datos de la cuenta.Retrieving tenants and subscriptions for the selection... [Tenant and subscription selection] No Subscription name Subscription ID Tenant ----- ------------------- ------------------------------------ ---------------------- [1] * Azure for Students 00000000-0000-0000-0000-000000000000 University of XXXXXXX
-
Seleccionar la suscripción y el proyecto con la que se desea trabajar. El listado aparecerá numerado. Introdcir el número correspondiente a la suscripción y al proyecto.
-
Obtener los detalles de la suscripción con
az account show
.{ "environmentName": "AzureCloud", "homeTenantId": "00000000-0000-0000-0000-000000000000", "id": "00000000-0000-0000-0000-000000000000", (1) "isDefault": true, "managedByTenants": [], "name": "Azure for Students", "state": "Enabled", "tenantDefaultDomain": "students.uxxxxxx.es", "tenantDisplayName": "University of XXXXXXX", "tenantId": "00000000-0000-0000-0000-000000000000", (2) "user": { "name": "robertsmith@ual.es", "type": "user" } }
-
ID de la suscripción
-
ID del proyecto
-
Los datos que necesitamos para configurar el provider de Azure en Terraform son los que hemos destacado en el listado anterior:
-
subscription_id
: ID de la suscripción. -
tenant_id
: ID del proyecto.
5.2. Configuración del provider
Para usarlo hay que configurar sus parámetros de acceso. Lo haremos en un archivo providers.tf
providers.tf
# We strongly recommend using the required_providers block to set the
# Azure Provider source and version being used
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=4.1.0"
}
}
}
# Configure the Microsoft Azure Provider
provider "azurerm" {
features {}
subscription_id = var.azure-subscription
tenant_id = var.azure-tenant
}
Se usan las variables definidas en el archivo variables.tf
variable "azure-subscription" {
description = "Azure subscription id"
}
variable "azure-tenant" {
description = "Azure tenant id"
}
variable "azure-resource-group" {
description = "Azure resource group name"
}
variable "azure-location" {
description = "Azure location"
}
Los valores de las variables se pueden definir en un archivo terraform.tfvars
terraform.tfvars
azure-subscription = "00000000-0000-0000-0000-000000000000"
azure-tenant = "00000000-0000-0000-0000-000000000000"
azure-resource-group = "tf-resource-group"
azure-location = "France Central"
Important
|
El archivo |
5.3. Inicializar el provider
Para inicializar ejecutar terraform init
.
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "4.1.0"...
- Installing hashicorp/azurerm v4.1.0...
- Installed hashicorp/azurerm v4.1.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
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.
Esto creará una carpeta .terraform
con en plugin de Azure instalado y disponible para ser usado en el proyecto. También crea un archivo .terraform.lock.hcl
que registra las selecciones de proveedores realizadas. Este archivo se debe incluir en el repositorio de control de versiones para garantizar que Terraform haga las mismas selecciones por defecto cuando se ejecute terraform init
en el futuro.
5.4. Creación de un grupo de recursos
Un grupo de recursos es un contenedor que mantiene los recursos relacionados para una solución de Azure. Los recursos pueden incluir instancias, bases de datos, redes, etc. Los recursos de un grupo de recursos pueden ser administrados, eliminados o actualizados en conjunto. Para crear un grupo de recursos usaremos el recurso azurerm_resource_group
.
En el siguiente ejemplo se crea un grupo de recursos denominado tf-resource-group
en la región France Central
, que es la región que hemos definido en el archivo terraform.tfvars
.
resource "azurerm_resource_group" "tf-resource-group" {
name = var.azure-resource-group
location = var.azure-location
}
Desplegando con terraform apply
se creará el grupo de recursos en Azure.
5.5. Configuración de la red
La configuración de una red en Azure pasa por la creación de una red virtual y una subred. Es posible crear la subred directamente en la creación de la red virtual. Sin embargo, es recomendable crear la red y la subred por separado para tener un mayor control sobre la configuración de la red. Posteriormente, necesitaremos crear una interfaz de red para conectar la instancia a la red. Esta interfaz de red deberá estar conectada a la subred. Por tanto, necesitamos que la subred sea un recurso independiente para poder usarlo en la creación de la interfaz de red. Así que, resumiendo, crearemos la red y la subred por separado.
Para crear una red en Azure usaremos el recurso azurerm_virtual_network
. Para crear una subred usaremos el recurso azurerm_subnet
. En el siguiente ejemplo se crea una red denominada tf-net
y una subred denominada tf-subnet
, ambas en la región France Central
con el rangos de direcciones 10.0.0.0/24
.
resource "azurerm_virtual_network" "tf-net" {
name = var.azure-net-name
location = azurerm_resource_group.tf-resource-group.location
resource_group_name = azurerm_resource_group.tf-resource-group.name
address_space = var.azure-address-space
dns_servers = var.azure-dns-servers
}
resource "azurerm_subnet" "tf-subnet" {
name = var.azure-subnet-name
resource_group_name = azurerm_resource_group.tf-resource-group.name
virtual_network_name = azurerm_virtual_network.tf-net.name
address_prefixes = var.azure-subnet-prefixes
}
El archivo variables.tf
tendrá que ser modificado para incluir las variables necesarias para la creación de la red.
...
# Nuevo contenido
variable "azure-net-name" {
description = "Azure virtual net name"
}
variable "azure-address-space" {
description = "Azure address space"
type = list(string)
}
variable "azure-dns-servers" {
description = "Azure DNS servers"
type = list(string)
}
variable "azure-subnet-name" {
description = "Azure subnet name"
}
variable "azure-subnet-prefixes" {
description = "Azure subnet prefixes"
type = list(string)
}
El archivo terraform.tfvars
tendrá que ser modificado para incluir los valores de las variables necesarias para la creación de la red.
...
# Nuevo contenido
azure-address-space = ["10.0.0.0/24"]
azure-dns-servers = ["8.8.8.8"]
azure-subnet-name = "tf-subnet"
azure-subnet-prefixes = ["10.0.0.0/24"]
Desplegando con terraform apply
se crearán en Azure la red y la subred.
5.6. Creación de una instancia
En esta sección vamos a crear una instancia en Azure configurada en el arranque con un servidor web Apache. Además, crearemos un disco de datos que se conectará a la instancia. La instancia se creará en la red creada anteriormente. En Azure, la conexión de una instancia a la red se realizar a través de un recurso denominado interfaz de red. Por tanto, necesitaremos crear una interfaz de red para conectar la instancia a la red. En cuanto al acceso a la instancia, se permitirá el acceso a través del puerto 22 para SSH y del puerto 80 para HTTP. Esto exige crear un grupo de seguridad de red que permita el tráfico a través de estos puertos. Además, habrá que crear una dirección IP pública y asignarla a la instancia. A continuación se muestran los pasos a seguir para crear la instancia;
-
Crear un grupo de seguridad de red que permita el tráfico a través de los puertos 22 y 80.
-
Crear una dirección IP pública.
-
Crear una interfaz de red conectada a la red y configurada con la dirección IP pública.
-
Aplicar el grupo de seguridad de red a la interfaz de red.
-
Crear la instancia conectada a la interfaz de red. La instancia se inicializará con un script de arranque que instalará y configurará el servidor web Apache.
-
Crear un disco de datos y conectarlo a la instancia.
5.6.1. Creación de un grupo de seguridad de red
Un grupo de seguridad de red es un conjunto de reglas que permiten o deniegan el tráfico de red a las instancias conectadas a la red. Para crear un grupo de seguridad de red usaremos el recurso azurerm_network_security_group
. En el siguiente ejemplo se crea un grupo de seguridad de red denominado tf-nsg
que permite el tráfico a través de los puertos 22 y 80.
...
# Nuevo contenido
resource "azurerm_network_security_group" "tf-nsg" {
name = "tf-nsg"
location = azurerm_resource_group.tf-resource-group.location
resource_group_name = azurerm_resource_group.tf-resource-group.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 = "HTTP"
priority = 1002
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
Desplegando con terraform apply
se creará en Azure el grupo de seguridad de red.
5.6.2. Creación de una dirección IP pública
En Azure, una dirección IP pública puede ser estática o dinámica. Esta funcionalidad se configura a través de la propiedad sku
. El sku básico es gratuito. La diferencia entre ambos es que el sku básico no permite la asignación de una dirección IP estática.
Para crear una dirección IP pública usaremos el recurso azurerm_public_ip
. En el siguiente ejemplo se crea una dirección IP pública denominada tf-ip
.
...
# Nuevo contenido
resource "azurerm_public_ip" "tf-web-server-ip" {
name = "tf-web-server-ip"
location = azurerm_resource_group.tf-resource-group.location
resource_group_name = azurerm_resource_group.tf-resource-group.name
allocation_method = "Dynamic"
sku = "Basic"
}
Desplegando con terraform apply
se creará en Azure la dirección IP pública.
5.6.3. Creación de una interfaz de red
Para crear una interfaz de red usaremos el recurso azurerm_network_interface
. En el siguiente ejemplo se crea una interfaz de red denominada tf-nic
conectada a la red y configurada con la dirección IP pública. También se aplica el grupo de seguridad de red creado anteriormente.
...
# Nuevo contenido
resource "azurerm_network_interface" "tf-nic" {
name = "tf-nic"
location = azurerm_resource_group.tf-resource-group.location
resource_group_name = azurerm_resource_group.tf-resource-group.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.tf-subnet.id
private_ip_address_allocation = "Dynamic" (1)
public_ip_address_id = azurerm_public_ip.tf-web-server-ip.id (2)
}
}
resource "azurerm_network_interface_security_group_association" "tf-nic-nsg" { (3)
network_interface_id = azurerm_network_interface.tf-nic.id
network_security_group_id = azurerm_network_security_group.tf-nsg.id
}
-
Asignación de una dirección IP dinámica
-
Asignación de la dirección IP pública a la interfaz de red
-
Asociación del grupo de seguridad de red a la interfaz de red
Desplegando con terraform apply
se creará en Azure la interfaz de red.
5.6.4. Creación de la instancia
Para crear una instancia usaremos el recurso azurerm_linux_virtual_machine
. En el siguiente ejemplo se crea una instancia denominada tf-vm
conectada a la interfaz de red creada anteriormente. La instancia se inicializará con un script de arranque que instalará y configurará el servidor web Apache. Además, será necesario configurar el nombre de usuario, la clave pública de acceso por SSH y los datos de la imagen de la instancia, que en Azure es un poco diferente a la de otros proveedores.
...
# Nuevo contenido
resource "azurerm_linux_virtual_machine" "tf-web-server" {
name = "tf-web-server"
resource_group_name = azurerm_resource_group.tf-resource-group.name
location = azurerm_resource_group.tf-resource-group.location
size = var.azure-vm-size
admin_username = var.azure-admin-username (1)
network_interface_ids = [ (2)
azurerm_network_interface.tf-nic.id,
]
admin_ssh_key {
username = var.azure-admin-username (3)
public_key = file("~/.ssh/id_rsa.pub") (4)
}
os_disk {
caching = "ReadWrite"
storage_account_type = var.azure-storage-account-type
}
source_image_reference {
publisher = var.azure-os-publisher (5)
offer = var.azure-os-offer (6)
sku = var.azure-os-sku (7)
version = var.azure-os-version (8)
}
user_data = base64encode(file("install-web-server.sh")) (9)
tags = {
web_server = "tf-web-server"
}
}
-
Nombre de usuario administrador de la instancia
-
ID de la interfaz de red a la que se conectará la instancia
-
Nombre de usuario para la clave pública de acceso por SSH
-
Clave pública de acceso por SSH
-
Publicador de la imagen de la instancia
-
Nombre de la imagen de la instancia
-
SKU de la imagen de la instancia. En Azure, el SKU de la imagen es el sistema operativo
-
Versión de la imagen del sistema operativo
-
Script de arranque de la instancia. El script hay que codificarlo en base64
La imagen de la instancia en Azure se define por el publicador, la oferta, el SKU y la versión. En el caso de la imagen de Ubuntu que usamos en el ejemplo, los valores son: publisher = "Canonical"
, offer = "ubuntu-24-04-lts"
, sku = "server"
, version = "latest"
. No obstante, este convenio puede variar en función de la imagen y de la versión que se desee usar. Este enlace muestra en forma de URN los valores de configuración de imágenes Ubuntu para Azure.
El archivo variables.tf
tendrá que ser modificado para incluir las variables necesarias para la creación de la instancia.
...
# Nuevo contenido
variable "azure-vm-size" {
description = "Azure VM size"
}
variable "azure-admin-username" {
description = "Admin username"
}
variable "azure-storage-account-type" {
description = "Storage account type"
default = "Standard_LRS"
}
variable "azure-os-publisher" {
description = "Publisher of the image"
default = "Canonical"
}
variable "azure-os-offer" {
description = "Offer of the image"
default = "0001-com-ubuntu-server-noble"
}
variable "azure-os-sku" {
description = "SKU of the image"
default = "24_04-lts"
}
variable "azure-os-version" {
description = "Version of the image"
default = "latest"
}
El archivo terraform.tfvars
tendrá que ser modificado para incluir los valores de las variables necesarias para la creación de la instancia.
...
# Nuevo contenido
azure-vm-size = "Standard_B1s"
azure-admin-username = "mtorres"
azure-storage-account-type = "Standard_LRS"
azure-os-publisher = "Canonical"
azure-os-offer = "ubuntu-24_04-lts"
azure-os-sku = "server"
azure-os-version = "latest"
El script de arranque de la instancia install-web-server.sh
instalará y configurará el servidor web Apache. A continuación se muestra el contenido del script.
install-web-server.sh
#!/bin/bash
apt-get update
apt-get install -y apache2
systemctl enable apache2
systemctl start apache2
echo "<h1>Welcome to Terraform Azure</h1>" > /var/www/html/index.html
Note
|
Se puede añadir un |
output "tf-web-server-ip" {
value = azurerm_public_ip.tf-web-server-ip.ip_address
depends_on = [azurerm_linux_virtual_machine.tf-web-server]
}
Desplegando con terraform apply
se creará en Azure la instancia y tras unos minutos se podrá acceder a través de un navegador a la dirección IP pública asignada a la instancia mostrando el mensaje de bienvenida que hemos configurado en el script de arranque. La imagen siguiente muestra el resultado.
5.6.5. Creación de un disco de datos
Azure ofrece gran flexibilidad en la creación de discos de datos. Se pueden crear discos de datos independientes o discos de datos que se conectan a la instancia. En este caso, crearemos un disco de datos gestionados que se conectará a la instancia. Para ello, usaremos el recurso azurerm_managed_disk
. En el siguiente ejemplo se crea un disco de datos denominado tf-web-server-disk
de 1GB de tamaño y posteriormente lo conectaremos a la instancia.
...
# Nuevo contenido
resource "azurerm_managed_disk" "tf-web-server-disk" {
name = "tf-web-server-disk"
location = azurerm_resource_group.tf-resource-group.location
resource_group_name = azurerm_resource_group.tf-resource-group.name
storage_account_type = var.azure-storage-account-type
create_option = "Empty"
disk_size_gb = 1 (1)
}
resource "azurerm_virtual_machine_data_disk_attachment" "tf-web-server-disk" {
managed_disk_id = azurerm_managed_disk.tf-web-server-disk.id
virtual_machine_id = azurerm_linux_virtual_machine.tf-web-server.id
lun = 10 (2)
caching = "ReadWrite"
}
-
Tamaño del disco en GB
-
Número de unidad lógica (LUN) del disco. Este número debe ser único para cada disco conectado a la instancia. Por defecto, el valor es 0.
Desplegando con terraform apply
se creará en Azure el disco de datos y se conectará a la instancia. Si nos conectamos a la instancia con ssh
podremos ver el disco creado con lsblk
.
6. Bucles en Terraform
Terraform tiene una forma particular de implementar bucles. Aquí veremos cómo hacerlo con contadores y con un bucle for
. En ambos casos contamos con las variables declaradas en variables.tf
e inicializadas en terraform.tfvars
, como se muestra a continuación. Para ilustrar el uso de los bucles usaremos un ejemplo de creación de dos instancias. Los valores de creación de las instancias los inicializaremos en un array de objetos, donde cada objeto incluye el nombre de la instancia, el tipo de máquina y la imagen. El ejemplo lo implementaremos para Google Cloud Platform.
variables.tf
variable "gcp-username" { description = "GCP user name" } variable "gcp-project" { description = "GCP project" } variable "gcp-region" { description = "GCP region" } variable "gcp-zone" { description = "GCP zone" } variable "gcp-network" { description = "GCP network" } variable "gcp-subnetwork" { description = "GCP subnetwork" } variable "instances" { description = "Number of instances to create" type = list(object({ name = string, machine_type = string, image = string })) }
terraform.tfvars
gcp-username = "your-username" gcp-project = "your-project" gcp-region = "us-central1" gcp-zone = "us-central1-c" gcp-network = "terraform-network" gcp-subnetwork = "terraform-subnetwork" instances = [{ (1) name = "database" machine_type = "n1-standard-1" image = "debian-cloud/debian-11" }, { (2) name = "web-server" machine_type = "n1-standard-1" image = "ubuntu-os-cloud/ubuntu-2204-lts" } ]
-
Primera instancia. Nombre:
database
, Tipo de máquina:n1-standard-1
, Imagen:debian-cloud/debian-11
-
Segunda instancia. Nombre:
web-server
, Tipo de máquina:n1-standard-1
, Imagen:ubuntu-os-cloud/ubuntu-2204-lts
providers.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "6.5.0"
}
}
}
provider "google" {
credentials = file("../gcp-identity.json")
project = var.gcp-project
region = var.gcp-region
zone = var.gcp-zone
}
6.1. Bucles con count
Para este ejemplo usaremos un contador para crear varias instancias en GCP. La propiedad count
se inicializa con la longitud del array de instancias. El índice del array se obtiene con count.index
.
main.tf
resource "google_compute_instance" "instance" {
count = length(var.instances) (1)
name = var.instances[count.index].name (2)
machine_type = var.instances[count.index].flavor (3)
boot_disk {
initialize_params {
image = var.instances[count.index].image (4)
}
}
network_interface {
network = google_compute_network.tf-net.self_link
}
}
-
La propiedad
count
se inicializa con la longitud del arrayinstances
-
El nombre de la instancia se obtiene del array
instances
con el índicecount.index y la propiedad `name
-
El sabor de la instancia se obtiene del array
instances
con el índicecount.index y la propiedad `flavor
-
La imagen de la instancia se obtiene del array
instances
con el índicecount.index y la propiedad `image
En este caso, el recurso openstack_compute_instance_v2
se creará tantas veces como elementos tenga el array instances
. El índice del array se obtiene con count.index
.
Note
|
El ejemplo crea dos instancias, una para la base de datos y otra para el servidor web. Es sólo un ejemplo para ilustrar el uso de bucles en Terraform. Como se puede observar, no se realiza ninguna configuración adicional en las instancias. Para configurar las instancias, se puede configurar la propiedad |
Para obtener la dirección IP de las instancias creadas, usaremos output
. En lugar de añadir una salida por cada instancia, usaremos un bucle for
para recorrer las instancias y obtener sus direcciones IP. Sin embargo, output
no soporta count
. En su defecto, usaremos for
para obtener las direcciones IP de las instancias y asignarlas a la propiedad value
de output
. Y en lugar de añadir el código de output
en main.tf
, lo añadiremos en un archivo independiente outputs.tf
.
outputs.tf
output "tf-vm-internal-ip" {
value = [
for vm in google_compute_instance.tf-vm : vm.network_interface.0.network_ip (1)
]
depends_on = [google_compute_instance.tf-vm]
}
output "tf-vm-ephemeral-ip" {
value = [
for vm in google_compute_instance.tf-vm : vm.network_interface.0.access_config.0.nat_ip (2)
]
depends_on = [google_compute_instance.tf-vm]
}
-
Dirección IP interna de las instancias. El bucle
for
recorre las instancias creadas y disponibles engoogle_compute_instance.tf-vm
y obtiene la dirección IP interna de cada instancia. -
Dirección IP externa de las instancias. El bucle
for
recorre las instancias creadas y disponibles engoogle_compute_instance.tf-vm
y obtiene la dirección IP externa de cada instancia.
Una vez aplicados los cambios con terraform apply
, se crearán las instancias en GCP y se podrán obtener las direcciones IP de las instancias con output
.
$ terraform apply
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
tf-vm-ephemeral-ip = [
"34.69.211.150",
"35.193.129.189",
]
tf-vm-internal-ip = [
"10.128.0.11",
"10.128.0.10",
]
Cuando ya no se necesiten las instancias, se pueden destruir con terraform destroy
.
6.2. Bucles con for_each
En Terraform, el bucle for
se usa para crear estructuras de datos, pero no se aplica para crear recursos. Por tanto, si queremos crear varios recursos en un bucle sin usar count
necesitaremos otra construcción. Aquí es donde entra el uso de for_each
. Veamos cómo usar for_each
para crear las dos instancias del ejemplo anterior en GCP.
Para la preparación, necesitaremos modificar los archivos variables.tf
y terraform.tfvars
ya que for_each
usa mapas en lugar de arrays. A continuación se muestran las modificaciones que hay que realizar.
variables.tf
...
variable "instances" {
description = "Number of instances to create"
type = map(object({
name = string,
machine_type = string,
image = string
}))
}
terraform.tfvars
...
instances = {
database = {
name = "database"
machine_type = "n1-standard-1"
image = "debian-cloud/debian-11"
},
web-server = {
name = "web-server"
machine_type = "n1-standard-1"
image = "ubuntu-os-cloud/ubuntu-2204-lts"
}
}
El archivo main.tf
se modificará para usar for_each
en lugar de count
. La propiedad for_each
se inicializa con el mapa de instancias. Una vez inicializado, each.key
se podrá usar para obtener el nombre de la instancia (p.e. database
, web-server
) y each.value
para obtener las propiedades de la instancia.
main.tf
resource "google_compute_instance" "instance" {
for_each = var.instances (1)
name = each.value.name (2)
zone = var.gcp-zone
machine_type = each.value.machine_type (3)
boot_disk {
initialize_params {
image = each.value.image (4)
}
}
# Startup script
# metadata_startup_script = "${file("script.sh")}"
network_interface {
network = var.gcp-network
subnetwork = var.gcp-network
access_config {}
}
}
-
La propiedad
for_each
se inicializa con el mapa de instancias -
El nombre de la instancia se obtiene con
each.value.name
-
El tipo de máquina se obtiene con
each.value.machine_type
-
La imagen de la instancia se obtiene con
each.value.image
Una vez aplicados los cambios con terraform apply
, se crearán las instancias en GCP y se podrán obtener las direcciones IP de las instancias con output
.
$ terraform apply
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
tf-vm-ephemeral-ip = [
"34.132.169.85",
"34.69.211.150",
]
tf-vm-internal-ip = [
"10.128.0.15",
"10.128.0.14",
]
Cuando ya no se necesiten las instancias, se pueden destruir con terraform destroy
.
7. Módulos en Terraform
En Terraform, los módulos son una manera de organizar, reutilizar y estructurar el código, lo cual permite crear y gestionar infraestructuras de una manera más eficiente y escalable. Un módulo en Terraform es un conjunto de archivos de configuración que se agrupan para realizar una función específica. Los módulos ayudan a dividir la infraestructura en componentes lógicos, que luego pueden ser reutilizados en otros proyectos o aplicaciones. Así, usaremos los módulos en estos casos:
-
Reutilización del Código: Los módulos permiten empaquetar configuraciones de recursos complejas en un solo bloque, que luego puede utilizarse en varios proyectos.
-
Mantenimiento Sencillo: Los módulos organizan el código, facilitando el mantenimiento y las actualizaciones en infraestructuras grandes y complejas.
-
Consistencia y Estandarización: Al usar un mismo módulo en diferentes entornos (desarrollo, pruebas y producción), se asegura que las configuraciones sean consistentes en todos los entornos.
7.1. Estructura básica de un módulo
Un módulo básico suele consistir en tres archivos principales:
-
main.tf
: Define los recursos principales del módulo. -
variables.tf
: Declara las variables necesarias para parametrizar el módulo. -
outputs.tf
: Especifica los valores que el módulo devolverá al ser invocado.
Además, un módulo puede tener otros archivos o carpetas, como configuraciones adicionales, archivos de documentación (README.md
), o scripts personalizados, según sea necesario.
7.2. Uso de módulos en un proyecto
Una vez definido el módulo, se puede llamar en el código de Terraform utilizando el bloque module
. A continuación, se muestra un ejemplo:
...
module "web_server" {
source = "./path/to/module" # Puede ser una ruta local o un repositorio remoto.
name = "web-server" # Variables pasadas al módulo
region = "us-west-1"
...
}
...
Al ejecutar este código, Terraform invoca el módulo, utiliza sus configuraciones y aplica las variables definidas al llamar al módulo.
7.3. Creación de un módulo para la creación de instancias en GCP
En este ejemplo, crearemos un módulo denominado instance_module
para la creación de instancias en Google Cloud Platform. El módulo estará en una carpeta denominada modules
y creará una instancia en GCP con un disco de datos y una dirección IP pública. El módulo se compondrá de los siguientes archivos:
-
main.tf
: Definirá los recursos principales del módulo. -
variables.tf
: Declarará las variables necesarias para parametrizar el módulo. -
outputs.tf
: Especificará los valores que el módulo devolverá al ser invocado.
La estructura del proyecto será la siguiente:
.
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
├── providers.tf
└── modules
└── instance_module
├── main.tf
├── variables.tf
└── outputs.tf
Veamos el código de los archivos del módulo. Comencemos especificando las variables necesarias en el archivo variables.tf
.
modules/instance_module/variables.tf
variable "gcp-username" {
description = "GCP user name"
}
variable "gcp-zone" {
description = "GCP zone"
}
variable "gcp-network" {
description = "GCP network"
}
variable "gcp-subnetwork" {
description = "GCP subnetwork"
}
variable "instance-name" {
description = "Name of the instance"
}
variable "machine-type" {
description = "Machine type"
}
variable "image" {
description = "Image"
}
A continuación, definimos los recursos principales del módulo en el archivo main.tf
. Los valores de las variables se obtendrán en tiempo de ejecución de las variables con las que se llame al módulo.
modules/instance_module/main.tf
resource "google_compute_instance" "tf-vm" {
name = var.instance-name
zone = var.gcp-zone
machine_type = var.machine-type
boot_disk {
initialize_params {
image = var.image
}
}
# Add SSH access to the Compute Engine instance
metadata = {
ssh-keys = "${var.gcp-username}:${file("~/.ssh/id_rsa.pub")}"
}
# Startup script
# metadata_startup_script = "${file("script.sh")}"
network_interface {
network = var.gcp-network
subnetwork = var.gcp-network
access_config {}
}
}
Finalmente, especificamos en el archivo outputs.tf
los valores que el módulo devolverá al ser llamado.
modules/instance_module/outputs.tf
output "tf-vm-internal-ip" { (1)
value = google_compute_instance.tf-vm.network_interface.0.network_ip
depends_on = [google_compute_instance.tf-vm]
}
output "tf-vm-ephemeral-ip" { (2)
value = google_compute_instance.tf-vm.network_interface.0.access_config.0.nat_ip
depends_on = [google_compute_instance.tf-vm]
}
-
Dirección IP interna de la instancia. Este nombre es el que se usará para obtener el valor devuelto por el módulo.
-
Dirección IP externa de la instancia. Este nombre es el que se usará para obtener el valor devuelto por el módulo.
7.4. Uso del módulo en el proyecto
Una vez definido el módulo, se puede llamar en el código de Terraform utilizando el bloque module
. A continuación, se muestra un ejemplo de cómo llamar al módulo instance_module
en el archivo main.tf
. No obstante, también será necesario definir las variables necesarias en el archivo variables.tf
y en el archivo terraform.tfvars
, así como el proveedor de GCP en el archivo providers.tf
y la salida de los valores del módulo en el archivo outputs.tf
.
providers.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "6.5.0"
}
}
}
provider "google" {
credentials = file("./gcp-identity.json")
project = var.gcp-project
region = var.gcp-region
zone = var.gcp-zone
}
variables.tf
variable "gcp-username" {
description = "GCP user name"
}
variable "gcp-project" {
description = "GCP project"
}
variable "gcp-region" {
description = "GCP region"
}
variable "gcp-zone" {
description = "GCP zone"
}
variable "gcp-network" {
description = "GCP network"
}
variable "gcp-subnetwork" {
description = "GCP subnetwork"
}
variable "instances" {
description = "Number of instances to create"
type = map(object({
name = string,
machine_type = string,
image = string
}))
}
terraform.tfvars
gcp-username = "your-username"
gcp-project = "your-project"
gcp-region = "us-central1"
gcp-zone = "us-central1-c"
gcp-network = "terraform-network"
gcp-subnetwork = "terraform-subnetwork"
instances = {
database = {
name = "database"
machine_type = "n1-standard-1"
image = "debian-cloud/debian-11"
},
web-server = {
name = "web-server"
machine_type = "n1-standard-1"
image = "ubuntu-os-cloud/ubuntu-2204-lts"
}
}
main.tf
module "database" { (1)
source = "./instance-module" (2)
instance-name = var.instances.database.name (3)
machine-type = var.instances.database.machine_type
image = var.instances.database.image
gcp-zone = var.gcp-zone (4)
gcp-username = var.gcp-username
gcp-network = var.gcp-network
gcp-subnetwork = var.gcp-subnetwork
}
module "web-server" {
source = "./instance-module"
instance-name = var.instances.web-server.name
machine-type = var.instances.web-server.machine_type
image = var.instances.web-server.image
gcp-zone = var.gcp-zone
gcp-username = var.gcp-username
gcp-network = var.gcp-network
gcp-subnetwork = var.gcp-subnetwork
}
-
Llamada al módulo
instance-module
-
Ruta al módulo
-
Variables pasadas al módulo para la personalización de la instancia
-
Variables genéricas necesarias para la creación de la instancia
outputs.tf
output "database_external_ip" {
value = module.database.tf-vm-ephemeral-ip (1)
}
output "web-server_external_ip" {
value = module.web-server.tf-vm-ephemeral-ip
}
output "database_internal_ip" {
value = module.database.tf-vm-internal-ip (2)
}
output "web-server_internal_ip" {
value = module.web-server.tf-vm-internal-ip
}
-
Dirección IP externa de la instancia
database
obtenida de la salida del módulo -
Dirección IP interna de la instancia
database
obtenida de la salida del módulo
8. Recursos de interés
-
Tutorial Serverless Deployment on Cloud Run using Terraform y Configuring Cloud Run with Terraform
NoteActualmente, el provider para Cloud Run sólo permite acceso al puerto 8080 del contenedor. Por tanto, las imágenes Docker tienen que servir su contenido a través de ese puerto para un despliegue en Cloud Run desde Terraform.
9. Conclusiones
En este tutorial hemos visto cómo usar Terraform para desplegar infraestructura en la nube en diferentes proveedores. Hemos visto cómo configurar Terraform para trabajar con los proveedores y cómo crear configuraciones para desplegar recursos en la nube. Hemos visto cómo crear instancias, redes, volúmenes, direcciones IP, etc. en los proveedores OpenStack, Google Cloud y Microsoft Azure. Básicamente, hemos desarrollado un ejemplo de preparación de la infraestructura de red y de despliegue de una instancia con un servidor web Apache en cada uno de los proveedores. Esto nos ha permitido ver las similitudes en la conceptualización en el proceso de despliegue de infraestructura en la nube en diferentes proveedores así como las diferencias en la configuración de los recursos en cada uno de ellos.