تفاوت Ansible و Terraform؛ کدام را انتخاب کنیم؟
تفاوت اصلی در مدیریت وضعیت است. Terraform زیرساخت را ایجاد میکند و Ansible آن را پیکربندی مینماید. در این راهنما یاد میگیرید چرا نباید وظایف این دو را ترکیب کنید.
تفاوت Ansible و Terraform در یک جمله
انتخاب بین Ansible و Terraform، انتخاب بین دو ابزار با وظیفه یکسان نیست. Terraform وضعیت زیرساخت موجود را تعریف میکند: سرورها، دیسکها، شبکهها و رکوردهای DNS. در مقابل، Ansible وضعیت داخلی یک ماشینِ از پیش موجود را تعیین میکند: بستهها، کاربران، فایلهای پیکربندی و سرویسهای در حال اجرا. Terraform یک VPS ایجاد میکند و Ansible آن VPS را به یک وبسرور تبدیل مینماید.
هر دو ابزار اعلانی (declarative) هستند و هر دو در دسته زیرساخت به عنوان کد (IaC) قرار میگیرند. تفاوت اصلی در نحوه نگهداری وضعیت است. Terraform یک فایل state مینویسد که هر منبع در کد شما را به یک شیء واقعی که از طریق 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 دیگر نمیداند که آن سرورها متعلق به شما هستند، بنابراین اجرای بعدی سعی میکند نسخههای تکراری ایجاد کند. بهمحض اینکه بیش از یک نفر دستورات را اجرا میکند، آن را در یک backend راه دور نگهداری کنید، زیرا اجرای همزمان توسط دو نفر منجر به این وضعیت میشود:
Error: Error acquiring the state lockOpenTofu یک 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 به یک طرح کلی (plan) است: این دستور گزارش میدهد که چه تغییراتی اعمال خواهد شد بدون آنکه واقعاً تغییری ایجاد کند؛ هرچند تسکهایی که به تسکهای قبلی وابستهاند ممکن است در حالت 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 مالکیت هر چیزی که داخل یک سیستمعاملِ بوتشده قرار دارد را بر عهده بگیرد.
تحویل کار، با موفقیت
تحویل کار (handoff) یک مرز است، نه یکپارچهسازی. 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 اجرا میکند، بنابراین آن دایرکتوری باید از قبل initialized شده باشد، در غیر این صورت پلاگین با خطا مواجه میشود. همچنین، این روش میزبانها را از منابع سرور شما ابداع نمیکند: بلکه منابع 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 را دارد که باید به عنوان اولین تسک در play اجرا شود. هنگامی که یک playbook به جای یک سرور تازه، گروهی از میزبانها را هدف قرار میدهد، از قبل تصمیم بگیرید که در صورت غیرقابل دسترس شدن یک میزبان چه اتفاقی باید بیفتد، زیرا Ansible آن میزبان را از ادامه اجرا حذف میکند و تنها در خط خلاصه پایان کار (recap) این موضوع را به شما اطلاع میدهد.
کلید میزبان (host key) تغییر کرده است. شما سرور را تخریب و دوباره ایجاد کردهاید و سرور جدید با کلیدی متفاوت به همان آدرس پاسخ میدهد.
Host key verification failed.ورودی قدیمی را با ssh-keygen -R 203.0.113.10 حذف کنید. این اتفاق زمانی که Terraform عملیات بازسازی (rebuild) را انجام میدهد به طور مداوم رخ میدهد؛ این دلیل خوبی است که بازسازی ماشینهایی که دادههای مهم دارند را به حداقل برسانید.
اجرای 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) میدهد. یک تسک shell بدون محافظ creates یا when، بدون قید و شرط اجرا میشود. این فقط یک مشکل ظاهری نیست، زیرا به این معنی است که دیگر نمیتوانید از changed=0 به عنوان نشانهای برای اطمینان از اینکه سرور در وضعیت مطلوب شما قرار دارد، استفاده کنید.
FAQ
آیا Terraform میتواند جایگزین Ansible شود؟
خیر، برای پیکربندی داخل یک سرور مناسب نیست. Terraform میتواند اسکریپتها را با استفاده از provisioner در remote-exec فراخوانی کند، اما این اسکریپتها فقط در زمان ایجاد منبع اجرا میشوند، هرگز در terraform plan ظاهر نمیشوند و در صورت شکست، منبع را آلوده (taint) میکنند که منجر به برنامهریزی برای تخریب و بازسازی در اجرای بعدی میشود. Terraform معادل ماژولی که بررسی کند آیا nginx از قبل نصب شده است یا خیر و در صورت نصب بودن کاری انجام ندهد، ندارد. از Terraform برای ایجاد ماشین استفاده کنید و سپس کار را به Ansible بسپارید.
آیا Ansible میتواند جایگزین Terraform شود؟
برای تعداد کمی سرور با طول عمر بالا، بله. Ansible ماژولهای ابری برای ایجاد سرور دارد و اگر دو نمونه VPS سفارش دهید و آنها را نگه دارید، کافی است. آنچه از دست میدهید، فایل وضعیت (state file) و نمودار وابستگی است: اگر وظیفهای را از playbook حذف کنید، منبع همچنان در حال اجرا باقی میماند و هزینه آن محاسبه میشود، زیرا Ansible هرگز ثبت نکرده است که آن را ایجاد کرده است. Terraform در این حالت برای تخریب آن برنامهریزی میکرد.
کدامیک را باید ابتدا یاد بگیرم؟
اگر در حال حاضر سرور دارید، Ansible را یاد بگیرید. این ابزار از همان ماشین اول بازدهی دارد، به چیزی جز SSH نیاز ندارد و مهارت آن برای سروری که بهصورت دستی سفارش دادهاید نیز کاربرد دارد. Terraform زمانی بازدهی دارد که محیطها را بهطور مکرر بازسازی کنید یا منابع ارائهدهنده فراتر از سرورها، مانند رکوردهای 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 با شکست مواجه میشود؟
ارائهدهنده به محض اینکه API اعلام کند سرور ایجاد شده است، وضعیت آن را «ایجاد شده» گزارش میدهد، در حالی که سیستمعامل هنوز در حال بوت شدن است؛ بنابراین در ثانیههای اول اتصال SSH رد میشود. خطا UNREACHABLE! همراه با Connection refused است. بهجای حدس زدن مدت زمان انتظار (sleep)، ansible.builtin.wait_for_connection را به اولین وظیفه در play تبدیل کنید، زیرا زمان بوت بسته به image و plan متفاوت است.