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

OpenTofu

أداة بنية تحتية كشفرة مفتوحة المصدر وفرع مجتمعي من Terraform.

الأمرالوصف
brew install opentofuالتثبيت على macOS باستخدام Homebrew
snap install --classic opentofuالتثبيت على Linux باستخدام Snap
choco install opentofuالتثبيت على Windows باستخدام Chocolatey
curl -fsSL https://get.opentofu.org/install-opentofu.sh | shالتثبيت عبر سكريبت shell
docker run -it ghcr.io/opentofu/opentofuالتشغيل عبر Docker
tofu --versionعرض الإصدار المثبت
tofu -helpعرض الأوامر المتاحة
# استخدام tofuenv (مثل tfenv لـ OpenTofu)
git clone https://github.com/tofuutils/tofuenv.git ~/.tofuenv
echo 'export PATH="$HOME/.tofuenv/bin:$PATH"' >> ~/.bashrc

# عرض الإصدارات المتاحة
tofuenv list-remote

# تثبيت إصدار محدد
tofuenv install 1.8.0
tofuenv use 1.8.0

# تثبيت الإصدار في المشروع
echo "1.8.0" > .opentofu-version
الأمرالوصف
tofu initتهيئة دليل العمل وتنزيل الموفرين
tofu init -upgradeإعادة التهيئة وتحديث إصدارات الموفرين/الوحدات
tofu init -backend-config=prod.hclالتهيئة مع ملف تكوين الخلفية
tofu planمعاينة تغييرات البنية التحتية
tofu plan -out=plan.tfplanحفظ الخطة في ملف للتطبيق لاحقاً
tofu plan -target=aws_instance.webتخطيط التغييرات لمورد محدد فقط
tofu plan -var-file=prod.tfvarsالتخطيط مع ملف متغيرات محدد
tofu applyتطبيق تغييرات البنية التحتية (يطلب التأكيد)
tofu apply -auto-approveتطبيق التغييرات بدون تأكيد
tofu apply plan.tfplanتطبيق ملف خطة محفوظ
tofu destroyتدمير جميع البنية التحتية المُدارة
tofu destroy -auto-approveالتدمير بدون تأكيد
tofu destroy -target=aws_instance.webتدمير مورد محدد فقط
الأمرالوصف
tofu validateالتحقق من صحة بنية التكوين
tofu fmtتنسيق ملفات التكوين
tofu fmt -checkالتحقق من تنسيق الملفات (متوافق مع CI)
tofu fmt -recursiveتنسيق جميع الملفات في الأدلة الفرعية
tofu outputعرض جميع قيم المخرجات
tofu output -jsonعرض المخرجات بتنسيق JSON
tofu output db_passwordعرض قيمة مخرج محددة
tofu showعرض الحالة الحالية بتنسيق مقروء
tofu show -jsonعرض الحالة بتنسيق JSON
tofu show plan.tfplanعرض تفاصيل الخطة المحفوظة
tofu graphإنشاء رسم بياني لتبعيات الموارد (تنسيق DOT)
tofu graph | dot -Tpng > graph.pngتحويل رسم التبعيات إلى PNG
tofu refreshمزامنة الحالة مع البنية التحتية الحقيقية
tofu providersعرض الموفرين المستخدمين في التكوين
# كتلة الموفرين المطلوبة
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.5"
    }
  }

  # تشفير الحالة (ميزة حصرية لـ OpenTofu)
  encryption {
    method "aes_gcm" "default" {
      keys = key_provider.pbkdf2.mykey
    }

    state {
      method   = method.aes_gcm.default
      enforced = true
    }

    plan {
      method   = method.aes_gcm.default
      enforced = true
    }

    key_provider "pbkdf2" "mykey" {
      passphrase = var.state_passphrase
    }
  }
}

# موفر بتكوين افتراضي
provider "aws" {
  region = "us-east-1"

  default_tags {
    tags = {
      ManagedBy   = "opentofu"
      Environment = var.environment
      Project     = var.project_name
    }
  }
}

# اسم مستعار للموفر لمتعدد المناطق
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}

# استخدام موفر بالاسم المستعار على مورد
resource "aws_instance" "west_server" {
  provider      = aws.west
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
}
# مورد أساسي
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = {
    Name = "${var.project_name}-web"
  }
}

# مصدر بيانات (قراءة الموارد الموجودة)
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

# القيم المحلية
locals {
  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "opentofu"
  }

  az_count = length(data.aws_availability_zones.available.names)
}

# Count و for_each
resource "aws_subnet" "public" {
  count             = local.az_count
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = merge(local.common_tags, {
    Name = "${var.project_name}-public-${count.index}"
    Type = "public"
  })
}

resource "aws_security_group_rule" "ingress" {
  for_each = {
    http  = { port = 80, cidrs = ["0.0.0.0/0"] }
    https = { port = 443, cidrs = ["0.0.0.0/0"] }
    ssh   = { port = 22, cidrs = [var.admin_cidr] }
  }

  type              = "ingress"
  from_port         = each.value.port
  to_port           = each.value.port
  protocol          = "tcp"
  cidr_blocks       = each.value.cidrs
  security_group_id = aws_security_group.main.id
}
الأمرالوصف
variable "name" { type = string }تعريف متغير نصي
variable "port" { type = number default = 8080 }متغير بقيمة افتراضية
variable "tags" { type = map(string) }متغير خريطة
variable "cidrs" { type = list(string) }متغير قائمة
variable "config" { type = object({ ... }) }متغير كائن بهيكل محدد
variable "db_pass" { sensitive = true }تعليم المتغير كحساس (مخفي في المخرجات)
variable "env" { validation { ... } }متغير مع تحقق مخصص
الأمرالوصف
tofu plan -var="name=value"تمرير متغير عبر سطر الأوامر
tofu plan -var-file="prod.tfvars"تمرير متغيرات من ملف
TF_VAR_name=value tofu planتمرير عبر متغير بيئة
إنشاء ملف terraform.tfvarsقيم متغيرات تُحمّل تلقائياً
إنشاء ملف *.auto.tfvarsملفات متغيرات تُحمّل تلقائياً

أمثلة المتغيرات والمخرجات

Section titled “أمثلة المتغيرات والمخرجات”
# variables.tf
variable "environment" {
  type        = string
  description = "بيئة النشر"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "يجب أن تكون البيئة dev أو staging أو prod."
  }
}

variable "instance_config" {
  type = object({
    instance_type = string
    volume_size   = number
    encrypted     = bool
  })
  default = {
    instance_type = "t3.micro"
    volume_size   = 20
    encrypted     = true
  }
}

variable "database_password" {
  type      = string
  sensitive = true
}

# outputs.tf
output "vpc_id" {
  description = "معرّف VPC"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "قائمة معرّفات الشبكات الفرعية العامة"
  value       = aws_subnet.public[*].id
}

output "web_url" {
  description = "عنوان URL لخادم الويب"
  value       = "https://${aws_lb.web.dns_name}"
}

output "db_connection_string" {
  description = "سلسلة اتصال قاعدة البيانات"
  value       = "postgresql://${var.db_username}:${var.database_password}@${aws_db_instance.main.endpoint}/${var.db_name}"
  sensitive   = true
}
# prod.tfvars
environment = "prod"
instance_config = {
  instance_type = "t3.large"
  volume_size   = 100
  encrypted     = true
}

# dev.tfvars
environment = "dev"
instance_config = {
  instance_type = "t3.micro"
  volume_size   = 20
  encrypted     = false
}
الأمرالوصف
tofu state listعرض جميع الموارد في الحالة
tofu state show aws_instance.webعرض تفاصيل مورد محدد
tofu state mv aws_instance.old aws_instance.newإعادة تسمية مورد في الحالة
tofu state rm aws_instance.webإزالة مورد من الحالة (يحتفظ بالبنية التحتية الحقيقية)
tofu state pullسحب الحالة البعيدة إلى stdout
tofu state pushدفع الحالة المحلية إلى الخلفية البعيدة
tofu import aws_instance.web i-1234567استيراد مورد موجود إلى الحالة
tofu state replace-provider hashicorp/aws registry.opentofu.org/hashicorp/awsاستبدال الموفر في الحالة
tofu force-unlock LOCK_IDتحرير قفل حالة عالق
# خلفية S3 (الأكثر شيوعاً)
terraform {
  backend "s3" {
    bucket         = "my-tofu-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "tofu-locks"
    encrypt        = true
  }
}

# خلفية PostgreSQL
terraform {
  backend "pg" {
    conn_str = "postgres://user:pass@db.example.com/tofu_state?sslmode=require"
  }
}

# خلفية HTTP (تعمل مع GitLab وبدائل Terraform Cloud)
terraform {
  backend "http" {
    address        = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod"
    lock_address   = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
    unlock_address = "https://gitlab.example.com/api/v4/projects/1/terraform/state/prod/lock"
    username       = "gitlab-ci-token"
    password       = var.ci_token
  }
}

# خلفية محلية (افتراضي، للتطوير)
terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}

تشفير الحالة (حصري لـ OpenTofu)

Section titled “تشفير الحالة (حصري لـ OpenTofu)”
# تشفير الحالة مع AWS KMS
terraform {
  encryption {
    key_provider "aws_kms" "main" {
      kms_key_id = "arn:aws:kms:us-east-1:123456789:key/abcd-1234"
      region     = "us-east-1"
      key_spec   = "AES_256"
    }

    method "aes_gcm" "default" {
      keys = key_provider.aws_kms.main
    }

    state {
      method   = method.aes_gcm.default
      enforced = true
    }
  }
}
الأمرالوصف
module "vpc" { source = "./modules/vpc" }استخدام وحدة محلية
module "vpc" { source = "github.com/org/repo//modules/vpc" }استخدام وحدة من GitHub
module "vpc" { source = "registry.opentofu.org/..." }استخدام وحدة من سجل OpenTofu
tofu getتنزيل وتحديث الوحدات
tofu get -updateفرض تحديث جميع الوحدات
modules/
└── vpc/
    ├── main.tf          # الموارد
    ├── variables.tf     # متغيرات الإدخال
    ├── outputs.tf       # قيم المخرجات
    ├── versions.tf      # متطلبات الموفرين
    └── README.md        # توثيق الاستخدام
# modules/vpc/main.tf
resource "aws_vpc" "this" {
  cidr_block           = var.cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = merge(var.tags, { Name = var.name })
}

resource "aws_subnet" "public" {
  count                   = length(var.public_subnets)
  vpc_id                  = aws_vpc.this.id
  cidr_block              = var.public_subnets[count.index]
  availability_zone       = var.azs[count.index]
  map_public_ip_on_launch = true

  tags = merge(var.tags, {
    Name = "${var.name}-public-${count.index}"
  })
}

# modules/vpc/variables.tf
variable "name" { type = string }
variable "cidr" { type = string }
variable "azs" { type = list(string) }
variable "public_subnets" { type = list(string) }
variable "tags" { type = map(string) default = {} }

# modules/vpc/outputs.tf
output "vpc_id" { value = aws_vpc.this.id }
output "public_subnet_ids" { value = aws_subnet.public[*].id }

# استخدام الوحدة
module "vpc" {
  source = "./modules/vpc"

  name           = "production"
  cidr           = "10.0.0.0/16"
  azs            = ["us-east-1a", "us-east-1b", "us-east-1c"]
  public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  tags           = local.common_tags
}
الأمرالوصف
استبدال terraform بـ tofu في الأوامرأوامر CLI متوافقة مباشرة
tofu init -upgradeإعادة التهيئة مع موفري OpenTofu
الاحتفاظ بملفات .tf الموجودة دون تغييرصيغة HCL متوافقة بالكامل
الاحتفاظ بملفات .tfstate الموجودةتنسيق الحالة متوافق
الاحتفاظ بملف .terraform.lock.hcl الموجودملف القفل متوافق
تحديث CI/CD لاستخدام إجراء opentofu/setup-opentofuتبديل GitHub Actions

قائمة التحقق من الانتقال

Section titled “قائمة التحقق من الانتقال”
الانتقال من Terraform إلى OpenTofu:

1. تثبيت OpenTofu بجانب Terraform
2. في دليل المشروع، تشغيل: tofu init
3. تشغيل: tofu plan
   - مقارنة المخرجات مع: terraform plan
   - يجب أن تكون متطابقة (بدون تغييرات)
4. استبدال 'terraform' بـ 'tofu' في أنابيب CI/CD
5. تحديث GitHub Actions:
   - القديم: hashicorp/setup-terraform
   - الجديد: opentofu/setup-opentofu
6. تحديث مصادر الموفرين في ملف القفل (إذا لزم الأمر):
   - tofu providers lock
7. تفعيل تشفير الحالة (حصري لـ OpenTofu):
   - إضافة كتلة encryption إلى كتلة terraform {}
8. تبديل مراجع سجل الوحدات:
   - registry.terraform.io → registry.opentofu.org
   - (كلاهما يعمل، لكن سجل OpenTofu مفضّل)
name: OpenTofu Plan & Apply
on:
  push:
    branches: [main]
  pull_request:

jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: opentofu/setup-opentofu@v1
        with:
          tofu_version: 1.8.0

      - name: Init
        run: tofu init
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Plan
        run: tofu plan -out=plan.tfplan -no-color
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Apply (الفرع الرئيسي فقط)
        if: github.ref == 'refs/heads/main'
        run: tofu apply -auto-approve plan.tfplan
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

الميزات الحصرية لـ OpenTofu

Section titled “الميزات الحصرية لـ OpenTofu”

تشفير ملفات الحالة أثناء الراحة — غير متوفر في Terraform. يدعم موفري مفاتيح PBKDF2 و AWS KMS و GCP KMS و Azure Key Vault.

تشفير الحالة من جانب العميل

Section titled “تشفير الحالة من جانب العميل”

على عكس تشفير Terraform Cloud من جانب الخادم، يقوم OpenTofu بتشفير الحالة قبل مغادرتها جهازك، مما يضمن عدم تمكن أي طرف ثالث من قراءة حالة البنية التحتية الخاصة بك.

يحتفظ OpenTofu بسجل الموفرين والوحدات الخاص به في registry.opentofu.org، مما يضمن التوفر على المدى الطويل بشكل مستقل عن قرارات ترخيص HashiCorp.

  1. استخدام حالة بعيدة مع القفل — تكوين خلفية S3 + DynamoDB (أو ما يعادلها) لمنع التعديلات المتزامنة وتمكين التعاون بين الفريق.

  2. تفعيل تشفير الحالة — استخدام ميزة التشفير الحصرية لـ OpenTofu مع KMS أو PBKDF2 لحماية البيانات الحساسة في ملفات الحالة.

  3. تثبيت إصدارات الموفرين والوحدات — استخدام قيود ~> (مثل ~> 5.0) للسماح بتحديثات التصحيح مع منع التغييرات المعطّلة.

  4. استخدام مساحات العمل أو فصل الأدلة للبيئات — الحفاظ على فصل حالات dev و staging والإنتاج لمنع التغييرات العرضية بين البيئات.

  5. كتابة وحدات للأنماط القابلة لإعادة الاستخدام — استخراج الأنماط الشائعة (VPCs وقواعد البيانات ومجموعات Kubernetes) إلى وحدات بمدخلات ومخرجات واضحة.

  6. التحقق من المتغيرات بالقيود — إضافة كتل validation للمتغيرات لالتقاط القيم غير الصالحة قبل وصولها إلى موفر السحابة.

  7. حفظ الخطط قبل التطبيق — استخدام tofu plan -out=plan.tfplan ثم tofu apply plan.tfplan لضمان تطبيق ما راجعته بالضبط.

  8. استخدام default_tags على الموفرين — تعيين العلامات التنظيمية (ManagedBy و Environment و Project) على مستوى الموفر لضمان وسم متسق.

  9. استيراد الموارد الموجودة — استخدام tofu import لإدخال البنية التحتية غير المُدارة تحت سيطرة OpenTofu بدلاً من إعادة إنشائها.

  10. تشغيل tofu fmt -check في CI — فرض تنسيق متسق بين جميع أعضاء الفريق بإفشال بناءات CI على الملفات غير المنسقة.

  11. استخدام مصادر البيانات للمراجع الخارجية — الإشارة إلى البنية التحتية الموجودة (AMIs و VPCs ومناطق DNS) بكتل data بدلاً من ترميز المعرّفات الثابتة.

  12. مراجعة الخطط بعناية — قراءة مخرجات الخطة دائماً قبل التطبيق. الانتباه لإجراءات destroy و replace التي قد تسبب توقف الخدمة.