تخزين البيانات على المدى الطويل في Elasticsearch

تخزين البيانات على المدى الطويل في Elasticsearch

اسمي إيغور سيدورينكو ، أنا قائد تقني في فريق المسؤولين الذين يحافظون على البنية التحتية الكاملة لـ Domclick.

أرغب في مشاركة تجربتي في إعداد تخزين البيانات الموزعة في Elasticsearch. سننظر في الإعدادات الموجودة على العقد المسؤولة عن توزيع الأجزاء وكيفية عمل ILM وعمله.

أولئك الذين يعملون مع السجلات ، بطريقة أو بأخرى ، يواجهون مشكلة التخزين طويل الأجل لتحليلها لاحقًا. في Elasticsearch ، هذا صحيح بشكل خاص ، لأن كل شيء كان مؤسفًا مع وظيفة أمين المعرض. قدم الإصدار 6.6 وظائف ILM. تتكون من 4 مراحل:

  • ساخن - يتم تحديث الفهرس والاستعلام عنه بنشاط.
  • دافئ - لم يعد الفهرس محدثًا ، ولكن لا يزال الاستعلام قيد الاستفسار عنه.
  • بارد - لم يعد الفهرس محدثًا ونادرًا ما يتم الاستعلام عنه. يجب أن تظل المعلومات قابلة للبحث ، ولكن قد تكون الاستعلامات أبطأ.
  • حذف - لم تعد هناك حاجة للفهرس ويمكن حذفه بأمان.

دانو

  • Elasticsearch Data Hot: 24 معالجًا وذاكرة 128 جيجابايت و 1,8 تيرابايت SSD RAID 10 (8 عقد).
  • Elasticsearch Data Warm: 24 معالجًا وذاكرة 64 جيجابايت و 8 تيرابايت NetApp SSD Policy (4 عقد).
  • Elasticsearch Data Cold: 8 معالجات وذاكرة 32 جيجابايت و 128 تيرابايت HDD RAID 10 (4 عقد).

الهدف

هذه الإعدادات فردية ، كل هذا يتوقف على المكان الموجود على العقد ، وعدد الفهارس ، والسجلات ، وما إلى ذلك. لدينا 2-3 تيرابايت من البيانات يوميًا.

  • 5 أيام - المرحلة الساخنة (8 رئيسية / 1 نسخة طبق الأصل).
  • 20 يوم - المرحلة الدافئة (يتقلص الفهرس 4 رئيسي / 1 نسخة طبق الأصل).
  • 90 يومًا - المرحلة الباردة (مؤشر التجميد 4 رئيسي / 1 نسخة طبق الأصل).
  • 120 يومًا - مرحلة الحذف.

إعداد Elasticsearch

لتوزيع الأجزاء عبر العقد ، تحتاج فقط إلى معلمة واحدة:

  • عرض-العقد:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: hot
  • الود-العقد:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: warm
  • بارد-العقد:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: cold

إعداد Logstash

كيف يعمل كل شيء وكيف تم تنفيذ هذه الميزة؟ لنبدأ بالحصول على سجلات في Elasticsearch. هناك طريقتان:

  1. Logstash يجلب سجلات من كافكا. يمكن أن تلتقط نظيفة أو تحويل من جانبك.
  2. شيء ما يكتب إلى Elasticsearch ، على سبيل المثال ، خادم APM.

ضع في اعتبارك مثالاً لإدارة الفهارس من خلال Logstash. يقوم بإنشاء فهرس وتطبيقه عليه نمط الفهرس وما يقابلها ILM.

k8s-ingress.conf

input {
    kafka {
        bootstrap_servers => "node01, node02, node03"
        topics => ["ingress-k8s"]
        decorate_events => false
        codec => "json"
    }
}

filter {
    ruby {
        path => "/etc/logstash/conf.d/k8s-normalize.rb"
    }
    if [log] =~ "[warn]" or [log] =~ "[error]" or [log] =~ "[notice]" or [log] =~ "[alert]" {
        grok {
            match => { "log" => "%{DATA:[nginx][error][time]} [%{DATA:[nginx][error][level]}] %{NUMBER:[nginx][error][pid]}#%{NUMBER:[nginx][error][tid]}: *%{NUMBER:[nginx][error][connection_id]} %{DATA:[nginx][error][message]}, client: %{IPORHOST:[nginx][error][remote_ip]}, server: %{DATA:[nginx][error][server]}, request: "%{WORD:[nginx][error][method]} %{DATA:[nginx][error][url]} HTTP/%{NUMBER:[nginx][error][http_version]}", (?:upstream: "%{DATA:[nginx][error][upstream][proto]}://%{DATA:[nginx][error][upstream][host]}:%{DATA:[nginx][error][upstream][port]}/%{DATA:[nginx][error][upstream][url]}", )?host: "%{DATA:[nginx][error][host]}"(?:, referrer: "%{DATA:[nginx][error][referrer]}")?" }
            remove_field => "log"
        }
    }
    else {
        grok {
            match => { "log" => "%{IPORHOST:[nginx][access][host]} - [%{IPORHOST:[nginx][access][remote_ip]}] - %{DATA:[nginx][access][remote_user]} [%{HTTPDATE:[nginx][access][time]}] "%{WORD:[nginx][access][method]} %{DATA:[nginx][access][url]} HTTP/%{NUMBER:[nginx][access][http_version]}" %{NUMBER:[nginx][access][response_code]} %{NUMBER:[nginx][access][bytes_sent]} "%{DATA:[nginx][access][referrer]}" "%{DATA:[nginx][access][agent]}" %{NUMBER:[nginx][access][request_lenght]} %{NUMBER:[nginx][access][request_time]} [%{DATA:[nginx][access][upstream][name]}] (?:-|%{IPORHOST:[nginx][access][upstream][addr]}:%{NUMBER:[nginx][access][upstream][port]}) (?:-|%{NUMBER:[nginx][access][upstream][response_lenght]}) %{DATA:[nginx][access][upstream][response_time]} %{DATA:[nginx][access][upstream][status]} %{DATA:[nginx][access][request_id]}" }
            remove_field => "log"
        }
    }
}
output {
    elasticsearch {
        id => "k8s-ingress"
        hosts => ["node01", "node02", "node03", "node04", "node05", "node06", "node07", "node08"]
        manage_template => true # включаем управление шаблонами
        template_name => "k8s-ingress" # имя применяемого шаблона
        ilm_enabled => true # включаем управление ILM
        ilm_rollover_alias => "k8s-ingress" # alias для записи в индексы, должен быть уникальным
        ilm_pattern => "{now/d}-000001" # шаблон для создания индексов, может быть как "{now/d}-000001" так и "000001"
        ilm_policy => "k8s-ingress" # политика прикрепляемая к индексу
        index => "k8s-ingress-%{+YYYY.MM.dd}" # название создаваемого индекса, может содержать %{+YYYY.MM.dd}, зависит от ilm_pattern
    }
}

إعداد Kibana

يوجد نمط أساسي ينطبق على كافة الفهارس الجديدة. يحدد توزيع الفهارس الساخنة وعدد القطع والنسخ المتماثلة وما إلى ذلك. يتم تحديد وزن القالب بواسطة الخيار order. تتجاوز القوالب ذات الوزن الأعلى معلمات القوالب الموجودة أو تضيف أخرى جديدة.

تخزين البيانات على المدى الطويل في Elasticsearch
تخزين البيانات على المدى الطويل في Elasticsearch

احصل على _template / افتراضي

{
  "default" : {
    "order" : -1, # вес шаблона
    "version" : 1,
    "index_patterns" : [
      "*" # применяем ко всем индексам
    ],
    "settings" : {
      "index" : {
        "codec" : "best_compression", # уровень сжатия
        "routing" : {
          "allocation" : {
            "require" : {
              "box_type" : "hot" # распределяем только по горячим нодам
            },
            "total_shards_per_node" : "8" # максимальное количество шардов на ноду от одного индекса
          }
        },
        "refresh_interval" : "5s", # интервал обновления индекса
        "number_of_shards" : "8", # количество шардов
        "auto_expand_replicas" : "0-1", # количество реплик на ноду от одного индекса
        "number_of_replicas" : "1" # количество реплик
      }
    },
    "mappings" : {
      "_meta" : { },
      "_source" : { },
      "properties" : { }
    },
    "aliases" : { }
  }
}

ثم قم بتطبيق التعيين على الفهارس k8s-ingress-* باستخدام قالب ذي وزن أعلى.

تخزين البيانات على المدى الطويل في Elasticsearch
تخزين البيانات على المدى الطويل في Elasticsearch

احصل على _template / k8s-ingress

{
  "k8s-ingress" : {
    "order" : 100,
    "index_patterns" : [
      "k8s-ingress-*"
    ],
    "settings" : {
      "index" : {
        "lifecycle" : {
          "name" : "k8s-ingress",
          "rollover_alias" : "k8s-ingress"
        },
        "codec" : "best_compression",
        "routing" : {
          "allocation" : {
            "require" : {
              "box_type" : "hot"
            }
          }
        },
        "number_of_shards" : "8",
        "number_of_replicas" : "1"
      }
    },
    "mappings" : {
      "numeric_detection" : false,
      "_meta" : { },
      "_source" : { },
      "dynamic_templates" : [
        {
          "all_fields" : {
            "mapping" : {
              "index" : false,
              "type" : "text"
            },
            "match" : "*"
          }
        }
      ],
      "date_detection" : false,
      "properties" : {
        "kubernetes" : {
          "type" : "object",
          "properties" : {
            "container_name" : {
              "type" : "keyword"
            },
            "container_hash" : {
              "index" : false,
              "type" : "keyword"
            },
            "host" : {
              "type" : "keyword"
            },
            "annotations" : {
              "type" : "object",
              "properties" : {
                "value" : {
                  "index" : false,
                  "type" : "text"
                },
                "key" : {
                  "index" : false,
                  "type" : "keyword"
                }
              }
            },
            "docker_id" : {
              "index" : false,
              "type" : "keyword"
            },
            "pod_id" : {
              "type" : "keyword"
            },
            "labels" : {
              "type" : "object",
              "properties" : {
                "value" : {
                  "type" : "keyword"
                },
                "key" : {
                  "type" : "keyword"
                }
              }
            },
            "namespace_name" : {
              "type" : "keyword"
            },
            "pod_name" : {
              "type" : "keyword"
            }
          }
        },
        "@timestamp" : {
          "type" : "date"
        },
        "nginx" : {
          "type" : "object",
          "properties" : {
            "access" : {
              "type" : "object",
              "properties" : {
                "agent" : {
                  "type" : "text"
                },
                "response_code" : {
                  "type" : "integer"
                },
                "upstream" : {
                  "type" : "object",
                  "properties" : {
                    "port" : {
                      "type" : "keyword"
                    },
                    "name" : {
                      "type" : "keyword"
                    },
                    "response_lenght" : {
                      "type" : "integer"
                    },
                    "response_time" : {
                      "index" : false,
                      "type" : "text"
                    },
                    "addr" : {
                      "type" : "keyword"
                    },
                    "status" : {
                      "index" : false,
                      "type" : "text"
                    }
                  }
                },
                "method" : {
                  "type" : "keyword"
                },
                "http_version" : {
                  "type" : "keyword"
                },
                "bytes_sent" : {
                  "type" : "integer"
                },
                "request_lenght" : {
                  "type" : "integer"
                },
                "url" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword"
                    }
                  }
                },
                "remote_user" : {
                  "type" : "text"
                },
                "referrer" : {
                  "type" : "text"
                },
                "remote_ip" : {
                  "type" : "ip"
                },
                "request_time" : {
                  "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis||dd/MMM/YYYY:H:m:s Z",
                  "type" : "date"
                },
                "host" : {
                  "type" : "keyword"
                },
                "time" : {
                  "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis||dd/MMM/YYYY:H:m:s Z",
                  "type" : "date"
                }
              }
            },
            "error" : {
              "type" : "object",
              "properties" : {
                "server" : {
                  "type" : "keyword"
                },
                "upstream" : {
                  "type" : "object",
                  "properties" : {
                    "port" : {
                      "type" : "keyword"
                    },
                    "proto" : {
                      "type" : "keyword"
                    },
                    "host" : {
                      "type" : "keyword"
                    },
                    "url" : {
                      "type" : "text",
                      "fields" : {
                        "keyword" : {
                          "type" : "keyword"
                        }
                      }
                    }
                  }
                },
                "method" : {
                  "type" : "keyword"
                },
                "level" : {
                  "type" : "keyword"
                },
                "http_version" : {
                  "type" : "keyword"
                },
                "pid" : {
                  "index" : false,
                  "type" : "integer"
                },
                "message" : {
                  "type" : "text"
                },
                "tid" : {
                  "index" : false,
                  "type" : "keyword"
                },
                "url" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword"
                    }
                  }
                },
                "referrer" : {
                  "type" : "text"
                },
                "remote_ip" : {
                  "type" : "ip"
                },
                "connection_id" : {
                  "index" : false,
                  "type" : "keyword"
                },
                "host" : {
                  "type" : "keyword"
                },
                "time" : {
                  "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis||dd/MMM/YYYY:H:m:s Z",
                  "type" : "date"
                }
              }
            }
          }
        },
        "log" : {
          "type" : "text"
        },
        "@version" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "ignore_above" : 256,
              "type" : "keyword"
            }
          }
        },
        "eventtime" : {
          "type" : "float"
        }
      }
    },
    "aliases" : { }
  }
}

بعد تطبيق جميع القوالب ، نطبق سياسة ILM ونبدأ في مراقبة عمر الفهارس.

تخزين البيانات على المدى الطويل في Elasticsearch

تخزين البيانات على المدى الطويل في Elasticsearch

تخزين البيانات على المدى الطويل في Elasticsearch

احصل على _ilm / policy / k8s-ingress

{
  "k8s-ingress" : {
    "version" : 14,
    "modified_date" : "2020-06-11T10:27:01.448Z",
    "policy" : {
      "phases" : {
        "warm" : { # теплая фаза
          "min_age" : "5d", # срок жизни индекса после ротации до наступления теплой фазы
          "actions" : {
            "allocate" : {
              "include" : { },
              "exclude" : { },
              "require" : {
                "box_type" : "warm" # куда перемещаем индекс
              }
            },
            "shrink" : {
              "number_of_shards" : 4 # обрезание индексов, т.к. у нас 4 ноды
            }
          }
        },
        "cold" : { # холодная фаза
          "min_age" : "25d", # срок жизни индекса после ротации до наступления холодной фазы
          "actions" : {
            "allocate" : {
              "include" : { },
              "exclude" : { },
              "require" : {
                "box_type" : "cold" # куда перемещаем индекс
              }
            },
            "freeze" : { } # замораживаем для оптимизации
          }
        },
        "hot" : { # горячая фаза
          "min_age" : "0ms",
          "actions" : {
            "rollover" : {
              "max_size" : "50gb", # максимальный размер индекса до ротации (будет х2, т.к. есть 1 реплика)
              "max_age" : "1d" # максимальный срок жизни индекса до ротации
            },
            "set_priority" : {
              "priority" : 100
            }
          }
        },
        "delete" : { # фаза удаления
          "min_age" : "120d", # максимальный срок жизни после ротации перед удалением
          "actions" : {
            "delete" : { }
          }
        }
      }
    }
  }
}

مشاكل

كانت هناك مشاكل في مرحلة الإعداد والتصحيح.

المرحلة الساخنة

من أجل الدوران الصحيح للمؤشرات ، يعد التواجد في النهاية أمرًا بالغ الأهمية index_name-date-000026 تنسيق الأرقام 000001. توجد سطور في الكود تتحقق من الفهارس باستخدام تعبير عادي عن وجود الأرقام في النهاية. خلاف ذلك ، سيكون هناك خطأ ، ولن يتم تطبيق أي سياسات على الفهرس ، وسيظل دائمًا في المرحلة الساخنة.

المرحلة الدافئة

انكماش (cutoff) - تقليل عدد القطع ، لأن لدينا 4 عقد في المرحلتين الدافئة والباردة ، وتحتوي التوثيق على الأسطر التالية:

  • يجب أن يكون الفهرس للقراءة فقط.
  • يجب أن توجد نسخة من كل جزء في الفهرس على نفس العقدة.
  • يجب أن تكون حالة صحة المجموعة خضراء.

لتقليم فهرس ، ينقل Elasticsearch جميع الأجزاء الأولية إلى عقدة واحدة ، ويكرر الفهرس المقتطع مع المعلمات الضرورية ، ثم يحذف القديم. معامل total_shards_per_node يجب أن تكون مساوية أو أكبر من عدد الأجزاء الرئيسية لتناسب عقدة واحدة. خلاف ذلك ، ستكون هناك إشعارات ولن تنتقل الأجزاء إلى العقد الصحيحة.

تخزين البيانات على المدى الطويل في Elasticsearch
تخزين البيانات على المدى الطويل في Elasticsearch

احصل على / shrink-k8s-ingress-2020.06.06-000025/_settings

{
  "shrink-k8s-ingress-2020.06.06-000025" : {
    "settings" : {
      "index" : {
        "refresh_interval" : "5s",
        "auto_expand_replicas" : "0-1",
        "blocks" : {
          "write" : "true"
        },
        "provided_name" : "shrink-k8s-ingress-2020.06.06-000025",
        "creation_date" : "1592225525569",
        "priority" : "100",
        "number_of_replicas" : "1",
        "uuid" : "psF4MiFGQRmi8EstYUQS4w",
        "version" : {
          "created" : "7060299",
          "upgraded" : "7060299"
        },
        "lifecycle" : {
          "name" : "k8s-ingress",
          "rollover_alias" : "k8s-ingress",
          "indexing_complete" : "true"
        },
        "codec" : "best_compression",
        "routing" : {
          "allocation" : {
            "initial_recovery" : {
              "_id" : "_Le0Ww96RZ-o76bEPAWWag"
            },
            "require" : {
              "_id" : null,
              "box_type" : "cold"
            },
            "total_shards_per_node" : "8"
          }
        },
        "number_of_shards" : "4",
        "routing_partition_size" : "1",
        "resize" : {
          "source" : {
            "name" : "k8s-ingress-2020.06.06-000025",
            "uuid" : "gNhYixO6Skqi54lBjg5bpQ"
          }
        }
      }
    }
  }
}

المرحلة الباردة

تجمد (تجميد) - نقوم بتجميد الفهرس لتحسين الاستعلامات حول البيانات التاريخية.

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

نتائج

لقد تعلمنا كيفية إعداد العقد للعمل مع ILM ، وإعداد قالب لتوزيع القطع بين العقد الساخنة ، وإعداد ILM لفهرس بجميع مراحل الحياة.

وصلات مفيدة

المصدر: www.habr.com