SSD Nodes Learn 🎉 VPS $5.50/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor

একটি VPS-এ Self-Hosted Log Management: কোনটি যথেষ্ট?

একটি VPS-এ log management চালাতে journald ও logrotate আগে দেখুন, তারপর Loki বা OpenSearch বাছুন। RAM floor, retention এবং search-এর বাস্তব পার্থক্য জানুন।

একটি VPS-এ self-hosted log management-এর প্রকৃত খরচ

একটি VPS (virtual private server)-এ self-hosted log management-এর বিষয়টি মূলত একটি প্রশ্নে এসে দাঁড়ায়: আপনার কি search cluster দরকার, নাকি log rotation এবং grep-ই যথেষ্ট? অধিকাংশ vendor guide-এর উত্তর শুরু হয় কমপক্ষে তিনটি node এবং 12 GB RAM দিয়ে, এমনকি একটি log line ship করার আগেও। একটি server-এর ক্ষেত্রে এই উত্তর কার্যকর নয়। তাই নিচের তুলনায় প্রতিটি option কোনো কিছু সংরক্ষণ করার আগে ছোট server-এর কাছ থেকে কী দাবি করে, তার ভিত্তিতে মূল্যায়ন করা হয়েছে।

আপনি যদি এক বা দুটি server চালান এবং গত মঙ্গলবার কী ঘটেছিল তা জানতে চান, systemd-journald এবং logrotate ইতিমধ্যেই সেই কাজ করে। সে ক্ষেত্রে পরের section-এর পরেই থামতে পারেন। কয়েকটি machine-এর log এক জায়গায় এনে কয়েক সপ্তাহ ধরে search করতে হলে Grafana Loki ছোট server-এর জন্য উপযুক্ত, কারণ এটি log line-এর text নয়, label index করে। Elasticsearch এবং OpenSearch প্রকৃত full text search দেয়। এর জন্য তারা memory ব্যবহার করে, কারণ JVM (Java virtual machine) heap-এর একটি সর্বনিম্ন সীমা আছে, যার নিচে নামা যায় না।

journald দিয়ে শুরু করুন, কারণ অধিকাংশ ক্ষেত্রে এখানেই কাজ শেষ হয়

বর্তমান Ubuntu বা Debian server-এ systemd-journald আগে থেকেই চালু থাকে। এটি প্রতিটি service unit-এর standard output, kernel message এবং syslog-এ পাঠানো সবকিছু সংগ্রহ করে। অধিকাংশ incident বিশ্লেষণের জন্য চারটি command যথেষ্ট।

journalctl -u nginx.service --since "2026-08-14 09:00" --until "2026-08-14 10:00"
journalctl -p err -b
journalctl -f -u ssh.service
journalctl --disk-usage

শেষ command-টি Archived and active journals take up 1.1G in the file system.-এর মতো একটি line দেখায়। এই সংখ্যাটিই নির্ধারণ করে যে আপনার আর কিছু প্রয়োজন কি না। যদি এতে কয়েকশ megabyte দেখায় এবং -u--since ব্যবহার করে প্রয়োজনীয় তথ্য খুঁজে পান, তাহলে কাজ শেষ।

Reboot-এর পর journal সংরক্ষিত থাকবে কি না, তা Storage= এবং /var/log/journal উপস্থিত কি না তার ওপর নির্ভর করে। প্রচলিত Storage=auto setting ব্যবহার হলে, directory-টি উপস্থিত থাকলে journald /var/log/journal-এ এবং উপস্থিত না থাকলে /run/log/journal-এ লেখে। /run memory-backed, তাই ওই directory না থাকা server-এ reboot-এর সময় সব log মুছে যায়। অথচ log পড়ার প্রয়োজন সাধারণত তখনই হয়। Ubuntu image-এ এই directory থাকে। Minimal এবং container-based image-এ প্রায়ই এটি থাকে না।

ls -d /var/log/journal
sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald
journalctl --disk-usage

Restart-এর পর journalctl --disk-usage-এ /run-এর পরিবর্তে /var/log/journal-এর নিচে একটি size দেখানো উচিত। Default-গুলো আগে থেকেই সীমাবদ্ধ থাকে। এটাই journald-কে শুধু fallback নয়, বাস্তবসম্মত সমাধান করে। journald.conf man page অনুযায়ী SystemMaxUse= file system-এর size-এর 10% এবং SystemKeepFree= 15%; তবে হিসাব করা প্রতিটি default সর্বোচ্চ 4G পর্যন্ত সীমিত। SystemMaxFileSize= default হিসেবে SystemMaxUse=-এর এক-অষ্টমাংশ, সর্বোচ্চ 128M; তাই সাধারণত সাতটি rotated file সংরক্ষিত থাকে। MaxRetentionSec=-এর default 0, যার ফলে age-based deletion বন্ধ থাকে। শেষের default-টি আবার লক্ষ্য করুন: default configuration-এ journal শুধু size দ্বারা সীমাবদ্ধ, age দ্বারা কখনো নয়।

[Journal]
Storage=persistent
SystemMaxUse=2G
MaxRetentionSec=30day

এটি /etc/systemd/journald.conf.d/99-size.conf-এ লিখুন, journald restart করুন, তারপর দেখুন journalctl --disk-usage আপনার নতুন ceiling-এর দিকে এগিয়েছে কি না। পরবর্তী rotation-এর জন্য অপেক্ষা না করে এখনই space reclaim করতে sudo journalctl --vacuum-size=500M অথবা sudo journalctl --vacuum-time=14d চালান। উভয় command-ই অপসারণ করা প্রতিটি file দেখায়। তাই কোনো output না থাকলে বোঝায় যে মুছে ফেলার মতো কিছু ছিল না।

journal-এর বাইরের সবকিছু, যেমন /var/log/nginx/access.log, logrotate-এর দায়িত্ব। এটি systemd timer থেকে প্রতিদিন চলে। একটি বিষয় জানা জরুরি, কারণ এটি df-এ bug-এর মতো দেখা যায়। Rotation-এর পর পুরোনো file directory listing থেকে চলে গেলেও daemon সেটি open অবস্থায় ধরে রাখতে পারে। তাই df -h disk full দেখাতে পারে, কিন্তু du -sh /var/log অনেক কম space ব্যবহারের কথা জানাতে পারে। Process তার log পুনরায় open করলেই space ফিরে আসে। Configuration-এর postrotate reload line এই কাজের জন্যই থাকে। sudo lsof -nP +L1 এমন deleted file-এর তালিকা দেখায় যেগুলো এখনও open অবস্থায় আছে, এবং প্রতিটি file ধরে রাখা process-এর নামও দেখায়। কিছু পরিবর্তন না করেই rule পরীক্ষা করতে sudo logrotate -d /etc/logrotate.d/nginx ব্যবহার করুন।

একটি collector-এ একাধিক সার্ভার থেকে লগ পাঠানো

একটির বেশি server থাকলে, একসঙ্গে একাধিক Linux server পরিচালনা করা সহজ হয় যখন তাদের লগ একটি স্থানে আসে। অধিকাংশ distribution-এ rsyslog আগে থেকেই ইনস্টল করা থাকে। তাই সবচেয়ে কম খরচের কেন্দ্রীয় collector হলো প্রতিটি sender-এ একটি file ব্যবহার করা।

*.* action(type="omfwd" target="logs.example.com" port="514" protocol="tcp")

এটি /etc/rsyslog.d/50-forward.conf হিসেবে সংরক্ষণ করুন। sudo rsyslogd -N1 দিয়ে এটি পরীক্ষা করুন। এই command configuration যাচাই করে এবং কোনো কিছু start না করেই বেরিয়ে যায়। এরপর rsyslog restart করুন। collector-এ TCP input সক্রিয় করুন।

module(load="imtcp")
input(type="imtcp" port="514")

এখানে দুটি সতর্কতা রয়েছে। দুটিই প্রযুক্তিগত বিষয়। সাধারণ syslog কোনো encryption বা authentication ব্যবহার করে না। তাই port 514-এ পৌঁছাতে পারে এমন যেকোনো কিছু আপনার লগের মতো দেখতে log line inject করতে পারে। এটিকে private network বা VPN-এ bind করুন এবং firewall দিয়ে port-টি সীমাবদ্ধ করুন। দ্বিতীয়ত, default action queue memory-তে থাকে। তাই collector অপ্রাপ্য হলে queue পূর্ণ হয়ে যায় এবং message বাদ পড়ে; কোনো copy সংরক্ষিত থাকে না। এই পরিস্থিতির জন্য rsyslog তার reliable forwarding tutorial-এ disk assisted queue-এর পদ্ধতি বর্ণনা করেছে।

ছোট VPS-এ ELK stack উপযুক্ত নয় কেন

ELK বলতে storage ও search-এর জন্য Elasticsearch, ingestion pipeline-এর জন্য Logstash এবং interface-এর জন্য Kibana বোঝায়। ন্যূনতম প্রয়োজনীয়তা JVM heap-এর ওপর নির্ভর করে, এবং কোনো log আসার আগেই এই heap নির্ধারিত হয়।

Elastic-এর documentation অনুযায়ী, প্রতিটি Elasticsearch node-এর জন্য heap মোট উপলভ্য memory-এর 50%-এর বেশি হওয়া উচিত নয়। কারণ process-টি off-heap buffer-ও ব্যবহার করে এবং index file দ্রুত পড়ার জন্য operating system-এর file cache-এর ওপর নির্ভর করে। তাই 2 GB heap-এর জন্য 4 GB machine দরকার, Kibana এবং server-টি যে কাজের জন্য কেনা হয়েছে সেগুলোর কথা বাদ দিয়েও। Elastic আরও জানায় যে Elasticsearch node-এর role ও মোট memory দেখে স্বয়ংক্রিয়ভাবে heap-এর আকার নির্ধারণ করে। এর ফলে ছোট machine-এ heap ছোট হয় এবং process-টি বারবার garbage collection চালাতেই থাকে।

ছোট budget-এ Logstash-ই সবচেয়ে বড় সমস্যা তৈরি করে। Elastic-এর নিজস্ব JVM settings page typical ingestion-এর জন্য 4GB-এর কম নয় এবং 8GB-এর বেশি নয়—এমন heap সুপারিশ করে। 4 GB VPS-এর পুরো memory-ই একটি 4 GB heap নিতে পারে, যেখানে এটি pipeline-এর মাঝখানে থাকা মাত্র একটি process।

ChartDocumented JVM heap settings, from each project's own docs
The data behind this chart
[
  {
    "label": "Loki plus Alloy (no JVM)",
    "documented_heap_mb": 0
  },
  {
    "label": "OpenSearch demo compose",
    "documented_heap_mb": 512
  },
  {
    "label": "OpenSearch production example",
    "documented_heap_mb": 2048
  },
  {
    "label": "Logstash recommended minimum",
    "documented_heap_mb": 4096
  }
]

প্রতিটি project তার নিজস্ব documentation-এ এই মানগুলো প্রকাশ করে। এগুলো কোনো test box-এ নেওয়া measurement নয়, এবং আপনার workload অনুযায়ী মান পরিবর্তিত হতে পারে। OpenSearch-এর sample compose file demo-এর জন্য প্রতি node-এ 512 MB এবং production example-এ 2048 MB নির্ধারণ করে। অন্যদিকে, Logstash-এর সুপারিশ করা lower bound হলো 4096 MB। Loki ও Alloy-এর ক্ষেত্রে heap column-এ 0 দেখা যায়, কারণ এগুলো Go program এবং reserve করার মতো কোনো JVM heap নেই। একটি সংখ্যাতেই পুরো পার্থক্যটি বোঝা যায়: কোনো log না এলেও JVM-ভিত্তিক component তার নির্ধারিত memory reserve করে রাখে।

তবু একটি ছোট server-এ Elastic stack চালাতে চাইলে Logstash বাদ দিয়ে একটি হালকা collector-এর মাধ্যমে সরাসরি Elasticsearch-এ log পাঠান। Logstash বড় আকারের ingestion-এ parse ও transform করার জন্য ব্যবহৃত হয়। একটি server-এ এই কাজ edge-এ করা যায়, অথবা পুরোপুরি বাদ দেওয়া যায়।

Elasticsearch এবং OpenSearch—উভয়ের ক্ষেত্রেই vm.max_map_count 262144-এ বাড়াতে হবে। কারণ এগুলো index file memory map করে, আর Linux-এর default limit এ কাজের জন্য খুব কম। নতুনভাবে তৈরি করা machine-এ কোনো container start হওয়ার কয়েক সেকেন্ড পরেই বন্ধ হয়ে গেলে, সাধারণত এর কারণ এটিই।

OpenSearch নাকি Elasticsearch: কোনটি deploy করতে পারবেন?

সংক্ষিপ্ত license ইতিহাস জানা দরকার, কারণ আপনি কী চালাতে পারবেন তা license-ই নির্ধারণ করে। January 2021-এ Elastic, Elasticsearch এবং Kibana-কে Apache 2.0 থেকে সরিয়ে dual SSPL (server side public license) এবং Elastic License 2.0 মডেলে নিয়ে যায়। AWS সর্বশেষ Apache 2.0 code fork করে OpenSearch তৈরি করে, যা Apache 2.0 license-এই থাকে। September 2024-এ Elastic free source code-এর জন্য আরেকটি option হিসেবে AGPLv3 (GNU Affero General Public License version 3) যোগ করে। একটি VPS-এ একজন ব্যক্তি নিজে host করলে, এই সব license-ই আপনার কাজের অনুমতি দেয়। আপনি software-টি অন্যদের managed service হিসেবে দিলে license-এর শর্ত প্রযোজ্য হয়।

ছোট server-এ ব্যবহারিক পার্থক্য ইতিহাসের তুলনায় কম, কারণ উভয়ের ভিতরের engine একই। নামের পার্থক্য আছে: OpenSearch-এ index lifecycle-কে ISM (index state management) এবং Elasticsearch-এ ILM (index lifecycle management) বলা হয়। August 2026 অনুযায়ী, OpenSearch 2.12 এবং পরবর্তী version প্রথমবার চালুর সময় admin password সেট করা না থাকলে start হতে অস্বীকার করে।

sudo sysctl -w vm.max_map_count=262144
printf 'vm.max_map_count = 262144\n' | sudo tee /etc/sysctl.d/99-opensearch.conf
docker run -d -p 9200:9200 -p 9600:9600 -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=<custom-admin-password>" \
  opensearchproject/opensearch:latest

sysctl -w-এর line-টি এখন setting প্রয়োগ করে, আর /etc/sysctl.d/-এর file-টি reboot-এর পরও setting বজায় রাখে। curl -k -u admin:<password> https://localhost:9200 দিয়ে container চালু হয়েছে কি না পরীক্ষা করুন। এটি demo certificate ব্যবহার করে https-এ response দেয়। তাই -k verification বাদ দেয়। সুস্থ response-এ cluster-এর নাম এবং version-সহ একটি ছোট JSON block থাকে। OpenSearch install page-এ Docker Desktop ব্যবহারকারীদের host-এ অন্তত 4 GB memory দেওয়ার কথাও বলা আছে। এটি process-টির প্রয়োজনীয় resource সম্পর্কে একটি বাস্তবসম্মত ধারণা দেয়।

Loki কীভাবে ছোট থাকে: পূর্ণ-পাঠ্য index-এর বদলে label

Loki label-এর ওপর একটি index রাখে এবং log line-গুলো compressed chunk হিসেবে সংরক্ষণ করে। একটি query প্রথমে stream নির্বাচন করে, তারপর text filter করে। {unit="ssh.service"} |= "Failed password" label ব্যবহার করে stream নির্বাচন করে, এরপর ওই chunk-গুলোতে string খোঁজে। কোনো line-এর body index করা হয় না। তাই ingestion-এর খরচ কম থাকে এবং memory-তে রাখার জন্য inverted index লাগে না। খরচ query time-এ গিয়ে পড়ে। আপনি সাধারণত কোন service পরীক্ষা করছেন তা জানলে এই বিনিময়টি কার্যকর।

Grafana-এর documentation অনুযায়ী, monolithic mode-এ অর্থাৎ -target=all-সহ সমগ্র Loki একটি process-এ চললে, প্রতিদিন আনুমানিক 20GB পর্যন্ত কম read ও write volume-এর ক্ষেত্রে এটি উপযুক্ত। একটি VPS এই সীমার অনেক ভেতরে থাকে।

সমস্যাটি হলো label cardinality। label value-এর প্রতিটি স্বতন্ত্র combination একটি stream তৈরি করে। stream-এর সংখ্যা Loki-এর memory ব্যবহার এবং index-এর আকার বাড়ায়। client IP address বা request identifier ধারণকারী একটি label প্রতিটি value-এর জন্য আলাদা stream তৈরি করে। ফলে একটি ব্যস্ত web server দিনে কয়েক দশ হাজার stream তৈরি করতে পারে এবং kernel process-টি বন্ধ না করা পর্যন্ত সেটির আকার বাড়তে থাকে। Label হিসেবে এমন value রাখুন যা কাগজে গুনে লেখা যায়: unit, host, job, level। পরিবর্তনশীল বিস্তারিত line-এর মধ্যেই রাখুন। Query time-এ filter expression ব্যবহার করে সেখান থেকে তা খুঁজে নেওয়া যাবে।

একটি VPS-এ Loki এবং Alloy ইনস্টল করুন

দুটি process এই কাজ করে। Loki log সংরক্ষণ করে এবং query-এর উত্তর দেয়। Grafana Alloy log পড়ে এবং তা push করে। আগে Promtail shipper হিসেবে ব্যবহৃত হতো। এটি 2 March 2026-এ end of life-এ পৌঁছেছে। তাই নতুন installation-এ Alloy ব্যবহার করা হয়। Loki-এর নিজস্ব Docker example-এও এখন Alloy config দেওয়া থাকে।

wget https://raw.githubusercontent.com/grafana/loki/v3.7.0/cmd/loki/loki-local-config.yaml -O loki-config.yaml

ব্যবহারের আগে সেই file পড়ুন। এতে path_prefix: /tmp/loki সেট করা থাকে এবং chunks /tmp/loki/chunks-এর মধ্যে রাখা হয়। Demo-এর জন্য এটি সঠিক, কিন্তু server-এর জন্য নয়। Container পুনরায় তৈরি হলে তার /tmp-এর অধীনে থাকা কিছুই টিকে থাকে না। ফলে পরবর্তী image update-এর সময় আপনার history হারিয়ে যাবে। এটিকে এমন একটি path-এ নির্দেশ করুন, যেটি আপনি mount করেন।

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory
docker volume create loki-data
docker run --name loki -d \
  -v $(pwd):/mnt/config -v loki-data:/loki \
  -p 127.0.0.1:3100:3100 \
  grafana/loki:3.7.0 -config.file=/mnt/config/loki-config.yaml
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3100/ready

শেষের command-টি 200 দেখানো উচিত। Loki traffic গ্রহণের জন্য প্রস্তুত হলে /ready HTTP 200 ফেরত দেয়। অন্য কোনো ফলাফল মানে process এখনও start হচ্ছে অথবা config প্রত্যাখ্যাত হয়েছে। কোনটি ঘটেছে তা docker logs loki জানায়। run command-এর দুটি বিষয় ইচ্ছাকৃতভাবে রাখা হয়েছে। Port-টি শুধু 127.0.0.1-এ publish করা হয়েছে। কারণ sample config-এ auth_enabled: false আছে এবং Loki নিজস্ব user authentication দেয় না। তাই port 3100-এ পৌঁছাতে পারে এমন যে কেউ সব log পড়তে এবং জাল log লিখতে পারে। এটিকে loopback-এ রাখুন, অথবা VPN কিংবা authentication-সমর্থিত reverse proxy-এর পেছনে রাখুন। named volume-টিও গুরুত্বপূর্ণ। কারণ image-টি loki user হিসেবে, UID 10001 দিয়ে চলে। তাই root-এর মালিকানাধীন bind-mounted host directory-তে container লিখতে পারে না।

sudo apt-get update && sudo apt-get install -y gpg wget
sudo mkdir -p /etc/apt/keyrings/
sudo wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key
echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" \
  | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install -y alloy

Alloy /etc/alloy/config.alloy পড়ে। এই config system journal এবং একটি file set পড়ে। এরপর দুটিই local Loki-তে push করে।

loki.write "local" {
  endpoint {
    url = "http://127.0.0.1:3100/loki/api/v1/push"
  }
}

loki.relabel "journal" {
  forward_to = []
  rule {
    source_labels = ["__journal__systemd_unit"]
    target_label  = "unit"
  }
}

loki.source.journal "read" {
  forward_to    = [loki.write.local.receiver]
  relabel_rules = loki.relabel.journal.rules
  labels        = {job = "systemd-journal", host = "app-01"}
}

local.file_match "nginx" {
  path_targets = [{"__path__" = "/var/log/nginx/*.log", "job" = "nginx", "host" = "app-01"}]
}

loki.source.file "nginx" {
  targets    = local.file_match.nginx.targets
  forward_to = [loki.write.local.receiver]
}

relabel rule-টি journal-এর __journal__systemd_unit field-টি unit নামের একটি label-এ কপি করে। পরে {unit="ssh.service"} কাজ করার জন্য এটিই প্রয়োজন। এই rule না থাকলে unit name-টি label-এর বদলে entry-এর ভেতরে থাকে। তাই সেটি দিয়ে select করা যায় না এবং প্রতিটি query-কে সব entry scan করতে হয়।

sudo systemctl reload alloy
systemctl show -p User alloy
sudo journalctl -n 5
sudo -u alloy journalctl -n 5

বেশির ভাগ setup এখানে আটকে যায়। Alloy root হিসেবে নয়, নিজের service account হিসেবে চলে। System journal পড়তে systemd-journal group-এর সদস্যপদ প্রয়োজন। Debian এবং Ubuntu-তে /var/log/nginx-এর অধীনস্থ file-গুলো adm group-এর মালিকানাধীন। শেষের command-এ systemctl show যে account দেখিয়েছে, সেটি বসান। এটি root হিসেবে চালানোর তুলনায় অনেক কম entry ফেরত দিলে বুঝবেন, ওই account system journal পড়তে পারে না। আপনার config সঠিক হলেও Loki ফাঁকা থাকবে। sudo usermod -aG systemd-journal,adm alloy চালিয়ে group যোগ করুন। এরপর sudo systemctl restart alloy দিয়ে restart করুন।

curl -G -s "http://127.0.0.1:3100/loki/api/v1/query_range" \
  --data-urlencode 'query={job="systemd-journal"}' \
  --data-urlencode 'limit=5' | jq '.data.result | length'

0-এর বেশি সংখ্যা মানে ওই label-সহ stream আছে এবং তাতে entry রয়েছে। 0 মানে ওই label-এর অধীনে এখনও কিছু আসেনি। একটি default-এর কারণে সাধারণত ভুল alarm দেখা যায়: loki.source.journal max_age-কে 7h সেট করে। তাই নতুন করে start করলে journal-এর শেষ সাত ঘণ্টা পড়া হয়, তার আগের কিছু নয়। Human interface-এর জন্য একই box-এ Grafana চালান এবং একটি Loki data source-কে http://127.0.0.1:3100.-এ নির্দেশ করুন। Container log-এর জন্য আলাদা source প্রয়োজন। Alloy চলমান Docker container শনাক্ত করে এবং তাদের log tail করে। Loki-এর নিজস্ব getting started example-এও এভাবেই করা হয়। আর একটি VPS-এ single node k3s cluster-এ ওই job kubelet যে pod log directory-তে লেখে, সেখানে চলে যায়।

রিটেনশন: আপনার লগ কত দিন পরে মুছে যাবে, তা নির্ধারণ করুন

ডিস্ক পূর্ণ না হওয়া পর্যন্ত প্রায় কেউই রিটেনশন সময়সীমা নির্ধারণ করে না। তখন service বন্ধ থাকা অবস্থায় রাত 3টায় সেটি নির্ধারণ করতে হয়। প্রথম দিনেই দুটি প্রশ্নের উত্তর দিয়ে সময়সীমা ঠিক করুন: বাস্তবে কত দিন আগের লগ আপনি দেখেন, এবং আগামী মাসে কোনো incident review-এর সময় কোন তথ্য আপনার কাছে থাকতেই হবে। একটি single server-এর জন্য 14 থেকে 30 দিন সাধারণত উভয় প্রয়োজন মেটায়।

আপনি compactor enable না করা পর্যন্ত Loki কিছুই মুছে না। ডিফল্টভাবে retention বন্ধ থাকে। তাই configuration-এ retention_period থাকা সত্ত্বেও volume পূর্ণ হয়ে গেলে অনেকেই অবাক হন।

limits_config:
  retention_period: 744h

compactor:
  working_directory: /loki/retention
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h
  retention_delete_worker_count: 150
  delete_request_store: filesystem

744h হলো 31 দিন। এই block নিয়ন্ত্রণ করে এমন চারটি documented rule আছে:

  • Retention compactor প্রয়োগ করে। Grafana-এর documentation অনুযায়ী compactor-কে single instance হিসেবে চালাতে হবে। একটি VPS-এ এটি স্বয়ংক্রিয়ভাবেই ঘটে।
  • সর্বনিম্ন retention period হলো 24h। Retention কেবল index period 24h হলে কাজ করে। নমুনার schema_config-এ ইতিমধ্যেই period: 24h ব্যবহার করা হয়েছে, তাই এটি অপরিবর্তিত রাখুন।
  • retention_enabled true হলে delete_request_store প্রয়োজন। এটি delete request সংরক্ষণকারী store-এর নাম দেয়। তাই filesystem-backed single node-এ এটি schema-তে থাকা object_store: filesystem-এর সঙ্গে মিলে যায়।
  • প্রথমে chunks-কে marked করা হয় এবং retention_delete_delay পরে সেগুলো সরানো হয়। এখানে retention_delete_delay হলো 2h। তাই policy-তে নির্ধারিত সময়ের পরে free space ফিরে আসে। Reload-এর পাঁচ মিনিট পরের df দেখে setting-এর কার্যকারিতা বিচার করবেন না।

OpenSearch পৃথক line নয়, সম্পূর্ণ index মুছে দেয়। তাই log index প্রতিদিন তৈরি করা হয়। একটি ISM policy index-কে বিভিন্ন state-এর মধ্য দিয়ে পরিচালনা করে এবং যথেষ্ট পুরোনো হলে সেটি মুছে দেয়। একটি ism_template নতুন index-এ policy প্রয়োগ করে, ফলে আপনাকে এটি মনে করে আলাদাভাবে করতে হয় না।

14 দিন পর log index মুছে ফেলার ISM policy
{
  "policy": {
    "description": "delete log indexes after 14 days",
    "default_state": "hot",
    "states": [
      {
        "name": "hot",
        "actions": [],
        "transitions": [
          { "state_name": "delete", "conditions": { "min_index_age": "14d" } }
        ]
      },
      {
        "name": "delete",
        "actions": [ { "delete": {} } ],
        "transitions": []
      }
    ],
    "ism_template": { "index_patterns": ["logs-*"], "priority": 100 }
  }
}

_plugins/_ism/policies/logs-retention-এ PUT request পাঠিয়ে এটি তৈরি করুন। Policy তৈরি হওয়ার পরে যে index তৈরি হবে, template কেবল সেগুলোতে প্রযোজ্য। তাই ডিস্কে আগে থেকেই থাকা index-এ policy হাতে করে প্রয়োগ করতে হবে।

আপনি যে system-ই চালান, free space পরীক্ষা ছাড়া কোনো retention number কার্যকর নয়। 14 দিন পর deletion চালু থাকলেও 10 দিনের log-ই যদি volume পূর্ণ করে ফেলে, তাহলে এটি আপনাকে রক্ষা করবে না। তাই policy-এর সঙ্গে VPS-এ disk health monitoring যুক্ত করুন এবং 80% ব্যবহারে একটি alert সেট করুন।

প্রতি GB log-এর জন্য কত disk দরকার

সঠিক উত্তর আপনার log line ও field-এর ওপর নির্ভর করে। তাই প্রকাশিত কোনো অনুপাতের ওপর নির্ভর না করে নিজের data-তে মাপ নিন। ব্যবহৃত পদ্ধতিগুলোর পার্থক্য এতটাই বেশি যে কোন দিকে পরিবর্তন হবে, তা আগে থেকেই বোঝা যায়। OpenSearch এবং Elasticsearch প্রতিটি indexed field-এর জন্য stored document-এর পাশাপাশি একটি inverted index লেখে। তাই disk-এ জমা হওয়া data raw text-এর চেয়ে বড় হয়। প্রতিটি replica এই পরিমাণ আরও বাড়ায়। একটি single-node deployment-এ replica count 0 রাখুন। একই node-এ থাকা replica shard সেই node নষ্ট হলে টিকে থাকতে পারে না। এটি 1 রেখে দিলে disk usage দ্বিগুণ হয় এবং cluster health স্থায়ীভাবে yellow থাকে। Loki compressed chunk এবং একটি ছোট label index লেখে। তাই এর disk footprint log line-এর compressed size-এর সঙ্গে সামঞ্জস্যপূর্ণ থাকে।

sudo du -sh /var/lib/docker/volumes/loki-data/_data
curl -k -u admin:<password> "https://localhost:9200/_cat/indices?v&h=index,docs.count,store.size"

প্রযোজ্য পদ্ধতিটি পরপর 2 দিন চালান। দুই দিনের ফলাফলের পার্থক্যই আপনার দৈনিক বৃদ্ধির পরিমাণ। এটিকে retention days দিয়ে গুণ করুন। এরপর compaction ও merge-এর জন্য প্রায় 30% অতিরিক্ত headroom যোগ করুন। তারপর মোট পরিমাণটি volume-এর capacity-এর সঙ্গে তুলনা করুন। এতে না ধরলে disk কেনার আগে retention কমান। বড় volume কিনলে একই সমস্যা কেবল কয়েক সপ্তাহ পিছিয়ে যাবে।

ছোট সার্ভারে প্রথমে কী নষ্ট হয়

প্রথমে memory শেষ হয়। Kernel-এর OOM (out of memory) killer একটি বড় process বেছে নেয়, আর logging box-এ সবচেয়ে বড় process সাধারণত JVM। journalctl -k | grep -i "killed process"-এ brackets-এর মধ্যে process name-সহ kill হওয়া দেখানো হয়। ক্ষতিগ্রস্ত process সবসময় log stack হয় না; sshd বা আপনার database-ও নির্বাচিত হতে পারে। এভাবেই logging experiment থেকে সেই application-টি বন্ধ হয়ে যেতে পারে, যেখান থেকে আপনি log সংগ্রহ করতে চেয়েছিলেন। Container-গুলোর জন্য স্পষ্ট ceiling নির্ধারণ করুন, যাতে ব্যর্থতা আপনার নির্ধারিত সীমার মধ্যেই ঘটে। Docker Compose-এ memory limits এই কাজের জন্যই ব্যবহৃত হয়।

দ্বিতীয়ত disk ভরে যায়, এবং search engine-গুলো নির্দিষ্ট ও সহজে শনাক্তযোগ্যভাবে ব্যর্থ হয়। Elasticsearch এবং OpenSearch একাধিক স্তরে disk usage পর্যবেক্ষণ করে। Low watermark হলো 85% এবং high watermark হলো 90%। Flood stage 95%-এ পৌঁছালে ওই node-এ shard থাকা প্রতিটি index-এ index.blocks.read_only_allow_delete block প্রয়োগ করা হয়, এবং এরপর write blocked by: [FORBIDDEN/12/index read-only / allow delete (api)] দিয়ে ব্যর্থ হয়। Usage আবার high watermark-এর নিচে নামলে block সরিয়ে দেওয়া হয়। প্রথমে disk-এর free space তৈরি করুন। Block থেকে গেলে তবেই হাতে করে সেটি সরান।

curl -k -u admin:<password> -X PUT "https://localhost:9200/_all/_settings" \
  -H 'Content-Type: application/json' \
  -d '{"index.blocks.read_only_allow_delete": null}'

Loki আরও নীরবে ব্যর্থ হয়। এতে read-only mode নেই। তাই volume পূর্ণ হলে sender-এ failed push দেখা যায় এবং query result-এ gap তৈরি হয়। Cardinality সমস্যা error হিসেবে নয়, memory ধীরে ধীরে বাড়ার মাধ্যমে প্রকাশ পায়। Incident ঘটার পরে নয়, নির্ধারিত সময়সূচি অনুযায়ী chunks directory-এর size পর্যবেক্ষণ করুন।

শেষ ব্যর্থতা হলো ভুল data প্রবেশ করানো। Log system metrics system নয়। প্রতি 10 সেকেন্ডে নেওয়া CPU load text হিসেবে সংরক্ষণ করা ব্যয়বহুল এবং graph তৈরি করাও অসুবিধাজনক। এই কাজ Ubuntu 24.04-এ একটি Zabbix monitoring server-এর মতো system-এর জন্য। Application exception-এর জন্য grouping, deduplication এবং stack trace view দরকার। এই কাজ একটি self-hosted error tracker-এর। Site আদৌ down আছে কি না জানা আবার আলাদা কাজ। এর জন্য Uptime Kuma-এর মতো uptime ও status page ব্যবহার করা যায়। Log system-এ এমন text line রাখুন, যা কোনো ব্যক্তি পড়বে।

FAQ

এক বা দুটি server-এর জন্য নয়। journalctl ইতিমধ্যে unit, priority, boot এবং time range অনুযায়ী filter করতে পারে, আর rotated file-গুলো grep এবং zgrep-এর উত্তর দেয়। অনেক machine থাকলে, একসঙ্গে সব machine-এ free text search প্রয়োজন হলে, অথবা একাধিক ব্যক্তির shared interface দরকার হলে search cluster-এর অতিরিক্ত memory সার্থক হয়। এর কম পরিসরে size limit ও retention time নির্ধারিত journald কোনো অতিরিক্ত RAM ছাড়াই একই কাজ করে।

self-hosted log management-এর জন্য কত RAM প্রয়োজন?

অনুমানভিত্তিক নিয়মের বদলে প্রতিটি project-এর প্রকাশিত figure ব্যবহার করুন। Loki এবং Alloy হলো Go program; শুরুতেই reserve করে রাখার মতো কোনো heap নেই। Grafana monolithic Loki-এর জন্য প্রতিদিন সর্বোচ্চ আনুমানিক 20GB পর্যন্ত উল্লেখ করেছে। OpenSearch-এর sample compose demo-এর জন্য 512 MB heap এবং production example-এ 2 GB heap নির্ধারণ করে। Elastic বলছে, heap মোট memory-এর 50%-এর বেশি হওয়া যাবে না। তাই Kibana যুক্ত করার আগে 2 GB heap-এর জন্য 4 GB machine প্রয়োজন। Logstash-এর documentation নিজস্ব heap-এর জন্য কমপক্ষে 4GB সুপারিশ করে। এগুলো documented setting, benchmark নয়। তাই sizing plan নির্ধারণের আগে নিজের load মাপুন।

log-এর ক্ষেত্রে Loki এবং OpenSearch-এর প্রকৃত পার্থক্য কী?

Index model। Loki শুধু label index করে এবং log body compressed chunk হিসেবে সংরক্ষণ করে, যা query চালানোর সময় scan করা হয়। তাই write সস্তা, কিন্তু broad query-এর খরচ বেশি। OpenSearch field-এর content index করে। তাই arbitrary full text search দ্রুত হয়, তবে index-এর জন্য memory এবং disk—দুটিই বেশি লাগে। কোন service এবং time window খুঁজবেন তা জানা থাকলে Loki বেছে নিন। আগে থেকে অনুমান করা যায় না এমন text search করতে হলে OpenSearch বেছে নিন।

VPS-এ কত দিন log রাখা উচিত?

Disk নিজে সীমা নির্ধারণ করার আগে সময়সীমা নির্ধারণ করুন। প্রতিটি system-এ এটি ঠিক এক জায়গায় সেট করুন: journald-এর জন্য MaxRetentionSec= এবং SystemMaxUse=, compactor enabled থাকা অবস্থায় Loki-এর জন্য retention_period, এবং OpenSearch-এর জন্য min_index_age-সহ একটি ISM policy। অধিকাংশ single server setup-এর ক্ষেত্রে debugging এবং incident review-এর জন্য 14 থেকে 30 দিন যথেষ্ট। এর চেয়ে বেশি সময় রাখতে হলে server-এর বাইরে সংরক্ষিত একটি copy ব্যবহার করুন। কারণ যে server ব্যর্থ হয়েছে, শুধু সেই server-এ রাখা log কোনো নির্ভরযোগ্য record নয়।

Loki-তে log পাঠানোর জন্য Promtail কি এখনও ব্যবহার করা উচিত?

না। Promtail 2 March 2026-এ end of life-এ পৌঁছেছে এবং Grafana Alloy এখন এর বিকল্প। Loki-এর নিজস্ব Docker install example-এ এখন Alloy configuration দেওয়া হয়। Grafana এমন একটি converter-ও দেয়, যা বিদ্যমান Promtail config-কে Alloy syntax-এ রূপান্তর করে। বিদ্যমান Promtail install চলতে থাকবে, কিন্তু এতে আর fix আসবে না। তাই migration-কে এমন maintenance কাজ হিসেবে বিবেচনা করুন, যা অনির্দিষ্টকাল পিছিয়ে রাখা যায় না।

#logging#loki#opensearch#journald#monitoring