ארכיטקטורת GitLab CI: DAG, Runners ו-Auto DevOps לעומק

מאת צוות מדיה דיל · 02.08.2026 · DevOps · 8 דק׳

פירוק הארכיטקטורה של GitLab CI: מודל ה-DAG לעומת stages, סוגי runners ו-executors, security scanning מובנה ו-parent-child pipelines.

צוותים שמגיעים מ-GitHub Actions ל-GitLab CI לעיתים קרובות מנסים למפות מושגים אחד לאחד, ונתקלים בקיר: הארכיטקטורה של GitLab CI בנויה סביב פילוסופיה שונה לגמרי — .gitlab-ci.yml אחד מגדיר pipeline שלם עם stages מסודרים, ולא אוסף workflows עצמאיים כמו ב-GitHub. ההבדל הזה, שנראה קוסמטי, משפיע עמוקות על איך בונים תלויות בין jobs, איך מנהלים environments, ואיך משלבים בין CI ל-CD בפועל. הבעיה שכל ארגון גדל אליה זהה: קובץ CI יחיד שהתחיל ב-30 שורות והפך תוך שנה למונוליט בן 2000 שורות, שאף אחד לא מבין לגמרי, ושכל שינוי בו מרגיש כמו הימור. במאמר הזה נצלול לארכיטקטורת הליבה של GitLab CI: runners, stages מול DAG, ניהול environments, security scanning מובנה, ואיך בונים pipeline מודולרי שלא קורס תחת המשקל שלו.

Stages, Jobs ו-DAG: שני מודלים לתזמון

המודל הקלאסי של GitLab CI הוא stages — רשימה מסודרת (למשל build, test, deploy) שבה כל ה-jobs באותו stage רצים במקביל, וה-stage הבא מתחיל רק אחרי שכל ה-jobs בקודם הסתיימו בהצלחה. זה פשוט להבנה אבל יוצר bottleneck מלאכותי: אם יש 10 jobs ב-test stage ורק אחד מהם איטי, כל ה-deploy stage ממתין לו למרות שאין תלות אמיתית ביניהם.

הפתרון המודרני הוא needs:, שהופך את הפייפליין ל-DAG (Directed Acyclic Graph) — כל job מוגדר עם התלויות האמיתיות שלו, ורץ ברגע שהתלויות הספציפיות שלו הסתיימו, בלי להמתין לכל ה-stage. זה מקצר משמעותית את זמן הריצה הכולל בפייפליינים גדולים, אבל דורש חשיבה מדוקדקת יותר על גרף התלויות — טעות בהגדרת needs יכולה ליצור race condition שבו job רץ לפני שה-artifact שהוא צריך באמת מוכן.

שילוב חכם של שני המודלים אפשרי: אפשר להשתמש ב-stages כמסגרת ארגונית רחבה (build, verify, deploy) ולהוסיף needs ברמת ה-job כדי ליצור קיצורי דרך בתוכה. כך שומרים על קריאות (מפתח חדש עדיין מבין את זרימת ה-stages) בלי לוותר על הביצועים של DAG אמיתי.

Runners: Shared, Group ו-Project-Specific

GitLab מבחין בין שלוש רמות של runners: shared runners ברמת ה-instance, group runners שמוקצים לקבוצת פרויקטים ספציפית, ו-project runners שרשומים לפרויקט בודד. הבחירה בין הרמות היא שאלה ארכיטקטונית של בידוד: runners משותפים חוסכים תשתית אבל חושפים לסיכון cross-project אם לא מוגדרים כ-ephemeral, בעוד project runners מבטיחים בידוד מלא במחיר תחזוקה מוכפלת.

Executor הוא מושג מרכזי שלא קיים ב-GitHub Actions באותה צורה: כשמגדירים runner, בוחרים executor — docker, shell, kubernetes, docker+machine לאוטו-סקיילינג. Kubernetes executor מאפשר ל-runner עצמו לרוץ בתוך cluster ולהרים pod חדש לכל job, מה שמשלב טבעית עם תשתית ענן קיימת ותומך ב-autoscaling אמיתי; לעומת זאת shell executor רץ ישירות על המכונה המארחת ומתאים רק לסביבות מבודדות לגמרי, כי הוא לא מספק שום בידוד בין jobs שרצים ברצף על אותו runner.

נקודה שקל לפספס: ב-Kubernetes executor, כל job מקבל pod משלו עם resource requests/limits מוגדרים, אבל אם לא מגדירים אותם במפורש, ה-runner משתמש בברירת מחדל שלרוב לא מתאימה — jobs שדורשים זיכרון רב (בנייה של תמונות Docker כבדות, בדיקות עם דאטהבייס בזיכרון) עלולים להיהרג על ידי ה-OOM killer של Kubernetes בלי הודעת שגיאה ברורה, מה שגורם לצוותים לבזבז שעות באבחון "job שנכשל בלי סיבה".

Environments ו-Deployment Tracking

GitLab CI מספק אובייקט environment מובנה שעוקב אחרי מה בדיוק פרוס איפה — production, staging, review apps לכל branch. זו יכולת שדורשת בנייה ידנית ב-GitHub Actions, אבל ב-GitLab היא native: כל job שמוגדר עם environment: production נרשם ב-Deployments dashboard, מציג את ה-commit הפרוס, ומאפשר rollback בלחיצת כפתור ל-deployment קודם ישירות מה-UI.

Review apps הם דוגמה חזקה ליכולת הזו: כל merge request יכול לפרוס אוטומטית סביבה זמנית ומבודדת, עם URL דינמי המבוסס על environment: dynamic; url: https://$CI_ENVIRONMENT_SLUG.example.com, ולהרוס אותה אוטומטית כשה-MR נסגר דרך environment: action: stop. זה דורש תשתית תומכת (בדרך כלל Kubernetes עם ingress דינמי), אבל כשזה עובד, זה מקצר דרמטית את מחזור הבדיקה של PR.

Security Scanning מובנה (DevSecOps)

אחת הסיבות המרכזיות שארגונים גדולים בוחרים ב-GitLab CI היא ה-templates המובנים ל-SAST, dependency scanning, container scanning ו-secret detection — פשוט include: template: Security/SAST.gitlab-ci.yml ומקבלים סריקת קוד סטטית לרוב שפות הקוד הנתמכות, ללא הגדרת כלים חיצוניים.

נקודה ארכיטקטונית חשובה: תוצאות הסריקה מוצגות כ-merge request widgets ישירות בתוך ה-MR, כולל diff-aware findings שמראים רק פגיעויות חדשות שהוכנסו ב-diff הנוכחי ולא את כל החוב הטכני ההיסטורי — הבחנה קריטית שמונעת "alert fatigue" ומאפשרת למפתחים להתמקד רק במה שהם עצמם הכניסו.

Parent-Child Pipelines וארכיטקטורת Monorepo

ככל ש-.gitlab-ci.yml גדל, מנגנון parent-child pipelines הופך קריטי: pipeline "הורה" מטריגר pipelines "ילד" נפרדים באמצעות trigger: include: local: 'services/api/.gitlab-ci.yml', וכל ילד רץ באופן עצמאי עם ה-context שלו. זה שונה מ-multi-project pipelines בכך ששניהם עדיין באותו repository — ההפרדה היא לוגית בלבד, לצורך ארגון ולא לצורך בידוד בין repositories.

עבור monorepo עם מספר שירותים, השילוב בין parent-child pipelines ל-rules: changes: מאפשר לטרגר רק את ה-child pipeline של השירות שבאמת השתנה, בלי לרוץ על כל השירותים בכל push. זה דומה למה שכלים כמו nx או turborepo עושים ל-JavaScript monorepos, אבל ברמת ה-CI configuration עצמו ולא רק ברמת ה-build tool.

יתרון נוסף של parent-child pipelines שלעיתים מתפספס: ה-child pipeline מקבל CI_PIPELINE_SOURCE משלו ויכול להריץ לוגיקה שונה לגמרי מה-parent, כולל stages משלו שלא קשורים לשאר המונורפו. זה מאפשר לכל צוות בתוך ארגון גדול "לבעלים" את קובץ ה-CI של השירות שלו במלואו, בלי לגעת בקובץ המרכזי, ובלי לתאם עם צוותים אחרים כשמוסיפים שלב בדיקה חדש. הגישה הזו סקיילית הרבה יותר טוב מקובץ ci.yml מרכזי יחיד שדורש אישור מכל בעלי השירותים לכל שינוי.

Auto DevOps: אוטומציה מקצה לקצה

GitLab מציע Auto DevOps — תבנית שמזהה אוטומטית את שפת הפרויקט, בונה Dockerfile, מריץ בדיקות, סורק אבטחה, ופורס ל-Kubernetes, הכל בלי לכתוב שורת YAML אחת. זה שימושי מאוד ל-prototyping מהיר, אבל בפרודקשן אמיתי כמעט תמיד נדרש להתאים אישית חלקים ממנו — ברירות המחדל לא מכירות מוסכמות ארגוניות ספציפיות. הגישה הנפוצה בפועל היא להתחיל מ-Auto DevOps כבסיס וללמוד ממנו את המבנה, ואז לפצל לקונפיגורציה מפורשת כשהצרכים גדלים.

Pipeline Efficiency: Caching, Artifacts ו-Include/Extends

GitLab מבדיל בבירור בין cache (לתלויות שמורדות מחדש) לבין artifacts (לתוצרי build שצריך להעביר בין jobs). ההבדל קריטי: cache הוא best-effort ולא מובטח, בעוד artifacts הם חלק פורמלי מהפייפליין ונשמרים לפי מדיניות תפוגה (expire_in). מנגנון include מאפשר לפצל קובץ ci.yml ענק לקבצים מודולריים, ו-extends משלים זאת ברמת ה-job הבודד — מאפשר job לרשת הגדרות מ-template מוגדר מראש, מה שמצמצם כפילות קוד בין jobs דומים כמו deploy-staging ו-deploy-production.

Merge Trains ומניעת קונפליקטים בדיפלוי

בצוותים גדולים עם קצב מיזוג גבוה, בעיה נפוצה היא ש-MR נבדק ועובר CI בהצלחה על בסיס ה-branch כפי שהוא ברגע הבדיקה, אבל עד שהוא ממוזג בפועל, MR אחר כבר התמזג לפניו ושינה משהו שיוצר קונפליקט לוגי (לא Git conflict, אלא כשל בזמן ריצה). Merge trains פותרים את זה: MRs מסודרים בתור וירטואלי, וכל אחד נבדק כאילו כל ה-MRs שלפניו בתור כבר מוזגו, כך שה-pipeline שרץ באמת משקף את המצב העתידי של ה-branch. זה מונע את התופעה של "זה עבר CI אבל שבר את main" שקורית כשמספר מיזוגים מתרחשים כמעט בו-זמנית — ובלי merge trains, הפתרון היחיד היה למזג באופן טורי לגמרי, מה שהופך כל PR ממתין לצוואר בקבוק אנושי.

CI/CD Variables: Precedence ו-Scoping

מקור בלבול נפוץ בקרב צוותים שמתחילים עם GitLab CI הוא סדר העדיפויות של משתנים. GitLab מגדיר היררכיה ברורה: משתני trigger (שמועברים בזמן הפעלה ידנית) גוברים על project-level variables, שגוברים על group-level variables, שגוברים על instance-level variables, ואלה גוברים על משתנים המוגדרים בתוך ה-.gitlab-ci.yml עצמו. כשמשתנה עם אותו שם מוגדר בכמה רמות, קשה לפעמים להבין למה job "מקבל" ערך לא צפוי — הכלי המומלץ הוא echo של המשתנה בתחילת ה-job בזמן דיבוג, ולא ניחוש.

שכבה נוספת שחשוב להכיר: משתנים מוגדרים כ-protected זמינים רק ל-branches ו-tags שמסומנים כמוגנים, כך שסודות רגישים (מפתחות דיפלוי לפרודקשן) לא דולפים ל-feature branches רגילים. שילוב בין protected ל-masked (הסתרה אוטומטית מלוגים) הוא קו ההגנה הבסיסי, אבל הוא לא מספיק לבדו — job שמדפיס במפורש את תוכן המשתנה (למשל דרך env command או debug logging גורף) עדיין יכול לחשוף אותו, כי המיסוך פועל רק על הופעה מדויקת של הערך, לא על שינויים שלו (base64, חיתוך, שרשור עם מחרוזת אחרת).

טעויות נפוצות בפרודקשן

הטעות הראשונה היא הגדרת needs חלקי שיוצר תלות שקטה שלא מתועדת. השנייה היא שכחת rules: מדויקים, מה שגורם לפייפליינים לרוץ על branches שלא היו אמורים. השלישית היא אי-שימוש ב-interruptible: true על jobs ארוכים, מה שגורם לבזבוז compute כשמפתח דוחף commit נוסף לפני שה-pipeline הקודם הסתיים. הרביעית היא שמירת secrets כ-CI/CD variables ללא סימון protected ו-masked, מה שחושף אותם ל-branches לא מוגנים או ל-logs. חמישית — כלל rules: changes שנשכח מעדכון אחרי ריפקטור מבני, וגורם ל-job חשוב שפשוט לא רץ בלי שאף אחד ישים לב.

מתי GitLab CI עדיף

GitLab CI מצטיין בארגונים שרוצים פלטפורמה מאוחדת — ניהול קוד, CI/CD, security scanning ו-project management במקום אחד, במיוחד בסביבות self-managed שבהן compliance ובידוד רשת הם דרישה קשיחה. הוא פחות מתאים לצוותים קטנים שכבר חיים לגמרי באקוסיסטם GitHub. במדיה דיל אנחנו רואים ביקוש גובר ל-GitLab CI דווקא אצל ארגונים עם דרישות רגולציה — הפיצ'רים המובנים של security scanning ו-audit trail חוסכים חודשי עבודה של אינטגרציה עצמאית. למי שמעוניין להשוות מול העולם המקביל, כדאי לקרוא גם על ארכיטקטורת GitHub Actions.

סיכום

הארכיטקטורה של GitLab CI מתמקדת באיחוד: pipeline יחיד עם DAG גמיש, environments מובנים עם deployment tracking, ו-security scanning ללא אינטגרציה חיצונית. ההבנה של ההבדל בין stages ל-needs, בחירת ה-executor הנכון ל-runners, ושימוש נכון ב-parent-child pipelines למונורפו, הם ההחלטות הארכיטקטוניות שקובעות אם הפייפליין יהיה מהיר ובטוח או צוואר בקבוק שמאט כל מיזוג קוד. השקעה בשלב התכנון — לפני שהפרויקט גדל לעשרות jobs — חוסכת ריפקטור כואב של קובץ ci.yml שהפך למונוליט בלתי מתוחזק, ומאפשרת לכל צוות בארגון לתחזק את חלקו בפייפליין בעצמאות מלאה.

תגיות: GitLab CI · DevOps · CI/CD · DAG pipeline · GitLab runners · Auto DevOps · DevSecOps · parent-child pipelines

← חזרה לבלוג · צור קשר