Μακροπρόθεσμη αποθήκευση δεδομένων στο Elasticsearch

Μακροπρόθεσμη αποθήκευση δεδομένων στο Elasticsearch

Ονομάζομαι Igor Sidorenko, είμαι τεχνικός ηγέτης στην ομάδα των διαχειριστών που διατηρούν ολόκληρη την υποδομή του Domclick.

Θέλω να μοιραστώ την εμπειρία μου στη ρύθμιση κατανεμημένης αποθήκευσης δεδομένων στο Elasticsearch. Θα εξετάσουμε ποιες ρυθμίσεις στους κόμβους είναι υπεύθυνες για τη διανομή των θραυσμάτων, πώς λειτουργεί και λειτουργεί το ILM.

Όσοι εργάζονται με κορμούς, με τον ένα ή τον άλλο τρόπο, αντιμετωπίζουν το πρόβλημα της μακροχρόνιας αποθήκευσης για μεταγενέστερη ανάλυση. Στο Elasticsearch, αυτό ισχύει ιδιαίτερα, επειδή όλα ήταν ατυχή με τη λειτουργικότητα του επιμελητή. Η έκδοση 6.6 εισήγαγε τη λειτουργικότητα ILM. Αποτελείται από 4 φάσεις:

  • Hot - Το ευρετήριο ενημερώνεται ενεργά και υποβάλλεται ερωτήματα.
  • Ζεστό - Το ευρετήριο δεν ενημερώνεται πλέον, αλλά εξακολουθεί να τίθεται το ερώτημα.
  • Κρύο - Το ευρετήριο δεν ενημερώνεται πλέον και σπάνια ερωτάται. Πρέπει να υπάρχει δυνατότητα αναζήτησης των πληροφοριών, αλλά τα ερωτήματα μπορεί να είναι πιο αργά.
  • Διαγραφή - Το ευρετήριο δεν χρειάζεται πλέον και μπορεί να διαγραφεί με ασφάλεια.

Δεδομένος

  • Elasticsearch Data Hot: 24 επεξεργαστές, μνήμη 128 GB, 1,8 TB SSD RAID 10 (8 κόμβοι).
  • Elasticsearch Data Warm: 24 επεξεργαστές, μνήμη 64 GB, Πολιτική SSD NetApp 8 TB (4 κόμβοι).
  • Elasticsearch Data Cold: 8 επεξεργαστές, μνήμη 32 GB, 128 TB HDD RAID 10 (4 κόμβοι).

στόχος

Αυτές οι ρυθμίσεις είναι μεμονωμένες, όλα εξαρτώνται από τη θέση στους κόμβους, τον αριθμό των ευρετηρίων, τα αρχεία καταγραφής κ.λπ. Έχουμε 2-3 TB δεδομένων την ημέρα.

  • 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

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. Υπάρχουν γραμμές στον κώδικα που ελέγχουν ευρετήρια χρησιμοποιώντας μια τυπική έκφραση για την παρουσία αριθμών στο τέλος. Διαφορετικά, θα υπάρξει σφάλμα, δεν θα εφαρμοστούν πολιτικές στο ευρετήριο και θα βρίσκεται πάντα στην καυτή φάση.

Θερμή φάση

Shrink (αποκοπή) — μείωση του αριθμού των θραυσμάτων, επειδή έχουμε 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"
          }
        }
      }
    }
  }
}

Ψυχρή φάση

Πάγωμα (πάγωμα) - Παγώνουμε το ευρετήριο για να βελτιστοποιήσουμε τα ερωτήματα σε ιστορικά δεδομένα.

Οι αναζητήσεις που πραγματοποιούνται σε παγωμένους δείκτες χρησιμοποιούν το μικρό, αποκλειστικό, search_throttled threadpool για τον έλεγχο του αριθμού των ταυτόχρονων αναζητήσεων που χτυπούν παγωμένα θραύσματα σε κάθε κόμβο. Αυτό περιορίζει την ποσότητα επιπλέον μνήμης που απαιτείται για τις μεταβατικές δομές δεδομένων που αντιστοιχούν σε παγωμένα θραύσματα, γεγονός που κατά συνέπεια προστατεύει τους κόμβους από την υπερβολική κατανάλωση μνήμης.
Οι παγωμένοι δείκτες είναι μόνο για ανάγνωση: δεν μπορείτε να δημιουργήσετε ευρετήριο σε αυτούς.
Οι αναζητήσεις σε παγωμένους δείκτες αναμένεται να εκτελούνται αργά. Οι παγωμένοι δείκτες δεν προορίζονται για υψηλό φόρτο αναζήτησης. Είναι πιθανό ότι η αναζήτηση ενός παγωμένου ευρετηρίου μπορεί να διαρκέσει δευτερόλεπτα ή λεπτά για να ολοκληρωθεί, ακόμη και αν οι ίδιες αναζητήσεις ολοκληρώθηκαν σε χιλιοστά του δευτερολέπτου όταν οι δείκτες δεν είχαν παγώσει.

Αποτελέσματα της

Μάθαμε πώς να προετοιμάζουμε κόμβους για εργασία με το ILM, ρυθμίζουμε ένα πρότυπο για τη διανομή θραυσμάτων μεταξύ των θερμών κόμβων και ρυθμίζουμε το ILM για ένα ευρετήριο με όλες τις φάσεις της ζωής.

χρήσιμοι σύνδεσμοι

Πηγή: www.habr.com