פריסה ללא השבתה ב-Kubernetes: הגדרה ומיגרציות

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

היכולות שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

Zero-downtime deployment: מה נשבר בעת פריסה ב-Kubernetes

Zero-downtime deployment נשבר לא בשלב בניית ה-image, אלא ברגע שמורידים פוד מהאיזון.

תרחיש טיפוסי: RollingUpdate עם maxUnavailable גדול מאפס שולח פודים למחיקה מיד לאחר האות, האפליקציה לא מספיקה להשלים בקשות פעילות, והמאזן עדיין מחזיק אותן בנקודות הקצה שלו — החיבורים נקרעים באמצע התגובה, וחלק מהלקוחות מקבלים 502 במקום נתונים.

נקודת כשל שנייה — הפעלת קונטיינר. האפליקציה עונה ל-readiness probe לפני שחיממה מטמון, הקימה pool חיבורים ל-DB וקראה את התצורה.

התעבורה כבר זורמת לפוד לא מוכן, בקשות נופלות ב-timeout, פודים נכנסים ל-restart — והשירות הופך ללא זמין בדיוק באותה דקה שבה ה-release ממילא העלה את העומס על הרפליקות שנותרו.

תרחיש כשל שלישי — rollback תחת עומס. הגרסה הישנה והחדשה עובדות זמן מה מול אותה סכמה: המיגרציה בוצעה קדימה, האפליקציה של ה-release הקודם לא מכירה את העמודות החדשות ומציפה שגיאות בעת עדכון הגרסה. rollback שאמור לקחת שניות הופך לחקירת אירוע ולשחזור נתונים.

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

Release רציף ללא downtime: מה משתנה בעבודה

Downtime ב-release נוצר לא מהחלפת image, אלא מהרגע שבו הגרסה החדשה מתחילה לקבל תעבורה, והישנה כבר הפסיקה. readiness probe מחזיק מופע מחוץ לאיזון העומסים עד לחימום, hook לפני עצירה נותן לחיבורים פתוחים להיסגר, ואסטרטגיית עדכון עם אפס עותקים לא זמינים שומרת על מרווח ברפליקות.

readiness probe, hook לפני עצירה ומרווח של עותק אחד — מנגנונים סטנדרטיים של Kubernetes, שהם גם מסירים את החלון: השירות עונה לאורך כל הפריסה. הגדרה כמו maxUnavailable: 0 עם maxSurge: 1 מפסיקה להיות הסכמה ידנית והופכת לחלק מתיאור ה-deployment.

מוכנות לעומס נסגרת עם תקציב הפרעות: הוא לא נותן ל-update או לפינוי node לכבות יותר מדי עותקים בבת אחת, וקונטיינר שמחמם ממלא את המטמון לפני שהמופע נכנס לסבב.

מיגרציות — מקור נפילה נפרד, וכאן עובדת הסכמה «הרחב — החלף — כווץ». תחילה מוסיפים לסכמה שדה ששתי גרסאות הקוד מבינות, אחר כך מעבירים את התעבורה ללוגיקה החדשה, ורק אחר כך מוחקים את הישן. ה-release מפסיק להיות קפיצה אטומית — סכמה לא תואמת כבר לא שייכת למחלקות כשל.

אוטומציית שחרור גרסאות מביאה זאת ל-pipeline: הגרסה מתוארת באופן דקלרטיבי, rollback — החזרת התיאור הקודם, ולא rebuild ידני.

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

Zero-downtime deployment: השוואת אסטרטגיות פריסה

בחירת אסטרטגיה נקבעת לפי מה שיקר יותר דווקא במערכת שלכם: מהירות rollback, צריכת משאבים על מסלול שני או מורכבות ניתוב תעבורה. כל המצבים נשענים על אותה קבוצת פרימיטיבים — רפליקות, בדיקות מוכנות, שירות איזון עומסים — ונבדלים רק בסדר שבו הגרסאות מחליפות זו את זו.

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

אסטרטגיית פריסה מתי מתאימה מגבלות
עדכון הדרגתי שירותים ללא state עצמי, releases סדירים הגרסה הישנה והחדשה חיות במקביל: סכמת הנתונים, פורמטי הודעות ותגובות חייבים להיקרא על ידי שתיהן
Blue-green contour ממשקים קריטיים שבהם צריך rollback מיידי על ידי החלפת תעבורה צריכה כפולה של משאבים בזמן הפריסה, תמיכה בשני סטים של תצורה והרשאות
Canary release שינויים עם השפעה לא ברורה, צריך מדידה על חלק מהקהל נדרשים מדדים לפי גרסאות וניתוב לפי חלק תעבורה, ניטור מאריך את ה-release
יצירה מחדש מלאה שירותים פנימיים ו-batch, משימות רקע יש חלון אי-זמינות, לא מתאים לממשקים חיצוניים

בנפרד כדאי להתייחס למיגרציית סכמה: מבצעים אותה בשלושה צעדים — הרחבת המבנה, העברת נתונים, הסרת השדה הישן — ורק אחרי ששתי גרסאות האפליקציה הפסיקו לפנות לפורמט הקודם.

שלבי יישום: מ-audit של קלאסטר ועד מסירת סכמות

השלבים הולכים ברצף, וכל אחד נסגר בתוצאה ניתנת לבדיקה — בלעדיה לא מתחילים את הצעד הבא. אין לשנות את הסדר: הגדרת פריסה לפני audit של סביבות נותנת ביטחון שווא, ותקנון בלי בדיקה על סביבת תרגול לא שורד release ראשון.

  1. Audit של קלאסטר. נפרק איך השירותים פרוסים: מספר רפליקות, בדיקות readiness, נכונות סיום פודים וגורל החיבורים הפתוחים בעת עצירה. צוואר הבקבוק בדרך כלל אחד — מאזן חיצוני משותף בלי שליטה על חלק התעבורה.
  2. תוכנית יישום. מפת סביבות, הפרדת הרשאות וסודות, תור שינויים לפי שירותים — כך ש-releases לא יפריעו זה לזה, ו-rollback יתאפשר בכל שלב.
  3. הגדרת פריסה. עדכון הדרגתי עם מגבלה על מספר רפליקות לא זמינות בו-זמנית, בדיקות readiness עם ספים עובדים, השהיה לסגירת חיבורים לפני עצירת קונטיינר.
  4. מיגרציות סכמת נתונים. מפרקים לצעדים תואמים: הרחבה, העברה, מחיקת שדה ישן. הגרסה הישנה והחדשה של השירות עובדות בו-זמנית, rollback לא מאבד נתונים.
  5. בדיקה על סביבת תרגול. הרצת release תחת עומס ועם הדמיית כשלים: ניתוק node, קטיעת חיבורים, שגיאה בגרסה החדשה. מחשבים אובדן תעבורה וזמן התאוששות.
  6. מסירת תקנונים. מפות סביבות, תצורות, סדר release ו-rollback, רשימת מדדים ותנאים שבהם הפריסה נעצרת אוטומטית.

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

מה נשאר אצל הלקוח: תצורות, סכמות, תקנונים

אנחנו לא משאירים את הלקוח עם סט הסכמות בעל פה: כל מה שקובע את התנהגות המערכת בפריסה ללא downtime חי במאגר יחד עם היסטוריית השינויים. מהנדס שיצטרף לצוות מאוחר יותר יקרא לא «ככה זה התפתח היסטורית», אלא את הסיבה לכל פרמטר שהוגדר.

  • תצורות פריסה — תיאורים דקלרטיביים עם פרמטרי מוכנות, השהיה בין גלים ותנאים ל-rollback אוטומטי.
  • מפות סביבות — מפת סביבות, ההבדלים ביניהן לפי נתונים ועומס, סדר קידום build ביניהן.
  • מדיניות גישה — הרשאות ניתנות לתפקידים, לא לאנשים: גבולות «מי מפעיל פריסה, מי קורא לוגים» שורדים תחלופת חברי צוות.
  • תיעוד — מסלול הבקשה דרך גרסה מתעדכנת, בדיקות חובה וסדר פעולות כשיש פער בין המצב המצופה למצב בפועל.
  • תקנון תורנות — אילו אותות מעבירים את התורן למצב rollback, למי עוברת הסלמה ומה נחקר לאחר אירוע.
  • תרחישי מיגרציה — סדר שינויי סכמת הנתונים, תואם לגרסה הישנה והחדשה של האפליקציה בזמן החלפת התעבורה.
  • בדיקות לפני שחרור — סט הרצות אוטומטיות שבלעדיהן ה-build לא עובר הלאה מסביבת הבדיקות.

כל סעיף — קובץ עבודה, לא תיאור במילים: אפשר להשוות אותו למצב הנוכחי של המערכת ולראות מיד את הפער.

מקרה בוחן: release של שירות תשלומים ללא עצירת תעבורה

שירות תשלומים שחררו בחלון לילה: קליטת תשלומים הופסקה ל-30–40 דקות, עדכנו, והעלו בחזרה. כשמשהו השתבש, עשו rollback ידני — עוד ארבעים דקות עד שעה, וחלק מהפעולות היה צריך לחשב בנפרד. כל שחרור כזה תפס משמרת לילה של תורנים.

מה שינו

ב-Kubernetes עברו לאסטרטגיית RollingUpdate עם הפרמטרים maxUnavailable: 0 ו-maxSurge: 1: רפליקה חדשה נכנסת לאיזון העומסים רק אחרי readiness probe מוצלח, והישנה מקבלת אות לעצירה ומשלימה בקשות שכבר התקבלו.

מיגרציות סכמה הוצאו מהתחלת הקונטיינר לשלב נפרד עם נעילה — רפליקות במקביל כבר לא מריצות את אותה סכמה ולא מפילות את השחרור. שדות ב-DB הוספו לפי סכמת expand-contract: קוד חדש קורא גם נתונים ישנים וגם חדשים, ומחיקות לא תואמות עוברות ב-release הבא.

מדדים לפני ואחרי

זמן שחרור גרסה הצטמצם מ-40–60 דקות של חלון לילה ל-6–8 דקות בשעות עבודה. שיעור ה-rollbacks ירד מכ-כל פריסה חמישית לאחת מעשרים, וכמעט כולם אוטומטיים — לפי מדדי שגיאות, בלי חקירה ידנית. פניות משתמשים לעמוד שגיאה בזמן release — אפס: חיבורים ממתינים לתגובה מרפליקה חיה במקום להיקרע.

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

במה פריסת גרסה חדשה שונה מהפעלה מחדש של שירות?

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

ההבדל נראה באופן שבו נחשבת המוכנות לתעבורה. בהפעלה מחדש השירות חוזר לאיזון העומסים ברגע שהתהליך פתח פורט. בעדכון קונטיינר צריך probe שבודקת לא «האם התהליך חי», אלא «האם הוא מוכן לשרת בקשה»: חיבור ל-DB, מטמון מחומם, זמינות תלויות. בלעדיה הגרסה החדשה מקבלת פניות בשלב האתחול — ומכאן שגיאות כבר בבקשה הראשונה.

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

ב-Kubernetes השלבים האלה מתוארים באופן דקלרטיבי: כמה עותקים להעלות במקביל, איזה חלק מותר להוציא מכלל שימוש, כמה להמתין להשלמה. אלה פרמטרים של manifest, לא פעולות ידניות של מהנדס ברגע ה-release.

בדיוק ההבדל הזה מפריד בין הפעלה מחדש ל-zero-downtime deployment: הפריסה נמשכת יותר זמן, אבל המשתמש לא רואה downtime. לשירותים עם DB נוספים גם סדר המיגרציות — הרחבת סכמה לפני הפריסה, מחיקת שדות מיושנים אחרי.

ספקות טכניים: מיגרציות סכמות ותאימות לאחור

שינוי מבנה הנתונים — זה שבאמת שובר zero-downtime deployment. בזמן הפריסה חיים בקלאסטר בו-זמנית פודים מהדור הישן והחדש, ושניהם עובדים עם אותו DB. הסכמה חייבת להישאר תואמת לשתי גרסאות הקוד: אחרת החדשה נופלת על נתונים ישנים, והישנה — על חדשים.

לכן מחלקים את המיגרציות לשלבים. תחילה הרחבה: מוסיפים עמודה או טבלה, הקוד כותב גם למקום הישן וגם לחדש בו-זמנית. אחר כך העברת נתונים ברקע במנות, החלפת הקריאה — ורק ב-release נפרד מחיקת השדה הישן. rollback של האפליקציה בסכמה כזו לא מחייב rollback של ה-DB.

מה יקרה אם יהיה צורך לבצע rollback ל-release?

כל עוד המיגרציה בשלב ההרחבה, הגרסה הקודמת עובדת ללא שינויים — rollback הוא חזרה ל-image הקודם. מחיקת עמודות ישנות מוציאים ל-release נפרד לאחר תקופת ניטור, כדי לשמור על אפשרות rollback.

איך לוודא שהגרסה הישנה לא תישבר?

ב-pipeline מוסיפים בדיקת תאימות: ה-release הקודם מורץ מול הסכמה החדשה על עותק בדיקה. שינויים כבדים — בנייה מחדש של אינדקסים, החלפת טיפוס — מבצעים בפעולות לא חוסמות עם מגבלת זמן, אחרת נעילה על טבלה גדולה תעצור כתיבה.

איך מנוהלת גרסאות לסכמה?

בטבלת מיגרציות שהוחלו ובהשוואה ב-pipeline: ה-build לא נפרס אם גרסת הסכמה לא תואמת למצופה. המיגרציות עצמן אידמפוטנטיות ומוחלות לפני עליית פודים, לכן הרצה חוזרת לא שוברת דבר.

נדבר על המשימה: נחזור עם הערכה ותוכנית יישום

כדי שהערכת הפרויקט תישען על הסביבה שלכם ולא על הנחות כלליות, את הדיון במשימה מתחילים בניתוח טכני קצר. חשוב לנו להבין איך נבנה ה-release, איך תעבורה נכנסת לקלאסטר, אילו תהליכים חיים יותר מפוד בודד ומה קורה ל-DB בזמן פריסה. בדרך כלל מספיק שיחה אחת וגישה ל-manifests, לתקנון ה-releases ולמפת השירותים.

מה כדאי להכין לשיחה הזאת:

  • איך פריסת ה-release מתבצעת עכשיו: עדכון רציף של רפליקות, מסלול מקביל או חלק תעבורה לגרסה החדשה.
  • האם readiness probes מוגדרות ואיך השירות משלים בקשות כשהפוד נעצר.
  • איך מוחלות מיגרציות סכמה: בנפרד מהקוד, עם תאימות לאחור או בצעד אחד.
  • האם יש משימות רקע ותורים שאסור להריץ בשתי גרסאות בו-זמנית.
  • אילו בדיקות עוברות לפני פריסה ולפי אילו מדדים אתם מחליטים שה-release הצליח.
  • חלון תחזוקה מותר וחלק מותר של בקשות אבודות בזמן החלפה.

בתגובה נחזור עם הערכת פרויקט: הרכב העבודה לפי שלבים, סדר הפריסה, נקודות rollback ורשימת שינויים בתצורה. תוכנית היישום מחולקת לצעדים כך שהראשון כבר מסיר downtime, והשאר — מחזקים את התוצאה. תארו את המשימה — נחזור עם הערכה ותוכנית.