#vthang

Monitoring Elixir Applications

elixir
Mar 5, 2019

Khi web app của bạn đang ở giai đoạn nhi đồng dễ bảo thì mọi thứ bạn hoàn toàn có thể kiểm soát bằng cơm nhưng một khi app của bạn trở nên khó bảo thì đó là lúc bạn cần monitoring. Làm sao để biết được toàn cảnh hành vi hay perfomance của hệ thống của bạn có đang chạy đúng như bạn mong muốn không.

Việc monitoring trong Elixir application cũng có một lợi thế nữa là bạn có thể sử dụng khá nhiều tools từ Erlang đã có sẵn trước đó để áp dụng vào hệ thống của mình.

Stack

Có rất nhiều stack hiện nay có thể áp dụng cho nhiều loại hệ thống khác nhau nhưng để cho khởi đầu đơn giản thì tôi chọn sử dụng Influxdb cho time-series database, Telegraf làm agent cho việc pull metrics và Grafana cho việc visualizing metrics.

stack

Time-series database

Về lợi thế thì InfluxDB có những gì?

  • Timestamps optimization
  • High writes, high query loads
  • Strong SQL-like query language
  • Index theo tags, rất nhanh và thuận tiện cho việc query
  • Cung cấp api đơn giản thông qua http hoặc udp hoặc giao thức riêng của InfluxDB - line protocol
  • ...etc

Bạn có thể đọc thêm documentations của InfluxDB để hiểu rõ hơn về time-series database này.

Collecting and Reporting metrics

Bạn đã có database rồi. Giờ sao. Làm sao để collect metrics? Ở đây tôi sử dụng Telegraf để pull metrics từ các services. Telegraf là một pluginable agent có thể collect inputs từ rất nhiều nguồn khác nhau (K có đếm cơ mà đâu đó tầm 150 sources) và gửi đến các outputs mà bạn có thể chỉ định. Ví dụ output ở đây có thể là InfluxDB.

[agent]
  interval = "10s"
  round_interval = true
  metric_batch_size = 1000
  metric_buffer_limit = 10000
  collection_jitter = "0s"
  flush_interval = "10s"
  flush_jitter = "0s"
  precision = ""
  debug = false
  quiet = false
  hostname = ""
  omit_hostname = false

[[outputs.influxdb]]
  urls = ["http://influxdb:8086"]
  database = "telegraf"
  retention_policy = ""
  write_consistency = "any"
  timeout = "5s"

# Read metrics about cpu usage
[[inputs.cpu]]

# Read metrics about disk usage by mount point
[[inputs.disk]]

# Read metrics about disk IO by device
[[inputs.diskio]]

# Get kernel statistics from /proc/stat
[[inputs.kernel]]

# Read metrics about memory usage
[[inputs.mem]]

# Get the number of processes and group them by status
[[inputs.processes]]

# Read metrics about swap memory usage
[[inputs.swap]]

# Read metrics about system load & uptime
[[inputs.system]]

[[inputs.socket_listener]]
  service_address = "udp://:8092"
  data_format = "influx"

[[inputs.rabbitmq]]
   url = "http://rabbitmq-1:15672"
   name = "rmq-server-1" # optional tag
   username = "$R_USERNAME"
   password = "$R_PASSWORD"

Trên đây là một config cơ bản giúp bạn có thể pull metrics từ các nhiều nguồn khác nhau. Gởi các thông tin như mem, disk, cpu, rabbitmq overview,... tới outputs theo khoảng thời gian interval mà bạn có thể đặt trước. Điều này giúp bạn có thể tránh việc network traffic tăng cao hay việc writes data xuống InfluxDB quá nhiều trong một thời điểm. Vì vậy bạn có thể đặt Telegraf đứng giữa Application và database trong trường hợp bạn collect metrics từ app với số lượng lớn một cách liên tục. Trong trường hợp bạn muốn kéo metrics từ nhiều services khác nhau, bạn nên chạy nhiều telegraf daemons khác nhau để có thể cài đặt inputs-outputs cho phù hợp.

Elixir application metrics

Driver

Về driver cho InfluxDB thì tui có sử dụng Fluxter giúp tạo connections pool tới InfluxDB/Telegraf. Api thì cũng rất đơn giản.

Đầu tiên bạn cần define module sẽ sử dụng fluxter behavior:

defmodule MyApp.Fluxter do
  use Fluxter
end
def start(_type, _args) do
  children = [
    MyApp.Fluxter.child_spec(),
    # ...
  ]

  Supervisor.start_link(children, strategy: :one_for_one)
end

Config để kết nối tới metrics collector

config :fluxter, MyApp.Fluxter,
  host: System.get_env("TELEGRAF_HOST") || "web-telegraf",
  port: String.to_integer(System.get_env("TELEGRAF_PORT") || "8092"),
  pool_size: 10

Xong! Giờ thì bạn có thể report metrics với Fluxter

def do_something() do
  MyApp.Fluxter.write("something_done", [func: "do_something"], 1)
  Myapp.Fluxter.measure("func_exec_time", [func: "another_func"], &another_func/0)
end

Ví dụ bạn có thể collect request time của tất cả các request tới application:

defmodule MyApp.Plug.EuMetrics do
  @time_unit :milli_seconds

  def init(opts), do: opts

  def call(conn, _) do
    start_time = :erlang.monotonic_time(@time_unit)

    Plug.Conn.register_before_send(conn, fn conn ->
      end_time = :erlang.monotonic_time(@time_unit)

      MyApp.Fluxter.write(
        "request_time",
        [type: "render_req"],
        value: (end_time - start_time) / 1
      )

      conn
    end)
  end
end

hay measuring Ecto

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :myapp

  def log(entry) do
    MyApp.Fluxter.write(
      "query_exec_time",
      [],
      (entry.query_time + entry.queue_time || 0) / 1000
    )

    super(entry)
  end
end

##Erlang VM stats vmstats là một erlang application tổng hợp metrics từ erlang vm rồi gửi chúng tới một configurable sink

config :vmstats,
  sink: MyApp.Fluxter,
  sched_time: true,
  base_key: "vmstats",
  key_seperator: ".",
  # in ms
  interval: 1000,
  memory_metrics: [
    {:total, :total},
    {:processes_used, :procs_used},
    {:atom_used, :atom_used},
    {:binary, :binary},
    {:ets, :ets}
  ]

vmstats yêu cầu chúng ta phải implement vmstats_sink behavior và may thay Fluxter pool có thể được sử dụng như là một vmstats sink

 defmodule MyApp.Fluxter do
  use Fluxter

  @behaviour :vmstats_sink

  def collect(_type, name, value) do
    write(name, value: value)
  end
end

Ok vậy là cài đặt xong, vmtats giúp chúng ta collect những loại metrics gì? ETS tables, atoms, processes, binaries và total memory, number of process, process limit, garbage collection count,..

Visualizing metrics

Việc hiện thị metrics thì mình sử dụng Grafana. Grafana là 1 open-source cung cấp giao diện trực quan support các nguồn dữ liệu và plugins khác nhau và Grafana support InfluxDB. Sau tất cả chúng ta có thể có một dashboard kiểu như thế này:

alt text

Bạn chỉ cần cài đặt datasource đúng kiểu rồi có thể query data rất dễ dàng:

alt text

Alerting

Thực ra trọng bộ TICK stack còn có 1 component tên là Kapacitor - Realtime data process streaming có nhiệm vụ process data và trigger action dựa trên kết quả trả về. (VD như Kapacitor sẽ kéo cpu measurement về nếu như quá ngưỡng cho phép thì trigger alert). Nhưng hiện tại mình chưa có dùng Kapacitor mà dùng luôn tính năng alerting của Grafana.

alt text

Như vậy là mình có thể bật notification channel alerting trên slack rồi yên tâm nằm ngủ rồi.

Tổng kết

Sau khi nhận thấy application của mình ngày càng khó quản lý bằng cơm. Cuối cùng tôi cũng phải build một hệ thống monitoring riêng cho mình. Tuy đơn giản nhưng hiệu quả mang lại là rất cao. Cũng vì muốn các bạn chưa từng build một monitoring service nào có thể có một chút view về vấn đề này. Nên tôi viết bài này với hy vọng những bạn đang mò mẫm như tôi lúc mới bắt đầu làm có thể có những solutions cho riêng mình.