Pitkäaikainen tietojen tallennus Elasticsearchissa

Pitkäaikainen tietojen tallennus Elasticsearchissa

Nimeni on Igor Sidorenko, olen tekninen johtaja järjestelmänvalvojien tiimissä, joka ylläpitää koko Domclickin infrastruktuuria.

Haluan jakaa kokemukseni hajautetun tietotallennustilan määrittämisestä Elasticsearchissa. Tarkastellaan, mitkä solmujen asetukset ovat vastuussa sirpaleiden jakelusta, kuinka ILM toimii ja toimii.

Ne, jotka työskentelevät tukkien kanssa tavalla tai toisella, kohtaavat pitkäaikaisen varastoinnin ongelman myöhempää analysointia varten. Elasticsearchissa tämä on erityisen totta, koska kuraattoritoiminnassa kaikki oli valitettavaa. Versio 6.6 esitteli ILM-toiminnot. Se koostuu 4 vaiheesta:

  • Hot – Hakemistoa päivitetään aktiivisesti ja siihen tehdään kyselyjä.
  • Lämmin - Hakemistoa ei enää päivitetä, mutta siitä kysytään edelleen.
  • Kylmä - Hakemistoa ei enää päivitetä, ja sitä kysytään harvoin. Tietojen on silti oltava haettavissa, mutta kyselyt voivat olla hitaampia.
  • Poista - Hakemistoa ei enää tarvita, ja se voidaan poistaa turvallisesti.

Annettu

  • Elasticsearch Data Hot: 24 prosessoria, 128 Gt muistia, 1,8 Tt SSD RAID 10 (8 solmua).
  • Elasticsearch Data Warm: 24 prosessoria, 64 Gt muistia, 8 Tt NetApp SSD Policy (4 solmua).
  • Elasticsearch Data Cold: 8 prosessoria, 32 Gt muistia, 128 TB HDD RAID 10 (4 solmua).

Tavoite

Nämä asetukset ovat yksilöllisiä, kaikki riippuu paikasta solmuissa, indeksien, lokien jne. määrästä. Meillä on 2-3 Tt dataa päivässä.

  • 5 päivää - Kuuma vaihe (8 pääosaa / 1 kopio).
  • 20 päivää - lämmin vaihe (kutistuva indeksi 4 pääosaa / 1 kopio).
  • 90 päivää - kylmä vaihe (jäätymisindeksi 4 pääosaa / 1 kopio).
  • 120 päivää - Poista vaihe.

Elasticsearchin määrittäminen

Sirpaleiden jakamiseen solmujen välillä tarvitset vain yhden parametrin:

  • kuuma-solmut:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: hot
  • Lämmin-solmut:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: warm
  • Kylmä-solmut:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: cold

Asetetaan Logstash

Miten se kaikki toimii ja miten otimme tämän ominaisuuden käyttöön? Aloitetaan hankkimalla lokit Elasticsearchiin. On kaksi tapaa:

  1. Logstash hakee tukit Kafkasta. Voidaan noutaa puhtaana tai muuttaa puoleltasi.
  2. Jokin itse kirjoittaa Elasticsearchiin, esimerkiksi APM-palvelin.

Harkitse esimerkkiä indeksien hallinnasta Logstashin kautta. Se luo indeksin ja soveltaa sitä indeksikuvio ja vastaava 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
    }
}

Kibanan asennus

On olemassa perusmalli, joka koskee kaikkia uusia indeksejä. Se määrittää kuumaindeksien jakautumisen, sirpaleiden, replikoiden jne. määrän. Mallin paino määräytyy vaihtoehdon mukaan order. Suuremmat mallit ohittavat nykyiset malliparametrit tai lisäävät uusia.

Pitkäaikainen tietojen tallennus Elasticsearchissa
Pitkäaikainen tietojen tallennus Elasticsearchissa

GET_malli/oletus

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

Käytä sitten kartoitus indekseihin k8s-ingress-* käyttämällä mallia, jonka paino on suurempi.

Pitkäaikainen tietojen tallennus Elasticsearchissa
Pitkäaikainen tietojen tallennus Elasticsearchissa

HANKI _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" : { }
  }
}

Kun kaikki mallit on otettu käyttöön, otamme käyttöön ILM-käytännön ja alamme seurata indeksien käyttöikää.

Pitkäaikainen tietojen tallennus Elasticsearchissa

Pitkäaikainen tietojen tallennus Elasticsearchissa

Pitkäaikainen tietojen tallennus Elasticsearchissa

HANKI _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" : { }
          }
        }
      }
    }
  }
}

Ongelmat

Asennus- ja virheenkorjausvaiheessa oli ongelmia.

Kuuma vaihe

Indeksien oikean kierron kannalta on tärkeää, että ne ovat lopussa index_name-date-000026 muotoisia numeroita 000001. Koodissa on rivejä, jotka tarkistavat indeksit käyttämällä säännöllistä lauseketta numeroiden lopusta. Muuten tapahtuu virhe, indeksiin ei sovelleta käytäntöjä ja se on aina kuumassa vaiheessa.

Lämmin vaihe

Kutistua (cutoff) — sirpaleiden määrän vähentäminen, koska meillä on 4 solmua lämpimässä ja kylmässä vaiheessa. Dokumentaatio sisältää seuraavat rivit:

  • Hakemiston on oltava vain luku -tilassa.
  • Jokaisen indeksin sirpaleen kopion on sijaittava samassa solmussa.
  • Klusterin terveydentilan on oltava vihreä.

Karsiaakseen indeksin Elasticsearch siirtää kaikki ensisijaiset sirpaleet yhteen solmuun, kopioi katkaistun indeksin tarvittavilla parametreilla ja poistaa sitten vanhan. Parametri total_shards_per_node on oltava yhtä suuri tai suurempi kuin pääsirpaleiden määrä mahtuakseen yhteen solmuun. Muuten tulee ilmoituksia ja sirpaleet eivät siirry oikeisiin solmuihin.

Pitkäaikainen tietojen tallennus Elasticsearchissa
Pitkäaikainen tietojen tallennus Elasticsearchissa

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

Kylmä vaihe

Jäätyä (pysäytys) – Pysäytämme indeksin historiatietojen kyselyjen optimoimiseksi.

Jäädytetyillä indekseillä tehdyt haut käyttävät pientä, omistettua, search_throttled-säiepoolia hallitsemaan kunkin solmun jäädytettyihin sirpaleisiin osuvien samanaikaisten hakujen määrää. Tämä rajoittaa jäätyneitä sirpaleita vastaavien ohimenevien tietorakenteiden vaatiman lisämuistin määrää, mikä näin ollen suojaa solmuja liialliselta muistin kulutukselta.
Jäädytetyt indeksit ovat vain luku -muotoisia: et voi indeksoida niihin.
Jäädytettyjen indeksien haun odotetaan tapahtuvan hitaasti. Jäädytettyjä indeksejä ei ole tarkoitettu suureen hakukuormitukseen. On mahdollista, että jäädytetyn indeksin haku saattaa kestää sekunteja tai minuutteja, vaikka samat haut päättyisivät millisekunneissa, kun indeksit eivät olleet jäädytettyinä.

Tulokset

Opimme valmistelemaan solmuja ILM:n kanssa työskentelyä varten, luomaan mallin sirpaleiden jakamiseen hot solmujen kesken ja määrittämään ILM:n indeksille, joka sisältää kaikki elämänvaiheet.

Hyödyllisiä linkkejä

Lähde: will.com