SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

رفع مشکل تکراری بودن machine-id در VPS کلون شده

اگر دو سرور با یک /etc/machine-id در شبکه باشند با تداخل DHCP مواجه می‌شوید. با اجرای 4 دستور ساده این فایل را بازسازی کنید و قبل از گرفتن snapshot آن را خالی کنید.

فایل /etc/machine-id چیست و چرا تکراری بودن آن اهمیت دارد

یک VPS کلون‌شده با همان /etc/machine-id سروری که از آن کپی شده است بوت می‌شود، در حالی که این مقدار باید منحصراً متعلق به یک نصب باشد. برای رفع این مشکل باید چهار دستور را اجرا کنید: محتویات فایل را خالی کنید، نسخه کپی D-Bus را در صورتی که یک فایل واقعی است حذف کنید، آن را دوباره تولید کنید و سیستم را ریبوت کنید. ریبوت کردن مرحله‌ای است که افراد معمولاً از آن صرف‌نظر می‌کنند، در حالی که همین مرحله باعث اعمال تغییرات می‌شود.

فایل /etc/machine-id شامل یک رشته هگزادسیمال 32 کاراکتری با حروف کوچک است که با یک کاراکتر خط جدید (newline) پایان می‌یابد. این مقدار در حالت رمزگشایی‌شده، یک مقدار 16 بایتی (128 بیتی) است. صفحه راهنمای machine-id(5) این مقدار را محرمانه می‌داند و تأکید می‌کند که نباید در شبکه افشا شود، زیرا هر موجودیتی که آن را بخواند می‌تواند در مراجعات بعدی، ماشین شما را شناسایی کند. این فایل یک‌بار در زمان نصب سیستم نوشته می‌شود و پس از آن هیچ‌چیز آن را تغییر نمی‌دهد.

سه شناسه در اینجا با هم اشتباه گرفته می‌شوند که تفکیک آن‌ها ضروری است. Hostname برچسبی است که شما انتخاب می‌کنید و هر زمان که بخواهید می‌توانید آن را تغییر دهید. شناسه محصول DMI (رابط مدیریت دسکتاپ) در /sys/class/dmi/id/product_uuid از سمت هایپروایزر می‌آید و فقط توسط root قابل خواندن است. Machine ID شناسه سوم است: سیستم‌عامل آن را تولید می‌کند و تمام کاربران روی سیستم می‌توانند آن را بخوانند.

چه چیزی در واقع machine ID را می‌خواند

شناسه کلاینت DHCP. این موردی است که مشکل‌ساز می‌شود. systemd.network(5) در بخش [DHCPv4] مستند می‌کند که ClientIdentifier= به‌طور پیش‌فرض از duid استفاده می‌کند؛ این تنظیم یک شناسه کلاینت RFC 4361 می‌فرستد که از IAID و DUID (شناسه منحصربه‌فرد DHCP) ساخته شده است. networkd.conf(5) نوع پیش‌فرض DUID را vendor تعیین می‌کند که در آن مقدار DUID با استفاده از 43793 به‌عنوان شناسه فروشنده (systemd) و محتوای هش‌شدهٔ machine ID تولید می‌شود. DHCPv6 نیز از همین DUID استفاده می‌کند. دو کلون با machine ID یکسان، DUID یکسانی تولید می‌کنند و اگر نام اینترفیس آن‌ها نیز یکسان باقی بماند، شناسه کلاینتی کاملاً مشابه ارسال می‌کنند. در نتیجه، سرور DHCP به‌جای دو کلاینت، یکی را می‌بیند و به هر دو دستگاه یک lease یکسان ارائه می‌دهد. نشانه این وضعیت، آدرسی است که بین دو سرور جابه‌جا می‌شود یا یکی از سرورها هر بار که دیگری آدرس خود را تمدید می‌کند، آدرسش را از دست می‌دهد.

journald. فایل‌های ژورنال در /var/log/journal/<machine-id>/ قرار دارند. نام این دایرکتوری دقیقاً بر اساس همان ID است. اگر ژورنال‌های دو کلون را به یک جمع‌کننده (collector) ارسال کنید، هر دو در یک دایرکتوری قرار گرفته و به‌عنوان یک میزبان واحد خوانده می‌شوند.

D-Bus. /var/lib/dbus/machine-id جایی است که این فرمت فایل از آنجا شروع شد. در Debian و Ubuntu، این یک symlink به /etc/machine-id است. در برخی سیستم‌ها، این یک فایل واقعی و مجزا است که کپی مخصوص خود را نگه می‌دارد و همین کپی، تله‌ای در پروسه زیر است.

ایجنت‌های مختص هر میزبان. ایجنت‌های مانیتورینگ، بررسی‌های لایسنس، ابزارهای موجودی و کلاینت‌های پشتیبان‌گیری اغلب از machine ID به‌عنوان شناسه پیش‌فرض میزبان استفاده می‌کنند، زیرا ثابت است و نیازی به پیکربندی ندارد. دو سرور که یک هویت را گزارش می‌کنند، به معنای ادغام سری‌های متریک یا استفاده از یک لایسنس برای دو ماشین است. بررسی کنید که ایجنت شما چگونه host ID خود را استخراج می‌کند؛ فرض نکنید که حتماً از hostname استفاده می‌کند.

چگونه متوجه شویم که نسخه تکراری داریم

این دستور را روی هر دو سرور اجرا کرده و خروجی آن‌ها را با هم مقایسه کنید.

cat /etc/machine-id
ls -l /var/lib/dbus/machine-id
sudo cat /sys/class/dmi/id/product_uuid

cat /etc/machine-id

یکسان بودن machine ID در دو سرور فعال به این معناست که یکی از آن‌ها از روی دیگری کلون شده است. اگر ترجیح می‌دهید از یک دستور استفاده کنید، hostnamectl مقدار مشابه را در خط Machine ID: خود چاپ می‌کند.

نتیجه ls -l گام بعدی را تعیین می‌کند. یک symlink به این شکل دیده می‌شود:

lrwxrwxrwx 1 root root 15 Aug 21 09:12 /var/lib/dbus/machine-id -> /etc/machine-id

lrwxrwxrwx 1 root root 33 ... /etc/machine-id -> /var/lib/dbus/machine-id

خطی که با -rw-r--r-- شروع می‌شود به این معناست که این یک فایل واقعی است که کپی مخصوص به خود از ID قدیمی را نگه می‌دارد. شما باید آن را حذف کنید، زیرا systemd-machine-id-setup پیش از انجام هر کار دیگری آن را می‌خواند.

UUID محصول نیز اهمیت دارد. systemd-machine-id-setup(1) پیش از آنکه به سراغ تولید تصادفی برود، از KVM UUID استفاده می‌کند؛ بنابراین اگر ارائه‌دهنده خدمات شما به هر دو کلون، SMBIOS (سیستم مدیریت BIOS) UUID یکسانی داده باشد، بازتولید آن، دو بار همان machine ID را به شما می‌دهد. متفاوت بودن product UUID در دو دستگاه به این معناست که در این مورد جای نگرانی وجود ندارد.

بازسازی شناسه ماشین (machine ID) در یک VPS کلون‌شده

ترتیب مراحل اهمیت دارد. systemd-machine-id-setup(1) بیان می‌کند که اگر یک شناسه ماشین D-Bus معتبر از قبل برای سیستم پیکربندی شده باشد، همان شناسه کپی شده و برای مقداردهی اولیه /etc/machine-id استفاده می‌شود. اگر یک /var/lib/dbus/machine-id واقعی را در جای خود باقی بگذارید، همان مقداری را بازسازی خواهید کرد که قصد حذف آن را داشتید.

sudo truncate -s 0 /etc/machine-id
sudo rm -f /var/lib/dbus/machine-id       # only if ls -l showed a real file
sudo systemd-machine-id-setup
sudo ln -sf /etc/machine-id /var/lib/dbus/machine-id
cat /etc/machine-id

کوتاه کردن (Truncate) فایل در ابتدا الزامی است، زیرا این ابزار تنها زمانی عمل می‌کند که فایل موجود نباشد یا خالی باشد و روی فایلی که از قبل دارای یک شناسه معتبر است، هیچ تغییری اعمال نمی‌کند. systemd-machine-id-setup خروجی عملیات خود را در standard error گزارش می‌دهد. در یک KVM VPS معمولاً خروجی زیر را مشاهده خواهید کرد:

Initializing machine ID from KVM UUID.

Initializing machine ID from random generator. پیامی است که هنگام عدم دسترسی به UUID هایپروایزر نمایش داده می‌شود. هر دو نتیجه قابل‌قبول هستند، به شرطی که cat /etc/machine-id اکنون مقداری متفاوت از سرور دیگر چاپ کند.

لینک نمادین (symlink)، مقادیر D-Bus و systemd را یکسان نگه می‌دارد. اگر ترجیح می‌دهید یک فایل واقعی و مجزا داشته باشید، به جای آن دستور sudo dbus-uuidgen --ensure را اجرا کنید: این دستور در صورت عدم وجود فایل، آن را با یک UUID جدید ایجاد می‌کند. اگر dbus نصب نشده باشد، دایرکتوری /var/lib/dbus اصلاً وجود ندارد، ln با خطای No such file or directory مواجه می‌شود و می‌توانید از هر دوی آن خطوط صرف‌نظر کنید.

سپس سیستم را reboot کنید.

sudo reboot

چرا راه‌اندازی مجدد (reboot) اختیاری نیست

هر فرآیندی که مقدار قدیمی را خوانده باشد، همچنان از آن استفاده می‌کند. sd_id128_get_machine() شناسه را در حافظهٔ فرآیند فراخوان کش می‌کند، بنابراین یک دیمون در حال اجرا هرگز متوجه تغییر فایل نمی‌شود. journald از قبل /var/log/journal/<old-id>/system.journal را باز نگه داشته و به نوشتن در آن ادامه می‌دهد. سرویس systemd-networkd شناسه DUID خود را در زمان شروع کار محاسبه کرده و در هر تمدید، همان شناسه قدیمی کلاینت را ارسال می‌کند؛ این دقیقاً همان خطایی است که قصد رفع آن را دارید. D-Bus نیز شناسه خود را در زمان شروع به کار خوانده است. شما می‌توانید سرویس‌ها را یکی‌یکی restart کنید، اما حتماً یکی را از قلم می‌اندازید و PID 1 نیز همچنان مقدار قدیمی را در اختیار دارد.

پس از راه‌اندازی مجدد، هر دو بخش را بررسی کنید:

cat /etc/machine-id
ls /var/log/journal/

/var/log/journal/ اکنون شامل دایرکتوری دومی است که با شناسه جدید نام‌گذاری شده و ورودی‌های جدید در آنجا ذخیره می‌شوند. دستور ساده journalctl فقط دایرکتوری ماشین فعلی را می‌خواند، بنابراین تاریخچه پیش از clone در نمای پیش‌فرض ناپدید می‌شود. این داده‌ها همچنان روی دیسک موجود هستند: journalctl --merge تمام دایرکتوری‌های journal، از جمله دایرکتوری قدیمی را می‌خواند. پس از اطمینان از اینکه دیگر به آن لاگ‌ها نیازی ندارید، دایرکتوری قدیمی را حذف کنید.

به همین دلیل است که نمی‌توانید این رویه را در یک container تمرین کنید. یک container از هسته (kernel) میزبان استفاده می‌کند و هرگز PID 1 اختصاصی خود را بوت نمی‌کند، در حالی که راه‌اندازی مجدد، هدف اصلی این عملیات است. این کار را دقیقاً همان‌طور که در محیط عملیاتی (production) انجام می‌شود تست کنید: یک VM را clone کنید، دستورات را اجرا کنید، سیستم را reboot کنید و سپس شناسه را با ماشین منبع مقایسه کنید.

پیش از گرفتن snapshot فایل را خالی کنید، نه پس از clone کردن

اصلاح cloneها به‌صورت تک‌به‌تک کارساز است، اما اصلاح image اصلی روش بهتری است؛ چرا که هر سروری که از یک snapshot معیوب بازیابی شود، همان مقدار را به ارث می‌برد. این کار را به آخرین مرحله پیش از خاموش کردن template تبدیل کنید.

sudo truncate -s 0 /etc/machine-id
sudo rm -f /var/lib/dbus/machine-id
sudo ln -s /etc/machine-id /var/lib/dbus/machine-id
sudo shutdown -h now

محتوای فایل را خالی کنید. آن را حذف نکنید. machine-id(5) برای imageهایی که روی چندین ماشین استفاده می‌شوند، یک فایل خالی را پیشنهاد می‌کند؛ زیرا وجود یک فایل خالی در مسیر اصلی، امکان bind-mount کردن یک فایل موقت روی فایل واقعی را هنگام استفاده از image به‌صورت read-only فراهم می‌کند. در یک /etc که read-only است، ID تولیدشده در زمان boot در آن فایل موقت قرار می‌گیرد و systemd-machine-id-setup --commit پس از آنکه فایل‌سیستم قابل‌نوشتن شد، آن را ثبت می‌کند.

یک اثر جانبی که باید برای آن برنامه‌ریزی کنید این است که یک machine ID خالی، boot بعدی را به‌عنوان اولین boot علامت‌گذاری می‌کند؛ بنابراین unitهایی که دارای ConditionFirstBoot=yes هستند در آن boot اجرا شده و در bootهای بعدی نادیده گرفته می‌شوند. پیش از ساخت template، بررسی کنید که image شما با grep -rl ConditionFirstBoot /usr/lib/systemd/system/ چه مواردی را اجرا خواهد کرد.

یک template و یک snapshot دو موجودیت متفاوت هستند و همین تفاوت تعیین می‌کند که آیا هویت (identity) کپی می‌شود یا خیر. template یک artifact ساخت است که شما عمداً آماده می‌کنید، در حالی که یک snapshot کپی لحظه‌ای از یک سرور در حال اجرا است و هویت آن سرور را به همراه داده‌هایش حمل می‌کند.

چرا ایمیج‌های ابری این مورد را درست انجام می‌دهند اما snapshot شما خیر

ایمیج‌های ابری توزیع‌های مختلف به‌گونه‌ای ساخته شده‌اند که قابلیت کلون شدن داشته باشند؛ بنابراین آن‌ها بدون machine ID مقداردهی‌شده عرضه می‌شوند و در اولین بوت، این مقدار پر می‌شود. ابزار cloud-init یک مرحلهٔ مستندشده دقیقاً برای همین کار دارد. cloud-init clean --machine-id مقدار /etc/machine-id را روی سیستم‌های مبتنی بر systemd به رشتهٔ متنی uninitialized تنظیم می‌کند و مستندات CLI مربوط به cloud-init، این کار را به‌عنوان بهترین روش (best practice) هنگام کلون کردن یک golden image توصیه می‌کند تا در بوت بعدیِ آن ایمیج، یک machine ID منحصربه‌فرد تولید شود.

snapshotای که خودتان تهیه کرده‌اید، داستان متفاوتی دارد. آن فایل در لحظه‌ای که روی دکمهٔ snapshot کلیک کردید، از قبل مقداردهی شده بود؛ بنابراین تمام سرورهایی که از آن بازیابی (restore) می‌شوند، همان مقدار واحد را حمل می‌کنند و هیچ بخشی در مسیر بازیابی، آن را پاک نمی‌کند. این مشکل دقیقاً از همان دسته‌بندی انتقال یک سرور در حال اجرا به یک VPS جدید است؛ جایی که کپی به‌صورت دقیق انجام می‌شود و هویت سرور، همان بخشی است که شما نمی‌خواستید کپی شود.

موارد دیگری که در کپی (clone) تکثیر می‌شوند

  • کلیدهای میزبان SSH. فایل /etc/ssh/ssh_host_* نیز کپی می‌شود، بنابراین هر دو سرور اثر انگشت (fingerprint) یکسانی را به کلاینت‌ها ارائه می‌دهند. این فایل‌ها را حذف کنید و دستور sudo ssh-keygen -A را اجرا کنید، یا در توزیع‌های Debian و Ubuntu از sudo dpkg-reconfigure openssh-server استفاده کنید. پس از آن، کلاینت‌های شما در مورد تغییر کلید میزبان هشدار خواهند داد که این رفتار صحیح است.
  • نام میزبان (hostname). آن را با sudo hostnamectl set-hostname app02 تنظیم کنید، سپس بررسی کنید که /etc/hosts همچنان نام جدید را به درستی resolve می‌کند.
  • پیکربندی شبکه ایستا. کپیِ سیستمی که دارای آدرس IP ایستا است، به محض اتصال به شبکه با سیستم اصلی تداخل پیدا می‌کند. پیش از آنکه کپی به شبکه متصل شود، /etc/netplan/ را مطالعه کنید.
  • ساعت سیستم. اسنپ‌شات بازیابی‌شده با همان زمانی که در لحظه گرفتن اسنپ‌شات داشته است، شروع به کار می‌کند. پرش زمانی بزرگ در یک VPS بازیابی‌شده باعث اختلال در اعتبارسنجی گواهی‌های TLS و به‌هم‌ریختگی ترتیب لاگ‌ها می‌شود تا زمانی که همگام‌سازی زمان انجام شود.

بر روی کپی جدید، چک‌لیست ده دقیقه اول برای یک VPS جدید را نیز اجرا کنید. یک سیستم کپی‌شده، حساب‌های کاربری، کلیدهای SSH، قوانین فایروال و وظایف زمان‌بندی‌شده (cron jobs) سیستم منبع را به ارث می‌برد، در حالی که هیچ‌کدام از این موارد برای وظیفه‌ای که کپی قرار است انجام دهد، بازبینی نشده‌اند.

FAQ

آیا پس از تغییر /etc/machine-id باید سیستم را reboot کنم؟

بله. پردازش‌ها شناسه ماشین را یک‌بار می‌خوانند و آن را در حافظه کش می‌کنند، بنابراین مقدار جدید به هیچ‌یک از پردازش‌های در حال اجرا نمی‌رسد. journald به نوشتن در دایرکتوری ژورنالی که با شناسه قدیمی نام‌گذاری شده ادامه می‌دهد و کلاینت DHCP نیز همچنان یک شناسه کلاینت مشتق‌شده از مقدار قدیمی ارسال می‌کند؛ که معمولاً همان دلیلی است که شما آن را تغییر داده‌اید. راه‌اندازی مجدد سرویس‌های تکی برخی از آن‌ها را اصلاح می‌کند، اما PID 1 نیز مقدار قدیمی را در خود نگه می‌دارد. سیستم را reboot کنید، سپس با cat /etc/machine-id و مقایسه با سرور دیگر، آن را تأیید کنید.

آیا /etc/machine-id همان UUID سخت‌افزاری است؟

خیر. DMI product UUID در /sys/class/dmi/id/product_uuid از هایپروایزر می‌آید و فقط توسط root قابل خواندن است. شناسه ماشین توسط سیستم‌عامل تولید می‌شود و در یک فایل متنی ساده قرار دارد که هر کاربری می‌تواند آن را بخواند. این دو از یک جهت به هم متصل هستند: در یک KVM guest، اگر D-Bus ID برای کپی کردن وجود نداشته باشد، systemd-machine-id-setup یک شناسه ماشین جدید از روی UUID هایپروایزر ایجاد می‌کند. اگر دو کلون دارای product UUID یکسانی باشند، شناسه ماشین یکسانی تولید خواهند کرد، بنابراین پیش از اعتماد به نتیجه، آن فایل را نیز مقایسه کنید.

آیا باید /etc/machine-id را حذف کنم یا آن را خالی بگذارم؟

هنگام آماده‌سازی یک image، آن را خالی بگذارید. machine-id(5) فایل خالی را ترجیح می‌دهد، زیرا وقتی image با یک /etc فقط‌خواندنی اجرا می‌شود، systemd می‌تواند یک فایل موقت را روی آن bind-mount کند. حذف فایل در یک سیستم با قابلیت نوشتن کار می‌کند و برخی اسکریپت‌های کلون‌سازی از این روش استفاده می‌کنند، اما فایل خالی گزینه پیش‌فرض امن‌تری است. cloud-init برای همین منظور کلمه uninitialized را در فایل می‌نویسد.

چرا دو سرور کلون‌شده من آدرس DHCP یکسانی دریافت کردند؟

زیرا هر دو شناسه کلاینت یکسانی ارسال کردند. مقدار پیش‌فرض systemd-networkd برای DHCPv4 برابر با ClientIdentifier=duid است و DUID پیش‌فرض از هش /etc/machine-id ساخته می‌شود، بنابراین شناسه‌های ماشین یکسان در کلون‌هایی که نام اینترفیس مشابهی دارند، شناسه‌های یکسانی تولید می‌کنند. سرور DHCP بر اساس آن شناسه تطبیق انجام می‌دهد، هر دو درخواست را به عنوان یک کلاینت در نظر می‌گیرد و یک lease ارائه می‌دهد. به هر دستگاه یک شناسه ماشین اختصاصی بدهید و هر دو را reboot کنید. اگر سرور همچنان آدرس قدیمی را پیشنهاد می‌دهد، lease قدیمی را در خود سرور DHCP پاک کنید.

#machine-id#systemd#cloning#snapshots#dhcp