تخطَّ إلى المحتوى

المرحلة 1 — البناء

تنشر المرحلة 1 منطقة DNS أساسية موثوقة على F5 Distributed Cloud باستخدام Terraform، مدعومةً بحالة بعيدة في Azure Blob Storage. تعرض هذه الصفحة كل ملف في الخطة، وتشرح المتغيرات والأسرار المطلوبة، وتغطي كلًّا من التشغيل المحلي وخط أنابيب GitHub Actions.

  • منطقة xcsh_dns_zone للنطاق المفوَّض لديك، في مساحة الاسم system، مع مجموعة سجلات A باسم demo-records (www وapp وapi).
  • حالة Terraform مخزَّنة في حاوية Azure Blob Storage، بحيث تُشارَك نفس الحالة بين جهازك وبيئة CI.

تقع الخطة ضمن terraform/: وحدة جذر خفيفة تربط المدخلات بوحدة dns-zone.

يُثبّت إصدار 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"
}
}
}

لا يأخذ الموفِّر أي وسائط في الكود — يُصادق من البيئة، لذلك لا تُودَع أي أسرار في الكود.

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

خلفية 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" {}
}

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

تربط وحدة الجذر المدخلات بـ ./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"

لا يمكن لتخزين الخلفية تخزين تمهيد نفسه، لذا أنشئه مرة واحدة خارج النطاق باستخدام جلسة Azure CLI مُصادَق عليها. يُضمَّن في المستودع scripts/bootstrap-azure-state.sh:

Terminal window
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:

Terminal window
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. تهيئة المدخلات. انسخ الأمثلة وأدخل قيمك:

    Terminal window
    cp terraform/backend.hcl.example terraform/backend.hcl
    cp terraform/terraform.tfvars.example terraform/terraform.tfvars
  2. تصدير بيانات الاعتماد:

    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>"
  3. تهيئة الخلفية بالإعداد الجزئي:

    Terminal window
    cd terraform
    terraform init -backend-config=backend.hcl
  4. فحص التنسيق والتحقق من الصحة:

    Terminal window
    terraform fmt -check -recursive
    terraform validate
  5. التخطيط والتطبيق:

    Terminal window
    terraform plan
    terraform apply

يُنشئ التطبيق الناجح المنطقة ويكتب الحالة إلى حاوية Azure. تابع إلى المرحلة 2 — التحقق للتأكد من أنها تُحلِّل بشكل صحيح.

يُشغِّل سير العمل .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 بدلًا من الوظيفة بأكملها.