Fase 1 — Build
La Fase 1 distribuisce una zona DNS primaria autorevole su F5 Distributed Cloud con Terraform, supportata da uno stato remoto in Azure Blob Storage. Questa pagina mostra ogni file del piano, spiega le variabili e i segreti richiesti e copre sia l’esecuzione locale che la pipeline GitHub Actions.
Cosa viene distribuito
Sezione intitolata “Cosa viene distribuito”- Una
xcsh_dns_zoneper il dominio delegato, nel namespacesystem, con un gruppodemo-recordsdi record A (www,app,api). - Lo stato Terraform memorizzato in un container Azure Blob Storage, in modo che lo stesso stato sia condiviso tra il proprio computer e la CI.
Il piano
Sezione intitolata “Il piano”Il piano si trova sotto terraform/: un modulo radice leggero che collega gli input a un modulo dns-zone.
versions.tf
Sezione intitolata “versions.tf”Fissa Terraform e il provider. Il vincolo del provider è >= 3.62.0 — la release in cui
le risorse DNS solo per il sistema impostano automaticamente il namespace come predefinito.
terraform { required_version = ">= 1.5"
required_providers { xcsh = { source = "f5-sales-demo/xcsh" # >= 3.62.0: the namespace attribute for system-only DNS resources defaults # to "system" (spec-driven), so it can be omitted. Locally the provider is # consumed via dev_overrides, which ignores this constraint. version = ">= 3.62.0" } }}providers.tf
Sezione intitolata “providers.tf”Il provider non accetta argomenti nel codice — si autentica dall’ambiente, quindi nessun segreto viene sottoposto a commit.
# The xcsh provider authenticates from the environment — no secrets in code.# Export one of the following credential sets before running Terraform:## Token auth: XCSH_API_URL + XCSH_API_TOKEN# P12 auth: XCSH_API_URL + XCSH_P12_FILE + XCSH_P12_PASSWORD# PEM auth: XCSH_API_URL + XCSH_CERT + XCSH_KEY## See terraform/README.md for local dev setup (dev_overrides + env).provider "xcsh" {}backend.tf
Sezione intitolata “backend.tf”Un backend azurerm parziale: nessun valore specifico dell’ambiente è hardcoded. Le coordinate vengono
fornite al momento dell’init (da un file locale o dalle variabili GitHub Actions nella CI).
terraform { # Azure Blob Storage remote state, configured as a PARTIAL backend: # no environment-specific values are hardcoded here. Supply them at init time. # # CI: terraform init -backend-config="resource_group_name=$RG" ... # (values from GitHub Actions repository variables) # Local: terraform init -backend-config=backend.hcl (copy backend.hcl.example; gitignored) # # Auth is the storage account access key via the ARM_ACCESS_KEY environment # variable (never committed). Keyless auth (use_oidc / use_azuread_auth) is not # used: our Contributor-only RBAC cannot assign the "Storage Blob Data # Contributor" role those methods require. backend "azurerm" {}}variables.tf
Sezione intitolata “variables.tf”domain è obbligatorio (fornito in fase di esecuzione, mai hardcoded); a_records mappa ogni nome di record ai suoi
indirizzi IPv4. Non esiste una variabile namespace — il provider fissa gli oggetti DNS al namespace system
dal vincolo della specifica API, quindi non viene mai configurato qui.
variable "domain" { description = "DNS zone FQDN (required; supplied via TF_VAR_domain / a GitHub variable / tfvars)." type = string}
variable "labels" { description = "Labels applied to managed DNS objects." type = map(string) default = { managed_by = "terraform" use_case = "dns" }}
variable "a_records" { description = "A records: record name (\"\" = apex) to list of IPv4 addresses." type = map(list(string)) default = { www = ["203.0.113.10"] app = ["203.0.113.20"] api = ["203.0.113.30"] }}main.tf e il modulo dns-zone
Sezione intitolata “main.tf e il modulo dns-zone”Il modulo radice collega gli input a ./modules/dns-zone:
module "dns_zone" { source = "./modules/dns-zone"
domain = var.domain labels = var.labels a_records = var.a_records}Il modulo crea la zona (il namespace viene omesso — il provider lo imposta su system come predefinito). Un
blocco dynamic "rr_set" trasforma la mappa a_records in un set di record per ogni voce:
resource "xcsh_dns_zone" "this" { name = var.domain labels = var.labels
primary { default_soa_parameters {}
rr_set_group { metadata { name = "demo-records" }
dynamic "rr_set" { for_each = var.a_records content { ttl = var.record_ttl a_record { name = rr_set.key values = rr_set.value } } } } }}Il file outputs.tf radice riesporta il nome della zona e l’identificatore F5 XC dal modulo.
Variabili e segreti richiesti
Sezione intitolata “Variabili e segreti richiesti”Nulla di specifico per l’ambiente è incorporato nei file .tf. Tutto viene fornito in fase di esecuzione — dalle
variabili e dai segreti di GitHub Actions nella CI, o da file locali e variabili d’ambiente.
| Valore | Scopo | Sorgente CI | Sorgente locale |
|---|---|---|---|
resource_group_name, storage_account_name, container_name, key | Coordinate del backend azurerm | Variabili repository TFSTATE_RESOURCE_GROUP, TFSTATE_STORAGE_ACCOUNT, TFSTATE_CONTAINER, TFSTATE_KEY (passate tramite -backend-config) | backend.hcl (copiare backend.hcl.example; gitignored) |
domain | Input Terraform | Variabile repository DNS_DOMAIN (come TF_VAR_domain) | terraform.tfvars (copiare l’esempio) o TF_VAR_domain |
ARM_ACCESS_KEY | Auth backend azurerm (chiave dell’account di archiviazione) | Segreto repository | export ARM_ACCESS_KEY=... |
XCSH_API_URL, XCSH_API_TOKEN | Auth provider xcsh | Segreti repository | export XCSH_API_URL=... XCSH_API_TOKEN=... |
I due file di esempio inclusi nel repository:
resource_group_name = "f5-sales-demo-tfstate"storage_account_name = "f5salesdemotfstate"container_name = "tfstate"key = "dns.tfstate"domain = "f5-sales-demo.com"
labels = { managed_by = "terraform" use_case = "dns"}
a_records = { www = ["203.0.113.10"] app = ["203.0.113.20"] api = ["203.0.113.30"]}Bootstrap del backend di stato Azure
Sezione intitolata “Bootstrap del backend di stato Azure”Il backend di archiviazione non può memorizzare il proprio bootstrap, quindi è necessario crearlo una volta, fuori banda, con una
sessione Azure CLI autenticata. Il repository include scripts/bootstrap-azure-state.sh:
az group create --name f5-sales-demo-tfstate --location eastus2 \ --tags managed_by=terraform use_case=dns purpose=tfstate
az storage account create --name f5salesdemotfstate --resource-group f5-sales-demo-tfstate \ --location eastus2 --sku Standard_LRS --kind StorageV2 \ --min-tls-version TLS1_2 --allow-blob-public-access false
# State safety: keep prior versions and allow recovery of deleted state blobs.az storage account blob-service-properties update \ --account-name f5salesdemotfstate --resource-group f5-sales-demo-tfstate \ --enable-versioning true \ --enable-delete-retention true --delete-retention-days 7 \ --enable-container-delete-retention true --container-delete-retention-days 7
KEY="$(az storage account keys list \ --account-name f5salesdemotfstate --resource-group f5-sales-demo-tfstate \ --query '[0].value' -o tsv)"
az storage container create --name tfstate \ --account-name f5salesdemotfstate --auth-mode key --account-key "$KEY"Quindi esportare la chiave per le esecuzioni locali e impostarla (insieme alle credenziali del provider) come segreti GitHub:
export ARM_ACCESS_KEY="$KEY"gh secret set ARM_ACCESS_KEY -R f5-sales-demo/dnsgh secret set XCSH_API_URL -R f5-sales-demo/dnsgh secret set XCSH_API_TOKEN -R f5-sales-demo/dnsDistribuire in locale
Sezione intitolata “Distribuire in locale”-
Configurare gli input. Copiare gli esempi e inserire i propri valori:
Terminal window cp terraform/backend.hcl.example terraform/backend.hclcp terraform/terraform.tfvars.example terraform/terraform.tfvars -
Esportare le credenziali:
Terminal window export XCSH_API_URL="https://<tenant>.console.ves.volterra.io"export XCSH_API_TOKEN="<api-token>"export ARM_ACCESS_KEY="<storage-account-key>" -
Inizializzare il backend con la configurazione parziale:
Terminal window cd terraformterraform init -backend-config=backend.hcl -
Verificare la formattazione e validare:
Terminal window terraform fmt -check -recursiveterraform validate -
Pianificare e applicare:
Terminal window terraform planterraform apply
Un’applicazione riuscita crea la zona e scrive lo stato nel container Azure. Continuare con Fase 2 — Validate per confermare che si risolve correttamente.
Integrazione continua
Sezione intitolata “Integrazione continua”Il workflow .github/workflows/terraform.yml esegue un plan su ogni pull request e un apply
al merge su main. Legge le coordinate del backend e gli input dalle variabili repository e le
credenziali dai segreti, iniettando ciascuna solo nello step che ne ha bisogno, e serializza le esecuzioni su un
gruppo di concorrenza in modo che due apply non si sovrappongano mai sul blob di stato condiviso.
name: Terraform
on: pull_request: branches: [main] paths: ['terraform/**', '.github/workflows/terraform.yml'] push: branches: [main] paths: ['terraform/**']
permissions: contents: read
concurrency: group: terraform-state cancel-in-progress: false
env: TF_IN_AUTOMATION: 'true'
jobs: terraform: name: ${{ github.event_name == 'push' && 'apply' || 'plan' }} runs-on: ubuntu-latest defaults: run: working-directory: terraform steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Terraform uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
- name: Init env: ARM_ACCESS_KEY: ${{ secrets.ARM_ACCESS_KEY }} RESOURCE_GROUP: ${{ vars.TFSTATE_RESOURCE_GROUP }} STORAGE_ACCOUNT: ${{ vars.TFSTATE_STORAGE_ACCOUNT }} CONTAINER: ${{ vars.TFSTATE_CONTAINER }} STATE_KEY: ${{ vars.TFSTATE_KEY }} run: | terraform init -input=false \ -backend-config="resource_group_name=${RESOURCE_GROUP}" \ -backend-config="storage_account_name=${STORAGE_ACCOUNT}" \ -backend-config="container_name=${CONTAINER}" \ -backend-config="key=${STATE_KEY}"
- name: Format check run: terraform fmt -check -recursive
- name: Validate run: terraform validate -no-color
- name: Plan if: github.event_name == 'pull_request' env: ARM_ACCESS_KEY: ${{ secrets.ARM_ACCESS_KEY }} XCSH_API_URL: ${{ secrets.XCSH_API_URL }} XCSH_API_TOKEN: ${{ secrets.XCSH_API_TOKEN }} TF_VAR_domain: ${{ vars.DNS_DOMAIN }} run: terraform plan -input=false -no-color
- name: Apply if: github.event_name == 'push' env: ARM_ACCESS_KEY: ${{ secrets.ARM_ACCESS_KEY }} XCSH_API_URL: ${{ secrets.XCSH_API_URL }} XCSH_API_TOKEN: ${{ secrets.XCSH_API_TOKEN }} TF_VAR_domain: ${{ vars.DNS_DOMAIN }} run: terraform apply -input=false -auto-approve -no-colorLe azioni sono fissate a SHA di commit (con commenti di versione), e i segreti sono limitati agli step init,
plan e apply anziché all’intero job.