1단계 — 빌드
1단계에서는 Azure Blob Storage의 원격 상태를 기반으로 Terraform을 사용하여 F5 Distributed Cloud에 권위 있는 기본 DNS 존을 배포합니다. 이 페이지에서는 플랜의 모든 파일을 보여주고, 필수 변수와 시크릿을 설명하며, 로컬 실행과 GitHub Actions 파이프라인 모두를 다룹니다.
배포 대상
섹션 제목: “배포 대상”system네임스페이스에서 위임된 도메인에 대한xcsh_dns_zone과 A 레코드(www,app,api)로 구성된demo-records그룹.- Azure Blob Storage 컨테이너에 저장된 Terraform 상태로, 사용자 머신과 CI 간에 동일한 상태를 공유합니다.
플랜은 terraform/ 아래에 위치하며, 입력을 dns-zone 모듈로 연결하는 얇은 루트 모듈로 구성됩니다.
versions.tf
섹션 제목: “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
섹션 제목: “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
섹션 제목: “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
섹션 제목: “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 모듈
섹션 제목: “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 맵을 항목당 하나의 레코드 세트로 변환합니다:
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 식별자를 다시 내보냅니다.
필수 변수 및 시크릿
섹션 제목: “필수 변수 및 시크릿”환경별 정보는 .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을 복사; gitignored) |
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=... |
저장소에 포함된 두 가지 예제 파일:
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 상태 백엔드 부트스트랩
섹션 제목: “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로컬 배포
섹션 제목: “로컬 배포”-
입력을 구성합니다. 예제를 복사하고 값을 입력합니다:
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단계 — 검증으로 이동하여 DNS 확인을 확인하십시오.
지속적 통합
섹션 제목: “지속적 통합”.github/workflows/terraform.yml 워크플로우는 모든 풀 리퀘스트에서 플랜을 실행하고, main에 병합 시 적용을 실행합니다. 저장소 변수에서 백엔드 좌표와 입력을 읽고, 시크릿에서 자격 증명을 읽어 필요한 단계에만 각각 주입하며, 동시성 그룹에서 실행을 직렬화하여 두 적용이 공유 상태 블롭에서 경쟁하지 않도록 합니다.
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 단계로 범위가 제한됩니다.