Loki logging အတွက် Theory နဲ့ Lab

ဒီတခေါက် ကျွန်တော်တို့ Kubernetes cluster ထဲ Loki setup လုပ်ကြည့်ကြမယ်။ code စမရေးခင် Logging နဲ့ Loki အကြောင်း နည်းနည်းပြောရအောင်။ Logging အတွက်ဆိုရင် ELK stack ကတော်တော်လေး နာမည်ကြီးတယ်။ Datadog လို all-in-one platform တွေလည်း ရှိတာပေါ့။ ကျွန်တော် UK မှာပထမဆုံး အလုပ်လုပ်တဲ့ ကုမ္ပဏီမှာ ကိုယ်မရောက်ခင်က Datadog သုံးခဲ့တာ။ ဒါပေမယ့် တလကို Datadog ဖိုးနဲ့တင် ပေါင် ၃၀၀၀ လောက်ကုန်နေလို့ self-hosted အဖြေရှာရင်း Loki ကိုရွေးဖို့ ဆုံးဖြတ်လိုက်ကြတာပဲ။

Logging အကြောင်းပြောရင် အဓိက 3 ပိုင်းရှိတယ်။ ဦးနှောက်အင်ဂျင်နဲ့တူတဲ့ log server ရယ်၊ log တွေစုပြီး sever ဆီပို့မယ့် agent ရယ်၊ log တွေသိမ်းဖို့ storage server ရယ်ပေါ့။

design အပေါ်မူတည်ပြီး storage server ကိုအချို့က သီးသန့်ပြင်ပ object storage တွေနဲ့ integrate မလုပ်ပဲ local disk ပေါ်သိမ်းတဲ့ in-cluster ပုံစံလည်း လုပ်ကြတာလည်း ရှိတယ်။ ဥပမာ ELK ရဲ့ elasticsearch လိုမျိုးပေါ့။ ဒါပေမယ့် Loki မှာတော့ Log တွေဘယ်မှာသိမ်းမလဲဆိုတာ ကိုယ့်ဟာကိုယ် ကြိုက်တဲ့ backend ရွေးလို့ရတယ်။ AWS ကြိုက်ရင် S3 ဒါမှမဟုတ် GCP ရဲ့ GCS တို့၊ Azure က Azure Blob စသဖြင့်။ in-cluster ပုံစံထက် အားသာတာက log volume များလို့ဆိုပြီး Node အကြီးကြီးတွေ သုံးစရာ မလိုဘူးပေါ့လေ။

log agent တွေကတော့ ရှင်းတယ်။ local filesystem ပေါ်မှာ သတ်မှတ်ထားတဲ့ နေရာတွေက log ဖိုင်တွေကို သွားဖတ်တယ်။ ရလာတဲ့ log တွေထဲကမှ ကိုယ် configure လုပ်ထားတဲ့အတိုင်း filter လုပ်တယ်။ နောက် labeling လုပ်တယ်။ labeling ကဘယ်လိုလဲဆိုရင် meta tagging လို့မြင်ကြည့်လို့ရတယ်။ ဥပမာ log တကြောင်းစီက ဘယ်ကလာတယ်။ ဘယ်လိုပြန်ရှာရမယ်။ ဘယ် cluster ဘယ် tenant ဆိုတာမျိုး။ Kubernetes အတွက်ဆိုရင်လည်း Pod နာမည်ကဘာလဲ ReplicaSet ရဲ့ template hash ဘယ်လောက်ဆိုတာမျိုး စသဖြင့်ပေါ့လေ။

log agent ​တွေက ရွေးချယ်စရာမျိုးစုံရှိတယ်။ fluentbit, fluentd တို့ဆိုရင် အများစု ကြားဖူးပြီးသားတွေ။ fluentd က log တွေကို server ဆီမပို့ခင် parse ပြီး ကိုယ်ပို့ရမယ့် destination ကလက်ခံမယ့် ပုံစံအလိုက် transform လုပ်တာ၊ filter လုပ်တာ စသဖြင့် လုပ်ဖို့ plugin တွေရာနဲ့ချီရှိတယ်။ ဒါပေမယ့် Ruby သုံးတဲ့ software ဖြစ်လို့ performance တော့သိပ်မကောင်းဘူး။

performance ကအရေးကြီးတယ်ဆိုရင် fluentd API နဲ့ compatible ဖြစ်တဲ့ fluentbit ဆိုတာ သုံးလို့ရတယ်။ သူကျတော့ C နဲ့ရေးထားတာ။ တခြား ELK မှာသုံးတဲ့ filebeat တို့၊ နောက် throughput ကောင်းတယ်ဆိုပြီး တဖြေးဖြေး နာမည်ရလာနေတဲ့ Vector ဆိုတာလည်း ရှိသေးတယ်။ ဒါပေမယ့် Loki နဲ့ default အဆင်ပြေဆုံး agent ကတော့ promtail ပဲ။

Log ကိုလက်ခံတဲ့ log server ကတော့ logging process ကြီးတခုလုံးရဲ့ ဦးနှောက်လိုပဲ။ ရောက်လာတဲ့ log တွေကို စိစစ်ရတယ်။ နောက် indexing လုပ်ရတယ်။ index db မှာ entry ဆောက်တာ update ရေးတာတွေ လုပ်ရတယ်။ ပြီးရင် log တွေကို storage ပေါ်နေရာယူတာ သက်သာအောင် compress လုပ်ပြီး storage server ဆီ chunk လိုက်သွားသိမ်းတယ်။ တာဝန်တွေ အများကြီးပေါ့။

တခုရှိတာက server ကိုအခုလို monolithic ပုံစံထားလိုက်တာက scale ရတာ တော်တော်မလွယ်ဘူး။ ဒါကြောင့် Loki မှာ log server ကိုအပေါ်ကပြောသွားတဲ့ တာဝန်တခုချင်းစီကို ခွဲဝေလုပ်ပေးမယ့် microservice style သွားလို့ရတယ်။ ဒီတော့ ELK မှာလို scale ချင်ရင် cluster ထဲ Node တွေထည့်ထပ်ရတာ မဟုတ်ဘူးပေါ့။

Loki ကို microservice ပုံစံခွဲထုတ်လိုက်မယ်ဆိုရင် server တခုပဲ ရှိရာက distributor, ingester, compactor, frontend, querier စသဖြင့် ကွဲသွားမယ်။ distributor က agent တွေအတွက် sticky session ပါတဲ့ load balancer လိုမျိုး တာဝန်ယူတယ်။ ရောက်လာတဲ့ log stream တွေကို hash ပြီး hash ring အတိုင်း ဘယ် ingester ဆီပို့မလဲ ဆုံးဖြတ်မယ်။ log stream တခုတည်းက log တွေကို သူ့ကိုတာဝန်ယူထားတဲ့ ingester ဆီကိုပဲပို့မယ်။ တကယ်လို့ replicate လုပ်ဖို့လိုအပ်ရင် လုပ်မယ်ပေါ့။

ingester ကတော့ log stream တွေကို metadata တွေ tag တွေ label တွေအလိုက် index လုပ်မယ်။ နောက်ပြီး index db မှာ entry ဆောက်မယ်။ chunk တခုစာရရင် compress လုပ်ပြီး storage backend ဆီပို့မယ်။ storage backend မှာသိမ်းပြီး ရလာတဲ့ key ကို index db မှာပြန်လာ update လုပ်မယ်။ ဒီနေရာမှာလည်း index db အတွက် ရွေးစရာတွေ အများကြီးရှိတယ်။ BoltDB လိုမျိုး local အတွက် ပေါ့ပေါ့ပါးပါး file-based database တွေရွေးနိုင်သလို တော်တော်လေး log volume များတဲ့ cluster မျိုးတွေဆိုရင် DynamoDB နဲ့ Cassandra တို့လို LSM database တွေက write ပို heavy ဖြစ်တဲ့ logging nature နဲ့ပိုသင့်တော်တယ် (LSM နဲ့ B-tree database တွေဘယ်လိုကွာလဲ ဒီမှာရှင်းပြထားသေးတယ်)

နောက် compactor ဆိုတာရှိတယ်။ backend storage မှာသိမ်းလိုက်တဲ့ log chunk အသေးလေးတွေကိုစုပြီး chunk အကြီးဖြစ်အောင် ပြောင်းတယ်။ index db မှာသွား update တယ်။ ဒါက storage နေရာယူ ပိုသက်သာစေသလို range query တွေမှာ performance ပိုမြန် ပိုကောင်းစေတယ်။

frontend ကတော့ query ဆွဲပြီး ရလဒ်ကို chart တွေနဲ့ပြန်ပြပေးနိုင်မယ့် dashboard မျိုးတွေပေါ့။ ဥပမာ Grafana ပဲဆိုပါတော့။ frontend တွေက query result တွေကို cache လုပ်နိုင်တယ်။ တကယ်လို့ ရှာရမယ့် query ကကြီးတယ်ဆိုရင် sub query လေးတွေအဖြစ် ခွဲပြီး parallel အလုပ်လုပ်စေမယ်ပေါ့။ ဒါပေမယ့် query တွေရောက်လာရင် ingester နဲ့ storage backend ဆီကနေ index တွေကိုဖတ်၊ log ဖိုင်တွေရှာပြီး အဖြေထုတ်ပေးတဲ့သူက querier ဆိုတာလေးတွေဖြစ်တယ်။

ဒီလောက်ဆို Loki logging ကို theory ပိုင်းနဲ့ architecture အခြေခံ နားလည်သွားမယ်ထင်တယ်။ အခုသူ့ကို လက်တွေ့ setup လုပ်ကြည့်ကြတာပေါ့။ အဲဒီအတွက် ကျွန်တော်တို့ Terraform ရယ်၊ Terraform အတွက် helm provider ရယ်၊ နောက်ပြီး Grafana repository ထဲက chart တွေသုံးမယ်။ chart ရွေးတဲ့အခါ ၂ မျိုးရွေးလို့ရတယ်။ loki-stack ဆိုတဲ့ promtail ရော loki server ရော grafana ပါပါပြီးသား parent chart ကိုရွေးမလား ဒါမှမဟုတ် တခုချင်းစီအတွက် သီးသန့် chart တွေသုံးပြီး install လုပ်မလားဆိုတဲ့ option 2 ခုရှိတယ်။ loki-stack chart သုံးတာကတော့ အများကြီး ပိုရိုးရှင်းတယ်။

နောက်တခုက loki အတွက် ရွေးစရာ mode တွေစဥ်းစားရမယ်။ အပေါ်မှာ ပြောခဲ့သလို monolithic သွားမလား။ ဒါမှမဟုတ် microservice ပုံစံသွားမလားပေါ့။ တကယ်လို့ log volume ကတနေ့ကို 100 GB အောက်ပဲ ရှိတယ်ဆိုရင် monolithic နဲ့လုံလောက်တယ်။ အဲထက်ပိုလာရင်တော့ single binary မဟုတ်ပဲ decouple ဖြစ်တဲ့ mode တွေကို စဥ်းစားသင့်တယ်။

providers.tf

terraform {
    required_providers {
        helm = {
            source  = "hashicorp/helm"
            version = "~> 3.0.2"
        }
    }
}
    
provider "helm" {
    kubernetes = {
        config_path = "~/.kube/config"
    }
}

variables.tf

variable "chart_version" {
    type = string
    description = "The version of loki-stack Helm chart to install"
}

main.tf

locals {
    loki_namespace = "monitoring"
    loki_chart = "loki-stack"
}

loki.tf

resource "helm_release" "loki_stack" {
    namespace        = local.loki_namespace
    create_namespace = true
    
    name    = "loki"
    chart   = local.loki_chart
    version = var.chart_version
    
    repository = "https://grafana.github.io/helm-charts"
    
    values = [
        <<-YAML
            grafana:
                enabled: true
        YAML
    ]
    
}

terraform.tfvars

chart_version = "v2.10.2"

ဒါပြီးရင် terraform init && terraform apply -auto-approve လို့ terminal ထဲ run လိုက်တာနဲ့ loki install လို့ပြီးပါပြီ။ နောက်တဆင့်က promtail ကနေ loki server ကိုပို့ပြီး ingest လို့ရလာတဲ့ log တွေကို grafana dashboard ကနေ ဝင်ကြည့်ဖို့ဆိုရင် ကျွန်တော်တို့ credential ထုတ်ဖို့လိုပါတယ်။ ဒီ command ကိုသုံးပြီး credential တွေယူပါ။

kubectl get secret --namespace monitoring loki-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

ဒါဆို loki နဲ့ logging setup လုပ်လို့ပြီးပါပြီ။ ကိုယ့် cluster ကတနေ့ကို log volume 100 GB ကနေ 1 TB လောက်ဖြစ်လာပြီဆိုရင်တော့ microservice setup ဆီ ဖြေးဖြေးချင်း migrate လုပ်သွားဖို့ လိုအပ်တယ်။ ဒီလောက်ဆို loki နဲ့ပတ်သက်လို့ တော်တော်လေး သဘောပေါက်သွားမယ်လို့ ထင်ပါတယ်။