לדלג לתוכן

גיבויי VM

BombVault מגבה מכונות וירטואליות של KVM/libvirt בלי לעגן שום נתיב של libvirt. היא מריצה virsh במארח דרך SSH (qemu+ssh://), קוראת וכותבת את דיסקי המכונות דרך עיגון Host Data (הנתיב /mnt במארח הוא /host/user בקונטיינר), ומעתיקה את ה-NVRAM של UEFI דרך SSH. מכיוון שדבר השייך ל-libvirt לא מעוגן, BombVault אף פעם לא יכולה להפריע למנהל המכונות הווירטואליות של Unraid.

המדריך הזה מכסה את ההגדרה, שיטות הגיבוי, מכונות עם תצלומי Unraid ו-TrueNAS SCALE. גיבויי קונטיינרים לא צריכים אף אחד מזה.

גיבויי מערכי נתונים של ZFS משתמשים באותו קישור SSH, עם אותו מפתח, מארח ומשתמש. ברגע שגיבויי VM מתחברים, גם פריטי מערכי הנתונים של ZFS מתחברים. ראו מערכי נתונים של ZFS.

מה מגדירים

הגדרה היכן ברירת מחדל משמעות
Host SSH: Address (LIBVIRT_HOST) משתנה תבנית host.docker.internal מארח Unraid, שמגיעים אליו דרך SSH
Host SSH: Port (LIBVIRT_SSH_PORT) משתנה תבנית 22 פורט ה-SSH של Unraid
Host SSH: User (LIBVIRT_SSH_USER) משתנה תבנית root משתמש ה-SSH במארח
מפתח ציבורי הגדרות, אינטגרציות, SSH למארח נוצר בהפעלה הראשונה מאשרים אותו במארח

BombVault יוצרת את זוג מפתחות ה-SSH שלה בהפעלה הראשונה ב-/config/ssh/id_ed25519, שנשמר ב-appdata. מפתח המארח ננעץ ב-/config/ssh/known_hosts בחיבור הראשון.

התבנית ממלאת את Host SSH: Address בערך המציין 192.168.x.x. BombVault מתייחסת אליו כאל לא מוגדר ומנסה host.docker.internal, שעובד ברשת bridge ברירת המחדל. ברשת מותאמת מחליפים אותו בכתובת ה-IP של המארח ב-LAN (שלב 3 ב).

שלב 1: הפעלת SSH במארח Unraid

  1. ב-Unraid פותחים Settings, Management Access וקובעים Use SSH ל-Yes.
  2. אם שינית את פורט ה-SSH, רושמים אותו. Host SSH: Port צריך להתאים לו.

שלב 2: אישור המפתח הציבורי של BombVault

התיקייה /root של Unraid חיה בזיכרון ונמחקת בכל אתחול, ולכן המפתח צריך להיות גם בכונן ה-flash. מריצים את זה במסוף של Unraid:

K=$(docker exec BombVault cat /config/ssh/id_ed25519.pub)
mkdir -p /root/.ssh /boot/config/ssh && chmod 700 /root/.ssh
grep -qxF "$K" /root/.ssh/authorized_keys 2>/dev/null  || echo "$K" >> /root/.ssh/authorized_keys
grep -qxF "$K" /boot/config/ssh/root.pubkeys 2>/dev/null || echo "$K" >> /boot/config/ssh/root.pubkeys
chmod 600 /root/.ssh/authorized_keys

Unraid 6.9 ומעלה מעתיקים את /boot/config/ssh/root.pubkeys אל authorized_keys של root בכל אתחול, ולכן זה שורד אתחולים. זה חל על כל כניסה של root, לא משנה באיזה שם מארח או פורט BombVault משתמשת.

שלב 3: רשת

BombVault צריכה לפתוח חיבור TCP מהקונטיינר אל פורט ה-SSH של המארח. בוחרים את המקרה שמתאים להגדרה שלך.

א. BombVault ברשת bridge ברירת המחדל

  • משאירים את Host SSH: Address על host.docker.internal או על הערך המציין מהתבנית. התבנית מוסיפה --add-host=host.docker.internal:host-gateway, שמצביע על שער docker0, שהוא המארח.
  • קובעים את Host SSH: Port לפורט ה-SSH שלך.

ב. BombVault ברשת מותאמת

קונטיינר על br0.x, macvlan או ipvlan לא יכול להגיע למארח דרך host.docker.internal, כי 172.17.0.1 הוא docker0 ואי אפשר להגיע אליו מ-br0.x. במקום זה:

  1. ב-Unraid קובעים Settings, Docker, Host access to custom networks ל-Enabled.
  2. קובעים את Host SSH: Address לכתובת ה-IP של מארח Unraid ב-LAN, הכתובת שבה פותחים את ממשק הרשת.
  3. אם רשת הקונטיינר והמארח נמצאות ב-VLANs שונים, מאשרים את המסלול בנתב או בחומת האש: מ-VLAN של הקונטיינר אל ה-IP של המארח בפורט ה-SSH, דרך TCP.
  4. אם עדיין אי אפשר להגיע לכתובת ה-IP של המארח ב-LAN, משתמשים בכתובת ה-shim של המארח ברשת המשנה של הקונטיינר. מוצאים אותה במארח עם ip -4 addr show | grep -B2 <container-subnet>.

בודקים שהמארח עונה

מריצים את זה במסוף של Unraid, עם ה-IP והפורט שלך:

docker exec BombVault timeout 6 bash -c 'echo > /dev/tcp/192.168.1.10/22' && echo OPEN || echo UNREACHABLE

OPEN פירושו שהמסלול עובד. UNREACHABLE פירושו שהרשת בשלב 3 צריכה תיקון לפני כל דבר אחר.

שלב 4: קובעים את המשתנים ובודקים

  1. ב-Unraid פותחים Docker, לוחצים על BombVault, בוחרים Edit, קובעים את Host SSH: Address (ואת Host SSH: Port אם הוא לא 22) ולוחצים Apply. אם המשתנים חסרים, מייבאים את התבנית שוב, כי Unraid שומר את התצורה השמורה של קונטיינר קיים.
  2. ב-BombVault פותחים הגדרות, אינטגרציות, SSH למארח ולוחצים בדיקת חיבור. הוא אמור להפוך לירוק.

אותה קריאה מהמסוף מפרטת את המכונות הווירטואליות שלך:

docker exec BombVault virsh -c "qemu+ssh://root@192.168.1.10:22/system?keyfile=/config/ssh/id_ed25519&known_hosts=/config/ssh/known_hosts&known_hosts_verify=auto" list --all

שלב 5: השיטה לכל מכונה

לכל מכונה וירטואלית יש שיטה בכרטיס VM בלשונית ההגדרות שלה (פותחים את המכונה תחת גיבויים):

  • מסודרת (כיבוי) היא ברירת המחדל. היא מכבה את המכונה, מגבה את הדיסקים ומפעילה אותה שוב, כך שהגיבוי תמיד עקבי. היא ממתינה עד עשר דקות שמערכת האורח תכבה את עצמה ואף פעם לא כופה כיבוי. מערכת אורחת שעדיין רצה אחרי זה נשארת כמות שהיא, והגיבוי הזה נכשל עם הסיבה. אם מערכת האורח נכבית בתוך השעה שאחר כך, BombVault מפעילה אותה שוב. אם ההפעלה הזו נכשלת, הריצה שנכשלה אומרת זאת.
  • Snapshot חי מגבה מכונה שרצה דרך snapshot חיצוני ו-blockcommit. היא צריכה את הדיסק ב-/mnt/cache או ב-/mnt/diskX, לא ב-/mnt/user. עם qemu guest agent בתוך המכונה ה-snapshot שקט, ובלעדיו הוא עקבי ברמת קריסה (crash-consistent). במכונה כבויה Snapshot חי חוזר ל"מסודרת" מעצמו. כשאי אפשר ליצור את ה-snapshot, הגיבוי הזה נכשל והמכונה ממשיכה לרוץ, כי מכונה שבחרת לגבות חי אף פעם לא מכובה בשביל זה.

מה גיבוי משאיר בחוץ

גיבוי יכול להצליח ועדיין לא להחזיק את המכונה כולה. הריצה נושאת אז הערה בגוון של אזהרה שמציינת מה חסר:

  • דיסקים ש-BombVault לא קוראת. דיסק לקריאה בלבד, lun ודיסק network או volume נשארים בחוץ, וההערה מציינת כל אחד מהם עם התקן היעד והסוג שלו. ההגדרה שבגיבוי עדיין מפרטת אותם. cdrom ו-floppy לא מצוינים. התקן בלוק רגיל שאינו zvol של ZFS מכשיל את הגיבוי במקום זאת.
  • המצב של TPM מדומה. אין לו נתיב ב-XML של הדומיין. מנהל המכונות של Unraid מגדיר TPM כזה למכונה עם ה-BIOS OVMF TPM, ולכן ההערה מופיעה גם ב-Unraid. מערכת אורחת ששומרת סוד ב-TPM שלה, כמו ש-BitLocker יכול לעשות, עלולה לבקש את מפתח השחזור שלה אחרי שחזור שמאבד את המצב הזה, ולכן כדאי לשמור את המפתח הזה בהישג יד.

דיסקים דלילים

שחזור שומר על דיסק דליל (thin) דליל, בין אם הוא מחליף את הדיסק ובין אם הוא כותב דיסק חדש: restic כותב רצפים של אפסים כחורים בקובץ חדש, ו-BombVault משחררת את בלוקי האפסים של קובץ שהוחלף עם fallocate --dig-holes אחרי שהוא נכתב. בזמן ש-restic מחליף קובץ, הקובץ הזה תופס את מלוא הגודל שלו, אם יש ל-pool מקום. מערכת קבצים בלי ניקוב חורים משאירה את הדיסק בגודל המלא שלו, והיומן אומר זאת. בדיקת המקום עדיין סופרת דיסק חדש לפי הגודל המלא שלו.

שחזור שנכשל

שחזור במקום מכבה את המכונה, כותב את הדיסקים, ומחליף את ההגדרה ואת ה-NVRAM רק אחרי שהדיסקים נכתבו. שחזור שנכשל משאיר את המכונה מוגדרת עם ה-NVRAM שלה. אם הדיסקים שלה לא השתנו והיא רצה, היא רצה שוב. אם ייתכן שהם השתנו, היא נשארת כבויה, כי אתחול מדיסק שנכתב רק בחלקו היה כותב מעליו, והריצה אומרת זאת. שחזור של אותו גיבוי פעם נוספת משלים את העבודה.

תצלומים שרשומים ב-libvirt, שזו לא הדרך שבה מנהל המכונות של Unraid יוצר אותם, עוצרים שחזור במקום לפני שנוגעים במכונה, כי libvirt לא יכול להסיר מכונה שיש לה כאלה. מוחקים אותם קודם.

רק בלוקים שהשתנו

מכונה עם דיסקי qcow2 אפשר לגבות בקריאת מה שהשתנה בלבד. מפעילים רק בלוקים שהשתנו באותו כרטיס, ו-BombVault משתמשת ב-API הגיבוי של libvirt במקום בשיטה שלמעלה:

  • היא מתחילה עבודת גיבוי במצב משיכה (virsh backup-begin) שיוצרת באותו רגע checkpoint בשם bombvault-<time>, וקוראת את הדיסקים דרך קישור ה-SSH דרך שקע Unix מועבר. כשהאורח עונה, מערכות הקבצים של האורח מוקפאות רק בזמן שהעבודה מתחילה. הפשרה שנכשלה מנוסה שוב כחמש עשרה שניות, ואם האורח עדיין לא מופשר, BombVault שולחת התראה.
  • הריצה הראשונה קוראת כל בלוק שהוקצה. ריצות מאוחרות קוראות רק את הבלוקים שה-bitmap של ה-checkpoint מסמן ככתובים מאז הגיבוי הקודם.
  • כל דיסק נשמר כתיקייה של מקטעים בגודל קבוע שמחזיקים את מה שהאורח רואה. מקטעים שלא השתנו נלקחים מהתצלום הקודם בלי לקרוא אותם, כך שכל תצלום שלם ומשוחזר בפני עצמו.
  • שחזור בונה מחדש את הדיסק ממקטעיו וממיר אותו בחזרה ל-qcow2 עם qemu-img. לדיסק אותו תוכן כמו בזמן הגיבוי, אבל הקובץ הוא קובץ qcow2 חדש, ושרשרת backing חוזרת שטוחה.
  • הבדיקה לפני שחזור כזה מפרטת כל דיסק שהיא בונה מחדש וסופרת אותו בגודל המלא של האורח, פעמיים ל-qcow2, כי התמונה הגולמית והעותק המומר שלה עומדים זה לצד זה עד שהקובץ הישן מוחלף. זה המקסימום שדיסק יכול לקחת, ולכן חוסר לפי הספירה הזו משאיר את שורת המקום אפורה במקום לחסום. גם בדיקת השחזור אחרי הגיבוי הראשון, וגם בדיקת שחזור, מוודאות שהתצלום מחזיק כל מקטע של כל דיסק בגודל שצריך.
  • בין ריצות למכונה אין checkpoint רשום ב-libvirt. BombVault שומרת את ההגדרה של ה-checkpoint החדש ביותר של bombvault- תחת /config/vm-checkpoints ומסירה רק את הרישום של libvirt שלו (virsh checkpoint-delete --metadata). ה-bitmap שלו נשאר בקובץ ה-qcow2 וממשיך לספור כתיבות, והריצה הבאה מחזירה את ה-checkpoint (virsh checkpoint-create --redefine) לפני שהיא מתחילה. libvirt מסרבת לבטל הגדרה של מכונה שיש לה checkpoints, וזה מה שמאפשר ל-Remove VM של Unraid ולשחזור לעבוד כרגיל.
  • עבודת גיבוי ש-BombVault השאירה רצה, כי היא נעצרה באמצע גיבוי, מסתיימת כש-BombVault מתחילה שוב, יחד עם ה-checkpoints שהריצה ההיא השאירה רשומים.
  • כיבוי המתג, הוצאת המכונה מלוח הזמנים או מחיקת הגיבויים שלה מסיימים גם עבודה כזו, ומוחקים גם את ה-bitmap כל עוד המכונה רצה. במכונה כבויה libvirt לא יכולה למחוק אותו, והוא נשאר בתמונה בלי שימוש.
  • ה-checkpoints של כלים אחרים נשארים. מכונה שיש לה כאלה אי אפשר לשחזר עד שהם נמחקים, והשחזור נעצר לפני שהוא נוגע במכונה.

המתג חוזר אחורה מעצמו. checkpoint חסר או שבור, דיסק ששינה גודל, או תצלום חדש ביותר שאינו של בלוקים שהשתנו, פירושם שהדיסק כולו נקרא פעם נוספת. מכונה כבויה, דיסק גולמי או zvol, אין קישור SSH למארח, libvirt בלי API הגיבוי, או מעט מדי מקום פנוי תחת /config פירושם הגיבוי הרגיל עם השיטה שלמעלה. הכרטיס אומר איזה מאלה הריצה האחרונה השתמשה ולמה.

דרישות: libvirt עם API הגיבוי (נבדק עם libvirt 12.2 ו-QEMU 10.2 ב-Unraid 7.3), ‏sshd עם AllowStreamLocalForwarding yes (ברירת המחדל, גם ב-Unraid, שמכבה העברת TCP), ודיסקי qcow2. libvirt מסרבת ל-checkpoints על דיסקים גולמיים, ו-Unraid יוצר vdisks גולמיים כברירת מחדל. המכונה ממשיכה לרוץ בזמן הגיבוי, כמו עם Snapshot חי, והמקטעים שממתינים ל-restic תופסים עד 4 GiB תחת /config.

מכונות עם תצלומי Unraid

תצלום שמנהל המכונות הווירטואליות של Unraid לוקח הופך דיסק qcow2 לשרשרת: התמונה המקורית בתחתית, ומעליה קובץ לכל תצלום, שכל אחד מחזיק רק מה שהשתנה מאז זה שמתחתיו. המכונה כותבת לקובץ החדש ביותר. דיסק עם שני תצלומים הוא שלושה קבצים.

BombVault מגבה מכונה כזו במלואה:

  • כל קובץ בשרשרת. היא עוקבת אחרי שרשרת ה-backing של כל דיסק עד סופה ומגבה כל קובץ בה, יחד עם רשימת התצלומים של Unraid (/etc/libvirt/qemu/snapshotdb/<vm>/snapshots.db, נקראת דרך SSH). שרשרת שאי אפשר לעקוב אחריה עד סופה, למשל כי קובץ באמצע חסר, מכשילה את הגיבוי ומציינת את הקובץ.
  • גיבוי חי שומר על התצלומים. Snapshot חי מניח שכבה זמנית למעלה בזמן שהיא קוראת את הדיסק וממזגת אותה בחזרה אחר כך, רק לתמונה שמתחתיה ישר. השרשרת זהה אחרי הגיבוי כמו לפניו. מכונה בלי תצלומים מתנהגת כמו תמיד.
  • שחזור מחזיר את השרשרת. שחזור במקום כותב כל קובץ בחזרה למקומו ומחזיר את רשימת התצלומים של Unraid, כך שמנהל המכונות מציג שוב את התצלומים. שחזור לצד המקור כותב את הקבצים לתיקיית העותק ומכוון את הדיסקים ששוחזרו לקבצים ששוחזרו, כך שהעותק אף פעם לא קורא מהתמונות של המקור. רשימת התצלומים של Unraid חוזרת רק עם שחזור במקום, כי היא מציינת את הקבצים במקום שבו גובו. בלעדיה העותק עולה באותו אופן, ומנהל המכונות פשוט לא מציג לו תצלומים.
  • רק בלוקים שהשתנו קורא את מה שהאורח רואה, ולכן השחזור שלו חוזר כקובץ qcow2 שטוח אחד בלי התצלומים.
  • שחזור מחזיר את הדיסק של הגיבוי שבוחרים. תצלומים שנלקחו או מוזגו מאז הגיבוי הזה לא משנים את זה. גיבוי מלפני תצלום מגדיר את המכונה על הקובץ שהוא מחזיק, וגיבוי מלפני מיזוג מחזיר את השכבה (overlay) שלו ורץ עליה. קבצים חדשים יותר נשארים במקומם, בלי שימוש ובלי שינוי. גיבוי שמחזיק שכבה אבל לא את הקובץ שמתחתיה נדחה לפני שנוגעים במכונה. ההגדרה של המכונה לא מפרטת שרשרת אחרי שחזור, ולכן libvirt קורא את השרשרת מהקבצים ששוחזרו.

דוגמה: מכונה עם שני תצלומים

למכונה homeassistant דיסק אחד, vdisk1.qcow2, ושני תצלומים שלקחת במנהל המכונות לפני שני עדכונים. הדיסק שלה הוא עכשיו שרשרת של שלושה קבצים. היא רצה כל הזמן, ולכן היא משתמשת ב-Snapshot חי.

  1. פותחים גיבויים, אחר כך homeassistant, ואת לשונית הגדרות שלה. בכרטיס VM בודקים ששיטה היא Snapshot חי ורק בלוקים שהשתנו כבוי.
  2. לוחצים גיבוי עכשיו בראש הדף.
  3. כשהריצה נגמרת, השורה שלה בהרצות אחרונות בלשונית אימות ירוקה. המכונה המשיכה לרוץ כל הזמן, והגיבוי מחזיק את שלושת הקבצים של הדיסק ואת רשימת התצלומים של Unraid.
  4. במנהל המכונות שני התצלומים עדיין שם, והדיסק עדיין קורא דרך אותם שלושה קבצים.

חודש אחר כך עדכון משתבש ואתה רוצה את המצב של אתמול לצד השבור, כדי להשוות:

  1. פותחים שחזור, בוחרים VMs ולוחצים על homeassistant.
  2. משאירים את הגיבוי המומלץ מהלילה שעבר ובוחרים לצד המקור תחת לאן.
  3. לוחצים שחזור. שלושת הקבצים נוחתים בתיקייה בשם המכונה עם -restored, והדיסק ששוחזר מצביע על הקבצים ששוחזרו. שום דבר לא משתנה במכונה הרצה.

כדי להחזיר את כל המכונה אחורה במקום זה, בוחרים החלפת המקור עם גיבוי המצב הנוכחי קודם מופעל. BombVault מגבה את המצב השבור, מכבה את המכונה, כותבת בחזרה את שלושת הקבצים ואת רשימת התצלומים של Unraid, ומפעילה אותה שוב.

TrueNAS SCALE

השלבים למעלה כתובים ל-Unraid, אבל אותו קישור qemu+ssh:// עובד מול כל libvirtd שהקונטיינר יכול להגיע אליו, כולל TrueNAS SCALE, שיש בו מכונות וירטואליות על libvirt שוב מאז 25.04.2. ב-TrueNAS מחרוזת החיבור צריכה עקיפה, והמפתח הולך ל-root בהגדרות המשתמשים של TrueNAS עצמו. שלושה דברים נוספים משנים מה שגיבוי מחזיק: דיסקים על zvols, מצב vTPM, והשמות ש-TrueNAS נותן למכונות שלו.

1. שקע libvirt

libvirtd של TrueNAS לא מאזין במקום שמחרוזת החיבור שנבנית משדות Host SSH מצפה לו. קובעים במקום זה LIBVIRT_URI, עם הפרמטר הנוסף ?socket=:

LIBVIRT_URI=qemu+ssh://<user>@<truenas-host>/system?socket=/run/truenas_libvirt/libvirt-sock

BombVault משתמשת ב-LIBVIRT_URI בדיוק כפי שנכתב, ו-LIBVIRT_HOST, ‏LIBVIRT_SSH_USER ו-LIBVIRT_SSH_PORT לא נחוצים. לכל אחד מהם שלא מוגדר, BombVault לוקחת את המארח, המשתמש והפורט מהכתובת גם לפקודות ה-SSH שלה, כך שהעתקת ה-NVRAM וגיבויי מערכי הנתונים של ZFS מגיעים לאותה מכונה כמו virsh. משתנה שקבעת בעצמך תמיד גובר, ויומן ההפעלה מציין את היעד שנלקח מהכתובת. ב-TrueNAS משתמש שאינו root צריך גם zfs allow <user> snapshot,destroy,mount <dataset> לגיבויי מערכי נתונים של ZFS, ראו מערכי נתונים של ZFS.

שמים את כתובת ה-LAN של TrueNAS בכתובת. כש-LIBVIRT_URI ו-LIBVIRT_HOST שניהם לא מוגדרים, BombVault לוקחת את שער ברירת המחדל של האפליקציה כמארח, כי host.docker.internal לא נפתר באפליקציית TrueNAS, ויומן ההפעלה מציין את הכתובת שנבחרה. זה מספיק לגיבויי מערכי נתונים של ZFS. גיבויי VM עדיין צריכים את הכתובת בשביל השקע.

האפליקציה צריכה את היכולת SETPCAP לגיבויי VM, לצד CHOWN, ‏DAC_OVERRIDE, ‏FOWNER, ‏SETGID ו-SETUID. בלעדיה libvirt עוצרת עם "cannot apply process capabilities". ה-YAML תחת TrueNAS SCALE קובע את כולן, יחד עם SYS_CHROOT שתצלום במקום עצירה צריך.

ב-TrueNAS SCALE 25.10.0 נבדק נתיב השקע על חומרה אמיתית: qemu:///system הרגיל נכשל עם Failed to connect socket to '/var/run/libvirt/libvirt-sock': No such file or directory, ואותה קריאה עם ?socket=/run/truenas_libvirt/libvirt-sock מפרטת את הדומיינים.

2. המפתח של BombVault ב-TrueNAS

שלב 2 למעלה כותב לכונן ה-flash של Unraid ולא חל כאן. ב-TrueNAS:

  1. מפעילים את שירות ה-SSH תחת System, Services.
  2. תחת Credentials, Users עורכים את root ומדביקים את המפתח הציבורי של BombVault מהגדרות, אינטגרציות, SSH למארח כמפתח ה-SSH הציבורי שלו. TrueNAS שומר אותו לאורך אתחולים ועדכונים.

התקנות חדשות של TrueNAS 24.10 ומעלה לא נותנות ל-root סיסמה, וכניסה עם מפתח לא צריכה אחת. אם אתה מעדיף לא להשתמש ב-root, מאשרים את המפתח לחשבון מנהל עם sudo ושמים את השם שלו במקום root ב-LIBVIRT_URI. גיבויי מערכי נתונים של ZFS צריכים אז zfs allow כמתואר למעלה.

רפליקת ZFS לשרת שני נכנסת עם מפתח משלה, שחלון הרפליקה מציג. מפתח ה-SSH למארח לא מאשר רפליקה, ומפתח הרפליקה לא מאשר גיבויי VM.

TrueNAS לא רואה שינויים שנעשו עם virsh

ה-middleware של TrueNAS, השכבה שמאחורי מסכי המכונות הווירטואליות שלו, אמור להיות הדרך היחידה לשנות את המכונות שלו. פקודת virsh דרך קישור ה-SSH הזה, שזה מה שהגיבוי והשחזור של BombVault למכונות מריצים, עוקפת אותו, וייתכן ש-TrueNAS יתאים או ידרוס אחר כך מצב שהוא לא יצר. זה נובע מההתנהגות המתועדת של TrueNAS ולא נראה בתיבה אמיתית. זה אותו סוג של סיכון כמו זה שמנהל המכונות של Unraid מתאים שינויים שנעשו מחוצה לו, רק חד יותר ב-TrueNAS.

3. דיסקים על zvols

דיסקים של מכונות ב-TrueNAS הם לרוב zvols, התקני בלוק של ZFS ב-/dev/zvol/<pool>/<dataset>, לא קובצי qcow2. restic לא יכול לגבות התקן בלוק לפי הנתיב שלו כמו שהוא מגבה קובץ, ולכן BombVault מזהה דיסק zvol ב-XML של הדומיין (<source dev="..."> במקום <source file="...">) ומטפלת בו דרך אותו קישור SSH. הקונטיינר של BombVault לא צריך כלי ZFS.

גיבוי של zvol:

  1. zfs snapshot <dataset>@bombvault-vm-<time> מקבע נקודת זמן, גם כשהמכונה רצה.
  2. zfs clone -o volmode=dev -o readonly=on הופך את התצלום לקריא כהתקן. volmode=dev מונע מהמארח לסרוק את מחיצות האורח.
  3. dd קורא את ההתקן, והתמונה הגולמית נכנסת ל-restic כקובץ אחד, /vm-disks/<dataset>.img, מתויג vm:<name>:zvol:<dev>. restic חותך אותו לחלקים לפי תוכן, ולכן בלוקים שלא השתנו מאז הגיבוי האחרון לא נשמרים שוב.
  4. השיבוט והתצלום נהרסים אחר כך, בין אם הגיבוי הצליח ובין אם לא. אם BombVault נעצרה באמצע גיבוי, הגיבוי הבא של ה-zvol הזה מסיר קודם את התצלום bombvault-vm-<time> ואת השיבוט שלו <dataset>-bombvault-vm-<time>. הוא משאיר כל תצלום אחר, ומשאיר אחד ששיבטו ממנו משהו אחר. היומן מציין מה הוסר או נשאר.

BombVault לא משתמשת ב-zfs send לזה. כל רשומה בזרם send נושאת את ה-GUID של התצלום וסכום ביקורת רץ, ולכן restic לא ימצא כלום מהגיבוי האחרון לשימוש חוזר וישמור את הדיסק כולו שוב בכל פעם. נמדד ב-TrueNAS SCALE 25.10 עם zvol של 512 MiB שמחזיק 200 MiB נתונים: הגיבוי הראשון הוסיף 211 MB, שני בלי שינוי 52 KB, ואחד אחרי ש-1 MiB השתנה 3.6 MB. אותו zvol דרך zfs send הוסיף 214 MB בכל ריצה.

כל ריצה כותבת גם תצלום קטן מתויג vm:<name> עם ה-XML של הדומיין, אחרי שכל הדיסקים נכנסו. למכונה שכל הדיסקים שלה zvols אין תצלום קבצים, וזה זה שמפרט את הריצה תחת המכונה והופך אותה לניתנת לשחזור.

שחזור כותב כל zvol בחזרה למקומו, כמו שדיסק קובץ נכתב בחזרה לקובץ שלו, כך שהמכונה ששוחזרה עולה על הנתונים ששוחזרו:

  1. לפני שנוגעים בכל דבר, BombVault בודקת שהריצה מחזיקה תמונה של כל דיסק zvol. ריצה שחסרה אחת נדחית והמכונה נשארת כמות שהיא.
  2. ל-zvol קיים נלקח קודם תצלום, <dataset>@bombvault-before-restore-<time>, עם הזמן ב-UTC כמו כל שם תצלום ש-BombVault יוצרת, ולכן הוא יכול להיות רחוק שעות מהשעון המקומי. היומן מציין אותו, ו-zfs rollback אליו מבטל את השחזור כל עוד הוא התצלום החדש ביותר של ה-zvol. ברגע שקיים חדש יותר, כמו זה של השחזור הבא, zfs rollback -r עושה זאת והורס את התצלומים החדשים יותר. הוא נשאר עד שמסירים אותו עם zfs destroy אחרי שהמכונה ששוחזרה עובדת.
  3. zvol שכבר לא קיים נוצר בגודל שהיה לו בזמן הגיבוי, וקיים בגודל אחר מוחזר לגודל הזה.
  4. dd כותב את התמונה על /dev/zvol/<dataset>. עם דחיסה מופעלת, ברירת המחדל ב-TrueNAS, החלקים הריקים של התמונה חוזרים כחורים ולא תופסים מקום.

שחזור למופע אחר צריך מאגר יעד. הוא מעביר דיסקי קבצים לתיקיית יעד, אבל תיקייה לא מציינת מאגר ZFS, ולכן בלי אחד BombVault מסרבת לשחזור לפני שהוא מתחיל. מעבירים את המאגר כפרמטר zvolPool של POST /api/foreign/restore: כל zvol שומר על הנתיב שלו מתחת למאגר (tank/vms/win10/disk0 אל flashpool נעשה flashpool/vms/win10/disk0), וה-XML של הדומיין מצביע עליו. לחלון מתיקיית גיבויים ישנה בדף השחזור אין שדה לזה, ולכן אפשר להגיע אליו רק בקריאת API ישירה. שחזור באותו מופע כותב כל zvol בחזרה על עצמו.

ייצוא כקבצים משאיר zvols בחוץ. מכונה שכל הדיסקים שלה zvols נדחית עם הסיבה הזו, ולכן משחזרים אותה מגיבוי. למכונה שיש לה גם דיסקי קבצים הייצוא מחזיק את דיסקי הקבצים והערה שמציינת את ה-zvols שהשאיר בחוץ.

זה נוסה ב-TrueNAS SCALE 25.10 עם ZFS 2.3.4: שלושה גיבויים של zvol לבדיקה, שחזור במקום אחרי ש-zvol נדרס וגדל, ושחזור על zvol שנמחק. כל שחזור נקרא בחזרה עם אותו SHA-256 כמו המקור, ה-zvol חזר לגודלו הישן, ושום תצלום או שיבוט לא נשארו. מכונה שלמה שהדיסק היחיד שלה הוא zvol גובתה כשהיא רצה, עם "מסודרת" ועם Snapshot חי, וכשהיא כבויה, ושחזור של מכונה שלמה (ביטול הגדרה, כתיבת ה-zvols, הגדרה, אתחול) רץ שם גם: ה-zvol נקרא בחזרה עם סכום הביקורת של המקור והמכונה עלתה.

דיסקי קבצים, כפי ש-Unraid משתמש בהם, לא מושפעים מכל זה.

4. מצב vTPM

למכונה יכול להיות TPM וירטואלי, שאורחי Secure Boot ו-Windows 11 צריכים. המצב שלו חי מחוץ לדיסק ול-NVRAM, ולכן גיבוי של שניהם בלבד היה מחזיר אורח עם TPM חדש וריק.

  • TPM מועבר (passthrough), שבב אמיתי שמועבר לאורח, מציין את הנתיב שלו ב-XML של הדומיין. BombVault שומרת את המצב שלו לצד ה-NVRAM דרך SSH וכותבת אותו בחזרה לפני virsh define, במיטב היכולת כמו ה-NVRAM: קריאה או כתיבה שנכשלות נרשמות ביומן ולא מכשילות את הגיבוי או השחזור.
  • vTPM מדומה, מהסוג ש-TrueNAS מגדיר לאורחי Windows 11 ו-Secure Boot, לא מפרסם נתיב מצב ב-XML של הדומיין, ולכן המצב שלו לא מגובה. BombVault לא מנחשת נתיב, והגיבוי מסתיים עם הערה שהמצב של ה-TPM של המכונה לא נמצא בו. שומרים את מפתח השחזור של אורח כזה, כמו מפתח ה-BitLocker שלו, בהישג יד לפני ששחזרים אותו.
  • אי אפשר לחבר TPM למכונת TrueNAS בעצמך. ה-API של התקני המכונות הווירטואליות ב-TrueNAS SCALE 25.10 מקבל רק CDROM, ‏DISPLAY, ‏NIC, ‏PCI, ‏RAW, ‏DISK ו-USB, ודוחה TPM כסוג לא מוכר.

הטיפול ב-TPM נבדק רק בבדיקות יחידה ולא רץ מול אורח TrueNAS אמיתי עם vTPM. מכונה בלי TPM מגובה ומשוחזרת בדיוק כמו קודם.

ה-NVRAM נלכד כרגיל. TrueNAS שומר אותו לכל מכונה ב-/var/db/system/vm/nvram/<id>_<name>_VARS.fd, וה-XML של הדומיין מציין את הנתיב הזה.

5. שמות מכונות ב-TrueNAS

ב-Unraid שם ה-libvirt של מכונה הוא השם שבחרת. TrueNAS ממציא את שם ה-libvirt, ושינה איך פעמיים:

גרסת TrueNAS שם libvirt היכן השם שלך חי
25.10 "Goldeye" {id}_{name}, כמו 1_debian בשם ה-libvirt, אחרי {id}_
26 ה-UUID של המכונה, כמו 550e8400-e29b-41d4-a716-446655440000 רק ב-<title> של ה-XML של הדומיין

במארח ש-BombVault מזהה כ-TrueNAS, או ש-PLATFORM=truenas מציין, רשימת הגיבויים מציגה את השם שלך: debian במקום 1_debian ב-25.10, וה-<title> במקום ה-UUID ב-26, או ה-UUID כשאין כותרת. זה רק מה שהרשימה מציגה. כל קריאת virsh וההגדרות לכל מכונה עדיין משתמשות בשם ה-libvirt, ולכן גיבוי ושחזור תמיד פועלים על המכונה הנכונה. ב-TrueNAS 26 זה אומר שמכונה ש-TrueNAS יוצר מחדש עם UUID חדש מופיעה כפריט חדש בלי ההגדרות שלה, גם אם השם שלה נראה אותו דבר. ב-Unraid שום דבר לא משתנה, גם למכונה ששמה 10_Windows.

הסכמה של 25.10 נראתה בתיבת TrueNAS SCALE 25.10 אמיתית. הסכמה של TrueNAS 26 באה מהתיעוד של TrueNAS ונבדקה רק בבדיקות יחידה.

פתרון בעיות

תסמין סיבה ופתרון
בדיקת חיבור נתקעת ואז נכשלת אי אפשר להגיע למארח. בודקים את שלב 3 (רשת, VLAN, חומת אש) ואת Host SSH: Address ו-Port.
Permission denied (publickey) למרות שהמפתח ב-authorized_keys StrictModes של sshd דוחה את קובץ המפתחות כי לתיקייה מעליו יש בעלים או הרשאות שגויים. זה נפוץ כש-/root/.ssh הוא קישור לכונן ה-flash FAT (/boot/config/ssh/...), שניתן לכתיבה לכולם. יומן המארח מציג Authentication refused: bad ownership or modes for directory .... מוסיפים StrictModes no להגדרות ה-sshd של המארח בחלק הגלובלי, לפני כל בלוק Match, ואז מריצים /etc/rc.d/rc.sshd restart. Unraid כותב את /etc/ssh/sshd_config מחדש בכל אתחול, ולכן משמרים את זה כמתואר להלן.
Permission denied (publickey) והמפתח באמת חסר חוזרים על שלב 2 ובודקים ש-SSH מופעל.
Host key verification failed מריצים docker exec BombVault rm -f /config/ssh/known_hosts ובודקים שוב.
הבדיקה בשלב 3 אומרת UNREACHABLE הקונטיינר לא יכול להגיע לפורט ה-SSH של המארח. ראו שלב 3: גישת מארח לרשתות מותאמות, ניתוב VLAN, או כתובת ה-shim או ה-LAN.
המשתנים חסרים תחת Edit מייבאים את התבנית שוב. קונטיינר קיים שומר את התצורה השמורה הישנה שלו.
מכונה עם תצלומי Unraid נכשלת ומציינת קובץ אי אפשר לעקוב אחרי שרשרת ה-backing של הדיסק עד סופה. בודקים במנהל המכונות שכל התצלומים של הדיסק הזה עדיין שם, ושהקובץ שצוין קיים מתחת ל-/mnt.

עוד תסמינים של מכונות וירטואליות בפתרון בעיות.

שמירה לאורך אתחולים

Unraid כותב את /etc/ssh/sshd_config מחדש בכל אתחול, ולכן שינויים בצד המארח צריכים להיעשות שוב באתחול:

  • פורט ה-SSH נשמר ב-/boot/config/ident.cfg, ונקבע דרך ממשק הרשת של Unraid.
  • StrictModes no, אם היה צריך אותו למעלה, לא נשמר. מחילים אותו באתחול מ-/boot/config/go:
# /boot/config/go: keep SSH key auth working with flash-based authorized_keys
( for i in $(seq 1 30); do [ -f /etc/ssh/sshd_config ] && break; sleep 2; done
  grep -q '^StrictModes no' /etc/ssh/sshd_config || \
    sed -i '0,/^Match /s/^Match /StrictModes no\n&/' /etc/ssh/sshd_config
  # SIGHUP reloads sshd's config in place: it keeps the listener + open sessions
  # and avoids the "killing listener process / Restarting SSH server daemon"
  # warning that `rc.sshd restart` prints at boot. Only reload if sshd is already
  # up; if it starts after this runs, it reads the edited config on its own.
  pidof sshd >/dev/null && killall -HUP sshd ) &
  • מפתח ה-SSH של BombVault ו-~/.ssh/config חיים ב-/config (appdata) ונוצרים מחדש בכל הפעלה, ולכן הם שורדים הפעלות מחדש של הקונטיינר והמארח.

אבטחה

המפתח המאושר מעניק root במארח, אותה רמת אמון כמו שקע Docker ש-BombVault כבר משתמשת בו. משאירים את BombVault ברשת מהימנה. המפתח הוא ed25519, נשמר עם הרשאה 0600 ב-appdata, ומפתח המארח ננעץ. כל פקודת מארח מועברת כארגומנטים נפרדים, לעולם לא דרך מעטפת מרוחקת, וחיבור ה-SSH משתמש ב-BatchMode ובמגבלת זמן לחיבור.