ההבדלים בין Snapshot, גיבוי ו-Clone בשרתי VPS
מה ההבדל בין Snapshot לגיבוי בשרת VPS? למדו מדוע Snapshot אינו נחשב לגיבוי, מתי להשתמש ב-Clone ומהן הפעולות הקריטיות שיש לבצע בשרת משוכפל כדי למנוע התנגשויות זהות.
מהם למעשה snapshot, גיבוי ו-clone
snapshot של VPS הוא תמונת דיסק של השרת שלכם, המוחזקת על ידי ספק השירות, בתשתית של הספק, בתוך החשבון שלכם. גיבוי הוא עותק עצמאי של הנתונים שלכם שניתן לשחזר במקום אחר, ללא עזרת הספק שהחזיק את המקור. clone הוא מופע (instance) חדש שנפרס מתוך snapshot, כך שהוא מתחיל את חייו כעותק מדויק של המקור, כולל הזהות שלו.
הם פותרים בעיות שונות. snapshot מאפשר לבצע roll back לשדרוג כושל בתוך דקות, אך הוא אינו מועיל במקרה של חשבון שנסגר. גיבוי שורד גם אם הספק מפסיק את פעילותו, והשחזור שלו אורך זמן רב יותר מכיוון שיש לבנות מחדש את המכונה תחילה. clone מעניק לכם שרת שני פעיל בצעד אחד, אך הוא גם יוצר שתי מכונות ש"סבורות" שהן אותה מכונה בדיוק.
מדוע snapshot של VPS אינו נחשב לגיבוי
הבעיה טמונה בתחום הכשל (failure domain), ולא באיכות התמונה. snapshot מאוחסן על פלטפורמת האחסון של ספק השרתים שלך, בדרך כלל באותו אזור גיאוגרפי שבו נמצא השרת, ותמיד בתוך אותו חשבון משתמש. אירוע בודד עלול להוביל לאובדן השרת וה-snapshot שלו בו-זמנית.
- החשבון מושעה, תשלום נכשל, או שפרטי הגישה נגנבו.
- אדם או סקריפט בעל גישת API מוחק את ה-instance. אצל ספקים רבים, מחיקת instance גוררת מחיקה של ה-snapshots שלו. קראו את התיעוד של הספק שלכם לפני שתניחו אחרת.
- האזור הגיאוגרפי חווה תקלה וכל המשאבים בו הופכים לבלתי נגישים בבת אחת.
- תהליך שרץ כ-root על השרת מוצא את ה-API token של הספק שהשארתם ב-
/root, ומוחק את ה-snapshots לפני שהוא ניגש לדיסק.
גיבוי הוא עותק ששורד את כל ארבעת התרחישים הללו. המבחן הוא שאלה אחת: אם חשבון הספק שלכם היה מפסיק להתקיים היום אחר הצהריים, מה הייתם יכולים לשחזר, והיכן הייתם משחזרים זאת? כל מה שנכשל במבחן הזה הוא כלי לביצוע rollback בלבד. המשיכו ליצור snapshots, כיוון שאין דרך מהירה יותר לשחזור. לאחר מכן, שמרו עותק שני על אמצעי אחסון שהספק שלכם אינו שולט בו.
הכלל הישן נותר בתוקף: שלושה עותקים של הנתונים, על שני סוגי אחסון, כאשר אחד מהם מחוץ לפלטפורמה. snapshot של הספק בתוספת מאגר גיבוי של restic על תשתית נפרדת מכסה דרישה זו באמצעות שני רכיבים בלבד.
מדוע snapshot של מסד נתונים פעיל עלול להשתחזר במצב תקול
snapshot של ספק ענן מעתיק את התקן הבלוקים (block device) כפי שהוא ברגע נתון. הוא אינו מבקש מהיישומים לעצור לפני כן, והוא אינו יכול לראות נתונים שעדיין נמצאים ב-page cache. לכן, התמונה היא לכל היותר crash-consistent. היא נראית בדיוק כפי שהדיסק היה נראה אילו מישהו היה מנתק את כבל החשמל.
רוב רכיבי המערכת מתמודדים עם זה. ext4 ו-XFS מבצעים replay ל-journal שלהם בזמן ה-mount, כך שמערכת הקבצים עולה. PostgreSQL מבצע replay ל-write-ahead log שלו בעת ההפעלה, והלוג מציין זאת:
LOG: database system was not properly shut down; automatic recovery in progressInnoDB מבצע פעולה דומה ומדפיס שורות התאוששות מקריסה (crash recovery) במהלך העלייה. התאוששות זו היא עבודתו התקינה של מסד הנתונים, ולכן snapshot של כרך בודד של PostgreSQL או MySQL במצב שקט בדרך כלל ישתחזר היטב.
המקרים שבהם crash-consistent אינו מספיק הם אמיתיים, והם אלו שגורמים לנזק. אם הנתונים שלכם פרוסים על שני כרכים, הדיסק של ה-root ודיסק נתונים נפרד מצולמים ברגעים שונים, כך שקבצי הנתונים וספריית הלוגים עלולים לא להסתנכרן, ולמנגנון ההתאוששות לא יהיה מידע תקין לביצוע replay. כל קובץ שהיישום כותב מבלי לקרוא ל-fsync, כגון העלאה שהתקבלה בחלקה או קובץ תור, עלול לחזור כשהוא קטוע. כל דבר שהיישום מחזיק בזיכרון ומבצע לו flush לפי טיימר פשוט לא יהיה קיים בתמונה.
לכן, בצעו dump לדיסק לפני שאתם לוקחים את ה-snapshot. כך התמונה תכיל קובץ אחד שאתם יודעים שהוא עקבי פנימית, ללא קשר למצב שבו נמצאים קובצי הנתונים החיים.
sudo -u postgres pg_dumpall --clean --file=/var/backups/pg-$(date +%F).sql
sudo mysqldump --single-transaction --routines --all-databases > /var/backups/mysql-$(date +%F).sql--single-transaction מספק dump עקבי של טבלאות InnoDB מבלי לחסום כותבים, כיוון שה-dump רץ בתוך טרנזקציית repeatable-read אחת. הוא אינו מכסה טבלאות MyISAM, הזקוקות לנעילה או לעצירת השרת. ודאו שה-dump אינו ריק ואינו קטוע לפני שאתם מסתמכים עליו: tail -n 1 /var/backups/mysql-$(date +%F).sql על mysqldump מלא מסתיים בהערת Dump completed.
אם יש לכם כרך נתונים נפרד, ניתן להקפיא אותו למשך השניות שה-snapshot דורש:
sudo fsfreeze -f /srv
# take the snapshot from your provider's panel or API
sudo fsfreeze -u /srvהקפיאו כרך נתונים בלבד. לעולם אל תקפיאו את /. מערכת קבצים מסוג root קפואה חוסמת כל כתיבה במכונה, כולל ה-shell שבו תשתמשו כדי להקליד את פקודת ה-unfreeze, כך שתנעלו את עצמכם בחוץ ותאלצו להמתין ל-hard reset.
החצי המרוחק: restic או Borg
ה־snapshot הוא החצי המהיר. העותק המרוחק הוא החצי שישרוד את קריסת ספק השירות שלכם. restic הוא ברירת מחדל טובה מכיוון שהוא מבצע דה-דופליקציה, מצפין בצד הלקוח, וכותב לאחסון אובייקטים תואם S3, ל-SFTP או לספרייה רגילה. שרת VPS המשמש כיעד אחסון מרוחק עובד היטב במקרה זה, כיוון שמאגרי גיבוי דורשים נפח אחסון ולא IOPS גבוה.
sudo apt update && sudo apt install -y restic
sudo sh -c 'umask 077; head -c 24 /dev/urandom | base64 > /root/.restic-pass'
sudo chmod 600 /root/.restic-pass
sudo cat /root/.restic-passהעתיקו את סיסמת הגישה למנהל סיסמאות כעת, במכשיר שאינו השרת הזה. לא ניתן לפתוח מאגר של restic ללא סיסמה זו ואין נתיב לשחזור. אם העותק היחיד של הסיסמה היה על השרת שאבד, הגיבוי הוא רעש מוצפן בלבד.
export RESTIC_REPOSITORY="s3:https://s3.example.com/vps-backups"
export RESTIC_PASSWORD_FILE=/root/.restic-pass
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
sudo -E restic init
sudo -E restic backup /etc /srv /var/backups
sudo -E restic snapshotssudo -E שומר על המשתנים הללו, שכן בלעדיו root מקבל סביבה נקייה ו-restic מדווח שלא צוין מיקום מאגר. restic snapshots אמור להציג את ההרצה שביצעתם זה עתה, עם שם המארח והנתיבים שלה. בצעו אימות למאגר עצמו לפי לוח זמנים, וקראו חלק מהנתונים בחזרה במקום להסתפק בבדיקת המבנה בלבד:
sudo -E restic check --read-data-subset=5%
sudo -E restic restore latest --target /tmp/restore-check
sudo -E restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --pruneגיבוי שלא נבדק הוא בגדר ניחוש. בצעו שחזור ל-VPS אחר לפחות פעם אחת, מדדו את הזמן, ורשמו אותו, כיוון שמספר זה הוא יעד השחזור האמיתי שלכם. Borg הוא הבחירה המוצקה השנייה והוא מאחסן את המאגר שלו באמצעות SSH במקום באחסון אובייקטים; ההבדלים והשיקולים מפורטים ב-השוואה בין restic ל-BorgBackup.
מה יש לתקן לפני שמעבירים VPS משוכפל לסביבת ייצור
שיבוט הוא העתק מדויק. זהו היתרון המרכזי שלו, אך גם הבעיה. כל מה שהפך את השרת המקורי לייחודי משוכפל, ועותקים זהים יוצרים התנגשויות.
צרו מחדש את מפתחות ה-SSH של המארח. השיבוט נושא את קובצי ה-/etc/ssh/ssh_host_* של המקור, לכן שני שרתים מציגים את אותה זהות מארח. כל מי ששולט באחד מהם יכול להתחזות לשני מול כל לקוח שאישר את המפתח הזה, ו-SSH לא יציג אזהרה, כיוון שהמפתח הוא בדיוק זה שהלקוח ציפה לו.
sudo rm -f /etc/ssh/ssh_host_*
sudo ssh-keygen -A
sudo systemctl restart ssh
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pubהפקודה ssh-keygen -A יוצרת מפתח חדש מכל סוג שה-daemon מצפה לו. ה-fingerprint מהפקודה האחרונה חייב להיות שונה מזה שהיה בשרת המקורי. הסשן הנוכחי שלכם ישרוד את האתחול, כיוון שהפעלה מחדש של sshd לא סוגרת חיבורים קיימים. בצעו זאת לפני שמישהו מתחבר לשיבוט. אם תדחו זאת, כל לקוח שכבר נתן אמון במפתח שעבר בירושה יקבל WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! ויצטרך להריץ ssh-keygen -R <host> תחילה.
אפסו את מזהה המכונה (machine ID). ה-/etc/machine-id הוא מזהה ייחודי ש-systemd מייצר פעם אחת, בעלייה הראשונה, והשיבוט יורש אותו.
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 rebootקובץ /etc/machine-id ריק מורה ל-systemd לייצר ערך חדש באתחול הבא, וזו הסיבה שאתם מרוקנים את הקובץ במקום למחוק אותו. שני דברים משתבשים כאשר הוא משוכפל. בתמונות (images) שמקבלות כתובת דרך DHCP, ה-systemd-networkd גוזר כברירת מחדל את מזהה לקוח ה-DHCP שלו מה-machine ID, לכן שני השיבוטים מבקשים כתובת בתור אותו לקוח והשרת נותן לשניהם את אותה כתובת. בנוסף, journald חותם כל רשומה עם ה-machine ID, לכן אוסף לוגים מרכזי יתייק את שני השרתים תחת מכונה אחת. הריצו cat /etc/machine-id לאחר ה-reboot וודאו שהערך השתנה.
שנו את שם המארח (hostname).
sudo hostnamectl set-hostname web-02
grep 127.0.1.1 /etc/hostsהפקודה hostnamectl כותבת את /etc/hostname ומחילה את השם באופן מיידי. היא לא נוגעת ב-/etc/hosts, לכן עליכם לערוך את שורת ה-127.0.1.1 כדי שתתאים. אם תדלגו על כך, השם החדש לא יתורגם לשום מקום, וכל קריאת sudo תמתין לחיפוש שנכשל ותדפיס sudo: unable to resolve host web-02: Name or service not known.
רעננו כל פרט אימות (credential) שמוטמע בתמונה. השיבוט מחזיק את הסודות של המקור, וכעת שתי מכונות יכולות לפעול כשרת המקורי. עברו על קובצי ה-authorized_keys של SSH, אסימוני (tokens) ספק ו-API של DNS, קובצי .env של יישומים, סיסמאות מסד נתונים, מפתחות TLS פרטיים, אסימוני רישום לניטור, וסיסמת מאגר ה-restic. הפקודה הבאה תמצא את רובם:
sudo grep -rIlE 'PASSWORD|SECRET|TOKEN|API_KEY' /etc /srv /opt /home 2>/dev/null
sudo find / -name '.env' -not -path '/proc/*' -not -path '/sys/*' 2>/dev/nullאם השיבוט הוא עותק לבדיקות שלעולם לא ישרת תעבורה, בטלו את התוקף במקום לרענן. שרת staging שמחזיק אסימון API פעיל של סביבת ייצור הוא שרת ייצור עם רמת אבטחה נמוכה יותר.
כבו משימות שרצות כעת פעמיים. שני שרתים שמריצים את אותו ה-crontab יפנו לאותן מערכות חיצוניות באותה דקה.
systemctl list-timers --all
sudo crontab -l
sudo ls -l /etc/cron.d /etc/cron.dailyהמקרה של restic ראוי לציון מיוחד, כיוון שהוא משחית את מדיניות השמירה שלכם במקום רק להיכשל בקול. restic מתייג כל snapshot עם שם המארח, ו-restic forget --keep-daily 7 מחיל את המדיניות שלו לפי מארח. שתי מכונות שמדווחות על אותו שם מארח מטופלות כמכונה אחת, לכן שבעה snapshots "יומיים" יכולים כולם להגיע מהשיבוט בזמן שה-snapshots של המקור נמחקים. תקנו את שם המארח לפני הרצת הגיבוי הראשונה, או עצרו את ה-timer בשיבוט. המקרה של certbot פשוט יותר: שני שרתים שמחדשים את אותם שמות פוגעים במגבלת ה-rate limit של רשות התעודות עבור תעודות כפולות, והריצה שנכשלת תציג שגיאה על כך שהונפקו יותר מדי תעודות עבור אותו סט שמות בדיוק. שיבוט שהדומיין שלו עדיין מצביע על המקור לא יכול לעבור את ה-HTTP challenge בכל מקרה, לכן השביתו שם את החידוש.
טפלו בסוכן הניטור. רוב הסוכנים מזהים את עצמם לפי שם מארח או לפי קובץ מזהה שנכתב בזמן ההתקנה, לכן שני סוכנים שמדווחים כמאחר אחד יערבבו את המדדים שלהם לסדרה אחת. גרפי ה-CPU יציגו אז ערכים שאף מכונה בודדת לא יצרה, והתראות יקפצו ללא הפסקה. עצרו והסירו את הסוכן בשיבוט, או רשמו אותו מחדש תחת שם המארח החדש לפי התיעוד של הספק שלכם.
בדקו את תצורת הרשת עבור הכתובת של המקור. אם התמונה נושאת כתובת סטטית ב-netplan, השיבוט יתפוס IP ששייך למכונה אחרת.
ip -br addr
sudo grep -r addresses /etc/netplan/נקו את מצב ה-cloud-init אם השיבוט הזה הופך לתבנית (template).
sudo cloud-init clean --logsפעולה זו מסירה את המצב של cloud-init תחת /var/lib/cloud, כך שהאתחול הבא יריץ שוב את מודולי ה-first-boot, מה שכולל יצירת מפתחות מארח של SSH אם הם לא קיימים. גרסאות מסוימות מציעות גם דגל לאיפוס ה-machine ID. הריצו cloud-init clean --help על התמונה שלכם כדי לראות מה נתמך אצלכם, במקום להסתמך על רשימת דגלים ממקור אחר.
מתי להשתמש בכל שיטה
ביצוע Rollback לשדרוג מסוכן: צילום Snapshot. בצעו את הצילום דקות לפני השינוי, הריצו את השדרוג, ושחזרו את ה-image אם משהו משתבש. שחזור מוחק כל כתיבה שבוצעה מאז ה-snapshot, לכן בשרת שמטפל בתעבורה חיה, בצעו תחילה dump למסד הנתונים ודעו בדיוק איזה חלון זמן יאבד. עבור do-release-upgrade בשרת שניתן להוריד מהרשת לעשר דקות, ה-snapshot הוא התוכנית המלאה.
מעבר לתוכנית גדולה יותר: פריסת Clone. בנו את ה-clone מתוך ה-snapshot על גבי התוכנית הגדולה יותר, עברו על רשימת הזהויות שלעיל, ולאחר מכן בדקו אותו מול ה-IP שלו לפני העברת תעבורה כלשהי. הנמיכו את ה-TTL של ה-DNS יום מראש כדי שהמעבר יהיה מהיר, והשאירו את השרת המקורי פעיל עד שהשרת החדש יחזיק תעבורה אמיתית. ודאו תחילה שהתוכנית הגדולה יותר אכן מהירה יותר עבור עומס העבודה שלכם, באמצעות אותה שיטת benchmark בשני השרתים, כיוון שיותר vCPUs על חומרה עמוסה יותר הם לא תמיד שדרוג.
בניית תבנית (Template): צילום Snapshot למכונה נקייה. התקינו ואבטחו שרת אחד, ולאחר מכן הסירו כל דבר ייחודי לפני יצירת ה-image. ללא host keys, מזהה מכונה (machine ID) ריק, ללא authorized_keys אישי, ללא אישורי גישה, ועם cloud-init נקי. בצעו snapshot למצב זה. כל instance שייפרס ממנו ייצר זהות משלו בעלייה הראשונה, כך שרשימת הבדיקה שלעיל תפסיק להיות רשימת בדיקה. שלבו זאת עם עשר הדקות הראשונות הסטנדרטיות ב-VPS חדש כך שהתבנית כבר תכיל את העבודה שהייתם נדרשים לחזור עליה בכל פעם.
FAQ
האם snapshot של VPS נחשב לגיבוי?
לא, כיוון שהוא חולק את אותו תחום כשל (failure domain) עם השרת שממנו נוצר. ה-snapshot מאוחסן בתשתית של ספק הענן, תחת החשבון שלך ובדרך כלל באותו אזור גיאוגרפי. השעיית חשבון, גניבת מפתח API או מחיקה בשוגג של ה-instance עלולים להוביל לאובדן השרת וה-snapshots שלו בפעולה אחת; אצל ספקים רבים, מחיקת ה-instance גוררת מחיקה אוטומטית של ה-snapshots שלו. snapshot הוא אמצעי ה-rollback המהיר ביותר שעומד לרשותך, לכן מומלץ להמשיך ליצור אותם, אך יש לשמור עותק מוצפן נוסף בתשתית שאינה בשליטת הספק שלך.
האם עלי לעצור את מסד הנתונים לפני יצירת snapshot?
לא תמיד, אך עליך לקחת בחשבון את מצב הנתונים שתקבל. snapshot של ספק הוא crash-consistent, כלומר התמונה משקפת את מצב הדיסק כפי שהיה נראה לאחר הפסקת חשמל פתאומית. PostgreSQL ו-InnoDB מבצעים שחזור ממצב זה בעת העלייה, ו-PostgreSQL יציג לוגים מסוג database system was not properly shut down; automatic recovery in progress במהלך התהליך. השחזור אינו מובטח כאשר הנתונים פרוסים על פני שני כרכים (volumes) שצולמו ברגעים שונים, או כאשר יישום כותב נתונים ללא fsync. מומלץ לבצע pg_dumpall או mysqldump --single-transaction לדיסק לפני הצילום, כדי להבטיח שהתמונה מכילה קובץ אחד שאתה יודע שהוא עקבי.
מדוע שני שרתים משוכפלים מתנגשים על אותה כתובת IP?
מכיוון שהם חולקים את אותו /etc/machine-id. בתמונות מערכת המשתמשות ב-DHCP, ה-client identifier של systemd-networkd נבנה כברירת מחדל מתוך ה-machine ID; לכן, שני השיבוטים מבקשים כתובת מאותו לקוח, ושרת ה-DHCP מציע לשניהם את אותה הכתובת. יש לאפס את /etc/machine-id ל-0 בתים, למחוק את /var/lib/dbus/machine-id, ליצור קישור סימבולי (symlink) חזרה ל-/etc/machine-id, ולבצע reboot כדי ש-systemd ייצור ערך חדש. סיבה נפוצה נוספת היא כתובת סטטית שכתובה ב-/etc/netplan/, אשר הועתקה כפי שהיא לשיבוט; יש לבדוק זאת באמצעות ip -br addr.
מהי הדרך המהירה ביותר לוודא ששיבוט בטוח להעברה לסביבת ייצור?
יש להשוות ארבעה פרמטרים מול השרת המקורי. הרץ ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub בשני השרתים וודא שה-fingerprints שונים. הרץ cat /etc/machine-id בשניהם וודא שהערכים שונים. הרץ hostnamectl status וודא שהשם חדש ומתורגם לכתובת הנכונה, כך ש-sudo לא יציג אזהרה. לאחר מכן, הרץ systemctl list-timers --all ועצור כל טיימר שמתקשר למערכת משותפת, כגון גיבויים, חידוש תעודות או סוכן ניטור, עד שתחליט איזה שרת אחראי על המשימה הזו.