المرحلة 1 — البناء
تنشر المرحلة 1 منطقة DNS أساسية موثوقة على F5 Distributed Cloud باستخدام Terraform، مدعومةً بحالة بعيدة في Azure Blob Storage. تعرض هذه الصفحة كل ملف في الخطة، وتشرح المتغيرات والأسرار المطلوبة، وتغطي كلًّا من التشغيل المحلي وخط أنابيب GitHub Actions.
ما الذي يُنشر
Section titled “ما الذي يُنشر”- منطقة
xcsh_dns_zoneللنطاق المفوَّض لديك، في مساحة الاسمsystem، مع مجموعة سجلات A باسمdemo-records(wwwوappوapi). - حالة Terraform مخزَّنة في حاوية Azure Blob Storage، بحيث تُشارَك نفس الحالة بين جهازك وبيئة CI.
تقع الخطة ضمن 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 (من ملف محلي، أو من متغيرات GitHub Actions في 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
Section titled “variables.tf”domain مطلوب (يُوفَّر في وقت التشغيل، غير مُضمَّن أبدًا)؛ يربط a_records كل اسم سجل بعناوين
IPv4 الخاصة به. لا يوجد متغير namespace — يُثبِّت الموفِّر كائنات DNS في مساحة الاسم system
وفق قيد مواصفات API، لذا لا يُضبط هنا مطلقًا.
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 إلى مجموعة سجل واحدة لكل إدخال:
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. يُوفَّر كل شيء في وقت التشغيل — من
متغيرات وأسرار GitHub Actions في CI، أو من الملفات المحلية ومتغيرات البيئة.
| القيمة | الغرض | المصدر في 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=... |
ملفا المثال المُضمَّنان في المستودع:
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. يقرأ إحداثيات الخلفية والمدخلات من متغيرات المستودع وبيانات الاعتماد من الأسرار،
ويُدرج كلًّا منها فقط في الخطوة التي تحتاجه، ويُرتِّب التشغيلات على مجموعة تزامن حتى لا يتسابق
تطبيقان على نقطة الحالة المشتركة.
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الإجراءات مثبَّتة على تجزئات الالتزام (مع تعليقات الإصدار)، والأسرار محدودة النطاق لخطوات init
وplan وapply بدلًا من الوظيفة بأكملها.