Dlouhodobé ukládání dat v Elasticsearch

Dlouhodobé ukládání dat v Elasticsearch

Jmenuji se Igor Sidorenko, jsem technický vedoucí v týmu adminů, kteří spravují celou infrastrukturu Domclick.

Chci se podělit o své zkušenosti s nastavením distribuovaného úložiště dat v Elasticsearch. Podíváme se, jaká nastavení na uzlech jsou zodpovědná za distribuci shardů, jak funguje a funguje ILM.

Ti, kteří pracují s protokoly, tak či onak, čelí problému dlouhodobého ukládání pro pozdější analýzu. V Elasticsearch to platí obzvlášť, protože vše bylo nešťastné s funkčností kurátoru. Verze 6.6 zavedla funkcionalitu ILM. Skládá se ze 4 fází:

  • Hot – Index je aktivně aktualizován a dotazován.
  • Teplý – Index již není aktualizován, ale stále se na něj dotazuje.
  • Studený – Index již není aktualizován a dotazován je jen zřídka. Informace musí být stále vyhledatelné, ale dotazy mohou být pomalejší.
  • Smazat – Index již není potřeba a lze jej bezpečně smazat.

Dáno

  • Elasticsearch Data Hot: 24 procesorů, 128 GB paměti, 1,8 TB SSD RAID 10 (8 uzlů).
  • Elasticsearch Data Warm: 24 procesorů, 64 GB paměti, 8 TB NetApp SSD Policy (4 uzly).
  • Elasticsearch Data Cold: 8 procesorů, 32 GB paměti, 128 TB HDD RAID 10 (4 uzly).

terč

Tato nastavení jsou individuální, vše závisí na místě na uzlech, počtu indexů, logů atp. Máme 2-3 TB dat denně.

  • 5 dní - Horká fáze (8 hlavních / 1 replika).
  • 20 dní - Teplá fáze (smršťovací index 4 hlavní / 1 replika).
  • 90 dní – studená fáze (freeze-index 4 hlavní / 1 replika).
  • 120 dní - Smazat fázi.

Nastavení Elasticsearch

Chcete-li distribuovat fragmenty mezi uzly, potřebujete pouze jeden parametr:

  • Hot-uzly:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: hot
  • Teplý-uzly:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: warm
  • Studený-uzly:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: cold

Nastavení Logstash

Jak to celé funguje a jak jsme tuto funkci implementovali? Začněme získáním logů do Elasticsearch. Existují dva způsoby:

  1. Logstash stahuje protokoly od Kafky. Může vyzvednout čisté nebo převést na vaší straně.
  2. Něco samo zapisuje do Elasticsearch, například server APM.

Zvažte příklad správy indexů prostřednictvím Logstash. Vytvoří index a použije se na něj indexový vzor a odpovídající 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
    }
}

Nastavení Kibana

Existuje základní vzor, ​​který platí pro všechny nové indexy. Nastavuje distribuci horkých indexů, počet střepů, replik atd. Hmotnost šablony je určena volbou order. Šablony s vyšší váhou přepíší stávající parametry šablony nebo přidají nové.

Dlouhodobé ukládání dat v Elasticsearch
Dlouhodobé ukládání dat v 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" : { }
  }
}

Poté aplikujte mapování na indexy k8s-ingress-* pomocí šablony s vyšší gramáží.

Dlouhodobé ukládání dat v Elasticsearch
Dlouhodobé ukládání dat v Elasticsearch

ZÍSKEJTE _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" : { }
  }
}

Po aplikaci všech šablon aplikujeme politiku ILM a začneme sledovat životnost indexů.

Dlouhodobé ukládání dat v Elasticsearch

Dlouhodobé ukládání dat v Elasticsearch

Dlouhodobé ukládání dat v Elasticsearch

ZÍSKEJTE _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" : { }
          }
        }
      }
    }
  }
}

Problémy

Vyskytly se problémy ve fázi nastavení a ladění.

Horká fáze

Pro správnou rotaci indexů je přítomnost na konci kritická index_name-date-000026 formátovat čísla 000001. V kódu jsou řádky, které kontrolují indexy pomocí regulárního výrazu na přítomnost čísel na konci. V opačném případě dojde k chybě, na index nebudou aplikovány žádné zásady a bude vždy v horké fázi.

Teplá fáze

Scvrknout se (cutoff) — snížení počtu střepů, protože máme 4 uzly v teplé a studené fázi. Dokumentace obsahuje následující řádky:

  • Index musí být pouze pro čtení.
  • Kopie každého fragmentu v indexu musí být umístěna ve stejném uzlu.
  • Stav klastru musí být zelený.

Pro oříznutí indexu přesune Elasticsearch všechny primární fragmenty do jednoho uzlu, duplikuje zkrácený index s nezbytnými parametry a poté odstraní starý. Parametr total_shards_per_node musí být stejný nebo větší než počet hlavních úlomků, aby se vešly na jeden uzel. V opačném případě budou upozornění a úlomky se nepřesunou do správných uzlů.

Dlouhodobé ukládání dat v Elasticsearch
Dlouhodobé ukládání dat v 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"
          }
        }
      }
    }
  }
}

Studená fáze

Zmrazit (freeze) – Zmrazíme index, abychom optimalizovali dotazy na historická data.

Vyhledávání prováděná na zmrazených indexech používají malý, vyhrazený, search_throttled threadpool k řízení počtu souběžných vyhledávání, která zasáhla zmrazené úlomky na každém uzlu. To omezuje množství paměti navíc potřebné pro přechodné datové struktury odpovídající zmrazeným úlomkům, což následně chrání uzly před nadměrnou spotřebou paměti.
Zmrazené indexy jsou pouze pro čtení: nelze do nich indexovat.
Očekává se, že vyhledávání na zmrazených indexech bude probíhat pomalu. Zmrazené indexy nejsou určeny pro vysokou vyhledávací zátěž. Je možné, že dokončení vyhledávání zmrazeného indexu může trvat sekundy nebo minuty, i když stejná vyhledávání byla dokončena v milisekundách, když indexy nebyly zmrazeny.

Výsledky

Naučili jsme se připravit uzly pro práci s ILM, nastavit šablonu pro distribuci shardů mezi horké uzly a nastavit ILM pro index se všemi fázemi života.

Užitečné odkazy

Zdroj: www.habr.com