SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor

إدارة السجلات على VPS واحد: ما الخيار المناسب؟

قارن بين journald وlogrotate وLoki وOpenSearch على VPS واحد، مع حدود الذاكرة وقواعد الاحتفاظ التي تحدد ما يمكنك تشغيله فعلياً.

ما الذي تكلّفه إدارة السجلات المستضافة ذاتياً على VPS واحد

تتلخص إدارة السجلات المستضافة ذاتياً على VPS (خادم افتراضي خاص) واحد في سؤال واحد: هل تحتاج إلى عنقود بحث، أم تحتاج إلى تدوير السجلات وأداة grep؟ تبدأ معظم أدلة المورّدين من 3 عُقد و12 GB من RAM قبل إرسال سطر سجل واحد. لا يفيد هذا الجواب على خادم واحد، لذلك تقارن الأقسام التالية الخيارات وفقاً لما يتطلبه كل خيار من خادم صغير قبل أن يخزّن أي بيانات.

إذا كنت تشغّل خادماً واحداً أو خادمين وتريد معرفة ما حدث يوم الثلاثاء الماضي، فإن systemd-journald وlogrotate ينفّذان هذه المهمة بالفعل، ويمكنك التوقف بعد القسم التالي. إذا كان يجب أن تصل سجلات عدة أجهزة إلى مكان واحد مع إمكانية البحث فيها على مدى أسابيع، فإن Grafana Loki يناسب خادماً صغيراً، لأنه يفهرس التسميات لا نص السطور. يوفّر Elasticsearch وOpenSearch بحثاً فعلياً في النص الكامل، لكن ذلك يستهلك الذاكرة، لأن كومة JVM (الآلة الافتراضية لـJava) لها حد أدنى لا يمكنك النزول تحته.

ابدأ بـjournald، لأن معظم المستخدمين يتوقفون هنا

تعمل systemd-journald بالفعل على أي خادم Ubuntu أو Debian حديث. وهي تلتقط المخرجات القياسية لكل وحدة خدمة، ورسائل kernel، وكل ما يُرسل إلى syslog. تغطي أربعة أوامر معظم الحوادث.

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

يطبع الأمر الأخير سطراً مثل Archived and active journals take up 1.1G in the file system.. وهذا هو الرقم الذي يحدد ما إذا كنت تحتاج إلى أي إجراء آخر. إذا كانت القيمة بضعة مئات من الميغابايت، وتمكنت من العثور على ما تحتاج إليه باستخدام -u و--since، فقد انتهيت.

يعتمد بقاء journal بعد إعادة التشغيل على Storage= وعلى وجود /var/log/journal. مع إعداد Storage=auto الشائع، تكتب journald إلى /var/log/journal عند وجود ذلك الدليل، وإلى /run/log/journal عند عدم وجوده. يعتمد /run على الذاكرة، لذلك تُمحى كل السجلات عند إعادة التشغيل على جهاز لا يحتوي على ذلك الدليل. وهذا هو الوقت نفسه الذي تحتاج فيه إلى قراءتها. تتضمن صور Ubuntu الدليل. أما الصور المصغّرة والصور المعتمدة على الحاويات، فكثيراً ما لا تتضمنه.

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

بعد إعادة التشغيل، يجب أن يعرض journalctl --disk-usage حجماً أقل من /var/log/journal بدلاً من /run. الحدود الافتراضية محددة مسبقاً، وهذا هو السبب الرئيسي الذي يجعل journald حلاً فعلياً وليس خياراً احتياطياً. يضبط الدليل journald.conf قيمة SystemMaxUse= على 10% من حجم نظام الملفات، وقيمة SystemKeepFree= على 15%، ويضع حداً أقصى لكل قيمة افتراضية محسوبة يبلغ 4G. تكون القيمة الافتراضية لـSystemMaxFileSize= هي ثُمن SystemMaxUse=، بحد أقصى يبلغ 128M، ولذلك تحتفظ عادةً بسبعة ملفات مدوّرة. وتكون القيمة الافتراضية لـMaxRetentionSec= هي 0، ما يعطّل الحذف المعتمد على العمر. اقرأ هذه القيمة الافتراضية الأخيرة مرة أخرى: يكون journal عند الإعداد الافتراضي محدوداً بالحجم فقط، وليس بالعمر مطلقاً.

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

اكتب ذلك في /etc/systemd/journald.conf.d/99-size.conf، ثم أعد تشغيل journald، وتحقق بعد ذلك من أن journalctl --disk-usage اقترب من الحد الأقصى الجديد. لتحرير المساحة الآن بدلاً من انتظار التدوير التالي، شغّل sudo journalctl --vacuum-size=500M أو sudo journalctl --vacuum-time=14d. يطبع كلا الأمرين كل ملف يحذفانه، ولذلك تعني النتيجة الصامتة عدم وجود شيء لحذفه.

كل ما يقع خارج journal، مثل /var/log/nginx/access.log، من مسؤولية logrotate، وهو يعمل يومياً بواسطة مؤقت systemd. توجد حالة فشل واحدة تستحق المعرفة لأنها تبدو كأنها خطأ في df. بعد التدوير، يختفي الملف القديم من قائمة الدليل، بينما تظل الخدمة محتفظة به مفتوحاً، ولذلك يعرض df -h امتلاء القرص في حين يعرض du -sh /var/log مساحة أقل بكثير. لا تعود المساحة إلا عندما تعيد العملية فتح سجلها، وهذا هو الغرض من سطر إعادة التحميل postrotate في الإعدادات. يعرض sudo lsof -nP +L1 الملفات المحذوفة التي ما زالت مفتوحة، ويذكر العملية التي تحتفظ بكل ملف مفتوحاً. اختبر قاعدة من دون إجراء أي تغيير باستخدام sudo logrotate -d /etc/logrotate.d/nginx.

إرسال السجلات من عدة خوادم إلى جامع واحد

بعد أن يصبح لديك أكثر من خادم، يصبح إدارة عدة خوادم Linux في وقت واحد أسهل عندما تصل سجلاتها إلى مكان واحد. يكون rsyslog مثبتاً مسبقاً في معظم التوزيعات، لذلك فإن أبسط جامع مركزي هو ملف واحد على كل خادم مُرسِل.

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

احفظ ذلك باسم /etc/rsyslog.d/50-forward.conf، وتحقق منه باستخدام sudo rsyslogd -N1، إذ يتحقق من الإعدادات ويخرج من دون بدء أي شيء، ثم أعد تشغيل rsyslog. على الجامع، فعّل إدخال TCP.

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

هناك تحذيران، وكلاهما متعلق بطريقة التشغيل. لا يوفر syslog العادي تشفيراً أو مصادقة، لذلك يمكن لأي جهة تصل إلى المنفذ 514 حقن أسطر سجلات تبدو مطابقة تماماً لأسطرك. اربطه بشبكة خاصة أو VPN، وطبّق قواعد جدار ناري على المنفذ. ثانياً، توجد قائمة انتظار الإجراء الافتراضية في الذاكرة، لذلك عندما يتعذر الوصول إلى الجامع تمتلئ القائمة وتُسقط الرسائل من دون الاحتفاظ بنسخة منها. يوثّق rsyslog قائمة انتظار مساعدة على القرص لهذه الحالة في دليل إعادة التوجيه الموثوق.

لماذا لا تناسب حزمة ELK خادماً افتراضياً خاصاً صغيراً

تعني ELK‏ Elasticsearch للتخزين والبحث، وLogstash لمسار إدخال السجلات، وKibana للواجهة. الحد الأدنى هو ذاكرة JVM heap، وتُحدَّد قبل وصول أي سجل.

توصي وثائق Elastic بتعيين heap بما لا يتجاوز 50% من إجمالي الذاكرة المتاحة لكل عقدة Elasticsearch، لأن العملية تستخدم أيضاً مخازن خارج heap وتعتمد على ذاكرة التخزين المؤقت للملفات في نظام التشغيل لقراءة ملفات الفهارس بسرعة. لذلك، تعني ذاكرة heap بسعة 2 GB الحاجة إلى خادم بسعة 4 GB، قبل تشغيل Kibana، وقبل احتساب ما اشتُري الخادم لتشغيله فعلياً. وتذكر Elastic أيضاً أن Elasticsearch يضبط حجم heap تلقائياً وفق أدوار العقدة وإجمالي الذاكرة، ما يعني أن الخادم الصغير يحصل على heap صغيرة ثم يقضي وقته في جمع البيانات المهملة.

أما Logstash فهو الجزء الذي يستنفد الميزانية الصغيرة بالكامل. توصي صفحة إعدادات JVM الخاصة بـElastic بذاكرة heap لا تقل عن 4GB ولا تتجاوز 8GB للإدخال المعتاد. وهذا يعادل كامل ذاكرة VPS بسعة 4 GB، لعملية واحدة في منتصف المسار.

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
  }
]

هذه هي القيم التي ينشرها كل مشروع في وثائقه الخاصة. وهي ليست قياسات مأخوذة من خادم اختباري، كما أن عبء العمل لديك قد يغيّرها. يعيّن ملف compose النموذجي لـOpenSearch قيمة 512 MB لكل عقدة في العرض التجريبي، وقيمة 2048 MB في مثاله الإنتاجي، بينما يبلغ الحد الأدنى الموصى به لـLogstash 4096 MB. ويعرض عمود heap القيمة 0 لكل من Loki وAlloy لأنهما برنامجان مكتوبان بلغة Go ولا يحتاجان إلى حجز JVM heap. هذا هو الفرق الكامل في رقم واحد: يحجز مكوّن JVM ذاكرته سواء وصلت أي سجلات أم لا.

إذا أردت تشغيل حزمة Elastic على خادم صغير واحد رغم ذلك، فتخلَّ عن Logstash وأرسل السجلات مباشرة إلى Elasticsearch باستخدام جامع خفيف. صُمّم Logstash لتحليل السجلات وتحويلها على نطاق كبير، أما على خادم واحد فيمكن تنفيذ هذا العمل عند الحافة أو تخطيه.

يحتاج كل من Elasticsearch وOpenSearch أيضاً إلى رفع vm.max_map_count إلى 262144، لأنهما يربطان ملفات الفهارس بذاكرة النظام، والحد الافتراضي في Linux منخفض جداً بالنسبة إليهما. وعادةً ما يكون هذا هو السبب الوحيد لخروج حاوية بعد ثوانٍ من بدء التشغيل على خادم جديد.

OpenSearch أم Elasticsearch: أيهما يمكنك نشره؟

تاريخ الترخيص باختصار، لأنّه يحدد ما يُسمح لك بتشغيله. في يناير 2021، نقلت Elastic ترخيص Elasticsearch وKibana من Apache 2.0 إلى نموذج مزدوج يجمع بين SSPL (ترخيص الخادم العام) وElastic License 2.0. أنشأت AWS نسخة متفرعة من آخر إصدار يحمل ترخيص Apache 2.0 وسمّتها OpenSearch، وما زالت تحمل ترخيص Apache 2.0. في سبتمبر 2024، أضافت Elastic ترخيص AGPLv3 (الإصدار 3 من رخصة GNU Affero العامة) خياراً آخر للشفرة المصدرية المجانية. إذا كنت شخصاً واحداً يستضيف الخدمة ذاتياً على VPS واحد، تسمح لك جميع هذه التراخيص بما تفعله. تصبح التراخيص مقيّدة عندما تقدّم البرنامج إلى أشخاص آخرين كخدمة مُدارة.

الفرق العملي على خادم صغير أقل مما يوحي به هذا التاريخ، لأن كليهما يستخدم المحرك نفسه في الأساس. تختلف التسميات: تُسمّى إدارة دورة حياة الفهرس ISM (إدارة حالة الفهرس) في OpenSearch، وتُسمّى ILM (إدارة دورة حياة الفهرس) في Elasticsearch. اعتباراً من أغسطس 2026، يرفض OpenSearch 2.12 والإصدارات الأحدث بدء التشغيل إذا لم تعيّن كلمة مرور المسؤول عند التشغيل الأول.

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 الإعداد الآن، بينما يحفظ الملف في /etc/sysctl.d/ الجزء الذي يستمر بعد إعادة التشغيل. تحقق من بدء تشغيل الحاوية باستخدام curl -k -u admin:<password> https://localhost:9200. تستجيب عبر https باستخدام شهادة تجريبية، لذلك يتجاوز -k التحقق، وتكون الاستجابة السليمة كتلة JSON صغيرة تتضمن اسم العنقود والإصدار. كما تخبر صفحة تثبيت OpenSearch مستخدمي Docker Desktop بالسماح للمضيف بذاكرة لا تقل عن 4 GB، وهذا مؤشر مناسب إلى مقدار الذاكرة الذي تتوقع العملية توفره.

كيف يحافظ Loki على صغر حجمه: التسميات بدلاً من فهرس نص كامل

يحتفظ Loki بفهرس واحد للتسميات، ويخزّن أسطر السجل في كتل مضغوطة. يحدد الاستعلام التدفقات أولاً، ثم يرشّح النص ثانياً. يحدد {unit="ssh.service"} |= "Failed password" التدفق وفق تسميته، ثم يفحص تلك الكتل بحثاً عن السلسلة النصية. لا يفهرس أي مكوّن نص السطر، لذلك تبقى عملية الإدخال منخفضة التكلفة، ولا يوجد فهرس معكوس يجب إبقاؤه في الذاكرة. تنتقل التكلفة إلى وقت الاستعلام، وهذا تبادل مناسب عندما تعرف عادةً الخدمة التي تفحصها.

تضع وثائق Grafana وضع التشغيل الأحادي، أي تشغيل Loki بالكامل في عملية واحدة مع -target=all، ضمن أحجام القراءة والكتابة الصغيرة التي تصل إلى حوالي 20GB يومياً. يقع VPS واحد ضمن هذا النطاق بسهولة.

المشكلة هي تعدد قيم التسميات. كل تركيبة مختلفة من قيم التسميات تمثل تدفقاً واحداً، وعدد التدفقات يحدد حجم ذاكرة Loki وحجم فهرسه. تؤدي التسمية التي تحتوي على عنوان IP لعميل أو معرّف طلب إلى إنشاء تدفق لكل قيمة، لذلك يمكن لخادم ويب مشغول إنشاء عشرات الآلاف من التدفقات خلال يوم واحد، وتستمر العملية في النمو حتى يوقفها kernel. احصر التسميات في قيم يمكنك عدّها على الورق: الوحدة، والمضيف، والمهمة، والمستوى. ضع التفاصيل المتغيرة في السطر نفسه، حيث يعثر عليها تعبير الترشيح وقت الاستعلام.

تثبيت Loki وAlloy على VPS واحد

تتولى عمليتان هذه المهمة. يخزّن Loki السجلات ويجيب عن الاستعلامات. يقرأ Grafana Alloy السجلات ويدفعها إلى Loki. كان Promtail أداة الإرسال المستخدمة سابقاً، وانتهى عمره الافتراضي في 2 March 2026، لذلك تستخدم عمليات التثبيت الجديدة Alloy، كما يتضمن مثال Docker الخاص بـLoki الآن إعداداً لـAlloy.

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

اقرأ هذا الملف قبل استخدامه. فهو يضبط path_prefix: /tmp/loki باستخدام مقاطع ضمن /tmp/loki/chunks، وهذا مناسب للعرض التجريبي وغير مناسب للخادم: لا يبقى أي شيء ضمن /tmp الخاص بالحاوية بعد إعادة إنشاء الحاوية، لذلك يختفي السجل التاريخي عند تحديث الصورة التالي. وجّه الإعداد إلى مسار تقوم بربطه بالحاوية.

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

يجب أن يطبع الأمر الأخير 200، لأن /ready يعيد HTTP 200 عندما يصبح Loki جاهزاً لقبول حركة الشبكة. أي نتيجة أخرى تعني أن العملية لا تزال قيد بدء التشغيل أو أن الإعداد رُفض، ويبيّن docker logs loki السبب. هناك تفصيلان مقصودان في أمر التشغيل. نُشر المنفذ على 127.0.0.1 فقط، لأن نموذج الإعداد يتضمن auth_enabled: false، ولأن Loki لا يوفّر مصادقة للمستخدمين من تلقاء نفسه؛ لذلك يمكن لأي جهة تصل إلى المنفذ 3100 قراءة كل سجل وكتابة سجلات زائفة. أبقه على loopback، أو خلف VPN أو Reverse Proxy يوفّر المصادقة. وتهم وحدة التخزين المسماة لأن الصورة تعمل بالمستخدم loki ذي UID 10001، ولذلك لا تستطيع الحاوية الكتابة إلى مجلد مضيف مرتبط يملكه root.

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. يقرأ هذا الإعداد system journal ومجموعة واحدة من الملفات، ثم يدفع كليهما إلى Loki المحلي.

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]
}

تنسخ قاعدة إعادة التسمية حقل journal __journal__systemd_unit إلى label اسمه unit، وهذا ما يتيح عمل {unit="ssh.service"} لاحقاً. من دون هذه القاعدة يبقى اسم الوحدة داخل الإدخال بدلاً من وجوده في label، ولذلك لا يمكنك التصفية وفقاً له، ويضطر كل استعلام إلى فحص كل شيء.

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

هنا تتعطل معظم عمليات الإعداد. يعمل Alloy بحساب خدمة مستقل، وليس بصلاحيات root، وتتطلب قراءة system journal عضوية في مجموعة systemd-journal، بينما تنتمي الملفات ضمن /var/log/nginx إلى المجموعة adm في Debian وUbuntu. استبدل حساب الخدمة الذي طبعه systemctl show في الأمر الأخير. إذا أعاد عدداً أقل بكثير من الإدخالات مقارنة بالتنفيذ بصلاحيات root، فهذا الحساب لا يستطيع قراءة system journal، وسيظل Loki فارغاً مهما كان إعدادك صحيحاً. أضف المجموعتين ثم أعد التشغيل باستخدام sudo usermod -aG systemd-journal,adm alloy متبوعاً بـsudo systemctl restart alloy.

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 وجود streams تحمل تلك label وتحتوي على إدخالات. أما 0 فيعني أنه لم يصل شيء بعد تحت تلك label. يفسّر إعداد افتراضي واحد إنذاراً شائعاً غير صحيح: يضبط loki.source.journal قيمة max_age على 7h، لذلك تقرأ البداية الجديدة آخر سبع ساعات من journal ولا تقرأ أي شيء أقدم. لتوفير واجهة للمستخدم، شغّل Grafana على الخادم نفسه وأضف مصدر بيانات Loki يشير إلى http://127.0.0.1:3100.. تحتاج سجلات الحاويات إلى مصدر مختلف: يكتشف Alloy حاويات Docker قيد التشغيل ويتابع سجلاتها، وهذا ما يفعله مثال البدء السريع الخاص بـLoki، أما في عنقود k3s أحادي العقدة على VPS فتنتقل هذه المهمة إلى مجلد سجلات pod الذي يكتب فيه kubelet.

الاحتفاظ: حدّد اليوم الذي تنتهي فيه صلاحية سجلاتك

لا يحدّد أحد تقريباً مدة الاحتفاظ قبل امتلاء القرص، ثم يحدّدها عند الساعة 3 صباحاً بينما تكون الخدمة متوقفة. حدّدها في اليوم الأول بناءً على سؤالين: إلى أي مدى زمني تعود فعلياً عند المراجعة، وما الذي يجب أن يظل متاحاً أثناء مراجعة حادثة في الشهر المقبل. بالنسبة إلى خادم واحد، تكفي مدة من 14 إلى 30 يوماً للإجابة عن السؤالين.

لا يحذف Loki أي شيء على الإطلاق قبل تفعيل compactor. يكون الاحتفاظ معطلاً افتراضياً، وهذا يفاجئ من امتلأت وحدة التخزين لديه بينما كان retention_period موجوداً في الإعدادات دون أن يؤدي أي وظيفة.

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 يوماً. وتحكم هذه الكتلة أربع قواعد موثقة:

  • يطبّق compactor سياسة الاحتفاظ، وتذكر وثائق Grafana أن يُشغَّل compactor كنسخة واحدة. يحدث ذلك تلقائياً على VPS واحد.
  • الحد الأدنى لمدة الاحتفاظ هو 24h، ولا تعمل سياسة الاحتفاظ إلا عندما تكون فترة الفهرس 24h. يستخدم المثال schema_config القيمة period: 24h بالفعل، لذا اتركها كما هي.
  • يصبح delete_request_store مطلوباً عندما تكون retention_enabled مضبوطة على true. وهو يحدّد مخزن طلبات الحذف، لذلك يطابق في عقدة واحدة مدعومة بنظام الملفات قيمة object_store: filesystem الموجودة مسبقاً في المخطط.
  • تُعلَّم الأجزاء أولاً، ثم تُحذف بعد retention_delete_delay، وهي 2h هنا، لذلك تتحرّر المساحة لاحقاً مما توحي به السياسة. لا تقيّم الإعداد اعتماداً على df بعد خمس دقائق من إعادة التحميل.

يحذف OpenSearch الفهارس كاملة بدلاً من حذف أسطر منفردة، ولذلك تُنشأ فهارس السجلات يومياً. تنقل سياسة ISM الفهرس بين حالات مختلفة وتحذفه عندما يصبح قديماً بما يكفي، ويربط ism_template السياسة بالفهرس الجديد حتى لا تضطر إلى تذكّر ذلك.

سياسة ISM تحذف فهارس السجلات بعد 14 يوماً
{
  "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 }
  }
}

أنشئها باستخدام PUT إلى _plugins/_ism/policies/logs-retention. يُطبَّق القالب على الفهارس التي تُنشأ بعد وجود السياسة، لذلك يجب ربط السياسة يدوياً بأي فهارس موجودة مسبقاً على القرص.

أيّاً كان النظام الذي تشغّله، فإن مدة الاحتفاظ لا تكون فعّالة إلا بقدر فعالية فحص المساحة الحرة الذي يدعمها. لا ينقذك الحذف بعد 14 يوماً إذا كانت سجلات 10 أيام تملأ وحدة التخزين مسبقاً، لذلك اربط السياسة بـمراقبة سلامة القرص على VPS وأنشئ تنبيهاً عند استخدام 80% من المساحة.

كم يلزم من مساحة القرص لكل GB من السجلات

تعتمد الإجابة الدقيقة على أسطر السجل وحقولها، لذلك قِس ذلك على بياناتك بدلاً من الاعتماد على نسبة منشورة. تختلف الآليات بما يكفي لتحديد الاتجاه المتوقع. يكتب OpenSearch وElasticsearch فهرساً معكوساً لكل حقل مفهرس، إلى جانب المستند المخزّن، لذلك تكون البيانات التي تصل إلى القرص أكبر من النص الخام، وتضاعف كل نسخة متماثلة هذه المساحة. على عقدة واحدة، اضبط عدد النسخ المتماثلة على 0، لأن shard النسخة المتماثلة على العقدة نفسها لا يمكنه البقاء إذا تعطلت تلك العقدة؛ إبقاؤه عند 1 يضاعف مساحة القرص ويُبقي حالة العنقود عند yellow إلى الأبد. يكتب Loki مقاطع مضغوطة وفهرساً صغيراً للتسميات، لذلك تتناسب المساحة التي يشغلها مع الحجم المضغوط للأسطر.

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"

نفّذ الأمر المناسب ليومين متتاليين. يمثّل الفرق بينهما معدل النمو اليومي. اضربه في عدد أيام الاحتفاظ، ثم أضف نحو 30% كهامش احتياطي لعمليات الضغط والدمج، وبعد ذلك قارن الناتج بحجم وحدة التخزين. إذا لم تتسع له، فقلّل مدة الاحتفاظ قبل شراء قرص إضافي، لأن زيادة حجم وحدة التخزين ستؤجل المشكلة نفسها بضعة أسابيع فقط.

ما الذي يتعطل أولاً على خادم صغير

تنفد الذاكرة أولاً. تختار آلية OOM (نفاد الذاكرة) في النواة عملية كبيرة لإنهائها، وتكون أكبر عملية على خادم السجلات هي JVM. يعرض journalctl -k | grep -i "killed process" عملية الإنهاء مع اسم العملية بين قوسين. لا تكون العملية المتضررة دائماً هي مكدس السجلات؛ فقد يقع الاختيار على sshd أو قاعدة بياناتك، وهكذا تؤدي تجربة تسجيل السجلات إلى إسقاط التطبيق الذي أردت جمع سجلاته. ضع حدوداً صريحة لاستهلاك الحاويات، حتى يحدث الفشل في المكان الذي اخترته، وهذا هو الغرض من حدود الذاكرة في Docker Compose.

يمتلئ القرص ثانياً، وتفشل محركات البحث بطريقة محددة يسهل التعرّف إليها. يراقب Elasticsearch وOpenSearch استخدام القرص عند عدة مستويات. تكون العتبة المنخفضة عند 85%، والعتبة العالية عند 90%. عند بلوغ مرحلة الفيضان البالغة 95%، يتلقى كل فهرس يضم shard على تلك العقدة الحظر index.blocks.read_only_allow_delete، ثم تفشل عمليات الكتابة بالخطأ blocked by: [FORBIDDEN/12/index read-only / allow delete (api)]. يُرفع الحظر عندما ينخفض الاستخدام مجدداً إلى أقل من العتبة العالية. وفّر مساحة خالية أولاً، ثم أزل الحظر يدوياً فقط إذا ظل قائماً.

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 بهدوء أكبر. لا يملك وضعاً للقراءة فقط يمكنه الانتقال إليه، لذلك يظهر امتلاء وحدة التخزين على شكل عمليات دفع فاشلة لدى المرسل وفجوات في نتائج الاستعلام، بينما تظهر مشكلة cardinality على شكل ارتفاع بطيء في الذاكرة لا على شكل خطأ. راقب حجم دليل chunks وفق جدول زمني، ولا تنتظر وقوع حادثة.

أما الفشل الأخير فهو إدخال البيانات الخاطئة. نظام السجلات ليس نظام مقاييس: تخزين حمل CPU الذي يُقاس كل 10 ثوانٍ كنص مكلف من حيث التخزين وصعب العرض بيانياً، وهذه المهمة تناسب مثلاً خادم مراقبة Zabbix على Ubuntu 24.04. تحتاج استثناءات التطبيقات إلى التجميع وإزالة التكرار وعرض stack trace، وهذه مهمة متتبّع أخطاء مستضاف ذاتياً. أما معرفة أن الموقع متوقف أصلاً فهي مهمة منفصلة، ويجيب عنها نظام لمراقبة الجاهزية وصفحة حالة مثل Uptime Kuma. أبقِ نظام السجلات مخصصاً لأسطر النص التي سيقرأها شخص.

FAQ

هل أحتاج إلى Elasticsearch للبحث في سجلات خادمي؟

ليس إذا كان لديك خادم واحد أو خادمان. يوفّر journalctl تصفية حسب الوحدة والأولوية والإقلاع والنطاق الزمني، وتتيح الملفات المدورة الإجابة عن grep وzgrep. تصبح مجموعة البحث مفيدة من حيث استهلاك الذاكرة عندما تدير أجهزة كثيرة، أو تحتاج إلى بحث نصي حر في جميع أجهزتك دفعة واحدة، أو يحتاج عدة أشخاص إلى واجهة مشتركة. فيما عدا ذلك، ينجز journald المهمة نفسها مع تحديد للحجم ومدة للاحتفاظ، ومن دون استهلاك إضافي لذاكرة RAM.

ما مقدار RAM الذي أحتاج إليه لإدارة السجلات ذاتياً؟

استخدم الأرقام المنشورة لكل مشروع، ولا تعتمد على قاعدة عامة. Loki وAlloy برنامجان مكتوبان بلغة Go ولا يحتاجان إلى حجز heap مسبقاً، وتوثّق Grafana أن Loki في الوضع الأحادي يمكنه معالجة ما يصل تقريباً إلى 20GB يومياً. يضبط نموذج compose في OpenSearch حجم heap على 512 MB لعرض تجريبي وعلى 2 GB في مثال الإنتاج، وتذكر Elastic أن heap يجب أن يبقى عند 50% أو أقل من إجمالي الذاكرة، ولذلك يعني heap بحجم 2 GB أنك تحتاج إلى جهاز بسعة 4 GB قبل إضافة Kibana. توصي وثائق Logstash بحد أدنى قدره 4GB من heap له وحده. هذه إعدادات موثّقة وليست نتائج اختبار أداء، لذلك قِس حملك الفعلي قبل تحديد مواصفات الخطة.

ما الفرق الفعلي بين Loki وOpenSearch للسجلات؟

نموذج الفهرسة. يفهرس Loki التسميات فقط، ويحتفظ بجسم السجل على شكل أجزاء مضغوطة تُفحَص وقت الاستعلام، لذلك تكون عمليات الكتابة منخفضة التكلفة، بينما ترتفع تكلفة الاستعلامات الواسعة. يفهرس OpenSearch محتوى الحقول، لذلك يكون البحث النصي الكامل العشوائي سريعاً، وتتحمل الذاكرة والقرص تكلفة الفهرس. اختر Loki عندما تعرف الخدمة ونافذة الوقت التي تريدها. اختر OpenSearch عندما تحتاج إلى البحث عن نص لا يمكنك تحديده مسبقاً.

ما مدة الاحتفاظ بالسجلات على VPS؟

حدّد المدة قبل أن يفرضها القرص عليك. اضبطها في مكان واحد فقط لكل نظام: MaxRetentionSec= وSystemMaxUse= بالنسبة إلى journald، وretention_period مع تفعيل compactor بالنسبة إلى Loki، وسياسة ISM تستخدم min_index_age بالنسبة إلى OpenSearch. بالنسبة إلى معظم إعدادات الخادم الواحد، تغطي مدة من 14 إلى 30 يوماً تصحيح الأخطاء ومراجعة الحوادث. يجب تخزين أي بيانات تحتاج إلى الاحتفاظ بها مدة أطول في نسخة خارج الخادم، لأن السجل الموجود فقط على الخادم الذي تعطل لا يُعد سجلاً موثوقاً.

هل ما زال Promtail هو الطريقة المناسبة لإرسال السجلات إلى Loki؟

لا. انتهت دورة حياة Promtail في 2 March 2026، ويحل Grafana Alloy محله. يتضمن مثال التثبيت الخاص بـDocker في Loki الآن إعداداً لـAlloy، وتوفر Grafana محوّلاً يحوّل إعداد Promtail الحالي إلى صيغة Alloy. سيستمر تثبيت Promtail الحالي في العمل، لكنه لن يتلقى إصلاحات، لذلك تعامل مع الترحيل بوصفه صيانة، وليس ترقية يمكنك تأجيلها إلى أجل غير محدد.

#logging#loki#opensearch#journald#مراقبة