Elasticsearch의 장기 데이터 저장

Elasticsearch의 장기 데이터 저장

제 이름은 Igor Sidorenko입니다. 저는 Domclick의 전체 인프라를 관리하는 관리자 팀의 기술 리더입니다.

Elasticsearch에서 분산 데이터 저장소를 설정한 경험을 공유하고 싶습니다. 노드의 어떤 설정이 샤드 배포를 담당하는지, ILM이 작동하고 작동하는 방식을 살펴보겠습니다.

어떤 식으로든 로그 작업을 하는 사람들은 나중에 분석하기 위해 장기간 보관해야 하는 문제에 직면합니다. Elasticsearch에서는 모든 것이 큐레이터 기능에 문제가 있었기 때문에 특히 그렇습니다. 버전 6.6에는 ILM 기능이 도입되었습니다. 4단계로 구성됩니다.

  • 핫 - 인덱스가 활발하게 업데이트되고 쿼리되고 있습니다.
  • 웜 - 인덱스가 더 이상 업데이트되지 않지만 여전히 쿼리되고 있습니다.
  • 콜드 - 인덱스가 더 이상 업데이트되지 않으며 거의 ​​쿼리되지 않습니다. 정보는 여전히 검색 가능해야 하지만 쿼리가 느려질 수 있습니다.
  • 삭제 - 인덱스가 더 이상 필요하지 않으며 안전하게 삭제할 수 있습니다.

주어진

  • Elasticsearch Data Hot: 프로세서 24개, 128GB 메모리, 1,8TB SSD RAID 10(8노드).
  • Elasticsearch 데이터 웜: 프로세서 24개, 64GB 메모리, 8TB NetApp SSD 정책(노드 4개).
  • Elasticsearch Data Cold: 프로세서 8개, 32GB 메모리, 128TB HDD RAID 10(노드 4개).

이러한 설정은 개별적이며 모두 노드의 위치, 인덱스 수, 로그 등에 따라 다릅니다. 하루에 2-3TB의 데이터가 있습니다.

  • 5일 - 핫 단계(8 메인 / 1 레플리카).
  • 20일 - 온난기(수축 지수 메인 4개 / 레플리카 1개).
  • 90일 - 냉기(동결 지수 메인 4개 / 레플리카 1개).
  • 120일 - 삭제 단계.

Elasticsearch 설정

여러 노드에 샤드를 배포하려면 하나의 매개변수만 필요합니다.

  • HOT-노드:
    ~]# 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

로그스태시 설정

모든 것이 어떻게 작동하며 이 기능을 어떻게 구현했습니까? Elasticsearch에 로그를 가져오는 것부터 시작하겠습니다. 두 가지 방법이 있습니다.

  1. Logstash는 Kafka에서 로그를 가져옵니다. 깨끗하게 픽업하거나 옆에서 전환할 수 있습니다.
  2. 예를 들어 APM 서버와 같은 어떤 것 자체가 Elasticsearch에 씁니다.

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_템플릿/기본값

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

냉기

고정 (동결) - 과거 데이터에 대한 쿼리를 최적화하기 위해 인덱스를 동결합니다.

고정 인덱스에서 수행되는 검색은 작은 전용 search_throttled 스레드 풀을 사용하여 각 노드에서 고정 샤드에 도달하는 동시 검색 수를 제어합니다. 이는 고정된 샤드에 해당하는 임시 데이터 구조에 필요한 추가 메모리 양을 제한하여 결과적으로 과도한 메모리 소비로부터 노드를 보호합니다.
고정 인덱스는 읽기 전용이므로 인덱스를 생성할 수 없습니다.
고정 인덱스에 대한 검색은 느리게 실행될 것으로 예상됩니다. 고정 인덱스는 높은 검색 부하를 위한 것이 아닙니다. 인덱스가 고정되지 않은 경우 동일한 검색이 밀리초 내에 완료되더라도 고정된 인덱스 검색을 완료하는 데 몇 초 또는 몇 분이 걸릴 수 있습니다.

결과

우리는 ILM 작업을 위해 노드를 준비하고, 핫 노드 간에 샤드를 배포하기 위한 템플릿을 설정하고, 수명의 모든 단계에서 인덱스에 대해 ILM을 설정하는 방법을 배웠습니다.

유용한 링크

출처 : habr.com