Configuración de Terraform Cloud y Workspaces
Terraform Cloud es la plataforma gestionada de HashiCorp para ejecutar Terraform de forma colaborativa, con estado remoto seguro, control de acceso por equipos, integración con VCS y políticas de Sentinel para el cumplimiento normativo. Los workspaces permiten separar los entornos (desarrollo, staging, producción) y los componentes de infraestructura manteniendo estados independientes y controlando qué variables y credenciales tiene acceso cada ejecución.
Requisitos Previos
- Cuenta en Terraform Cloud (app.terraform.io) o Terraform Enterprise
- Terraform CLI 1.1+ instalado localmente
- Cuenta en GitHub, GitLab u otro VCS soportado
- Acceso de administrador para configurar la organización
# Instalar Terraform CLI
TERRAFORM_VERSION="1.7.4"
wget "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip"
unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip
mv terraform /usr/local/bin/
terraform version
# Autenticarse en Terraform Cloud
terraform login
# Abrirá el navegador para generar un token de API
# O usar directamente con un token:
export TF_TOKEN_app_terraform_io="tu-token-de-terraform-cloud"
Configuración Inicial de Terraform Cloud
# Crear la organización en Terraform Cloud
# (desde la UI en app.terraform.io → New Organization)
# Configurar el bloque cloud en el código Terraform
cat > main.tf << 'EOF'
terraform {
required_version = ">= 1.5.0"
cloud {
organization = "mi-empresa"
workspaces {
# Especificar el workspace por nombre o por tags
name = "infraestructura-produccion"
# O usar tags para seleccionar múltiples workspaces
# tags = ["produccion", "networking"]
}
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
EOF
# Inicializar Terraform (se conectará a Terraform Cloud automáticamente)
terraform init
# Verificar que el estado remoto está configurado
terraform state list
Creación y Configuración de Workspaces
# Crear workspaces mediante la API de Terraform Cloud
ORGANIZATION="mi-empresa"
TFC_TOKEN="tu-token-api"
# Crear el workspace de producción
curl -s -X POST \
-H "Authorization: Bearer $TFC_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces" \
-d '{
"data": {
"type": "workspaces",
"attributes": {
"name": "infraestructura-produccion",
"description": "Infraestructura de produccion en AWS",
"terraform-version": "1.7.4",
"auto-apply": false,
"execution-mode": "remote",
"tag-names": ["produccion", "aws", "infraestructura"]
}
}
}' | python3 -m json.tool
# Crear el workspace de staging
curl -s -X POST \
-H "Authorization: Bearer $TFC_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces" \
-d '{
"data": {
"type": "workspaces",
"attributes": {
"name": "infraestructura-staging",
"description": "Infraestructura de staging en AWS",
"terraform-version": "1.7.4",
"auto-apply": true,
"tag-names": ["staging", "aws"]
}
}
}'
# Listar todos los workspaces de la organización
curl -s \
-H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces" \
| python3 -m json.tool | jq '.data[].attributes.name'
Integración con VCS (GitHub/GitLab)
# Paso 1: Crear la conexión OAuth con GitHub en Terraform Cloud
# Navegar a: Settings → VCS Providers → Add VCS Provider → GitHub.com
# Paso 2: Configurar el workspace para usar el VCS via API
WORKSPACE_ID=$(curl -s \
-H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces/infraestructura-produccion" \
| python3 -m json.tool | jq -r '.data.id')
# Obtener el ID del VCS OAuth Token
OAUTH_TOKEN_ID=$(curl -s \
-H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/organizations/$ORGANIZATION/oauth-tokens" \
| python3 -m json.tool | jq -r '.data[0].id')
# Conectar el workspace al repositorio Git
curl -s -X PATCH \
-H "Authorization: Bearer $TFC_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID" \
-d "{
\"data\": {
\"type\": \"workspaces\",
\"attributes\": {
\"vcs-repo\": {
\"identifier\": \"miempresa/infraestructura-terraform\",
\"oauth-token-id\": \"$OAUTH_TOKEN_ID\",
\"branch\": \"main\"
},
\"working-directory\": \"/produccion\"
}
}
}"
Gestión del Estado Remoto
# Acceder al estado remoto de otro workspace (estado compartido entre workspaces)
cat > outputs-networking.tf << 'EOF'
# Leer outputs del workspace de networking desde el workspace de aplicación
data "terraform_remote_state" "networking" {
backend = "remote"
config = {
organization = "mi-empresa"
workspaces = {
name = "infraestructura-networking"
}
}
}
# Usar los outputs del workspace de networking
resource "aws_instance" "app_server" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
# Usar la subnet del workspace de networking
subnet_id = data.terraform_remote_state.networking.outputs.private_subnet_id
vpc_security_group_ids = [
data.terraform_remote_state.networking.outputs.app_security_group_id
]
tags = {
Name = "app-server"
Environment = "produccion"
}
}
EOF
# Migrar el estado local a Terraform Cloud
# 1. Configurar el bloque cloud en el código
# 2. Ejecutar terraform init (detecta la migración)
# 3. Confirmar la migración cuando se pregunte
terraform init
# Mover recursos entre workspaces
# Exportar el recurso del workspace origen
terraform state pull > state-backup.json
# Importar el recurso en el workspace destino
terraform state push state-backup-modificado.json
Variables y Variable Sets
# Crear variables de workspace mediante la API
# Variables sensibles (credenciales de AWS)
curl -s -X POST \
-H "Authorization: Bearer $TFC_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/vars" \
-d '{
"data": {
"type": "vars",
"attributes": {
"key": "AWS_ACCESS_KEY_ID",
"value": "AKIAIOSFODNN7EXAMPLE",
"category": "env",
"sensitive": true,
"description": "AWS Access Key ID para produccion"
}
}
}'
curl -s -X POST \
-H "Authorization: Bearer $TFC_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/vars" \
-d '{
"data": {
"type": "vars",
"attributes": {
"key": "AWS_SECRET_ACCESS_KEY",
"value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"category": "env",
"sensitive": true
}
}
}'
# Crear un Variable Set para compartir credenciales entre múltiples workspaces
curl -s -X POST \
-H "Authorization: Bearer $TFC_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/organizations/$ORGANIZATION/varsets" \
-d '{
"data": {
"type": "varsets",
"attributes": {
"name": "AWS Credenciales Produccion",
"description": "Credenciales de AWS para todos los workspaces de produccion",
"global": false
}
}
}'
Run Triggers entre Workspaces
# Los Run Triggers permiten que la ejecución de un workspace inicie automáticamente
# otro workspace (útil para dependencias de infraestructura)
# Obtener el ID del workspace origen (networking)
NETWORKING_WORKSPACE_ID=$(curl -s \
-H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces/infraestructura-networking" \
| python3 -m json.tool | jq -r '.data.id')
# Configurar el trigger: cuando networking se aplica → ejecutar el workspace de app
curl -s -X POST \
-H "Authorization: Bearer $TFC_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/run-triggers" \
-d "{
\"data\": {
\"type\": \"run-triggers\",
\"relationships\": {
\"sourceable\": {
\"data\": {
\"id\": \"$NETWORKING_WORKSPACE_ID\",
\"type\": \"workspaces\"
}
}
}
}
}"
# Listar los run triggers de un workspace
curl -s \
-H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/run-triggers?filter[run-trigger][type]=inbound" \
| python3 -m json.tool
Gestión de Equipos y Permisos
# Crear equipos con diferentes niveles de acceso
curl -s -X POST \
-H "Authorization: Bearer $TFC_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/organizations/$ORGANIZATION/teams" \
-d '{
"data": {
"type": "teams",
"attributes": {
"name": "sre-team",
"visibility": "organization",
"organization-access": {
"manage-workspaces": true,
"manage-policies": false,
"manage-vcs-settings": false
}
}
}
}'
# Asignar permisos del equipo SRE al workspace de producción
# Permisos: read, plan, write, admin
curl -s -X POST \
-H "Authorization: Bearer $TFC_TOKEN" \
-H "Content-Type: application/vnd.api+json" \
"https://app.terraform.io/api/v2/team-workspaces" \
-d "{
\"data\": {
\"type\": \"team-workspaces\",
\"attributes\": {
\"access\": \"write\",
\"runs\": \"apply\",
\"variables\": \"read\",
\"state-versions\": \"read\",
\"plan-outputs\": \"read\",
\"sentinel-mocks\": \"none\"
},
\"relationships\": {
\"team\": {\"data\": {\"type\": \"teams\", \"id\": \"team-XXXXX\"}},
\"workspace\": {\"data\": {\"type\": \"workspaces\", \"id\": \"$WORKSPACE_ID\"}}
}
}
}"
Políticas de Cumplimiento
# Las políticas de Sentinel/OPA permiten validar los plans antes de aplicarlos
# Crear una política que requiera tags en todos los recursos AWS
mkdir -p politicas-sentinel
cat > politicas-sentinel/require-tags.sentinel << 'SENTINEL'
# Política Sentinel: todos los recursos AWS deben tener los tags requeridos
import "tfplan/v2" as tfplan
# Tags obligatorios para todos los recursos de AWS
required_tags = ["Environment", "Owner", "CostCenter"]
# Obtener todos los recursos AWS del plan
aws_resources = filter tfplan.resource_changes as _, rc {
rc.mode is "managed" and
rc.type matches "^aws_" and
rc.change.actions contains "create"
}
# Verificar que cada recurso tiene todos los tags obligatorios
check_required_tags = rule {
all aws_resources as _, rc {
all required_tags as tag {
rc.change.after.tags contains tag
}
}
}
# La política FALLA si algún recurso no tiene los tags
main = rule {
check_required_tags
}
SENTINEL
cat > politicas-sentinel/policy-set.hcl << 'HCL'
policy "require-tags" {
source = "./require-tags.sentinel"
enforcement_level = "hard-mandatory"
}
policy "cost-limit" {
source = "./cost-limit.sentinel"
enforcement_level = "soft-mandatory"
}
HCL
# Publicar el Policy Set en Terraform Cloud
# (desde la UI: Settings → Policy Sets → Connect a new policy set)
Solución de Problemas
Error: workspace not found:
# Verificar que el workspace existe
curl -s \
-H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces" \
| python3 -m json.tool | jq '.data[].attributes.name'
# Verificar la configuración del bloque cloud en el código
terraform console
# > terraform.workspace
# Reinicializar si el workspace cambió
terraform init -reconfigure
Los plans fallan por variables faltantes:
# Verificar las variables configuradas en el workspace
curl -s \
-H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/vars" \
| python3 -m json.tool | jq '.data[].attributes | {key: .key, sensitive: .sensitive}'
# Las variables sensibles muestran su valor como null en la API
# Verificar desde la UI de Terraform Cloud
Run atascado en estado Pending:
# Ver el estado del run
curl -s \
-H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/runs/<run-id>" \
| python3 -m json.tool | jq '.data.attributes.status'
# Cancelar un run atascado
curl -s -X POST \
-H "Authorization: Bearer $TFC_TOKEN" \
"https://app.terraform.io/api/v2/runs/<run-id>/actions/cancel"
Conclusión
Terraform Cloud transforma la gestión de infraestructura como código de un proceso individual a uno colaborativo y gobernado, con estado remoto seguro, control de acceso granular y ejecuciones auditadas. La combinación de workspaces para separar entornos, Variable Sets para compartir credenciales de forma segura y Run Triggers para gestionar dependencias entre componentes de infraestructura proporciona un sistema completo para gestionar la infraestructura de una organización a escala, con los controles necesarios para garantizar la seguridad y el cumplimiento normativo.


