ru
Feedback
ToCode

ToCode

Открыть в Telegram

טיפים קצרים למתכנתים מאת ינון פרק

Больше
1 419
Подписчики
Нет данных24 часа
Нет данных7 дней
-230 день
Архив постов
ToCode
1 419
ואת קוד המשחק המלא עם האפקט שמציג את רצף הצבעים אפשר לראות בקודפן הבא: https://codesandbox.io/s/elated-minsky-3uuko?file=/src/App.js:362-799 ## קליטת רצף צבעים מהמשתמש עברנו את החלק הקשה בבניית המשחק אבל עדיין צריך להוסיף את הטיפול בקלט מהמשתמש כדי שיהיה כיף לשחק. עיקר הטיפול הוא תוספת של משתנה State מערכי בשם currentSequence שמייצג את הצבעים שמשתמש לחץ עליהם. כל פעם שמשתנה זה מתעדכן אני אבדוק אם יש התאמה באמצעות אפקט, ואם יש אוכל להציג הודעת Bravo ולאפס את כל המשתנים כדי להתחיל סיבוב חדש. כל פעם שמשתמש לוחץ על אחד ה div-ים אני מוסיף את הערך המתאים של ה div ל currentSequence. ובשביל שמשתמשים ידעו שהלחיצה נקלטה הוספתי מנגנון flash קצר של הכפתורים: משתנה State בשם flash מייצג את הערך של הכפתור שעכשיו צריך להציג אור לזמן קצר; כל פעם שמעדכנים אותו מתחיל useEffect שסופר 200 מילי שניות עד לכיבוי אוטומטי ועדכון הערך ל-0. קוד המשחק המלא יחד עם הפונקציה שמגרילה רצפים נראה כך:
import React, { useState, useEffect } from "react";
import _ from "lodash";

import "./styles.css";

const colors = ["red", "blue", "green", "yellow"];
const sequenceLength = 4;

function randomSequence(len) {
  const seq = new Array(len)
    .fill(0)
    .map((x) => Math.floor(Math.random() * colors.length) + 1);
  const zeros = new Array(len).fill(0);

  return _.flatMap(_.zip(seq, zeros));
}

function Game(props) {
  const [secretSequence, setSecretSequence] = useState(
    randomSequence(sequenceLength)
  );
  const [index, setIndex] = useState(0);
  const [currentSequence, setCurrentSequence] = useState([]);
  const [flash, setFlash] = useState(0);

  useEffect(() => {
    if (flash === 0) return;

    const timer = setTimeout(() => {
      setFlash(0);
    }, 200);

    return () => {
      clearTimeout(timer);
    };
  }, [flash]);

  useEffect(() => {
    if (
      _.isEqual(currentSequence.slice(-1 * sequenceLength * 2), secretSequence)
    ) {
      alert("Bravo!");
      setCurrentSequence([]);
      setSecretSequence(randomSequence(sequenceLength));
      setIndex(0);
    }
  }, [currentSequence, secretSequence]);

  useEffect(() => {
    if (index >= secretSequence.length) return;

    const timer = setTimeout(
      () => {
        setIndex((i) => i + 1);
      },
      index % 2 === 0 ? 600 : 200
    );

    return () => {
      clearTimeout(timer);
    };
  }, [index, secretSequence]);

  function cx(squareIndex) {
    return `
    colored-box
    ${colors[squareIndex - 1]}
    ${secretSequence[index] === squareIndex ? "active" : ""}
    ${flash === squareIndex ? "active" : ""}
    `;
  }

  function clicked(n) {
    setCurrentSequence((s) => [...s, n, 0]);
    setFlash(n);
  }

  return (
    <div>
      <div className="game">
        <div className={cx(1)} onClick={() => clicked(1)}>
          1
        </div>
        <div className={cx(2)} onClick={() => clicked(2)}>
          2
        </div>
        <div className={cx(3)} onClick={() => clicked(3)}>
          3
        </div>
        <div className={cx(4)} onClick={() => clicked(4)}>
          4
        </div>
      </div>
      <div>
        <button onClick={() => setIndex(0)}>Play Again</button>
      </div>
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <Game />
    </div>
  );
}
ואתם מוזמנים לשחק בו או לעדכן את הקוד בקישור: https://codesandbox.io/s/epic-bell-pcf9z?file=/src/App.js.

ToCode
1 419
# בואו נכתוב משחק סיימון ב React כדי ללמוד לעבוד עם שעונים אחד האתגרים בכתיב הקומפוננטות של ריאקט הוא יצירת קומפוננטות המושפעות מזמן. ברוב הפעמים זה הגיוני, כי לא היית רוצה שפתאום דברים ישתנו על המסך בלי שמשתמש עשה איזושהי פעולה - זה רק מבלבל. ועדיין, כשאנחנו כן רוצים לכתוב קוד שקשור לזמן כדאי שנדע איך לגשת לזה בלי להתבלבל. ## מהו משחק סיימון משחק סיימון הוא משחק זיכרון פשוט הכולל 4 כפתורים צבעוניים. הכפתורים נדלקים בסדר מסוים (כל כפתור לפי הצבע שלו) ואחרי הצגת הרצף המשתמש צריך ללחוץ על הכפתורים בדיוק באותו הסדר בו הם נדלקו אוטומטית קודם. ככל שמתקדמים במשחק הרצפים הופכים ארוכים יותר וכך קשה יותר לזכור אותם. בחלק מהגירסאות אפשר להוסיף צלילים במקום אורות או גם וגם. ## הצגת הממשק בגלל שאנחנו ב Web נוכל לייצג כל כפתור בתור div וסך הכל נציג 4 מהם על המסך. דיב שיש לו את הקלאס active יוצג בצבע שלו, ואלה בלי הקלאס active יוצגו שקופים. כשמשתמש ילחץ על div נוכל להוסיף את הקלאס active לכמה מאיות שניה כדי להראות אפקט של לחיצה, וכשמציגים את רצף האורות פשוט נוסיף ונוריד את הקלאס active ל div-ים לפי הסדר. קוד ה CSS יספיק בשביל הממשק:
.colored-box {
  width: 100px;
  height: 100px;
  vertical-align: top;
  border: 1px solid;
  display: inline-block;
  line-height: 100px;
  text-align: center;
  font-size: 1.5rem;
}

.red {
  background: red;
}
.yellow {
  background: yellow;
}
.blue {
  background: blue;
}
.green {
  background: green;
}

.colored-box:not(.active) {
  background: transparent;
}
ואיתו הקוד הבא עבור ה React:
import React, { useState } from "react";

import "./styles.css";

const colors = ["red", "blue", "green", "yellow"];

function randomSequence() {
  return [1, 2, 1, 1];
}

function Game(props) {
  const [currentSequence, setCurrentSequence] = useState(randomSequence());
  const [index, setIndex] = useState(0);

  function cx(squareIndex) {
    return `colored-box ${colors[squareIndex - 1]} ${
      index === squareIndex ? "active" : ""
    } `;
  }

  return (
    <div className="game">
      <div className={cx(1)}>1</div>
      <div className={cx(2)}>2</div>
      <div className={cx(3)}>3</div>
      <div className={cx(4)}>4</div>
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <Game />
    </div>
  );
}
אם בשביל הדוגמה נוסיף את הקלאס active לאחד ה div-ים נוכל לראות שהוא ייצבע בצבע המתאים לפי מערך הצבעים שהגדרתי בתחילת הקובץ. אפשר לראות שבשביל להתחיל את המשחק אני לא באמת צריך רצף אקראי ולכן הפונקציה randomSequence מחזירה מערך קבוע. אחרי שהכל יעבוד אוכל להחליף אותה לפונקציה שמייצרת מערך אקראי. אפשר לראות את הקוד לייב בקודסנדבוקס בקישור הבא: https://codesandbox.io/s/delicate-darkness-crrq1?file=/src/App.js ## מימוש הצגת רצף צבעים האתגר הראשון האמיתי שלנו במשחק סיימון הוא הצגת רצף הצבעים. בריאקט אנחנו משתמשים ב useEffect כדי לתת לרכיב יכולת "להזיז את עצמו". הרעיון בגדול: 1. אני מגדיר משתנה סטייט בשם index שמייצג את האינדקס ברצף שעכשיו צריך להציג. 2. אני מגדיר אפקט שכל פעם שמשתנה זה מתעדכן באופן אוטומטי בעוד שניה הוא יקודם שוב. כדי שהמימוש יראה טוב נרצה להוסיף עוד כמה טריקים: 1. אני מוסיף ברצף את הערך 0 בין כל שני צבעים כדי לייצר אפקט של מנורה שנדלקת ונכבית לפני שהמנורה הבאה נדלקת. 2. האפקט ישתמש בזמן המתנה שונה למספרים זוגיים (המייצגים צבעים אמיתיים) ולמספרים אי זוגיים (המייצגים את ההפוגה בין המנורות). 3. חשוב לזכור לבטל את ה timeout בקוד ביטול האפקט, כדי שכפתור Play Again יעבוד כמו שצריך ויבטל את הניגון האוטומטי הקודם. סך הכל הרעיונות האלה מתנקזים לאפקט הבא:
  useEffect(() => {
    if (index >= secretSequence.length) return;

    const timer = setTimeout(
      () => {
        setIndex((i) => i + 1);
      },
      index % 2 === 0 ? 600 : 200
    );

    return () => {
      clearTimeout(timer);
    };
  }, [index, secretSequence]);

  function cx(squareIndex) {
    return `colored-box ${colors[squareIndex - 1]} ${
      secretSequence[index] === squareIndex ? "active" : ""
    } `;
  }

ToCode
1 419
# הלאה ישבתי היום עם הבת שלי על משחק אותיות פשוט: לקחנו 5 כרטיסיות, כתבנו עליהם את האותיות "ג", "ד", "ו", "ל" ו-"ה" ולימדתי אותה איך להרכיב את המילה "גדולה" (כי היא כבר ילדה גדולה שיודעת אותיות). זה לקח כמה ניסיונות אבל בסוף היא הצליחה ממש טוב להרכיב את המילה, לא משנה כמה ערבבתי את הכרטיסיות ואפילו כשהוספתי כרטיסיות עם אותיות לא קשורות למילה זה לא הפריע לה. היא הסכימה לכל טריק שרציתי להוסיף למשחק מלבד אחד - רק לא להחליף מילה. מרגע שהיא קלטה שהיא יודעת להרכיב את המילה "גדולה" היא לא היתה מוכנה לוותר על התחושה של לעשות משהו כמו שצריך. המחשבה על החזרה אחורה לטעויות, לבלבול, לחוסר וודאות פשוט הוציאה לה את החשק ללמוד. אותו פחד מהחזרה לבלבול, לטעויות ולחוסר הוודאות לא נעלם עם הגיל ולא משתנה כשעוברים ללמוד דברים יותר קשים מאותיות ומילים. הוא שם כשאני מתלבט אם זה זמן טוב עכשיו להיכנס לעולם ה Front End, או אם שווה ללמוד איך עובדים חוזרים חכמים ויודע שכבר מהצעד הראשון בתחום החדש ארגיש את אותו תסכול, אותו בלבול ואותו חוסר וודאות. הלאה. למילה הבאה. לטכנולוגיה הבאה. לתחום הבא. כמו שלמדנו את הדבר שאנחנו עכשיו טובים בו, נצליח ללמוד גם את הדבר הבא. ואחרי? נמשיך למילה הבאה כמובן.

ToCode
1 419
# להוריד רעיונות מהשולחן אחד הדברים שאנחנו יודעים על תכנות זה שלא תמיד הרעיון הראשון שנגיע אליו יהיה הכי טוב האפשרי, ויותר גרוע, לא תמיד הרעיון הראשון שנגיע אליו יהיה בכלל ישים. זאת אחת הסיבות שלפעמים מביך לצפות ב Live Coding - אתה מסתכל על בן אדם מנסה להתקדם בכיוון שלא יכול לעבוד בגלל מגבלה שהוא עדיין לא הבין אותה, ואתה יודע שעוד רגע הוא הולך להתנגש בקיר וזה הולך לכאוב. אבל מחוץ ל Live Coding, בעולם האמיתי, החוויה של להתקדם עם רעיון עד שמתנגשים בקיר היא חלק בלתי נפרד מתהליך הלימוד במיוחד של טכנולוגיות חדשות. יש לי איזה כיוון לגבי בחירה טכנולוגית, אני כותב סביבו את הקוד, מכניס למערכת נתונים אמיתיים ורואה איך היא מתרסקת. חלק מהפעמים היא תתרסק בגלל ששכחתי משהו קטן ואני מיד יכול לתקן ולהתקדם, ופעמים אחרות היא תתרסק כי חשבתי שהמידע יעבור ברשת מספיק מהר וראיתי שבעולם האמיתי המערכת עובדת הרבה יותר לאט מאשר בתוכנית שלי. "לא, אתה לא יכול לכתוב סקריפט שמושך את הנתונים מהאתר ההוא כי יש שם מנגנוני הגנה חזקים" זה לקח טוב שעוזר לנו בפעם הבאה להתחיל מבדיקת מנגנוני ההגנה לפני שרצים לכתוב את הסקריפט. וברגע שהורדתי את הרעיון הזה מהשולחן אני פותח את הדלת לרעיונות חדשים שאולי כן יעבדו טוב יותר.

ToCode
1 419
# גישה גרפית לשרת יוניקס מרוחק רוב הפעמים שנצטרך לעשות עבודה על שרת מרוחק נעדיף להשתמש בממשק טקסטואלי כי הוא מהיר ויציב יותר בעבודה ברשת. אבל מדי פעם כן נצטרך לעבוד על תוכנית גרפית או אפילו על שולחן עבודה שלם שרצים על שרת מרוחק. בפוסט זה אציג את שתי הדרכים המרכזיות לעשות את זה. ## הפעלת תוכנית גרפית בודדת על שרת מרוחק עם X11 Forwarding גישה ראשונה תעזור לנו להפעיל תוכנית גרפית ספציפית על מכונה שרוב העבודה איתה היא עבודה דרך מסוף טקסטואלי. זה יכול להיות למשל הפעלה של דפדפן על מכונת ג'נקינס כדי לדבג בדיקה מסוימת שנכשלת רק על שרת ה CI, או להשאיר חלון של gvim של המכונה פתוח אצלי על המסך. תוכנות גרפיות ביוניקס מציגות את הממשק שלהן על מסוף גרפי שנקרא X. בדרך כלל הן מצפות שהמסוף הגרפי הזה ירוץ על המכונה שעליה התוכנית רצה, אבל ל X יש תמיכה מלאה בהצגת הממשק על מכונה אחרת. לכן בשביל להריץ תוכנה גרפית על מכונה מרוחקת נריץ אצלנו שרת X ונבקש מאותה התוכנית שתשלח את פקודות התצוגה שלה לשרת ה X שרץ אצלי על המכונה. בלינוקס יש לכם כבר שרת X פעיל (זה שרת ה Desktop שלכם) ואפשר להשתמש בו באמצעות האופציה -X לפקודת ה ssh. בדוגמה הבאה אני מתחבר למכונה מרוחקת ברשת המקומית שלי ומפעיל gvim כך ש gvim רץ על המכונה המרוחקת והממשק שלו מוצג אצלי על המסך:
$ ssh -X ynon@192.168.0.113
$ gvim
זה עבד בגלל שפקודת ssh מגדירה משתנה סביבה מיוחד בשם DISPLAY. משתנה זה קובע לתוכניות גרפיות על איזה מסוף להציג את הממשק. נציג את ערך המשתנה כדי לראות שזה עבד:
$ echo $DISPLAY
localhost:10.0
אם אתם עובדים בחלונות תצטרכו להתקין אצלכם שרת X לחלונות לדוגמה את Xming וב putty לסמן את האפשרות X11 Forwarding. לא ניסיתי את זה אבל כאן יש דף הוראות להרפתקנים: https://cat.pdx.edu/platforms/windows/remote-access/xming/. ## חיבור לשולחן עבודה מלא על שרת מרוחק עם VNC אומנם X11 Forwarding מצליח להעביר ממשק של תוכנית בודדת למכונה אחרת והוא פיתרון די נוח לתוכניות קטנות, אבל המנגנון אינו Optimised מספיק כדי להריץ שולחן עבודה מלא ולשדר את הממשק דרך הרשת. אם אתם רוצים להציג את כל שולחן העבודה מהמכונה המרוחקת על המכונה שלכם יהיה יעיל יותר להשתמש בפיתרון ייעודי לזה כדוגמת vnc. פרוטוקול VNC כולל שני חלקים: צד לקוח וצד שרת, ודואג לשדר את המידע הגרפי מהשרת ללקוח ואת הקלט מהלקוח לשרת. לכן על המכונה שנרצה להתחבר אליה יש להתקין את רכיב צד השרת של vnc, לדוגמה על debian זה יהיה:
$ sudo apt-get install tightvncserver
ובצד הלקוח נתקין את תוכנת הצפיה, לדוגמה על ubuntu זה יהיה:
$ sudo apt-get install xtightvncviewer
אתם יכולים למצוא VNC Viewer גם לחלונות למשל תוכנת tightvnc שזמינה בחינם בעמוד הזה: https://www.tightvnc.com/download.php. אחרי כל ההתקנות נכנסים למכונה אליה רוצים להתחבר ושם מפעילים את ה VNC Server עם:
$ vncserver
New 'X' desktop is ubuntu-s-1vcpu-1gb-ams3-01:1

Starting applications specified in /root/.vnc/xstartup
Log file is /root/.vnc/ubuntu-s-1vcpu-1gb-ams3-01:1.log
הפלט לא מספר לנו על איזה פורט הוא מאזין אבל זה כתוב בתוך קובץ ה log, אצלי הופיעה השורה הבאה שם:
24/03/21 18:36:46 Listening for VNC connections on TCP port 5901
אחרי זה הולכים ל VNC Viewer אצלכם על המכונה, מקלידים את כתובת ה IP של השרת ואת הפורט ואתם מחוברים. בסיום כדאי לסגור את השרת עם:
vncserver -kill :1
המספר :1 זה אותו מספר שהופיע בסוף שורת הפלט הראשונה בהפעלה של השרת והוא מייצג את ה DISPLAY עליו השרת רץ.

ToCode
1 419
ממש שמחתי לראות את העבודה הטובה שעשו ב Docker כדי לייצר ממשק זהה להעלאת קונטיינרים למספר ספקי ענן. למרות זאת קשה להתעלם מהעובדה שהגישה לשרתים של Azure היתה קלה ומהירה יותר. גם אם נתעלם מנושא בחירת ההרשאות - ב Azure היה לי הרבה יותר קל למצוא את ה Instances שנוצרו בממשק הבקרה שלהם ולראות סטטוס של המכונות דרך ממשק ה Web. זמני המתנה לפקודות docker היו קצרים יותר ולא נתקלתי בכישלונות אקראיים שהסתדרו מעצמם. אם היתה תחרות בין עננים אז לפחות בצד של ממשק עם דוקר הענן של מייקרוסופט מנצח בגדול.

ToCode
1 419
# העלאת קונטיינר דוקר לענן של AWS אתמול פירסמתי מדריך על העלאת קונטיינר דוקר לענן של Microsoft, וכבר כמה אנשים שאלו איך אפשר לפרסם את אותו קונטיינר בענן הגדול יותר של AWS. אז פתחתי את ה AWS CLI וציפיתי להתפנק עם כל הכלים המושקעים של אמזון. לצערי זאת לא היתה החוויה שקיבלתי אבל בכל זאת הצלחתי להגיע לענן וגם לשרוד כדי לספר. גירסאות עדכניות של docker כוללות מנגנון שנקרא Context. הקונטקסט מספר לדוקר איפה המכונות שצריכות להפעיל את הקונטיינרים יושבות ובאיזה מנגנון אנחנו מדברים עם מכונות אלה (מי ה Orchestrator). לדוקר יש אינטגרציה מובנית עם AWS ועם Azure Devops כך שכל מה שצריך בשביל להעלות קונטיינרים לענן הוא: 1. ליצור Context ולחבר אותו ל Azure Devops או ל AWS. 2. להשתמש בפקודות Docker רגילות כדי להפעיל קונטיינרים. 3. באופן אוטומטי הקונטיינרים ישתמשו בקונטקסט שהגדרנו ויעלו בענן במקום אצלי על המכונה. ניסוי? בשמחה. אבל קודם נצטרך לעבור את מדיניות האבטחה הדרקונית של אמזון. בתוך קונסולת הניהול יש להיכנס לשירות שנקרא IAM וליצור משתמש חדש. יש במדריך כאן רשימה של כל ההרשאות שאותו משתמש צריך. אני לא מצאתי איך לעשות copy/paste בקלות להרשאות אז בחרתי מרשימת ה Policies מה שנראה לי קשור. סך הכל בשביל משתמש שמצליח להעלות קונטיינרים לענן שלהם הוספתי את ה Policies הבאים:
AmazonEC2FullAccess
IAMFullAccess
AWSAgentlessDiscoveryService
ElasticLoadBalancingFullAccess
AmazonEC2ContainerRegistryFullAccess
AWSCloudMapFullAccess
CloudWatchLogsFullAccess
AmazonECS_FullAccess
AmazonRoute53FullAccess
AWSCloudFormationFullAccess
השיטה של להוסיף הרשאות עד שזה עובד מתאימה לפוסט, אבל לאפליקציה אמיתית כן שווה להתאמץ ולמצוא את ההרשאות המינימליות שאתם צריכים. אחרי יצירת המשתמש וההרשאות אתם צריכים לחבר אותו לדוקר קומפוז שלכם ובשביל זה נכנסים (עדיין ב IAM) לטאב Security Credentials, בוחרים Create Access Key ומשאירים את החלון עם ה Access Key ID וה Secret פתוח. משם פותחים בחלון חדש מסוף שורת פקודה ומפעילים משם:
docker context create ecs myawscontext
המילה האחרונה בשורה היא שם ה Context ואפשר לשנות אותה לכל שם שאתם רוצים. בתגובה דוקר ישאל אתכם איך להתחבר ל AWS ואתם תוכלו להגיד לו שיש לכם אסימון גישה ותדביקו את המזהה והסוד במסוף. בסיום התהליך יהיה לכם Context חדש עבור AWS ואפשר לוודא שזה עבד עם:
$ docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT   ORCHESTRATOR
default             moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                         swarm
myawscontext *      ecs                 (eu-north-1)
שימו לב לשורה השניה מסוג ecs ועם האזור שבחרתם ליצור בו את המכונות. ההבדל השני בין AWS ל Azure הוא שבעבודה עם AWS חייבים להשתמש ב Docker Compose ולא ניתן להעלות קונטיינר בודד. זה לא סוף העולם כי תמיד אפשר ליצור קובץ docker-compose.yml שמתאים לקונטיינר בודד. הנה הדמו שאני העליתי:
version: "3.9"
services:
  web:
    image: "nginxdemos/hello"
    ports:
      - "80:80"

את התוכן שמרתי בקובץ בשם docker-compose.yml בתיקיה חדשה (זה הקובץ היחיד בתיקיה) והמשכתי להעלות אותו לאמזון עם הפקודה:
$ docker compose up
הפקודה נכשלה באקראי מדי פעם על Timeout-ים, אבל גם לפעמים הצליחה, אז אם אתם מפעילים ומקבלים שגיאה שווה להפעיל פעם שניה בשביל לוודא שזו שגיאה אמיתית. כשהכל עובד תוכלו לראות את התוצאה עם:
$ docker compose ps
NAME                                                 SERVICE             STATUS              PORTS
task/mydockerdemo/cdf554598aab4f1fb3f1685bc074de98   web                 Running             mydoc-LoadB-13SBJMOZQ5M30-224910942.us-east-1.elb.amazonaws.com:80->80/http
שכבר נותן לנו את כתובת המכונה שמריצה את הדמו. גלישה לנתיב שמופיע שם תציג דף פתיחה משרת nginx. אחרי שהכל עבד אפשר להשתמש ב:
$ docker compose down
כדי להוריד את הסרביסים שיצרתם, או להפעיל מחדש docker compose up כדי להעלות גירסה חדשה.

ToCode
1 419
# העלאת קונטיינר דוקר לענן של Microsoft לפני כמה ימים כתבתי כאן על החשבון החינמי לשנה שפתחתי על Azure Devops, והיום היתה לי הזדמנות לשחק עם עוד מנגנון נחמד של המערכת והוא התקנת קונטיינרים בענן וגיליתי שדוקר כבר עשו בשבילנו את כל העבודה הקשה. גירסאות עדכניות של docker כוללות מנגנון שנקרא Context. הקונטקסט מספר לדוקר איפה המכונות שצריכות להפעיל את הקונטיינרים יושבות ובאיזה מנגנון אנחנו מדברים עם מכונות אלה (מי ה Orchestrator). לדוקר יש אינטגרציה מובנית עם AWS ועם Azure Devops כך שכל מה שצריך בשביל להעלות קונטיינרים לענן הוא: 1. ליצור Context ולחבר אותו ל Azure Devops או ל AWS. 2. להשתמש בפקודות Docker רגילות כדי להפעיל קונטיינרים. 3. באופן אוטומטי הקונטיינרים ישתמשו בקונטקסט שהגדרנו ויעלו בענן במקום אצלי על המכונה. ניסוי? בשמחה. בהנחה שיש גם לכם חשבון ב Azure Devops תוכלו להפעיל:
docker login azure
ואז דוקר יפתח דפדפן ויבקש מכם להתחבר לחשבון ה Azure Devops שלכם. אחרי הכניסה מפעילים את הפקודה:
docker context create aci mycontext
כדי ליצור את הקונטקסט שיהיה כבר מחובר לחשבון ה Azure שלנו. אפשר לראות שהפקודה הצליחה עם:
$ docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT   ORCHESTRATOR
default             moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                         swarm
mycontext *         aci                 yo@centralus
אפשר לראות שיש לי שני קונטקסטים על המכונה: הראשון נקרא default והשני נקרא mycontext. הפקודה הבאה אומרת לדוקר להתחיל להשתמש בקונטקסט השני:
$ docker context use mycontext
ועכשיו אנחנו מוכנים להרים קונטיינר לענן. הפקודה הבאה יצרה קונטיינר דמו של שרת node.js:
$ docker run -d -p 80:80 nginxdemos/hello
לפי האימג' nginxdemos/hello. כמובן שאתם יכולים לבחור כל Docker Image שתרצו ויש גם תמיכה מלאה ב docker-compose (עליה עוד נדבר בפוסט המשך בעתיד). לפקודת ה run יכול לקחת קצת זמן לסיים לרוץ אבל בסוף זה נגמר ואפשר לראות עם docker ps מה קיבלנו:
$ docker ps
CONTAINER ID        IMAGE               COMMAND             STATUS              PORTS
boring-aryabhata    nginxdemos/hello                        Running             52.182.228.142:80->80/tcp
כניסה לכתובת ה IP של המכונה תציג את דף הכניסה מהאימג', שהוא דף Hello World של שרת nginx.

ToCode
1 419
# מה צריך לקרות בשביל שתשנה את דעתך? ויכוחים בין מתכנתים נשמעים לכם אולי כמו דבר קטן אבל הרבה פעמים הרוחות יכולות להתלהט בגלל נושאים שלרוב האנשים בכלל לא אכפת מהם: צריך או לא צריך לכתוב בדיקות; כמה שורות כדאי לכתוב בקובץ; צריך או לא צריך עוד קומפוננטה; וים או אימקס. כשאנחנו תקועים בוויכוח כזה עם חבר ולא לגמרי בטוחים איך הגענו אליו (או איך יוצאים ממנו) השאלה שבכותרת יכולה לעזור: "מה צריך לקרות בשביל שתשנה את דעתך?". האם יש משהו שיכול לקרות שאחריו גם אתה תרגיש שבדיקות שוות את ההשקעה שלנו? האם יש משהו שיקרה בעתיד שיגרום לך לחשוב שהקוד הזה צריך לשבת בקומפוננטה נפרדת? מה צריך בשביל שתבין שאת החלק הזה בקוד צריך לכתוב ב C ? "מה צריך לקרות בשביל שתשנה את דעתך" לוקחת אותנו לצעד הבא. או שיש כזה דבר ואז אפשר לחשוב איך להגיע אליו בצורה לא מסוכנת, או שאין כזה ואז אפשר להפסיק את הוויכוח ולחפש את הדרכים שכל אחד יוכל להתקדם עם הדברים שחשובים לו בלי לשבור את הקוד לאחר.

ToCode
1 419
# הקמת אפליקציית Node.JS בענן של Azure DevOps מייקרוסופט הפכה בשנים האחרונות לאחת מספקיות הענן הגדולות, יחד עם AWS וגוגל, וכמו כולם גם הם מחלקים שנה ראשונה מתנה למי שיירשם. החלטתי לקחת את ההצעה ולראות דרך כמה ניסויים איך מתנהג ה Azure DevOps בהשוואה למתחרים. ## איך להרים שרת Node.JS בענן של מייקרוסופט המשחק הראשון שלי היה להרים אפליקציית Node.JS בענן. את התוצאה אפשר לראות כאן: https://yo3333.azurewebsites.net/. בגדול אתם מתחילים בהתקנת כלי ה CLI דרך המדריך הזה: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli. אחר כך יוצרים שרת בענן דרך המדריך הזה: https://docs.microsoft.com/en-us/azure/developer/javascript/tutorial/tutorial-vscode-azure-cli-node/tutorial-vscode-azure-cli-node-03. ובסוף בשביל לחבר את האפליקציה בענן לקוד שלכם אתם מגדירים מאגר גיט מרוחק על Azure Devops ודוחפים אליו את הקוד. את הפקודות שמחברות מצאתי במדריך הזה: https://docs.microsoft.com/en-us/azure/developer/javascript/tutorial/tutorial-vscode-azure-cli-node/tutorial-vscode-azure-cli-node-04 סך כל הסיפור לקח לי פחות מחצי שעה. ## מה אנחנו מקבלים אחרי כל ההתקנות יש לי בתיקיית הפרויקט על המחשב חיבור למאגר המרוחק של Azure Devops:
$ git remote -v
azure   https://ynonp-deploy@yo3333.scm.azurewebsites.net/yo3333.git (fetch)
azure   https://ynonp-deploy@yo3333.scm.azurewebsites.net/yo3333.git (push)
כל פעם שאני משנה קוד ועושה push למאגר azure בצורה אוטומטית המכונה בענן של MS לוקחת את הקוד החדש, מפעילה npm install ומאתחלת את האפליקציה לגירסה החדשה. בלי מאמץ קיבלתי Zero Downtime Deployment ותיעוד מלא של פעולות ה Deploy בממשק הניהול. ## למה לשים לב במדריכים של מייקרוסופט החיבור שנוצר הוא לענף master במאגר המרוחק. בגיטהאב ענף ברירת המחדל נקרא main במאגרים חדשים. שימו לב אם אתם עובדים עם כמה מדריכים לא להתבלבל בשמות. דבר נוסף הוא שבהתקנת כלי ה CLI הם מציעים סקריפט אוטומטי שעושה הכל. אצלי הוא לא עבד. ## מה עוד נשאר לניסויים הבאים עכשיו שיש לי אפליקציה ראשונה בענן יש המון דברים שיהיה מעניין לנסות: 1. איך מקימים בסיס נתונים ומתחברים אליו 2. איך מקימים Load Balancer ועושים Scale Up לאפליקציה 3. איך להשתמש במערכת הלוגים של Azure Devops 4. איך להשתמש במערכת הניטור של Azure Devops 5. איך להקים Micro Service נוסף (אולי בשפה אחרת) ולתקשר ביניהם כמובן אמשיך לכתוב כאן גם על הניסויים הבאים. בינתיים כבר שמתי לב שממשק הניהול של Azure Devops נראה הרבה יותר פשוט מזה של AWS, אבל אולי זה בגלל שאני רק בתחילת הדרך שם.

ToCode
1 419
# גנרטור רקורסיבי ב Python שני פיצ'רים של פייתון שמסתדרים יופי יחד הם ה Generators והרקורסיות. בתרגיל של היום רציתי לשלב את שניהם כדי לחפש מידע במבנה נתונים של עץ. במבנה של עץ יש לכל צומת ערך ואוסף של ילדים אליהם אפשר להגיע מאותו צומת. ב Python אפשר לתאר את כל המבנה בצורה רקורסיבית עם המחלקה הבאה:
@dataclass
class Node:
    value: int
    children: List['Node']
יש המון מבנים מהעולם האמיתי שאפשר לתאר באמצעות מבנה נתונים של עץ - לדוגמה בואו נחשוב על מתקנים בלונה פארק. הערך המספרי ייצג כמה המתקן מדליק והחיבורים יהיו השבילים בין המתקנים, כך שאם מתקן מסוים הוא ברשימת הילדים של מתקן אחר זה אומר שאפשר להגיע מהמתקן שלנו למתקן האחר. הקוד הבא יכול לייצר את המפה של הלונה פארק במבנה העץ שאנחנו צריכים:

root = Node(10, [
    Node(5, [
        Node(99, [
            Node(101, []),
            Node(102, []),
            Node(103, []),
            ]),
        Node(9, [
            Node(25, [])
            ]),
        ]),
    Node(8, [])
    ])
עכשיו אפשר להמשיך ולשאול "מה המתקן הכי שווה בפארק?". קל לראות בעין שהערך של המתקן הכי מוצלח הוא 103. עכשיו נראה איך פייתון היה יכול למצוא את זה:
def dfs(node):
    yield node.value
    for child in node.children:
        yield from dfs(child)
הפונקציה מקבלת צומת בעץ ומתפקדת כ Generator, היא מדווחת על הערך שהיא רואה ואז ממשיכה את הטיול בפארק וכל פעם ממשיכה לדווח על ערכים. רק זה מספיק בשביל שפייתון יוכל להדפיס את הערך הגדול ביותר בעץ:
print(max(dfs(root)))
עכשיו נסו אתם שני תרגילי הרחבה: 1. הקוד שכתבתי לא יתמודד טוב עם עץ שחלקים ממנו חוזרים על עצמם. אני יודע בלונה פארק זה יהיה מוזר לדמיין קטע של הפארק משוכפל לכמה שבילים, אבל במקומות אחרים זה יכול לקרות. נסו לחשוב מה יקרה אם אחד הצמתים יופיע בכמה מקומות ואז לתקן את הקוד. 2. בטיול אמיתי בלונה פארק אם יש מתקן קרוב אליי שערכו 70 ומתקן ממש רחוק שערכו 75, אני אעדיף את המתקן הקרוב. נסו לחשוב איך להוסיף את המרחק לחישוב ומצאו את המתקן הכי אטרקטיבי כשלוקחים בחשבון את המרחק. הקוד המלא שכתבתי כתוכנית אחת למי שרוצה לקחת כבסיס לתרגילי ההרחבה הוא:
from dataclasses import dataclass
from typing import List


@dataclass
class Node:
    value: int
    children: List['Node']


def dfs(node):
    yield node.value
    for child in node.children:
        yield from dfs(child)


root = Node(10, [
    Node(5, [
        Node(99, [
            Node(101, []),
            Node(102, []),
            Node(103, []),
            ]),
        Node(9, [
            Node(25, [])
            ]),
        ]),
    Node(8, [])
    ])

print(max(dfs(root)))
print("---")

for v in dfs(root):
    print(v)