יום אחד, במהלך עדכון מתוכנן של cluster k8s, גילינו שכמעט כל ה-POD שלנו (כ-500 מתוך 1,000) בצמתים חדשים לא הצליחו להתחיל, והדקות הפכו במהירות לשעות. מחפשים אותנו באופן פעיל אחר הסיבה לשורש, אך לאחר שלוש שעות, ה-PODS עדיין היו בסטטוס ContainerCreating
.
למרבה המזל, זו לא הייתה סביבת הפרוד וחלון התחזוקה נקבע לסוף השבוע. היה לנו זמן לחקור את הנושא ללא כל לחץ.
היכן כדאי להתחיל את החיפוש אחר סיבת השורש? האם תרצה ללמוד עוד על הפתרון שמצאנו? התחברו ותהנו!
הבעיה הייתה שהיו לנו מספר גדול של תמונות docker שצריך למשוך ולהתחיל בכל צומת באשכול בו זמנית. הסיבה לכך היא שמשיכות מרובות של תמונת docker בו-זמנית על צומת בודד יכולות להוביל לניצול גבוה של דיסקים וזמני התחלה קרים מוארכים.
מעת לעת, תהליך התקליטור לוקח עד 3 שעות כדי למשוך את התמונות. עם זאת, הפעם זה היה תקוע לחלוטין, כי כמות ה-PODS במהלך שדרוג ה-EKS (משולב, כאשר אנו מחליפים את כל הצמתים באשכול) הייתה גבוהה מדי.
כל האפליקציות שלנו חיות ב-k8s (מבוסס EKS ). כדי לחסוך בעלויות שלנו עבור DEV env, אנו משתמשים במופעים נקודתיים.
אנו משתמשים בתמונת AmazonLinux2 עבור הצמתים.
יש לנו מספר רב של ענפי תכונה (FBs) בסביבת הפיתוח שנפרסים ברציפות באשכול Kubernetes שלנו. לכל FB יש סט יישומים משלו, ולכל אפליקציה יש סט תלות משלו (בתוך תמונה).
בפרויקט שלנו, כמעט 200 אפליקציות והמספר הזה הולך וגדל. כל אפליקציה משתמשת באחת מ-7 תמונות העגינה הבסיסיות בגודל של ~2 GB. הגודל המקסימלי הכולל של התמונה המאוחסנת בארכיון (ב- ECR ) הוא כ-3 GB.
כל התמונות מאוחסנות ב- Amazon Elastic Container Registry (ECR).
אנו משתמשים בסוג ברירת המחדל של gp3 EBS עבור הצמתים.
זמן התחלה קרה מורחב: הפעלת פוד חדש עם תמונה חדשה עשויה להימשך יותר משעה, במיוחד כאשר מספר תמונות נמשכות במקביל על צומת בודד.
שגיאות ErrImagePull: ErrImagePull
תכופות או נתקעות במצבי ContainerCreating
, מה שמצביע על בעיות עם משיכת תמונה.
ניצול דיסק גבוה: ניצול הדיסק נשאר קרוב ל-100% במהלך תהליך משיכת התמונה, בעיקר בשל הקלט/פלט הדיסק האינטנסיבי הנדרש לביטול הדחיסה (למשל, "unpigz").
בעיות במערכת DaemonSet: חלק מה-DaemonSets של המערכת (כמו aws-node
או ebs-csi-node
) עברו למצב "לא מוכן" עקב לחץ בדיסק, והשפיע על מוכנות הצומת.
אין מטמון תמונה בצמתים: מכיוון שאנו משתמשים במופעי נקודתיים, איננו יכולים להשתמש בדיסק המקומי לאחסון תמונות במטמון.
זה גורם לפריסות עצומות רבות בענפי תכונה, במיוחד בגלל שב-FB השונה יש סטים שונים של תמונות בסיס.
לאחר חקירה מהירה, מצאנו שהבעיה העיקרית הייתה לחץ הדיסק על הצמתים על ידי תהליך unpigz
. תהליך זה אחראי לביטול הדחיסה של תמונות ה-docker. לא שינינו את הגדרות ברירת המחדל עבור סוג עוצמת הקול gp3 EBS, כי לא מתאים למקרה שלנו.
כצעד ראשון, החלטנו לצמצם את מספר ה-PODs על הצמתים.
הרעיון המרכזי של הפתרון הוא לחמם את הצמתים לפני שתהליך התקליטור מתחיל על ידי החלק הגדול ביותר של תמונת ה-docker (שכבת התלות של JS), המשמשת כתמונת השורש של כל האפליקציות שלנו. יש לנו לפחות 7 סוגים של תמונות השורש עם התלות ב-JS, שקשורים לסוג האפליקציה. אז בואו ננתח את עיצוב ה-CI/CD המקורי.
בצינור ה-CI/CD שלנו, יש לנו 3 עמודים:
צינור CI/CD מקורי:
בשלב Init
it: אנחנו מכינים את הסביבה/משתנים, מגדירים את סט התמונות לבנייה מחדש וכו'...
בשלב Build
: אנו בונים את התמונות ודוחפים אותן ל-ECR
בשלב Deploy
: אנו פורסים את התמונות ל-k8s (פריסות עדכון וכו'...)
פרטים נוספים על עיצוב ה-CICD המקורי:
main
. בתהליך ה-CI, אנו תמיד מנתחים את סט התמונות ששונו ב-FB ובונים אותם מחדש. הענף main
תמיד יציב, כהגדרה, צריכה להיות תמיד הגרסה העדכנית ביותר של תמונות הבסיס.יש דרישות לתהליך החימום.
הֶכְרֵחִי:
ContainerCreating
.נחמד שיש שיפורים:
לאחר ניתוח הדרישות והאילוצים, החלטנו ליישם תהליך חימום שיחמם מראש את הצמתים עם תמונות המטמון הבסיסיות של JS. תהליך זה יופעל לפני תחילת תהליך התקליטור, מה שמבטיח שהצמתים מוכנים לפריסה של ה-FB, ויש לנו סיכוי מקסימלי להגיע למטמון.
את השיפור הזה חילקנו לשלבים גדולים בעץ:
צור את קבוצת הצמתים (Virtual Node Group) לכל FB
הוסף תמונות בסיס לסקריפט של Cloud-init עבור הצמתים החדשים
הוסף שלב של פריסה מוקדמת להפעלת ה-DaemonSet עם הקטע initContainers
כדי להוריד את תמונות ה-docker הדרושות לצמתים לפני תחילת תהליך התקליטור.
צור קבוצה חדשה של צמתים עבור כל FB באמצעות קריאת API (למערכת קנה המידה האוטומטי של צד שלישי) מצינור ה-CI שלנו.
בעיות שנפתרו:
בידוד : לכל FB יש קבוצה משלו של צמתים, מה שמבטיח שהסביבה לא מושפעת מ-FBs אחרים.
גמישות : אנו יכולים לשנות בקלות את סוג הצומת ואת תוחלת החיים שלו.
יעילות עלות : אנו יכולים למחוק את הצמתים מיד לאחר מחיקת ה-FB.
שקיפות : אנו יכולים לעקוב בקלות אחר השימוש והביצועים של הצמתים (לכל צומת יש תג הקשור ל-FB).
שימוש יעיל במופעי הנקודה : מופע הנקודה מתחיל בתמונות בסיס מוגדרות מראש, כלומר, לאחר שהצומת הנקודתי מתחיל, יש כבר תמונות הבסיס על הצומת (מהענף הראשי).
הורד את כל תמונות הבסיס של JS מהסניף הראשי לצמתים החדשים באמצעות סקריפט cloud-init
.
בזמן הורדת התמונות ברקע, תהליך התקליטור יכול להמשיך לבנות תמונות חדשות ללא בעיות. יתרה מכך, הצמתים הבאים (שייווצרו על ידי מערכת קנה מידה אוטומטי) מקבוצה זו ייווצרו עם הנתונים המעודכנים cloud-init
, שכבר יש להם הוראות להורדת תמונות לפני ההתחלה.
בעיות שנפתרו:
פתרון הבעיה : לחץ הדיסק נעלם, מכיוון שעדכנו את הסקריפט של cloud-init
על ידי הוספת ההורדה של תמונות הבסיס מהסניף הראשי. זה מאפשר לנו להיכנס למטמון בהתחלה הראשונה של ה-FB.
שימוש יעיל במופעי הנקודה : מופע הנקודה מתחיל בנתונים מעודכנים cloud-init
. זה אומר שאחרי שהצומת הנקודתי מתחיל, יש כבר תמונות הבסיס על הצומת (מהענף הראשי).
ביצועים משופרים : תהליך התקליטור יכול להמשיך לבנות תמונות חדשות ללא בעיות.
פעולה זו הוסיפה ~17 שניות (קריאת API) לצינור ה-CI/CD שלנו.
הפעולה הזו הגיונית רק בפעם הראשונה כשאנחנו מתחילים את ה-FB. בפעם הבאה, אנו פורסים את האפליקציות שלנו לצמתים קיימים שכבר יש להם את תמונות הבסיס, שסיפקנו בפריסה הקודמת.
אנחנו צריכים את השלב הזה, כי תמונות FB שונות מתמונות הסניף הראשי. עלינו להוריד את תמונות הבסיס של FB לצמתים לפני תחילת תהליך התקליטור. זה יעזור להפחית את זמני ההתחלה הקרה הממושכים וניצול הדיסק הגבוה שיכול להתרחש כאשר מספר תמונות כבדות נמשכות בו זמנית.
המטרות של שלב הטרום-פריסה
מניעת לחץ בדיסק : הורד ברצף את רוב התמונות הכבדות של Docker. לאחר שלב ה-init-deploy, כבר יש לנו את תמונות הבסיס על הצמתים, מה שאומר שיש לנו סיכוי גדול למטמון ההיט.
שפר את יעילות הפריסה : ודא שהצמתים מחוממים מראש עם תמונות דוקר חיוניות, מה שמוביל לזמני הפעלה מהירים יותר (כמעט מיידי) של POD.
שפר את היציבות : צמצם למינימום את הסיכוי להיתקל בשגיאות ErrImagePull
/ ContainerCreating
והבטח שקבוצות הדמונים של המערכת נשארות במצב "מוכן".
בשלב זה, אנו מוסיפים 10-15 דקות לתהליך התקליטור.
פרטי שלב טרום הפריסה:
initContainers
.initContainers
מבוצע לפני שהמכל הראשי מתחיל, ומבטיח שהתמונות הדרושות יורדו לפני שהמכולה הראשית מתחילה.השוואה בין השלבים המקוריים והמעודכנים לתהליך החימום מראש.
שָׁלָב | התחל את שלב הפריסה | שלב פריסה מראש | לִפְרוֹס | זמן כולל | הבדל |
---|---|---|---|---|---|
ללא חימום מוקדם | 0 | 0 | 11 מ' 21 שניות | 11 מ' 21 שניות | 0 |
עם חימום מוקדם | 8 שניות | 58 שניות | 25 שניות | 1 מ' 31 שניות | -9 מ' 50 |
העיקר, זמן "הפריסה" השתנה (מהפקודה הראשונה להחלה למצב ריצה של התרמילים) מ-11 מ' 21 שניות ל-25 שניות. הזמן הכולל השתנה מ-11 מ' 21 שניות ל-1 מ' 31 שניות.
נקודה חשובה, אם אין תמונות בסיס מהסניף הראשי, אז זמן "הפריסה" יהיה זהה לזמן המקורי או קצת יותר. אבל בכל מקרה, פתרנו בעיה עם לחץ הדיסק וזמן ההתחלה הקרה.
הבעיה העיקרית ContainerCreating
נפתרה בתהליך החימום. כהטבה, הפחתנו משמעותית את זמן ההתחלה הקרה של ה-POD.
לחץ הדיסק נעלם, כי כבר יש לנו את תמונות הבסיס על הצמתים. מערכת daemonSets נמצאים במצב "מוכן" ו"בריא" (מכיוון שאין לחץ בדיסק), ולא נתקלנו בשגיאות ErrImagePull
הקשורות לבעיה זו.
נ.ב.: ברצוני להודות לצוות הטכני הנהדר ב- Just ( https://www.linkedin.com/company/justt-ai ) על העבודה הבלתי נלאית והגישה היצירתית באמת לכל נושא שהם מתמודדים איתם. עִם. במיוחד, צעקה לרוני שרעבי, המוביל המעולה שאחראי על העבודה הנהדרת שהצוות עושה. אני מצפה לראות עוד ועוד דוגמאות נהדרות לאופן שבו היצירתיות שלך משפיעה על המוצר Justt.