EventEmitter הוא מודל בילט אין בNodeJS, הוא מאפשר "האזנה" לאירועים ו"שידור" שאירוע קיים קרה לכל ה"מאזינים".
לדוגמה, נוכל להאזין לאירוע בשם "NEW_USER_REGISTER" שישודר בכל פעם שמשתמש חדש נירשם למערכת שלנו, במקביל נוכל "להאזין" לאירוע מכל מקום שנירצה. בכל פעם שהאירוע יתקיים – נוכל להריץ פונקציות בצורה אוטומטית.
const EventEmitter = require('events');
const ourEmitter = new EventEmitter();
//נאזין לאירוע NEW_USER_REGISTER
//בכל פעם שהאירוע ישודר, הפונקציה הנל תרוץ
ourEmitter.on('NEW_USER_REGISTER'), () => {
console.log('משתמש חדש נירשם למערכת');
}
//ניתן להאזין לאירוע ביותר ממקום אחד
ourEmitter.on('NEW_USER_REGISTER'), () => {
console.log('שלח אימייל ברוך הבא');
}
//על מנת לשדר אירוע נריץ את הפקודה הבאה
ourEmitter.emit('NEW_USER_REGISTER');
הEventEmitter עוזר כאשר אנו צריכים לעדכן מודלים אחרים שאירוע מסויים קרה והוא שימושי מאוד, חשוב להכיר אותו!
שימו לב, אם האירוע "ישודר" לפני שאנו נאזין לו – מה שמאזין לו לא יידע שהאירוע קרה, לכן – קודם כל צריך להאזין ורק לאחר מכן לשדר.
בקוד הנל, אנו קוראים את כל התוכן של הקובץ, לאחר שקראנו אותו אנו מדפיסים את התוכן שלו. ולאחר מכן מדפיסים 'Sync Test'. בקוד הסינכרוני הנל קודם יודפס תוכן הקובץ ורק לאחר מכן יודפס 'Sync Test'. חשוב להדגיש: הקוד הנל לא עובר דרך הEvent Loop של NodeJS.
Async Error CallBack
בדוגמה הבאה, נכתוב קוד אסינכרוני בNodeJS בגישת CallBack. הקוד הנל יעבור דרך הEvent Loop ויתבצע בצורה אסינכרונית.
const fs = require('fs');
let fileName = 'logs.txt';
fs.readFile(fileName, function cback(err,data) {
console.log('File content is', data);
});
console.log('Async Test');
בקוד הנל, אנו משתמשים במתודה readFile שהיא מתודה אסינכרונית אשר עושה שימוש בEventLoop. אנו לא נוכל לגשת לתוכן הקובץ ישירות אחרי קריאה לפונקציה הזאת – כיוון שהוא עדיין לא זמין, וזאת הסיבה שאנו צריכים להשתמש בפונקצית CallBack – במקרה שלנו cback שמקבלת 2 פרמטרים. הפרמטר הראשון err (לשגיאות) והפרמטר השני data – התוכן של הקובץ.
לאחר שקריאת הקובץ תסתיים, מתבצעת קריאה לפונקצית הCallBack שלנו ורק בה יש לנו גישה לתוכן של הקובץ (Event Loop)
קריאת הקובץ
הדפסת Async Test
הדפסת תוכן הקובץ על ידי פונקציית הCallBack כאשר למעשה הEvent Loop רץ רק פעמיים, בפעם הראשונה קריאת הקובץ + הדפסת הAsync Test ובפעם השנייה הרצת פונקצית הCallBack שלנו.
שימו לב, שכיון שמדובר בכתיבה אסינכרונית, שורת הAsync Test שלנו תופיע לפני שורות התוכן של הקובץ שלנו.
חשוב לשים לב שפונקצית הCallBack שלנו תמיד תקבל בפרמטר הראשון את משתנה הerr, אם תחול שגיאה בקוד פרמטר הerr שלנו יהיה אוביקט מסוג error ובמידה ולא תתרחש שגיאה הערך של הפרמטר err יהיה null.
החיסרון העיקרי בשימוש בכתיבה עם callbacks הוא הצורך לשרשר את הפונקציות שלנו, מה שקרוי "עץ אשוח" – דבר הגורם לקוד לא קריא וקשה לתיחזוק. אם נירצה לדוגמה גם לקרוא מהקובץ וגם לכתוב לקובץ אחר בשימוש בתוכן הקובץ הראשון נקבל קוד בסיגנון הבא:
const fs = require('fs');
let fileName = 'logs.txt';
let newFileName = 'newlog.txt';
fs.readFile(fileName, function cback(err,data) {
fs.writeFile(newFileName, data, function cback2(err) {
//yada yada
});
});
console.log('Async Test');
שימו לב, איך 2 הפונקציות הנל משורשרות, עכשיו דמיינו לכם קוד אסיכרוני כזה שעושה 5 ויותר פעולות.. פיתרון לבעיתיות הזו נמצא בתבנית אסינכרונית אחרת בשם Promise Pattern
The Promise Pattern, Async – Await
Nodejs מגיע עם כלי שמאפשר לעשות "להמיר" לpromise כל פונקציה אסינכרונית שהיא בילט אין.
בדוגמה זו אנו משתמשים בpromisify כדי להשתמש בפונקציה readFile (שהיא פונקציה אסינכרונית בילט אין) – בצורה שמחזירה Promise. אנו יכולים להמיר כל פונקציה אסינכרונית שכתובה בצורת CallBack לצורה שמחזירה Promise. הערת אגב: למודל FS יש כבר פונקציות Promise מובנות, להלן:
const readFile = util.promisify(fs.readFile);
בדוגמה הקודמת כמובן לא השתמשנו בהן ועשינו במקום זאת שימוש בutil.promisify.
ראו את הדוגמה הבאה בה אנו גם קוראים מקובץ וגם כותבים לקובץ עם Promise
const fs = require('fs').promises;
let fileName = 'logs.txt';
let newFileName = 'newlog.txt';
async function copyLog(filename, newFileName) {
await logFileData = await fs.readFile(filename);
await fs.writeFile(newFileName, logFileData);
}
copyLog('logs.txt','logs-copy.txt');
console.log('Async Test');
נכון שהקוד הרבה יותר ברור מהקוד הבא?
const fs = require('fs');
let fileName = 'logs.txt';
let newFileName = 'newlog.txt';
fs.readFile(fileName, function cback(err,data) {
fs.writeFile(newFileName, data, function cback2(err) {
//yada yada
});
});
console.log('Async Test');
לסיכום: לטעמי האישי, Promises הן הרבה יותר נוחות לתחזוקה וגם לכתיבה מאשר CallBacks.
מצאתם טעות? הערות? שאלות? הסתדרתם? נתקעתם? כתבו לי בתגובות!
WebHooks בגיטהאב מאפשרים לנו לקבל עדכון כאשר בוצע שינוי בקוד שלנו, בחיבור לJenkins נוכל להגדיר JOB שירוץ כאשר מתקבל עדכון כזה. לשמחתנו, Jenkins יכול להגדיר עבורנו אוטומטית WebHooks בגיטאהב.
השלבים:
יצירת Access Token בחשבון הגיטאהב שלנו עם הרשאות לקרוא ולכתוב לWebHooks + הוספת שרת גיטאהב לJenkins על מנת שינהל עבורנו WEBHOOKS בגיטאהב.
לאחר מכן ליחצו על "Generate New Token". בטופס שקיבלנו נמלא את הפרטים, בNOTE אני כתבתי "Jenkins Test" ובהרשאות יש לסמן "admin:repo_hook"
ואז נילחץ על הכפתור הירוק "Generate Token".
נעתיק את הAPI KEY שקיבלנו – אנו ניצטרך אותו כאשר נגדיר את הJOB בJenkins. בתפריט הJenkins שלנו נלחץ על "Manage Jenkins"
ואז נלחץ על "Configure System"
נגלול למטה עד שנגיע ל"GitHub Servers" ונמלא את הפרטים.
נלחץ על "Add" שליד "Credentials" ובתפריט דרופדאון של הKIND נבחר SECRET KEY. בSECRET נדביק את הAPI KEY שקיבלנו. בID אני הזנתי github_key ובתיאור נזין מה שנוח לנו. במקרה שלי הזנתי GitHub Key
לאחר שסיימנו נלחץ על Add. ואז נסמן V ליד "Manage hooks". כמו כן בדרופ דאון של ה"Credentials" נסמן את הקי החדש שהוספנו שקראתי לו "GitHub Key"
נלחץ על הכפתור "Test Connection" כדי לוודא שהכל פועל כשורה, במיקרה שלי קיבלתי בחזרה: Credentials verified for user avivronen, rate limit: 4998
נלחץ על SAVE.
סיימנו את השלב הראשון, עכשיו שרת הJENKINS שלנו יכול לתקשר ישירות עם GITHUB וליצור ולנהל עבורנו WEBHOOKS.
נחזור לריפו שלנו בגיטאהב, נלחץ על SETTINGS, ואז נלחץ על Web hooks. ושימו לב, שכבר עכשיו תוכלו לראות את הWEBHOOK שנוצר אוטומטית על ידי הJENKIN שלנו. ברגע שתעשו COMMIT לריפו שלכם, הBUILD בגנקינס ירוץ אוטומטית. בהצלחה!
קוברנטיס מאפשר לנו ליצור Virtual Network שפועל בכל הקלסטר שלנו, כל Pod בקלסטר מקבל כתובת IP ייחודית ויכול לתקשר עם כל pod אחר בקלסטר – גם במיקרה שהPod רץ בNode אחר.
לאחר מכן נריץ את הפקודה הבאה כדי לקבל רשימה של הPODS שלנו כולל כתובת הIP שלהם:
kubectl get pods -o wide
בשלב האחרון ננסה להריץ CURL מתוך הbusybox שלנו לכתובת הIP של הפודים האחרים שלנו, כאשר את ה00.00.00.00 אנו נחליף בכתובת הIP של הפוד אליו אתה רוצים לבצע את הCURL. הפקודה kubectl exec busybox בעצם מריצה את הcurl 00.00.00.00 בתוך POD הbusybox שלנו.
kubectl exec busybox -- curl 00.00.00.00
במידה והצלחנו לבצע CURL וקיבלנו את עמוד הדיפולט של Nginx זה בעצם מוכיח, שPODS יכולים לתקשר בניהם, גם אם הם ממוקמים בNodes אחר ושההתקנה של Flannel בוצעה בהצלחה.
לאפשר לקונטיינרים לתקשר בניהם כאשר הם נמצאים בNODES שונים
למנוע כפילות של כתובת IP זהה בין דוקרים שממוקמים בNODES שונים
כשמחליפים קונטיינר – הוא בדרכ יקבל כתובת IP חדשה שזה מצב שאנו לא תמיד מעוניינים בו.
היחידה הקטנה ביותר בקוברנטיס נקראת POD, הPOD יכול להכיל קונטיינר אחד או קבוצה של קונטיינרים. כל הקויינטנרים שנמצאים באותו POD יקבלו את אותה כתובת IP, וכל POD שממוקם בקלסטר יקבל כתובת IP ייחודית באותו IP SPACE.
כל הקונטיינרים יכולים לתקשר עם קונטיינרים אחרים ללא NAT.
כתובת הIP שהקונטיינר מזהה ככתובת IP ששייכת לו היא אותה כתובת IP שהקונטיינרים האחרים רואים כשייכת להם.
הNetwork Overlay אחראי על חלוקת כתובת הIP. הEtcd הוא Key-Value דאטאבייס לאובייקטים של קוברנטיס הKubelet הוא הסרביס של קוברנטיס שרץ על כל הנודים בקלסטר – גם במאסטר וגם בWORKERS.
קוברנטיס כולל רכיבים שונים שעובדים ביחד ומאפשרים את תפקודו של הקוברנטיס קלסטר. כדי לראות את כל הPODS שאחראים על תפקודו של הקוברנטיס שלנו נריץ את הפקודה הבאה:
kubectl get pods -n kube-system
כל POD שמופיע לנו הוא בעצם POD שמריץ בתוכו רכיב שקשור לקוברנטיס עצמו. הרכיבים העיקריים הם:
kube-apiserver זהו הAPI הראשי של קוברנטיס. הוא מבוסס REST. למשל הפקודה שהרצנו מקודם – kubectl get pods מבצעת למעשה פנייה לAPI.
etcd מנהל את נושא המידע – סטורג של הקלסטר. לא מדובר בסטורג' שמאחסן בתוכו את האתר את הקבצים שלנו למשל, מדובר בסטורג שמחזיק את הדאטא שקשור לכמה PODS רצים כרגע, NODES, איזה כתובת IP יש לכל אחד וכו – כל המידע שדרוש לקוברנטיס בשביל לנהל ולהחזיק את הקלסטר שלנו. במידה ויש לנו יותר מMASTER אחד, etcd ידאג שהדאטא הזה יהיה מסונכרן בין כל הMasters שלנו.
kube-controller-manager מאחד בתוכו רכיבים שונים לחבילה אחת. הוא מחזיק בתוכו את כל השירותים והספריות הדרושות לקוברנטיס. אם אנו מתייחסים ל jube-apiserver כפרונט של קוברנטיס, אזי ה kube-controller-manager הוא למעשה הבקאנד של קוברנטיס.
kube-scheduler אחראי על יצירת הPODS, מתי להריץ אותם, מתי לכבות אותם, באיזה Node עצמאי להריץ אותם וכו.
kube-proxy כל NODE זקוק לkube-proxy שלנו, הוא אחראי על התקשורת בין הNODES השונים על ידי הוספת כללי Firewall. כאשר POD בNODE X צריך לדבר עם POD שממוקם בNODE Y חייב להיות ROUTE וRULE שמאפשר את זה.
kubelet הAGENT שמריץ את הקונטיינרים שלנו בכל NODE. הוא בעצם המתווך בין הAPI של קוברנטיס לדוקר. (Docker במקרה שלנו, אגב קוברנטיס תומך לא רק בדוקר). הוא רץ כSERVICE ולא כPOD (כי הוא אחראי למעשה על הרצת הקונטיינר) ולכן הוא לא מופיע לנו ברשימת הPODS. כדי לראות אותו נריץ את הפקודה:
באחד המדריכים, יצרנו קוברנטיס קלסטר עם 3 Nodes, כל Node הוא בעצם שרת/מכונה שמריץ קונטיינרים (Pod).
לכל קוברנטיס קלסטר חייב להיות לפחות Node אחד שהוא יהיה הNode המנהל – הוא מריץ בתוכו את הKubernetes API ואת שאר כלי הניהול של קוברנטיס, שאר הNodes מכונים Workers.
Pods הוא היחידה הבסיסית ביותר של קוברנטיס. כל Pod מורכב מקונטיינר אחד או יותר (בדרכ יש קונטיינר אחד בכל POD אבל ניתן שיהיה יותר מקונטיינר אחד), כתובת IP בתוך הקוברנטיס קלסטר ואיחסון (Storage). כל Pod אחד או יותר יוצרים ביחד Node. קלסטר מורכב מNode אחד או יותר.
כשאנחנו מפעילים Pod, קוברנטיס בעצם מתזמן – Schedules Pod שיריץ אותו. הPod יריץ את כל הקונטיינטרים שם חלק מאותו Pod.
כדי ליצור POD, אנו משתמשים בקובץ YAML שמכיל את הפרטים של אותו POD. לדוגמא, אם נירצה ליצור POD שמריץ קונטיינר Nginx ניצור קובץ בשם ourtamlefilename.yaml לדוגמא עם הפרטים הבאים:
מכונה = Node לשם ההדגמה אנו ניצור kubernetes קלסטר שמורכב מ3 מכונות , 2 מכונות שיהיו הNODES ומכונה אחת שתהיה הNode Master שלנו. לשרת המסטר שלנו נקרא k8master , לשרת NODE הראשון נקרא k8node1 ולשני k8node2 ההפצה שבחרתי לעבוד איתה בהדגמה היא Ubuntu 18 LTS
בMASTER שלנו אנו נתקין: * Docker * Kubeadm * Kubelet * Kubectl * Control Plane
אחרי כמה דקות, נקבל פלט שמכיל את הפקודה אותה אני נצטרך להריץ אחכ בכל Node שלנו על מנת שהוא יוכל להצטרף לקלסטר. חשוב לשמור את הפקודה ואת הטוקן – כי בלעדיו לא תוכלו לצרף Nodes לCluster שלכם. במקרה שלי הפלט שהתקבל הוא
אחרי שהרצנו את הפקודה ב2 השרתים (ייתכן ותתבקשו להוסיף sudo) נריץ בשרת הMaster שלנו (k8master) שוב את הפקודה הבאה:
user@servername:~$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
servername1.serverdomain.com NotReady master 10m v1.12.7
servername2.serverdomain.com NotReady <none> 40s v1.12.7
servername3.serverdomain.com NotReady <none> 85s v1.12.7
ושימו לב שהפעם המסטר שלנו מזהה את 2 השרתים האחרים, כולם עדיין בסטטוס של NotReady.
kubeadm הוא כלי שמאפשר אוטומציה של חלק גדול של יצירת קלסטר, בתכלס – הופך את העבודה שלנו ביצירת קלסטר להרבה יותר קלה.
מה זה kubelet?
kubelet הוא שירות (Agent) שרץ בכל קוניינר שקיים בכל Node שלנו, כל שרת שיריץ קונטיינר זקוק לשירות kubelet.
מה זה kubectl?
kubectl הוא כלי command line שמאפשר לנו לתקשר עם הקלסטר (Cluster) שלנו ולנהל אותו.
על מנת להתקין את השירותים הנל, נריץ את הפקודות הבאות: (אני בחרתי להתקין את גירסת 1.12.7-00 , בהתאם לצורך שלכם – ייתכן ואתם תירצו להתקין גירסה אחרת. שימו לב, במידה ותיבחרו להתקין גירסה מתקדמת יותר, עקבו אחרי ההוראות הנוספות בלינק הזה, הדבר העיקרי הוא שגירסאות מתקדמות יותר לא יעבדו טוב במידה ומוגדר לכם שטח SWAP במערכת ההפעלה, לכן יש קודם לבטל את הSWAP.)
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat << EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update
sudo apt-get install -y kubelet=1.12.7-00 kubeadm=1.12.7-00 kubectl=1.12.7-00
sudo apt-mark hold kubelet kubeadm kubectl
ולאחר מכן נוודא שההתקנה הצליחה על ידי הפקודה הבאה: