Aller au contenu

Phase 1 — Construction

La Phase 1 déploie une zone DNS primaire faisant autorité sur F5 Distributed Cloud avec Terraform, adossée à un état distant dans Azure Blob Storage. Cette page présente chaque fichier du plan, explique les variables et secrets requis, et couvre à la fois une exécution locale et le pipeline GitHub Actions.

  • Une xcsh_dns_zone pour votre domaine délégué, dans le namespace system, avec un groupe demo-records d’enregistrements A (www, app, api).
  • L’état Terraform stocké dans un conteneur Azure Blob Storage, afin que le même état soit partagé entre votre machine et la CI.

Le plan réside sous terraform/ : un module racine léger qui relie les entrées à un module dns-zone.

Fixe les versions de Terraform et du fournisseur. La contrainte du fournisseur est >= 3.62.0 — la version où les ressources DNS exclusivement système définissent automatiquement leur namespace par défaut.

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"
}
}
}

Le fournisseur ne prend aucun argument dans le code — il s’authentifie depuis l’environnement, donc aucun secret n’est validé dans le dépôt.

# 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" {}

Un backend azurerm partiel : aucune valeur spécifique à l’environnement n’est codée en dur. Les coordonnées sont fournies au moment de init (depuis un fichier local, ou depuis les variables GitHub Actions en 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" {}
}

domain est requis (fourni à l’exécution, jamais codé en dur) ; a_records associe chaque nom d’enregistrement à ses adresses IPv4. Il n’y a pas de variable namespace — le fournisseur fixe les objets DNS au namespace system d’après la contrainte de la spécification API, donc il n’est jamais configuré ici.

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"]
}
}

Le module racine relie les entrées à ./modules/dns-zone :

module "dns_zone" {
source = "./modules/dns-zone"
domain = var.domain
labels = var.labels
a_records = var.a_records
}

Le module crée la zone (le namespace est omis — le fournisseur le définit par défaut à system). Un bloc dynamic "rr_set" transforme la map a_records en un jeu d’enregistrements par entrée :

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
}
}
}
}
}
}

Le fichier outputs.tf racine réexporte le nom de la zone et l’identifiant F5 XC depuis le module.

Aucune valeur spécifique à l’environnement n’est intégrée dans les fichiers .tf. Tout est fourni à l’exécution — depuis les variables et secrets GitHub Actions en CI, ou depuis des fichiers locaux et des variables d’environnement.

ValeurObjectifSource CISource locale
resource_group_name, storage_account_name, container_name, keyCoordonnées du backend azurermVariables de dépôt TFSTATE_RESOURCE_GROUP, TFSTATE_STORAGE_ACCOUNT, TFSTATE_CONTAINER, TFSTATE_KEY (passées via -backend-config)backend.hcl (copier backend.hcl.example ; gitignored)
domainEntrée TerraformVariable de dépôt DNS_DOMAIN (sous forme de TF_VAR_domain)terraform.tfvars (copier l’exemple) ou TF_VAR_domain
ARM_ACCESS_KEYAuthentification du backend azurerm (clé du compte de stockage)Secret de dépôtexport ARM_ACCESS_KEY=...
XCSH_API_URL, XCSH_API_TOKENAuthentification du fournisseur xcshSecrets de dépôtexport XCSH_API_URL=... XCSH_API_TOKEN=...

Les deux fichiers d’exemple fournis dans le dépôt :

resource_group_name = "f5-sales-demo-tfstate"
storage_account_name = "f5salesdemotfstate"
container_name = "tfstate"
key = "dns.tfstate"

Le stockage du backend ne peut pas stocker son propre bootstrap, il faut donc le créer une seule fois, hors bande, avec une session Azure CLI authentifiée. Le dépôt fournit scripts/bootstrap-azure-state.sh :

Fenêtre de terminal
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"

Exportez ensuite la clé pour les exécutions locales, et définissez-la (ainsi que les identifiants du fournisseur) comme secrets GitHub :

Fenêtre de terminal
export ARM_ACCESS_KEY="$KEY"
gh secret set ARM_ACCESS_KEY -R f5-sales-demo/dns
gh secret set XCSH_API_URL -R f5-sales-demo/dns
gh secret set XCSH_API_TOKEN -R f5-sales-demo/dns
  1. Configurez les entrées. Copiez les exemples et renseignez vos valeurs :

    Fenêtre de terminal
    cp terraform/backend.hcl.example terraform/backend.hcl
    cp terraform/terraform.tfvars.example terraform/terraform.tfvars
  2. Exportez les identifiants :

    Fenêtre de terminal
    export XCSH_API_URL="https://<tenant>.console.ves.volterra.io"
    export XCSH_API_TOKEN="<api-token>"
    export ARM_ACCESS_KEY="<storage-account-key>"
  3. Initialisez le backend avec la configuration partielle :

    Fenêtre de terminal
    cd terraform
    terraform init -backend-config=backend.hcl
  4. Vérifiez le formatage et validez :

    Fenêtre de terminal
    terraform fmt -check -recursive
    terraform validate
  5. Planifiez et appliquez :

    Fenêtre de terminal
    terraform plan
    terraform apply

Une application réussie crée la zone et écrit l’état dans le conteneur Azure. Passez à la Phase 2 — Validation pour confirmer la résolution.

Le workflow .github/workflows/terraform.yml exécute un plan à chaque pull request et un apply lors d’une fusion dans main. Il lit les coordonnées du backend et les entrées depuis les variables de dépôt, et les identifiants depuis les secrets, en les injectant uniquement dans l’étape qui en a besoin, et sérialise les exécutions sur un groupe de concurrence afin que deux applications ne soient jamais en concurrence sur le blob d’état partagé.

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-color

Les actions sont épinglées à des SHA de commit (avec des commentaires de version), et les secrets sont limités aux étapes init, plan et apply plutôt qu’à l’ensemble du job.