Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash

Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash

Mening ismim Igor Sidorenko, men Domclick-ning barcha infratuzilmasini qo'llab-quvvatlaydigan ma'murlar jamoasining texnik rahbariman.

Men Elasticsearch-da taqsimlangan ma'lumotlarni saqlashni sozlash bo'yicha tajribam bilan o'rtoqlashmoqchiman. Biz tugunlardagi qanday sozlamalar parchalarning taqsimlanishi, ILM qanday ishlashi va ishlashini ko'rib chiqamiz.

Jurnallar bilan ishlaydiganlar, u yoki bu tarzda, keyinchalik tahlil qilish uchun uzoq muddatli saqlash muammosiga duch kelishadi. Elasticsearch-da, bu ayniqsa to'g'ri, chunki kuratorning funksionalligi bilan hamma narsa baxtsiz edi. 6.6 versiyasi ILM funksiyasini taqdim etdi. U 4 bosqichdan iborat:

  • Issiq - indeks faol ravishda yangilanadi va so'raladi.
  • Issiq - indeks endi yangilanmagan, lekin hali ham so'roq qilinmoqda.
  • Sovuq - indeks endi yangilanmaydi va kamdan-kam so'raladi. Ma'lumot hali ham qidirilishi kerak, ammo so'rovlar sekinroq bo'lishi mumkin.
  • O'chirish - indeks endi kerak emas va xavfsiz tarzda o'chirilishi mumkin.

Berilgan

  • Elasticsearch Data Hot: 24 protsessor, 128 GB xotira, 1,8 TB SSD RAID 10 (8 tugun).
  • Elasticsearch Data Warm: 24 protsessor, 64 GB xotira, 8 TB NetApp SSD siyosati (4 tugun).
  • Elasticsearch Data Cold: 8 protsessor, 32 GB xotira, 128 TB HDD RAID 10 (4 tugun).

Maqsad

Ushbu sozlamalar individualdir, barchasi tugunlardagi joyga, indekslar soniga, jurnallarga va boshqalarga bog'liq. Bizda kuniga 2-3 TB ma'lumot mavjud.

  • 5 kun - Issiq faza (8 asosiy / 1 nusxa).
  • 20 kun - Issiq faza (qisqarish indeksi 4 asosiy / 1 nusxa).
  • 90 kun - sovuq faza (muzlash indeksi 4 asosiy / 1 nusxa).
  • 120 kun - bosqichni o'chirish.

Elasticsearch o'rnatilmoqda

Tugunlar bo'ylab parchalarni tarqatish uchun sizga faqat bitta parametr kerak bo'ladi:

  • issiq-tugunlar:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: hot
  • iliq-tugunlar:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: warm
  • sovuq-tugunlar:
    ~]# cat /etc/elasticsearch/elasticsearch.yml | grep attr
    # Add custom attributes to the node:
    node.attr.box_type: cold

Logstash o'rnatilmoqda

Bularning barchasi qanday ishlaydi va biz bu xususiyatni qanday amalga oshirdik? Keling, Elasticsearch-ga jurnallarni kiritishdan boshlaylik. Ikkita yo'l bor:

  1. Logstash Kafkadan jurnallarni oladi. Siz tomondan toza yoki aylantirishi mumkin.
  2. Elasticsearch-ga biror narsa yozadi, masalan, APM serveri.

Logstash orqali indekslarni boshqarish misolini ko'rib chiqing. U indeks yaratadi va unga amal qiladi indeks namunasi va mos keladi 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 sozlamalari

Barcha yangi indekslar uchun qo'llaniladigan asosiy naqsh mavjud. U issiq indekslarning taqsimlanishini, parchalar sonini, replikalarni va boshqalarni belgilaydi. Shablonning og'irligi variant bilan belgilanadi order. Og'irligi yuqori bo'lgan andozalar mavjud shablon parametrlarini bekor qiladi yoki yangilarini qo'shadi.

Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash
Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash

_shablon/standartni oling

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

Keyin indekslarga xaritalashni qo'llang k8s-ingress-* og'irligi yuqori bo'lgan shablonni ishlatish.

Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash
Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash

_shablon/k8s-kirish OLING

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

Barcha shablonlarni qo'llaganimizdan so'ng, biz ILM siyosatini qo'llaymiz va indekslarning ishlash muddatini kuzatishni boshlaymiz.

Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash

Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash

Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash

_ilm/policy/k8s-ingress OLING

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

Muammolar

Sozlash va disk raskadrovka bosqichida muammolar bor edi.

Issiq faza

Indekslarning to'g'ri aylanishi uchun oxirida mavjudligi juda muhimdir index_name-date-000026 raqamlarni formatlash 000001. Kodda indekslarni oxirida raqamlar mavjudligi uchun muntazam ifoda yordamida tekshiradigan satrlar mavjud. Aks holda, xatolik yuz beradi, indeksga hech qanday siyosat qo'llanilmaydi va u har doim issiq bosqichda bo'ladi.

Issiq faza

Shrink (kesish) β€” parchalar sonini kamaytirish, chunki bizda issiq va sovuq fazalarda 4 ta tugun mavjud.Hujjatlarda quyidagi qatorlar mavjud:

  • Indeks faqat o'qish uchun bo'lishi kerak.
  • Indeksdagi har bir parchaning nusxasi bir xil tugunda bo'lishi kerak.
  • Klaster salomatligi holati yashil bo'lishi kerak.

Indeksni kesish uchun Elasticsearch barcha asosiy parchalarni bitta tugunga ko'chiradi, kesilgan indeksni kerakli parametrlar bilan takrorlaydi va keyin eskisini o'chiradi. Parametr total_shards_per_node bitta tugunga sig'adigan asosiy parchalar soniga teng yoki undan ko'p bo'lishi kerak. Aks holda, bildirishnomalar bo'ladi va parchalar to'g'ri tugunlarga o'tmaydi.

Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash
Elasticsearch-da uzoq muddatli ma'lumotlarni saqlash

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

Sovuq faza

Muzlatmoq (muzlatish) - Tarixiy ma'lumotlar bo'yicha so'rovlarni optimallashtirish uchun indeksni muzlatib qo'yamiz.

Muzlatilgan indekslar bo'yicha amalga oshirilgan qidiruvlar kichik, ajratilgan, search_throttled ish zarrachasidan har bir tugundagi muzlatilgan parchalarga tegadigan bir vaqtda qidiruvlar sonini nazorat qilish uchun foydalanadi. Bu muzlatilgan parchalarga mos keladigan vaqtinchalik ma'lumotlar tuzilmalari uchun zarur bo'lgan qo'shimcha xotira miqdorini cheklaydi, bu esa tugunlarni ortiqcha xotira sarfidan himoya qiladi.
Muzlatilgan indekslar faqat o'qish uchun mo'ljallangan: siz ularni indekslay olmaysiz.
Muzlatilgan indekslar bo'yicha qidiruvlar sekin bajarilishi kutilmoqda. Muzlatilgan indekslar yuqori qidiruv yuki uchun mo'ljallanmagan. Muzlatilgan indeksni qidirishni yakunlash uchun bir necha soniya yoki daqiqalar ketishi mumkin, hatto indekslar muzlatilmaganda bir xil qidiruvlar millisekundlarda yakunlangan bo'lsa ham.

natijalar

Biz ILM bilan ishlash uchun tugunlarni qanday tayyorlashni, issiq tugunlar orasida parchalarni taqsimlash uchun shablonni o'rnatishni va hayotning barcha bosqichlari bilan indeks uchun ILMni o'rnatishni o'rgandik.

Foydali havolalar

Manba: www.habr.com