قم تلقائيًا بتحديث Elastic Stack باستخدام كتيبات التشغيل Ansible
نشرت: 2022-03-11كان تحليل السجل لشبكة مكونة من آلاف الأجهزة مهمة معقدة ومستهلكة للوقت ومملة قبل أن أقرر استخدام Elastic Stack كحل تسجيل مركزي. ثبت أنه قرار حكيم للغاية. لا أمتلك مكانًا واحدًا للبحث عن جميع سجلاتي فحسب ، بل أحصل على نتائج فورية تقريبًا لعمليات البحث ، وتصورات قوية مفيدة للغاية للتحليل واستكشاف الأخطاء وإصلاحها ، ولوحات معلومات جميلة تعطيني نظرة عامة مفيدة على الشبكة.
يطلق Elastic Stack باستمرار ميزات جديدة ومذهلة ، ويحافظ على وتيرة تطوير نشطة للغاية ، وغالبًا ما يقدم إصدارين جديدين كل شهر. أرغب دائمًا في تحديث بيئتي للتأكد من أنه يمكنني الاستفادة من الميزات والتحسينات الجديدة. وأيضًا لإبقائها خالية من الأخطاء ومشكلات الأمان. لكن هذا يتطلب مني تحديث البيئة باستمرار.
على الرغم من أن موقع Elastic يحتفظ بوثائق واضحة ومفصلة ، بما في ذلك عملية ترقية منتجاتهم ، فإن الترقية اليدوية مهمة معقدة ، لا سيما مجموعة Elasticsearch. هناك الكثير من الخطوات المتضمنة ، ويجب اتباع ترتيب محدد للغاية. لهذا السبب قررت أتمتة العملية برمتها ، منذ فترة طويلة ، باستخدام Ansible Playbooks.
في هذا البرنامج التعليمي Ansible ، سوف أطلعنا على سلسلة من Ansible Playbooks التي تم تطويرها للترقية التلقائية لتثبيت Elastic Stack الخاص بي.
ما هو المكدس المرن
يتكون Elastic Stack ، المعروف سابقًا باسم ELK stack ، من Elasticsearch و Logstash و Kibana ، من شركة Elastic مفتوحة المصدر ، والتي توفر معًا منصة قوية لفهرسة بياناتك والبحث فيها وتحليلها. يمكن استخدامه لمجموعة واسعة من التطبيقات. من التسجيل وتحليل الأمان إلى إدارة أداء التطبيق والبحث في الموقع.
Elasticsearch هو جوهر المكدس. إنه محرك بحث وتحليلات موزع قادر على تقديم نتائج بحث في الوقت الفعلي تقريبًا حتى مع وجود حجم ضخم من البيانات المخزنة.
Logstash عبارة عن خط أنابيب معالجة يحصل على البيانات أو يستقبلها من العديد من المصادر المختلفة (50 مكونًا إضافيًا للإدخال الرسمي أثناء كتابتي) ، وتحليلها وتصفيتها وتحويلها وإرسالها إلى واحد أو أكثر من المخرجات المحتملة. في حالتنا ، نحن مهتمون بالمكوِّن الإضافي لإخراج Elasticsearch.
Kibana هي واجهة المستخدم والعملية. يتيح لك تصور بياناتك والبحث فيها والتنقل فيها وإنشاء لوحات معلومات تمنحك رؤى مذهلة عنها.
ما هو أنسبل
Ansible عبارة عن نظام أساسي لأتمتة تكنولوجيا المعلومات يمكن استخدامه لتكوين الأنظمة ونشر البرامج أو ترقيتها وتنسيق مهام تكنولوجيا المعلومات المعقدة. أهدافها الرئيسية هي البساطة وسهولة الاستخدام. الميزة المفضلة لدي في Ansible هي أنه بدون وكيل ، مما يعني أنني لست بحاجة إلى تثبيت وإدارة أي برامج إضافية على الأجهزة المضيفة والأجهزة التي أرغب في إدارتها. سنستخدم قوة أتمتة Ansible لترقية Elastic Stack تلقائيًا.
إخلاء المسؤولية وكلمة تحذير
تستند كتيبات التشغيل التي سأشاركها هنا إلى الخطوات الموضحة في وثائق المنتج الرسمية. من المفترض استخدامه فقط للترقيات من نفس الإصدار الرئيسي. على سبيل المثال: 5.x
→ 5.y
أو 6.x
→ 6.y
حيث x
> y
. غالبًا ما تتطلب الترقيات بين الإصدارات الرئيسية خطوات إضافية ولن تعمل كتيبات اللعبة هذه مع تلك الحالات.
بصرف النظر عن ذلك ، اقرأ دائمًا ملاحظات الإصدار ، خاصةً قسم التغييرات الفاصلة قبل استخدام كتيبات التشغيل للترقية. تأكد من فهمك للمهام المنفذة في كتيبات التشغيل وتحقق دائمًا من إرشادات الترقية للتأكد من عدم تغيير أي شيء مهم.
بعد قولي هذا ، كنت أستخدم كتيبات التشغيل هذه (أو الإصدارات السابقة) منذ الإصدار 2.2 من Elasticsearch دون أي مشاكل. في ذلك الوقت ، كان لدي كتيبات لعب منفصلة تمامًا لكل منتج ، حيث لم يشاركوا نفس رقم الإصدار كما يعرفون.
بعد قولي هذا ، لست مسؤولاً بأي شكل من الأشكال عن استخدامك للمعلومات الواردة في هذه المقالة.
بيئتنا الوهمية
ستتكون البيئة التي سيتم تشغيل كتيبات اللعبة على أساسها من 6 خوادم CentOS 7:
- 1 × خادم Logstash
- 1 × خادم Kibana
- 4 × عقد Elasticsearch
لا يهم ما إذا كانت بيئتك بها عدد مختلف من الخوادم. يمكنك ببساطة عكسها وفقًا لذلك في ملف الجرد ويجب تشغيل كتيبات التشغيل دون مشكلة. ومع ذلك ، إذا كنت لا تستخدم توزيعًا قائمًا على RHEL ، فسأتركه كتدريب لك لتغيير المهام القليلة الخاصة بالتوزيع (بشكل أساسي عناصر مدير الحزم)
المخزون
يحتاج Ansible إلى قائمة جرد لمعرفة المضيفين الذين يجب تشغيل كتيبات اللعبة في مقابلهم. أربعة سيناريو وهمي لدينا سنستخدم ملف الجرد التالي:
[logstash] server01 ansible_host=10.0.0.1 [kibana] server02 ansible_host=10.0.0.2 [elasticsearch] server03 ansible_host=10.0.0.3 server04 ansible_host=10.0.0.4 server05 ansible_host=10.0.0.5 server06 ansible_host=10.0.0.6
في ملف الجرد Ansible ، يمثل أي [section]
مجموعة من المضيفين. يحتوي مخزوننا على 3 مجموعات من المضيفين: logstash
و kibana
و elasticsearch
. ستلاحظ أنني أستخدم أسماء المجموعات في كتيبات اللعب فقط. هذا يعني أنه لا يهم عدد المضيفين في المخزون ، طالما أن المجموعات صحيحة ، فسيتم تشغيل دليل التشغيل.
عملية الترقية
ستتألف عملية الترقية من الخطوات التالية:
1) قم بتنزيل الحزم مسبقًا
2) ترقية Logstash
3) التحديث المتداول لمجموعة Elasticsearch
4) ترقية Kibana
الهدف الرئيسي هو تقليل وقت التوقف عن العمل. في معظم الأحيان لن يلاحظ المستخدم. في بعض الأحيان قد لا يكون Kibana متاحًا لبضع ثوان. هذا مقبول بالنسبة لي.
دليل أنسبل الرئيسي
تتكون عملية الترقية من مجموعة من كتيبات اللعبة المختلفة. سأستخدم ميزة import_playbook في Ansible لتنظيم جميع كتيبات اللعبة في ملف playbook رئيسي واحد يمكن استدعاؤه لرعاية العملية برمتها.
- name: pre-download import_playbook: pre-download.yml - name: logstash-upgrade import_playbook: logstash-upgrade.yml - name: elasticsearch-rolling-upgrade import_playbook: elasticsearch-rolling-upgrade.yml - name: kibana-upgrade import_playbook: kibana-upgrade.yml
بسيطة الى حد كبير. إنها مجرد طريقة لتنظيم تنفيذ كتيبات اللعبة بترتيب واحد محدد.
الآن ، دعنا نفكر في كيفية استخدام مثال Ansible playbook أعلاه. سأشرح كيف نطبقه لاحقًا ، ولكن هذا هو الأمر الذي سأقوم بتنفيذه للترقية إلى الإصدار 6.5.4:
$ ansible-playbook -i inventory -e elk_version=6.5.4 main.yml
قم بتنزيل الحزم مسبقًا
هذه الخطوة الأولى اختيارية في الواقع. سبب استخدامي لهذا هو أنني أعتبر ممارسة جيدة بشكل عام لإيقاف خدمة قيد التشغيل قبل ترقيتها. الآن ، إذا كان لديك اتصال إنترنت سريع ، فقد يكون الوقت الذي يستغرقه مدير الحزم لتنزيل الحزمة ضئيلاً. ولكن هذا ليس هو الحال دائمًا وأريد تقليل مقدار الوقت الذي تتعطل فيه أي خدمة. هذه هي الطريقة التي سيستخدم بها كتاب اللعب الأول yum لتنزيل جميع الحزم مسبقًا. بهذه الطريقة عندما تأتي أوقات الترقية ، تم بالفعل الاهتمام بخطوة التنزيل.
- hosts: logstash gather_facts: no tasks: - name: Validate logstash Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get logstash current version command: rpm -q logstash --qf %{VERSION} args: warn: no changed_when: False register: version_found - name: Pre-download logstash install package yum: name: logstash-{{ elk_version }} download_only: yes when: version_found.stdout is version_compare(elk_version, '<')
يشير السطر الأول إلى أن هذه المسرحية ستنطبق فقط على مجموعة logstash
. يخبر السطر الثاني أنسبل ألا يكلف نفسه عناء جمع الحقائق عن المضيفين. سيؤدي ذلك إلى تسريع اللعب ، ولكن تأكد من عدم احتياج أي من المهام في المسرحية إلى أي حقائق عن المضيف.
ستتحقق المهمة الأولى في المسرحية من صحة متغير elk_version
. يمثل هذا المتغير إصدار Elastic Stack الذي نقوم بالترقية إليه. يتم تمرير ذلك عندما تستدعي الأمر ansible-playbook. إذا لم يتم تمرير المتغير أو لم يكن تنسيقًا صالحًا ، فسيتم إنهاء اللعب على الفور. ستكون هذه المهمة في الواقع المهمة الأولى في جميع المسرحيات. والسبب في ذلك هو السماح بتنفيذ المسرحيات بشكل منفصل عند الرغبة أو الضرورة.
ستستخدم المهمة الثانية الأمر rpm
للحصول على الإصدار الحالي من Logstash والتسجيل في المتغير version_found
. سيتم استخدام هذه المعلومات في المهمة التالية. تشير السطور إلى ما args:
، warn: no
، وتغيرت changed_when: False
فالخطأ موجود لإضفاء السعادة على الحروف ، ولكنها ليست ضرورية تمامًا.
ستقوم المهمة الأخيرة بتنفيذ الأمر الذي يقوم بالفعل بتنزيل الحزمة مسبقًا. ولكن فقط إذا كان الإصدار المثبت من Logstash أقدم من الإصدار المستهدف. ليس نقطة التنزيل والإصدار الأقدم أو نفسه إذا لم يتم استخدامه.
المسرحتان الأخريان متماثلتان بشكل أساسي ، باستثناء أنهما بدلاً من Logstash ، سيتم تنزيل Elasticsearch و Kibana مسبقًا:
- hosts: elasticsearch gather_facts: no tasks: - name: Validate elasticsearch Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get elasticsearch current version command: rpm -q elasticsearch --qf %{VERSION} args: warn: no changed_when: False register: version_found - name: Pre-download elasticsearch install package yum: name: elasticsearch-{{ elk_version }} download_only: yes when: version_found.stdout is version_compare(elk_version, '<') - hosts: kibana gather_facts: no tasks: - name: Validate kibana Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get kibana current version command: rpm -q kibana --qf %{VERSION} args: warn: no changed_when: False register: version_found - name: Pre-download kibana install package yum: name: kibana-{{ elk_version }} download_only: yes when: version_found.stdout is version_compare(elk_version, '<')
ترقية Logstash
يجب أن يكون Logstash هو المكون الأول المطلوب ترقيته. وذلك لأن Logstash مضمون للعمل مع إصدار أقدم من Elasticsearch.
تتطابق المهام الأولى للمسرحية مع نظيرتها التي تم تنزيلها مسبقًا:
- name: Upgrade logstash hosts: logstash gather_facts: no tasks: - name: Validate ELK Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get logstash current version command: rpm -q logstash --qf %{VERSION} changed_when: False register: version_found
يتم تضمين المهمتين النهائيتين في كتلة:
- block: - name: Update logstash yum: name: logstash-{{ elk_version }} state: present - name: Restart logstash systemd: name: logstash state: restarted enabled: yes daemon_reload: yes when: version_found.stdout is version_compare(elk_version, '<')
يضمن الشرط when
تنفيذ المهام في الكتلة فقط إذا كان الإصدار الهدف أحدث من الإصدار الحالي. تقوم المهمة الأولى داخل الكتلة بإجراء ترقية Logstash والمهمة الثانية إعادة تشغيل الخدمة.

ترقية المتداول العنقودية Elasticsearch
للتأكد من أنه لن يكون هناك توقف لمجموعة Elasticsearch ، يجب علينا إجراء ترقية متجددة. هذا يعني أننا سنقوم بترقية عقدة واحدة في كل مرة ، ونبدأ فقط في ترقية أي عقدة بعد التأكد من أن المجموعة في حالة خضراء (صحية تمامًا).
من بداية المسرحية ستلاحظ شيئًا مختلفًا:
- name: Elasticsearch rolling upgrade hosts: elasticsearch gather_facts: no serial: 1
هنا لدينا خط serial: 1
. السلوك الافتراضي لـ Ansible هو تنفيذ اللعب ضد مضيفين متعددين بالتوازي ، وهو عدد المضيفين المتزامنين المحدد في التكوين. يضمن هذا الخط أن المسرحية ستنفذ ضد مضيف واحد فقط في كل مرة.
بعد ذلك ، نحدد بعض المتغيرات لاستخدامها على طول المسرحية:
vars: es_disable_allocation: '{"transient":{"cluster.routing.allocation.enable":"none"}}' es_enable_allocation: '{"transient":{"cluster.routing.allocation.enable": "all","cluster.routing.allocation.node_concurrent_recoveries": 5,"indices.recovery.max_bytes_per_sec": "500mb"}}' es_http_port: 9200 es_transport_port: 9300
سيكون معنى كل متغير واضحًا كما يظهر في المسرحية.
كما هو الحال دائمًا ، فإن المهمة الأولى هي التحقق من صحة الإصدار المستهدف:
tasks: - name: Validate ELK Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")
ستتألف العديد من المهام التالية من تنفيذ استدعاءات REST ضد مجموعة Elasticsearch. يمكن تنفيذ المكالمة على أي من العقد. يمكنك ببساطة تنفيذه ضد المضيف الحالي في المسرحية ، ولكن سيتم تنفيذ بعض الأوامر أثناء تعطيل خدمة Elasticsearch للمضيف الحالي. لذلك ، في المهام التالية ، نتأكد من تحديد مضيف مختلف لتشغيل مكالمات REST ضده. لهذا ، سوف نستخدم وحدة set_fact ومتغير المجموعات من مخزون Ansible.
- name: Set the es_host for the first host set_fact: es_host: "{{ groups.elasticsearch[1] }}" when: "inventory_hostname == groups.elasticsearch[0]" - name: Set the es_host for the remaining hosts set_fact: es_host: "{{ groups.elasticsearch[0] }}" when: "inventory_hostname != groups.elasticsearch[0]"
بعد ذلك ، نتأكد من أن الخدمة موجودة في العقدة الحالية قبل المتابعة:
- name: Ensure elasticsearch service is running systemd: name: elasticsearch enabled: yes state: started register: response - name: Wait for elasticsearch node to come back up if it was stopped wait_for: port: "{{ es_transport_port }}" delay: 45 when: response.changed == true
كما هو الحال في المسرحيات السابقة ، سوف نتحقق من الإصدار الحالي. باستثناء هذا الوقت ، سوف نستخدم Elasticsearch REST API بدلاً من تشغيل rpm. كان بإمكاننا أيضًا استخدام الأمر rpm ، لكنني أريد إظهار هذا البديل.
- name: Check current version uri: url: http://localhost:{{ es_http_port }} method: GET register: version_found retries: 10 delay: 10
المهام المتبقية موجودة داخل كتلة سيتم تنفيذها فقط إذا كان الإصدار الحالي أقدم من الإصدار المستهدف:
- block: - name: Enable shard allocation for the cluster uri: url: http://localhost:{{ es_http_port }}/_cluster/settings method: PUT body_format: json body: "{{ es_enable_allocation }}"
الآن ، إذا اتبعت نصيحتي وقرأت الوثائق ، فستلاحظ أن هذه الخطوة يجب أن تكون عكس ذلك: لتعطيل تخصيص الأجزاء. أود وضع هذه المهمة هنا أولاً في حالة تعطيل القطع من قبل لسبب ما. هذا مهم لأن المهمة التالية ستنتظر حتى تصبح الكتلة خضراء. إذا تم تعطيل تخصيص الجزء ، فستظل المجموعة صفراء وستتوقف المهام حتى تنتهي مهلتها.
لذلك ، بعد التأكد من تمكين تخصيص الأجزاء ، نتأكد من أن المجموعة في حالة خضراء:
- name: Wait for cluster health to return to green uri: url: http://localhost:{{ es_http_port }}/_cluster/health method: GET register: response until: "response.json.status == 'green'" retries: 500 delay: 15
بعد إعادة تشغيل خدمة العقدة ، يمكن أن تستغرق المجموعة وقتًا طويلاً للعودة إلى اللون الأخضر. هذا هو سبب retries: 500
delay: 15
. هذا يعني أننا سننتظر 125 دقيقة (500 × 15 ثانية) حتى تعود الكتلة إلى اللون الأخضر. قد تحتاج إلى تعديل ذلك إذا كانت العقد الخاصة بك تحتوي على كمية هائلة من البيانات. بالنسبة لغالبية الحالات ، فإن الأمر أكثر من كافٍ.
الآن يمكننا تعطيل تخصيص الجزء:
- name: Disable shard allocation for the cluster uri: url: http://localhost:{{ es_http_port }}/_cluster/settings method: PUT body_format: json body: {{ es_disable_allocation }}
وقبل إيقاف تشغيل الخدمة ، نقوم بتنفيذ تدفق المزامنة الاختياري ، ولكن الموصى به. ليس من غير المألوف الحصول على خطأ 409 لبعض المؤشرات عند إجراء تدفق المزامنة. نظرًا لأن هذا أمر آمن للتجاهل ، فقد أضفت 409 إلى قائمة أكواد حالة النجاح.
- name: Perform a synced flush uri: url: http://localhost:{{ es_http_port }}/_flush/synced method: POST status_code: "200, 409"
الآن ، هذه العقدة جاهزة للترقية:
- name: Shutdown elasticsearch node systemd: name: elasticsearch state: stopped - name: Update elasticsearch yum: name: elasticsearch-{{ elk_version }} state: present
مع توقف الخدمة ، ننتظر حتى يتم تخصيص جميع الأجزاء قبل بدء العقدة مرة أخرى:
- name: Wait for all shards to be reallocated uri: url=http://{{ es_host }}:{{ es_http_port }}/_cluster/health method=GET register: response until: "response.json.relocating_shards == 0" retries: 20 delay: 15
بعد إعادة تخصيص الأجزاء ، نعيد تشغيل خدمة Elasticsearch وننتظر حتى تصبح جاهزة تمامًا:
- name: Start elasticsearch systemd: name: elasticsearch state: restarted enabled: yes daemon_reload: yes - name: Wait for elasticsearch node to come back up wait_for: port: "{{ es_transport_port }}" delay: 35 - name: Wait for elasticsearch http to come back up wait_for: port: "{{ es_http_port }}" delay: 5
الآن نتأكد من أن الكتلة باللون الأصفر أو الأخضر قبل إعادة تمكين تخصيص الجزء:
- name: Wait for cluster health to return to yellow or green uri: url: http://localhost:{{ es_http_port }}/_cluster/health method: GET register: response until: "response.json.status == 'yellow' or response.json.status == 'green'" retries: 500 delay: 15 - name: Enable shard allocation for the cluster uri: url: http://localhost:{{ es_http_port }}/_cluster/settings method: PUT body_format: json body: "{{ es_enable_allocation }}" register: response until: "response.json.acknowledged == true" retries: 10 delay: 15
وننتظر حتى تتعافى العقدة تمامًا قبل معالجة العقدة التالية:
- name: Wait for the node to recover uri: url: http://localhost:{{ es_http_port }}/_cat/health method: GET return_content: yes register: response until: "'green' in response.content" retries: 500 delay: 15
بالطبع ، كما قلت من قبل ، يجب تنفيذ هذه الكتلة فقط إذا كنا نقوم بالفعل بترقية الإصدار:
when: version_found.json.version.number is version_compare(elk_version, '<')
ترقية Kibana
العنصر الأخير الذي سيتم ترقيته هو Kibana.
كما قد تتوقع ، لا تختلف المهام الأولى عن ترقية Logstash أو عمليات التنزيل المسبق. باستثناء تعريف متغير واحد:
- name: Upgrade kibana hosts: kibana gather_facts: no vars: set_default_index: '{"changes":{"defaultIndex":"syslog"}}' tasks: - name: Validate ELK Version fail: msg="Invalid ELK Version" when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+") - name: Get kibana current version command: rpm -q kibana --qf %{VERSION} args: warn: no changed_when: False register: version_found
سأشرح المتغير set_default_index
عندما نصل إلى المهمة التي تستخدمه.
ستكون المهام المتبقية داخل كتلة سيتم تنفيذها فقط إذا كان الإصدار المثبت من Kibana أقدم من الإصدار المستهدف. سيتم تحديث أول مهمتين وإعادة تشغيل Kibana:
- name: Update kibana yum: name: kibana-{{ elk_version }} state: present - name: Restart kibana systemd: name: kibana state: restarted enabled: yes daemon_reload: yes
وكان ينبغي أن يكون ذلك كافياً بالنسبة لكيبانا. لسوء الحظ ، لسبب ما ، بعد الترقية ، تفقد Kibana مرجعها إلى نمط الفهرس الافتراضي الخاص بها. يؤدي هذا إلى مطالبة المستخدم الأول الذي يصل بعد الترقية بتعريف نمط الفهرس الافتراضي ، مما قد يتسبب في حدوث ارتباك. لتجنب ذلك ، تأكد من تضمين مهمة لإعادة تعيين نمط الفهرس الافتراضي. في المثال أدناه ، هو syslog
، ولكن يجب عليك تغييره إلى أي شيء تستخدمه. قبل تعيين الفهرس ، علينا التأكد من أن Kibana جاهز وجاهز لخدمة الطلبات:
- name: Wait for kibana to start listening wait_for: port: 5601 delay: 5 - name: Wait for kibana to be ready uri: url: http://localhost:5601/api/kibana/settings method: GET register: response until: "'kbn_name' in response and response.status == 200" retries: 30 delay: 5 - name: Set Default Index uri: url: http://localhost:5601/api/kibana/settings method: POST body_format: json body: "{{ set_default_index }}" headers: "kbn-version": "{{ elk_version }}"
خاتمة
تعد Elastic Stack أداة قيمة وأنا أوصيك بالتأكيد بإلقاء نظرة إذا لم تستخدمها بعد. إنه رائع كما هو ويتحسن باستمرار ، لدرجة أنه قد يكون من الصعب مواكبة التحديث المستمر. آمل أن تكون كتيبات Ansible Playbooks مفيدة لك كما هي بالنسبة لي.
لقد جعلتها متاحة على GitHub على https://github.com/orgito/elk-upgrade. أوصي باختباره في بيئة غير إنتاجية.
إذا كنت مطور Ruby on Rails وتتطلع إلى دمج Elasticsearch في تطبيقك ، فراجع Elasticsearch عن Ruby on Rails: برنامج تعليمي عن Chewy Gem بواسطة مهندس البرمجيات Core Toptal Arkadiy Zabazhanov.