เริ่มต้นศึกษา Fluentd



วันนี้ผมจะมาเริ่มศึกษาลงลึกทีละส่วน โดยจะเริ่มจาก Fluentd ซึ่งเป็น Free Opensource Software สำหรับทำ Data Collector นั่นเองหรือจะเรียกว่า Log Collector ก็ได้อยู่นะครับ โดยเจ้า Fluentd เนี่ยทางบริษัทผู้ผลิตเคลมว่าสามารถรองรับระบบได้มากกว่า 125 ชนิดกันเลยทีเดียว [อ้างอิง] โดย Fluentd นั้นจะเก็บรวบรวม Log ในรูปแบบของ JSON Format ทำให้ง่ายต่อการทำความเข้าใจ และมันถูกเขียนขึ้นมาจากภาษาซีเป็นหลัก แล้วใช้ Ruby ผสมผสานเข้าด้วยกันเพื่อเพิ่มความยืดหยุ่นในการทำงานนั่นเอง



ทั้งนี้ในตอนนี้ที่เว็บไซต์ผู้ผลิตเคลมอีกว่าระบบที่ใหญ่ที่สุดที่ใช้ Fluentd ในการเก็บข้อมูล Log นั้นเก็บข้อมูลมาจากกลุ่มเซิฟเวอร์ที่มีจำนวนมากกว่า 50000 เครื่องเชียวนะครับไปอ่านจากลิงค์อ้างอิงเดียวกันได้เลย

คราวนี้เราจะมาพูดถึงว่าในการตั้งค่ามันมีอะไรบ้าง ส่วนการติดตั้งเดี๋ยวทำให้ทีหลังเพราะผมทำเสร็จไปก่อนแล้วถึงค่อยมาเขียนบทความมันมีติดบ้าง ผ่านบ้าง เดี๋ยวไงจะทำให้ใหม่ครับ

เรามาเริ่มกันที่ตัว Fluentd มีอะไรบ้างนะครับ ตัว Fluentd นั้นทุกเหตุการณ์ที่เกิดขึ้นจะเรียกว่า Event ซึ่งในทุก ๆ  Event ก็จะมี Tag กับ Input อยู่ด้วยกันเสมอโดยตัว Fluentd จะทำการ Match Tag กับข้อมูลที่ถูก Input เข้ามาแล้วส่งออกไปยัง Output ต่าง ๆ ตามที่ตั้งค่าไว้ตัว Fluentd นั้นรองรับ Input และ Output ที่หลากหลายขึ้นอยู่กับการตั้งค่าของเรา รวมถึงมันยังสามารถส่งข้อมูลในรูปแบบ Forward จาก Fluentd ตัวนึงไปยัง Fluentd อีกตัวนึงได้ โดยการทำแบบนี้เป็นการกระจายโหลดรุปแบบนึง

ใน Configuration File จะอนุญาตให้ผู้ใช้ หรือผู้ดูแลระบบเข้าไปตั้งค่าเกี่ยวกับพฤติกรรมต่าง ๆ Input, Output ต่าง ๆ ของโปรแกรมได้ โดยที่อยู่ของไฟล์เนื่องจากผมใช้ Ubuntu 18.04 จะอยู่ที่

$ sudo vi /etc/td-agent/td-agent.conf

หากท่านใดต้องการศึกษา Commandline Option ให้คลิก ที่นี่

องค์ประกอบของ Config File


  1. Source  ใช้สำหรับกำหนด Input แต่ละประเภท
  2. Match  ใช้สำหรับกำหนด Output แต่ละประเภทโดยอ้างอิงจากค่าที่อยู่ในตัวแปรนี้
  3. Filter  ใช้สำหรับกำหนด Event Processing Pipeline
  4. System  ใช้สำหรับกำหนด System Wide Configuration (การตั้งค่าที่กระทบกับภาพรวมของระบบ)
  5. Label  ใช้สำหรับแบ่งกลุ่มของ Output และ Filter ที่ใช้ Route ภายในระบบ
  6. @include  ใช้สำหรับแนบไฟล์การตั้งค่าอื่น ๆ เข้ามาในไฟล์นั้น ๆ

1. Source ใช้สำหรับกำหนดว่าแหล่งข้อมูลที่ได้มานั้นมาจากที่ใด

          ในส่วนนี้ผู้ดูแลระบบสามารถตั้งค่าได้ว่าจะให้ Fluentd รับข้อมูลจากแหล่งใดบ้าง ซึ่งสามารถติดตั้ง Plugin เพิ่มได้จากหน้าเว็บตามประเภทของอุปกรณ์ที่ต้องการได้เลย โดยที่ตัว Fluentd จะมี Plugin มาให้ด้วยแต่แรกระดับนึง ซึ่งหนึ่งในนั้นคือ http และ forward.http ซึ่งจะถูกติดตั้งลงไปที่ HTTP Endpoint เพื่อใช้สำหรับรับ HTTP incoming message ที่ Endpoint เพื่อให้รับ TCP Packet แต่เราก็ยังสามารถเพิ่มแหล่งข้อมูลได้เท่าที่เราต้องการนั่นเอง

# Receive events from 24224/tcp
# This is used by log forwarding and the fluent-cat command

  @type forward  << เป็นการประกาศว่าจะใช้ input plugin ประเภท forward คือรับ message มาตรง ๆ
  port 24224  <<  เป็นการบอกว่าจะรับ input เข้ามาที่ port ไหน
</source>

# http://this.host:9880/myapp.access?json={"event":"data"}

  @type http
  port 9880
</source>


**ข้อควรระวัง
         ในแต่ละ Source directive จะต้องมีการใส่ตัวแปร @type ไว้ด้วยเพื่อเป็นตัวกำหนดว่าเราจะใช้ Plugin ใดในการรับข้อมูลเข้ามายัง Fluentd


แนะนำเรื่อง Routing ใน Fluentd

          Source ส่งข้อมูล event มาที่ routing engine ของ Fluentd ซึ่งใน event จะมีเอกลักษณ์ (Entity) อยู่ 3 อย่างคือ tag, time และ record โดย tag คือ ข้อมูลแบบ string ที่ถูกแบ่งด้วยเครื่องหมาย "." เช่น myapp.access โดยจะถูกใช้เป็นตัวระบุทิศทางการ route ของ routing engine นั่นเอง ส่วน time จะถูกใช้ในบาง input plugin โดยในฟิลด์นี้จะต้องถูกแสดงในรูปแบบของ Unix time format เท่านั้น ส่วนฟิลด์สุดท้าย record จะเป็นข้อมูลในรูปแบบของ JSON Object

Noted : ในฟิลด์ tag นั้นรองรับอักขระทุกแบบยกเว้นเครื่องหมาย "." หรือเรียกอีกชื่อว่า period character เนื่องจากเราจะใช้ tag ในหลายบริบททั้งใน destination output ตัวอย่างเช่น table name, database name, key name เป็นต้นแต่ทางผู้ผลิตแนะนำว่าให้ใช้แค่ a-z, 0-9 และเครื่องหมาย _ เท่านั้น

ถ้าหากว่าแหล่งข้อมูลที่เราต้องการใช้นั้นไม่มี plugin เราสามารถค้นหาเพิ่มเติม หรือสามารถเขียนขึ้นมาเองได้เช่นกัน โดยสามารถศึกษาข้อมูลเพิ่มเติมได้จาก ที่นี่

จาก config ตัวอย่างข้างบนหากมีการส่งข้อมูล http message ข้างล่างเข้าจะมาแสดงผลดังนี้

ข้อมูลถูกส่งเข้ามา
# generated by http://this.host:9880/myapp.access?json={"event":"data"}
และจะถูกแสดงผลตามนี้
tag: myapp.access
time: (current time)
record: {"event":"data"}


2. Match ใช้บอก Fluentd ว่าต้องทำอย่างไรต่อไปเมื่อได้รับ input เข้ามา

          Tag match นี้จะคอยมองหา event ที่ match กับ tag ต่าง ๆ ที่กำหนดไว้แล้วประมวลผลมันซะ ซึ่ง Match ที่พบบ่อยที่สุดคือ เมื่อเจอแล้วให้ส่งต่อข้อมูลออกไปยังที่ใด ( plugin ใด ๆ ที่มีการติดต่อโดยตรงกับ match นั้นจะเรียกว่า output plugin ) Fluentd มี plugin ที่ให้มาด้วยสองตัวคือ file กับ forward นั่นเองเราจะลองเพิ่มการใช้งาน match ลงไปใน config file กัน


# Receive events from 24224/tcp
# This is used by log forwarding and the fluent-cat command

  @type forward
  port 24224
</source>

# http://this.host:9880/myapp.access?json={"event":"data"} << Input จำลองที่ถูกส่งเข้ามาที่ source

  @type http
  port 9880
</source>

# Match events tagged with "myapp.access" and
# store them to /var/log/fluent/access.%Y-%m-%d
# Of course, you can control how you partition your data
# with the time_slice_format option.
 << match ทำการตรวจสอบพบคำว่า myapp.access ซึ่งจะตรงกับ input ข้างบนที่ถูกส่งเข้ามาจึงสั่งให้ทำงาน
  @type file << Output ปลายทางเป็น plugin ประเภทไฟล์เอกสาร
  path /var/log/fluent/access << ถูกส่งไปเก็บในไฟล์ชื่อ access ใน path /var/log/fluent/ นั่นเอง

โดยแต่ละ match จะต้องมี match pattern และแน่นอนต้องมีตัวแปร @type ด้วยเพื่อบอกให้รู้ว่าปลายทางจะเป็น plugin อะไร ไม่สิ ต้องบอกว่าเพื่อให้รู้ว่าจะต้องส่งข้อมูลต่อไปยังที่ใด ซึ่งจากตัวอย่างข้างบนจะมีเพียงแค่ event ที่ชื่อ "myapp.access" เท่านั้นที่จะ match กับข้อมูลข้างบน และเช่นเดียวกับ input source คุณสามารถเขียน output plugin เองได้เช่นกันครับ ถ้าหากต้องการทำความเข้าใจว่า match ทำงานยังไง คลิกที่นี่ แต่ถ้าไม่รีบก็ค่อย ๆ ทำความเข้าใจไปทีละนิดครับ

3. Filter  ใช้สำหรับคัดกรอง input ที่เข้ามา

          ในส่วนของ filter นั้นจะทำงานคล้ายกับ match คือจะทำการค้นหาคำเหมือนกับที่ match ทำแต่ต่างกันตรงที่ filter ใช้กรองได้นั่นเอง ซึ่งในที่นี้จะเรียกว่ามีการทำ process pipeline นั่นเอง

Input -> filter 1 -> ... -> filter N -> Output

จากตัวอย่างจะเห็นได้ว่า filter จะเป็นการ match ข้อมูล แล้วนำผลลัพธ์ที่ได้ไป match ต่อ หรือจะเรียกว่าเป็น nested match ก็ได้นะครับ เอาเป็นว่ามันคือ filter แล้วกันครับเหอ ๆ ๆ ตอนนี้เราจะลองเพิ่ม filter ที่มากับโปรแกรมอีกตัวชื่อว่า record_transformer เพื่อทำเป็นตัวอย่างให้ดูตามนี้ครับ

# http://this.host:9880/myapp.access?json={"event":"data"}

  @type http
  port 9880
</source>

  <<  สร้างฟิลเตอร์เพื่อ match myapp.access ขึ้นมา
  @type record_transformer  <<  แน่นอนต้องมีตัวแปร @type เพื่อระบบ plugin ว่าจะใช้เป็น record_transformer 
  
    host_param "#{Socket.gethostname}"  <<  เพิ่มตัวแปร host_param เข้าไปใน event ฟิลด์ record ซึ่งเป็นฟิลด์ประเภท JSON object ตามที่อธิบายไว้ข้างบน
  


จาก tag filter ทำให้ข้อมูลที่รับมาจาก input เปลี่ยนจาก json={"event":"data"} เป็น json={"event":"data","host_param":"webserver1 (ผลลัพธ์จากการดึงชื่อเครื่องเซิฟเวอร์ที่รับ input เข้ามาครับ)"}


  @type file
  path /var/log/fluent/access

หลังจากผ่านการทำ filter แล้วก็จะทำการส่งข้อมูลไปประมวลผลต่อผ่าน tag match แล้วเซฟผลลัพธ์ลงไฟล์นั่นเอง และแน่นอนคุณสามารถสร้าง filter ของคุณเองใน plugin ได้หากต้องการศึกษาเพิ่มคลิก

4. System  กลุ่มของการปรับแต่งค่าที่ส่งผลต่อภาพรวมทั้งระบบ

          การตั้งค่าใด ๆ ภายใต้ tag system นั้นจะส่งผลกับระบบทั้งหมด เช่น มีการติดตั้ง Fluentd ไว้กับเซิฟเวอร์จำนวน 20 เครื่องหากมีเครื่องใดปรับแต่งค่าด้วย tag system จะกระทบทั้งหมด 20 เครื่องทันทีซึ่งถ้าจะมีการตั้งค่าในส่วนนี้ก็ควรจะตั้งค่าให้เหมือนกันทั้งหมดเพื่อป้องกันปัญหาที่จะเกิดขึ้นในอนาคต

  • log_level
  • suppress_repeated_stacktrace
  • emit_error_log_interval
  • suppress_config_dump
  • without_source
  • process_name (only available in system directive. No fluentd option)
รายชื่อตัวแปรข้างบนเป็นเพียงบางส่วนที่ใช้ในการปรับแต่งค่า


  # equal to -qq option <<  เป็น quiet mode สามารถตั้งค่าได้ 2 อย่างคือ -q กับ -qq โดย -qq จะแจ้งเตือนในกรณีที่เกิด error เท่านั้นส่วน -q จะแจ้งเตือนเมื่อเกิด warning
  log_level error
  # equal to --without-source option <<  เป็นการตั้งค่าให้สามารถเรียก Fluentd ขึ้นมาทำงานโดยไม่มี input plugin
  without_source
  # ...


ถ้าตั้งค่าตัวแปร process_name จะทำให้ Fluentd worker และ supervisor เปลี่ยนชื่อของ process ไปตามที่ตั้งค่าดังตัวอย่างด้านล่าง

  process_name fluentd1


ถ้าหากว่ามีการตั้งค่า process ไว้แบบนี้เวลาสั่งให้ Fluentd ทำงานจะเกิด process name ที่ชื่อว่า fluentd1 ขึ้นมาซึ่งสามารถตรวจสอบได้โดยพิมพ์

% ps aux | grep fluentd1
foo      45673   0.4  0.2  2523252  38620 s001  S+    7:04AM   0:00.44 worker:fluentd1
foo      45647   0.0  0.1  2481260  23700 s001  S+    7:04AM   0:00.40 supervisor:fluentd1


5. Label หรือเรียกว่ากลุ่มของ filter และ output ก็ได้

          อย่างที่บอกข้างบน Label เป็นกลุ่มของ filter และ output ที่ใช้ลดความซับซ้อนในการจัดการ tag โดยจะใช้สำหรับควบคุมดูแล filter และ output ที่ใช้ในการ route ภายในเท่านั้น


  @type forward  <<  รับข้อมูลเข้ามาจาก input ผ่าน plugin forward
</source>


  @type tail  <<  รับข้อมูลเข้ามาจาก input ประเภท tail (เปิดอ่านไฟล์ใด ๆ จากบรรทัดล่าสุด)
  @label @SYSTEM  <<  ติด Label ให้กับ input นี้ว่าเป็น label @SYSTEM
</source>


  @type record_transformer  <<  สร้าง filter จาก input plugin record_transformer สำหรับเพิ่มเติม หรือเปลี่ยนแปลงค่าในฟิลด์ record ของ event ที่ match
  
    # ...
  


  @type elasticsearch  <<  สร้าง match จาก input ทั้งหมดส่งไปที่ output plugin ชื่อ elasticsearch
  # ...





จากตัวอย่างข้างบน event ที่ถูก route เข้ามาจะมี 2 ส่วนจาก 2 input source คือจาก forward ในส่วนของ input ที่มาจาก forward จะถูก route ไปยัง filter record_transformer และถูกส่งไปยัง output plugin ที่ชื่อ elasticsearch ในขณะที่ event ที่มาจาก tail นั้นมีการใส่ label กำกับไว้ว่าเป็น label ภายใต้ชื่อ @SYSTEM ดังนั้น event จะถูก route ข้ามไปยัง filter ที่อยู่ใน label @SYSTEM แทนเป็นการข้าม filter อื่น ๆ ทั้งหมดไปเข้า filter ที่อยู่ใน label นั้น ๆ แทนนั่นเอง จากตัวอย่างนี้หมายถึง event ที่มาทาง input tail จะถูกส่งไปที่ filter grep และจะถูกส่งไปยัง output s3 นั่นเอง

@ERROR Label คือ built-in label ซึ่งจะถูกใช้สำหรับส่ง error record โดย emit_error_event API

ถ้าเรามีการตั้งค่า label @ERROR ไว้ใน config file เวลาส่งข้อมูล event จะถูก route มาที่ label นี้เมื่อตรวจพบ error เช่น buffer เต็ม หรือข้อมูลใน record ผิดนั่นเอง

6. @include  ใช้สำหรับแบ่ง config file ออกเป็นหลาย ๆ ไฟล์แยกกันแล้วนำมาใช้รวมกัน เพื่อใช้ลดความสับสนในกรณีที่มีการตั้งค่าการทำงานร่วมกับกลุ่มของเซิฟเวอร์ปริมาณมาก ประโยชน์เพื่อลดความซับซ้อน ป้องกันความผิดพลาดที่อาจเกิดขึ้นได้อีกด้วย

# Include config files in the ./config.d directory
@include config.d/*.conf

@include นั้นรองรับการอ้างอิงถึง path ในหลายรูปแบบทั้ง regular file path, glob pattern และ http url conventions

# absolute path
@include /path/to/config.conf

# if using a relative path, the directive will use
# the dirname of this config file to expand the path
@include extra.conf

# glob match pattern
@include config.d/*.conf

# http
@include http://example.com/fluent.conf

Note : ในกรณีที่มีการใช้การอ้างอิง path แบบ glob pattern 
ถ้าเรามี a.conf,b.conf,... จนถึง z.conf แต่เราต้องการ import แค่ a.conf กับ z.conf 
# ไม่ควรใช้การอ้างถึงแบบนี้
@include *.conf

# ควรจะใช้การอ้างอิงถึงแบบนี้มากกว่า
@include a.conf
@include config.d/*.conf
@include z.conf


Match ทำงานยังไง

          จากที่อธิบายมาข้างต้นทั้งหมดนั้น Fluentd ให้สิทธิเราในการ route event ไปที่ไหนก็ได้ตาม tag ที่กำหนดแต่เราสามารถใส่ tag ตรง ๆ ลงไปเลยก็ได้เช่น <filter app.log> ถ้าเราต้องการดึงข้อมูลจาก tag นี้เท่านั้นจากตัวอย่างข้างบนที่เราใช้ <filter app.*> ก็สามารถระบุตัว tag จริง ๆ ที่ต้องการลงไปได้เลยเช่นกัน

Wildcard และ Expansions

          Match pattern ต่อไปนี้สามารถนำไปประยุกต์ใช้ได้กับทั้ง match tag และ filter tag จะเรียกว่า Regular Expression ก็ได้แต่ไม่เหมือนซะทีเดียวแต่ก็ใกล้เคียง

  • * ใช้สำหรับ match อักขระตัวเดียว หรือ single match เช่น a.* นั้นจะ match กับ a.b ได้แต่จะไม่ match กับ a หรือ a.b.c เพราะว่ามันมีได้แค่ค่าเดียวเท่านั้น
  • ** ใช้สำหรับ match อักขระหลายตัว เรียกว่า zero or more ก็ได้ ซึ่งสำหรับส่วนนี้จะมีค่าเท่ากับ * ตัวเดียวใน RegEx นั่นเองตัวอย่าง a.** จะ match ได้กับ a, a.b, a.b.c ได้ทั้งหมด
  • {a,b,c} ใช้สำหรับ match กลุ่มของอักขระตัวอย่างแบ่งออกเป็น 2 แบบดังนี้
    • {a,b} จะ match กับ a และ b แต่ไม่ match c
    • เราสามารถใช้งานร่วมกับ pattern ข้างบนได้ดังนี้ a.{b,c}.* และ a.{b,c,**}
  • เราสามารถใช้การผสมผสานกันระหว่างหลาย ๆ pattern ใน tag เดียวกันได้เช่นกันโดยจะแบ่งแต่ละ pattern ด้วยช่องว่างอย่างน้อย 1 ช่องเช่น
    • match a b จะหมายถึง match a หรือ b ก็ได้
    • match a.** b.* จะหมายถึง match a, a.b, a.b.c ก็ได้ตาม pattern แรกหรือจะ match b.c หรือ b.d ก็ได้ตาม pattern ที่สองนั่นเอง

ในการ match ของ Fluentd นั้นจะ match จากบนลงล่าง

# ** matches all tags. Bad :(

  @type blackhole_plugin



  @type file
  path /var/log/fluent/access


จากตัวอย่างด้านบนนั้น myapp.access จะไม่มีทาง match ได้เลยเนื่องจากทุก event ที่เข้ามาจะ match กับ rule ตัวบนก่อนแล้วหายไปจนหมด


  @type file
  path /var/log/fluent/access


# Capture all unmatched tags. Good :)

  @type blackhole_plugin


ดังนั้นเราควรจะเรียงลำดับตามความเหมาะสมนั่นเอง

และแน่นอนว่าในการส่ง output ออกไปครั้งละหลาย ๆ ที่ หรือที่เรียกว่า multiple output นั้นก็ควรพิจารณาในการจัดลำดับของ tag ต่าง ๆ ด้วยเช่นกัน ซึ่งในกรณีนี้สามารถพิจารณาใช้ plugin ที่ชื่อว่า out_copy ได้ตามตัวอย่างด้านล่าง


  @type copy  <<  เลือกใช้ output ชื่อ copy plugin 
    <<  เป็นคำสั่งให้นำข้อมูลที่ได้ไปเก็บที่ไหนซึ่งสามารถนำข้อมูลไปเก็บไว้ใน in-memory หรือ storage หรือ other key-value stores ตามที่ต้องการได้
    @type file  <<  เก็บข้อมูลเป็นไฟล์
    path /var/log/fluent/myapp  <<  เก็บไฟล์ไว้ที่ path นี้
    compress gzip  <<  มีการบีบอัดข้อมูล
    
      localtime false  <<  ไม่ให้มีการใช้ localtime ให้ใช้ UTC time แทน
    
      <<  กำหนดช่วงเวลาในการเก็บข้อมูล
      timekey_wait 10m  <<  ให้ถือข้อมูลไว้ก่อน 10 นาที
      timekey 86400  <<  ให้เขียนข้อมูลบันทึกทุกวัน
      timekey_use_utc true  <<  ใช้เวลาสากลแทนที่จะใช้ Unix time
      path /var/log/fluent/myapp  <<  เก็บที่ path นี้
    
      <<  เป็นการใส่ค่าไปที่ฟิลด์ record โดยตรงเพื่อแทรกค่าเข้าไปใน JSON format สามารถใช้ได้กับ match และ filter
      time_format %Y%m%dT%H%M%S%z  <<  กำหนดให้แสดงผลวันเวลาตาม format นี้
      localtime false  <<  ไม่ให้ใช้ localtime ให้ใช้ UTC time แทน
    
  
  
    @type elasticsearch  <<  นอกจาก store เป็นไฟล์แล้วให้ส่งข้อมูลไปเก็บที่ elasticsearch plugin
    host fluentd  <<  กำหนดชื่อเครื่องเป็น fluentd
    port 9200  <<  ส่งไปยัง port 9200 ซึ่งเป็น default port ของ elasticsearch
    index_name fluentd  <<  ตั้งชื่อ index name ให้เป็น fluentd
    type_name fluentd  <<  ชนิดของ type ให้เป็น fluentd
  



หากต้องการศึกษาข้อมูลของ out_copy เพิ่มเติมคลิกที่นี่

หากต้องการศึกษาเรื่องของ data type ที่ fluentd support คลิกที่นี่


No comments:

Post a Comment

วิธีการติดตั้ง และตั้งค่า Apache2 พร้อมเปิดใช้งาน HTTPS ด้วยวิธี Self-Sign บน Ubuntu 18.04.1 LTS

ดีจร้า วันนี้ช่วงเช้าว่างเลยมาเขียนบทความเพิ่มเติมเกี่ยวกับการติดตั้ง และตั้งค่า Apache2 ให้สามารถใช้งานผ่าน HTTPS ได้กันมาเริ่มกันเลยครับ ...