קוברנטיס (Kubernetes) עומד להיות הענן-בתוך-הענן של רובנו

אני לא כותב כ״כ בבלוג על נושאים “חדשותיים”, זו לא ההתמחות של הבלוג – אבל בפוסט הבא אני הולך להיות טיפה ספקולטיבי, על מגמה שנראית שהולכת להיות השלטת בשנים הקרובות, גם אם כרגע היא רק החלה.

מדוע אני כותב את הפוסט דווקא עכשיו? שום דבר לא קרה בשבוע האחרון. רק יצא יום כיפור (אין קשר), אמנם הכריזו על ליין חדש של אייפונס, ויש גם את ארכיטקטורת Turing החדשה של nvidia – אבל לא באמת היה השבוע משהו גדול ורועש בגזרת ה Containers ו/או Container Orchestration Framework (בקיצור עבור הפוסט: COF).
בכל זאת, לפני כשנה כתבתי פוסט שעסק ב COFs והשווה קצת בין מזוס, ECS, קוברנטיס, Swarm ו Nomad – ובאותה עת נראה היה שהתחרות בין כמה מה COFs שהוזכרו עדיין פתוחה.
כשאני מסתכל על ההתרחשויות של השנה האחרונה – נראה שהעניין נסגר. אם “קולות החיילים” לא יבצעו מהפך של הרגע האחרון – Kubernetes הוכרזה בחודשים האחרונים כ COF שתכתיב את הטון בעתיד הנראה לעין.
תזכורת קצרה:
  • קוברנטיס (לעתים נכתב בקיצור K8s) היא פריימוורק ששוחרר כפרויקט קוד פתוח ע״י גוגל באמצע שנת 2014 לניהול Orchestrations של Containers. בסה”כ בת ארבע שנים.
    • לגוגל היה ניסיון קודם בפרויקטים פנימיים דומים: הראשון בשם Borg והמחליף שלו – Omegaֿ, להרצת קונטיינרים. הם לא הריצו Docker או Rkt – אלא קונטיינרים פרי פיתוח מוקדם של גוגל. הניסיון הזה הוכיח את עצמו.
  • קוברנטיס (בדומה ל COFs אחרים) מספקת יכולות Load Balancing, Discovery ו Auto-Scaling ובעצם מהווה סוג של ״מיני-ענן״ בו יחידת ה compute היא Container. האיום הזה לא נחבא מעיניהם של ספקי-הענן הגדולים, והם ניסו לפתח COFs מקבילים שישמרו את ה Lock-In לענן שלהם.
  • אמזון יצאה עם ECS, ומייקרוסופט עם ACS (שתי החברות שאולי היה להן הכי הרבה להפסיד) – פתרון הרצת ו Orchestrations של Containers שתפור לענן שלהן. והן הטילו את כובד משקלן בכדי לשכנע שזו אלטרנטיבה ראויה (וטובה יותר) לקוברנטיס – אלטרנטיבה שרק צריכה עוד זמן להבשיל.
  • השוק לא הגיב יפה להצעות הללו – ופנה ל manual installation של קוברנטיס על גבי EC2.
  • מייקורוסופט הוציאה את AKS (קרי Azure Kubernetes Service) ואמזון את EKS (קרי Elastic Kubernetes Service) כ Kubernetes מנוהל. בהתחלה נראה היה ש EKS ו AKS הולכים להיות offering משני על מנת לשמר לקוחות ולא להישאר יותר מדי מאחור, אבל מהר מאוד התברר שהשירותים הללו תופסים את תשומת-לב הלקוחות, ומשם השתנו הדברים והם התחילו לקבל גם את מירב ההשקעה מצד ספקי הענן. ECS/ACS הופכים להיות פתרונות נישה, בעיקר עבור לקוחות שכבר נרתמו לחזון שהוצג ב 2015, והשקיעו בו, או אולי יצליחו לשלב אותם גם בעולם של קוברנטיס (עבור ה worker nodes – על זה בהמשך).
  • גם פתרונות ענן כגון Pivotal Container Service ו Cloud Foundary – הגיעו למסקנה דומה, ופנו לכיוון קוברנטיס.
  • אפילו החברה מאחורי Docker שניסתה להציג חזון משלה בדמות Swarm (לאחר שהבינה שב COFs יש פוטנציאל עסקי גדול יותר מתמיכה ב Docker בלבד) הכריזה לפני כשנה על תמיכה ״גם״ בקוברנטיס – מה שבפועל ״הוציא את העוקץ״ מ Swarm שמותג כ”פתרון הרשמי של דוקר”.
  • Mesosphere DC/OS – הגרסה המסחרית של Mesos, מציעה גם היא תמיכה בקוברנטיס.
בקיצור: מלחמת ה COFs הסתיימה. Long live Kubernetes!
חשוב לציין שלמרות שקוברנטיס הוא ה COF שהפך לברירת-המחדל, יש עדיין תהליכים שלא ברור כיצד יסתיימו:
  • מייקרוסופט שחררה את ACI (קרי Azure Container Instances) ואמזון את AWS Fargate כשירותים בהם ניתן להריץ container on-demand ולשלם ע״פ השימוש הנקודתי. השירותים מזכירים מאוד את AWS Lambda או Azure functions כאשר הרזולוציה היא Container ולא פונקציה, על אף שמנסים לשווק אותם כ”תחליף ל EC2″.
    • השימוש העיקרי הוא בהפעלה של containers המבצעים batch של עבודה – ולא בהרכבה של containers מסוגים שונים המתקשרים זה עם זה. האלמנט של Orchestration לא ממש קיים בשירותים הללו.
    • גם קוברנטיס תומכת בהפעלת containers באופן on-demand, מה שעשוי לרמז שהלקוחות העיקריים של ACI ו Fargate הם לקוחות שלא מריצים קובנרטיס או כאלו שיש להם Workload מאסיבי שאינם רוצים להפעיל על ה Kubernetes cluster הרגיל שלהם (למשל: משימות AI כבדות).
  • קוברנטיס עצמה נפתחה לספקי הענן, למשל: החל מגרסה 1.9 קוברנטיס תומכת באופן טבעי ב AWS NLB (קרי Network Load Balancer). מיותר לציין שיש הרבה Kubernetes-plugins שמסייעים לבצע את החיבורים לעננים ספציפיים בצורה טבעית יותר.
  • ישנה השקעה בהרצה של Kubernetes בכדי להפעיל VMs ולא רק Containers. המוטיבציה היא לשלב גם שרתים שאינם לינוקס (למשל Windows או אפליקציות Unikernel) ו/או בידוד גבוה יותר – בעיקר משיקולי אבטחה.
  • קוברנטיס מאפשרת יכולות ניהול משאבים טובות ברמת התשתית, אבל ניהול של מאות מיקרו-שירותים הוא עדיין דבר קשה מאוד בקוברנטיס. יש פרויקטים (למשל: lstio הנתמך ע״י Lyft, IBM וגוגל) המנסים לספק שכבת ניהול ברמה יותר ״אפליקטיבית״ שתקל על ניהול שכזה. פרויקטים כאלו יכולים להשתלב עם קוברנטיס כ plug-in ו/או להחליף כמה מיכולות הליבה שלה (למשל: Service Discovery או Load Balancing).

Managed Kubernetes

כמה שאנו אוהבים לשמוע כמה אוטומטית ו״חכמה״ היא קוברנטיס בניהול ה Containers Cluster, הפעלה של קוברנטיס היא עדיין משימה מורכבת:

  • יש שורה של בחירות שיש לבצע בהתקנה של קוברנטיס. האם אתם מעדיפים א או ב, ג או ד? דוגמה טובה היא תקשורת בין Containers: האם Kubenet (ברירת-המחדל) מספיק לכם? אולי אתם צריכים scale, ביצועים, ו/או יכולות שליטה מתקדמות יותר ברמת הרשת וכדאי להשתמש ב Weave? אולי בעצם ב Calico? ואם אתם בוחרים ב Calico – אתם מעדיפים אותו עם Flannel (כלומר: Canal) או בלעדיו?
  • קוברנטיס מטפל בשורה של מקרי-קצה מורכבים להבנה, שללא ניסיון ניכר – קשה להבין אותם ולוודא שהם עובדים בצורה תקינה.
    • High Availability – איך להבטיח ש pods ימשיכו לתפקד ככל האפשר. לדוגמה: האם ה masters הם באמת highly available? האם אתם יכולים ליצור master חדש עם קונפיגורציה שלמה בצורה אוטומטית?
    • אבטחה – האם אתם מודעים כיצד להקשיח התקנה של קוברנטיס, ויש לכם את המנגנון להתקין עדכונים בכל השכבות?
  • חיבור של קוברנטיס לספק הענן הספציפי היא עוד משוכה שיש לעבור. למשל: על EC2 לא תוכלו ליצור cluster גדול מ 50 nodes ללא החלפה של ה CNI Network plugin. כנראה שתרצו כמה מה nodes שירוצו על spots. כדאי מאוד להתחבר ל IAM בצורה נכונה, לקנפג את Route53 כך שה master ימצא את כל הרכיבים שלו ואם יש כמה clusters – הם יהיו זקוקים ל subdomains, וכו׳.
הפתרון הטבעי, בדומה מאוד לבחירה בספק ענן (ולא הפעלה של Data Center של החברה) – הוא שימוש ב Managed Kubernetes. זו הבחירה הטבעית עבור הרוב הגדול של משתמשי Kubernetes, במיוחד אלו שלא הולכים להריץ אלפי שרתים ב Cluster של קוברנטיס (ולכן סביר יותר שיהיה לכם את האינטרנס והמשאבים לעבוד עם קונפיגורציה מאוד ספציפית (“לא מקובלת”) לצרכים שלכם).

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

  • גוגל – בתור היוצרים של Kubernetes והמאמצים הראשונים שלו כשירות Managed בענן, אין הפתעה בכך ש GKE (להלן Google Kubernetes Engine) הוא המוצר הבשל ביותר.
    • האינטגרציה הטובה ביותר, UI מלוטש לניהול, והפעלה מהירה של Cluster מאשר של המתחרות.
    • הבעיה היחידה: רוב החברות (בכלל, אך בישראל בפרט) לא פועלות על הענן של גוגל, והם לא יעבירו אותן לשם בשביל “Managed Kubernetes טוב יותר”. זה עשוי להשתנות בעתיד – אבל כרגע לא נראה שזה המצב.
    • בניגוד ל BigQuery שהוא שירות שלקוח של AWS עשוי להשתמש בו על אף שהוא שוכן בענן אחר – קוברנטיס מריצה את לב המערכת שלנו. להשתמש בקוברנטיס בענן אחר, משמע להתחיל לעבור לענן אחר.
  • אמזון מציעה את EKS, שהיה עד לא מזמן plan B – שהתממש.
    • EKS תנהל עבורכם את ה Control plane מבחינת scalability ו high-availability על גבי AZs שונים. לא תהיה לכם גישה ברמת Admin אליו (בדומה ל RDS), אבל אתם ממשיכים לעבוד עם kubectl כרגיל.
    • לEKS יש אינטגרציה עם שירותי אמזון בראשם IAM, אבל גם ELB, VPC ו CloudTrail.
    • את ה Worker nodes עדיין עליכם לנהל לבד – ויש עוד קונפיגורציה ועבודה שעליכם לעשות על גבי EKS. יש תוכניות לבצע אינטגרציה בין EKS ל ECS כך ש ECS ינהל את ה worker nodes. מכיוון שהוא לא נבנה מלכתחילה לצורת העבודה הזו – צריך לראות כמה טוב זה יעבוד. רעיון דומה מתוכנן לאינטגרציה בין EKS ל Fargate להרצה של Containers שהם task-oriented.
    • לאחרונה שוחררה גרסאת eks.2 של EKS – ויהיו עוד רבות. כרגע ל EKS יש סיכוי טוב להיות פתרון ה Managed Kubernetes הפופולארי ביותר (גם אם לא בהכרח יהיה המתקדם ביותר).
  • מייקורוספט מציעה על גבי Azure את שירות AKS המנוהל, שדומה ביכולות ל EKS.
    • אולי בגלל קהילה קטנה יותר ופעילה פחות, AKS נראה כשירות קצת פחות פופולארי. אפשר לנחש שהלקוחות הקלאסיים של מייקרוסופט הם ברובם לא early adapters של טכנולוגיות חדשות.
  • גם OpenShift ויבמ BlueMix – מציעות פתרונות Kubernetes שנחשבים מפותחים, אך הם מתאימים בפועל בעיקר למי שכבר פעיל על תשתיות הענן הללו.
  • Stackpoint מציעה פתרון של managed control plane על ידה, כאשר את ה worker nodes תתקינו על ענן לבחירתכם. הפתרון אטרקטיבי בעיקר למי שמריץ את ה workload בענן שבו אין offering סביר של managed kubernetes.
השוואה שנעשתה בין שלושת פתרונות ה Managed Kubernetes הנפוצים. מקור

גם מי שמתקין קוברנטיס לבד (על הענן או On-Premises) לרוב לא עושה זה בדרך הארוכה והקשה.
kubernetes-the-hard-way הוא שם של מדריך פופולארי ונחשב להתקנת קוברנטיס מ scratch, שבעיקר משמש בכדי ללמוד את הפרטים השונים שקיימים בהתקנת קוברנטיס, ואת השיקולים שנלקחים בכזו התקנה.

בפועל, מי שמתקין קוברנטיס, יעשה זאת כמעט תמיד עם “installer” המיועד לכך:

  • Kops הוא המקובל ביותר. הוא חלק מפרויקט קוברנטיס והיה במשך תקופה כלי להתקנת קוברנטיס על גבי AWS בלבד. הוא יודע לייצר קבצי Terraform, יודע לבנות תצורות high-availability (כמובן), ותומך ב-7 אפשרויות שונות ל CNI (כלומר: plugin לתקשורת פנימית. אחת הבחירות עם הווריאציות המגוונות יותר בהתקנת קוברנטיס).
  • Kubespray (לשעבר Kargo), תת-פרויקט של קוברנטיס הוא כלי להתקנת קוברנטיס בהתבסס על Ansible, כלי ה configuration management שנחשב לפופולארי ולמתקדם – עד להופעת ה COFs שהולכים ומייתרים אותו. Kubespray נחשב יותר גמיש מ Kops ומציע מגוון רחב יותר של אפשרויות התקנה. יש לו גם תמיכה ספציפית ב AWS, Azure, Google Cloud, Digital Ocean ו Open Stack.
  • TK8 הוא עוד כלי פופולרי, שהדגש שלו הוא עבודה צמודה עם Terraform והוא זוכה לכמה נקודות פופולריות בזכות זה שהוא כתוב ב Go (אזהרת באזז). TK8 כולל גם אפשרות התקנה של כמה אפליקציות פופולריות בהמשך ל Kubernetes Cluster כמו Zipkin+Jagger, Prometheus ועוד.
  • עוד שמות שאפשר לציין הם RKE (להתקנה של ה Cluster בלבד, לאחר שהכנתם את המכונות באופן מסוים), מודול שקיים ומתוחזק ב Puppet להתקנה של Kubernetes, או Kubeinformation שהוא כלי online שעוזר לייצר templates של קונפיגורציות ע”פ סט בחירות שתתנו לו. Kubeinformation עשוי להיות מקור מעניין להתרשם בזריזות מתהליך ההתקנה, אבל בעת כתיבת פוסט זה הוא עדיין לא תומך ב EKS (זו הפריט הבא ב roadmap שלו).

מכיוון שהתקנת קוברנטיס הפכה לעניין שדרש הרבה השלמות, פרויקט Kubernetes החל לחשוף את ה Kubernetes Cluster API שאמור להיות בעתיד הדרך הקלה והנפוצה ליצור ולקנפג Kubernetes Cluster. התוכנית היא שה Installers השונים יעברו דרכו, ותוך כדי כך יהפכו לפשוטים, אמינים, ו”סטנדרטיים” יותר (מנקודת המבט של קוברנטיס).

כלים להתקנת קוברנטיס יכלו בקלות להפוך לסוג של Distros כמו Linux Distros אך מכיוון שנראה היום שרוב העולם ילך לכיוון של managed Kubernetes – להם תישאר נישה קטנה יחסית, של משתמשים מתקדמים שרוצים לשלוט בהתקנת ה Kubernetes שלהם בעצם.

סיכום

העולם הטכנולוגי מתקדם בחזית רחבה לכיוון קוברנטיס (גם בצורה עיוורת לפעמים – סממן חזק להפיכתו ל”באזז”). קוברנטיס היא תשתית מרשימה, אבל גם לא-פשוטה לניהול. התסריט הסביר ביותר הוא שפתרונות של Managed Kubernetes ישמשו את רוב השחקנים בשוק, בעיקר הקטנים והבינוניים (תמיד יכולה להיות שחקנית שתדמה ל Netflix ע”ג AWS, כלומר: שתחליט להריץ גם מאות-אלפי worker nodes על גבי פתרון Managed).

Enterprises שרצים On-Premises, או מריצים ענן משלהם, או סתם חברות שמריצות Workload גדול במיוחד – כנראה ימשיכו לנהל את ה Kubernetes Cluster מא’ עד ת’ – אבל זו התמחות שהן יכולות להרשות לעצמן לפתח ולתחזק.

בעת כתיבת הפוסט, קוברנטיס הוא עדיין “דבר ממש מגניב”, אבל עוד כמה שנים הוא עשוי להפוך לדבר מובן מאליו עבור רוב החברות, כמו “ריצה בענן”. אתם מדמיינים את עצמכם מתרשמים ממקום עבודה רק מכיוון שהוא עובד בענן?

EKS נראה כרגע כאופציה בעלת נקודת הפתיחה הטובה ביותר להיות הפופולרית ביותר (פופולריות שנגזרת באופן ישיר מ AWS ומגוון השירותים הטובים שהיא מציעה) – אבל הכל עוד יכול לקרות. למשל: אם הקוברנטיס יהפוך לחשוב יותר משאר שירותי הענן – אזי ל GKE (קרי Google Kubernetes Engine) יש את היתרון הברור.

בגדול, עובדות בפניכם כרגע האפשרויות הבאות:

  • להתקין קוברנטיס לבד – על בסיס Installers כמובן, ולא בדרך הקשה והארוכה. יש לפניכם השקעה משמעותית בלמידה והתמחות של Infrastructure חדש ונוסף.
  • להשתמש בפתרון Managed Kubernetes, תוך שאתם משלמים “מחיר התבגרות” בשנה-שנתיים הבאות עד שהפתרון שבחרתם יתבגר.
  • לדחות בשנה-שנתיים את אימוץ קוברנטיס עד שהוא יהיה קל מאוד לצריכה בצורה Managed.
    • אם אתם לא יודעים לומר מה Kubernetes יתרום לכם ברגע זה – זו הבחירה הנבונה (אם כי לא הכי-מגניבה)
  • להחליט שאתם בוחרים לא להשתמש בקוברנטיס כעיקרון, מה שיכול לזרוק אתכם לאחת משתי נקודות קיצון חברתיות בעולם התוכנה: “סופר מיושנים” או “סופר מגניבים וחתרניים”.
  • להחליט שאתם מתחילים עם קוברנטיס היום, לדבר על זה הרבה, להשקיע ב POCs ולמידה – אבל בעצם להתברבר עם זה עוד שנה-שנתיים עד שנקודה שבה אימוץ Kubernetes יהיה דבר קל – מה שבעצם שם אתכם עם ה Majority של התעשייה.

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

לינקים רלוונטיים: 

השוואה בין GKE, EKS וAKS (מקור: Kubdex)

לקבל מושג ירוק על nginx

nginx הוא רכיב בסיסי ונפוץ בחלק נכבד ממערכות הווב כיום. לאלו שאין nginx, בד”כ יש Apache Httpd – כלי מקביל שנחשב קצת יותר מיושן.את nginx מתקינים ב-3 תצורות עיקריות:

  • שרת Web המגיש תוכן HTML/JS/CSS למשתמשים. שימוש נפוץ – אבל נראה שזה לא השימוש הנפוץ של קוראי הבלוג הזה.
  • Reverse Proxy – מותקן מאחורי ה Load Balancer (למשל: ELB) ולפני המערכת שלנו / המיקרו-שירות. זה כנראה השימוש הנפוץ בקרב קוראי הבלוג.
    • וריאציה של התצורה האחרונה היא API Gateway – מונח שנטען בבאזז מעולם המיקרו-שירותים. בוריאציה הזו ה nginx גם מבצע את ה Authentication.
  • Load Balancer – בתצורה הזו nginx משמש לרוב גם כ Reverse Proxy וגם כ Load Balancer.

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

בפלטפורמות שאינן בנויות ל concurrency (למשל: PHP, Ruby, או פייטון) nginx הוא רכיב קריטי לטפל ב traffic גבוה. ה nginx יכול “לספוג” מאות, אלפי, ועשרות אלפי concurrent connections ש backends מהסוגים הללו לא מתמודדים איתם יפה, ולהקל על ה backend במקרים בעייתיים כמו בעיית ה Slow Client.

ב Backends הבנויים למקביליות (כמו Java או Go) – הצורך ב nginx הוא פחות מובן-מאליו, ויש מקרים שבהם הוא לא באמת נדרש, אך אנו ממשיכים להתקין אותו כי “זה Best Practice” או מתוך הרגל.

בכל מקרה, נראה שכל הנושא של nginx נמצא בידע חסר. מי שניגש אליו הוא אנשי ה Operations ו/או מפתחים ובעיקר כאשר יש “בעיות”. למשל: nginx החליט (בחוצפתו) לחתוך URLs ארוכים במיוחד של בקשות GET.

המפגש עם nginx עשוי לפעמים להתאפיין בסריקה של StackOverflow והדבקה של כל מיני Settings לקונפיגורציה עד אשר נראה שהבעיה חדלה מלהציק.

בפוסט הזה אני רוצה לספק הצצה מהירה, hands-on ברובה, ל nginx כך שהמפגשים הבאים שלנו איתו יהיו מעמיקים, יעילים, ונעימים יותר.

ירוק כבר יש לנו. עכשיו חסר רק מושג.

להכיר את nginx

כיצד כדאי לגשת ולהכיר את nginx (מבוטא כ “engine X”)?
אולי מדריך התקנה והרצה? אולי התעמקות במבנה הארכיטקטורה? אולי השוואה ל Apache httpd (שגם אותו – רובנו לא ממש מכירים)?

נראה לי שהדרך היעילה ביותר היא פשוט לגשת לקונפיגורציה. הקונפיגורציה היא הממשק העיקרי של מפתחים עם nginx – והיא משקפת בצורה טובה את אופן העבודה של nginx.

לקחתי כדוגמה את הגדרות ברירת-המחדל (או לפחות אלו שמגיעות עם ה Docker Image הרשמי). נראה לי שזו התחלה טובה. בחלק הבא אנו נעבור פריט פריט על הקונפיגורציה. מתוך הפריטים – נלמד כיצד nginx עובד ומקונפג. כלומר: זהו טקסט לקריאה – ולא טקסט לרפרנס.

קובץ ההגדרות הראשי של nginx נקרא nginx.conf ולרוב נמצא בתיקיה etc/nginx/.

אתם בוודאי שואלים את עצמכם “איזה פורמט זה?”. ובכן, זהו לא פורמט “סטנדרטי” – זהו פורמט ספציפי שבו משתמשים ב nginx – המושפע מ C-Syntax. בואו נתחיל:

  1. השורה הראשונה היא דוגמה טיפוסית למה שנקרא בקונפיגורציה directive (= פקודה/הוראה). לאחר ה directive יש רווח ואז מספר משתנה של פרמטרים. את ה directive מסיים הסימן ; – שהוא חשוב.
    1. הדיירקטיב של user מגדיר באיזה משתמש (מערכת) התהליכים (OS processes) של nginx ירוצו. לא משהו שנשנה, בד”כ.
  2. כמה Worker Processes להפעיל.
    בניגוד ל Apache Httpd המייצר worker process לכל connection, ב nginx ה worker processes עובדים בסוג של Event Loop ומטפלים כ”א במאות ואלפי connections. הרי, nginx נכתב בכדי להתמודד עם האתגר של C10K – טיפול של יותר מ 10,000 connections בו זמנית מתוך שרת אחד.

    1. כלל האצבע המומלץ הוא להגדיר מספר workers כמספר ה CPU cores הזמינים לנו. את זה ניתן להשיג בעזרת הערך auto. אני לא יודע לומר למה קבעו אותו פה ל 1.
    2. אם הפעולות חסומות ב I/O (למשל: nginx משמש בעיקר ב proxy) – אזי כלל האצבע אומר לקבוע את הערך ל 1.5-2 ממספר ה cores הזמינים למכונה.
  3.  מה הרמה המינימלית של Log level, שאותה נרצה לשמור לתוך לוג ייעודי לבעיות? אפשר להגדיר כמה קבצים כאלו, ברמות (levels) שונות.
    שימו לב ששמות ה log levels ב nginx הוא קצת לא-סטנדרטי.
    בכדי לקבל logs ברמת debug יש להשתמש ב executable של nginx שקומפל עם פרמטר של with-debug–. זהו שיקול של אופטימיזציית ביצועים (לקמפל בלי משמע לדלג על הרבה בדיקות של רמת ה log level).
  4. שם קובץ שבו יישמר מספר התהליך (ברמת מערכת ההפעלה) שאותו קיבל ה master process של nginx.
  5. כאן אנו נתקלים בכמה דברים חדשים:
    1. יש לנו תחביר מעט שונה: תחביר של context בקונפיגורציה.
      1. context מגדיר scope, וכל ה directives שהוגדו בתוכו זמינים רק לו, או ל contexts שנמצאים בתוכו.
      2. יש בקונפיגורציה קונספט של הורשה, כך ש context מקבל את כל ה directives של ה context מעליו – אך לא ליהפך. directive שהוגדר בתוך context ידרוס את ההגדרות של אותו directive שהוגדרו ב context חיצוני יותר (יש גם יוצאי-דופן). החלק הזה שימושי בעיקר ב contexts של server ו location – שלא הגענו אליהם עדיין. אבל שם מוגדרת רוב הקונפיגורציה הספציפית למערכת שלנו.
      3. הרמה הגבוהה ביותר נקראת ה main context, והיא לא מפורשת – אך מתנהגת כ context לכל דבר. סעיפים 1-4 בעצם הוגדרו בתוך ה main context.
    2. ספציפית ה event context מנהל את מה שקשור לניהול connections. היה כנראה יותר נכון לקרוא לו connections.
      1. אנו מגדירים כאן שכל worker process יוכל לפתוח עד 1024 connections. בקשת ה connection ה connection ה 1025 יצטרך להמתין עד ש connection קיים ייסגר – על מנת שיטופל.
      2. אם nginx משמש להגיש קבצים סטטיים – אזי נוכל להגיש קבצים ל 1024 connections. צריך לזכור שדפדפנים עדיין פותחים 2-3 simultaneous connections ל host אם הם צריכים כמה קבצים. אם nginx משמש כ proxy אז המספר הרלוונטי הוא חצי – כי חצי מה connections יפתחו ל application server שמאחורי ה nginx – מה שנקרא במינוח של nginx ה upstream.
      3. הארכיטקטורה של nginx מאפשרת ל nginx לצרוך רק כמה MB של זכרון לכל 1000 connections פתוחים. התוכן שמועבר ב connections (ה buffers אם יש הרבה מידע, ויש פער בקצב ההעברה של ה client וה applications server) הוא גורם שעלול להגדיל את צריכת הזיכרון בצורה מורגשת.
      4. אם צריכת הזיכרון קטנה כ”כ, למה לא להגדיר ערך של 10,000 ב directive הזה?
        אליה וקוץ בה: ללינוקס יש מגבלה 1024 file descriptors ל process, ויש לשנות את המגבלה הזו – בכדי שנוכל להפעיל באמת יותר מ 1000 connections ל worker. ב distros מסוימים של לינוקס יש מגבלה קשיחה ל 4000 open files descriptors ל process.
      5. מסקנת ביניים חשובה: אנו יכולים להגדיר כל מיני דברים בקונפיגורציה של nginx, אבל לא פעם ההגדרות הללו הן לא מה שיקרה בגלל מגבלות / התנהגויות של פרוטוקול ה HTTP, של מערכת ההפעלה, או של ה Application Server שלנו. העבודה ב nginx היא הרבה פעמים עבודה ב infrastructure רחב יותר מסביב ל nginx.
  6. כאן אנו נתקלים ב directive מיוחד של הקונפיגורציה של nginx: ה include.
    1. בעצם מה שקורה הוא שכל תוכן הקובץ המתואר “מוכנס” (inlined) בקונפיגורציה במקום שורת ה include. ה include מאפשר ניהול נוח יותר של קונפיגורציה בקבצים קטנים וממוקדים יותר.
    2. במקרה הזה מדובר על קובץ די משעמם הכולל את ה types context ובו שורה ארוכה של מיפויים בין MIME-types לסיומות של שמות קבצים.
    3. ה directive הבא מתאר fallback: איזה MIME-type להצהיר אם לא מוגדר לנו MIME-type לסיומת של קובץ. גם משעמם.
    4. השימוש בקובץ שהוא included יכולה לגרום לבלבול בהתחלה: בקובץ לא מוגדר ה context בו אנו פועלים ואנו יכולים בטעות להגדיר directives לא רלוונטיים. nginx עשוי לזרוק warnings בעליה (שלא נראה אותם) או פשוט להתעלם – ואז לא נבין מדוע הוא לא מתנהג כפי שציפינו. אולי צריך לשנות עוד קונפיגורציה?
      ארחיב על העניין בזה בהמשך.
  7. כאן אנחנו מגדירים פורמט מסוים ל access log ושומרים את הפורמט בשם “main”.
    ה Access log של nginx הוא כלי שימושי למדי, אשר קיומו הוא לעתים אחת הסיבות מדוע אנו מציבים nginx כ reverse-proxy לפני שרת האפליקציה שלנו. הוא שומר שורה בקובץ הלוג לכל בקשה שעברה דרך שרת ה nginx – מה שיכול לסייע לניתוח ה traffic שאנו מקבלים.

    1. לעתים, כאשר ה nginx מטפל בכמות גדולה של תעבורה (נניח: אלפי בקשות בשנייה) – יש היגיון לסגור את ה access log כהתנהגות ברירת מחדל, או לכתוב סלקטיבית רק חלק מהרשומות.
  8. אנו כותבים access log במיקום מסוים, וע”פ פורמט ה main שהגדרנו שורה קודם.
    1. את הפורמט של ה error_log לא ניתן לשנות.
  9. sendfile היא קריאת מערכת של לינוקס שמעתיקה נתונים בין 2 file descriptions כפעולת kernel, בלי להעתיק נתונים ל user spaces. כאשר nginx משמש להגיש קבצים סטטיים (קובץ ל http connection) – היא יכולה לייעל מאוד את עבודתו.
    1. מצד שני, sendfile לא עובדת עבור file descriptors של UNIX sockets, וסתם מציבה מעט overhead מיותר. שרתי רובי (Unicorn למשל) מתקשרים מול nginx על גבי Unix socket ולא קריאות Http (מה שחוסך ב latency לתרגם את המידע לפורמט HTTP, ובחזרה). עבורם – לא נרצה להשתמש ב sendfiile.
  10. tcp_nopush היא אופטימיזציה של לינוקס / FreeBSD שאומרת ל TCP למלא packets לפני שהוא שולח אותם. למשל: לשלוח את ה headers של תשובת ה HTTP באותן tcp packets עם ה body. זו אופטימיזציה ל throughput. מהצד השני קיימת התנהגות הנקראת tcp_nodelay שהיא אופטימיזציה לצמצום ה latency. “כשיש משהו – תשלח”.
    1. אם nginx מגיש הרבה תוכן, ה tcp_nopush עשויה להיות אופטימיזציה יעילה למדי: שימוש יעיל יותר ב bandwidth של הרשת, כי ip+tcp headers הם overhead על כל pakcet,  וגם שימוש יותר יעיל בזיכרון של nginx (בעקבות ניצולת גבוהה של ה buffers).
    2. במקרה שלנו האופציה הזו כבויה. לא רוצים להפעיל אותה בתצורת ברירת-המחדל, אבל רוצים להזכיר לנו על קיומה.
    3. tcp_nopush היא קונפיגורציה שתפעל רק עם sendfile מופעלת. התלות הזו מוזכרת בתיעוד הרשמי – אבל היא מסוג הדברים שאפשר לפספס ולבזבז שעות בכדי להבין מה חסר. כמו כן תיאור הפקודה מסתמך על הבנה עמוקה של לינוקס / http. התיאור בתיעוד הרשמי הוא דיי לקוני למי שלא מכיר את המנגנון:

      1. נ.ב. – נאמר לי שאם נראה לי ש nginx מתועד בצורה לקונית, ומאפשר למשתמשים שלו מרחב הגון של טעויות – עלי לנסות לקנפג Apache Httpd. לא זכיתי.
  11. הנה סופסוף קונפיגורציה שמאוד קל להבין: אנו רוצים לעשות שימוש חוזר ב tcp connections שנוצרו (three-way-handshake, או שבע ב https, וכד’) על מנת לשרת בקשות HTTP מאותם מקורות. אני מניח שאנחנו זוכרים ש keep_alive ארוך הוא מצוין לביצועים של ה clients, אבל מפחית את ה utilization של ה connections בצד השרת – וזה מחיר בזיכרון / מקביליות שהשרת משלם עליו.
    1. בכדי לאפשר keepalive connections גם מול שרת האפליקציה, עלינו להגדיר את ערך ב keepalive directive שב upstream context. פרמטר זה אומר כמה connections אנו מרשים לשמור “בחיים” במסגרת keepalive.
    2. עוד דוגמה לקונפיגורציה תלויה, ולא בהכרח צפויה היא ה directive הבא בתוך context ה location או ה server:
      ;"" proxy_set_header Connection
      המשמעות כאן היא שאנו דורסים את ה header בשם Connection. בכדי למנוע מצב בו ה Client שלח ל nginx את ה header עם ערך close (הוא מבקש מאיתנו לסגור לאחר הבקשה את ה keep_alive connection), ואנו נעביר את ה header הלאה ל Application Server – והוא יבין ש nginx ביקש ממנו לסגור את ה connection. קומדיה של טעויות.
    3. כאשר אנו קובעים keepalive_timeout מומלץ גם להגדיר את proxy_http_version (גרסת ה http שאנו עובדים עם שרת האפליקציה שאנו עושים לו proxy) ל 1.1. אם ה nginx והשרת החליטו לעבוד ב http 1.0 (ברירת המחדל מצד nginx) – אז לא יהיה שימוש ב keepalive.
  12. אני מניח שאתם מכירים את דחיסת ה gzip: לא הדחיסה הכי אגרסיבית, אבל יעילה מבחינת ההשקעה של ה CPU לצמצום ה bandwidth ובעלת תמיכה רחבה בקרב דפדפנים ורכיבי-רשת שונים. לכאורה בחירה ב gzip היא no_brainer, אך דבר שעלול לגרום למשהו לא לעבוד – לא צריך להיות ב default ולכן אני מניח שהיא נמצאת ב comment.
  13. זוהי שורה חשובה מאוד: בעצם כאן אנו עושים include לכל קבצי הקונפיגורציה בתיקיה בשם conf.d. בתיקיה הזו נהוג להחזיק קובץ לכל Virtual Host ועוד קובץ בשם default.conf המכיל הגדרות ברירת-מחדל של ה server context. ה server context חייב להיות מוגדר בתוך ה http context (אחרת הוא לא תקף).
    1. Virtual hosting – הוא הרעיון שעל שרת פיסי אחד אנחנו מארחים כמה שרתים “לוגים”, למשל: אפליקציות שונות. כאשר nginx (או בזמנו: Apache httpd) אירח כמה אתרי-אינטרנט לוגיים – זה היה ממש virtual hosting. בעזרת patterns על הבקשה (למשל: רושמים כל אתר ב DNS כ path מעט שונה) – ה nginx יודע לאיזה שרת לוגי להפנות את הבקשה.
      1. התצורות המקובלות קצת השתנו, בעוד הטרמינולוגיה נשארה. גם כאשר ה nginx משרת כ reverse proxy אך הוא מתנהג מעט שונה לכמה urls שונים – אנו עדיין קוראים לזה virtual hosting.
        ה context בשם server מגדיר “שרת וירטואלי” שבו אנו מטפלים, כלומר: port ותחילית של URL, בעוד קיימת רמה נוספת בשם location המתארת urls ספציפיים יותר בתוך השרת.
      2. בתוך ה location אנו יכולים לאכוף / לעדכן headers מסוימים, לנהל רמה מסוימת של אבטחה (למשל: Authentication או סינון בקשות העונות ל patterns מסוימים), לפצל traffic עבור A/B Testing ועוד. בפוסט הזה לא אכסה אף אחד מהנושאים הללו.
    2. ההפרדה לקבצים היא מודולריזציה חשובה של הקונפיגורציה של nginx, בעיקר כאשר הקונפיגורציה גדלה.
  14. אם אתם עדיין מסוגלים לקלוט עוד מידע – אז משהו בוודאי נראה לכם משונה בשורה הזו. היא הופיעה כבר קודם לכן!
    1. זה נכון. שורה זו איננה חלק מהקונפיגורציה ברירת-המחדל, אך רציתי להשתמש בה להזכיר עקרון חשוב: ה nginx הוא (לרוב) רכיב קריטי ב infrastructure, ולא כ”כ קשה לעשות בו טעויות בהגדרות.
    2. המלצה ראשונה היא לערוך את הקונפיגורציה של nginx ב Editor או IDE שמכיר את מבנה הקובץ ויודע להתריע בפני שגיאות אפשריות. יש plugins כאלו ל IntelliJ ול VS Code, ובוודאי לעוד עורכים.
    3. כאשר אנו טוענים קונפיגורציה שגויה (= עם טעות) ל nginx, ייתכנו אחת מ-3 תגובות:
      1. nginx יעלה כשורה, ויום אחד (ע”פ חוק מרפי – השיא יגיע ביום שישי בערב) – תתגלה בעיית production.
      2. nginx יעלה כשורה, אך יזרוק warnings בעליה – אבל מי בודק אותם בכלל לפני שיש בעיה כללית? הבעיה עוד תגיע…
      3. nginx יסרב לעלות – ואז יש לנו פאדיחת deployment קטנה.
    4. אף אחת מהאופציות היא לא נהדרת.
      1. על מנת לצמצם את הבעיות, ניתן להפעיל בשלב מוקדם ב deployment את הפקודה nginx -t. הפקודה הזו תבצע בדיקות על הקונפיגורציה ותזרוק error אם נמצאה בעיה. וריאציה אחרת: static analysis בזמן ה build.
        למשל: ההגדרה הכפולה שביצעתי למעלה לא נתפסה ע”י ה IDE (בעיות רבות אחרות – נתפסות), אך היא נתפסת כ warning ע”י nginx -t.
      2. דפוס שימוש מקובל הוא לבצע בדיקת קונפיגורציה לפני reload, ולהכשיל את ה reload על כל
        בעיה אפשרית: nginx -t && nginx -s reload. נראה שזו התנהגות ברירת-המחדל ב AWS Beanstalk, למשל.
      3. חברות עתירות משאבי כ”א עשויות להשקיע אף יותר בבדיקות התקינות של קונפיגורציות ה nginx שלהן. כפי שאמרנו, בעיות קונפיגורציה רבות נובעות מתוך פרוטוקולי התקשורת או מערכת ההפעלה – לא משהו ש nginx יידע בהכרח לבדוק עבורנו.
תצורה טיפוסית של התקנת nginx כ reverse proxy בענן.
האם nginx באמת נדרש?!

אז למה בעצם צריך nginx עבור ג’אווה (JVM) או Go?

לכאורה כאשר יש לנו פלטפורמה המסוגלת בקלות לטפל במספיק connections במקביל, וכאשר יש לנו Load Balancer – אנחנו “מסודרים”. מדוע רבים עדיין מתקינים ומתפעלים nginx בין ה LB ל Application Server/Process?
האם זה רק כוחו של הרגל?

אם מעולם לא שיניתם את הקונפיגורציה הסטנדרטית של nginx, או לסירוגין: הקונפיגורציה רק יצרה בעיות שיש לפתור – המשיכו לקרוא, ושקלו בסוף אולי עדיף בלי.

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

  • Access Log – רישום כל הקריאות שנעשו לשירות, עם overhead מינימלי.
  • תפקיד קלאסי של Reverse Proxy – “להחביא” כתובות פנימיות של שרתים, משיקולי אבטחה.
  • Easy SSL termination – כאשר יש תקשורת https.
  • Caching והגשה סופר-יעילה של static resources.
  • GZIP
  • אפשרות קלה להוסיף headers מסוימים על הבקשות המתקבלות.
  • אפשרות לשכתב URLs או להפנות URLs ל ports או URLs פנימיים אחרים.
    • אופציה פופולרית לדוגמה: הפניית (redirect) קריאות http ל https.

היכולות האלו שימושיות בעיקר עבור שרתים שהם Customer Facing, כלומר – אלו שאליהם פונים ישירות המשתמשים (דפדפנים / אפליקציות מובייל).
אם אנחנו עובדים עם AWS אזי Cloudfront מספק פתרון עדיף להגשת static resources, וה ELB (בעיקר הווריאציה שנקראת ALB) מספקת יכולות של Reverse Proxy, SSL Termination ועוד. למשל: לאחרונה הוסיפו את היכולת להפנות קריאות http ל https בסימון של checkbox. שרתי ג’אווה ו Go מספקים Access Log יעיל גם כן.

אם אתם עובדים על גבי תשתית ענן – ליתרונות הנ”ל יש תחליפים מובנים.
ככל שהזמן עובר, ספקי הענן “נוגסים” בצורך ב nginx כ reverse proxy ומצמצמים אותו.

אבטחה

סט יכולות חשוב נוסף ש nginx ממלא הוא יכולות אבטחה:

  • אימות זהות (Authentication) – מה שהופך אותו מ “סתם Reverse Proxy” ל “API Gateway מ-ט-ו-ר-ף”
    • המונח API Gateway הוא באזז רשמי לשנים 2017-2018. אולי גם יגלוש ל 2019.
    • ל nginx יש מגוון יכולות Authenticatoin ואפילו SSO, רבות מבוססות modules (כלומר: plugins).
    • רבים ממשמשים פתרונות אימות לבד, או בעזרת צד-שלישי כמו OKTA או Auth0. גם שירותי הענן השונים נוגסים בנישה הזו של nginx.
  • הגבלת מספר ה connections ע”פ לקוח (נניח: טווח כתובות IP)
  • אפשור / חיוב הצפנה קרי TLS/SSL – הרבה יותר קל ליישם על גבי AWS ELB.
  • היכולת לחסום תעבורה מאזורים גאוגרפים שונים. למשל: אני פועל בארה”ב וארצה לחסוך תעבורה מסין (שיותר סביר שהיא לא-לגיטימית)
    • על בסיס module
    • יכולת בסיסית ומקובלת היום של WAF
  • חשוב לציין שעצם כך שהשרת הראשון שה Traffic רואה הוא שרת פשוט (לא מסובך, פחות באגים סבירים) המאמת את השימוש בפרוטוקולי הרשת השונים (קרי IP/TCP/HTTP, וכו’) – זה כבר יתרון אבטחה חשוב שעשוי למנוע לנו בעיות.
    • היתרון הזה נכון גם ל ELB.

כפי שאתם רואים, גם כאן מוצרי צד-שלישי ויכולות ענן מכרסמות ב”טריטוריה” של nginx.

מצד האבטחה עולה שאלה נוספת: האם כדאי לנהל nginx לכל שירות – או אחד לכל המערכת?

תפקיד חשוב ש nginx יכול למלא הוא לאכוף כללי אבטחה על כלל המערכת. למשל: מדיניות אבטחה המחייבת headers של HSTS ו X-XSS-Protection ומצד שני – הסרה של כמה headers פנימיים מה requests (למשל session token). קל יותר, ונכון יותר לאכוף כללים כאלו פעם אחת ברמת ה nginx מאשר בקוד מספר רב של פעמים. ההתנהגות מ nginx ברמת ה HTTP היא מובנת וצפויה יותר מאשר התנהגויות של frameworks ומנועים שונים שאנו עובדים איתם.

  • nginx לכל שירות יכול להופיע כהתקנה אחת לכל cluster של השירות (s1 ו s2 בתרשים למטה) או nginx על כל server node (בתרשים למטה – s3. התצורה הזו יותר מקובלת).
  • nginx יכול להיות גם מותקן כ cluster יחיד על כלל המערכת. ה cluster של ה nginx חשוב מאוד עבור high-availability..

למרות ש cluster יחיד של nginx לכלל המערכת נשמע הגיוני מבחינת ריכוז השליטה על האבטחה, בעידן הענן קל יותר לנהל nginx על כל מכונה (או pod, אם אנחנו עובדים עם קוברנטיס). את אחידות כללי האבטחה בין כל מופעי ה nginx, עלינו לנהל בעזרת configuration management. נניח: קובץ אחיד conf. שיהיה Included בכל מופעי ה nginx במערכת.

גמישויות נוספות

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

לעתים מדובר גם בצרכים שהם לא מידיים כמו רצון להגדיר throttling, סתם לדבר תעבורה לשירות מסוים, ביצוע a/b testing ועוד. אלו דברים שהכלי שנקרא nginx יכול לספק בצורה טובה ומהירה – למי שיודע להשתמש בו.

אם אתם עובדים בענן, על פלטפורמה ללא מגבלות concurrency, מעולם לא שיניתם קונפיגורציה של nginx ואתם גם לא ממש יודעים איך לעשות זאת – אולי באמת nginx הוא רכיב מיותר שמוסיף סיכונים לתקלות – ללא ערך המצדיק את הסיכון הנוסף הזה.

סיכום

ראינו פרטי קונפיגורציה של nginx בכדי לספק תחושה והתמצאות בסיסית בכלי, וקצת דנו בשיקולי ארכיטקטורה ומקומו ב System Landscape.

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

להלן כמה לינקים שימושיים שאספתי בזמן כתיבת הפוסט, שעשויים לשרת אתכם הלאה:

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

Infrastructure as a Service: Terraform

אם אני מעוניין לתת דוגמה לפער בין התיעוד הרשמי ל\”מידע מהשטח\” בעולם של AWS – אני נותן את הדוגמה של Terraform.
בתיעוד של אמזון, הכלי המדובר להגדרת תצורה רחבות היקף הוא Cloud-Formation (של אמזון) – אבל בפועל, אני לא מכיר אף אחד שעובד עם Cloud Formation לאורך זמן: כולם עוברים ל Terraform.
אם תשאלו מישהו שעובד כבר עם AWS – הוא כנראה יפנה אתכם ישר לשם.

Terraform הוא כלי שמנסה לספק כמה מטרות:

  • לספק תמונה אחת לכל תשתית המערכת שלנו.
  • לתמוך בתצורות production מודרניות (ענן, וכו\’)
  • לספק אופן צפוי ובטוח לעדכן את תצורת התשתית שלנו.
  • לספק Workflow אחיד לעבודה מול ספקי-תשתיות שונים.
    • משום מה, אנשים נוטים להניח ש Terraform מאפשר להגדיר תצורה לספק ענן אחד (נניח גוגל) ואז להפעיל אותה על ענן אחר (נניח Azure). זה בהחלט לא המצב! זה לכל היותר מיתוס!

נפתח בדוגמה

מה ש Terraform (מעתה אשתמש בקיצור: טרה) מאפשר – הוא להגדיר תצורה רצויה בצורה פשוטה – ו\”לגרום לה לקרות\”.

את הגדרת התצורה עושים בקבצים עם סיומת  tf. כמו זה:

\”היי!\” – אתם עשויים לומר, גם בעזרת  aws-cli , או סתם סקריפט פייטון שמשתמש ב AWS SDK – אני יכול להרים instance בצורה פשוטה למדי! אז מה ההבדל?

יש כמה הבדלים:

  • בעזרת טרה, אני משתמש באותה שפה ואותו תהליך אני יכול להגדיר תצורה על תשתיות שונות: AWS, גוגל, Dyn, Cloudflare – ועוד. זה קצת יותר פשוט מלהתחיל לעבוד עם כלים שונים ו SDKs שונים.
  • טרה מוסיף \”חוכמה\” של חישוב המסלול הנכון להגיע למצב הרצוי: טרה בודק מה המצב הקיים, ומחשב אלו שינויים יש לבצע בכדי להגיע לתצורה הרצויה. לעשות את זה לבד – זו הרבה מאוד עבודה!

נמשיך:
לאחר שיצרתי את קובץ הגדרת התצורה שלי, אני מקליד את הפקודה

$ terraform plan
ומקבל את הפלט הבא:

מה קרה כאן?
עדיין לא קרה שומדבר. המצב שלי באמזון עדיין לא השתנה.

מה שפקודת plan עושה היא מריצה סימולציה של השינוי המתוכנן – ומספקת לי פרטים שאוכל לבחון אם זה אכן המצב הרצוי (אנחנו רוצים לצמצם טעויות בפרודקשן, נכון??).

הפלט מופיע בתצורה של diff:

  • + – על משאב חדש שנוצר.
  • – על משאב שמבוטל.
  • ~ – על משאב שעובר שינויי תצורה.
  • -/+ – על משאב שמבוטל, ומוחלף במשאב אחר.

ניתן גם:

  • להעשיר את הפלט בצורה מותאמת אישית, בעזרת פקודות output (או פונקציות כמו count) בהגדרת התצורה. למשל: אני רוצה לדעת איזה IP ה ELB שלי הולך לקבל, או כמה instances מסוג מסוים יהיו על מכונות spot.
  • להשתמש בפקודה terraform graph על מנת לקבל גרף המתאר את סדר הפעולות המתוכנן. טרה עובד עם פורמט גרפים סטנדרטי, ואת הפלט של הפקודה ניתן לטעון ל webgraphviz.com – על מנת לצפות בגרף בצורה ויזואלית.
    • הפלט של פקודת plan מסודר ע\”פ סדר הא\”ב על מנת לספק יכולת סריקה מהירה של התוכן.
  • לשמור את תוצאת התכנון, בעזרת הפרמטר out=path-, כך שיובטח שבעת הפעולה תרוץ התוכנית שבחנתי.

כאשר אני מקליד:

$ terraform apply

עברה כחצי דקה ואני מקבל את ההודעה הבאה:

אני יכול גם להסתכל ולראות את ה instance שנוצר ב console

או פשוט לקרוא ל \”aws ec2 describe-instances\” ולראות אותו ב console.

לאחר ביצוע קבוצת ה apply, טרה יצר לנו שני קבצים: קובץ tfstate. וקובץ tfstate.backup.

הקבצים הללו (יש להם תוכן שונה זה מזה) מתארים את ה last-known-state של התצורה המרוחקת (כלומר: מה שקורה בפועל), והם ישמשו אותנו לפעולות הבאות.

במקרה שלנו הקבצים נוצרו רק לאחר פעולת ה apply (כי נוצר מצב שלא ניתן לשחזר בעזרת ה API של AWS בלבד), אבל גם בהרצת פקודת plan ייתכן והקבצים הללו ייווצרו / יתעדכנו.

את הקבצים הללו אני אוסיף ל gitignore. כך שלא יגיעו ל source control. מדוע – אסביר בהמשך.

נמשיך:

נעשה שינוי קטן בכדי לראות את ניהול הדלתאות של טרה:

ביצעתי שני שינויים. קראתי ל terraform plan וקיבלתי:

האמת שהייתי מצפה בשינוי instance_type שזו תהיה פעולת +/- – אבל כנראה בגלל שספציפית באמזון השוני בין t2.micro  ל t2.nano הוא רק ב CPU capping – אני מניח שה instance לא מוחלף פיסית. אם הייתי משנה AMI, למשל – זו הייתה פעולת +/-.

לאחר terraform apply אקבל:

הפעולה לקחה קצת יותר זמן ממה שציפיתי – אבל הזמן הזה קשור לאמזון ולא לטרה.

כמובן שלאחר ה apply אני מעדכן את הקוד בגיט: אני מאוד רוצה snapshot נכון של התצורה שהפעלתי בכל רגע: אנחנו מדברים הרי על infrastructure as code. אולי שווה אפילו לעשות מקרו שלאחר apply מוצלח דואג להכניס לעדכן commit – שלא אשכח…

והנה המצב ב Aws Console:

ניסוי אחרון, נעשה שינוי אפילו פחות מהותי ב instance:

נריץ plan:

נריץ apply:

הפעם השינוי היה מהיר מאוד. זה רק שינוי של metadata ופה בכל מקרה לא ישתנה לי ה instance ID.

נכניס את הקוד לגיט. נבדוק את ה console עכשיו:

כדי שלא נשלם הרבה $$, בואו נסגור את ה landscape שיצרנו:

פקדנו terraform destroy, אימתנו שזה לא בטעות – yes, וזהו. תוך כמה שניות כל המכונות (כלומר: instance אחד בגודל nano) – למטה.

אם אני מעוניין בניתוח של מה המשמעות של destroy אני יכול לפקוד: terraform plan -destroy – ולקבל הדמייה / הערכה.
destroy כמובן לא ייגע במשאבים שלא הוגדרו ע\”י טרה.

Great Success!

מה קרה כאן, בעצם?

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

הקובץ הוא בפורמט HCL (קיצור של HashiCorp Configuration Language) ומתאר בצורה דקלרטיבית קונפיגורציה של Infrastructure. הוא מבוסס על פורמט \”רזה\” של JSON בשם UCL בה משתמשים בקונפיגורציה של nginx.
אפשר לכתוב את הקבצים בצורה ה JSON-ית ה\”כבדה\” (מרכאות מסביב לכל אלמנט, וסוגריים מסולסלים מסביב לכל Key-Value pair) – או בצורה ה\”רזה\”. אנחנו, כמובן, ניצמד לצורה ה\”הרזה\”.

1. בתור התחלה אנחנו מגדירים עם איזה (provider(s. קובץ טרה יכול לעבוד עם כמה ספקי-תשתיות שונים במקביל.

  • טרה תומך בעשרות \”ספקי תשתיות\”, כולל Cloudflare, New Relic, MailGun, ועוד. 
  • בתוך provider מסוג aws\’ עלי לספק את ה region. בחרתי region זול (בכל זאת, לבלוג אין הרבה תקציבים…)
  • צורת העבודה, והשפה (HCL) הם אחידים עבור כל ספק תשתיות – אבל הערכים שזמינים לכל ספק הם שונים לחלוטין. ל Dyn אין בכלל regions, אבל נדרשים לתאר את שם הלקוח (שלכם). בענן של גוגל יש לתאר region אבל גם project.

    2. בכל ספק תשתיות, ניתן להגדיר כמות וסוגים שונים של resources. לכל סוג resource – הגדרות משלו.
    הפורמט הוא:

    resource  \”_\” \”\” {
     
    }
    כאשר: 
    • provider צריך לתאום ל provider שהגדרנו בתחילת הקובץ
    • ה type הוא ערך מתוך מרחב ערכים שמוגדל לכל provider. למשל ל dyn יש resource בשם \”dyn_record\”
    • ה resource_name הוא שם שיזהה בצורה ייחודית את המשאב הזה בתוך ההגדרות של טרה.
    • ה config הוא רשימה של שדות, שמרחב הערכים מוגדר ע\”פ הצמד provider ו type.

    3. AMI, כפי שאתם בוודאי מכירים הוא Amazon Machine Image. מאיפה בוחרים AMI, אם לא שלכם? אני בוחר בעזרת ה AMI Locator של אובונטו (רק AMIs של אובונטו). שווה להכיר ש:

    • ל regions שונים יש AMI ID שונים: ה AMI של אובונטו בצפון וריגיניה וזה של אורגון אולי יהיה זהים ברמת הביטים – אבל יהיה להם ID אחר.
    • במכונה מסוג t2.micro או t2.nano אני יכול להשתמש רק ב AMI שעובד עם hvm (וירטואליזציה) ו ebs (אחסון). סתם שווה להכיר.
    • אפשר לקבל במהירות עוד מידע על AMI בעזרת הפקודה הבאה:
      aws ec2 describe-images –image-ids ami-a60c23b0

    בגלל שה id של ה image לא אומר לי כלום – אני מוסיף הערה. Infra as Code משמעו גם להתייחס לקונפיגורציה שלכם ברצינות שמתייחסים לקוד Production שהולך ומזדקן עם הזמן.

    4. בתוך ה config של resource type מסוים יכולים להיות אובייקטים מקוננים, כמו אובייקט ה tags.
    tags מסוימים, כמו Name – הם משמעותיים מאוד ב AWS.

    ב HCL מגדירים:

    • ארגומנט (כמו אלו של ה resource) כקלט לקונפיגורציה. משהו שאנחנו יודעים ומציבים בקונפיגורציה.
    • Attribute (\”תכונה\”) כפלט של הקונפיגורציה בעת ההרצה – פקודת apply. משהו שלא ידענו לספק בעצמנו אבל מתאר את התצורה. למשל: כאשר אני יוצר instance ב AWS אני לא יודע איזה כתובת IP הוא יקבל – אבל ייתכן והכתובת הזו צריכה לשמש אותי לקונפיגורציה בהמשך, למשל: בכדי ליצור רשומת DNS.
      • ה attributes הם חלק חשוב מה state – שנגדיר אותו מייד.
    כאשר אנו מתכננים plan, טרה מאחורי הקלעים יוצר גרף (מסוג DAG) של תוכנית הפעולה להגעה למצב הרצוי. הקונפיגורציה עצמה לא מתארת את סדר הפעולות (אלא אם נוסיף אילוץ – depends_on) – טרה מחשב אותו בעצמו.
    טרה ידע מתי הוא יכול למקבל פעולות – והוא ינסה לעשות זאת ככל האפשר, אבל יימנע ממקבול מתי שהוא מסוכן. למשל: כאשר אני מחליף instance ממכונה קטנה לגדולה – טרה לא יודע מה המשמעות של כמה השניות שהמכונות הללו יחיו במקביל. בגלל שזה עלול להיות מזיק – הוא יימנע מכך ויעשה את סדר הפעולות סדרתי.
    קונספט חשוב נוסף הוא ה State
    בטרה מדברים על:
    • Last known state – המורכב מהארגומנטים השונים, משתני הסביבה בעת ההרצה, וכו\’ – כל מה שטרה עשוי להשתמש בו בעתיד. הוא נשמר בקבצי terraform.tfstate ו terraform.tfstate.backup.
    • Actual state – המצב בפועל של התשתית שלנו. זהו מצב לא ידוע כי ייתכן ויש שינויים שלא נעשו ע\”י טרה, או אפילו לא ע\”י העותק הנוכחי של טרה.
    לפני שטרה מבצע תוכנית או apply הוא פונה ל APIs ולומד מה שהוא יכול על ה actual state. אנחנו בהחלט רוצים להימנע מטעויות.

    הוא גם ייעזר ב local state על מנת לבצע:

    • מיפוי נכון, למשל ה resource x הוא בעצם instance עם id כזה וכזה ב EC2
    • לשפר את מהירות הפעולה: אם instance id קיים – אני יכול להניח שכמה attributes שלו לא השתנו.

    ולכן ה state הוא חשוב לפעולה תקינה. אם אתם עובדים לבדכם, שמירת קבצי ה state בדיסק הלוקלי הוא אולי רעיון טוב, אבל כשמגיעים לעבוד בקבוצה – יש מחשבה לשתף את קבצי ה state ב source control. יש בזה כמה בעיות:

    • מישהו, מתישהו, הולך להפעיל את טרה עם קבצי state שאינם up-to-date (למשל: שכח לעשות git pull לפני). אתם יודעים, כל טעות בפרודקשיין עשויה להיות אסון. לא טוב!
    • כל קבצי ה state נשמרים כ plain text וכוללים את כל משתני הסביבה. יש סיכוי טוב שסודות (ססמאות, keys, וכו\’) יכנסו לתוכן הקבצים. אתם לא מכניסים ססמאות לתוך ה source control שלכם – אני מקווה!
    בקיצור: ההמלצה הגורפת היא להכניס את הקבצים הללו ל gitignore. ולא לעדכן אותם לתוך ה source control.
    אז מה עושים בקבוצה?

    לטרה יש מנגנון שנקרא remote state (אולי היה אפשר לקרוא לו shared state) ששומר את ה state באחסון מרוכז (consul, S3, ועוד) – ומשתמש בהם משם. כמובן שאת שטח האכסון (למשל: S3) – חשוב להגדיר כמוצפן.

    Remote State דורש קצת עבודת הגדרות – אבל הוא בהחלט הדרך הנכונה לעבוד!

    דוגמה ל folder structure של פרויקט טרה. מקור

    נקודת מפתח שאולי לא הזכרתי מספיק בטרה, היא באמת הפילוסופיה של Infrastructure as Code. אפילו שזה קוד דקלרטיבי – ישמש אתכם מאוד לעבוד בפרקטיקות טובות של קוד.

    למשל: קבצים גדולים? חס וחלילה – קשה להתמצא ולקרוא את הקוד.
    טרה מעודדת לפרק את הקוד לקבצים קטנים יותר: כל הקבצים בסיומת tf. ו/או tf.json. בתוך אותה התיקיה – יעברו merge לפני שיורצו.

    יש כאלו שמפרקים את הפרויקט לקבצים כמו: variables, output ו main – חלוקה בהחלט לא scalable מבחינת ניהול קוד.
    תצורה עדיפה היא חלוקה ע\”פ תוכן: dns, instances, launch_configurations, security_groups, וכו\’.

    כמו כל פרויקט, וקצת מעבר – ההמלצה היא לפרק את הפרויקט לתתי פרויקטים (כמו בתרשים למעלה).
    החלוקה לתתי פרויקטים היא ה bulkhead שלכם! אם משהו משתבש – אתם תרצו שהוא ישתבש ב scope מצומצם ומוגדר היטב.

    כלים נוספים בשפת ה HCL הם:

    מודולים

    אם יש לכם 100 שירותים בפרודקשיין, אני מקווה בשבילכם שאתם לא מנהלים 100 עותקים מאוד דומים של קוד!
    אתם רוצים להגדיר מודול של service_instance, של service_elb, של service_db וכו\’ – ולשכפל מעט עד כמה שאפשר קוד.

    Interpolation (התייחסות למשאבים אחרים)

    בעזרת תחביר ה interpolation אנחנו יכולים לקצר ולייעל את הקוד שלנו (וגם להשיג דברים – שאחרת פשוט לא יכולנו). התחביר נקרא לעתים גם \”dirty money\” והוא נראה כך:

    אני מגדיר בתוך רשומת ה DNS את כתובת ה IP של ה ec2 instance בשם example. ערך שאי אפשר לדעת לפני הריצה.
    ה interpolation הוא, כמובן, כלי עיקרי לטרה לקבוע את סדר הפעולות ההרצה.

    משתנים

    שניתן להגדיר פעם אחת מרכזית – ולהשתמש בהם ברחבי הפרויקט. אני מניח שאתם מכירים משתנים.

    סיכום

    מהסקירה הזו של טרה, אתם אמורים להבין בצורה דיי טובה מהו Terraform, מה ה workflow שלו, וכיצד הוא עובד.
    כמובן שיש עוד פרטים רבים להשלים – על מנת לעבוד איתו בצורה שוטפת.

    לאורך הפוסט ניסיתי להדגיש גם את האלמנטים של Infrastructure as Code.

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

    —-

    לינקים רלוונטיים

    מדריך לכתיבת מודולים בטרה: https://linuxacademy.com/howtoguides/posts/show/topic/12369-how-to-introduction-to-terraform-modules

    Terraform Comprehensive Training – מצגת שנראה שמקיפה הרבה נקודות:
    https://www.slideshare.net/brikis98/comprehensive-terraform-training