יום רביעי, 16 בנובמבר 2016

אז... אתם רוצים Continuous Deployment?

נתקלתי בארגונים שרצו לעשות Continuous Delivery (או Deployment, או אפילו Integration) משום שרצו להידמות לארגונים מצליחים אחרים. הייתה מן מחשבה מעורפלת שאם נעשה את "זה" - דברים יסתדרו טוב יותר, כל זאת ללא התעמקות אמיתית בהבנת התמורות הצפויות ממהלך שכזה.
המחשבה כנראה הולכת כך: "CD זה טוב => אנחנו רוצים להיות טובים יותר => בואו נעשה CD".
(ייתכן, כמובן, והייתה התעמקות - אך פשוט לא נחשפתי אליה.)

Continuous Delivery היא כבר שאיפה סטנדרטית: כמעט כולם רוצים להיות שם. כ"כ הרבה נמצאים "בתהליך...". כרגיל...

בפוסט הזה אני רוצה לחזור ולדבר על הציפיות הסבירות מ CD, על המשמעות, המעבר, וכו׳...






הפשט


בדומיין שאנו מדברים עליו יש 3 פרקטיקות דומיננטיות, שנהוג לפרש אותן בערך כך:
  • Continuous Integration (בקיצור CI) - יש לנו שרת Build עם בדיקות אוטומטיות. כל כמה זמן (לילה?) - כל הבדיקות רצות ואנו מגלים אם יש בעיות בקוד - ואז מתקנים.
  • Continuous Delivery (בקיצור CD) - יש לנו שרת Build אוטומטי, שגם אורז את הקוד ל Delivery. למשל: בונה את ה ISO (אם אנו מפיצים את המערכת על DVD - פחחח) או Docker Image - שניתן להטמיע בפרודקשיין (SaaS או On-Premises).
  • Continuous Deployment (בקיצור גם CD, לצורך הפוסט אשתמש בקיצור CDD) - כמו Continuous Delivery, אבל ההחלטה לשלוח ל Production היא אוטומטית. כל קוד שנכנס למערכת - מגיע ל Production. לכאורה, תוספת קטנה לתהליך.

התיאור הזה הוא נכון בגדול - אך גם שטחי למדי.


נכון, אך פשטני משהו...
מקור: notafactoryanymore.com



מוטיבציה - אז למה לנו CD עכשיו...?


CD אמור לשפר כמה מדדים במערת, הנה העיקריים:
  • צמצום אי-ודאות - היכולת לשחרר קוד מהר יותר, מספקת לנו פידבק מהיר ותדיר יותר מהשטח. ״לפגוע בקרקע בתדירות גבוהה יותר״. (רלוונטי בעיקר ל Continuous Deployment)
  • פישוט תהליכי ה Integration / Deploy - ארגונים רבים נאבקים לסיים גרסאות בזמן, ובאיכות טובה. CD היא דרך לשפר את התהליכים, בכמה צורות.
  • יציבות, או Uptime של המערכת בפרודקשיין.
איזה מדד הכי חשוב? - תלוי בארגון.


אני רוצה להרחיב מעט יותר על המוטיבציות:

צמצום אי-ודאות / סיכון

כשמנהלים פרויקט או מוצר בארגון, תמיד יש לנו חוסר ודאות מסוים: לפעמים הוא בינוני, ולפעמים - גדול.
  • חוסר ודאות מה הלקוחות צריכים -לפעמים הם בעצמם לא יודעים, ופשוט יש לראות כיצד הם משתמשים במוצר.
  • חוסר ודאות לגבי התוצאות של פעולות שנעשות - האם המעבר ל NoSQL DB יספק את ה Scalability הצפוי. האם ה Design החדש של מודול Y יעמוד יפה בשטף הדרישות העתידיות?
  • חוסר ודאות לגבי הבנת תוכנית הפעולה והתיאום בין אנשינו - כאשר האנשים שעובדים איתנו לא מבינים את אותה התוכנית - הם עלולים למשוך לכיוונים מנוגדים.
שווה לציין שהרעיון של Continuous Deployment קיבל תנופה גדולה מתנועת ה Lean Startup, שם צמצום מחזורי הלמידה והפידבק הם ערך מרכזי. הדרישה ל CDD, ע״פ המתודולוגיה, אמורה להגיע כיעד עסקי מה CEO - זה לא עניין ״פנימי״ של ה R&D...

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

התרבות הארגונית של אמזון, שגובתה בעזרת יכולות CDD, אפשרה למפתח שמאוד מאמין במשהו לבצע A/B test (על אוכלוסיה קטנה) - ולנסות להוכיח את צדקתו. כל אחד כמעט - יכול להציע ולהתניע ניסוי.
הניסוי הנ״ל הראה גידול של כ 3% במכירות (נתון מרשים מאוד!) - מה שהסיר מייד כל התנגדות, והפך אותו בן רגע לפיצ׳ר רצוי ע"י הפרודקט.

אם כולם מאמינים במערכת ובנתונים שהיא מייצרת,  אזי נתונים מהשטח הם ״מחסלי ויכוחים ארגוניים״.

CDD, אם כן, בשילוב A/B Tests ותרבות ארגונית מתאימה - מספקת Empowerment לעובדים, ויכולה להביא רעיונות חכמים שלהם - לידי יישום.

מיחסים ל CDD גם שיפור בשביעות הרצון של המפתחים, שכבני-אדם, גם הם צמאים לפידבק מהיר. מה יותר כיף: לפתח פיצ׳ר במשך שנה - ואם לראות אותו עובד (אם בכלל), או לראות שינוי בפרודקשיין כל שבוע-שבועיים? להרגיש את העשייה בצורה מתמשכת?



פישוט תהליכי ה Integration / Deploy

כשהחברה גדלה, תהליכי ה Integration וה Deploy הם לא פעם - כאב ראש רציני!

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

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

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

אין כאן קסם:

בתהליכי Integration/Deploy תקופתיים (נאמר: פעם בחודש) - משקיעים המון.
וגם בתהליכי CD - משקיעים המון.

התמורה היא שב CD הסיכויים להגיע לתהליך מוצלח - הם טובים יותר. במיוחד כאשר המערכת שלנו מחולקת למודולים נפרדים (למשל: microservices) ואז ניתן לייצר pipeline נפרד ומותאם לכל חלק במערכת (sub-system).


מקור: http://www.mindtheproduct.com


יציבות

לאחר מעבר מוצלח ל CD/CDD, המערכות נוטות להיות יציבות יותר.
זה לכאורה פרדוקס: שינוי בפרודקשיין הוא סיכון - והנה אנחנו הולכים לעשות הרבה יותר כאלה. במקום שינוי בחודש, אולי כמה שינויים כל יום!
מצד שני, כשהשינויים הם מאוד קטנים - הסיכון מאוד קטן, יכולת האיתור של תקלות משתפר פלאים, וכמו שאומרים: Practice Makes Perfect. הרבה אימון בתהליך מאפשר להשתפר בצורה מהירה יותר.

מודל היציבות של CD הוא MTTR (קרי: Mean Time To Resolve) על פני מודל של MTBF (כלומר: Mean Time between Failures). יהיו לנו יותר תקלות במספר - אך הן יתוקנו מהר, וסה״כ ה downtime שלנו יהיה נמוך יותר.

שאלה מפורסמת ב Lean היא: "כמה עבודה עליכם לעשות בכדי לבצע Deploy של שורת קוד בודדת במערכת?" (כלומר: נזניח את תוכן השינוי, ואז - מהי התקורה על כל deploy של גרסה?). התרגיל הזה ידוע גם כ Single Line of Code DevOps Kata.

בארגונים ללא CD, מדובר בד"כ על כמה ישיבות, עבודה ידנית של ביצוע רגרסיות ומעבר על התוכן, ואף כמה פעולות ידניות של איש Operations שמלווה את ה deploy. כמה שעות עבודה, וכמה דקות של פחד (במקרה הטוב).

אם אתם רוצים לעשות deploy כל יום, זה נראה כחוסר-יעילות, ולכן ה"Best Practice" שהיה מקובל במשך שנים הוא להפחית את מספר ה releases, או לפחות למתן אותו - נאמר, פעם או פעמיים בחודש, ולא יותר.

לא שעות העבודה שמושקעות ב deployment הן המחסום העיקרי - אלא הפחד מתקלות: תקלת Deploy עלולה לגרום להפסדים כספיים ("פגיעה בביזנס"), לעלות עשרות שעות עבודה של מפתחים ,ולגרום לפגיעה במוניטין החברה.

הגישה של CD נקראת: Bring the Pain forward (כמו באג'ייל).
אם זה כואב, עשו את זה בתכיפות רבה יותר - עד שתשתפרו וזה כבר לא יכאב.

בפועל, באימוץ CD אתם הולכים "לשבור דברים" כ"כ הרבה פעמים - כך שתוך כדי אתם צפויים לפתח יכולות Recovery טובות במיוחד ולשפר מאוד את ה MTTR. כמובן שחשוב מאוד לשבור דברים קטנים, ולהיות עם יכולת recovery כמעט מיידית, למשל: לסגור את ה feature flag או לחזור ל deployment הקודם תוך שניות (ע"י blue/green deployment - פרטים בהמשך).

המערכת לא תהיה יציבה יותר כי היא "תשבר פחות".
אם תציבו לארגון ה R&D מדד פנימי של "מינימום תקלות" - קרוב לוודאי שתהרגו את תהליך ה CD.

המדד צריך להיות: תוך כמה זמן אנו מתקלים תקלה, end-to-end: זיהוי התקלה עד אימות התיקון.

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






Continuous Integration (בקיצור: CI)


זהו הרעיון שקוד המערכת יהיה כל הזמן במצב תקין ("working state").

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

העיקרון הוא שאינטגרציה של קוד ("merge") הוא הכלי האולטימטיבי לזהות שבירות במערכת - ולכן עליכם לבצע אינטגרציה כל הזמן.

כפי שאמרו זאת מייסדי ה Extreme Programming (שהיו הראשונים לאמץ את הפרקטיקה בצורה רחבה):
"אם אינטגרציה של קוד היא טובה - עשו אינטגרציה של קוד כל הזמן."

המדד המקובל ליישום CI הוא:
כל מפתח, מבצע בכל יום עבודה - לפחות פעם אחת merge של הקוד שלו ל branch המרכזי. (ועדיף: חמש פעמים)


(רמז: Jenkins לא גורם לזה לקרות.)

שימו לב: זה לא שכל מפתח מושך את ה branch המרכזי (נקרא לו לצורך הפוסט: master) ועושה merge לתוך ה feature branch. זה פחות מועיל / מלמד על שברים. העניין הוא לבצע merge לתוך ה master בכל יום.

Feature Branches? לא כ"כ מומלצים, אלא אם אתם מקפידים לבצע merge ל master.
זה מחייב את המפתחים לחשוב ביחידות קטנות, לפרק את הפיצ'ר לשלבים קטנים (שזה טוב - בפני עצמו), ולדאוג כל הזמן שהקוד הנכתב - לא שובר את המערכת.

מה קורה עם קוד שמשנה חלק גדול במערכת? איך אפשר לעשות merge כאשר הקוד לא "מוכן"? - פשוט הכניסו feature flag, ואל תדליקו אותו עד שהקוד מוכן. תחשבו איזה יופי יהיה להדליק את הפיצ'ר רק עבור המשתמש שלכם בפרודקשיין - ולבדוק איך הפיצ'ר עובד בלייב עם traffic/data אמיתי!

העיקרון החשוב השני של CI הוא:
כאשר ה Build נשבר - לתקן אותו זו עדיפות עליונה של הארגון. לפני כל דבר אחר.

יעד מקובל הוא תיקון תוך 10 דקות - לרוב ע"י rollback.
אפילו יותר טוב: בדקו את ה merge בצד, ורק לאחר שהצליח - בצעו את ה merge בפועל. השיטה הזו עובדת יותר טוב כאשר יש code bases קטנים (למשל: microservices) ואין race condition על merges.

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

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


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


בדיקות יחידה (Unit-Tests) או אינטגרציה (Integration-Tests) הגיעו לתהליך ה CI מאוחר יותר - והפכו לחזות עיקר התהליך. אין ספק: הן חשובות מאוד, ומועילות מאוד. מה היה קודם לכן? קומפליציה + בדיקת המערכת end-to-end (שאורכת זמן רב, ותופסת רק חלק מסוים מהבעיות).

האם בדיקות אוטומטיות טובות מספיקות, ויכולות לייתר את הצורך ב merges תכופים?
אם אתם רואים שאצלכם זה עובד - אדרבא.

בד"כ השבירות בקוד (גם אם הן נתפסות ע"י Unit tests) מגיעות אחרי merges של קוד - ואתם לא רוצים "לחטוף אותן" בסוף הספרינט - חשוב לבצע אינטגרציה כל הזמן.

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

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


האם שווה לעשות CI ע"י merges תכופים, אך ללא בדיקות יחידה מקיפות?
- גם על זה לא הייתי ממליץ. זה כנראה עדיף במעט על בדיקות ללא merges תכופים - אך זה לא ייתן את האפקט המלא.


CI הוא האב הרוחני של CD, ולרוב מגיע איתו ביחד.
האם חובה ליישם CI לפני שמתחילים לעבוד על CD? - נדבר על זה בהמשך...



סיכום


בפוסט זה ניסיתי להסביר מה הציפיות הסבירות מ CI/CD/CDD - תהליך חשוב שלאט הולך ומוטמע באמת בעוד ועוד חברות. אימוץ תהליכים אלו הם לרוב מאמץ ארגוני אינטנסיבי של חודשים - אבל גם השיפור עשוי להיות משמעותי למדי.

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


שיהיה בהצלחה!



12 תגובות:

  1. נהניתי לקרוא,תודה

    השבמחק
  2. מתי בתהליך הזה מתרחש ה-Code Review?

    השבמחק
    תשובות
    1. לפני push ל trunk / master branch.
      ב Github פשוט עושים Pull Request, ומי שעושה review ומאשר את הקוד פשוט עושה merge.

      מחק
    2. איך בדיוק? חמישה code reviews לכל מפתח ביום?

      מחק
  3. הזכרת הרבה merge ים תכופים ל branch הראשי. במקרה זה האם יש בכלל יתרון לפתח ב branch נפרד?

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

      כמו כן, ברור שיש הבדל בין תיאוריה למציאות. לא כולם קנאים לשיטה (יש כאלו שכן, אני יכול להעיד) - אבל יש כאלו שיחליטו ש CI עבורם זה נניח אינטגרציה פעם בשבוע לכל הפחות, ולא פעם ביום. במקרים כאלו יש יותר שימוש ב Branches.

      הייתי אפרופו בסדנא של Jez Humble בה הוא אמר שגיט הוא אכן רע ל CI, עם הקלות והעידוד שלו לייצר Brachnes.

      מחק
    2. שני, יש עוד סיבה שאפשר למנות. כשרוצים לבצע Pull Request לקוד. לא מכיר שיטה אחרת פרט לפתיחת branch צדדי ודחיפת הקוד שלו ל-master.

      מחק
    3. ניתן לעבוד ישירות על ה master.
      pull, ביצוע שינויים, commit, ופוש.

      לא חייבים לעבוד עם Pull Requests...

      מחק
    4. צודק, התכוונתי ל-Code Reviews.

      מחק
  4. בא לי להיות פרקליט השטן : וש פראדיגמה בבסיס ה CI שאין לה שום ביסוס והצדקה : אם שינוי(קטן) שהוכנס למערכת גורם לבעיה , פשוט תוציא אותו מהר כי השינוי האחרון הוא תמיד מקור הבעיה. ומה אם הוא מתנגש עם שינוי קטן אחר שהוכנס לפני N מחזורי CI כי השינוי הישן לא בוצע בצורה נכונה ? אם אין Branches נבטל כול מה שעבר CI עד עכשיו ? או שנעשה את השינוי האחרון בצורה "עקומה" כדי שיתאים לשגיאה שכבר השתרשה במערכת ? זה יוצר תחרות קטלנית בין מפתחים מי יצליח לדחוף את השינויים שלו הכי מהר ולקבוע עובדות בשטח. זה מגביר פרודוקטיביות ?

    השבמחק
    תשובות
    1. היי דני,

      ביקורת זה דבר טוב, והייתי שמח ליותר ממנה - במיוחד בנושא זה שאיננו פשוט :)

      להוציא את השינוי בד"כ הכוונה לעשות rollback לגרסה מעט יותר ישנה - בשאיפה כמה שעות אחורה. גרסה שבה כל השינויים עבדו זה עם זה. יכול להיות ששינוי x ישן יותר מתנגש עם שינוי y שלנו. נחזור לגרסה ללא y - נתקן את הקוד ואז נוסיף את y' : קוד שעובד עם x - ואולי כלל שינויים ב x.

      ברגע השינויים קטנים, קל למצוא את מקור הבעיה - וזה מגביר פרודקטיביות.

      דווקא כאשר עושים merge לעבודה של שבוע+ - התנגשויות הרבה יותר קשות לחקירה. אני זוכר את עצמי כמפתח צעיר במירוץ (קולני, מודע, ומתובל בהומור) להכניס שינויים גדולים לפני חברים - כדי שהם יתמודדו עם ה merge.

      מחק