تفاوت Ansible و Terraform: کدام را انتخاب کنیم؟
تفاوت اصلی در مدیریت وضعیت است. Terraform زیرساخت را با فایل state میسازد و Ansible از طریق SSH تنظیمات داخلی را اعمال میکند. راهنمای کاربردی برای ترکیب این دو ابزار.
تفاوت Ansible و Terraform در یک جمله
انتخاب بین Ansible و Terraform، انتخاب بین دو ابزار با وظیفه یکسان نیست. Terraform وضعیت زیرساخت موجود را تعریف میکند: سرورها، دیسکها، شبکهها و رکوردهای DNS. اما Ansible وضعیت داخلی یک ماشینِ از پیش موجود را تعیین میکند: بستهها، کاربران، فایلهای پیکربندی و سرویسهای در حال اجرا. Terraform یک VPS ایجاد میکند و Ansible آن VPS را به یک وبسرور تبدیل میکند.
هر دو ابزار اعلانی (declarative) هستند و هر دو در دسته زیرساخت به عنوان کد (IaC) قرار میگیرند. تفاوت اصلی در چیزی است که هر کدام به خاطر میسپارند. Terraform یک فایل وضعیت (state file) مینویسد که هر منبع در کد شما را به یک شیء واقعی که از طریق API ساخته شده است نگاشت میکند؛ بنابراین میتواند تشخیص دهد که حذف پنج خط کد، به معنای نابودی یک سرور است. در مقابل، Ansible بین دفعات اجرا هیچ چیزی را به خاطر نمیسپارد. این ابزار از طریق SSH متصل میشود، وضعیت ماشین را بررسی میکند و فقط مواردی را تغییر میدهد که با playbook مطابقت ندارند.
همین یک تفاوت، باقی این راهنما و دلیل شکست خوردنِ ترکیب وظایف این دو ابزار در یک ابزار واحد را توضیح میدهد.
عملکرد واقعی Terraform
ابزار Terraform از طریق یک پلاگین provider با API ارتباط برقرار میکند. صفحه registry مربوط به provider شما، انواع resourceهایی را که میتوانید بنویسید تعریف میکند؛ بنابراین یک سرور روی یک میزبان با سروری روی میزبان دیگر، نامهای resource و آرگومانهای متفاوتی دارند.
resource "cloud_server" "web" {
name = "web1"
image = "ubuntu-24.04"
type = "small"
}
output "web_ip" {
value = cloud_server.web.ipv4_address
}عبارت cloud_server را با نوع resourceای که در مستندات provider شما آمده است جایگزین کنید. بلوک output بخش مهم این راهنماست، زیرا مشخص میکند که آدرس چگونه از Terraform خارج میشود.
terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplanدستور terraform init، provider را دانلود کرده و یک فایل lock ایجاد میکند. دستور terraform plan تفاوت بین کد شما و فایل state را نمایش میدهد و در نهایت به خطی مانند Plan: 1 to add, 0 to change, 0 to destroy. ختم میشود. هر بار آن خط را بخوانید. برخی آرگومانها را نمیتوان در محل تغییر داد و plan با عبارت # forces replacement در کنار attribute و به دنبال آن 1 to add, 0 to change, 1 to destroy، این موضوع را اعلام میکند. اعمال (apply) کردن چنین planای، سرور را حذف و یک سرور خالی جدید ایجاد میکند؛ این همان روشی است که افراد دادههایی را که تصور میکردند امن است، از دست میدهند.
ذخیره کردن plan در یک فایل و اعمال همان فایل، به جای اجرای یک دستور ساده terraform apply، تضمین میکند که آنچه بررسی کردهاید همان چیزی است که اجرا میشود. بین اجرای این دو دستور، ممکن است شخص دیگری زیرساخت را تغییر داده باشد.
فایل terraform.tfstate حافظه سیستم است. اگر آن را از دست بدهید، Terraform دیگر نمیداند که آن سرورها متعلق به شما هستند، بنابراین اجرای بعدی (apply) سعی میکند نسخههای تکراری ایجاد کند. به محض اینکه بیش از یک نفر دستورات را اجرا میکند، آن را در یک remote backend نگهداری کنید، زیرا اجرای همزمان توسط دو نفر منجر به این وضعیت میشود:
Error: Error acquiring the state lockابزار OpenTofu یک fork از Terraform است که دستورات و فرمت فایل یکسانی دارد. تا ژوئیه 2026، اگر به جای terraform از دستور tofu استفاده کنید، تمام موارد این راهنما به درستی کار خواهند کرد.
عملکرد واقعی Ansible
Ansible به هیچ agent یا API نیاز ندارد. این ابزار یک اتصال SSH برقرار میکند، یک ماژول کوچک Python را به مقصد کپی کرده، آن را اجرا میکند و سپس حذف مینماید. هر چیزی که از طریق SSH و رمز عبور sudo در دسترس باشد، توسط Ansible قابل پیکربندی است.
- name: Base web server
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure nginx is running at boot
ansible.builtin.service:
name: nginx
state: started
enabled: trueansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.yml --check --diff
ansible-playbook -i inventory.ini site.ymlماژول ping پیش از شروع عیبیابی یک playbook، وضعیت SSH، Python و sudo را بررسی میکند. نتیجهٔ موفقیتآمیز به صورت web1 | SUCCESS => {"ping": "pong"} نمایش داده میشود. اجرای --check --diff نزدیکترین معادل Ansible به یک طرح کلی است: این دستور گزارش میدهد که چه تغییراتی اعمال خواهد شد بدون آنکه واقعاً تغییری ایجاد کند؛ هرچند تسکهایی که به تسکهای قبلی وابسته هستند ممکن است در حالت check mode گزارشهای نادرستی ارائه دهند، زیرا تغییرات قبلی در واقعیت رخ ندادهاند.
هر اجرا با یک خلاصه مانند ok=6 changed=2 unreachable=0 failed=0 پایان مییابد. همان playbook را دو بار اجرا کنید. اجرای دوم باید وضعیت changed=0 را گزارش دهد. تسکی که در هر اجرا وضعیت changed را گزارش میکند، idempotent نیست و معمولاً یک تسک command یا shell است که باید به صورت یک ماژول واقعی پیادهسازی میشد. اگر این مبحث برای شما جدید است، با اولین playbook برای Ansible روی یک VPS تکی شروع کنید و از آنجا کار را گسترش دهید.
جایی که این دو ابزار همپوشانی دارند و جایی که با هم تداخل پیدا میکنند
Terraform میتواند با استفاده از provisioner در remote-exec، دستوراتی را روی سرور جدید اجرا کند. مستندات رسمی HashiCorp، استفاده از provisionerها را آخرین راهکار میداند. دلایل خوبی برای این توصیه وجود دارد.
یک provisioner فقط زمانی اجرا میشود که منبع (resource) ایجاد شود. اگر اسکریپت را ویرایش کنید، هیچ اتفاقی روی سرور موجود نمیافتد، زیرا از دیدگاه Terraform، منبع از قبل با کد مطابقت دارد. مراحل provisioner هرگز در terraform plan ظاهر نمیشوند، بنابراین در بررسیهای شما هیچ اثری از آنها دیده نمیشود. اگر اسکریپت با خطا مواجه شود، Terraform منبع را tainted (آلوده) علامتگذاری میکند و اجرای بعدی دستور apply، سروری را که احتمالاً سالم بوده است، تخریب و دوباره بازسازی میکند.
زمانبندی این خطا نیز نامناسب است. provider به محض اینکه API اعلام کند سرور ایجاد شده است، آن را به عنوان یک منبع آماده گزارش میدهد، در حالی که سیستمعامل هنوز در حال بوت شدن است و sshd هنوز در حال گوش دادن نیست.
Error: remote-exec provisioner error
timeout - last error: dial tcp 203.0.113.10:22: connect: connection refusedAnsible وسوسه متفاوتی ایجاد میکند. ماژولهای ابری میتوانند سرورها را ایجاد کنند و برای تعداد کمی ماشین، این روش کارآمد است. چیزی که در این حالت از دست میدهید، گراف وابستگیها و فایل state است. Ansible با کمال میل یک منبع را ایجاد میکند، اما اگر آن task را از playbook خود حذف کنید، منبع همچنان در حال اجرا باقی میماند و هزینه آن محاسبه میشود، زیرا هیچ رکوردی وجود ندارد که نشان دهد آن منبع متعلق به شما بوده است.
قانونی که از این موضوع استخراج میشود این است: اجازه دهید Terraform مالکیت اشیایی را که توسط API ایجاد و تخریب میشوند در اختیار داشته باشد و اجازه دهید Ansible مالکیت هر چیزی که درون یک سیستمعامل بوتشده قرار دارد را بر عهده بگیرد.
انتقال موفقیتآمیز
انتقال، یک مرز است نه یک ادغام. Terraform کار خود را به پایان میرساند، یک آدرس منتشر میکند و متوقف میشود. Ansible از همان آدرس شروع به کار میکند.
terraform apply -auto-approve
terraform output -raw web_ip
printf '[web]\n%s ansible_user=root\n' "$(terraform output -raw web_ip)" > inventory.ini
ansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.ymlterraform output -raw یک مقدار را بدون کوتیشن و بدون wrapper از نوع JSON چاپ میکند؛ این دقیقاً همان چیزی است که برای جایگذاری در shell به آن نیاز دارید. برای چندین سرور، از terraform output -json استفاده کنید و inventory را بر اساس آن بسازید، زیرا -raw فقط یک رشته، عدد یا مقدار boolean را مدیریت میکند.
نگه داشتن مرحله ping بین این دو ابزار ارزشمند است. این مرحله باعث میشود خطای «Terraform آدرس اشتباهی به من داد» از خطای «playbook من باگ دارد» جدا شود؛ وقتی playbook اولین چیزی باشد که با سرور جدید تعامل میکند، تشخیص این دو مشکل از یکدیگر غیرممکن است.
خواندن وضعیت Terraform به عنوان inventory در Ansible
اگر ترجیح میدهید اصلاً فایل inventory ننویسید، کالکشن cloud.terraform وضعیت را مستقیماً میخواند.
ansible-galaxy collection install cloud.terraformفایل terraform.yml را در کنار playbook خود بنویسید:
plugin: cloud.terraform.terraform_provider
project_path: /home/deploy/infraansible-inventory -i terraform.yml --graph
ansible-playbook -i terraform.yml site.ymlپیش از تکیه بر این روش، دو نکته را باید بدانید. این پلاگین دستور terraform show را روی project_path اجرا میکند، بنابراین آن دایرکتوری باید از قبل مقداردهی اولیه (initialize) شده باشد، در غیر این صورت پلاگین با خطا مواجه میشود. همچنین، این روش میزبانها را از منابع سرور شما ابداع نمیکند: بلکه منابع ansible_host و ansible_group را میخواند که شما آنها را در کد Terraform خود با استفاده از Ansible provider تعریف کردهاید. تا زمانی که آنها را اضافه نکنید، هیچ چیزی در ansible-inventory --graph ظاهر نمیشود.
یک فایل inventory ساده و تولیدشده، عیبیابی آسانتری دارد و با هر provider کار میکند. این پلاگین زمانی ارزش خود را نشان میدهد که تعداد ماشینها در inventory از چند مورد فراتر رفته و ویرایش دستی منجر به بروز غلطهای تایپی شود؛ این همان نقطهای است که مدیریت چندین سرور لینوکسی از یک ماشین کنترل به یک گردشکار واقعی تبدیل میشود، نه صرفاً یک عادت.
آیا واقعاً به Terraform نیاز دارید؟
بیشتر افرادی که این مطلب را میخوانند، حداقل در حال حاضر نیازی به آن ندارند. Terraform زمانی ارزش هزینهکردن را دارد که ایجاد و حذف زیرساخت، خود یک وظیفهٔ تکراری باشد. اگر یک VPS را از طریق پنل کنترل سفارش دادهاید و قصد دارید آن را برای 2 سال نگه دارید، Terraform چیزی را توصیف میکند که فقط یکبار رخ میدهد و یک فایل state به شما اضافه میکند که نباید آن را از دست بدهید.
زمانی به سراغ Terraform بروید که محیطها را بهطور مکرر بازسازی میکنید، زمانی که محیط staging باید دقیقاً با محیط production مطابقت داشته باشد، زمانی که چندین نفر زیرساخت را تغییر میدهند و شما پیش از حذف هر چیزی به یک طرح قابلبازبینی نیاز دارید، یا زمانی که آنچه مدیریت میکنید فراتر از سرورها رفته و شامل رکوردهای DNS، لود بالانسرها و قوانین فایروالی است که در API ارائهدهنده وجود دارند.
زمانی که سرورها عمر طولانی دارند و تعدادشان کم است، و زمانی که پرسش روزانه این است که «آیا این سرور بهدرستی پیکربندی شده است؟» نه اینکه «آیا این سرور وجود دارد؟»، تنها از Ansible استفاده کنید. یک playbook واحد که یک سرور تازه را ایمن میکند، همان هدفی را پوشش میدهد که در ده دقیقه اول روی یک VPS جدید انجام میشود، با این مزیت که روی سرور بعدی نیز به همان شکل اجرا میشود.
ترتیب یادگیری از همینجا مشخص میشود. Ansible روی اولین سروری که دارید بازدهی خود را نشان میدهد. Terraform روی سومین محیطی که بازسازی میکنید، بازدهی خواهد داشت.
چه چیزی در انتقال دچار اختلال میشود
سرور آماده نیست. Terraform با موفقیت اجرا میشود، اما Ansible بلافاصله شکست میخورد.
fatal: [web1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.10 port 22: Connection refused", "unreachable": true}API آدرسی را پیش از آنکه sshd آمادهٔ گوش دادن باشد، بازگردانده است. بهجای اضافه کردن یک sleep ثابت، منتظر باز شدن پورت بمانید. Ansible برای همین منظور ansible.builtin.wait_for_connection را دارد؛ آن را به عنوان اولین task در play اجرا کنید.
کلید میزبان (host key) تغییر کرده است. شما سرور را تخریب و دوباره ایجاد کردهاید و سرور جدید با کلیدی متفاوت روی همان آدرس پاسخ میدهد.
Host key verification failed.ورودی قدیمی را با ssh-keygen -R 203.0.113.10 حذف کنید. این اتفاق زمانی که Terraform مسئول بازسازی است بهطور مداوم رخ میدهد؛ این دلیل خوبی است که بازسازی ماشینهایی که داده ذخیره میکنند را به حداقل برسانید.
اجرای Sudo شکست میخورد. fatal: [web1]: FAILED! => {"msg": "Missing sudo password"} به این معنی است که become: true در آن میزبان نیاز به رمز عبور دارد. یا sudo بدون رمز عبور را برای کاربر deploy پیکربندی کنید، یا از --ask-become-pass استفاده نمایید.
Terraform میخواهد چیزی را که شما تغییر ندادهاید تخریب کند. طرح (plan) تغییراتی را نشان میدهد که شما ننوشتهاید؛ این یعنی زیرساخت واقعی از کد فاصله گرفته است (drift)، که معمولاً به دلیل تغییر تنظیمات در پنل وب ارائهدهنده توسط شخصی دیگر رخ میدهد. برای مشاهدهٔ تفاوتها بهصورت مجزا، terraform plan -refresh-only را اجرا کنید و سپس تصمیم بگیرید که آیا کد اشتباه است یا منبع زنده. هرگز طرحی مخرب را که نمیتوانید خطبهخط توضیح دهید، اعمال (apply) نکنید.
Ansible در هر اجرا گزارش تغییر (changed) میدهد. یک task از نوع shell که فاقد guard از نوع creates یا when باشد، بدون قید و شرط اجرا میشود. این فقط یک مشکل ظاهری نیست، زیرا به این معنی است که دیگر نمیتوانید از changed=0 به عنوان نشانهای برای اطمینان از اینکه سرور در وضعیت مطلوب شما قرار دارد، استفاده کنید.
FAQ
آیا Terraform میتواند جایگزین Ansible شود؟
خیر، برای پیکربندی داخل یک سرور خیر. Terraform میتواند اسکریپتها را با استفاده از provisioner نوع remote-exec فراخوانی کند، اما این اسکریپتها فقط در زمان ایجاد منبع اجرا میشوند، هرگز در terraform plan ظاهر نمیشوند و در صورت شکست، منبع را taint میکنند که باعث میشود در اجرای بعدی (apply)، دستور تخریب و بازسازی صادر شود. Terraform معادل ماژولی ندارد که بررسی کند آیا nginx قبلاً نصب شده است یا خیر و در صورت نصب بودن، هیچ کاری انجام ندهد. از Terraform برای ایجاد ماشین استفاده کنید و سپس کنترل را به Ansible بسپارید.
آیا Ansible میتواند جایگزین Terraform شود؟
برای تعداد کمی سرور با طول عمر بالا، بله. Ansible ماژولهای ابری دارد که سرورها را ایجاد میکنند و اگر دو نمونه VPS سفارش دهید و آنها را نگه دارید، همین کافی است. چیزی که از دست میدهید، فایل وضعیت (state file) و نمودار وابستگی است: اگر یک task را از playbook حذف کنید، منبع همچنان در حال اجرا باقی میماند و هزینه آن محاسبه میشود، زیرا Ansible هرگز ثبت نکرده است که آن را ایجاد کرده است. در حالی که Terraform برای آن یک عملیات تخریب (destroy) برنامهریزی میکرد.
کدامیک را باید ابتدا یاد بگیرم؟
اگر در حال حاضر سرورهایی دارید، Ansible. این ابزار از همان ماشین اول بازدهی دارد، به چیزی جز SSH نیاز ندارد و مهارت آن روی سروری که بهصورت دستی سفارش دادهاید نیز قابلاعمال است. Terraform در مراحل بعدی بازدهی دارد؛ زمانی که محیطها را بهطور مکرر بازسازی میکنید یا منابع ارائهدهنده (provider) فراتر از سرورها، مانند رکوردهای DNS و قوانین فایروال را مدیریت میکنید.
چگونه IP سرور جدید را از Terraform به Ansible منتقل کنم؟
یک output در کد Terraform خود تعریف کنید و سپس آن را پس از apply بخوانید. terraform output -raw web_ip مقدار خام را برای جایگزینی در shell چاپ میکند و terraform output -json در صورتی که چندین میزبان داشته باشید، تمام خروجیها را یکجا به شما میدهد. آن را در یک فایل inventory بنویسید یا مجموعه cloud.terraform را نصب کرده و ansible-inventory -i terraform.yml --graph را به دایرکتوری پروژه اشاره دهید.
چرا playbook من بلافاصله پس از پایان کار Terraform با شکست مواجه میشود؟
ارائهدهنده (provider) به محض اینکه API اعلام کند سرور ایجاد شده است، آن را بهعنوان ساختهشده گزارش میدهد، در حالی که سیستمعامل هنوز در حال بوت شدن است و به همین دلیل در ثانیههای اول اتصال SSH رد میشود. خطا UNREACHABLE! همراه با Connection refused است. بهجای حدس زدن مدت زمان sleep، ansible.builtin.wait_for_connection را به اولین task در play تبدیل کنید، زیرا زمان بوت بسته به image و پلن متفاوت است.