ماقیسه جینجا با ترافورم تمپلیت

کشف قدرت templatefile() در Terraform از نگاه DevOps

یک از چیزهایی که واقعاً در کار DevOps دوست دارم، آن لحظاتی است که به ویژگی‌ای برمی‌خورم و می‌گویم: «وای، این واقعاً زندگی‌ام را راحت‌تر می‌کند.» اخیراً دقیقاً همین حس را با تابع templatefile() در Terraform داشتم.

اگر مدت‌ها در اتوماسیون زیرساخت کار کرده باشید، احتمالاً با Jinja2 آشنا هستید. شاید در playbookهای Ansible، پروژه‌های پایتون، یا در ابزارهایی مثل Helm. بدون شک Jinja2 بسیار قدرتمند است. اما وقتی برای اولین بار templatefile() را امتحان کردم، فهمیدم که Terraform عمداً مسیر متفاوتی را انتخاب کرده — و دلایل خوبی دارد. بگذارید برایتان بگویم چرا این تابع کوچک مثل یک گوهر پنهان در جعبه ابزار Terraform به نظر می‌رسد.

templatefile() دقیقاً چیست؟

در هستهٔ خود، templatefile() به شما اجازه می‌دهد که فایل‌های قالب خارجی را با متغیرهایی از Terraform رندر کنید. شما مسیر یک فایل .tpl را می‌دهید و یک نگاشت از متغیرها، و خروجی تمیز، قابل‌استفاده را دریافت می‌کنید.

templatefile(path, vars)
  • path: مسیر فایل قالب شما (مثلاً userdata.tpl, config.yaml.tpl و غیره)
  • vars: یک map از مقادیری است که می‌خواهید در آن فایل جایگذاری شوند

قالب خود از syntax آشنا استفاده می‌کند:

  • ${variable} → برای جایگذاری متغیر
  • %{ if … } / %{ for … } → شرط‌ها و حلقه‌ها

همین. ساده، خوانا، قابل پیش‌بینی است.

و با این‌حال … وقتی بفهمی کجای اکوسیستم Terraform جای می‌گیرد، شگفت‌آوراً قدرتمند است.


اولین «ایول» : User Data

وقتی بخواهی یک EC2 instance را provision کنی، معمولاً باید اسکریپتی را به صورت user data داخل کد Terraform قرار دهی. این کار چندان خوشایند نیست — اسکریپت‌های چندخطی shell در HCL شلوغ و زشت می‌شوند.

اینجا templatefile() وارد می‌شود.

مثلاً یک فایل userdata.tpl:

#!/bin/bash
echo "Hello, ${name}" > /var/www/html/index.html
apt-get update -y
apt-get install -y nginx
systemctl start nginx

در Terraform:

resource "aws_instance" "web" {
  ami           = "ami-123456"
  instance_type = "t2.micro"

  user_data = templatefile("${path.module}/userdata.tpl", {
    name = "Ashkan"
  })
}

ناگهان کد Terraform تو مرتب است، اسکریپت جداست و قابل استفادهٔ مجدد است؛ فقط با تغییر متغیرها در محیط‌های مختلف.

این حس جدا کردن Frontend از Backend در مهندسی نرم‌افزار را دارد — مرزهای واضح، تفکر پاک‌تر.

دومین «ایول» : YAML برای Kubernetes

حالا بیایید درباره Kubernetes صحبت کنیم. Terraform می‌تواند منابع Kubernetes را مدیریت کند، اما اغلب manifestهای YAML دارید که می‌خواهید دوباره استفاده کنید. تبدیل دستی آن‌ها به Terraform HCL دردسر دارد.

به جای آن، YAML خود را داخل یک قالب بگذارید:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${app_name}
spec:
  replicas: ${replicas}
  template:
    spec:
      containers:
      - name: ${app_name}
        image: ${image}:${tag}

و بعد در Terraform:

resource "kubernetes_manifest" "app" {
  manifest = yamldecode(
    templatefile("${path.module}/deployment.tpl", {
      app_name = "myapp"
      replicas = 3
      image    = "nginx"
      tag      = "1.27.0"
    })
  )
}

بوووم! Terraform حالا منابع Kubernetes شما را به صورت داینامیک مدیریت می‌کند، در حالی که همچنان manifestهای YAML به سبکی که تیم شما می‌داند را می‌نویسید. آن لحظه بود که به خودم گفتم: «چرا زودتر این کار را نکرده بودم؟»


مقایسه با Jinja2: تجربه‌ای آشنا اما متفاوت

به عنوان کسی که به شدت از Jinja2 در Ansible استفاده کرده‌ام، اولین حدسم این بود: «خوبه، اما آیا به اندازه Jinja2 قدرتمند است؟»

واقعیت این است: قرار نیست باشد.

Jinja2 تقریباً مثل یک زبان برنامه‌نویسی مستقل است. می‌توانی شرط‌ها و حلقه‌های پیچیده بنویسی، فیلترها، ماکروها، حتی وراثت قالب‌ها. این قدرت وقتی عالی است که playbookهای Ansible بسیار پیچیده بسازی یا کانفیگ‌های داینامیک در برنامه‌های پایتون.

اما همراه قدرت، پیچیدگی هم می‌آید. دیده‌ام قالب‌های Jinja2 آنقدر پر از منطق شده‌اند که خواندنشان سخت است — دیباگ کردنش انگار وارد مغز کسی شدی که سیم‌ها را به هم گره زده است.

از طرف دیگر، templatefile() در Terraform عمداً مینیمالیستی است:

Terraform templatefile() vs Jinja2
A concise comparison for DevOps engineers — syntax, power, use cases, complexity, dependency
Terraform — templatefile()
Lightweight templating for IaC; renders external templates using Terraform variables.
Tightly integrated with Terraform, minimal logic (interpolation, simple loops & conditionals), ideal for user-data, manifests and config files tied to provisioning.
Jinja2
Full-featured templating engine used widely in Ansible and Python applications.
Powerful filters, macros, inheritance and rich logic — great for complex config generation and dynamic templates across multiple ecosystems.
Syntax
Terraform: `${var}`, `%{ if }` / `%{ for }` — lightweight interpolation.
Jinja2: `{{ var }}`, `{% if %}` / `{% for %}` — rich templating syntax with filters.
Power
Terraform: Basic substitution + simple control flow — enough for IaC templates.
Jinja2: Full templating engine (macros, filters, inheritance) — suitable for heavy logic and complex templating.
Primary Use Cases
Terraform: User-data scripts, cloud-init, Kubernetes manifests, config files generated at provisioning time.
Jinja2: Ansible playbooks, Python app templates, complex config pipelines, templating across tools.
Complexity & Maintainability
Terraform: Low complexity — encourages readability and predictable infrastructure lifecycle.
Jinja2: High potential complexity — templates can become logic-heavy and harder to debug.
Dependencies
Terraform: Native (no extra runtime), works inside Terraform only.
Jinja2: Requires Python/Jinja runtime; portable across ecosystems (Ansible, Flask, etc.).

در ابتدا فکر می‌کردم این یک محدودیت است. اما سپس فهمیدم: Terraform نیازی به آن سطح از قدرت ندارد.

Terraform می‌خواهد تعاریف زیرساخت شما قابل پیش‌بینی، بازتولیدپذیر، شفاف باشند. منطق زیاد در قالب‌ها می‌تواند این شفافیت را خراب کند. templatefile() دقیقاً به اندازه کافی انعطاف‌پذیری دارد بدون این‌که درب آشوب را باز کند.


مثال مقایسه‌ای (Side-by-Side Example)

فرض کنید می‌خواهید یک Deployment در Kubernetes راه‌اندازی کنید با تعداد replica، نسخه کانتینر (image) و نام برنامه قابل تنظیم.

در Jinja2 (Ansible):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ app_name }}
spec:
  replicas: {{ replicas }}
  selector:
    matchLabels:
      app: {{ app_name }}
  template:
    metadata:
      labels:
        app: {{ app_name }}
    spec:
      containers:
      - name: {{ app_name }}
        image: {{ image }}:{{ tag }}
        {% if env is defined %}
        env:
        {% for key, value in env.items() %}
        - name: {{ key }}
          value: "{{ value }}"
        {% endfor %}
        {% endif %}

و در Ansible:

- name: Render Kubernetes deployment
  template:
    src: deployment.yaml.j2
    dest: /tmp/deployment.yaml
  vars:
    app_name: "myapp"
    replicas: 3
    image: "nginx"
    tag: "1.27.0"
    env:
      LOG_LEVEL: debug
      FEATURE_FLAG: enabled

در Terraform با templatefile():

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${app_name}
spec:
  replicas: ${replicas}
  selector:
    matchLabels:
      app: ${app_name}
  template:
    metadata:
      labels:
        app: ${app_name}
    spec:
      containers:
      - name: ${app_name}
        image: ${image}:${tag}
%{ if length(env) > 0 }
        env:
%{ for key, value in env }
        - name: ${key}
          value: "${value}"
%{ endfor }
%{ endif }

در Terraform HCL:

resource "kubernetes_manifest" "app" {
  manifest = yamldecode(
    templatefile("${path.module}/deployment.tpl", {
      app_name = "myapp"
      replicas = 3
      image    = "nginx"
      tag      = "1.27.0"
      env      = {
        LOG_LEVEL    = "debug"
        FEATURE_FLAG = "enabled"
      }
    })
  )
}

ملاحظه کنید که چقدر شبیه‌اند، اما Terraform منطق را ساده نگه می‌دارد: بدون فیلترها، بدون ماکروها، بدون پیچیدگی لانه‌کود شدن. فقط مقدار لازم منطق (if و for) تا کار را انجام دهد، و کاملاً در چرخه عمر Terraform تنیده شده.

پیام اصلی از مقایسه

  • با Jinja2، شما قدرت حداکثری را دارید، اما آن قدرت می‌تواند قالب‌هایتان را به «مینی‌برنامه‌ها» تبدیل کند و دیباگشان شود کابوس.
  • با templatefile() در Terraform، شما همین‌قدر قدرت به اندازهٔ کافی دریافت می‌کنید، و در ذهنیت زیرساخت-به‌عنوان-کد (Infrastructure-as-Code) باقی می‌مانید — ساده، اعلامی (declarative)، قابل پیش‌بینی.

و این دقیقاً فلسفهٔ طراحی است:

  • Jinja2 یک موتور قالب همه‌منظوره است.
  • templatefile() یک ابزار کمکی متمرکز بر IaC است.


چرا این موضوع برای ما مهندسین DevOps اهمیت دارد

ما مهندسین DevOps در دنیایی از رابط‌ها زندگی می‌کنیم: ابزارها به ابزارها وصل‌اند، سیستم‌ها به سیستم‌ها. اغلب نیاز به کمی چسب داریم — یک فایل کانفیگ اینجا، یک manifest YAML آنجا، یک اسکریپت شروع در کنار.

  • با Jinja2، وقتی نیاز به تولید کانفیگ پیچیده دارید سراغش می‌روید؛ مثل playbookهای داینامیک Ansible یا برنامه‌های وب.
  • با templatefile()، وقتی بخواهید فقط به اندازه کافی templating داشته باشید تا پل بین زیرساخت و کانفیگ‌ها/اسکریپت‌هایتان را تمیز بسازد، استفاده می‌کنید.

زیبایی در ساده بودن است. templatefile() تلاش نمی‌کند همه چیز باشد — تلاش می‌کند دقیقاً همان چیزی باشد که Terraform نیاز دارد.

و صادقانه بگویم، همین باعث شد آن حس «ایول!» را در جریان‌هایی از Terraform داشته باشم. انگار Terraform می‌گوید: «ما می‌دانیم که قالب‌ها نیاز خواهند بود، ولی اجازه می‌دهیم کارت را مناسب و قابل کنترل پیش ببری.»


نتیجه‌گیری

وقتی نسخه یک Deployment در Kubernetes را در هر دو حالت دیدم، برایم روشن شد:

  • Jinja2 مثل چاقوی سوئیسی است با هر تیغه ممکن.
  • templatefile() مثل پیچ‌گوشتی‌ای است که همیشه کنار کد Terraform تو ایستاده است.

هر دو جای خود را دارند، اما دانستن زمان استفاده از کدام ابزار، آتوماسیون‌ها را تمیز، قابل نگهداری، و لذت‌بخش نگه می‌دارد.

 این است دلیل اینکه کشف templatefile() حس یک انقلاب کوچک در جریان‌های کاری Terraform برایم داشت. موضوع این نیست که بخواهی همه چیز را انجام دهی، بلکه بخواهی چیز درست در زمان درست را انجام دهی.