SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor

إصلاح تكرار machine-id بعد استنساخ VPS

تظهر المشكلة عندما يتشارك خادمان الملف /etc/machine-id نفسه ويتنافسان على عقدة DHCP. تعرّف إلى الخطأ، وجدّد المعرّف بأمان، وفرّغه قبل أخذ النسخة الذهبية.

ما هو /etc/machine-id ولماذا يمثل تكراره مشكلة

يُقلِع VPS مستنسخ بالقيمة نفسها في /etc/machine-id الموجودة على الخادم الذي استُنسخ منه، مع أن هذه القيمة يُفترض أن تخص تثبيتاً واحداً فقط. يتطلب الإصلاح تنفيذ أربعة أوامر: إفراغ الملف، وإزالة نسخة D-Bus إذا كان الملف حقيقياً، وإعادة إنشاء القيمة، ثم إعادة التشغيل. إعادة التشغيل هي الخطوة التي يتجاوزها كثيرون، وهي التي تجعل التغيير نافذاً.

يحتوي /etc/machine-id على سلسلة سداسية عشرية صغيرة من 32 محرفاً وتنتهي بحرف سطر جديد. وبعد فك ترميزها، تكون قيمة من 16 بايت (128 بت). تصف صفحة الدليل الخاصة بـmachine-id(5) هذه القيمة بأنها سرية، وتذكر أنه يجب عدم كشفها على الشبكة، لأن أي جهة تقرؤها يمكنها التعرّف إلى جهازك مرة أخرى لاحقاً. تُكتب هذه القيمة مرة واحدة عند تثبيت النظام، ولا يغيّرها شيء بعد ذلك.

يحدث هنا خلط بين 3 معرّفات، لذلك من المفيد فصلها. اسم المضيف هو تسمية تختارها ويمكنك تغييرها في أي وقت. يأتي UUID الخاص بمنتج DMI (واجهة إدارة سطح المكتب) في /sys/class/dmi/id/product_uuid من برنامج مراقبة الأجهزة الافتراضية، ولا يمكن قراءته إلا من جانب root. أما معرّف الجهاز فهو المعرّف الثالث: ينشئه نظام التشغيل، ويمكن لكل مستخدم على الخادم قراءته.

ما الذي يقرأ معرّف الجهاز فعلياً

معرّف عميل DHCP. هذا هو الجزء الأكثر تسبباً في المشكلات. توثّق systemd.network(5) أن ClientIdentifier= في قسم [DHCPv4] تكون قيمته الافتراضية duid، ما يرسل معرّف عميل وفق RFC 4361 مبنياً على IAID ومعرّف DUID (معرّف DHCP الفريد). وتوثّق networkd.conf(5) أن نوع DUID الافتراضي هو vendor، حيث تُنشأ قيمة DUID باستخدام 43793 بوصفه معرّف المورّد (systemd)، وباستخدام محتوى machine ID بعد تجزئته. ويستخدم DHCPv6 معرّف DUID نفسه. إذا كان لدى نسختين مستنسختين machine ID نفسه، فستنتجان DUID نفسه بعد التجزئة. وإذا احتفظتا أيضاً باسم الواجهة نفسه، فسترسلان معرّف عميل متطابقاً بايتاً ببايت. عندئذ يرى خادم DHCP عميلاً واحداً بدلاً من عميلين، ويمنح الخادمين العنوان نفسه. تتمثل المشكلة في انتقال عنوان بين الخادمين، أو فقدان أحد الخادمين عنوانه كلما جدّد الآخر مدة إيجاره.

journald. توجد ملفات Journal في /var/log/journal/<machine-id>/. يحمل الدليل اسم المعرّف حرفياً. إذا أرسلت سجلات نسختين مستنسختين إلى مجمّع واحد، فستُحفظ تحت دليل واحد وتُقرأ على أنها صادرة عن مضيف واحد.

D-Bus. بدأ تنسيق الملف هذا في /var/lib/dbus/machine-id. في Debian وUbuntu، يكون هذا المسار رابطاً رمزياً إلى /etc/machine-id. وفي بعض الأنظمة يكون ملفاً حقيقياً مستقلاً يحتوي على نسخة خاصة به، وهذه النسخة هي موضع الخطأ في الإجراء أدناه.

وكلاء كل مضيف. غالباً ما تستخدم وكلاء المراقبة، وفحوص التراخيص، وأدوات الجرد، وعملاء النسخ الاحتياطي machine ID بوصفه معرّف المضيف الافتراضي، لأنه ثابت ولا يحتاج إلى إعداد. إذا أبلغ خادمان عن هوية واحدة، فسينتج عن ذلك دمج سلسلة المقاييس في سلسلة واحدة، أو استخدام مقعد ترخيص واحد لتغطية جهازين. تحقّق من طريقة اشتقاق الوكيل لمعرّف المضيف، ولا تفترض أنه يستخدم اسم المضيف.

كيفية التحقق من وجود نسخة مكررة

نفّذ هذا الأمر على كلا الخادمين، ثم قارن الناتج.

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

تشير مطابقة معرّفات الجهاز على خادمين قيد التشغيل إلى أن أحدهما استُنْسخ من الآخر. يطبع hostnamectl القيمة نفسها في السطر Machine ID: إذا كنت تفضّل استخدام أمر واحد.

تحدّد نتيجة ls -l الخطوة التالية. يظهر الرابط الرمزي بهذا الشكل:

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

يعني السطر الذي يبدأ بـ -rw-r--r-- أنه ملف حقيقي يحتوي على نسخة خاصة به من المعرّف القديم. يجب حذف هذا الملف، لأن systemd-machine-id-setup يقرأه قبل تنفيذ أي إجراء آخر.

يهم معرّف UUID الخاص بالمنتج أيضاً. يستخدم systemd-machine-id-setup(1) معرّف KVM UUID قبل اللجوء إلى التوليد العشوائي. لذلك، إذا زوّدك موفّر الخدمة بنسختين مستنسختين تحملان معرّف UUID نفسه في SMBIOS (BIOS الخاص بإدارة النظام)، فستحصل على معرّف الجهاز نفسه مرتين عند إعادة التوليد. إذا اختلف معرّف UUID الخاص بالمنتج على الخادمين، فلا توجد مشكلة يجب معالجتها هنا.

إعادة إنشاء معرّف الجهاز على VPS مستنسخ

الترتيب مهم. يوضّح systemd-machine-id-setup(1) أنه إذا كان معرّف جهاز D-Bus صالحاً مكوّناً للنظام، فسيُنسخ معرّف جهاز 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

يجب إجراء الاقتطاع أولاً، لأن الأداة تعمل فقط عندما يكون الملف مفقوداً أو فارغاً، ولا تفعل شيئاً إذا كان الملف يحتوي بالفعل على معرّف صالح. يعرض systemd-machine-id-setup ما فعله على standard error. على VPS يعمل عبر KVM، سترى عادةً:

Initializing machine ID from KVM UUID.

تظهر رسالة Initializing machine ID from random generator. عندما لا يتوفر UUID من hypervisor. كلا الناتجين مقبول، ما دام cat /etc/machine-id يعرض الآن قيمة مختلفة عن القيمة الموجودة على الخادم الآخر.

يحافظ الرابط الرمزي على استخدام D-Bus وsystemd للقيمة نفسها. إذا كنت تفضّل ملفاً حقيقياً منفصلاً، فنفّذ sudo dbus-uuidgen --ensure بدلاً من ذلك؛ إذ ينشئ الملف باستخدام UUID جديد عندما لا يكون الملف موجوداً. إذا لم يكن dbus مثبتاً، فلن يكون مجلد /var/lib/dbus موجوداً أصلاً، وسيفشل ln مع No such file or directory، ويمكنك تخطي كلا السطرين.

أعد التشغيل بعد ذلك.

sudo reboot

لماذا لا تُعد إعادة التشغيل خياراً

تظل كل عملية قرأت القيمة القديمة تستخدمها. تخزّن sd_id128_get_machine() المعرّف داخل العملية المستدعية، لذلك لا يلاحظ daemon قيد التشغيل أن الملف تغيّر. يكون journald قد فتح /var/log/journal/<old-id>/system.journal مسبقاً، ويواصل الإضافة إليه. ويكون systemd-networkd قد حدّد DUID عند بدء تشغيله، ويواصل إرسال معرّف العميل القديم عند كل تجديد. وهذا هو عادةً سبب الفشل الذي أردت معالجته تحديداً. كما يقرأ D-Bus المعرّف عند بدء التشغيل. يمكنك إعادة تشغيل الخدمات واحدةً تلو الأخرى، لكنك ستنسى إحداها، كما أن PID 1 يحتفظ بالقيمة القديمة أيضاً.

بعد إعادة التشغيل، تحقّق من الجانبين:

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

يحتوي /var/log/journal/ الآن على دليل ثانٍ يحمل اسم المعرّف الجديد، وتُكتب الإدخالات الجديدة فيه. يقرأ journalctl العادي دليل الجهاز الحالي فقط، لذلك يختفي السجل السابق للاستنساخ من العرض الافتراضي. لكنه لا يزال موجوداً على القرص: يقرأ journalctl --merge كل أدلة journal، بما فيها الدليل القديم. احذف الدليل القديم عندما تتأكد من أنك لم تعد تحتاج إلى تلك السجلات.

ولهذا السبب أيضاً لا يمكنك تجربة الإجراء في حاوية. تشارك الحاوية نواة المضيف، ولا تقلع PID 1 خاصاً بها، بينما تمثل إعادة التشغيل جوهر هذا الإجراء. اختبره بالطريقة التي يحدث بها في بيئة الإنتاج: استنسخ VM، وشغّل الأوامر، ثم أعد التشغيل، وبعد ذلك قارن المعرّف بالمعرّف الموجود على الجهاز المصدر.

افرغ الملف قبل إنشاء snapshot، لا بعد استنساخه

ينجح إصلاح النسخ المستنسخة واحداً تلو الآخر. لكن إصلاح 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) باستخدام ملف فارغ للصور المستخدمة على عدة أجهزة، لأن إبقاء ملف فارغ في موضعه يتيح إجراء bind mount لملف مؤقت فوق الملف الفعلي عند استخدام image في وضع القراءة فقط. في /etc للقراءة فقط، يُخزَّن المعرّف الذي يتم إنشاؤه عند الإقلاع في ذلك الملف المؤقت، وتكتبه systemd-machine-id-setup --commit مرة واحدة بعد أن يصبح نظام الملفات قابلاً للكتابة.

يجب التخطيط لأثر جانبي واحد: يؤدي غياب machine ID إلى اعتبار الإقلاع التالي إقلاعاً أولاً، لذلك تعمل الوحدات التي تحتوي على ConditionFirstBoot=yes أثناء ذلك الإقلاع، ثم يجري تخطيها في كل إقلاع لاحق. تحقّق مما ستشغّله image باستخدام grep -rl ConditionFirstBoot /usr/lib/systemd/system/ قبل إنشاء template.

يختلف template عن snapshot، وهذا الاختلاف يحدد ما إذا كانت الهوية ستُنقل. template هو ناتج بناء تُعدّه عمداً، بينما snapshot هو نسخة في نقطة زمنية محددة لخادم واحد قيد التشغيل، ويحمل هوية ذلك الخادم مع بياناته.

لماذا تتعامل صور السحابة مع هذا بشكل صحيح بينما لا تتعامل معه اللقطة التي أنشأتها

تُبنى صور السحابة الخاصة بالتوزيعات بحيث يمكن استنساخها، لذلك تُطرح مع ترك machine ID غير معبّأ، ثم تُنشئه عملية الإقلاع الأولى. يحتوي cloud-init على خطوة موثقة لهذا الغرض تحديداً. يضبط cloud-init clean --machine-id قيمة /etc/machine-id على السلسلة النصية الحرفية uninitialized في أنظمة systemd، ويصف مرجع CLI الخاص بـcloud-init ذلك بأنه أفضل ممارسة عند استنساخ صورة أساسية، لذلك تنشئ عملية الإقلاع التالية لتلك الصورة machine ID فريداً.

أما اللقطة التي أنشأتها بنفسك فتختلف عن ذلك. كان الملف يحتوي على قيمة بالفعل عندما نقرت على زر إنشاء اللقطة، لذلك يحمل كل خادم مستعاد منها القيمة نفسها، ولا يمسحها أي إجراء ضمن مسار الاستعادة. هذه المشكلة من النوع نفسه الذي يحدث عند نقل خادم قيد التشغيل إلى VPS جديد، إذ تكون النسخة مطابقة للأصل، بينما تكون الهوية هي الجزء الذي لم ترغب في نسخه.

ما الذي ينسخه الاستنساخ أيضاً

  • مفاتيح مضيف SSH. يُنسخ /etc/ssh/ssh_host_* أيضاً، ولذلك يعرض الخادمان بصمة الإصبع نفسها للعملاء. احذف هذه الملفات وشغّل sudo ssh-keygen -A، أو sudo dpkg-reconfigure openssh-server على Debian وUbuntu. سيحذّرك العملاء بعد ذلك من تغيّر مفتاح المضيف، وهذا هو السلوك الصحيح.
  • اسم المضيف. عيّنه باستخدام sudo hostnamectl set-hostname app02، ثم تحقّق من أن /etc/hosts ما زال يحلّ الاسم الجديد.
  • إعدادات الشبكة الثابتة. يتعارض استنساخ خادم ذي عنوان ثابت مع الخادم الأصلي فور تشغيله. اقرأ /etc/netplan/ قبل أن ينضم الاستنساخ إلى الشبكة.
  • الساعة. تستأنف لقطة مستعادة العمل بالوقت الذي كانت عليه عند أخذ اللقطة. يؤدي قفز كبير في وقت خادم VPS مستعاد إلى تعطيل التحقق من شهادات TLS وتشويش ترتيب السجلات إلى أن تكتمل مزامنة الوقت.

طبّق قائمة التحقق الخاصة بالدقائق العشر الأولى لخادم VPS جديد على الاستنساخ أيضاً. يرث الخادم المستنسخ حسابات المستخدمين ومفاتيح SSH وقواعد جدار الحماية والمهام المجدولة من الخادم المصدر، ولم تُراجع أيٌّ من هذه العناصر للعمل الذي سيؤديه الاستنساخ.

FAQ

هل يجب إعادة التشغيل بعد تغيير /etc/machine-id؟

نعم. تقرأ العمليات معرّف الجهاز مرة واحدة وتخزّنه مؤقتاً، لذلك لا تصل القيمة الجديدة إلى أي عملية قيد التشغيل. يواصل journald الكتابة إلى مجلد السجل الذي يحمل اسم المعرّف القديم، ويواصل عميل DHCP إرسال معرّف عميل مشتق من القيمة القديمة، وهذا هو السبب المعتاد لتغييرك لها. تؤدي إعادة تشغيل الخدمات الفردية إلى إصلاح بعضها، لكن PID 1 يحتفظ بالقيمة القديمة أيضاً. أعد التشغيل، ثم تحقّق باستخدام cat /etc/machine-id وقارن النتيجة بالخادم الآخر.

هل /etc/machine-id هو نفسه UUID الخاص بالأجهزة؟

لا. يأتي UUID الخاص بمنتج DMI في /sys/class/dmi/id/product_uuid من hypervisor، ولا يمكن قراءته إلا بواسطة root. ينشئ نظام التشغيل machine ID ويخزّنه في ملف نصي عادي يمكن لأي مستخدم قراءته. توجد علاقة بينهما في اتجاه واحد: في ضيف KVM، يستخدم systemd-machine-id-setup UUID الخاص بـ hypervisor لإنشاء machine ID جديد عندما لا يتوفر معرّف D-Bus لنسخه. إذا كانت نسختان مستنسختان تشتركان في UUID الخاص بالمنتج، فستعيدان إنشاء machine ID نفسه، لذلك قارن هذا الملف أيضاً قبل الوثوق بالنتيجة.

هل يجب حذف /etc/machine-id أم تركه فارغاً؟

اتركه فارغاً عند إعداد image. يفضّل machine-id(5) الملف الفارغ، لأن systemd يستطيع إجراء bind mount لملف مؤقت فوقه عندما تعمل image باستخدام /etc للقراءة فقط. ينجح حذف الملف على نظام قابل للكتابة، وتستخدم بعض scripts الخاصة بالاستنساخ هذه الطريقة، لكن الملف الفارغ هو الخيار الافتراضي الأكثر أماناً. يكتب cloud-init الكلمة uninitialized في الملف للغرض نفسه.

لماذا حصل الخادمان المستنسخان على عنوان DHCP نفسه؟

لأن كليهما أرسل معرّف العميل نفسه. يعيّن systemd-networkd القيمة ClientIdentifier=duid تلقائياً لـ DHCPv4، ويُبنى DUID الافتراضي من hash للقيمة /etc/machine-id، لذلك تنتج machine IDs المتطابقة معرّفات متطابقة في النسخ المستنسخة التي احتفظت أيضاً باسم الواجهة نفسه. يطابق خادم DHCP الطلبات وفق ذلك المعرّف، ويتعامل معهما كعميل واحد، ثم يمنحهما lease واحداً. امنح كل خادم machine ID خاصاً به وأعد تشغيل الخادمين. إذا استمر الخادم في عرض العنوان القديم، فامسح lease القديم من خادم DHCP نفسه.

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