ذخیره سازی طولانی مدت داده در 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
    }
}

راه اندازی کیبانا

یک الگوی پایه وجود دارد که برای همه شاخص های جدید اعمال می شود. توزیع شاخص های داغ، تعداد خرده ها، کپی ها و غیره را تنظیم می کند. وزن قالب توسط گزینه تعیین می شود order. الگوهایی با وزن بالاتر پارامترهای قالب موجود را نادیده می گیرند یا موارد جدیدی را اضافه می کنند.

ذخیره سازی طولانی مدت داده در Elasticsearch
ذخیره سازی طولانی مدت داده در Elasticsearch

GET_template/default

{
  "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

GET _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

GET _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. خطوطی در کد وجود دارد که نمایه ها را با استفاده از یک عبارت منظم برای وجود اعداد در انتها بررسی می کند. در غیر این صورت خطایی رخ می دهد و هیچ سیاستی روی شاخص اعمال نمی شود و همیشه در فاز داغ خواهد بود.

فاز گرم

کوچک شدن (قطع) - کاهش تعداد خرده ها، زیرا ما 4 گره در فازهای گرم و سرد داریم. مستندات شامل خطوط زیر است:

  • ایندکس باید فقط خواندنی باشد.
  • یک کپی از هر خرده در ایندکس باید در همان گره باشد.
  • وضعیت سلامت خوشه باید سبز باشد.

برای هرس کردن یک ایندکس، Elasticsearch همه خرده‌های اولیه را به یک گره منتقل می‌کند، شاخص کوتاه‌شده را با پارامترهای لازم کپی می‌کند، و سپس نمونه قدیمی را حذف می‌کند. پارامتر total_shards_per_node باید مساوی یا بیشتر از تعداد خرده های اصلی برای قرار گرفتن در یک گره باشد. در غیر این صورت، نوتیفیکیشن هایی وجود خواهد داشت و خرده ها به گره های صحیح منتقل نمی شوند.

ذخیره سازی طولانی مدت داده در Elasticsearch
ذخیره سازی طولانی مدت داده در Elasticsearch

GET /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"
          }
        }
      }
    }
  }
}

فاز سرد

منجمد (تجمیع) - برای بهینه سازی پرس و جوها در داده های تاریخی، ایندکس را منجمد می کنیم.

جستجوهای انجام شده بر روی شاخص‌های منجمد شده از Threadpool کوچک اختصاصی search_throttled برای کنترل تعداد جستجوهای همزمانی که بر روی هر گره به تکه‌های منجمد برخورد می‌کنند، استفاده می‌کنند. این مقدار حافظه اضافی مورد نیاز برای ساختارهای داده گذرا مربوط به خرده های منجمد را محدود می کند، که در نتیجه از گره ها در برابر مصرف بیش از حد حافظه محافظت می کند.
شاخص‌های منجمد فقط خواندنی هستند: شما نمی‌توانید در آنها فهرست کنید.
انتظار می رود جستجوها در شاخص های منجمد به کندی انجام شود. شاخص های منجمد برای بار جستجوی بالا در نظر گرفته نشده اند. این امکان وجود دارد که جستجوی یک فهرست ثابت چند ثانیه یا چند دقیقه طول بکشد، حتی اگر جستجوهای مشابه در زمانی که شاخص‌ها ثابت نشده بودند، در میلی‌ثانیه کامل شوند.

نمایش نتایج: از

ما یاد گرفتیم که چگونه گره‌ها را برای کار با ILM آماده کنیم، یک الگو برای توزیع خرده‌ها در میان گره‌های داغ راه‌اندازی کنیم و ILM را برای یک فهرست با تمام مراحل زندگی راه‌اندازی کنیم.

لینک های مفید

منبع: www.habr.com