צעד אחר צעד: הקמת תשתית לטיפול בלוגים מבוססת ELK (חלק ראשון)
כאשר מדברים על טיפול בכמויות גדולות של מידע (“ביג-דאטה”), אחת הדוגמאות הנפוצות היא טיפול בלוגים. הסיבה היא שטיפול בלוגים משלב ביחד כמה אתגרים: יש הרבה מאד “event-ים” (הודעות לוג במקרה הזה), קצב ההגעה שלהם גדול (אפליקציות כותבות הרבה הודעות לוג), אנחנו רוצים לנתח אותם בצורות שונות שכוללות בין היתר גם חיפוש full text search על מחרוזות שונות כדי למצוא הודעות שמעניינות אותנו, וגם ביצוע אגרגציות על שדות שונים שיש בהודעת הלוג. למעשה, אפשר להגיד שמדובר פה בשילוב של לא מעט תחומים, מה שהופך באמת את נושא הלוגים לנושא “חם” יחסית.
כאשר מסתכלים על עולם הפתרונות לבעייה הזאת, אחד המוצרים העיקריים המתחרים בקטגוריה הזאת הוא ה- “ELK stack”. כאשר, בפועל מדובר בשילוב של שלושה מוצרים: Elasticsearch, Logstash ו-Kibana.
בפוסט הזה אני אראה איך ניתן להרים תשתית לוגים שלמה וסקלאבילית, שכוללת קבלת מידע, עיבודו והכנסתו לאינדוקס טקסטואלי. בחלק הבא, אני אראה גם איך מקנפגים ממשק משתמש שיאפשר שליפות נוחות מול התשתית שנקים.
דיזיין כללי
הפיתרון שלנו יורכב ממס’ חלקים, כפי שאפשר לראות בשרטוט הכללי הבא:
תחילה, יש לנו את האפליקציות השונות שלנו שיכתבו לוגים לתוך תשתית ה- logging שנפרוס. אנחנו נרצה לעשות איזושהי סטנדרטיזציה של צורת השליחה, ולכן בדיזיין שלנו נעשה את זה באמצעות סרביס שאחראי על כתיבת ההודעות בפורמט המתאים ל- RabbitMQ שנתקין. RabbitMQ הוא message broker, שישמש אותנו להעברת ההודעות בין ה- service שאנחנו מחצינים כלפי האפליקציות לבין הרכיב שבסוף אחראי לקרוא משם את ההודעות, לעשות עליהם עיבוד כלשהו (במקרה שלנו זה יהיה עיבוד די בסיסי) ולהכניס אותם למעשה ל-Elasticsearchשבמקרה הזה ישמש בתור בסיס הנתונים שלנו לאכסון ההודעות.
בסוף, יש לנו את משתמש הקצה, הבנאדם המסכן שצריך לדבג משהו תוך הסתמכות על הלוגים. הוא יעבוד מול ממשק גרפי, שנקרא Kibana, שמאפשר יצירת dashboards שמתשאלים מידע שנמצא ב- Elasticsearch ועבודה מולם.
ככה בגדול נראה הפיתרון. תשימו לב, שעל אף שהתרחיש שאני אדבר עליו בפוסט הזה הוא “לוגים”, הפיתרון הזה (הדיזיין והפלטפורמות שבהם אנחנו נשתמש) יכול להתאים לתרחישים מגוונים נוספים שדורשים אינדוקס מידע לטובת חיפוש שמשלב full text search ואגרגציות.
ועכשיו אחרי שסקרנו את הדיזיין הכללי, נתחיל בשלבים להתקנת התשתיות שישמשו אותנו ולקונפיגורציה שלהן.
RabbitMQ
בשלב הראשון, נרצה להתקין RabbitMQ. אבל לפני שנגיע לאיך מתקינים (שזה די פשוט), נסביר חלק מהמונחים שמשמשים אותנו בעבודה מול RabbitMQ. אני ממליץ בחום שלא להסתפק בהסבר שלי ולקרוא את ה- tutorial המוצלח שזמין באתר שלהם.
RabbitMQ הוא כאמור message broker שמאזן מאד טוב בין ביצועים גבוהים לבין נוחות.
Message broker הוא רכיב שהתפקיד שלו זה להעביר הודעות מהצד השולח (Producer) לצד המקבל (Consumer). מכאן, אפשר להבין שהישות הבסיסית שעליה אנחנו מדברים כשאנחנו עובדים עם RabbitMQ היא “הודעה”.
הודעה יכולה להיות למעשה כל רצף בינארי שאנחנו רוצים. עם כי, בפועל די נפוץ להיצמד לפורמטים טקסטואליים, כמו JSON או XML.
כאשר אפליקציה רוצה לקבל הודעה, היא למעשה פותחת connection מול RabbitMQ, באמצעות ה- connection הזה היא פותחת channel (שעליו אפשר לחשוב כמו “ערוץ שיחה” לוגי) ומאזינה בפועל ל- Queue. זה יכול להיות תור שנוצר דינמית (עוד רגע נדבר מול מה הוא נוצר), או תור שהיה קיים כבר קודם והצטברו בו הודעות.
כאשר אפליקציה רוצה לשלוח הודעה, היא יכולה לשלוח אותו לתור ספיציפי (שעליו אפליקציה אחרת אמורה להאזין), אבל זה לא מקובל. בד”כ, כאשר אפליקציה תשלח הודעה היא תשלח אותה ל- Exchange.
ה- Exchange הוא למעשה “גשר” בין מי שרוצה לשלוח הודעה למי שרוצה לקבל הודעה והוא זה שמאפשר לנו למעשה לפצל הודעה שנשלחת פעם אחת כך שהיא תגיע למס’ אפליקציות, או לאפשר לאפליקציה להירשם רק להודעות מסוג מסויים שמעניין אותה וכו’.
למשל, בדוגמא שלנו אנחנו נשלח הודעות log. כאשר את ההודעות log לא נשלח ישירות ל- queue שעליו מאזין logstash, הרכיב שבפועל יטפל בהודעות שלנו. את ההודעות נשלח ל- Exchange שייקרא, למשל LogMessages. אנחנו יכולים להגדיר אפס או יותר תורים שהם binded ל-exchange הזה. כלומר, לשכפל את ההודעה לאפס או יותר תורים. במקרה שלנו, למשל, נגדיר תור שיהיה binded ל- exchange הזה שאליו יגיעו ההודעות ומשם יימשכו לעיבוד ע”י logstash. אם מחר נחליט שאנחנו רוצים שעוד אפליקציה תקבל את ההודעות הללו (למשל, שהם ייכנסו ל- Splunk) – לא נצטרך לכתוב את ההודעה פעמיים. רק נגדיר עוד queue שיהיה גם הוא binded ל- exchange הזה ויקבל למעשה שכפול של כל ההודעות, באופן בלתי תלוי בתור של logstash, ויאפשר גם לאפליקציה האחרת להאזין ולקבל הודעות.
המנגנון של ה- Exchnage אפילו יותר מתוחכם מזה. עד עכשיו, מה שהזכרנו זה רק היכולת “לשכפל” הודעות למס’ תורים, כלומר exchange מסוג Fanout. אנחנו יכולים גם להגדיר exchange שמפצל הודעות לתורים לפי topic, כלומר לפי routing key.
ה- routing key הוא string אופציונאלי שמוצמד להודעה, ומורכב למעשה מאוסף של פרמטרים מופרדים בנקודות (הפרמטרים הם מה שנגדיר שיהיו). למשל, routing key של אפליקציה יכול להיראות משהו בסגנון הזה: MyAppName.ModuleName.Critical או MyAppName.OtherModuleName.Verbose . כאשר, במקרה הזה כשתור הוא binded ל-exchange, הוא יכול לבחור להיות binded רק לסוג מסויים של הודעות. למשל, ל MyAppName.*.Verbose [כל הודעות ה- verbose של MyAppName, בלי תלות בשם הרכיב באפליקציה] או MyAppName.# [כל ההודעות מ- MyAppName] או *.*.Critical [כל ההודעות הקריטיות] וכו’.
אחרי שהבנו מה נותן לנו RabbitMQ, בואו נתקין אותו. כדי להתקין את RabbitMQ, צריך קודם כל להתקין את Erlang, השפה וה- framework שעל בסיסם מפותח RabbitMQ. ניתן להוריד אותם מהאתר הרשמי, כאשר יש installer-ים עבור windows. משתמשי לינוקס יכולים להתקין באמצעות ה- package manager. לאחר שמורידים ומתקינים את Erlang, יש להוריד ולהתקין גם את RabbitMQ. ההתקנה פשוטה והיא למעשה כמה לחיצות על Next.
אחת הסיבות שאני אוהב את RabbitMQ היא ממשק הניהול הנוח שלו. כדי להפעיל את ממשק הניהול הזה, צריך למעשה להפעיל את הפלאגין שכולל אותו. כדי לעשות זאת, נפתח את RabbitMQ Command Prompt (יופיע לנו ב- Start Menu לאחר ההתקנה) ונריץ את הפקודה:
rabbitmq-plugins enable rabbitmq_management
לאחר ההרצה, ממשק הניהול יהיה זמין בפורט 15672 (עד כדי לאפשר ב- firewall קודם לכן). כלומר, ניתן מהמכונה עצמה לגלוש ל http://localhost:15672 ולהיכנס לממשק ניהול עם היוזר והסיסמא guest / guest. עם זאת, לא ניתן יהיה להתחבר עם השם משתמש והסיסמא האלה מרחוק.
כדי לאפשר התחברות מרחוק, ניצור יוזר אדמיניסטרטיבי על גבי המכונה, דרך אותו ה- command prompt שפתחנו קודם, באמצעות הפקודות הבאות:
rabbitmqctl add_user test test
rabbitmqctl set_user_tags test administrator
rabbitmqctl set-permissions -p / test ".*" ".*" ".*"
לאחר הקשת הפקודות האלה, נוכל להתחבר מרחוק (למשל ע”י גלישה במקרה שלי ל http://192.168.1.104:15672/) עם היוזר test והסיסמא test. כמובן, שבסביבת production נרצה לדאוג שב- firewall לא נאםשר גישה בפורטים של RabbitMQ משרתים שאינם שרתי האפליקציה שלנו.
עכשיו, כל מה שנשאר לנו לעשות מבחינת קונפיגורציית RabbitMQ, זה להגדיר את ה- Exchange שלנו ואז להגדיר את התור. נתחבר לממשק הניהול, ונלך לטאב Exchanges:
נמלא את הפרטים בחלק של ה- Add a new exchange בתחתית העמוד, בהתאם לתמונה המצורפת:
לאחר שנלחץ על Add exchange, נראה אותו ברשימה. לאחר מכן, נעבור לטאב Queues בראש העמוד ונייצר Queue חדש:
ונלחץ על Add queue כדי להוסיף. בשלב הזה, יש לנו ביד תור ו- Exchange – אבל אין קשר בינהם. נרצה להוסיף binding כזה שיעביר את כל ההודעות שמגיעות ל- exchange שנקרא LogExchange שיצרנו קודם לתור שיצרנו עכשיו.
מכיוון שהגדרנו את ה- Exchange כ- topic, אנחנו יכולים להעביר pattern מסויים שנקבל הודעות שה- routing key שלהם מתאים ל- pattern הזה. בתור התחלה לא נעשה את זה, ונרשם לקבל את כל ההודעות.
לצורך כך ניכנס לעמוד של התור, ונפתח את הטאב של Bindings ונמלא את הפרטים לפי הצילום:
לאחר שמילאנו את הפרטים, נלחץ על Bind.ונראה שזה התווסף לרשימת ה- Bindings שאנחנו רואים (הרשימה ריקה בצילומסך שלמעלה, טרם הוספת ה- binding). למעשה, מה שהגדרנו פה זה שכל הודעה שמגיעה ל- Exchange שנקרא LogExchange תגיע לתור הזה ותמתין שמישהו ייקח אותה. מי זה המישהו הזה? במקרה שלנו, מדובר ברכיב בשם Logstash שבהמשך נגיע ללהתקין ולקנפג אותו.
Elasticsearch
בסוף התהליך, אנחנו רוצים לשמור את המידע של הלוגים ב-DB מסוג כלשהו. הדרישה העיקרית שלנו בעבודה עם לוגים היא יכולת חיפוש טקסטואלי, כלומר full text search. אנחנו רוצים לחפש על ההודעה, או על שדות ספיציפיים בצורה חופשית. למשל, לחפש את כל ההודעות שמופיעה בהם NullReferenceException או לחפש הודעות שמופיעה בהם שם משתמש, או שם של קובץ קוד שלנו – בקיצור, חיפוש טקסטואלי רחב. עם זאת, כמובן שחיפוש טקסטואלי לא מספיק לנו. אנחנו רוצים יכולת לעשות אגרגציות מסוגים שונים על מידע (כדי לקבל למשל סטטיסטיקות על רכיבים בעייתיים). ושאילתות גם יותר כלליות. את כל אלה מספק לנו Elasticsearch.
אפשר לחשוב על Elasticsearchכעל Document DB (בדומה, למשל, למונגו) שההתמקדות שלו היא חיפוש טקסטואלי. הוא מבוסס על מנוע חיפוש טקסטואלי בשם Lucene (שעליו מבוסס גם מתחרה עיקרי שלו, גם הוא אופן סורסי, Solr) ומציע למעשה ממשק שמאפשר לשמור documents ואז לבצע עליו חיפושים מסוגים שונים. הוא מותאם ל- scale-out, ויש לו גם יכולת בדומה לרוב המנועים המבוזרים לרפליקציה פנימית שנותנת מענה במקרה של נפילת node אחד או יותר (בהתאם לקונפיגורציה).
מונחים בסיסיים ב- Elasticsearch
כאשר אנחנו מתקינים Elasticsearch, אנחנו למעשה נתקין node בודד. בסופו של דבר, node זה שרת שמריץ service של Elasticsearch שיודע לעשות עבודה, וגם לדבר עם nodes אחרים שפועלים במקביל אליו בשרתים אחרים. כל ה- nodes מאוגדים ביחד תחת ישות לוגית שהיא ה- cluster.
ברגע נתון, יש node שהוא ה- master – הוא זה שאחראי על הניהול של ה- cluster. אם ה- master נופל, אז נבחר עבורו מחליף ע”י ה- nodes האחרים (מבין שרתים שמוגדר להם שיכולים לשמש כ- master).
מרבית ה- nodes ב- cluster של Elasticsearch משמשים כ- data nodes. כלומר, הם מחזיקים אצלם חלק מהמידע שמאוכסן ב- cluster, יודעים לקבל מידע חדש, ולשרת שליפות שונות על מידע קיים.
מה זה המידע שהם מחזיקים? אז בסופו של דבר, היחידה הבסיסית ביותר היא document. מדובר למעשה באובייקט JSON, שמכיל שדות שונים וערכים שונים (בכל סכימה שהיא, לא נדרש שום סוג של הצהרה על הסכימה מראש). כברירת מחדל, כל השדות מתאנדקסים וניתן לחפש על כולם. הזיהוי של ה- data types מתבצע גם הוא לפי הערך הראשון שהוכנס (אבל יש API שלם שמאפשר שליטה על כל אחת ואחת מההגדרות האלה).
כל document נמצא למעשה כחלק מ- “index”. ב- cluster יכולים להיות אינדקסים רבים, והם נותנים חלוקה בין documents שהיא גם לוגית וגם פיזית. כלומר, אפשר להפריד אינדקסים למטרת הפרדה לוגית (אני מפריד את המערכת logging שלי מהמערכת של החיפוש על תוכן האתר) וגם למטרת הפרדה פיזית של מידע (יש לי לוגים של שנה אחורה, כאשר אני רוצה שהם יתאכסנו בקבצים שונים, אולי תהיה להם מדיניות שכפול שונה, אולי אני רוצה אפשרות למחוק את חלקם – ואז אני משיג באמצעות ההפרדה הפיזית יותר שליטה).
אינדקס יחיד יכול להכיל כמות נתונים גדולה, שפוטנציאלית יותר גדולה גם מכמה שנכנס ל- node בודד. בנוסף, Elasticsearch רוצה לתת מענה ל- scaling (רוצה לשפר ביצועים או להגדיל נפח? תדחוף עוד כמה שרתים ל- cluster) ולשרידות (מה קורה אם node נופל, או סתם יורד לשדרוג). את זה משיגים באמצעות שני מונחים חשובים: Shards ו- Replica. כל אינדקס מחולק פנימית למס’ shard-ים, שיכולים לשבת על nodes שונים. כלומר, שחלק מהמידע של האינדקס נמצא ב- node א’, חלק ב- node ב’ וכו’. ואז בשליפות העבודה מתחלקת על יותר שחקנים, מה שמאפשר ביצועי שליפות טובים יותר ביחס לאם זה היה נופל על כתפיו (ובעיקר, על ה- spindles) של שרת בודד.
כמובן, שאנחנו לא רוצים שיהיה העתק בודד לכל Shard – כי אז זה אומר שברגע שנפל node שמכיל את ה- shard הזה, השלמות של האינדקס כולו נפגעת. ולכן, יש לנו את מונח ה- Replica: כמה עותקים של אותו Shard נשמרים ב- cluster.
הן החיפוש, והן הניהול של cluster מבוסס Elasticsearch מתבצעים באמצעות REST API שנכיר אותו באופן בסיסי בהמשך. בכל אופן, בתהליך הזה אנחנו לא נידרש כמעט לנגיעה ב-API בהקשרי חיפוש, כי בהמשך “נלביש” רכיב (Kibana) שיספק לנו את ממשק המשתמש ואת כל יכולות החיפוש.
עכשיו, אחרי שאנחנו מבינים באופן בסיסי מה זה Elasticsearch, ניגש ללהתקין אותו. לא צריך להיות מומחה Elasticsearchכדי להתקין סביבה שכוללת מס’ nodes שונים. אני אראה כיצד עושים את זה, ונעשה את ההתקנה במקביל – גם על מכונת Windows Server 2016 וגם על מכונת Ubuntu 16.04.1.
התקנה על Windows
לפני שנוכל להתקין Elasticsearch, נצטרך להתקין JRE (סביבת הריצה של Java) מהלינק הזה (שימו לב להוריד את גרסת ה- 64 ביט). אחרי שהתקנו, נוריד את ה- ZIP של Elasticsearchמעמוד ההורדה (אני מתייחס לגרסא 5.0). נחלץ מה- ZIP שהורדנו את התיקייה, ונשים אותה במקום כלשהו. בדוגמאות אני אניח שיש לנו עכשיו תיקייה C:\elasticsearch-5.0.0. דבר ראשון, נרצה להתקין את ה- service של Elasticsearchעל המכונה שלנו (כדי שההפעלה לא תהיה תלוייה בהרצת batch על ידנו…).
לטובת זאת, נצטרך לערוך תחילה את הקובץ C:\elasticsearch-5.0.0\config\jvm.options ולהוסיף אליו את השורה הבאה: –Xss1m, או שנקבל בעת ההתקנה את השגיאה “thread stack size not set”. הנה צילומסך של הקובץ לאחר הוספת השורה:
לטובת התקנת הסרביס נריץ את הפקודות הבאות מ- cmd שמורץ כ- administrator:
cd c:\elasticsearch-5.0.0\bin
elasticsearch-service.bat install
כדי שנוכל להפעיל את ה- service, נצטרך גם להגדיר environment variable בשם JAVA_HOME. נעשה את זה ע”י הרצת הפקודה הבאה מ- PowerShell שרץ כ- administrator:
[Environment]::SetEnvironmentVariable("JAVA_HOME", "C:\Program Files\Java\jre1.8.0_111", "Machine")
לפני שנפעיל את ה- service שהותקן, נרצה לעשות עוד כמה שינויי קונפיגורציה קטנים בקובץ C:\elasticsearch-5.0.0\config\elasticsearch.yml. חלק מהשינויים האלה הם למעשה לשנות שורות שמסומנות בהערה, ולטובת הנוחות אני פשוט כותב את כל השינויים כבלוק אחד (ולא ב- section-ים שהם מופיעים בקובץ למטרת סדר):
cluster.name: logs-cluster
node.name: node-1
node.master: true
node.data: true
network.host: 0.0.0.0
http.cors.allow-origin: "*"
http.cors.enabled: true
נשמור את השינויים הללו בקובץ הקונפיגורציה, ולאחר מכן נעשה start ל- service שנקרא Elasticsearch5.0.0. כדי לראות שזה עובד, ננסה לגלוש למכונה שלנו בפורט 9300 (עדיף מבחוץ עם IP חיצוני של המכונה, ולא עם 127.0.0.1 כדי לאמת שאתם מצליחים לתקשר משרתים אחרים וה- firewall לא עומד באמצע – כי נזדקק לזה בהמשך). ככה למשל זה נראה אצלי:
מזל טוב. יש לנו Elasticsearchמותקן.
ניהול Cluster של Elasticsearch
הממשק ש- Elasticsearchחושף כלפי חוץ הוא REST API. אנחנו יכולים לעבוד מולו, עם כלים כמו Postman ולתשאל את ה- REST API, אבל זה קצת פחות כיף.
גם אם עובדים עם cluster קטן של שני nodes בלבד, עדיין נעדיף ממשק נוח. בד”כ, הממשק שבו אני משתמש הוא ElasticHQ. ההורדה וההתקנה קלות מאד, והשימוש אינטואיטיבי ביותר. עם זאת, במדריך הזה הראיתי עד עכשיו איך מתקינים את Elasticsearchבגרסא 5.0, הגרסא העדכנית ביותר שיצאה. גרסא זו כללה כמה שינויי API, שגרמו לכך ש- ElasticHQ (וכלי ניהול אחרים) צריכים לעשות התאמות על מנת לעבוד. נכון לזמן שבו אני כותב את הפוסט הזה, ההתאמות הללו עוד לא התבצעו, עם כי יש להניח (מניסיון העבר) שיתבצעו בקרוב.
התקנה על Linux
אחרי שהתקנו node אחד על Windows, נתקין את ה- node השני שלנו על לינוקס. בצורה הזאת נרוויח שני דברים: יהיה לנו node נוסף שיעזור לנו לשרידות של המידע שלנו ולביצועי החיפוש (כפי שהוסבר קודם), וגם אני אוכל להראות לכם את השלבים המקדימים להתקנה שעשינו קודם – על לינוקס.
אז דבר ראשון, צריך להתקין java באמצעות הרצת הפקודה הבאה:
sudo apt-get install default-jre
לאחר מכן צריך להתקין את ה-package העדכני שניתן להורדה מהעמוד הזה. אנחנו רוצים להוריד למעשה את ה- deb package. אחרי שהורדנו אותו, נריץ מה- shell את הפקודה הבאה (מהתיקייה המתאימה):
sudo dpkg -i elasticsearch-5.0.0.deb
כעת, נרצה להגדיר את Elasticsearchלהיות זמין כ- service כך שנוכל להגדיר שיעלה אוטומטית עם המכונה. נעשה זאת באמצעות הפקודה הבאה:
sudo systemctl enable elasticsearch.service
הגדרה נוספת שאנחנו צריכים לעשות, היא להגדיל את כמות ה heap-ים הזמינים ב-JVM. נעשה זאת באמצעות:
sudo sysctl -w vm.max_map_count=262144
עכשיו, נצטרך ללכת ולערוך את קובץ הקונפיגורציה של elasticsearch. הקונפיגורציה שנגדיר תהיה למעשה די זהה לקונפיגורציה שהגדרנו קודם עבור ה- Windows.
כדי לערוך אותה, נכניס את הפקודה הבאה:
sudo nano /usr/share/elasticsearch/config/elasticsearch.yml
בואו נסתכל מה הערכים המעניינים (חלקם כבר קיימים, וצריך “למזג” אותם לקונפיגורציה הדיפולטית ע”י שינוי הערך):
cluster.name: logs-cluster
node.name: node-2
node.master: true
network.host: 0.0.0.0
http.port: 9200
discovery.zen.ping.unicast.hosts: ["192.168.1.105"]
בואו נעבור על הפרמטרים, שחלקם כבר ראינו בהתקנה שעשינו קודם על שרת ה- Windows ונראה מה מעניין פה:
- cluster.name: אנחנו רוצים להגדיר את אותו שם cluster, כי אנחנו רוצים למעשה לצרף את ה- node החדש ל-cluster עם ה-node שהקמנו קודם על שרת windows
- node.name: זה השם שניתן ל- node החדש, פשוט נמשיך עם מוסכמת השמות שהתחלנו קודם.
- node.master: פה אנחנו מגדירים האם ה- node יכול לשמש בתור master. זה לא אומר שהוא יהיה ה- maser בפועל, אלא זה אומר שהוא משתתף בתהליך הבחירה (election) במקרה של כשל של ה- master הקיים, ויכול לקבל את התפקיד על עצמו
- network.host: אנחנו אומרים להאזין במקרה הזה על כל האינטרפייסים בפורט שמופיע ב- http.port. כמובן שהיינו יכולים להגדיר IP ספיציפי
- discovery.zen.ping.unicast.hosts: פה אנחנו יכולים להעביר לו שמות או IP-ים של node-ים אחרים, ופה שמתי לו את ה- IP של שרת ה- windows שהתקנו עליו Elasticsearchקודם לכן. ל- ElasticSearch יש מנגנון discovery מתוחכם. כדי לוודא שהקונפיגורציה במדריך הזה עובדת בדיוק כפי שהיא אנחנו מציינים פה ספיציפית את ה- IP של השרת השני, אבל כמובן שבסביבת production שבה יש הרבה שרתים, לא צריך לציין את כולם (ובכלל, יש מנגנון discovery מבוסס unicast-ים וכו’).
זהו, סיימנו את שלב הקונפיגורציה. עכשיו מה שנשאר לעשות זה רק להפעיל את ה- service:
sudo systemctl start elasticsearch
ונראה את הסטאטוס שלו באמצעות הפקודה:
sudo systemctl start elasticsearch
ואת הסטאטוס נוכל לראות באמצעות הפקודה הבאה:
אם נגלוש עכשיו לכתובת של השרת Elasticsearchשהתקנו בפורט 9200 נצפה לקבל את הפלט הבא:
{
"name" : "node-2",
"cluster_name" : "logs-cluster",
"cluster_uuid" : "g2In6mvAT3OXFmqTOemWPw",
"version" : {
"number" : "5.0.0",
"build_hash" : "253032b",
"build_date" : "2016-10-26T04:37:51.531Z",
"build_snapshot" : false,
"lucene_version" : "6.2.0"
},
"tagline" : "You Know, for Search"
}
בדיקת סטאטוס של cluster
יופי. התקנו שני node-ים של Elasticsearch. עכשיו אנחנו רוצים לראות שבאמת הכל עובד תקין, הם מדברים אחד עם השני, רואים שהם באותו ה- cluster וכו’, ועל הדרך נראה איך ניתן לעבוד עם ה- REST API של ElasticSearch.
כל מי שעובד או עבד בעבר עם REST API מכל סוג שהוא צריך להכיר את Postman. הוא זמין גם בתור פלאגין לכרום, וגם (ולטעמי הרבה יותר נוח) בתור אפליקציה חלונאית. אחרי שנתקין ונריץ את Postman נרצה לפנות ל-API של ElasticSearch בבקשת GET אל /cluster/Health (המיקום של האנדרסקור קצת השתבש פה, תסתכלו בצילום המסך):
סימנתי בצהוב את הכתובת שאליה צריך לפנות ואת שני השדות הכי מעניינים: ה- status שאמור להיות green (אחרי שהוספתם והרצתם שני nodes לפי הגדרות הברירת מחדל) והשדה של number_of_nodes שאמור להראות 2 (כי הוספנו node אחד על windows ואחד על לינוקס).
אם אנחנו רוצים לראות קצת יותר פירוט על ה- nodes השונים, אנחנו יכולים לשלוח בקשת GET ל- _nodes ולראות פירוט רב יותר:
Elasticsearchas a Service
אם אתם לא רוצים להתקין בעצמכם Elasticsearchעל מכונות on-prem, אתם יכולים להשתמש באחד מבין השירותים השונים שמציעים Elasticsearchas a service ב- cloud. למעשה, החברה שמאחורי Elasticsearchמציעה בעצמה את שירות Elastic Cloud שמציע בדיוק את זה מעל התשתית של אמזון. יש גם עוד לא מעט מתחרים אחרים, חיפוש של “Hosted Elasticsearch” בגוגל מניב לא מעט תוצאות של שירותים שנותניםי בדיוק את זה.
Logstash
אז אמרנו כבר שאנחנו רוצים לאכסן מידע על לוגים, ושהשכבה שתשמש אותנו בתור database בסיפור הזה זה Elasticsearch– כדי שנוכל להנות מיתרונות החיפוש הטקסטואלי.
בתכלס, מה שנרצה לעשות זה לייצג כל הודעת לוג בתור document, בפורמט json, כאשר הפורמט של ה- json הזה יכלול מספר שדות metadata קבועים, ועוד מספר כלשהו של שדות שרלוונטיים לאפליקציה שכותבת את הודעות הלוג (למשל, אפליקציה שמעבדת קבצים תוכל לכתוב את נתיב הקובץ שההודעת לוג נוגעת אליו בתור שדה, אפליקציה שמשרתת משתמשים תוכל לשים את שם המשתמש בתור שדה וכו’).
בשרטוט שהוצג בתחילת הפוסט אחנו רואים שה-front מול האפליקציות הוא למעשה איזשהו web service כלשהו (שבתכלס, לא באמת משנה האם הוא WCF, ASP.NET WebAPI, NodeJS או כל דבר אחר) שמקבל את הודעות הלוג וכותב אותם ל- RabbitMQ.
שאלה לגיטימית וטובה היא למה לא לכתוב את ההודעות הללו ישירות ל- Elasticsearch? מסיבה פשוטה – אנחנו רוצים לאפשר רמה נוספת של גמישות. למשל, מה נעשה אם מחר נרצה לשלב אפליקצייה שיודעת לכתוב לוגים רק ל- event log של windows? או אפליקציה ששולחת syslog-ים לכתובת מסויימת וזה כל הלוגים שהיא יודעת להוציא? או אולי יש לנו לוג טקסטואלי של שרת apache שנרצה להזרים גם לתשתית הלוגים שלנו?
המשותף לכל הדברים האלה, הוא שאנחנו נצטרך משהו שיידע לקחת אותם מאיזשהו input, לעשות עליהם איזשהו משחק של המרה כדי לנרמל אותם לפורמט שבסוף אנחנו רוצים לשמור ל- Elasticsearchואז יידע להכניס אותם ל- Elasticsearchעצמו.
על הדרך, אם הדבר הזה מטפל בלוג הלוגים שלנו, אז גם נרצה שהוא יידע אולי לא רק לשלוח ל- Elasticsearch. למשל, אולי נרצה לכתוב חלק מהלוגים האלה גם לאיזשהו קובץ טקסטואלי? לפלטר את הלוגים ועבור חלקם גם להריץ איזשהו סקריפט? אולי חלקם מייצגים באגים שאנחנו רוצים לפתוח ישר עבורם task ב- JIRA?
כלומר, אנחנו מבינים שה- front הזה של הסרביס שמאפשר לכתוב הודעות ב- push לפלטפורמת ה- logging שלנו הוא רק ממשק אחד להכנסת מידע, מתוך רבים פוטנציאליים אחרים. גם “לאכסן ב- Elasticsearch” היא פעולה אחת שאנחנו רוצים לעשות עם המידע – אבל לא בהכרח היחידה.
כדי לאפשר למעשה להתמודד עם כל התרחישים שמניתי קודם, נוכל לעשות שימוש בכלי בשם LogStash – וזאת הסיבה שכבר בשרטוט שציינתי קודם אנחנו מכניסים אותו כחלק אינטגרלי מה- pipeline שאנחנו בונים.
Logstash הוא כלי חינמי ואופן-סורסי שאפשר להריץ גם על windows וגם על linux. הוא מאפשר לנו לתת קונפיגורציה שמכילה למעשה את החלקים הבאים:
- input: מאיפה אנחנו מקבלים מידע – זה יכול להיות קובץ, RabbitMQ, או הרבה דברים אחרים
- output: לאן אנחנו כותבים את המידע – למשל, במקרה שלנו, ל- Elasticsearch
- filter / data manipulation: אנחנו יכולים לקבוע חוקים שמפלטרים את המידע לפי פרמטרים שונים, ועושים איתו מניפולציות כאלה ואחרות (הוספת שדה, מחיקת שדה, שינויי פורמט) ולמעשה עושים איזשהו branching של הלוגיקה שאנחנו מפעילים עליו.
logstash תומך בפלאגינים, כך שבנוסף לפיצ’רים המובנים שהוא מגיע איתם יש לא מעט פלאגינים שמתוחזקים ע”י הקהילה שמאפשרים מקורות input ו- output נוספים – ודברים שונים שאפשר לעשות עם המידע.
חשוב לשים לב ש- Logstash הוא stateless. כלומר, אין מניעה להריץ אותו ביותר משרת אחד ולבזר את הפעילות שלו – גם בהקשרי ביצועים וגם בהקשרי שרידות.
התקנת וקינפוג Logstash
את ההתקנה הפעם נעשה על שרת ה- Windows שלנו. תחילה נוריד את ה- ZIP, ונפתח אותו לתוך c:\logstash-5.0.1.
נייצר קובץ c:\logstash-5.0.1\config\logs-pipeline.conf. נדביק בו את התוכן הבא:
input {
rabbitmq {
host => "127.0.0.1"
subscription_retry_interval_seconds => 90
queue => "LogstashQueue"
threads => 2
passive => true
codec => "json"
}
}
filter {
date {
locale => "en"
match => ["EventDateTime", "YYYY-MM-dd-HH:mm:ss.SSS"]
target => "@timestamp"
add_field => { "debug" => "timestampMatched"}
}
}
output {
elasticsearch{
hosts => ["127.0.0.1"]
}
}
נשים לב שיש לנו בקונפיגורציה 3 חלקים:
- input: אנחנו מגדירים מקור מידע יחיד, שהוא ה- RabbitMQ. אנחנו מגדירים ל- Logstash להפעיל שני consumer threads על תור בשם LogstashQueue שהצהרנו עליו כבר (זאת המשמעות של passive, כלומר התור כבר קיים ואנחנו דאגנו לזה מראש).
- filter: בהודעת ה- JSON שנוציא מהסרביס שלנו, נרצה שיהיה שדה שמתאר את הזמן שבו קרה האירוע שעליו היה הלוג. בדיפולט, Logstash מתייחס לשדה @timestamp בתור שדה הזמן, ויש פורמטים מסויימים של תאריך ושעה שהוא מצפה לקבל. נניח, לטובת ההדגמה, שאנחנו רוצים שהוא יוציא את התאריך והשעה שלפיהם הוא שומר את הלוגים בשדה אחר (הלוגים נשמרים כברירת מחדל באינדקסים מופרדים לפי ימים ב- Elasticsearch, וכשנסתכל בהמשך על איך מתבצעים החיפושים וכו’, אז הזמן הוא מרכיב די משמעותי בפילטור) . נניח ששדה הזמן מופיע בהודעה (שהיא ב-JSON) בשם EventDateTime. אנחנו רוצים לשנות לו את השם ל- @timestamp וכמובן להתאים אותו לפי הפורמט שבו אנחנו כותבים. כל זה אנחנו מבצעים פה בחלק של ה- filter.
- output: אנחנו אומרים לו לכתוב ל – Elasticsearch שרץ על אותו השרת.
כדי להריץ את logstash ולראות שהקונפיגורציה שהכנסנו עובדת, נשתמש בפקודה הבאה שאותה נריץ ב- cmd:
c:\logstash-5.0.1\bin\logstash.bat -f c:\logstash-5.0.1\config\logs-pipeline.conf
הפלט שאנחנו אמורים לראות הוא משהו כזה:
איך בודקים שזה עובד?
כדי לראות שהכל עובד, נשלח הודעת בדיקה ב- RabbitMQ, נראה שלא מופיעות שגיאות ב- logstash (אם יופיעו שגיאות נראה אותם ב- console שבו אנחנו מריצים את logstash) ואז נראה שבאמת נוצר לנו אינדקס ב- Elasticsearch ושיש בו document.
- נגלוש לממשק של ה- RabbitMQ .בהנחה שזה באותו השרת שהתקנו עליו קודם, הממשק יהיה זמין בכתובת http://127.0.0.1:15672
- נלך למעלה לטאב Queues
- ניכנס לתור LogstashQueue שיצרנו קודם
- אנחנו אמורים לראות את המסך הבא:
כמו שאתם רואים, תחת ה- Consumers אנחנו רואים את שני ה- consumers שהעלה logstash.
קצת יותר למטה בעמוד, יש לנו חלק של Publish Message. נדביק בו את ההודעה הבאה:
{
"msg": "Hello World!",
"EventDateTime": "2016-01-01-13:23:00.999"
}
וכך זה אמור להראות:
ולסיום – נלחץ על Publish message.
נסתכל שוב ב- console שבו הרצנו את Logstash ונראה שלא התווספו שורות שגיאה. אם אין שורות שגיאה – אנחנו בכיוון הנכון.
עכשיו נרצה לאמת שאכן נוצר לנו האינדקס. לטובת זה ניכנס שוב ל- Postman ונריץ שוב GET כמו שרואים בצילומסך:
ניתן לראות שהאינדקס נוצר, והשם שלו הוא עם התאריך שאליו שייכת ההודעה (שפורסר מתוך השדה הרלוונטי בהודעה ע”י Logstash). כלומר, על אף ששלחתי את ההודעה בנובמבר 16, ההודעה נכנסה לינואר 16 כי היא מייצגת מידע ישן (על סמך התוכן שלה).
הגדרת Logstash בתור Windows Service
אחרי שעשינו את הבדיקה דרך command line (וכמובן כל שינוי קונפיגורציה שנעשה נוכל להרים instance של logstash שעובד מול הקונפיגורציה החדשה ב- cmd ולראות שהכל עובד טוב, ואיזה שגיאות יש) – אנחנו כמובן לא נרצה להשאיר את העבודה בתצורה של cmd שחייב לרוץ כל הזמן, אלא נרצה לתת ל- service manager לנהל את הסיפור הזה. לצערנו, Logstash לא כולל באופן מובנה התקנה כ- Windows Service כמו Elasticsearch. לשמחתנו, יש כלי בשם NSSM (Non-Sucking Service Manager) שמאפשר לנו בקלות להפוך את Logstash (ולמעשה הרבה דברים אחרים שכתובים ב- java) ל- WIndows Services.
תחילה, נוריד את הגרסא האחרונה של NSSM ונפתח אותה ל c:\nssm (שתחתיו יהיו התיקיות win32 ו- win64 שב-ZIP). נריץ את הפקודה הבאה: c:\nssm\win64\nssm.exe install ונמלא את הפרטים בחלון שייפתח כך:
נלחץ על Install service, ניכנס ל- services.msc ונעשה Start ל-service החדש שיצרנו (שייקרא logstash בדוגמה הזאת). כמובן, כדאי לעשות שוב את בדיקת ה- sanity שתוארה קודם כדי לראות שגם הסרביס עובד כמו שצריך.
סיכום ביניים של מה שיש לנו עד עכשיו
עד עכשיו מימשנו למעשה את ה- core של המערכת שלנו:
- יש לנו שני שרתי Elasticsearch ב- cluster מוכנים ומזומנים לקבל מידע ולאנדקס אותו
- יש לנו תור (RabbitMQ) שישמש אותנו כדי לקבל את המידע
- יש לנו רכיב שיודע לעבד מידע מהתור הזה (לא רק) ולהכניס אותו ל-Elasticsearch שהרמנו
מה שחסר לנו זה למעשה הממשקים. – הממשק האפליקטיבי, שיאפשר לאפליקציה לכתוב הודעות (למרות שכבר עכשיו היא יכולה לכתוב הודעות ל- RabbitMQ, אבל נעטוף את זה בממשק יפה יותר שידגים לנו גם התממשקות עם RabbitMQ) וממשק משתמש לטובת שליפות (Kibana). על אלה אכתוב בפוסט הבא בסדרה.