קורס Docker ו Kubernetes
אחרי יותר מדי זמן והרבה התלבטויות על התכנים, אני שמח להשיק כאן את קורס Docker של טוקוד. הנה מה שתמצאו בפנים וקצת רכילות מאחורי הקלעים.
טיפים קצרים וחדשות למתכנתים
אחרי יותר מדי זמן והרבה התלבטויות על התכנים, אני שמח להשיק כאן את קורס Docker של טוקוד. הנה מה שתמצאו בפנים וקצת רכילות מאחורי הקלעים.
קובץ ה Dockerfile הבא מגדיר בניה של אפליקציית Rails שגם מכילה קוד צד לקוח, שבתורו מתקמפל עם node.js:
FROM ruby:2.7
WORKDIR /app
COPY . .
ENV RAILS_ENV=production
ENV NODE_ENV=production
RUN gem install bundler:2.2.5
RUN sh -c "apt-get update && apt-get install -y nodejs npm"
RUN bundle install
RUN sh -c "cd client; npm install"
RUN "./bin/rails assets:precompile"
CMD ["./bin/rails", "s"]
רואים מה הבעיה כאן?
כשריילס התחיל לצבור תאוצה הרבה דיברו על כמה הפריימוורק טוב אם אתה עובד כמו שריילס התכוונו שתעבוד, וכמה אתה הולך לסבול אם שיטת העבודה שלו שונה. לימים, ריילס אכן הפך גמיש יותר, אבל הכלל הישן תקף עדיין ובכל פריימוורק שנלמד: אם מצאת טריק מתוחכם לגרום לפריימוורק להתנהג אחרת ממה שכותביו התכוונו, אולי עדיף לאחסן את הטריק הזה במגירה ולמצוא דרך אחרת (או פריימוורק אחר). אלה הסיבות שבגללן אני מעדיף לא להכריח פריימוורקס לעבוד כמו שאני רוצה:
אין אחריות (או יותר נכון אין עזרה מגוגל) - מרגע שהתחלנו לשבור את הפריימוורק כדי להתאים אותו לצרכים שלנו, יהיה קשה מאוד להיעזר בגוגל כדי למצוא ולתקן באגים. ה Use Case שלנו יהיה שונה ואי אפשר לדעת איזה באגים הגיעו בגלל השינוי שלנו, איזה באגים נובעים באמת מטעויות ושימוש לא נכון, ואיזה באגים באמת קיימים בפריימוורק בלי קשר אלינו.
מחר זה יישבר - אם אני משנה פריימוורק לעבוד כמו שאני רוצה היום, אף אחד לא מבטיח לי שבגירסה הבאה של הפריימוורק החריץ דרכו הכנסתי את השינוי שלי עדיין יהיה שם. קוד שמייצר סכנה שבגללו אי אפשר יהיה לשדרג גירסה זה קוד מסוכן.
המנגנון החדש שלי לא יהיה עקבי עם מנגנונים אחרים קיימים של הפריימוורק, ולפעמים ישבור אותם.
רק בגלל שאני חושב שמנגנון מסוים צריך לעבוד בפריימוורק לא אומר שהוא באמת עובד או שדברים צריכים להיות קלים כמו שנדמה לי. כשאני בונה פיצ'ר ומרגיש שהכלים עובדים נגדי, כדאי לקחת את הרמז - או להבין איך להשתמש בכלי בצורה נכונה, או להחליף את הכלי.
סלקטור מאוד שימושי ב CSS שגיליתי רק לאחרונה הוא :focus-within
. הוא מאפשר לזהות מתי פוקוס נמצא על אלמנט או על אחד הילדים שלו.
ולמה זה טוב? נניח שאנחנו בונים קומפוננטה מורכבת ב HTML/CSS. בואו נגיד שזו תיבת טקסט לבחירת תגיות, שכשלוחצים על התיבה מופיעה רשימה של כל התגיות לבחירה. מבחינת הפוקוס, לחיצה על תיבת הטקסט נותנת לה את הפוקוס, ואחרי זה לחיצה על כל אחד מהפריטים מעבירה את הפוקוס לפריט שנלחץ. אם תיבת הטקסט ורשימת הפריטים נמצאים בתוך אותו div, אז אני יכול להשתמש ב focus-within
על הדיב העוטף הראשי כדי להחליט אם להציג או להסתיר את רשימת האפשרויות.
בקוד ה HTML יראה כך:
<div class="multiselect">
<label>
Click To Select:
<input type="text" />
</label>
<ul>
<li><label tabindex="0"><input type="checkbox" />item 1</label></li>
<li><label tabindex="0"><input type="checkbox" />item 2</label></li>
<li><label tabindex="0"><input type="checkbox" />item 3</label></li>
<li><label tabindex="0"><input type="checkbox" />item 4</label></li>
<li><label tabindex="0"><input type="checkbox" />item 5</label></li>
</ul>
</div>
<div class="multiselect">
<label>
Click To Select:
<input type="text" />
</label>
<ul>
<li><label tabindex="0"><input type="checkbox" />item 1</label></li>
<li><label tabindex="0"><input type="checkbox" />item 2</label></li>
<li><label tabindex="0"><input type="checkbox" />item 3</label></li>
<li><label tabindex="0"><input type="checkbox" />item 4</label></li>
<li><label tabindex="0"><input type="checkbox" />item 5</label></li>
</ul>
</div>
<div class="multiselect">
<label>
Click To Select:
<input type="text" />
</label>
<ul>
<li><label tabindex="0"><input type="checkbox" />item 1</label></li>
<li><label tabindex="0"><input type="checkbox" />item 2</label></li>
<li><label tabindex="0"><input type="checkbox" />item 3</label></li>
<li><label tabindex="0"><input type="checkbox" />item 4</label></li>
<li><label tabindex="0"><input type="checkbox" />item 5</label></li>
</ul>
</div>
וה CSS יהיה:
.multiselect:focus-within {
background: lightblue;
}
.multiselect ul {
display: none;
}
.multiselect label {
display: block;
width: 100%;
}
.multiselect:focus-within ul {
display: block;
}
והתוצאה live בקודפן היא:
נסו ללחוץ על תיבה כדי לראות רשימת אפשרויות שמופיעה מתחתיה, ואז סמנו חלק מהאפשרויות ותראו שהרשימה עדיין מוצגת.
נניח שיש לכם שתי רשימות ממוינות בפייתון ואתם צריכים לייצר מהן רשימה שלישית שתהיה ממוינת גם היא. כלומר בהינתן:
a = [10, 20, 33, 51, 94, 100]
b = [2, 3, 99, 102, 500]
צריך להדפיס את הרשימה:
[2, 3, 10, 20, 33, 51, 94, 99, 100, 102, 500]
נו, מה הבעיה אתם אומרים, אנחנו בפייתון, אז אפשר לכתוב פשוט:
def test1():
return sorted([*a, *b])
וזה עובד עד שאיזה קול בראש מתחיל לנג'ס שמה פתאום אני מחבר את שתי הרשימות וממיין את התוצאה כששתי הרשימות הגיעו ממוינות, ולמה לא להתאמץ קצת ולרוץ במקביל על שתי הרשימות לפי סדר המיון, כלומר משהו כזה:
def test2():
i = 0
j = 0
output = []
while True:
if i >= len(a):
output.extend(b[j:])
break
if j >= len(b):
output.extend(a[i:])
break
next_a = a[i]
next_b = b[j]
if next_a < next_b:
i += 1
output.append(next_a)
else:
j += 1
output.append(next_b)
return output
ואז בדיוק כשאני עף על עצמי על הפיתרון המתוחכם שכתבתי ואיזה מתכנת על מהחלל אני שיודע לרוץ בלולאה על שתי רשימות במקביל, אני רוצה לקחת את זה עוד צעד קדימה ומחליט למדוד ביצועים רק בשביל לראות כמה זמן מעבד העבודה שלי חסכה. ונו אנחנו בפייתון אז גם את זה אפשר בקלות:
print("Short solution took - ", timeit.timeit("test1()", globals=locals()))
print("Long smart solution tool - ", timeit.timeit("test2()", globals=locals()))
והתוצאה הלא ממש מפתיעה:
Short solution took - 0.26264833300956525
Long smart solution tool - 1.3138192089973018
יש הרבה סיבות שהקוד השני מפשל, אבל הן לא העיקר כאן. יותר חשוב לשים לב שביצועים זה לא עניין של אינטואיציה ומה נראה לי יותר מוצלח. במקום לבזבז את הזמן על פיתרונות מתוחכמים, עדיף לוודא קודם שבאמת יש בעיה, ובכל מקרה לעשות קומיט רק אחרי שאתם בטוחים שהפיתרון המתוחכם משפר את המצב.
לפעמים אנחנו יושבים מול בעיה ולא בטוחים מספיק לגבי הטכנולוגיה או הפרטים הטכניים שלה. במצבים כאלה אנחנו עשויים להגיד משהו כמו "בואו ננסה ונראה מה יקרה".
בתיאוריה אין בזה שום סיכון...
במציאות, אם כבר החלטנו לנסות משהו קשה לנו להודות בכישלון, ואם דברים יסתבכו יש סיכוי לא רע שנבזבז על זה יותר זמן ממה שתכננו בהתחלה לפני שנוותר. וזה עדיין התרחיש הטוב.
בתרחיש היותר גרוע הניסוי הצליח, ואז יש לנו בעיה חדשה - האם להשתמש בדבר שבנינו, למרות שהתחלנו לבנות אותו בלי הבנה מלאה של הטכנולוגיה ואולי יש בו בעיות שאנחנו עדיין לא מודעים אליהן.
במקום זה עדיף לתחום את הניסוי ולהפריד בינו לבין עבודה אמיתית. "זה ייקח לי שבוע ללמוד לעומק את הכלי, ואז עוד יומיים לבנות פיתרון" זו גישה הרבה יותר בריאה. את השבוע נחלק לבלוקים של יומיים, וכל תחילת בלוק נתחיל את הפרויקט מחדש בגישה קצת אחרת. בבלוק האחרון כבר נהיה מספיק מיומנים כדי לבנות את הקוד נכון מהשורה הראשונה.
יש שני סוגים מרכזיים של לימוד-
הלימוד הקל זה כשאתה לומד משהו שקודם לא ידעת. לדוגמה היום למדתי ש"יעה" ברוסית זה "סאבוק". הנה עכשיו גם אתם יודעים את זה.
הלימוד הקשה זה כשאתה לומד משהו חדש שצריך להחליף ידע קיים. לי זה קרה לדוגמה כשגיליתי שיש הגבלה על גודל מסמך ב MongoDB. ברגע אחד הבנתי שאולי יש בעיה בכל הקוד שכתבתי שמתקשר עם MongoDB בכל פרויקט שאי פעם כתבתי. לא רציתי לקבל את זה.
דוגמה יותר קשה היתה כשיותר ויותר לקוחות שלי עברו מ perl ל Python. זה לקח לי שנתיים להבין שאנשים מעדיפים Python על פני perl. זה לקח לי עוד שנתיים אחרי זה להתחיל להעדיף בעצמי את פייתון על פני פרל. 90% מהזמן לא הושקע בלימוד פייתון אלא בניסיון מנטלי לדחות את מה שהעולם ניסה להגיד לי.
המוח האנושי לא אוהב שמזיזים לו את הגבינה. כשיש פריט מידע חדש שלא מסתדר עם דברים שכבר ידענו, נעשה הכל כדי לפסול את המידע החדש. וההרגל הזה יכול לעלות לנו ביוקר, כמתכנתים וכעובדים בכלכלה המודרנית.
החדשות הטובות הן שאפשר להתאמן ולשנות את זה. הצעד הראשון הוא לזהות את השיעורים הקשים כשהם עומדים מולנו, להסתכל עליהם בסקרנות ולשאול - מה אני יכול ללמוד ממה שקורה עכשיו? מה היה קורה אם הייתי בוחר בחירה אחרת? איזה עלות שקועה גורמת לי להמשיך להתווכח? ומה צריך לקרות כדי שאוכל לקבל את המידע החדש ולהתקדם איתו?
שרת לוגים הוא בסך הכל סרביס שכולם פונים אליו עם הודעות הלוג שלהם והוא כותב את המידע לאיזשהו יעד. ברירת מחדל פשוטה היא לכתוב את כל הלוגים לקבצים וזה יתאים לנו בשביל התחלה, אבל הרבה פעמים נרצה לנתב את הלוגים החוצה משרת הלוגים גם לבסיס נתונים או אפילו לשירות איחסון לוגים בענן. תוכנת syslog-ng היא מערכת ניהול לוגים פופולרית שמאפשרת גם שמירה מקומית של הלוגים וגם יש לה אינטגרציה מצוינת עם שירותי איחסון לוגים בענן כך שיהיה קל לעבור לשם כשנצטרך.
״בשביל להגיע ל Zero Downtime Deployments אנחנו צריכים לשדרג לקוברנטיס.״
״בשביל לפתור את בעיות הביצועים אנחנו חייבים לעבור מאנגולר לריאקט.״
״בשביל שנוכל לבנות פיצ'רים חדשים יותר מהר אנחנו חייבים לעשות Rewrite לכל המערכת.״
״בשביל לפתור את בעיית האבטחה אנחנו חייבים לשדרג את התשתית לגירסה החדשה ביותר ולשבור המון קוד בתהליך.״
״בשביל שאוכל להתחיל ללמוד את הטכנולוגיה ההיא שנשמעת לי מעניינת אני צריך שהבוס ייתן לי לעבוד על פרויקט שכתוב בה.״
אנחנו יוצרים לעצמנו אילוצים כל הזמן, בדרך כלל כי האילוץ נותן לנו תירוץ לעשות את הדבר הקשה. אבל שווה לזכור שמותר לוותר על האילוץ אם בפועל הוא מונע מכם לעשות את הדבר הקל. במילים אחרות, אם בגלל שאין זמן עכשיו לשדרג לקוברנטיס אתם מוותרים על Zero Downtime Deployment, הבעיה לא קשורה לקוברנטיס.
בבניית Docker Image יהיו לנו מצבים שאנחנו צריכים אימג' בסיס מסוים כדי לבצע עבודת "הכנה", אבל אחרי שההכנה הסתיימה אפשר לקחת את התוצרים ולהגיש אותם מאימג' אחר. דוגמה פשוטה היא אפליקציית next.js שיוצרת אתר HTML סטטי. אני צריך את node.js וכל מה שקשור אליו בשביל לבנות את האתר הסטטי, אבל אחרי שבניתי אותו אני יכול להגיש את התוצאה מאימג' של nginx.
בדוקר יש מנגנון שנקרא multi stage builds שנועד לתת מענה למצבים כאלה ולאפשר בניה מאימג' אחד ואריזה באימג' אחר. בואו נראה איך זה עובד.