بلاگ/خودکارسازی راه‌اندازی اولیه سرور با Cloud-Init: کاربر، SSH، بسته‌ها و فایروال از همان بوت اول
به‌روز شده

خودکارسازی راه‌اندازی اولیه سرور با Cloud-Init: کاربر، SSH، بسته‌ها و فایروال از همان بوت اول

1405/06/215 بازدید
خودکارسازی راه‌اندازی اولیه سرور با Cloud-Init: کاربر، SSH، بسته‌ها و فایروال از همان بوت اول

خودکارسازی راه‌اندازی اولیه سرور با Cloud-Init: کاربر، SSH، بسته‌ها و فایروال از همان بوت اول

نمای کلی راه‌اندازی سرور با Cloud-Init بعد از تحویل یک سرور تازه، معمولاً چند کار تکراری در انتظار مدیر سیستم است: ساخت کاربر غیر روت، افزودن کلید SSH، به‌روزرسانی فهرست بسته‌ها، نصب ابزارهای ضروری و بستن پورت‌های اضافی. انجام دستی این مراحل برای یک سرور زمان زیادی نمی‌گیرد، اما وقتی تعداد ماشین‌ها بیشتر شود، تفاوت‌های کوچک میان تنظیمات به خطا و دردسر نگهداری منجر می‌شوند. Cloud-Init این فرایند را به یک فایل پیکربندی قابل تکرار تبدیل می‌کند. ارائه‌دهنده زیرساخت، فایل را هنگام ساخت ماشین در اختیار آن قرار می‌دهد و Cloud-Init در نخستین بوت تنظیمات خواسته‌شده را اجرا می‌کند. در نتیجه، سرور از همان ابتدا با کاربر، کلید SSH، بسته‌ها و قواعد فایروال مشخص آماده می‌شود. در این آموزش، یک نمونه عملی برای راه‌اندازی سرور با cloud-init می‌سازیم. مثال بر مبنای Ubuntu و فایروال UFW است، اما ساختار اصلی را می‌توان با تغییر نام بسته‌ها و فرمان‌ها برای توزیع‌های دیگر نیز به کار برد.

Cloud-Init چیست و چه زمانی اجرا می‌شود؟

Cloud-Init سرویسی برای مقداردهی اولیه ماشین‌های ابری و سرورهاست. ایمیج سیستم‌عامل در زمان بوت، داده‌های مرتبط با ماشین را از یک منبع داده یا DataSource می‌خواند. این داده‌ها می‌توانند شامل نام میزبان، اطلاعات شبکه، کلیدهای SSH و فایل user-data باشند. مراحل اجرای Cloud-Init به‌طور کلی شامل شناسایی منبع داده، اعمال تنظیمات اولیه، پیکربندی شبکه و اجرای ماژول‌های نهایی است. دستورهایی مانند نصب بسته‌ها و runcmd معمولاً در بخش پایانی اجرا می‌شوند؛ بنابراین ممکن است ورود SSH پیش از پایان همه کارها ممکن شود. Cloud-Init با ابزارهای مدیریت پیکربندی مانند Ansible یکسان نیست. وظیفه اصلی آن آماده‌سازی اولیه ماشین است، در حالی که Ansible، Salt یا Puppet برای مدیریت مداوم وضعیت سیستم مناسب‌تر هستند.

ابزارزمان استفادهکاربرد مناسبوضعیت نگهداری
Cloud-Initبوت اول ماشینکاربر، SSH، بسته‌های پایه و فایل‌های اولیهمعمولاً یک‌بار
اسکریپت Shellدستی یا از طریق ابزار دیگرکارهای ساده و ترتیبیوابسته به طراحی اسکریپت
Ansibleپس از دسترسی شبکه‌ایتنظیم سرویس‌ها و مدیریت چند سرورقابل اجرای مجدد
Terraformهنگام ساخت زیرساختایجاد ماشین، شبکه و منابع ابریمدیریت چرخه عمر زیرساخت

یک الگوی رایج این است که Terraform یا پنل ارائه‌دهنده، ماشین را ایجاد کند؛ Cloud-Init دسترسی امن و پیش‌نیازها را بسازد؛ سپس Ansible تنظیمات برنامه را ادامه دهد.

پیش‌نیازهای راه‌اندازی سرور با Cloud-Init

پیش از نوشتن فایل، این موارد را بررسی کنید: - ایمیج سیستم‌عامل باید Cloud-Init را نصب و فعال داشته باشد. ایمیج‌های ابری Ubuntu معمولاً این شرط را دارند. - بستر ساخت سرور باید راهی برای ارسال user-data ارائه کند. - کلید عمومی SSH خود را در اختیار داشته باشید. - نام رابط شبکه و سیاست فایروال را بشناسید. - پورت‌های لازم برای سرویس نهایی را از قبل مشخص کنید. - مطمئن شوید کنسول یا روش بازیابی خارج از SSH در دسترس است. کلید عمومی SSH را می‌توانید روی رایانه خود با فرمان زیر ببینید:

cat ~/.ssh/id_ed25519.pub

اگر هنوز کلید ندارید، یک جفت کلید Ed25519 بسازید:

ssh-keygen -t ed25519 -a 64

فقط محتوای فایل دارای پسوند .pub باید داخل Cloud-Init قرار گیرد. کلید خصوصی را روی سرور، پنل یا فایل user-data کپی نکنید.

ساخت فایل پایه Cloud-Init

فایل‌های Cloud-Init با خط #cloud-config شروع می‌شوند و از قالب YAML استفاده می‌کنند. فاصله‌گذاری در YAML بخشی از ساختار فایل است؛ استفاده نادرست از تورفتگی می‌تواند یک بخش کامل را از کار بیندازد. نمونه زیر کاربر deploy را می‌سازد، ورود با رمز عبور را غیرفعال می‌کند و کلید SSH را برای او قرار می‌دهد:

#cloud-config
hostname: app-server-01
manage_etc_hosts: true
users:
  - default
  - name: deploy
    gecos: Deployment User
    groups:
      - sudo
    shell: /bin/bash
    sudo:
      - ALL=(ALL) NOPASSWD:ALL
    lock_passwd: true
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleReplaceWithYourPublicKey admin@example
ssh_pwauth: false
disable_root: true

وجود - default باعث می‌شود کاربر پیش‌فرض تعریف‌شده در ایمیج، در صورت نیاز منبع داده، حفظ شود. کاربر deploy عضو گروه sudo است و می‌تواند فرمان‌های مدیریتی اجرا کند. گزینه lock_passwd: true ورود با رمز عبور را برای این حساب می‌بندد. ssh_pwauth: false نیز احراز هویت رمزی SSH را غیرفعال می‌کند. قبل از اعمال این تنظیمات، کلید عمومی را دقیق بررسی کنید؛ یک کلید ناقص یا اشتباه می‌تواند دسترسی SSH را از بین ببرد. دستور disable_root: true رفتار ورود مستقیم روت را مطابق سازوکار Cloud-Init محدود می‌کند. با این حال، تنظیم نهایی SSH به ایمیج و فایل‌های موجود در /etc/ssh/sshd_config.d/ نیز وابسته است. پس از بوت، نتیجه واقعی را روی همان توزیع بررسی کنید. برای سخت‌سازی بیشتر دسترسی SSH پس از این مرحله اولیه، این راهنما تنظیمات تکمیلی sshd_config و fail2ban را پوشش می‌دهد.

نصب بسته‌ها در بوت اول

Cloud-Init می‌تواند فهرست مخازن را به‌روزرسانی و بسته‌های پایه را نصب کند. برای Ubuntu یا Debian این بخش مناسب است:

package_update: true
package_upgrade: false
packages:
  - curl
  - ca-certificates
  - git
  - jq
  - unzip
  - ufw
  - fail2ban

فعال‌کردن package_upgrade همه بسته‌های قابل ارتقا را در بوت اول به‌روزرسانی می‌کند. این کار می‌تواند زمان آماده‌شدن سرور را افزایش دهد و گاهی به راه‌اندازی مجدد نیاز داشته باشد. برای سرورهایی که باید سریع تحویل شوند، معمولاً نصب بسته‌های مشخص و اجرای ارتقای کامل در پنجره نگهداری، قابل پیش‌بینی‌تر است. بسته‌های این فهرست باید در مخازن توزیع مقصد وجود داشته باشند. اگر فایل را برای چند توزیع استفاده می‌کنید، یک پیکربندی مشترک و کوچک نگه دارید یا برای هر خانواده سیستم‌عامل فایل جداگانه بسازید.

ایجاد فایل‌های تنظیمات با write_files

بخش write_files برای ساخت فایل‌های اولیه مناسب است. برای نمونه، می‌توان تنظیمات پایه Fail2ban را بدون اجرای چند فرمان Shell نوشت:

write_files:
  - path: /etc/fail2ban/jail.d/sshd.local
    owner: root:root
    permissions: "0644"
    content: |
      
      enabled = true
      maxretry = 5
      findtime = 10m
      bantime = 1h

مشخص‌کردن مالک و مجوز فایل مهم است، به‌خصوص زمانی که فایل شامل تنظیمات امنیتی یا داده حساس باشد. اطلاعات محرمانه بلندمدت مانند توکن API و کلید خصوصی را تا جای ممکن داخل user-data نگذارید. داده‌های Cloud-Init ممکن است در پنل زیرساخت، گزارش‌های سیستم یا فایل‌های محلی ماشین باقی بمانند. جریان اعمال تنظیمات کاربر، بسته‌ها و فایل‌ها در بوت اول

تنظیم فایروال بدون قطع‌کردن SSH

ترتیب قواعد فایروال اهمیت زیادی دارد. ابتدا باید پورت مدیریت را مجاز کنید و بعد سیاست ورودی را ببندید. اگر SSH روی پورت استاندارد ۲۲ اجرا می‌شود، تنظیم UFW می‌تواند به این شکل باشد:

runcmd:
  - [ufw, default, deny, incoming]
  - [ufw, default, allow, outgoing]
  - [ufw, limit, "22/tcp"]
  - [ufw, allow, "80/tcp"]
  - [ufw, allow, "443/tcp"]
  - [ufw, --force, enable]
  - [systemctl, enable, --now, fail2ban]

قاعده ufw limit 22/tcp اتصال SSH را مجاز می‌کند و برای تلاش‌های پرتکرار محدودیت ساده‌ای در نظر می‌گیرد. پورت‌های ۸۰ و ۴۴۳ برای وب‌سرور باز شده‌اند. اگر ماشین فقط یک سرویس داخلی اجرا می‌کند، این دو قاعده را حذف کنید. در صورت استفاده از پورت سفارشی SSH، عدد ۲۲ را با پورت واقعی جایگزین کنید. تغییر پورت SSH و فعال‌سازی فایروال در یک بوت نیازمند دقت بیشتری است: ابتدا پیکربندی SSH را بنویسید، صحت آن را آزمایش کنید، سرویس را بارگذاری مجدد کنید و همان پورت را در فایروال باز نگه دارید. همچنین ممکن است ارائه‌دهنده یک فایروال شبکه‌ای خارج از ماشین داشته باشد. UFW جایگزین آن نیست؛ یکی ترافیک را پیش از رسیدن به سرور فیلتر می‌کند و دیگری روی خود سیستم‌عامل اعمال می‌شود. قواعد این دو لایه نباید با هم تعارض داشته باشند.

نمونه کامل برای Ubuntu

نمونه زیر بخش‌های قبلی را در یک فایل جمع می‌کند. کلید SSH، نام میزبان و پورت‌های سرویس را پیش از استفاده تغییر دهید:

#cloud-config
hostname: app-server-01
manage_etc_hosts: true
users:
  - default
  - name: deploy
    gecos: Deployment User
    groups:
      - sudo
    shell: /bin/bash
    sudo:
      - ALL=(ALL) NOPASSWD:ALL
    lock_passwd: true
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleReplaceWithYourPublicKey admin@example
ssh_pwauth: false
disable_root: true
package_update: true
package_upgrade: false
packages:
  - curl
  - ca-certificates
  - git
  - jq
  - unzip
  - ufw
  - fail2ban
write_files:
  - path: /etc/fail2ban/jail.d/sshd.local
    owner: root:root
    permissions: "0644"
    content: |
      
      enabled = true
      maxretry = 5
      findtime = 10m
      bantime = 1h
runcmd:
  - [ufw, default, deny, incoming]
  - [ufw, default, allow, outgoing]
  - [ufw, limit, "22/tcp"]
  - [ufw, allow, "80/tcp"]
  - [ufw, allow, "443/tcp"]
  - [ufw, --force, enable]
  - [systemctl, enable, --now, fail2ban]
  - [sh, -c, "echo 'Cloud-Init completed at '$(date -Is) > /var/log/bootstrap-complete.log"]
final_message: "Cloud-Init finished after $UPTIME seconds"

در runcmd استفاده از قالب فهرستی، مشکل نقل‌قول و تفسیر آرگومان‌ها را برای فرمان‌های ساده کمتر می‌کند. برای فرمانی که به قابلیت‌های Shell مانند تغییر مسیر خروجی یا جای‌گذاری فرمان نیاز دارد، اجرای صریح sh -c لازم است.

اعتبارسنجی فایل قبل از ساخت سرور

خطاهای YAML بهتر است پیش از تحویل فایل به سرور پیدا شوند. اگر Cloud-Init روی یک سیستم آزمایشی نصب است، فایل را با ابزار داخلی آن بررسی کنید:

cloud-init schema --config-file cloud-config.yaml

این بررسی مشکلات نحوی و تعدادی از خطاهای مربوط به کلیدهای پیکربندی را گزارش می‌کند. موفقیت در اعتبارسنجی به این معنا نیست که همه بسته‌ها، فرمان‌ها و سرویس‌ها روی ایمیج مقصد وجود دارند؛ یک ماشین آزمایشی همچنان لازم است. برای پیکربندی‌های تولیدی، فایل را در مخزن نسخه‌گذاری کنید و متغیرهایی مانند نام میزبان، کلید عمومی و نقش سرور را در مرحله ساخت جایگزین کنید. اطلاعات محرمانه نباید وارد تاریخچه Git شوند.

بررسی نتیجه پس از بوت

پس از اتصال به سرور، ابتدا وضعیت Cloud-Init را ببینید:

sudo cloud-init status --wait

اگر اجرا موفق باشد، وضعیت نهایی معمولاً done است. سپس این موارد را بررسی کنید:

id deploy
sudo ufw status verbose
sudo systemctl status fail2ban --no-pager
sudo cloud-init query userdata

گزارش‌های اصلی نیز در مسیرهای زیر قرار دارند:

/var/log/cloud-init.log
/var/log/cloud-init-output.log

فایل اول جزئیات مراحل و ماژول‌ها را ثبت می‌کند. فایل دوم خروجی فرمان‌ها و نصب بسته‌ها را نشان می‌دهد و برای پیدا‌کردن خطای runcmd مفید است.

چرا تغییر فایل و ریبوت کافی نیست؟

بیشتر ماژول‌های Cloud-Init بر اساس تناوب مشخص، مانند یک‌بار برای هر Instance، اجرا می‌شوند. ویرایش user-data داخل ماشین و ریبوت معمولاً کل فرایند بوت اول را تکرار نمی‌کند. برای آزمایش مجدد، امن‌ترین روش ساخت یک ماشین تازه است؛ چون شرایط واقعی بوت اول را بازتولید می‌کند. دستور cloud-init clean می‌تواند وضعیت Cloud-Init را پاک کند، اما اجرای آن روی سرور فعال ممکن است بخش‌هایی از مقداردهی اولیه را دوباره اعمال کند. از آن فقط در ماشین آزمایشی و با آگاهی از نتیجه استفاده کنید.

خطاهای رایج

یکی از خطاهای متداول، استفاده از Tab یا تورفتگی ناهماهنگ در YAML است. فایل ممکن است ظاهراً خوانا باشد، اما Cloud-Init بخش موردنظر را نادیده بگیرد یا اجرای آن را متوقف کند. خطای دیگر، بستن ورود رمزی قبل از اطمینان از صحت کلید SSH است. همیشه اتصال را با همان کلید روی یک ماشین آزمایشی بررسی کنید و دسترسی کنسول بازیابی را نگه دارید. قرار دادن تمام منطق در یک runcmd طولانی نیز عیب‌یابی را دشوار می‌کند. تنظیمات ساختاریافته را با users، packages و write_files انجام دهید و runcmd را برای فعال‌سازی سرویس‌ها یا فرمان‌های پایانی نگه دارید. Cloud-Init نیز نباید به‌عنوان محل ذخیره رمزها در نظر گرفته شود. برای اطلاعات حساس، از مدیر اسرار، هویت ماشین یا توکن‌های کوتاه‌عمر استفاده کنید.

از پیکربندی نمونه تا سرور آماده

راه‌اندازی سرور با cloud-init زمانی بیشترین فایده را دارد که فایل پیکربندی مانند کد نگهداری شود: تغییرات آن بازبینی شوند، روی یک ماشین تازه آزمایش شود و نسخه مشخص هر فایل برای هر نقش سرور وجود داشته باشد. به این ترتیب، سرور وب، ماشین CI یا گره مانیتورینگ می‌تواند در هر بار ساخت، نقطه شروع یکسانی داشته باشد. برای اجرای این پیکربندی روی یک ماشین تازه، می‌توانید هنگام سفارش سرور ابری آریانت فایل user-data را متناسب با سرویس خود آماده کنید. پیش از استفاده در محیط تولید، کلید SSH، پورت‌ها و بسته‌های نمونه را با نیاز واقعی سرور جایگزین کنید. Cloud-Init کارهای بوت اول را تکرارپذیر می‌کند، اما پایان مدیریت سرور نیست. پس از آماده‌شدن ماشین، وصله‌های امنیتی، نسخه پشتیبان، مانیتورینگ و مدیریت مداوم تنظیمات همچنان باید در فرایند عملیاتی شما قرار داشته باشند.