לקבל מושג ירוק על 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.

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

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

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

לינוקס/אובונטו: חיפוש אחר לוגים – ומציאת דברים רבים נוספים

היבט חשוב של תפעול ועבודה עם מערכת הם הלוגים (log files) של המערכת ואפליקציות השונות.

משמעות המילה log, כדרך אגב, היא \"בול עץ\". כיצד בולי-עץ קשורים לקבצי log? הנה סיפור קצר:

בעבר ניווט בים נעשה ע\"י סכימת מרחקי שיט שהספינה עברה. הספינה יצאה מנמל (עכו, למשל) והקפטן ספר: \"6 שעות לכיוון מערב במהירות 10\".
המהירות חושבה בצורה מדעית למדי: מלח השליך בול עץ (log) למים מחרטום האוניה. מכיוון שהאוניה מהירה יותר (יש לה הנעה מהמפרשים), היא עקפה את בול העץ בהדרגה. המלח ליווה את בול העץ מחרטום האוניה על לירכתיים – ושר לעצמו שיר. ברגע שבול העץ הגיע לקו יכרכתי האוניה, השירה נפסקה ובול העץ נשלף מהמים. ע\"פ המקום בו הפסיק השיר נקבעה – מהירות השיט: \"אבל את לא מבינה…\" – מהירות 7. \"…אל תהיי תמימה\" – מהירות 6.

הקפטן, מצידו, ניהל מסמך ארוך שנקרא Captain\'s log שכלל שורה לכל קטע בשיט: .
לפי הרכבת השורות בלוג ניתן היה אפשר לשחזר את מסלול הספינה – ומכאן את מיקומה. כמה מאות ק\"מ לפה או לשם…

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

למידע נוסף על ניווט ימי, אני ממליץ להאזין לפרק המצוין של \"עושים היסטוריה\": פרק 82: האדמירל שהלך לאיבוד- על ניווט ימי ומפות עולם.

פוסט זה הוא חלק מהסדרה: לינוקס / אובונטו

חיפוש אחר …

בואו נחפש את קבצי הלוג של מערכת האובונטו. בכדי למצוא קבצים במערכת הקבצים של לינוקס, משתמשים בפקודת find. למשל, כך מחפשים קובץ index.html בתוך התיקיה של המשתמש שלי (~):

find ~ -name index.html

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

התחביר שלה בפועל הוא משהו כזה:

find

  • symbolic links options – נתעלם מחלק זה. כברירת מחדל find לא \"נכנסת\" ל symbolic links.
  • root path – מתחתיו יתבצע החיפוש. אם לא צוין, יהיה . (התיקיה הנוכחית).
  • search options – נוגעים לאופן החיפוש: איזה עומק לחפש (depth), האם לחפש ב mounted drives (פרמטר mount-) וכו\'.
  • search arguments – נוגעים לתכונות הקבצים עצמם: שם, גודל, תאריכים, הרשאות וכו\'.
  • action – מה לעשות עם הקבצים שהתאימו לחיפוש. ברירת המחדל היא print- (הצגה ל standard output). אפשרויות אחרות כוללות הפעלה פקודה כלשהי בלינוקס (rm, mv וכו\').
קצת קשה לזכור את הסדר של סוגי הפרמטרים השונים. אם מתבלבלים בסדר הפרמטרים אזי יופיע warning נוסח \"non-option argument\", או במקרה הפחות טוב – יקרה משהו שונה ממה שהתכוונתם.

הנה דוגמה לכמה חיפושים אפשריים:

find ~/js_project -iname \"*.js\" -size -10k

תחפש קבצים בסיומת js. הארגומנט iname מחפש ע\"פ שם בהתעלמות מ case. כלומר גם סיומת \"Js\" תמצא. הארגומנט size- מגביל את גודל הקובץ, במקרה שלנו עד 10kB. שימוש ב 10k+ היה מחפש קבצים בגודל 10kB ומעלה.

דוגמה קצת יותר מורכבת:

find /home ! -user baronlior -exec touch {} \\;

תחפש את הקבצים של משתמש שאינו (סימן !) המשתמש \"baronlior\" בתיקיה home/, ובמקום להדפיס את הקבצים למסך – תפעיל עליהם את פקודת הלינוקס touch. הפעולה exec- מקבלת expression כלשהו שנגמר ב ;. באובונטו צריך escaping (כל הפצה מתנהגת מעט שונה) – ולכן  ;\\ מציין את סוף ה expression. הסוגריים המסולסלים יוחלפו כל פעם בשם הקובץ. לכן המשמעות בפועל היא הפעלת הביטוי הבא על כל קובץ שנמצא:

touch

פקודת touch מעדכנת את ה modification date של הקובץ לזמן הנוכחי. \"נוגעת בקובץ, מבלי לשנות את תוכנו באמת\".
אם הקובץ לא קיים – היא תיצור אותו. פקודה שאני מוצא כשימושית למדי.

אחד העקרונות של ה shell של לינוקס הוא: \"נגעת – נשאת\". אין שאלות \"Are you sure\" גם על פעולות משמעותיות. כדי להמנע מ\"תקלה מצערת\", ניתן להפעיל את פקודת ה find הנ\"ל בצורה הבאה:

find /home ! -user baronlior –ok touch {} \\;

ok- הוא כמו exec-, רק שהוא שואל אותנו, קובץ אחר קובץ, אם אנחנו בטוחים.

הנה כמה לינקים עם דוגמאות נוספות לשימוש ב find:
60 דוגמאות מעשיות ל find – חלק א\'
60 דוגמאות מעשיות ל find – חלק ב\'
15 דוגמאות מעשיות לשימוש ב find (קצר יותר)

חיפוש אחר לוגים

טוב… סטינו קצת מהדרך. אנו מחפשים את קבצי ה log של לינוקס. היכן הם יהיו?
אם אתם זוכרים את הפוסט הראשון בסדרה, קבצים בעלי גודל משתנה מאוחסנים בד\"כ בתיקיה var/. בואו נחפש:

find /var -name \"*log*\" | grep log
כאשר משתמשים ב regex / wildcards – כדאי לעטוף את הערכים במרכאות כדי למנוע בלבול.
אני משתמש גם ב grep, כטריק קטן, בכדי להדגיש את התוצאות. 
grep, אם אתם לא זוכרים מהפוסט השני בסדרה, הוא כלי ש\"מפלטר\" זרם (stream) של טקסט רק לשורות המכילות מילת מפתח – וגם מדגיש את המלים הללו. 
grep יכול לעשות יותר מזה. הוספה של פרמטר 2 A- ,למשל, תאמר ל grep להציג 2 שורות נוספות מהטקסט לאחר השורה שבה היה match. הפרמטר הוא A עבור after ויש לו אח מקביל, B, עבור before.
ב grep ניתן להשתמש גם באופן עצמאי בכדי לחפש טקסט בתוך מספר קבצים (ע\"פ pattern).
בכל מקרה, הנה התוצאה של החיפוש שלי:
קצת ארוך. בואו נקצר ע\"י בקשה של חיפוש אחר תיקיות (type = d) בלבד:
2 תוצאות שמצאנו – נראות בהחלט רלוונטיות. 
כפי שניתן לראות יש עוד 4 תיקיות בהן לא הצלחנו לבצע חיפוש – מכיוון שאין לנו הרשאות קריאה בהן. אולי אנחנו מפספסים משהו?
ניתן להשתמש ב sudo.
קצת מעצבן להקליד את השורה הארוכה (נניח) שכבר הרכבנו פעם נוספת, לא?
בעזרת SSH client מתקדם ל ניתן להריץ מחדש עם sudo בקלות יחסית עם העכבר / קופי-פייסט.
יש דרך קלה אפילו יותר, שתעבוד עם ה SSH client הפרימיטיבי ביותר / terminal:
sudo !!
!! הוא משתנה שערכו הוא הפקודה האחרונה שהוקלדה ב shell. לאחר שימוש בפקודה. כאשר אנו משתמשים ב !!, ה history שיזכר הוא כאילו הקלדנו הכל מחדש. מאוד נוח – רק שימו לב לא להקל ראש בשימוש ב sudo עם הטריק הזה.
בעזרת הרשאות sudo, מצאנו עוד תיקיה בשם logrotate. לא תיקיה שמעניינת אותנו כרגע.
בואו נתבונן על תסריט מעט אחר:
נניח שלא ניתן להשתמש ב sudo ויש הרבה תיקיות שאין לנו אליהן הרשאות (מצב הגיוני בשימוש ב find, במיוחד על שרת production). מה עושים? כיצד מפלטרים את כל הודעות השגיאה מהתוכן המשמעותי?
תזכורת של ה\"בעיה\"
אפשר בוודאי לעשות משהו עם grep, אבל אני רוצה לתקוף את הבעיה מכיוון אחר. ננסה:
זהו. אני מרגיש שהתחלנו להיכנס ל hardcore של השימוש ב shell. אני מניח שרוב מפתחי java ו / או C מכירים את 3 הערוצים של מערכת ההפעלה: stdout, stdin ו stderr (הממוספרים 0, 1 ו 2 – בהתאמה).
אלו הם ערוצי ה shell של לינוקס שב C עושים בהם שימוש, וג\'אווה אמצה אותם כממשק (תחת המחלקה System):
ישנו ערוץ אחד של קלט (stdin) ושני ערוצי פלט. למרות שאנו רואים על המסך תוצאה של הרצת התוכנה / פקודה כשטף אחד של שורות טקסט, בעצם מערכת ההפעלה מסווגת ומנהלת אותם כ 2 ערוצים שונים:
  • stdout הוא קיצור של standard output – תוצאת ההרצה
  • stderr הוא קיצור של standard error – שגיאות שהתגלו בהרצה.
בעזרת פעולת I/O redirect (קרי <, <<, אך גם > הנדירה יותר) אנו יכולים לשלוח את התוצאות של פלט התכנית ל stream (קובץ, רשת) שהגדרנו. בעזרת \" <n \", כאשר n הוא מספר ערוץ הפלט 1 או 2, אנו יכולים לפצל את הערוצים ולשלוח רק אחד מהם ל stream נבחר.

בפקודה למעלה שלחתי את ערוץ השגיאות לקובץ בשם err.txt, שכפי שניתן לראות בתצוגה שלו מיד לאחר כך – הוא קיבל את כל הודעות השגיאה.

אני מקווה שההסבר מובן.

קבצים מיוחדים

נשמור את קובץ השגיאות שלנו עכשיו במקום אחר:

find /var -name \"*log*\" -type d 2> /dev/null | grep log

מה זה הקובץ הזה? בואו נבדוק:

  1. ניסינו להציג את הקובץ – אך שום תוכן לא נראה. היכן השגיאות שלנו?!
  2. האם קובץ כזה בכלל קיים? בדקנו – וכן, הוא קיים.
  3. נשתמש ב file לבדוק מאיזה טיפוס הוא. הממ… \"סוג מיוחד\" (character special)

מה קורה פה?
dev/null/ הוא קובץ מערכת מיוחד, אחד מכמה בודדים במערכת Linux (עד כמה שידוע לי).
dev/null/ הוא סוג של \"recycled bin\": מה שנשלח אליו – נמחק לעד לפני שתספיקו לומר… \"… sudo\".

מתי הוא שימושי? כתחליף לקובץ זמני שאנו מתכננים למחוק – אך אנו יכולים לשכוח למחוק (ואז נשאר \"זבל\"). אני מניח שהוא גם מהיר יותר – כי שום דבר לא נכתב באמת לדיסק.
אם יש לנו פעולה שמייצרת הרבה error, אבל אנו יודעים שזה בסדר ולא רוצים לראות את השגיאות (כמו בדוגמה לעיל) – כתיבה ל dev/null/ במקום err.txt – היא שימושית.
קובץ מיוחד נוסף הוא dev/zero שהוא בעצם stream אינסופי של אפסים.
כיצד ניתן להשתמש בכזו חיה מוזרה?
למשל:
cat /dev/zero >> large.txt
ייצר לנו קובץ (מלא אפסים) בגודל כמה MB תוך שניות בודדות. שימושי לצורך בדיקות מקרי-קצה.
הפקודה \"cat large.txt\" לא תציג שום דבר, כי ערך 0 של ASCII הוא בלתי מוצג (זה לא התו \"0\", שערך ה ASCII שלו הוא 48). הפקודה:

xxd large.txt | less
דווקא תציג את תוכן הקובץ. xxd – קיצור (משונה) של hex dump. תוכנה זו הופכת מידע בינארי (כלומר: בייצוג הטבעי שלו) לתצוגה בינארית [א]. הצגתי את תוצאת ההמרה בתוך ה viewer הפשוט של לינוקס שנקרא less.

כשאפסים לא מתאימים למשימה, ניתן להשתמש באופן דומה dev/urandom/, שהוא פחות מהיר אך מייצר stream של מספרים פסודו-אקראיים.

קובץ מיוחד אחרון שאזכיר הוא dev/full/, שבכל פעם שננסה לכתוב אליו נקבל שגיאה שהדיסק מלא (שימושי עבור בדיקות).

אבל מה עם הלוגים?!

כן, נכון. התחלנו את המסע הזה בחיפוש אחר הלוגים.

לוגים נמצאים בעקרון בתיקיה var/logs/:

אפשר לראות שלוגים ישנים יותר נדחסים (צבע אדום) בכדי לחסוך מקום. התהליך שעושה זאת נקרא logrotate.
אפשר לראות תיקיות עם עוד logs ע\"פ נושאים (apt – התקנות או upstart – תהליך האתחול של אובונטו). כשמותקן שרת Apache, לדוגמה, הוא יאכסן את הלוגים שלו בתיקיית בת בשם /httpd.

כדי לצפות בקובץ שדחוס ב gzip (סיומת gz), יש פשוט להשתמש ב zcat, גרסה של cat שפותחת את הדחיסה on the fly:

zcat syslog.2.gz

אפשר לציין כמה לוגים עקריים:

  • syslog – לוג ברירת המחדל להודעות מערכת. syslog הוא לא רק קובץ לוג, אלא גם שם של פרוטוקול להעברת לוגים בין מערכות. למשל: הקצאת שרת אחד עליו יכתבו כל הלוגים של כל שרתי ה production. 
  • daemon.log – לוג של שירותים שרצים ברקע.
  • kernel.log – הודעות קרנל של מערכת ההפעלה.
  • auth.log – רשימה של פעולות התחברות למערכת ושימוש בפקודת sudo.

אפשר למצוא רשימה מקיפה של קבצי הלוג בלינק הכללי הבא ללינוקס או בתיעוד של אובונטו (הקבצים מעט שונים. למשל באובונטו אין קובץ לוג של messages שמאוד נפוץ לשימוש בכל העולם של red hat).
פורמט קבצי הלוג, הוא בברירת המחדל משהו כזה (תלוי בקובץ, ותלוי בהגדרות המערכת):

[Timestamp in syslog format] [Host] [Process]: [Message text]

host, אפרופו, הוא חשוב מכיוון שפעמים רבות מרכזים בעזרת syslog את הלוגים לא על השרת בו התרחשו האירועים.

כדי לנטר בעיות עלייה של המערכת כולה, הכי פשוט להשתמש בפקודה בשם dmesg המרכזת נתונים שונים על טעינת המערכת. כדי להתמצא בשלל המידע, כדאי להשתמש ב grep, כפי שמוסבר במדריך הקצר שבלינק זה.

את הגדרות המערכת, מה הולך לאיזה לוג, ניתן למצוא תחת קבצי הקונפיגורציה של הלוגים בתיקיה etc/rsyslog.d/
הסיומת d. משמשת ב UNIX (ולכן גם בלינוקס) לסמן ספריות, בעיקר כאשר יש קובץ עם שם דומה במערכת.

קובץ ההגדרות הראשי הוא rsyslog.conf (הנמצא במקביל תיקיה rsyslog.d) – המגדיר את המודולים האחראים על הלוגים, הרשאות של קבצי log שיכתבו וכו\'.

את ההגדרות היותר שימושיות ניתן למצוא בקובץ ברירת המחדל של הגדרות הלוג etc/rsyslog.d/50-default.conf:

ניתן למשל לראות, שקובץ הלוג של תהליכי הרקע (daemon.log) הוא disabled כברירת מחדל.

עוד טיפ קטן:
כאשר קבצי הלוג גדולים ו / או אתם בוחנים אותם דרך הרשת (למשל ssh) – הטעינה/הצגה שלהם הולכת לקחת זמן. פעמים רבות יעניינו אותנו רק השורות האחרונות, ואז כדאי להשתמש בפקודה tail. למשל:

tail -50 syslog | less

תציג לנו בתוכנת less רק את 50 השורות האחרונות של קובץ ה syslog. הטריק הוא לקרוא את כל הקובץ ולהציג רק את 50 השורות האחרונות: tail בעצם מבצע seek על הקובץ וקורא רק את הבלוקים האחרונים שלו עד שיש לו את מספר השורות שביקשנו – מה שמיעל דרמטית את זמן הטעינה של הלוג.

עוד דבר נחמד שקיים ב tail הוא הפרמטר f- (קיצור של follow) שיאזין לשינויים בקובץ לוג ויציג אותם ברגע שיתווספו לקובץ. למשל:

tail -f syslog

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

סיכום

עברנו עוד כברת דרך בטיפול בקבצים בלינוקס/אובונטו: פקודת find, קבצים מיוחדים, לוגים, ועוד. לפעמים ה\"עוד\" הזה הוא החלק החשוב ביותר 🙂
בתחום הקבצים בלינוקס/אובונטו נותר לנו לדבר בעיקר על הרשאות ו symbolic links…

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

[א] בדומה למצב debug ב DOS – למי שזוכר, אבל אז כל מילה הייתה 16 ביט / 2 בתים.

לינוקס/אובונטו – התנהלות עם קבצים, ההתחלה…

בלינוקס, יש לנו path המתאר תיקיות בהן מחפשים קבצים, אם הם לא נמצאו בתיקיה הנוכחית – ממש כמו בחלונות.

הערה: קצת מוזר לומר שמשהו ב Linux דומה לחלונות. המקור ממנו שתיהן הושפעו הוא כמובן UNIX, כאשר Linux הושפעה מאוד מ UNIX, וחלונות – רק במעט. בגרסאות האחרונות (Windows server 2003 ומעלה) ניתן לראות יותר מנגנונים בחלונות שדומים ל UNIX (ביצוע פעולות עם super user, יציאה מה registry לתיקיות קונפיגורציה, כל ההתפתחות של power shell, ועוד) – אולי בכדי לסגור פערים מול פ\'יצרים מוערכים בלינוקס.

פוסט זה הוא חלק מהסדרה: לינוקס / אובונטו

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

echo $PATH | tr \':\' \'\\n\'

מדוע השתמשתי בפקודה tr*
א. כדי להוסיף קצת אקשן, וללמוד כמה דברים נוספים על הדרך, ו
ב. כדי לא לקרוא את ה path בשורה אחת צפופה, כמו כלב.

* יש פה גם pipe. אם אתם לא מכירים – קפצו לפוסט הקודם בסדרה או חפשו בגוגל.

הנה תוצאת הפקודה על המחשב שלי:

כמה הסברים על התוצאה – מדוע כך נראה PATH ברירת-המחדל:

  • לינוקס מפרידה בין executables לשימוש כללי (תיקיות bin; קבצים לדוגמה: ls או cat) ו executables המשמשים את מערכת ההפעלה עצמה (תיקיות  sbin; קבצים לדוגמה: reboot, init או mkfs [א]).
  • תיקיות bin/ ו sbin/ (ישירות תחת ה root) – הן עבור executables שצריכים להיות זמינים בשלב מוקדם של ה boot sequence של מערכת ההפעלה, לפני שהתבצע mount לתיקיית ה usr/
  • תיקיית usr/, בניגוד ליוניקס או חלונות, היא איננה תיקיית \"user data\" אלא \"user applications\".
  • תיקיות */usr/local/ נועדו לאפליקציות (או סקריפטים) שאנו מוסיפים למערכת. הם לא חלק מהמערכת  ו / או מנוהלים ע\"י ה package manager. אפליקציות שמגיעות דרך ה package manager לא יכולות להיות מותקנות בתיקיית ה local.
  • משחקים? על ubuntu server? אולי לשעות המתות של ההתקנות… (התיקיות עצמן ריקות).

נחזור לפקודה עצמה:

echo – אנחנו מכירים (אמורים להכיר מפוסט קודם). שלא כמו בחלונות, יש להקפיד על upper-case ב PATH (או כל משתנה אחר).

tr (קיצור של transliterate, אפשר לזכור את זה כקיצור של translate) הוא בעצם סוג של כלי search-replace. אנו מחליפים את \":\" (ה separator בין paths במשתנה PATH) לשורה חדשה. מתכנתים אמורים להרגיש נוח עם n\\.

tr עובדת על תווים בודים (characters) ומבצעת החלפה, בהתאמה, בין ערכי הפרמטר הראשון לערכי הפרמטר השני. לצורך התרגיל: 
echo $PATH | tr \':\' \'\\n\' | tr \'a-z\' \'A-Z\'
תחליף גם אותיות לטיניות ל upper case.

אם אנו באים מרקע של חלונות, אולי כדאי להציג את ה PATH בצורה יותר מוכרת, ב \"Windows format\"?     😉

echo $PATH | tr \':\' \'\\n\' | tr \'a-z\' \'A-Z\' | tr \'/\' \'\\\\\' | sed -e \'s/^/C:/\'
הנה התוצאה:

לא להיבהל אם הפקודה עמוסה בסימנים. חתכו בראש את ה pipes ועברו שלב-שלב: אלו שלבים פשוטים.
את ההתחלה אתם כבר מכירים. עבור התו \\ היינו זקוקים ל escaping (\\\\). את ההוספה של :C בתחילת כל שורה לא יכולתי לעשות בעזרת tr: מכיוון ש tr מחליפה כל סימן בסימן אחר (או כלום) – אך היא לא יכולה לטפל במחרוזות ארוכות יותר \":C\". הקלידו man tr בשורת הפקודה כדי ללמוד על כמה אופציות נוספות שיש לה (בעיקר זיהוי סימנים טוב יותר או העלמה יעילה של תוים – החלפה בכלום).
כש tr הפשוטה לא מספיקה – עוברים ל sed (קיצור של stream editor).

sed היא פקודה רבת-עוצמה המאפשרת חופש פעולה רב מאוד. במקרה הזה השתמשתי בה בכדי לבצע החלפת טקסט דומה ל tr. הפרמטר e- אומר שאני רוצה לעבוד עם sed script (מה שבמרכאות) והפתיחה באות s היא פקודה ב sed script בפורמט \"/s/regex/replacement\". כלומר: ה regex הוא ^ (תחילת שורה) וההחלפה היא המחרוזת \":C\". / הם רק separators בין הפרמטרים.

צחקנו קצת – יופי.

הוספה ל path
נניח ואנו רואים את ה path ולא מרוצים ממנו. אנו רוצים להוסיף לו את התיקיה: usr/local/games/2048/ כדי שהמשחק (בגרסה ה shell-ית שלו) יהיה זמין לנו להפעלה מכל מקום ובכל רגע.

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

כדי להוסיף למשתנה path יש לכתוב:

PATH=$PATH:/usr/local/games/2048

תחביר דומה ל x = x + n. שימו לב שאנו זקוקים prefix של $ בכדי לקרוא את הפרמטר PATH – אך לא כדי לכתוב לתוכו.

יש לנו בעיה: לאחר login מחדש עם ה shell – ה PATH יחזור למצבו הקודם.
הסיבה: בכל התחברות מחדש עם SSH – רץ סקריפט שדורס את משתנה ה PATH.

מה נעשה? נדרוס אותו עוד פעם בעצמנו.

אתנחתא קלה

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

ובכן, הכל בסדר בלינוקס. לינוקס פשוט – גלויה יותר.
גם בחלונות רצים scripts להגדרת הסביבה בכל פתיחה שלה (יהיה זה PowerShell, cmd, או Connect-WSMan) – פשוט מערכת ההפעלה מחביאה פרט זה מהמשתמש. כל script כזה הולך למקום בו שמורים ה environment variables ויוצר אותם בסביבה. אפשר בקלות רבה לממש לוגיקה דומה בלינוקס: קובץ משתנים וסקריפט שנטען לכל משתמש ויוצר את המשתנים הללו בסביבה.

הדרך המקובלת בלינוקס היא פשוט להוסיף עוד שורה ל script האתחול.
בהפעלה של ssh – רצים כמה סקריפטים:

  1. etc/profile/ – סקריפט אתחול לכלל המשתמשים
  2. profile./~ – סקריפט אתחול למשתמש, שבעקרון קיים בשביל תאימות-לאחור (אך בהחלט אפשר להשתמש בו)
  3. bash_profile./~ סקריפט שרץ בזמן login של המשתמש
  4. etc/bash.bashrc/ סקירפט אתחול לכלל המשתמשים בעת הפעלה של bash ב interactive mode (קרי טרמינל או SSH).
  5. bashrc./~ סקריפט אתחול למשתמש בעת הפעלה של bash ב interactive mode.
רשימת הסקריפטים שנטענים תהיה שונה אם עובדים עם shell אחר, למשל ksh (קיצור של kornShell).
בד\"כ עורכים את bashrc/~ (כלומר: בתיקיית הבית של המשתמש), אם רוצים להשפיע על המשתמש הבודד ב bash.
אם מדובר על משתני סביבה שיהיו בשימוש עבור איזה תהליך שרץ עבור המשתמש, בלי שהוא מחובר אינטרקטיבית – יש לערוך את profile./~ בכדי לקבל את התוצאה הרצויה.
איך עושים זאת בצורה פשוטה מקוצרת [ב]?
echo \'PATH=$PATH:/usr/local/games/2048\' >> ~/.bashrc

\"<<" עובד בדיוק כמו שעובד בחלונות (הוספה לסוף קובץ),  כך שלא משנה מה יש בקובץ – הוספה לסופו היא בטוחה למדי (כלומר: יחסית. כל מה שיכול להשתבש – אכן ישתבש).

\"Unix is user-friendly. It just isn\'t promiscuous about which users it\'s friendly with.\" – \"Steven King, Software Archaeologist\"

עוד טיפ קטן: לפעמים אתם רוצים לדעת איפה executable יושב: אולי חסרות לו הרשאות, ואולי לכם הוא זמין ב PATH ולחברכם לא – אבל אתם לא רוצים לזרוק עליו עשרה paths שאתם משתמשים בהם. פקודת which מחפשת ב PATH (ללא כניסה ל symbolic links) אחר executables ומדווחת על מיקומם:

סיכום

בחיי שרציתי לדבר על ניהול קבצים! ניהול הרשאות עם chmod, חיפוש עם find, אולי symbolic links, אולי משהו נוסף.
אבל – קצת נגררתי. אני רוצה לכסות את הנושאים בהם אני נוגע בצורה מעמיקה, כך שמי שקורא יבין ולא רק \"ידע\" – אבל בלינוקס זה אומר כנראה שיש הרבה מאוד מה ללמוד.

נראה לי שמשיך עוד קצת, עד שימאס לי או שאראה (ע\"פ השימוש) – שלכם הקוראים זה נמאס.

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

[א]  קיצור של make file system

[ב] הדרך הפשוטה להוסיף שורה בסוף הקובץ הוא לפתוח אותו ב editor כלשהו ולראות בעיניים מה עושים (אלא אם עושים זאת בסקריפט).

ה UNIX WAY אומרת: Clarity is better than cleverness. בטוח שהרבה כותבי סקריפטים בלינוקס לא הולכים לפי כלל זה. הנה דוגמה לסקריפט למעלה כשהוא \"מתוחכם\", אך לא קריא (לטעמי):

echo $PATH | tr \':/a-z\' \'\\n\\\\A-Z\' | sed -e \'s/^/C:/\'