Archiviazione dei dati a lungo termine in Elasticsearch

Archiviazione dei dati a lungo termine in Elasticsearch

Mi chiamo Igor Sidorenko, sono il responsabile tecnico di un team di amministratori che mantiene in funzione l'intera infrastruttura di Domklik.

Vorrei condividere la mia esperienza nella configurazione dell'archiviazione distribuita dei dati in Elasticsearch. Vedremo quali impostazioni sui nodi sono responsabili della distribuzione degli shard, come è strutturato e funziona ILM.

Coloro che lavorano con i log si trovano in un modo o nell'altro ad affrontare il problema della conservazione a lungo termine per l'analisi successiva. Ciò è particolarmente vero in Elasticsearch perché la funzionalità del curatore è stata pessima. Nella versione 6.6 è apparsa la funzionalità ILM. Si compone di 4 fasi:

  • Hot: l'indice viene aggiornato e interrogato attivamente.
  • Warm: l'indice non viene più aggiornato, ma viene comunque interrogato.
  • Freddo: l'indice non viene più aggiornato e viene interrogato raramente. Le informazioni dovrebbero essere ancora ricercabili, ma le query potrebbero essere più lente.
  • Elimina: l'indice non è più necessario e può essere eliminato in tutta sicurezza.

Dano

  • Elasticsearch Data Hot: 24 processori, 128 GB di memoria, SSD RAID 1,8 da 10 TB (8 nodi).
  • Elasticsearch Data Warm: 24 processori, 64 GB di memoria, Policy SSD NetApp da 8 TB (4 nodi).
  • Elasticsearch Data Cold: 8 processori, 32 GB di memoria, HDD RAID 128 da 10 TB (4 nodi).

bersaglio

Queste impostazioni sono individuali, tutto dipende dallo spazio sui nodi, dal numero di indici, log, ecc. Per noi si tratta di 2-3 TB di dati al giorno.

  • 5 giorni - Fase calda (8 principali / 1 replica).
  • 20 giorni - Fase calda (indice di contrazione 4 principali / 1 replica).
  • 90 giorni - Fase fredda (indice di congelamento 4 principali / 1 replica).
  • 120 giorni - Fase di eliminazione.

Configurazione di Elasticsearch

Per distribuire i frammenti tra i nodi, è necessario solo un parametro:

  • Hot-nodi:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: hot
  • Chauds-nodi:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: warm
  • Freddo-nodi:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: cold

Configurazione di Logstash

Come funziona tutto questo e come abbiamo implementato questa funzione? Iniziamo con l'inserimento dei log in Elasticsearch. Ci sono due modi:

  1. Logstash recupera i log da Kafka. Può essere raccolto pulito o convertito su un lato.
  2. Qualcosa si scrive su Elasticsearch, ad esempio un server APM.

Diamo un'occhiata a un esempio di gestione degli indici tramite Logstash. Crea un indice e si applica modello di indice e corrispondente ILM.

k8s-ingresso.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
    }
}

Creazione di Kibana

Esiste un modello di base che si applica a tutti i nuovi indici. Imposta la distribuzione degli hot index, il numero di shard, repliche, ecc. Il peso del modello è determinato dall'opzione order. I modelli con pesi più elevati sovrascrivono i parametri del modello esistente o ne aggiungono di nuovi.

Archiviazione dei dati a lungo termine in Elasticsearch
Archiviazione dei dati a lungo termine in Elasticsearch

GET_modello/predefinito

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

Quindi applicare la mappatura agli indici k8s-ingress-* utilizzando un modello con un peso maggiore.

Archiviazione dei dati a lungo termine in Elasticsearch
Archiviazione dei dati a lungo termine in 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" : { }
  }
}

Dopo aver applicato tutti i modelli, applichiamo la politica ILM e iniziamo a monitorare la vita degli indici.

Archiviazione dei dati a lungo termine in Elasticsearch

Archiviazione dei dati a lungo termine in Elasticsearch

Archiviazione dei dati a lungo termine in 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" : { }
          }
        }
      }
    }
  }
}

Problematica

Si sono verificati problemi in fase di configurazione e debug.

Fase calda

Per una corretta rotazione degli indici, la presenza alla fine di index_name-date-000026 numeri di formato 000001. Ci sono righe nel codice che controllano gli indici utilizzando un'espressione regolare per i numeri alla fine. Altrimenti si verificherà un errore, le policy non verranno applicate all'indice e sarà sempre nella fase calda.

Fase calda

Strizzacervelli (taglio) - riducendo il numero di frammenti, perché abbiamo 4 nodi nelle fasi calda e fredda. La documentazione contiene le seguenti righe:

  • L'indice deve essere di sola lettura.
  • Una copia di ogni frammento nell'indice deve risiedere sullo stesso nodo.
  • Lo stato di integrità del cluster deve essere verde.

Per tagliare un indice, Elasticsearch sposta tutti i frammenti primari su un nodo, duplica l'indice tagliato con i parametri necessari e quindi elimina quello vecchio. Parametro total_shards_per_node deve essere uguale o maggiore del numero di frammenti principali per adattarli a un nodo. In caso contrario, verranno inviate notifiche e gli shard non verranno spostati nei nodi richiesti.

Archiviazione dei dati a lungo termine in Elasticsearch
Archiviazione dei dati a lungo termine in Elasticsearch

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

Fase fredda

Congelare (congelamento): congeliamo l'indice per ottimizzare le query in base ai dati storici.

Le ricerche eseguite su indici congelati utilizzano il piccolo pool di thread dedicato search_throttled per controllare il numero di ricerche simultanee che colpiscono gli shard congelati su ciascun nodo. Ciò limita la quantità di memoria aggiuntiva richiesta per le strutture dati transitorie corrispondenti agli shard congelati, proteggendo di conseguenza i nodi da un consumo eccessivo di memoria.
Gli indici congelati sono di sola lettura: non è possibile indicizzarli.
Si prevede che le ricerche sugli indici congelati verranno eseguite lentamente. Gli indici congelati non sono destinati a un carico di ricerca elevato. È possibile che il completamento della ricerca di un indice congelato richieda secondi o minuti, anche se le stesse ricerche vengono completate in millisecondi quando gli indici non erano congelati.

Risultati di

Abbiamo imparato come preparare i nodi per lavorare con ILM, impostare un modello per la distribuzione degli shard tra gli hot node e configurare ILM per un indice con tutte le fasi di vita.

Link utili

Fonte: habr.com