Este proyecto configura la infraestructura base en Azure utilizando Terraform. Está diseñado para establecer una base sólida y gobernable para futuras cargas de trabajo, almacenando el estado de Terraform de forma remota y segura.
Primero, autentícate en tu cuenta de Azure y selecciona la suscripción donde deseas desplegar los recursos.
# Inicia sesión de forma interactiva
az login
# Establece la suscripción a utilizar
az account set --subscription "<NOMBRE_O_ID_DE_TU_SUSCRIPCION>"Para colaborar y mantener la seguridad, el estado de Terraform se guarda en una cuenta de Azure Storage. Hemos automatizado su creación.
- Ejecutar el script: El script
create_backend.shse encarga de crear un grupo de recursos, una cuenta de almacenamiento y un contenedor (tfboot) para guardar el ficheroterraform.tfstate.bash 00-setup/create_backend.sh
- Salida del script: Al finalizar, el script te proporcionará los nombres de los recursos creados. Estos datos son cruciales para configurar el backend en Terraform.
- Acceso publico: Si
PUBLIC_NETWORK_ACCESS=Disabled, el script deshabilita el acceso publico del Storage Account.
Con los recursos del backend ya creados, configuramos Terraform para que los utilice.
- Archivo
backend.tf: En el directorio10-governance/, hemos creado un archivobackend.tfpara decirle a Terraform dónde debe guardar su estado usando Azure AD (sin access keys). La configuración es la siguiente:terraform { backend "azurerm" { resource_group_name = "rg-bootstrap-state-aq7yx" storage_account_name = "stbootstraptfstateaq7yx" container_name = "tfboot" key = "10-governance.tfstate" use_azuread_auth = true } }
- Archivo
main.tf: En el mismo directorio, un ficheromain.tfdefine el proveedor que usará Terraform, en este caso,azurerm(Azure).terraform { required_version = "= 1.14.3" required_providers { azurerm = { source = "hashicorp/azurerm" version = "= 3.117.1" } } }
- Inicialización: El último paso es ejecutar
terraform initdentro de la carpeta10-governance/. Este comando prepara el entorno, descarga el proveedor de Azure y conecta Terraform con el backend remoto que hemos configurado.
Cada etapa tiene su propio state en la misma Storage Account y contenedor, con key distinta:
10-governance/->10-governance.tfstate20-logging/->20-logging.tfstate30-networking/->30-networking.tfstate40-shared-services/->40-shared-services.tfstate
Se aplican policies al RG rg-bootstrap-state-aq7yx:
- Tags obligatorios:
owner,env,costCenter - Regiones permitidas (ej.
westeurope) - Bloqueo de Public IP
Se crea un RG dedicado rg-bootstraplogging-state-aq7yx con un Log Analytics Workspace:
- Retención: 30 días
- Diagnósticos: Activity Log de la suscripción
- Diagnósticos: Storage Account del state (
stbootstraptfstateaq7yx) - Export: Archive en Storage Account (
stbootlogarchaq7yx) - Alertas: eliminación de RG y de Storage Account
- Action Group: email definido en
alert_email - Dashboard: portal dashboard básico con referencia al workspace
- Nota: se ignoran cambios en
metricdel diagnostic setting para evitar drift de Azure (verignore_changes).
Se crea red dedicada para Private Endpoints:
- RG:
network-Bootstrap - VNet:
10.10.0.0/16 - Subnet Private Endpoints:
10.10.1.0/24 - Private DNS Zone:
privatelink.blob.core.windows.net - Private Endpoints para:
- Storage Account del state
- Storage Account de archive
Nota: para bloquear acceso publico, usar public_network_access_enabled = false en la Storage Account de archive y deshabilitar acceso publico en la Storage Account del state (si ya existe, hacerlo via az storage account update).
VM minima para self-hosted runner (Linux):
- RG:
rg-bootstrap-shared - VM:
vm-bootstrap-runner(B1s) - Subnet:
10.10.2.0/24 - SSH: clave publica en
ssh_public_key - NSG: permite SSH desde
ssh_allowed_cidrs(ajusta en produccion)
Pasos resumidos:
- Crear la VM con
40-shared-services/. - Instalar dependencias en la VM:
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash sudo apt-get update && sudo apt-get install -y gnupg software-properties-common wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt-get update && sudo apt-get install -y terraform
- Registrar runner:
- GitHub → Settings → Actions → Runners → New self-hosted runner (Linux)
- Ejecutar los comandos en la VM
- Instalar y arrancar el servicio:
sudo ./svc.sh install sudo ./svc.sh start
- Asegurar etiqueta
azuresi usamosruns-on: [self-hosted, azure]para mayor seguridad y monitorizacion.
Nota: los workflows incluyen jobs para encender/apagar la VM (start_runner / stop_runner). Ademas de incluir jobs checks Setup Terraform para que vea si en el runner esta instalado Terraform, si el check indica que no esta Terraform instalado, dicho paso se activa para que se instale.
El siguiente paso es automatizar los despliegues con GitHub Actions usando OIDC (sin secrets). El flujo general es:
- Registrar una App en Azure Entra ID: Se crea una identidad para GitHub Actions.
- Configurar Federated Credentials: Se liga el repo y la rama (
refs/heads/main). - Asignar RBAC: Dar permisos mínimos sobre la suscripción o grupo de recursos.
- Crear Workflows: Se definen los pasos de
planyapplyen.github/workflows/para10-governance/,20-logging/,30-networking/y40-shared-services/, con aprobación manual víaenvironment.
Con esto, el proceso de CI/CD para la infraestructura queda establecido sin manejar claves.
Workflow separado en .github/workflows/drift.yml:
- Corre diario (cron) y manual (
workflow_dispatch) - El job de
Setup Terraformse ejecuta solo si el check verifica que no esta Terraform instalado en el runner. - Ejecuta
terraform plan -detailed-exitcodeen10-governance/,20-logging/,30-networking/y40-shared-services/ - Publica resumen en Job Summary (incluye extracto del plan si hay drift)
- Sube artifacts:
tfplan,plan.txt,plan.show.txt - Envía email con Gmail usando:
vars.GMAIL_ALERT(email destino)secrets.AZ_BOOTSTRAP_GMAIL_ALERT(app password)
Workflow manual para verificar acceso al backend:
- Archivo:
.github/workflows/state-check.yml - Usa el runner
self-hosted, azure - Autenticación OIDC y
terraform initen10-governance/ - Incluye
start_runnerystop_runnerpara encender/apagar la VM
Para mantener el state privado:
- Asegura los tags requeridos en el Storage Account del state (
owner,env,costCenter). - Deshabilita el acceso público:
az storage account update \ --name stbootstraptfstateaq7yx \ --resource-group rg-bootstrap-state-aq7yx \ --public-network-access Disabled
- Verifica el estado:
az storage account show \ --name stbootstraptfstateaq7yx \ --resource-group rg-bootstrap-state-aq7yx \ --query publicNetworkAccess -o tsv
- Ejecuta el workflow
state-checkpara confirmar acceso desde el runner privado.
Rol custom para la app OIDC en la suscripcion (permite crear/editar/eliminar recursos del bootstrap):
Archivo: 50-custom-roles/role-bootstrap-custom.json
Crear rol:
az role definition create --role-definition 50-custom-roles/role-bootstrap-custom.jsonActualizar rol (si cambian permisos):
az role definition update --role-definition 50-custom-roles/role-bootstrap-custom.jsonAsignar rol a la app OIDC:
az role assignment create \
--assignee f8053b2b-0618-4baf-9764-dd6edd5ca136 \
--role "BootstrapCustomOperator" \
--scope /subscriptions/2a23dc3f-267e-4cd1-a12a-695e2623f1f7Nota: el backend del state requiere ademas Storage Blob Data Contributor en la Storage Account del state.
Pasos recomendados para rotar la app OIDC:
- Crear una nueva App en Entra ID.
- Asignar el rol custom
BootstrapCustomOperatory el rolStorage Blob Data Contributor(state). - Crear el Federated Credential (repo + rama).
- Actualizar
AZ_APPID_BOOTSTRAPen GitHub Variables. - Ejecutar un workflow de prueba (plan) para validar acceso.
Si solo cambia la rama o environment, basta con actualizar el Federated Credential. Cuando la nueva app funcione, eliminar la app antigua y sus role assignments.
Comandos básicos por carpeta:
cd 10-governance
terraform init
terraform plan
terraform applycd 20-logging
terraform init
terraform plan
terraform applycd 30-networking
terraform init
terraform plan
terraform applycd 40-shared-services
terraform init
terraform plan
terraform applyDónde revisar resultados:
- GitHub Actions: Job Summary y artifacts en el workflow de drift.
- Para aplicar cambios manuales, usa el workflow principal con
environmentde aprobación.
Orden recomendado (por dependencias entre stages):
10-governance/20-logging/30-networking/(requiere el Storage Account de archive)40-shared-services/(requiere la VNet)
El workflow aplica este orden y omite 30-networking/ si el archive no existe.
Errores comunes y soluciones:
-
403 al iniciar backend (AzureAD auth):
Error típico:AuthorizationPermissionMismatchal listar blobs.
Solución: asignar rol Storage Blob Data Contributor a la identidad que ejecuta Terraform (tu usuario local y/o la app OIDC) en el scope de la Storage Account del state. -
azurerm_policy_assignmentno soportado:
Solución: usarazurerm_resource_group_policy_assignmentpara scope de RG. -
Retención de Log Analytics fuera de rango:
retention_in_daysmínimo 30 paraPerGB2018. -
Nombre de Storage Account inválido:
Debe ser minúsculas/números y 3–24 caracteres. -
Argumento inválido
allow_blob_public_access:
Solución: usarallow_nested_items_to_be_public = falseenazurerm_storage_account.
Si aparece un error nuevo, añádelo aquí con su solución para mantener la guía actualizada.
00-setup/: Contiene scripts para la configuración inicial.10-governance/: Contiene la configuración raíz de Terraform para el gobierno de la suscripción.20-logging/: Configuración de logging y diagnósticos.30-networking/: Destinado a los recursos de red centrales.40-shared-services/: VM para self-hosted runner.50-custom-roles/: Destinado a roles personalizados