フェーズ 1 — ビルド
フェーズ 1 では、Terraform を使用して F5 Distributed Cloud 上に権威 プライマリ DNS ゾーン をデプロイします。Azure Blob Storage によるリモートステートで管理されます。このページでは、プランのすべてのファイルを示し、必須の変数とシークレットを説明し、ローカル実行と GitHub Actions パイプラインの両方についても解説します。
デプロイ内容
Section titled “デプロイ内容”systemネームスペースに、demo-recordsグループの A レコード(www、app、api)を持つ、委任ドメイン用のxcsh_dns_zone。- Azure Blob Storage コンテナに保存された Terraform ステート。これにより、ローカルマシンと CI の間でステートが共有されます。
プランの構成
Section titled “プランの構成”プランは terraform/ 配下に存在します。入力を dns-zone モジュールに接続する薄いルートモジュールで構成されています。
versions.tf
Section titled “versions.tf”Terraform とプロバイダーをピン留めします。プロバイダーの制約は >= 3.62.0 です — このリリースからシステム専用 DNS リソースのネームスペースが自動的にデフォルト設定されます。
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
Section titled “providers.tf”プロバイダーはコード内に引数を取りません — 環境から認証情報を取得するため、シークレットがコミットされることはありません。
# 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
Section titled “backend.tf”部分的な azurerm バックエンド: 環境固有の値はハードコードされていません。座標は init 時に(ローカルファイルから、または CI では GitHub Actions 変数から)提供されます。
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
Section titled “variables.tf”domain は必須です(実行時に提供され、ハードコードされません)。a_records は各レコード名を IPv4 アドレスにマッピングします。namespace 変数は存在しません — プロバイダーが API 仕様の制約により DNS オブジェクトを system ネームスペースに固定するため、ここで設定する必要はありません。
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 と dns-zone モジュール
Section titled “main.tf と dns-zone モジュール”ルートモジュールは入力を ./modules/dns-zone に接続します:
module "dns_zone" { source = "./modules/dns-zone"
domain = var.domain labels = var.labels a_records = var.a_records}モジュールはゾーンを作成します(ネームスペースは省略 — プロバイダーがデフォルトで system に設定します)。dynamic "rr_set" ブロックは a_records マップをエントリごとに 1 つのレコードセットに変換します:
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 } } } } }}ルートの outputs.tf はモジュールからゾーン名と F5 XC 識別子を再エクスポートします。
必須の変数とシークレット
Section titled “必須の変数とシークレット”環境固有の情報は .tf ファイルに組み込まれていません。すべては実行時に提供されます — CI では GitHub Actions の 変数 と シークレット から、またはローカルのファイルと環境変数から提供されます。
| 値 | 用途 | CI ソース | ローカルソース |
|---|---|---|---|
resource_group_name, storage_account_name, container_name, key | azurerm バックエンドの座標 | リポジトリ 変数 TFSTATE_RESOURCE_GROUP、TFSTATE_STORAGE_ACCOUNT、TFSTATE_CONTAINER、TFSTATE_KEY(-backend-config 経由で渡される) | backend.hcl(backend.hcl.example をコピー;gitignore 対象) |
domain | Terraform 入力 | リポジトリ 変数 DNS_DOMAIN(TF_VAR_domain として) | terraform.tfvars(例をコピー)または TF_VAR_domain |
ARM_ACCESS_KEY | azurerm バックエンド認証(ストレージアカウントキー) | リポジトリ シークレット | export ARM_ACCESS_KEY=... |
XCSH_API_URL、XCSH_API_TOKEN | xcsh プロバイダー認証 | リポジトリ シークレット | export XCSH_API_URL=... XCSH_API_TOKEN=... |
リポジトリに同梱されている 2 つのサンプルファイル:
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"]}Azure ステートバックエンドのブートストラップ
Section titled “Azure ステートバックエンドのブートストラップ”バックエンドストレージは自身のブートストラップを保存できないため、認証済みの Azure CLI セッションを使用して、帯域外で一度だけ作成します。リポジトリには 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"次に、ローカル実行用にキーをエクスポートし、(プロバイダーの認証情報とともに)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/dnsローカルでのデプロイ
Section titled “ローカルでのデプロイ”-
入力を設定します。 サンプルをコピーして値を入力します:
Terminal window cp terraform/backend.hcl.example terraform/backend.hclcp terraform/terraform.tfvars.example terraform/terraform.tfvars -
認証情報をエクスポートします:
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>" -
部分的な設定を使用してバックエンドを初期化します:
Terminal window cd terraformterraform init -backend-config=backend.hcl -
フォーマットを確認してバリデーションします:
Terminal window terraform fmt -check -recursiveterraform validate -
プランを実行してアプライします:
Terminal window terraform planterraform apply
アプライが成功するとゾーンが作成され、Azure コンテナにステートが書き込まれます。フェーズ 2 — バリデーション に進んで、名前解決が正常に機能することを確認してください。
継続的インテグレーション
Section titled “継続的インテグレーション”.github/workflows/terraform.yml ワークフローは、すべてのプルリクエストで プラン を実行し、main へのマージ時に アプライ を実行します。バックエンドの座標と入力はリポジトリ変数から、認証情報はシークレットから読み取り、各値は必要なステップにのみ注入されます。また、共有ステートブロブで 2 つのアプライが同時に競合しないよう、コンカレンシーグループで実行をシリアル化します。
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アクションはコミット SHA にピン留めされており(バージョンコメント付き)、シークレットはジョブ全体ではなく init、plan、apply の各ステップにスコープされています。