> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# OpenSearch: البديل المفتوح لـ Elasticsearch وقصة الترخيص
- URL: https://arabroot.io/articles/opensearch-البديل-المفتوح-ل-elasticsearch/
- Published: 2026-10-12T14:00:00.000Z
- Updated: 2026-10-11T21:16:33.000Z
- Description: لماذا ولد OpenSearch من Elasticsearch، وكيف تشغله على سيرفرك بصلاحيات دقيقة وتنبيهات حقيقية على سجلاتك.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, هندسة البرمجيات وتصميم الأنظمة, قواعد البيانات, الاستضافة الذاتية

في [دليل مجموعة ELK](https://arabroot.io/articles/%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-elk-elasticsearch-%D9%88-logstash-%D9%88-kibana/) جمعنا سجلات nginx في Elasticsearch وبحثنا فيها من Kibana، وذكرنا أن رخصة Elasticsearch تغيرت في 2021 فخرجت من تعريف المصدر المفتوح، وأن AWS فرعت آخر إصدار برخصة Apache 2.0 فولد OpenSearch. وفي هذا الدليل نأخذ نفس السجلات بالضبط ونضعها في OpenSearch، ونرى ما الذي بقي كما هو، وما الذي اختلف بعد خمس سنوات من التفرع، ولماذا قد يكون هو الخيار الأنسب لمؤسستك أو لا يكون.

وسوف نناقش في هذا المقال ما يلي:

- قصة التفرع Fork من AWS إلى OpenSearch Software Foundation في Linux Foundation، ورخصة Apache 2.0.
- مكونات OpenSearch: السيرفر وOpenSearch Dashboards والإضافات Plugins مثل Security وAlerting وk-NN.
- التوافق مع Elasticsearch اليوم، وما الذي لا ينتقل بينهما.
- تجربة على نفس سجلات nginx: Fluent Bit يرسل، ومسار إدخال Ingest Pipeline يحلل، والإضافة Security تعطي كل حساب ما يحتاجه فقط، وتنبيه حقيقي يصل إلى Webhook.
- الأخطاء الشائعة، وأهمها كلمات المرور الافتراضية في إعداد التجربة Demo Configuration.

## قصة التفرع: من AWS إلى Linux Foundation

في 21 يناير 2021، بعد أسبوع من إعلان Elastic تغيير الرخصة، كتبت AWS أنها [سوف تفرع Elasticsearch وKibana](https://aws.amazon.com/blogs/opensource/stepping-up-for-a-truly-open-source-elasticsearch/?ref=arabroot.io) من آخر إصدار برخصة Apache وهو 7.10، ثم في 12 أبريل 2021 [أعلنت مشروع OpenSearch](https://aws.amazon.com/blogs/opensource/introducing-opensearch/?ref=arabroot.io): سيرفر OpenSearch مشتق من Elasticsearch 7.10.2، وواجهة OpenSearch Dashboards مشتقة من Kibana 7.10.2، ومعهما الإضافات التي كانت في توزيعة Open Distro for Elasticsearch مثل الصلاحيات والتنبيهات وإدارة دورة حياة ال Indices، وكل ذلك برخصة Apache 2.0\. وصدر الإصدار 1.0 في [12 يوليو 2021](https://opensearch.org/blog/opensearch-general-availability-announcement/?ref=arabroot.io).

وكان السؤال الذي يطرحه الجميع: هل هو مشروع مجتمعي أم مشروع AWS باسم آخر؟ والإجابة جاءت في 16 سبتمبر 2024 حين [أعلنت Linux Foundation](https://www.linuxfoundation.org/press/linux-foundation-announces-opensearch-software-foundation-to-foster-open-collaboration-in-search-and-analytics?ref=arabroot.io) إنشاء OpenSearch Software Foundation ونقلت AWS المشروع إليها، فأصبحت العلامة والقرار في مؤسسة محايدة لها مجلس إدارة من عدة شركات، وهو نفس الطريق الذي سلكه Valkey وOpenTofu كما في [مقال مشاريع Linux Foundation](https://arabroot.io/articles/%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-linux-foundation-%D8%AE%D8%A7%D8%B1%D8%AC-cncf/). والصورة التالية تبين الفرعين:

![خط زمني بفرعين من دائرة خضراء مكتوب فيها 7.10.2 وApache 2.0: الفرع الأعلى Elasticsearch بمحطات 7.11 فبراير 2021 SSPL أو ELv2، ثم 8.16 نوفمبر 2024 تضاف AGPLv3، ثم 9.5 في 2026، والفرع الأسفل OpenSearch بمحطات 1.0 يوليو 2021 و2.0 مايو 2022 وسبتمبر 2024 تنتقل إلى Linux Foundation و3.0 مايو 2025 و3.9 سبتمبر 2026 ومكتوب Apache 2.0 في كل الإصدارات](https://arabroot.io/content/images/2026/10/opensearch-01-fork.webp)

فرعان من نفس الكود، وكل منهما مشروع مستقل اليوم

والرخصة اليوم كما هي منذ البداية، فملف [LICENSE.txt في المستودع](https://github.com/opensearch-project/OpenSearch/blob/main/LICENSE.txt?ref=arabroot.io) هو Apache License 2.0، والإضافات الرسمية كذلك، ولا توجد طبقة مدفوعة داخل البرنامج نفسه، والسبب في أهمية ذلك أن الرخصة المتساهلة Permissive تسمح لك بتقديمه خدمة لعملائك أو تضمينه في منتجك دون شروط، وقد شرحنا أنواع الرخص في [مقال الرخص](https://arabroot.io/articles/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D9%88%D8%AA%D8%B1%D8%A7%D8%AE%D9%8A%D8%B5%D9%87%D8%A7/).

## مكونات OpenSearch

- **OpenSearch:** السيرفر نفسه، مبني على Apache Lucene مثل Elasticsearch، بنفس مفاهيم ال Index وال Mapping وال Shards التي شرحناها في [دليل ELK](https://arabroot.io/articles/%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-elk-elasticsearch-%D9%88-logstash-%D9%88-kibana/#concepts)، ونفس شكل ال REST API في أغلب العمليات اليومية.
- **OpenSearch Dashboards:** واجهة الويب، ومنها Discover واللوحات، وصفحات الإضافات مثل التنبيهات وإدارة الصلاحيات.
- **Security:** إضافة الصلاحيات، وفيها المستخدمون والأدوار Roles، وصلاحيات على مستوى ال Index والمستند Document-Level Security والحقل [Field-Level Security](https://docs.opensearch.org/latest/security/access-control/field-level-security/?ref=arabroot.io)، وتسجيل التدقيق Audit Logs، والربط مع LDAP وSAML وOpenID Connect.
- **Alerting وNotifications:** [Monitors](https://docs.opensearch.org/latest/observing-your-data/alerting/index/?ref=arabroot.io) تشغل استعلاماً على جدول زمني وتطلق تنبيهاً عند شرط، و[قنوات إشعار](https://docs.opensearch.org/latest/notifications-plugin/index/?ref=arabroot.io) مثل Webhook والبريد وSlack.
- **Index State Management:** واختصاراً [ISM](https://docs.opensearch.org/latest/im-plugin/ism/index/?ref=arabroot.io)، وهو مقابل ILM في Elasticsearch: حالات وانتقالات بين Hot وDelete وغيرها.
- **k-NN والبحث بالمتجهات:** [Vector Search](https://docs.opensearch.org/latest/vector-search/?ref=arabroot.io) بفهارس HNSW عبر Faiss أو Lucene، ومعها البحث الهجين Hybrid Search، وهي التي نستخدمها في [مقال البحث الدلالي](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A7%D9%84%D8%AF%D9%84%D8%A7%D9%84%D9%8A-semantic-search-%D8%A8%D8%A7%D9%84-embeddings/).
- **Data Prepper:** بديل Logstash من نفس المشروع، وهو خيار إذا احتجت معالجة كبيرة قبل الفهرسة، أما في التجربة فاكتفينا بـ Fluent Bit ومسار إدخال داخل السيرفر.

وعند تشغيل الأمر `_cat/plugins` على Docker Image الرسمية بالإصدار 3.9.0 سوف تجد أنها تأتي بكل الإضافات جاهزة:

```text
opensearch-alerting           opensearch-knn                  opensearch-observability
opensearch-anomaly-detection  opensearch-ltr                  opensearch-performance-analyzer
opensearch-asynchronous-search opensearch-ml                  opensearch-reports-scheduler
opensearch-cross-cluster-replication opensearch-neural-search opensearch-search-relevance
opensearch-custom-codecs      opensearch-notifications        opensearch-security
opensearch-flow-framework     opensearch-notifications-core   opensearch-security-analytics
opensearch-geospatial         opensearch-index-management     opensearch-skills
opensearch-job-scheduler      opensearch-sql                  opensearch-system-templates
opensearch-ubi                query-insights
```

## التوافق مع Elasticsearch اليوم

قد يتساءل البعض: هل أستطيع نقل تطبيقي من Elasticsearch إلى OpenSearch بتغيير العنوان فقط؟ والإجابة أن ذلك كان صحيحاً تقريباً في 2021، ثم ابتعد المشروعان مع كل إصدار، والتفصيل كما يلي:

- **ال API الأساسية:** البحث والفهرسة وال Mapping وأغلب استعلامات Query DSL والتجميعات بنفس الشكل، وقد استخدمنا في التجربة نفس استعلامات دليل ELK ونفس المحلل `arabic` دون تغيير.
- **مكتبات العملاء Clients:** مكتبات Elasticsearch الرسمية منذ الإصدار 7.14 تتحقق من أن السيرفر Elasticsearch فعلاً، فمكتبة Python مثلاً [ترفع الخطأ UnsupportedProductError](https://github.com/elastic/elasticsearch-py/releases/tag/v7.14.0?ref=arabroot.io) إذا لم يكن كذلك، لذلك استخدم [مكتبات OpenSearch](https://docs.opensearch.org/latest/clients/?ref=arabroot.io) لكل لغة.
- **أدوات الجمع:** Logstash يحتاج [مخرج OpenSearch الخاص](https://docs.opensearch.org/latest/tools/logstash/index/?ref=arabroot.io)، وFluent Bit فيه [مخرج opensearch](https://docs.fluentbit.io/manual/pipeline/outputs/opensearch?ref=arabroot.io) مدمج، وهذا ما استخدمناه.
- **ما لا ينتقل:** لا تعتمد على استعادة لقطات Snapshots من Elasticsearch 8 أو 9 في OpenSearch، ولا اللوحات المحفوظة من Kibana الحديثة، ولا الميزات الخاصة بكل طرف مثل ES|QL في Elasticsearch أو PPL في OpenSearch، والطريق الآمن للانتقال هو إعادة الفهرسة من المصدر.

## متى تختار OpenSearch ومتى تختار Elasticsearch؟

**نقاط القوة:** رخصة Apache 2.0 للكل دون طبقة مدفوعة، فالصلاحيات على مستوى الحقل والتنبيهات والدخول الموحد SAML وOpenID Connect كلها موجودة في نفس ال Docker Image، ومؤسسة محايدة تملك العلامة، ومزودو سحابة متعددون يقدمونه خدمة مدارة إذا احتجت ذلك لاحقاً.

**نقاط الضعف:** أغلب الأدلة والإجابات على الإنترنت مكتوبة لـ Elasticsearch، وبعضها لا ينطبق، وتكاملات Elastic Agent وFleet الجاهزة لعشرات الأنظمة لا توجد بنفس الشكل، وواجهة Dashboards أبطأ تطوراً من Kibana في بعض المجالات مثل لوحات المراقبة الجاهزة.

والقاعدة العملية: إذا كنت تبني منتجاً تبيعه وفيه محرك البحث، أو كانت سياسة مؤسستك تشترط رخصة معتمدة من OSI في التوزيعة نفسها، أو تحتاج الصلاحيات على مستوى الحقل دون اشتراك، فاختر OpenSearch. وإذا كان فريقك يعرف Elastic جيداً ويعتمد على Elastic Agent وتكاملاته، فـ Elasticsearch بالتوزيعة الافتراضية مناسب للاستخدام الداخلي.

## التجربة: نفس سجلات nginx في OpenSearch

نفس nginx ونفس مولد الطلبات من دليل ELK، ولكن بدل Filebeat وLogstash نستخدم Fluent Bit 5.0.11 يرسل السطر كما هو، ويتولى مسار إدخال Ingest Pipeline داخل OpenSearch تحليله. والإصدارات: OpenSearch وOpenSearch Dashboards بالإصدار 3.9.0، وكل المنافذ على `127.0.0.1`، وكل ال Containers تبدأ بـ `lab-os-`. ولا تشغل هذه التجربة وتجربة ELK في نفس الوقت على جهاز صغير، لأن كل واحد منهما يحجز ذاكرة Java كاملة. والصورة التالية تبين ما سوف نبنيه:

![مسار مرقم: nginx إلى Fluent Bit بالمستخدم log_shipper، ثم إلى صندوق OpenSearch 3.9 مع الإضافة security، وفيه Ingest Pipeline باسم nginx-access يحلل السطر ثم Data Stream باسم logs-nginx-lab مع ISM، والدوران log_writer للكتابة فقط وshop_developer للقراءة دون عنوان IP، وMonitor كل دقيقة يسأل هل هناك أكثر من 20 خطأ 5xx، ثم Dashboards في المتصفح، وهاتف يستقبل webhook من lab-os-hook](https://arabroot.io/content/images/2026/10/opensearch-02-lab.webp)

التحليل والصلاحيات والتنبيه كلها داخل OpenSearch

### ملف Docker Compose

```yaml
# OpenSearch lab: the same nginx traffic as the ELK lab, shipped by Fluent Bit,
# parsed by an OpenSearch ingest pipeline, protected by the security plugin.
# Every port is bound to 127.0.0.1 only. Run: docker compose -p lab-os up -d
name: lab-os

services:
  node:
    image: opensearchproject/opensearch:3.9.0
    container_name: lab-os-node
    environment:
      - cluster.name=lab-os
      - node.name=lab-os-node
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${ADMIN_PASSWORD}
    ulimits:
      memlock: { soft: -1, hard: -1 }
      nofile: { soft: 65536, hard: 65536 }
    mem_limit: 1536m
    volumes:
      - os-data:/usr/share/opensearch/data
    ports:
      - "127.0.0.1:19201:9200"

  dashboards:
    image: opensearchproject/opensearch-dashboards:3.9.0
    container_name: lab-os-dashboards
    depends_on: [node]
    environment:
      - OPENSEARCH_HOSTS=["https://lab-os-node:9200"]
    mem_limit: 1g
    ports:
      - "127.0.0.1:15602:5601"

  nginx:
    image: nginx:1.31.6-alpine
    container_name: lab-os-nginx
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - nginx-logs:/var/log/lab

  fluentbit:
    image: fluent/fluent-bit:5.0.11
    container_name: lab-os-fluentbit
    depends_on: [node, nginx]
    environment:
      - SHIPPER_PASSWORD=${SHIPPER_PASSWORD}
    volumes:
      - ./fluent-bit/fluent-bit.yaml:/fluent-bit/etc/fluent-bit.yaml:ro
      - nginx-logs:/var/log/lab:ro
    command: ["/fluent-bit/bin/fluent-bit", "-c", "/fluent-bit/etc/fluent-bit.yaml"]

  # receives the alert webhook, so we can see what OpenSearch sends
  hook:
    image: python:3.13.16-slim
    container_name: lab-os-hook
    volumes:
      - ./hook/hook.py:/hook.py:ro
    command: ["python", "-u", "/hook.py"]

volumes:
  os-data:
  nginx-logs:
```

في الإعداد أعلاه لاحظ التالي:

- الذاكرة `-Xms512m -Xmx512m` هي القيمة التي يستخدمها مثال Docker Compose في [توثيق التثبيت بـ Docker](https://docs.opensearch.org/latest/install-and-configure/install-opensearch/docker/?ref=arabroot.io)، وهي تكفي لتجربة ولا تكفي للإنتاج.
- منذ OpenSearch 2.12 لا يبدأ إعداد التجربة إلا بكلمة مرور قوية للمستخدم `admin` في المتغير `OPENSEARCH_INITIAL_ADMIN_PASSWORD`، ولا توجد كلمة مرور افتراضية للمدير.
- الإضافة Security مفعلة مع شهادات تجريبية ذاتية التوقيع Self-signed، لذلك يعمل المنفذ بـ HTTPS ونستخدم `curl -k` في التجربة فقط، وفي الإنتاج ضع شهاداتك كما في [دليل الشهادات](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A%D8%A9-tls-%D9%88-x509-%D9%88%D8%B5%D9%8A%D8%BA-pem-%D9%88-der/).
- ال Container باسم `lab-os-hook` برنامج Python من عشرة أسطر يطبع كل طلب POST يصله، حتى نرى التنبيه بأعيننا.

إعداد Fluent Bit:

```yaml
service:
  flush: 2
  log_level: info

pipeline:
  inputs:
    - name: tail
      path: /var/log/lab/access.log
      tag: nginx.access
      read_from_head: true
  filters:
    - name: modify
      match: "*"
      add: service.name shop
  outputs:
    - name: opensearch
      match: "*"
      host: lab-os-node
      port: 9200
      tls: on
      tls.verify: off           # lab only: the demo certificate is self-signed
      http_user: log_shipper
      http_passwd: ${SHIPPER_PASSWORD}
      index: logs-nginx-lab
      write_operation: create   # data streams accept "create" only
      pipeline: nginx-access    # parsing happens inside OpenSearch
      suppress_type_name: on
      replace_dots: off
      retry_limit: 5
```

لاحظ `write_operation: create`، لأن ال Data Stream لا يقبل إلا إضافة مستندات جديدة، و`pipeline: nginx-access` الذي يطلب من OpenSearch تمرير كل مستند على مسار الإدخال قبل الفهرسة.

### مسار الإدخال وISM والقالب والأدوار

والملف `setup.sh` كاملاً، وهو يقابل `setup.sh` و`nginx.conf` في دليل ELK معاً:

```bash
#!/usr/bin/env bash
# Prepares OpenSearch: an ingest pipeline that parses nginx lines, an ISM policy, a data stream template,
# and two roles: one that can only write logs and one that reads them without the client IP.
set -euo pipefail
source .env
OS=https://127.0.0.1:19201
os() { curl -sk -u "admin:$ADMIN_PASSWORD" -H 'Content-Type: application/json' "$@"; echo; }

# 1) parse the combined log line inside OpenSearch (instead of Logstash)
os -X PUT "$OS/_ingest/pipeline/nginx-access" -d '{
  "description": "nginx combined log to structured fields",
  "processors": [
    { "grok":   { "field": "log", "patterns": ["%{COMBINEDAPACHELOG}"] } },
    { "date":   { "field": "timestamp", "formats": ["dd/MMM/yyyy:HH:mm:ss Z"], "target_field": "@timestamp" } },
    { "rename": { "field": "clientip", "target_field": "source.address" } },
    { "rename": { "field": "verb", "target_field": "http.request.method" } },
    { "rename": { "field": "request", "target_field": "url.original" } },
    { "convert":{ "field": "response", "type": "integer", "target_field": "http.response.status_code" } },
    { "rename": { "field": "log", "target_field": "message" } },
    { "grok":   { "if": "ctx.url?.original != null && ctx.url.original.startsWith(\"/search?\")",
                  "field": "url.original", "patterns": ["^%{URIPATH:url.path}\\?%{GREEDYDATA:url.query}"] } },
    { "kv":     { "if": "ctx.url?.query != null", "field": "url.query", "field_split": "&", "value_split": "=",
                  "target_field": "_tmp", "include_keys": ["q"] } },
    { "gsub":   { "if": "ctx._tmp?.q != null", "field": "_tmp.q", "pattern": "\\+", "replacement": " " } },
    { "urldecode": { "if": "ctx._tmp?.q != null", "field": "_tmp.q", "target_field": "search_query" } },
    { "user_agent": { "field": "agent", "target_field": "user_agent", "ignore_missing": true } },
    { "remove": { "field": ["_tmp", "timestamp", "response", "ident", "auth", "httpversion", "agent", "referrer", "bytes"],
                  "ignore_missing": true } }
  ],
  "on_failure": [ { "set": { "field": "error.message", "value": "{{ _ingest.on_failure_message }}" } } ]
}'

# 2) lifecycle with ISM: roll over daily or at 10 GB, delete after 30 days
os -X PUT "$OS/_plugins/_ism/policies/nginx-logs" -d '{
  "policy": {
    "description": "nginx logs: 30 days",
    "default_state": "hot",
    "states": [
      { "name": "hot", "actions": [ { "rollover": { "min_index_age": "1d", "min_primary_shard_size": "10gb" } } ],
        "transitions": [ { "state_name": "delete", "conditions": { "min_index_age": "30d" } } ] },
      { "name": "delete", "actions": [ { "delete": {} } ], "transitions": [] }
    ],
    "ism_template": [ { "index_patterns": [".ds-logs-nginx-*"], "priority": 100 } ]
  }
}'

# 3) the data stream template, with the Arabic field
os -X PUT "$OS/_index_template/logs-nginx" -d '{
  "index_patterns": ["logs-nginx-*"],
  "data_stream": {},
  "priority": 200,
  "template": {
    "settings": { "number_of_shards": 1, "number_of_replicas": 0 },
    "mappings": {
      "dynamic": false,
      "properties": {
        "@timestamp":   { "type": "date" },
        "message":      { "type": "text" },
        "tags":         { "type": "keyword" },
        "error":        { "properties": { "message": { "type": "text" } } },
        "service":      { "properties": { "name": { "type": "keyword" } } },
        "source":       { "properties": { "address": { "type": "ip" } } },
        "http": { "properties": {
          "request":  { "properties": { "method": { "type": "keyword" } } },
          "response": { "properties": { "status_code": { "type": "short" } } } } },
        "url": { "properties": { "original": { "type": "keyword" }, "path": { "type": "keyword" } } },
        "user_agent": { "properties": { "name": { "type": "keyword" }, "os": { "properties": { "name": { "type": "keyword" } } } } },
        "search_query": { "type": "text", "analyzer": "arabic", "fields": { "raw": { "type": "keyword" } } }
      }
    }
  }
}'

# 4) security: a shipper that can only write, and a developer role that reads without the client IP
os -X PUT "$OS/_plugins/_security/api/roles/log_writer" -d '{
  "cluster_permissions": ["cluster_monitor", "indices:data/write/bulk"],
  "index_permissions": [{ "index_patterns": ["logs-nginx-*"],
                          "allowed_actions": ["create_index", "indices:admin/mapping/put", "indices:data/write/index", "indices:data/write/bulk*"] }]
}'
os -X PUT "$OS/_plugins/_security/api/internalusers/log_shipper" -d "{\"password\":\"$SHIPPER_PASSWORD\"}"
os -X PUT "$OS/_plugins/_security/api/rolesmapping/log_writer" -d '{"users":["log_shipper"]}'

os -X PUT "$OS/_plugins/_security/api/roles/shop_developer" -d '{
  "cluster_permissions": ["cluster_composite_ops_ro"],
  "index_permissions": [{ "index_patterns": ["logs-nginx-*", ".ds-logs-nginx-*"],
                          "allowed_actions": ["read"],
                          "fls": ["~source.address", "~message"] }]
}'
os -X PUT "$OS/_plugins/_security/api/internalusers/developer1" -d "{\"password\":\"$READER_PASSWORD\"}"
os -X PUT "$OS/_plugins/_security/api/rolesmapping/shop_developer" -d '{"users":["developer1"]}'
```

والمخرج سوف يكون كما يلي:

```text
{"acknowledged":true}
{"_id":"nginx-logs","_version":1,"_primary_term":1,"_seq_no":0,"policy":{"policy":{"policy_id":"nginx-logs", ...
{"acknowledged":true}
{"status":"CREATED","message":"'log_writer' created."}
{"status":"CREATED","message":"'log_shipper' created."}
{"status":"CREATED","message":"'log_writer' created."}
{"status":"CREATED","message":"'shop_developer' created."}
{"status":"CREATED","message":"'developer1' created."}
{"status":"CREATED","message":"'shop_developer' created."}
```

في الإعداد أعلاه لاحظ التالي:

- مسار الإدخال [Ingest Pipeline](https://docs.opensearch.org/latest/ingest-pipelines/?ref=arabroot.io) فيه نفس خطوات Logstash: `grok` ثم `date` ثم `kv` و`urldecode` لكلمات البحث، ثم `user_agent`، والفرق أن التحليل يأخذ من معالج OpenSearch وليس من سيرفر منفصل، فإذا كانت السجلات كثيرة جداً فضع العقد Nodes الخاصة بالإدخال Ingest Nodes منفصلة.
- الكتلة `on_failure` تضع رسالة الخطأ في الحقل `error.message` بدل أن يرفض المستند، وقد عرفنا الحقل في القالب حتى نستطيع البحث عنه، وهذا الدرس الذي تعلمناه في [دليل ELK](https://arabroot.io/articles/%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-elk-elasticsearch-%D9%88-logstash-%D9%88-kibana/#break-grok).
- سياسة ISM فيها `ism_template` فتطبق تلقائياً على كل Backing Index جديد يبدأ بـ `.ds-logs-nginx-`.
- الدور `shop_developer` فيه `"fls": ["~source.address", "~message"]`، أي أن المطور يقرأ كل الحقول إلا عنوان الزائر والسطر الخام الذي يحتوي العنوان أيضاً، والسبب أن العنوان بيانات شخصية لا يحتاجها من يصلح خطأ 503.

### كلمات المرور الافتراضية في إعداد التجربة

قبل أن نكمل، لننظر إلى المستخدمين الذين أنشأهم إعداد التجربة Demo Configuration تلقائياً، ثم نجرب الدخول بأسمائهم كلمات مرور:

```bash
curl -sk -u "admin:$ADMIN_PASSWORD" "https://127.0.0.1:19201/_plugins/_security/api/internalusers" | python -c "import sys,json; print(sorted(json.load(sys.stdin).keys()))"
curl -sk -o /dev/null -w "%{http_code}\n" -u kibanaserver:kibanaserver https://127.0.0.1:19201/
curl -sk -o /dev/null -w "%{http_code}\n" -u logstash:logstash https://127.0.0.1:19201/
```

```text
['admin', 'anomalyadmin', 'developer1', 'kibanaro', 'kibanaserver', 'log_shipper', 'logstash', 'readall', 'snapshotrestore']
200
200
```

المستخدمان `kibanaserver` و`logstash` دخلا بكلمة مرور تساوي الاسم، والسبب أن [إعداد التجربة](https://docs.opensearch.org/latest/security/configuration/demo-configuration/?ref=arabroot.io) مصمم للاختبار، وكلمة المرور القوية الإلزامية تخص `admin` وحده. لذلك لا تستخدم إعداد التجربة في الإنتاج أبداً، وإنما ابدأ بـ `DISABLE_INSTALL_DEMO_CONFIG=true` وملف `internal_users.yml` خاص بك وشهاداتك، أو على الأقل غير كلمات مرور هؤلاء المستخدمين واحذف من لا تحتاجه منهم.

### تشغيل الطلبات والبحث

```bash
docker compose -p lab-os up -d
docker run --rm --name lab-os-traffic --network lab-os_default \
  -v "$PWD/traffic:/app:ro" python:3.13.16-slim python /app/traffic.py 240
```

```text
done: 1044 iterations in 240s
```

والمستند كما خزنه OpenSearch بعد مسار الإدخال:

```json
{
  "service.name" : "shop",
  "source" : { "address" : "192.0.2.24" },
  "message" : "192.0.2.24 - - [11/Oct/2026:11:29:58 +0000] \"GET /search?q=%D9%83%D8%AA%D8%A8+%D8%A7%D9%84%D8%A3%D8%B7%D9%81%D8%A7%D9%84 HTTP/1.1\" 200 8 ...",
  "search_query" : "كتب الأطفال",
  "url" : { "path" : "/search", "original" : "/search?q=%D9%83...", "query" : "q=%D9%83..." },
  "@timestamp" : "2026-10-11T11:29:58.000Z",
  "http" : { "request" : { "method" : "GET" }, "response" : { "status_code" : 200 } },
  "user_agent" : { "os" : { "name" : "Android", "version" : "16" }, "name" : "Chrome Mobile", "device" : { "name" : "Pixel 9" } }
}
```

ونفس استعلامات دليل ELK تعمل كما هي، فهذا توزيع رموز الحالة، ثم البحث بالمحلل العربي عن «مستشفى»:

```text
{"aggregations":{"s":{"buckets":[{"key":200,"doc_count":909},{"key":503,"doc_count":166},{"key":404,"doc_count":75},{"key":401,"doc_count":56}]}}}
```

```json
{
  "hits" : { "total" : { "value" : 57 } },
  "aggregations" : { "q" : { "buckets" : [
    { "key" : "المستشفى التخصصي", "doc_count" : 29 },
    { "key" : "مستشفيات الرياض", "doc_count" : 28 }
  ] } }
}
```

نفس 57 طلباً تقريباً بنفس التوزيع، لأن مولد الطلبات يستخدم بذرة عشوائية Seed ثابتة، والفرق البسيط في الأعداد من اختلاف التوقيت بين التشغيلين. ولم يفشل أي سطر في مسار الإدخال، فعدد المستندات التي فيها `error.message` صفر. وهذه شاشة Discover في OpenSearch Dashboards على نفس البيانات، ويظهر فيها الارتفاع وقت تعطل `/api/orders`:

![شاشة Discover في OpenSearch Dashboards على النمط logs-nginx-* بين 14:25:50 و14:30:10، رسم بياني بأعمدة لكل خمس ثوان يرتفع إلى قرابة 45 طلباً حول 14:28، وتحته جدول بأعمدة source.address وhttp.request.method وurl.original وhttp.response.status_code، منها 203.0.113.66 يطلب /wp-login.php و/.env برمز 404](https://arabroot.io/content/images/2026/10/opensearch-04-dashboards-discover.webp)

1206 طلبات، والارتفاع في المنتصف هو تعطل /api/orders

### هل يرى كل حساب ما يحتاجه فقط؟

نسأل بحساب المطور `developer1` عن طلب دخول فاشل، ثم نحاول التجميع على عنوان IP، ثم الحذف، ثم نسأل بحساب Fluent Bit:

```bash
curl -sk -u "developer1:$READER_PASSWORD" -H 'Content-Type: application/json' \
  "https://127.0.0.1:19201/logs-nginx-lab/_search?pretty&filter_path=hits.hits._source" \
  -d '{"size":1,"query":{"term":{"http.response.status_code":401}}}'
```

```json
"_source" : {
  "service.name" : "shop",
  "source" : { },
  "url" : { "original" : "/login" },
  "@timestamp" : "2026-10-11T11:29:58.000Z",
  "http" : { "request" : { "method" : "POST" }, "response" : { "status_code" : 401 } },
  "user_agent" : { "os" : { "name" : "Linux" }, "name" : "Firefox", "device" : { "name" : "Other" }, "version" : "144.0." }
}
```

```text
# developer1: terms aggregation on source.address
{"aggregations":{"ip":{"doc_count_error_upper_bound":0,"sum_other_doc_count":0,"buckets":[]}}}
# developer1: DELETE _data_stream/logs-nginx-lab
no permissions for [indices:admin/data_stream/delete] and User [name=developer1, backend_roles=[], requestedTenant=null]
# log_shipper: _search
no permissions for [indices:data/read/search] and User [name=log_shipper, backend_roles=[], requestedTenant=null]
```

في المخرج أعلاه لاحظ التالي: المستند وصل للمطور دون `source.address` ودون `message`، والتجميع على العنوان رجع فارغاً فلا يستطيع استنتاج العناوين بطريق آخر، والحذف مرفوض، وحساب Fluent Bit لا يقرأ شيئاً. والصورة التالية تضع المستندين بجانب بعضهما:

![لوحتان لنفس المستند: يسار admin يرى @timestamp وsource.address وmessage باللون الأحمر وhttp.request.method POST وurl.original /login وstatus_code 401 وuser_agent.name Firefox، وفي الوسط قفل أخضر، ويمين developer1 بالدور shop_developer يرى نفس الحقول ولكن source فارغ وmessage مخفي](https://arabroot.io/content/images/2026/10/opensearch-03-fls.webp)

Field-Level Security تخفي الحقل من البحث والتجميع معاً

### تنبيه حقيقي: ارتفاع أخطاء 5xx

الآن الشيء الذي لا تعطيك إياه اللوحة وحدها: أن يصلك خبر عندما يتعطل شيء وأنت لا تنظر. الملف `alert.sh` ينشئ قناة Webhook ثم Monitor يعمل كل دقيقة ويعد أخطاء 5xx في الدقيقة الأخيرة:

```bash
#!/usr/bin/env bash
# An alert: if the shop returns more than 20 server errors (5xx) within one minute, call the webhook.
set -euo pipefail
source .env
OS=https://127.0.0.1:19201
os() { curl -sk -u "admin:$ADMIN_PASSWORD" -H 'Content-Type: application/json' "$@"; echo; }

# 1) where the alert goes: a webhook channel in the Notifications plugin
os -X POST "$OS/_plugins/_notifications/configs" -d '{
  "config_id": "ops-webhook",
  "config": { "name": "ops webhook", "description": "lab receiver", "config_type": "webhook",
              "is_enabled": true, "webhook": { "url": "http://lab-os-hook:8080/alert" } }
}'

# 2) the monitor: runs every minute, counts 5xx in the last minute, triggers above 20
os -X POST "$OS/_plugins/_alerting/monitors" -d '{
  "type": "monitor",
  "monitor_type": "query_level_monitor",
  "name": "shop 5xx spike",
  "enabled": true,
  "schedule": { "period": { "interval": 1, "unit": "MINUTES" } },
  "inputs": [ { "search": {
    "indices": ["logs-nginx-*"],
    "query": { "size": 0, "query": { "bool": { "filter": [
      { "range": { "http.response.status_code": { "gte": 500 } } },
      { "range": { "@timestamp": { "gte": "{{period_end}}||-1m", "lte": "{{period_end}}", "format": "epoch_millis" } } }
    ] } } }
  } } ],
  "triggers": [ { "query_level_trigger": {
    "name": "more than 20 server errors",
    "severity": "1",
    "condition": { "script": { "source": "ctx.results[0].hits.total.value > 20", "lang": "painless" } },
    "actions": [ { "name": "notify ops",
                   "destination_id": "ops-webhook",
                   "message_template": { "source": "{{ctx.monitor.name}}: {{ctx.results.0.hits.total.value}} errors between {{ctx.periodStart}} and {{ctx.periodEnd}}" },
                   "throttle_enabled": true, "throttle": { "value": 10, "unit": "MINUTES" } } ]
  } } ]
}'
```

وأثناء التعطل الذي يصنعه مولد الطلبات في منتصف المدة وصل هذا إلى `lab-os-hook`:

```bash
docker logs lab-os-hook
```

```text
ALERT RECEIVED: shop 5xx spike: 103 errors between 2026-10-11T11:27:44.418Z and 2026-10-11T11:28:44.418Z
192.168.48.3 - - [11/Oct/2026 11:28:45] "POST /alert HTTP/1.1" 200 -
```

و`_plugins/_alerting/monitors/alerts` يبين التنبيه وأن التكرار التالي كتم بسبب `throttle`، لأننا طلبنا ألا يتكرر الإشعار أكثر من مرة في عشر دقائق:

```text
ACTIVE more than 20 server errors 1791718125471 [{'action_id': '5_y2KqEBv8MSRh1SLFtg', 'last_execution_time': 1791718125363, 'throttled_count': 1}]
```

![صفحة Alerting في OpenSearch Dashboards بتبويب Alerts وجدول Alerts by triggers فيه تنبيه واحد اسم المشغل more than 20 server errors بدأ 10/11/26 2:28 pm وآخر تحديث 2:29 pm بشدة 1 من المراقب shop 5xx spike](https://arabroot.io/content/images/2026/10/opensearch-05-alerting.webp)

التنبيه في صفحة Alerting، وانتهى بعد أن عادت الطلبات طبيعية

والرقم 20 هنا مختار للتجربة، أما في الإنتاج فابدأ من متوسط أخطائك الحقيقي في الأسبوع الماضي، واجعل الحد أعلى منه بوضوح، وإلا تعود الفريق على تجاهل التنبيهات كما شرحنا في [قسم إرهاق التنبيهات](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D9%85%D8%AD%D8%B1%D9%83-%D9%83%D8%B4%D9%81-siem-%D9%85%D9%86-%D8%A7%D9%84%D8%AF%D8%A7%D8%AE%D9%84/#fatigue).

## التشغيل اليومي

- **الذاكرة:** [توثيق التثبيت](https://docs.opensearch.org/latest/install-and-configure/install-opensearch/index/?ref=arabroot.io) يوصي بأن يكون ال Heap نصف ذاكرة السيرفر، والباقي لذاكرة الملفات، والقيمة الافتراضية دون تحديد هي 1 جيجابايت، وإذا استخدمت k-NN فالفهارس الشعاعية تحجز ذاكرة إضافية خارج ال Heap.
- **النسخ الاحتياطي:** Snapshots إلى مستودع مشترك أو تخزين S3، مع سياسة لقطات من إضافة Index Management، واختبر الاستعادة كما في [قاعدة 3-2-1](https://arabroot.io/articles/%D8%A7%D9%84%D9%86%D8%B3%D8%AE-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%8A%D8%A7%D8%B7%D9%8A-%D8%A8%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-3-2-1-%D9%88%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%B9%D8%A7%D8%AF%D8%A9/).
- **الترقية:** بين الإصدارات الكبيرة اقرأ ملاحظات الإصدار، فـ OpenSearch 3 يقرأ Indices أنشئت في 2.x فقط بحسب `minimum_index_compatibility_version` الذي ظهر في أول طلب، وما أنشئ في 1.x يحتاج إعادة فهرسة قبل الترقية.
- **الدخول الموحد:** اربط Dashboards بـ OpenID Connect من مزود هويتك مثل [authentik](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-authentik-%D9%84%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D8%A7%D9%84%D9%85%D9%88%D8%AD%D8%AF/)، واجعل الأدوار تأتي من مجموعات المزود Backend Roles بدل إنشاء مستخدمين داخليين لكل شخص.

## أخطاء شائعة

- **تشغيل إعداد التجربة في الإنتاج:** رأينا أن `kibanaserver:kibanaserver` يدخل، وأن الشهادات ذاتية التوقيع، وهذا يكفي ليصل أي شخص في الشبكة إلى السجلات.
- **تعطيل الإضافة Security لتسهيل البداية:** المتغير `DISABLE_SECURITY_PLUGIN=true` يترك المنفذ 9200 مفتوحاً بلا حساب، ولا مكان له خارج تجربة على `127.0.0.1`.
- **استخدام مكتبة Elasticsearch مع OpenSearch:** تعمل الإصدارات القديمة منها ثم تتوقف عند أول ترقية، فاستخدم مكتبة OpenSearch من البداية.
- **نسيان `write_operation: create` مع Data Stream:** ال Data Stream يرفض عملية `index` العادية، فتظهر أخطاء في سجل Fluent Bit ولا يصل شيء.
- **إخفاء الحقل ونسيان نسخته الأخرى:** إذا أخفيت `source.address` وتركت `message` فالعنوان ما زال في السطر الخام، لذلك أخفينا الاثنين.
- **تنبيه بلا `throttle`:** Monitor كل دقيقة دون كتم يرسل ستين رسالة في ساعة التعطل الواحدة.

## تدريب عملي

1. أضف إلى الدور `shop_developer` قاعدة Document-Level Security تجعله يرى طلبات `/api/` فقط، ثم تحقق بنفس الاستعلام.
2. غير شرط التنبيه ليستخدم نسبة الأخطاء من كل الطلبات بدل العدد، ثم شغل مولد الطلبات مرة ثانية وقارن.

## حذف المختبر

```bash
docker compose -p lab-os down -v
rm .env
# optional
docker rmi opensearchproject/opensearch:3.9.0 opensearchproject/opensearch-dashboards:3.9.0 fluent/fluent-bit:5.0.11
```

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- OpenSearch تفرع من Elasticsearch 7.10.2 في 2021، ويدار اليوم في OpenSearch Software Foundation التابعة لـ Linux Foundation، وكل شيء فيه برخصة Apache 2.0.
- ال API الأساسية متقاربة، فنفس الاستعلامات والمحلل العربي عملت دون تغيير، ولكن مكتبات العملاء واللقطات واللوحات لا تنتقل بين المشروعين.
- الإضافة Security تعطي كل حساب أقل صلاحية، وتخفي الحقول الحساسة من البحث والتجميع، وكلمات مرور إعداد التجربة الافتراضية لا مكان لها في الإنتاج.
- Alerting مع Notifications يحول البحث إلى تنبيه يصلك، ومعه `throttle` حتى لا تغرق في الرسائل.
- اختره إذا كانت الرخصة مهمة لك أو تحتاج الصلاحيات المتقدمة دون اشتراك، واختر Elasticsearch إذا كان فريقك يعتمد على Elastic Agent وتكاملاته.

[![](https://arabroot.io/content/images/2026/10/elk-stack-cover-cover.webp)دليلمجموعة ELK: Elasticsearch وLogstash وKibanaكيف تجمع سجلات سيرفراتك في Elasticsearch وتبحث فيها من Kibana بأمان، وما الذي تعنيه رخصته لك اليوم.](https://arabroot.io/articles/%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A9-elk-elasticsearch-%D9%88-logstash-%D9%88-kibana/)[![](https://arabroot.io/content/images/2026/10/semantic-search-cover-cover.webp)دليلالبحث الدلالي Semantic Search بالـ Embeddingsمتى يتفوق البحث بالمعنى على البحث بالكلمات في النص العربي، وكيف تقيس ذلك على أسئلتك قبل أن تختار.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A7%D9%84%D8%AF%D9%84%D8%A7%D9%84%D9%8A-semantic-search-%D8%A8%D8%A7%D9%84-embeddings/)[![](https://arabroot.io/content/images/2026/10/logging-basics-cover-cover.webp)دليلالسجلات Logging للمطورين: ماذا تسجل وماذا لا تسجلكيف تكتب سجلات Logs تساعدك على فهم ما حدث في الإنتاج، وما الذي لا يدخل السجل أبداً، وكيف تجمعها في مكان واحد.](https://arabroot.io/articles/%D8%A7%D9%84%D8%B3%D8%AC%D9%84%D8%A7%D8%AA-logging-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D9%88%D9%85%D8%AF%D9%8A%D8%B1%D9%8A-%D8%A7%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA/)[![](https://arabroot.io/content/images/2026/10/arabic-search-cover-cover.webp)دليلالبحث في النصوص العربية مع OpenSearch وMeilisearchلماذا يرجع البحث صفحة فارغة لمن يكتب «مستشفي» أو «اجازه»، وكيف تضبط محركك حتى يجد الكلمة بكل أشكالها.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D9%81%D9%8A-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D8%A7%D9%84%D8%B9%D8%B1%D8%A8%D9%8A%D8%A9-%D9%85%D8%B9-opensearch-%D9%88-meilisearch/)

## سجل التحديثات

- أكتوبر 2026: كتابة الدليل، وتجربة على OpenSearch وOpenSearch Dashboards 3.9.0 مع Fluent Bit 5.0.11 ونفس سجلات nginx في دليل ELK، والتحقق من قصة التفرع من إعلانات AWS وLinux Foundation وملف الرخصة في المستودع.