ToCode
Открыть в Telegram
1 420
Подписчики
Нет данных24 часа
+27 дней
-230 день
Архив постов
1 420
# דוקר למה להעתיק את ה package.json לפני כל השאר?
בגלל שקובץ Dockerfile לא "מתאר" את הסביבה שאנחנו בונים אלא הוא מהווה ממש מתכון לבניית הסביבה, שינויים קטנים בקובץ יכולים להשפיע על תהליך העבודה אפילו שהסביבה שתיווצר תהיה זהה. קחו לדוגמה את קובץ ה Dockerfile הבא:
FROM node:16
WORKDIR /app
COPY app /app
RUN npm install
CMD ["node", "main.js"]
זה נראה פשוט נכון? הוא מעתיק את היישום לתיקיית היעד, מפעיל npm install ומגדיר את פקודת ההפעלה. וזה באמת עובד יופי, עם בעיה קטנה - כל פעם שמשנים קובץ קוד ביישום ובונים מחדש את האימג', דוקר צריך להפעיל מחדש npm install ולהתקין מחדש את כל התלויות.
אם אתם בונים את האימג' שוב ושוב על אותו מחשב, שינוי קטן בדוקרפייל יכול לעשות הבדל גדול. זה הקוד המתוקן:
FROM node:16
WORKDIR /app
COPY app/package.json /app
RUN npm install
COPY app /app
CMD ["node", "main.js"]
הקוד המתוקן מפריד בין הצעד של התקנת התלויות לצעד של העתקת היישום, ומתחיל את העבודה בהתקנת התלויות מאחר וזה הצעד היותר ארוך. בגלל שהעתקת היישום קורית אחרי התקנת התלויות, אם נשנה את הקוד בקבצים בתוך תיקיית app ונבנה מחדש דוקר לא יצטרך להתקין מחדש את התלויות. הוא יכול להשתמש בשכבה שהוא כבר בנה אחרי התקנת התלויות ולהמשיך רק את פקודת ה COPY app /app מאותה נקודה.
במקרה הזה שינוי סדר השורות ובחירת שכבות נכונה לא משפיעה על התוצאה הסופית, אבל הופכת את תהליך הפיתוח שלנו למהיר יותר.1 420
# טיפ דיבאג - שימו לב לגירסאות
סאם הוא כלי פיתוח של AWS שעוזר לכתוב קוד מקומי, לבדוק מקומית חלק מהדברים, ליצור את כל התשתית בתור קוד (קבצי yaml) ולהעלות ל AWS את כל העבודה שלכם. בגדול באמת אחלה כלי אבל יש לו די הרבה באגים וכמו כל העולם של AWS צריך לדעת לעבוד איתו.
הסיפור היום הוא על אחד הבאגים של סאם שכמעט עלה לי בשעתיים מבוזבזות, והזכיר לי את אחת הבעיות בעבודה עם requirements.txt.
## מה יכול להשתבש
קובץ requirements.txt מאפשר לנו להגדיר את הספריות שתוכנית הפייתון שלנו צריכה, ובעזרת פקודת:
$ pip install -r requirementst.txt
אנחנו מתקינים את כל הספריות בגירסאות שביקשנו.
מה שנוטים לשכוח הוא שפייתון לא בודק בעליה של התוכנית שהגירסאות של הספריות שיש לו מתאימות למה שביקשתם ב requirements.txt.
תיאורטית אם מנסים לעשות import לחבילה שצריכה התקנה והחבילה הותקנה, אז היא כנראה הותקנה מהגירסה שביקשתם. אם היא לא הותקנה בכלל אז ה import ייכשל. המצב השלישי הוא הבעייתי - מה אם מי שמריץ את הסקריפט הוא Docker Image שכבר מותקנות בו חבילות מסוימות בגירסאות מסוימות, ובמקרה החבילה שרציתם כבר מותקנת בגירסה ישנה יותר?
## הבאג של סאם
הפקודה:
sam init --runtime python3.9 --name location-demo
יוצרת פרויקט Sam מקומי חדש עבור פייתון. תבנית ה Quickstart שלהם יוצרת קובץ בשם requirements.txt והפקודה:
sam local start-api
מפעילה שרת מקומי שיפעיל את תוכנית הפייתון ויחזיר תשובה. אבל אף אחד מהצעדים האלה לא מתקין את החבילות ב requirements.txt. בשביל להתקין את החבילות יש להפעיל:
sam build
האתגר הוא שחבילות מסוימות כבר מותקנות בתוך ה Image שסאם מריץ, ולכן גם אם לא תפעילו build עדיין תקבלו לדוגמה את חבילת boto3 שאיתה אפשר לפנות ל AWS, אבל בגירסה ישנה שלפעמים מחזירה מידע חלקי (בלי הודעות שגיאה, מי צריך אותן).
האתגר השני הוא שעד שלא הפעלתם build אתם עובדים עם האימג' הדיפולטי ואז סאם מזהה שינויים בקוד ומיד אתם מקבלים על השרת את הגירסה העדכנית ביותר (Hot Reloading). מרגע שהפעלתם build פעם אחת זיהוי שינויים אוטומטי יפסיק לעבוד ותצטרכו להפעיל build אחרי כל שינוי בקוד.
## איך בכל זאת רואים את זה
האמת שהחשד הראשון שלי כשראיתי את המידע החלקי בלי הודעות שגיאה היה בעיית גירסאות, וכדי לאמת אותו הוספתי לקוד הפייתון שלי את הבלוק:
import boto3
if boto3.__version__ != "1.26.82":
raise Exception(f"Incorrect Boto Version {boto3.__version__}")
וכצפוי התוכנית התרסקה עם הודעת שגיאה מפורשת.1 420
# תנו צ'אנס לנווד - ארבע תכונות טובות של Nomad
האשיקורפ נוסדה ב 2012 ושלוש שנים מאוחר יותר ב 2015 הוציאה את כלי ניהול הקונטיינרים שלה שנקרא Nomad. קוברנטיס היה אז בן שנה אבל כבר השיג אחיזה בשוק, ובנוסף נהנה מהגיבוי של גוגל כך שהסיכוי של נומד מלכתחילה היה נמוך.
אבל למרות שנומד לא ניצח בתחרות הפופולריות, הוא עדיין גדל, מתוחזק ונמצא בשימוש בעולם האמיתי. ואם אתם עדיין לא "נעולים" על קוברנטיס, הנה שלוש סיבות בגללן שווה לפחות לבחון את הנווד-
1. קלות התקנה - בשביל להקים קלאסטר של נוודים כל מה שצריך זה להוריד קובץ בינארי אחד למחשב (התוכנה זמינה למק, חלונות ולינוקס). הפעלה של אותו Agent הופכת את המכונה שלכם לשרת Nomad. כשתפעילו את ה Agent על מחשב נוסף תוכלו לתת לו את כתובת ה IP של המחשב הראשון ויש לכם קלאסטר. נומד לא בררן ואפשר להפעיל Agent-ים שלו על כל דבר. ה Agent אוטומטית סורק את המכונה ומצרף אותה לקלאסטר עם היכולות שהוא מזהה.
2. דוגמה נוספת היא ביצירת גמישות - קוברנטיס רוצה שליטה מלאה על הקלאסטר והמכונות שבו, ומוכן להריץ רק קונטיינרים. נומד הוא כבר הרבה יותר גמיש. יש לך קובץ בינארי להפעיל? קיבלת. תוכנית Java? מספיק טוב. קונטיינר? כמובן. הוא מצליח להפעיל כל דבר כי כל מכונה שמתחברת לקלאסטר מדווחת על היכולות שלה, למשל אם יש לה דוקר מותקן או אם היא יכולה להריץ קבצי exe או אם מותקן עליה Java, ואז כשהקלאסטר צריך להריץ משימה הוא יחפש איזה מכונה הכי תתאים להרצה לפי האילוצים ויתן לה את המשימה. דוגמה נוספת היא ב Storage: נומד מוכן להתחבר לכל התקן איחסון שיש לכם, בין אם זה כונן מקומי, תיקיית רשת או איחסון בענן ולחבר אותם לקונטיינרים.
3. פיצ'רים קטנים שעושים את ההבדל - נכון אין לו את כל הפיצ'רים המסובכים של קוברנטיס, אבל בפינוקים קטנים נומד מצטיין. לדוגמה הפקודה
nomad plan תקבל קובץ מניפסט (זה נקרא Job Specification שם) ותגיד לכם בדיוק ובצורה מפורטת מה הוא הולך לעשות כשתנסו להריץ את הג'וב הזה, וכמובן תזהה בעיות לפני שהן קורות. או התמיכה ב Canary Updates שבעזרת שורת הגדרה אחת נומד יריץ את המערכת שלכם במכונה חדשה בתור קנרית ורק אם היא עלתה כמו שצריך הוא ישדרג את כל המכונות הקיימות.
4. אינטגרציה עם שאר הכלים של HashiCorp - במקום להכניס את הכל לכלי אחד האשיקורפ יצרו אוסף כלים עצמאיים שיכולים לעבוד יחד בתור מכפיל כח. לדוגמה Terraform יודע לבנות ארכיטקטורת רשת על כל ענן שתתנו לו (וכך יהיה קל להתקין את נומד על כל ענן ולעבור בקלות בין עננים), Vault יודע לנהל סודות ולכן קלאסטרים של נומד ישתמשו בו כדי להזריק סודות לקונטיינרים ו Consul מנהל את ה Service Discovery בתוך הקלאסטר. היתרון בבחירה לחלק את המשימות לכלים יחסית קטנים ועצמאיים היא שאפשר ללמוד כל כלי בפני עצמו ולהתחיל להשתמש למשל ב Nomad בלי הכלים האחרים, אבל אז כשלומדים על Terraform מגלים כמה קל לשלב בין השניים.
סך הכל נומד אולי לא ניצח בתחרות פופולריות, אבל אם גם אתם קצת עייפים מהסיבוכיות או הקיבעונות של קוברנטיס, ומחפשים לדוגמה משהו שיהיה קל להתקנה, יאפשר לכם לעבוד גם עם התשתית הקיימת וגם לשדרג לקונטיינרים רק חלקים מהמערכת בהדרגה אז נומד יכול להיות פיתרון מעניין.
נ.ב. נקודת התחלה טובה לעבודה איתו היא אוסף ה Tutorials באתר שלהם כאן:
https://developer.hashicorp.com/nomad/tutorials/get-started.1 420
# האם ריאקט הוא סקאם?
כשמישהו שואל "האם טכנולוגיה X היא Scam" הוא בדרך כלל מתכוון לשילוב של הגורמים הבאים:
1. יש טכנולוגיות יותר פשוטות שפותרות את אותה בעיה.
2. הרבה אנשים בוחרים בטכנולוגיה המסובכת בגלל יחסי הציבור שלה ולא בגלל הערך האמיתי.
וכך השאלה ממשיכה לחזור לגבי שיטות עבודה וטכנולוגיה חדשות - האם הענן הוא רמאות (יותר זול להחזיק שרת שלכם), האם אג'ייל הוא רמאות (זה רק גורם לנו לעבוד יותר לאט), האם בדיקות יחידה הן רמאות (אני עובד כל היום לכתוב בדיקות והן אף פעם לא מצאו באגים), וכמובן האם JavaScript Frameworks הן רמאות.
אנחנו בוחרים טכנולוגיה שאנחנו חושבים שתפתור לנו בעיות שכבר יש לנו עם הטכנולוגיה שעבדנו איתה בפרויקט הקודם. מי שבחר ריאקט ידע טוב מאוד מה זה jQuery או AngularJS ואיך ריאקט יפתור את הבעיות הספציפיות שהיו בטכנולוגיות הקודמות. מי שבחר בענן ידע בדיוק מה זה לנהל שרת לבד, כולל את כל הבלאגן של להרים עוד ועוד שרתים ולעקוב אחריהם. לאנשים האלה היתה בעיה אמיתית והטכנולוגיה החדשה פתרה אותה.
בנוסף אליהם יש קבוצה מאוד גדולה של אנשים שמסתכלים מה עושים בפרויקטים אחרים ובוחרים את הטכנולוגיה שכולם לוקחים כי "אם זה מספיק טוב לפייסבוק זה בטח מספיק טוב גם בשבילי". אלה האנשים שבוחרים את הפיתרון לפני שנתקלו בבעיה כדי למנוע אותה. גם זאת לא התנהגות כל כך מוזרה, אם ניזכר שפרויקטי תוכנה הם לא צפויים ואין לך מושג באיזה בעיות תיתקל. לבחור טכנולוגיה שכולם משתמשים בה מבטיח שמגבלה טכנולוגית לא תכריח אותך לשכתב את כל הקוד בעוד שנה כשהפרויקט יגדל.
כן ריאקט מסובך, האקוסיסטם עמוס וכל הזמן צריך להתעדכן בשינויים ושיטות עבודה חדשות. כן הרבה אנשים שעובדים בריאקט היום רואים את הבעיות שבו. אבל לא, זה לא Scam ונכון להיום בפרויקט גדול יש עדיין הרבה יתרונות בחלוקת המערכת לקוד צד שרת שמחזיר JSON-ים דרך API (לא משנה אם REST או GraphQL) וקוד צד לקוח עצמאי שאחראי על התצוגה.
1 420
// ...define your main app here }ולמרות שהשטרודל באמת מחליף את המימוש של הפונקציה בפונקציה אחרת (תוצאת הפעלת הפונקציה connect), הרבה יותר קל וברור לעשות את ההחלפה לבד ולייצא את התוצאה של connect:
class MyApp extends React.Component {
// ...define your main app here
}
export default connect(mapStateToProps, mapDispatchToProps)(MyApp);
דוגמה שלישית מהספריה InversifyJS שהכריחה אותנו להשתמש בדקורטור בשביל להצהיר שמחלקה מסוימת ניתנת להזרקה:
@injectable()
class Katana implements Weapon {
public hit() {
return "cut!";
}
}
אבל מבט במימוש מראה שוב שבסך הכל יש פה קריאה לפונקציה:
function injectable() {
return function <T extends abstract new (...args: any) => unknown>(target: T) {
if (Reflect.hasOwnMetadata(METADATA_KEY.PARAM_TYPES, target)) {
throw new Error(ERRORS_MSGS.DUPLICATED_INJECTABLE_DECORATOR);
}
const types = Reflect.getMetadata(METADATA_KEY.DESIGN_PARAM_TYPES, target) || [];
Reflect.defineMetadata(METADATA_KEY.PARAM_TYPES, types, target);
return target;
};
}
export { injectable };
ולכן היה יותר קל אם היו מבקשים מאיתנו להפעיל את Reflect.defineMetadata עבור כל מחלקה שצריכה להיות Injectable. בצורה כזאת הייתי יכול להחליט לבד אם אני רוצה לקרוא לפונקציה בבנאי של המחלקה, או בקובץ נפרד שיגדיר במרוכז את כל המחלקות שאפשר להזריק.
הבעיה הגדולה ש Decorators יצרו בפעמים הקודמות שיכלנו לכתוב אותם היא שימוש יתר על חשבון מנגנונים פשוטים ומוכרים יותר. כל כותבי הספריות של העולם החליטו שעדיף להוסיף שטרודל מעל קלאס או פונקציה מאשר פשוט לקרוא לפונקציה מהספריה שלהם, אפילו שדרישה כזו לא תמיד היתה לטובת הקוד שמשתמש בספריה.1 420
# תפסו מחסה, הדקורטורים חוזרים!
טייפסקריפט 5 תכף יוצאת ואחד הפיצ'רים המטרידים בה הוא התמיכה המחודשת בדקורטורים. מסתבר שבזמן שלא שמנו לב סימני הכרוכית הצליחו לגייס תמיכה ב TC39 ועושים את דרכם במהירות לדפדפנים שלנו, כשטייפסקריפט היא הסנונית הראשונה. בואו ניזכר מהם ולמה בפעם הקודמת הם לא עבדו טוב.
## מהם דקורטורים
בהצעה ב TC39 מסבירים לנו שדקורטור הוא תחביר שמאפשר להחליף פונקציה באחרת, וכך לשתף קוד בקלות בין פונקציות. אחת הדוגמאות שם היא הדקורטור logged:
function logged(value, { kind, name }) {
if (kind === "method") {
return function (...args) {
console.log(`starting ${name} with arguments ${args.join(", ")}`);
const ret = value.call(this, ...args);
console.log(`ending ${name}`);
return ret;
};
}
}
class C {
@logged
m(arg) {}
}
התוספת @logged מעל הגדרת הפונקציה m בקלאס C תגרום להחלפת הפונקציה m בפונקציה שחוזרת מהפונקציה logged, וכך להוספת הדפסות דיבג "לפני" ו"אחרי" הפעלת הפונקציה. בתיאוריה היכולת לשנות את התוכן של פונקציה בלי לגעת במימוש עצמו מאפשרת "לקשט" פונקציות בכל מיני יכולות. דוגמה קלאסית היא דקורטור של Memoization שגורם לפונקציה "לזכור" תוצאות של הפעלות ישנות שלה ואם יפעילו את הפונקציה פעם נוספת עם אותם פרמטרים היא תחזיר את התוצאה מהזיכרון במקום לחשב מחדש.
## אז מה רע בזה?
עד לכאן אפשר לקבל את הרושם ש Decorators הם פיצ'ר קצת אזוטרי אבל לא מאוד מזיק של JavaScript. לפחות כך אני חשבתי כשלמדתי עליהם לראשונה. לצערי המציאות היתה גרועה יותר מכל דמיון. ההצעה למימוש דקורטורים היא למעשה האיטרציה הרביעית של התחביר שלהם (אפשר לקרוא על ההיסטוריה כאן), וכבר לפני כמה שנים כלי בניה כבר הכניסו תמיכה בדקורטורים באחת האיטרציות הקודמות שלהם. זה נגמר רע כי התברר שהתקן יהיה שונה ממה שכולם חשבו, אבל הספיק לתת לנו הצצה לאיך אנשים משתמשים בדקורטורים.
מסתבר שהשימוש המרכזי בדקורטורים לא היה כדי להחליף פונקציה בפונקציה אחרת או "להלביש" אספקט מימושי מסוים על מספר פונקציות, אלא בתור אנוטציה או "תיעוד חי". במקום סתם להפעיל פונקציה, כותבי ספריות דרשו מאיתנו להוסיף אנוטציות על "דברים" כחלק מהממשק של הספריה. כמה דוגמאות-
ספריית TypeORM משתמשת ב Decorators כדי לציין שמחלקה מסוימת היא Entity וצריכה להיות "מחוברת" לטבלה בבסיס הנתונים:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column()
age: number
}
אבל מבט למימוש מראה לנו שכל מה ש @Entity עושה זה לקרוא לפונקציה:
export function Entity(
nameOrOptions?: string | EntityOptions,
maybeOptions?: EntityOptions,
): ClassDecorator {
const options =
(ObjectUtils.isObject(nameOrOptions)
? (nameOrOptions as EntityOptions)
: maybeOptions) || {}
const name =
typeof nameOrOptions === "string" ? nameOrOptions : options.name
return function (target) {
getMetadataArgsStorage().tables.push({
target: target,
name: name,
type: "regular",
orderBy: options.orderBy ? options.orderBy : undefined,
engine: options.engine ? options.engine : undefined,
database: options.database ? options.database : undefined,
schema: options.schema ? options.schema : undefined,
synchronize: options.synchronize,
withoutRowid: options.withoutRowid,
} as TableMetadataArgs)
}
}
הבחירה ב Decorator שלא מחליף את הפונקציה במימוש אחר היא לא טבעית, מייצרת עקומת למידה והופכת את כל ה API לפחות נגיש.
אפילו כשהיתה לכאורה הצדקה לשימוש בדקורטור כי אנחנו באמת רוצים להחליף את המימוש, התוצאה היתה לא טבעית ומבלבלת. ניקח את react-redux כדוגמה וניזכר שפעם כתבנו קוד כזה כדי לחבר קומפוננטת ריאקט ל Redux Store:
@connect(mapStateToProps, mapDispatchToProps)
export default class MyApp extends React.Component {1 420
# היום למדתי: כבר מזמן לא צריך להוסיף קו תחתי אחרי exec ב Qt for Python
לפמעים צריך לחכות שמשהו יהפוך ל Deprecated עד שאני אשים לב שכבר הרבה זמן הוא לא היה הכרחי. זה לפחות היה הסיפור עם PyQt ו PySide והפקודה exec.
בפייתון 2 המילה exec היתה מילה שמורה. הפקודה exec של Qt היא זאת שמפעילה את הלולאה הראשית של היישום, והשם exec הוא בדיוק שם הפונקציה ב
C++, לכן ובשביל לא לבלבל יותר מדי מתכנתים שכבר מכירים Qt מ C++, בפייתון בחרו לאמץ את השם אבל להוסיף _ בסוף. תוכנית PyQt פשוטה של שלום עולם לכן נראתה כך:
import sys
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QLabel
if __name__ == "__main__":
app = QApplication(sys.argv)
label = QLabel("Hello World", alignment=Qt.AlignCenter)
label.show()
sys.exit(app.exec_())
אבל בפייתון 3 המילה exec כבר לא היתה מילה שמורה, ולכן החל מ Qt5 אפשר להשתמש באותו exec של C++. בגירסה 6 אנחנו כבר מקבלים Deprecation Warning על ה exec עם הקו התחתי בסוף. אגב גם על sys.exit שמופיע בתוכנית הדוגמה אפשר לוותר והקוד הבא יעשה בדיוק את אותו דבר ובלי להתלונן:
import sys
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QLabel
if __name__ == "__main__":
app = QApplication(sys.argv)
label = QLabel("Hello World", alignment=Qt.AlignCenter)
label.show()
app.exec()
נ.ב. רוצים להפעיל אותו אצלכם? כל מה שצריך זה להתקין לפני את החבילות של PySide ואתם מסודרים:
pip install PySide61 420
# טיפ גיטהאב: איך להדפיס את הלוג של ההרצה האחרונה דרך שורת הפקודה
לגיטהאב יש כלי שורת פקודה שנקרא gh שהוא סוג של קסם, כי הוא מאפשר לעשות כל דבר שאפשר לעשות דרך האתר, משורת הפקודה ועם פלט JSON שמאוד קל לפענוח. בעבר כתבתי עליו בפוסט איך ליצור Pull Request משורת הפקודה והיום הוא חוזר בשביל לענות על עוד שאלה מתלמיד - איך להדפיס את הלוג של ההרצה האחרונה של Github Action משורת הפקודה? ופה עלינו בסך הכל לשלב שתי פקודות.
הפקודה הראשונה שצריך להכיר היא
gh run list. כשאני מריץ את הפקודה מתיקיה עם ריפוזיטורי, היא מדפיסה למסך את כל ההרצות של ה Actions מהריפוזיטורי. בעזרת המתג --json אני יכול לבקש רק פרטים מסוימים על כל הרצה ובעזרת -L אני מגביל את מספר התוצאות. ככה זה נראה עם ריפו שלי בהגבלה ל-3 הרצות אחרונות:
$ PAGER= gh run list --json name,databaseId,startedAt -L 3
[
{
"databaseId": 4289773851,
"name": "publish-daily-post-to-telegram",
"startedAt": "2023-02-28T05:11:50Z"
},
{
"databaseId": 4279130715,
"name": "publish-daily-post-to-telegram",
"startedAt": "2023-02-27T05:11:47Z"
},
{
"databaseId": 4273470712,
"name": "publish-daily-post-to-telegram",
"startedAt": "2023-02-26T05:10:54Z"
}
]
הפקודה השניה שצריך להכיר היא gh run view שמקבלת מספר ריצה ומדפיסה פרטים עליה. המתג --log יגרום ל view להציג את הלוג המלא של ההרצה. ככה זה נראה אצלי עם ההרצה העדכנית ביותר:
$ PAGER= gh run view --log 4289773851 | head
publish Set up job 2023-02-28T05:12:00.4187561Z Current runner version: '2.301.1'
publish Set up job 2023-02-28T05:12:00.4215234Z ##[group]Operating System
publish Set up job 2023-02-28T05:12:00.4215881Z Ubuntu
publish Set up job 2023-02-28T05:12:00.4216132Z 22.04.2
publish Set up job 2023-02-28T05:12:00.4216414Z LTS
publish Set up job 2023-02-28T05:12:00.4216728Z ##[endgroup]
publish Set up job 2023-02-28T05:12:00.4217052Z ##[group]Runner Image
publish Set up job 2023-02-28T05:12:00.4217433Z Image: ubuntu-22.04
publish Set up job 2023-02-28T05:12:00.4217721Z Version: 20230219.1
publish Set up job 2023-02-28T05:12:00.4218252Z Included Software: https://github.com/actions/runner-images/blob/ubuntu22/20230219.1/images/linux/Ubuntu2204-Readme.md
וזה כמובן ממשיך לכל הלוג.
שילוב של שתי הפקודות יאפשר לי בשורה אחת לקבל את הלוג המלא של ההרצה האחרונה של Action בריפו:
$ PAGER= gh run view --log $(gh run list --json databaseId -q '.[].databaseId' -L 1)
נ.ב. המתג -q מקבל שאילתה בפורמט של jq. בזכותו פלט הפקודה היה רק מזהה הריצה במקום ה JSON המלא.1 420
# מה הסיפור עם הלוכסנים אחרי כתובת IP?
נתקלתם פעם בכתובת 10.10.64.0/27? ומה לגבי 192.168.0.0/24?
כשאנחנו מדברים על אוסף כתובות IP ברשת מסוימת הרבה פעמים נבחר לכתוב אותן בתור כתובת ואחריה לוכסן ואז מספר, בכתיב שנקרא CIDR Block. האותיות CIDR הן ראשי תיבות של המילים Classless Inter-Domain Routing. השיטה עצמה הוצגה כבר ב 1993 במטרה לחסוך בכתובת IP ולספק גמישות לארגונים שרצו לחלק את הרשת הפנימית שלהם למקטעים.
חלוקת רשת למקטעים חוסכת בכתובות IP כי לא כל מכונה ברשת הפנימית צריכה כתובת IP ציבורית. חישבו על הרשת הפנימית שלכם בבית - המחשבים יכולים לקבל כתובת IP כמו 192.168.0.124, ובעזרתה המכונות ברשת הפנימית מתקשרות אחת עם השניה, אבל רק כתובת ה IP החיצונית של הראוטר תהיה נגישה מהאינטרנט. בניית רשת פנימית חסכה לספק האינטרנט שלכם כתובות IP, כי עכשיו הם יכולים לתת לכם רק כתובת IP אחת בשביל הראוטר ולא צריכים להקצות כתובת לכל מכונה ברשת הביתית.
כתיב CIDR הוא פשוט דרך לייצג אוסף של כתובות IP עוקבות. כשהראוטר שלכם רואה הודעה שנשלחת בין שתי מכונות בתוך אותו בלוק שהוא מנהל, הוא לא צריך לשלוח את ההודעה החוצה ל ISP, ויכול מיד להעביר אותה למכונת היעד.
לכן כשאני כותב 192.168.0.0/30 אני בעצם כותב את 4 כתובות ה IP:
192.168.0.0
192.168.0.1
192.168.0.2
192.168.0.3
ובאופן כללי כתיב של כתובת IP שאחריה לוכסן ואז מספר מפורש בתור בלוק של כתובות IP צמודות.
איך יודעים איזה כתובות IP מתאימות ל CIDR Block מסוים? טוב ששאלתם. קודם כל יש תוכנות שפורסות רשימות כאלה, לדוגמה אם יש לכם nmap מותקן תוכלו לכתוב:
$ nmap -sL -n 192.168.0.0/30| awk '/for/{print $NF}'
ולקבל בדיוק את הרשימה (ה awk בסוף בא לסנן כמה שורות לא חשובות ש nmap מדפיס). ואפשר גם לחשב את הרשימה יחסית בקלות כשמבינים מה הכתיב מייצג.
כל כתובת IP ניתנת לייצוג בגירסה בינארית באמצעות המרה לבינארי של כל אחד מהמספרים שמרכיב אותה. לדוגמה הכתובת 172.16.21.9 מיוצגת בבינארית על ידי המספר:
10101100.00010000.00010101.00001001
בכתיב CIDR המספר שאחרי הלוכסן מייצג כמה ביטים משמשים לייצוג הרשת, ובעצם יישארו קבועים בכל כתובת IP שנכתוב. המספר הזה נע בין 0, המייצג בלוק של כל כתובות ה IP האפשריות, ל 32 המייצג בלוק שמורכב מכתובת IP בודדת. בשביל שיהיה נוח לכתוב, כתובת הבסיס של בלוק CIDR מגדירה את הביטים של ה Host להיות מאופסים.
אז אם נשאר עם הכתובת 172.16.21.9 אני יכול לדבר על הבלוק 172.16.21.9/31 בו נמצאות רק שתי הכתובת:
172.16.21.8
172.16.21.9
או בכתיב בינארי:
10101100.00010000.00010101.00001000
10101100.00010000.00010101.00001001
ושימו לב איך ה 31 ביטים הראשונים זהים ורק הביט האחרון משתנה מ 0 ל 1. ככל שאוריד את המספר שאחרי הלוכסן (מספר הביטים של הרשת), כך יהיו לי יותר כתובות IP שונות בבלוק. הבלוק 172.16.0.0/24 כבר מכיל 256 כתובות - כל הכתובות בהן שלושת המספרים הראשונים הם 172.16.0. הבלוק 172.16.0.0/28 מכיל רק 16 כתובות, הראשונה היא 172.16.0.0 והאחרונה היא 172.16.0.15. בשביל להגיע לבלוק הבא של 16 כתובות אני מגדיל את המספר האחרון בכתובת, כלומר הבלוק 172.16.0.16/28 מכיל גם הוא 16 כתובות אבל הפעם הכתובת הראשונה היא 172.16.0.16 והאחרונה היא 172.16.0.31.1 420
# באגים בלתי נראים
כלי פיתוח טובים הם קודם כל כלים שיודעים להצביע (ברמת דיוק גבוהה) על קוד שנראה כמו באג. וכן, כלי פיתוח קשורים לשפה ועובדים יחד איתה. בדוגמה קלאסית מ JavaScript, הקוד הזה שגוי אבל ל eslint לא אכפת:
function countUntil(n) {
let i=0;
while(i < n)
console.log(i);
i++;
}
countUntil(10);
קל לראות שהשורה השניה ב"בלוק" ה while היא בעצם בכלל לא בבלוק ה while, ולכן הלולאה תרוץ עד אינסוף. בשביל לתקן אני לא צריך לעבור לפייתון, מספיק להוסיף ל eslint את הכלל indent כדי שהוא יצעק שהשורה i++ לא במקום.
אחת העבודות החשובות שלנו כמתכנתים היא למצוא את הכלים הנכונים - לא הכלים שיכתבו במקומנו את הקוד, אלא קודם כל הכלים שיצביעו על שגיאות שאולי לעין קל לפספס.1 420
# המסמך הכי חשוב שאתם לא כותבים
שידרוגי תוכנה זה קשה וככל שהמערכת בשלה ומורכבת יותר זה רק נהיה יותר מסובך. בעיה ראשונה ומוכרת היא שגירסאות חדשות של תלויות יוצאות כל הזמן, לפעמים באמת מתקנות בעיות אבל הרבה פעמים גם שוברות תאימות API. שידרוג תלויות יכול לשבור הרבה דברים במערכת.
בעיה שניה ואפילו יותר מטרידה היא שכמות המערכות שיש להן תלויות גדלה, וקורה שאנחנו מאבדים קשר עם כל התלויות שצריך לשדרג ואיך לשדרג כל דבר.
במערכת Full Stack טיפוסית יהיו לנו את מערכת ההפעלה של השרת, גירסאות של כל הספריות המובנות במערכת ההפעלה, בסיס הנתונים, מעל זה תוכנות נלוות שהתקנו בשביל האפליקציה, כשהאפליקציה רצה בתוך Docker Container אז קבצי ה Dockerfile מתיחסים לתוכנות בגירסאות מסוימות (וגם אותן צריך לשדרג), וברמת האפליקציה ה requirements.txt, Gemfile או package.json צריכים טיפול ושדרוג.
לכן במקום להגיד "נשדרג כשיהיה זמן" או לשדרג כשמשהו נשבר, מומלץ לקבע את השידרוגים במסמך אחד שאומר הכל. טבלת אקסל תעבוד פה טוב, אבל אפשר גם קובץ טקסט פשוט. העמודות יהיו "מה", "איך לשדרג", "מתי שדרגנו פעם אחרונה", "תאריך שידרוג הבא", לדוגמה:
1. בסיס הנתונים, קישור למדריך שמסביר איך לשדרג את בסיס הנתונים, שודרג לאחרונה ב XXX, ישודרג פעם הבאה ב YYY.
2. קובץ ה Dockerfile של השרת, קישור למסמך שמסביר איך לשדרג, שודרג לאחרונה ב XXX ישודרג פעם הבאה ב YYY.
3. תלויות צד-לקוח של האפליקציה הראשית, קישור למסמך שמסביר איך לשדרג, שודרג לאחרונה ב XXX, ישודרג פעם הבאה ב YYY.
טבלת אקסל מסודרת עם לינקים שתעודכן בכל שידרוג יכולה להפוך את השידרוגים לחוויה הרבה פחות מלחיצה ובלי לשכוח אף חלק במערכת.
Уже доступно! Исследование Telegram 2025 — ключевые инсайты года 
