Приложение микросервиса Spring Boot интегрирует Prometheus + Grafana для реализации сигнализации мониторинга.

Spring Boot
Приложение микросервиса Spring Boot интегрирует Prometheus + Grafana для реализации сигнализации мониторинга.

предисловие

Ключевые слова: Prometheus, Grafana, Alertmanager, SpringBoot, SpringBoot Actuator, мониторинг, оповещение, оповещение.

в предыдущей статьеПодробное объяснение модуля Spring Boot Actuator: проверка работоспособности, метрики, сбор метрик и мониторингВ разделе мы узнали о роли, настройке и введении важных конечных точек модуля Spring Boot Actuator.

Я также упомянул, что моей основной целью является добавление тревожных сигналов мониторинга в микросервисные приложения нашего проекта. Знакомство с Spring Boot Actuator — это только первый шаг.В этой главе я расскажу:

  • Как интегрировать систему мониторинга и оповещения Prometheus и графический интерфейс Grafana
  • Как настроить индикаторы мониторинга и сделать мониторинг приложений скрытыми точками
  • Как Prometheus интегрирует Alertmanager для оповещений

image.png

теоретическая часть

Prometheus

Prometheus, чье китайское название Prometheus, вдохновлен системой мониторинга Google Brogmon, которая была разработана бывшими инженерами Google на Soundcloud в виде программного обеспечения с открытым исходным кодом с 2012 года, а версия 1.0 была выпущена в июне 2016 года. Prometheus можно рассматривать как реализацию Borgmon, внутренней системы мониторинга Google.

Следующая диаграмма иллюстрирует архитектуру Prometheus и некоторые компоненты его экосистемы. Среди них Alertmanager используется для оповещения, а Grafana — для визуализации данных мониторинга, о чем будет сказано далее в статье.

undefined

Здесь мы узнаем об этих особенностях Prometheus:

  • Сборщик данных, который периодически извлекает данные метрик по протоколу HTTP с настроенным интервалом.
  • База данных временных рядов для хранения всех данных метрик.
  • Простой пользовательский интерфейс, в котором вы можете визуализировать, запрашивать и отслеживать все показатели.

Для получения дополнительной информации, пожалуйста, прочитайтеОфициальная документация Прометея

Grafana

Grafana — это приложение с открытым исходным кодом, написанное на языке go, которое позволяет вам извлекать данные из различных источников данных, таких как Elasticsearch, Prometheus, Graphite, InfluxDB, и визуализировать их с помощью красивых графиков.

undefined

В дополнение к AlertManager Prometheus, который может отправлять оповещения, Grafana также поддерживает оповещения. Grafana может легко определять расположение сигналов тревоги в данных, визуально определять пороговые значения и получать уведомления о сигналах тревоги через такие платформы, как DingTalk и электронная почта. Самое главное — интуитивно определить правила предупреждений, постоянно оценивать и отправлять уведомления.

Поскольку оповещения Grafana относительно слабы, большинство оповещений поступают через Prometheus Alertmanager.

Обратите внимание, что панель инструментов Prometheus также имеет простую графику. Но Grafana намного лучше графически.

Дальнейшее чтение:

Alertmananger

В дополнение к сбору и хранению данных, платформа мониторинга Prometheus также может настраивать правила событий, но эти правила событий должны быть дополнены компонентом Alertmanager для получения уведомления о тревоге.

AlertManager поддерживает группировку предупреждений (отправку нескольких предупреждений вместе), подавление предупреждений и заглушение предупреждений (отсутствие повторяющихся предупреждений, выдаваемых в течение одного периода времени).

undefined

Дальнейшее чтение:Знакомство с Alertmanager на официальном сайте

Мониторинг Java-приложений

режим монитора

В настоящее время существует два способа сбора индикаторов системой мониторинга: один — «проталкивание», а другой — «вытягивание»:

К представителям push относятся ElasticSearch, InfluxDB, OpenTSDB и т. д. Вам необходимо протолкнуть индикаторы из программы в соответствующее приложение мониторинга с помощью TCP, UDP и т. д., но если вы используете только TCP, то после зависания приложения мониторинга или узкое место, легко повлиять на само приложение, а при использовании UDP, хотя вам не нужно беспокоиться о мониторинге приложений, легко потерять данные.

Представитель pull, главный представитель Prometheus, так что нам не придется беспокоиться о мониторинге состояния самого приложения. И вы можете автоматически добавить мониторинг, используя функции обнаружения служб, такие как DNS-SRV или Consul.

Как контролировать

Способ, которым Prometheus отслеживает приложение, очень прост, пока процесс предоставляет адрес доступа HTTP для получения текущих выборочных данных мониторинга. Такая программа называетсяExporter, экземпляр Exporter называетсяTarget. Прометей периодически вращается из этихTargetДля приложения необходимо предоставить только адрес доступа HTTP, содержащий данные мониторинга.Конечно, предоставленные данные должны соответствовать определенному формату.Этот форматMetricsФормат.

metric name>{<label name>=<label value>, ...}

В основном делится на три части Каждая часть должна соответствовать соответствующему регулярному выражению

  • имя метрики: Имя метрики, которое в основном отражает смысл отслеживаемой выборки.a-zA-Z_:*_
  • имя метки: метка отражает размер функции текущего образца.[a-zA-Z0-9_]*
  • значение метки: значение каждой метки, без ограничений по формату

Следует отметить, что лучше всего использовать значения перечисления для значений меток, а не использовать неограниченные значения, такие как идентификатор пользователя, адрес электронной почты и т. д., иначе это будет потреблять много памяти и не соответствует смыслу индикатора коллекция.

MicroMeter

Принцип мониторинга Prometheus кратко описан выше. Итак, как наше приложение Spring Boot предоставляет такой адрес доступа HTTP, и предоставленные данные должны соответствовать приведенному вышеMetricsФормат ?

помните, вПодробное объяснение модуля Spring Boot Actuator: проверка работоспособности, метрики, сбор метрик и мониторингВ, я упомянул, что модуль Actuator также может быть интегрирован с некоторыми внешними системами мониторинга приложений, включая Prometheus. Итак, как Spring Boot Actuator объединяет приложения Spring Boot с системами мониторинга, такими как Prometheus?

этот мостMicroMeter. Micrometer предоставляет общий API для сбора данных о производительности на платформе Java, и приложениям нужно только использовать общий API Micrometer для сбора показателей производительности. Micrometer будет отвечать за завершение работы по адаптации с различными системами мониторинга.

image.png

Практическая часть первая

Далее мы объясним описание, объединяя фактическую демонстрацию.

Для первоначального создания демонстрационного проекта см.Подробное объяснение модуля Spring Boot Actuator: проверка работоспособности, метрики, сбор метрик и мониторинг

Практическая часть будет разделена на две части, в основном эта частьКак приложение интегрирует Prometheus и Grafana для сбора и визуализации метрик.

1. Добавьте зависимости

Чтобы ваше приложение Spring Boot интегрировалось с Prometheus, вам нужно добавитьmicrometer-registry-prometheusполагаться.

<!-- Micrometer Prometheus registry  -->
<dependency>
	<groupId>io.micrometer</groupId>
	<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

После добавления вышеуказанных зависимостей Spring Boot автоматически настроитPrometheusMeterRegistryиCollectorRegistryдля сбора и экспорта данных метрик в формате, который Prometheus может сканировать (упомянутый выше формат метрик).

Все соответствующие данные будут храниться в актуаторе./prometheusКонечная точка открыта. Prometheus может очищать эту конечную точку, чтобы периодически получать данные метрик.

Актуатора/prometheusконечная точка

Мы по-прежнему используем наш предыдущий демонстрационный проект в качестве примера. Копните глубже в содержание этой конечной точки. Добавить кmicrometer-registry-prometheusПосле зависимости мы получаем доступhttp://localhost:8080/actuator/prometheusАдрес, вы можете увидеть следующее содержимое:

# HELP jvm_buffer_total_capacity_bytes An estimate of the total capacity of the buffers in this pool
# TYPE jvm_buffer_total_capacity_bytes gauge
jvm_buffer_total_capacity_bytes{id="direct",} 90112.0
jvm_buffer_total_capacity_bytes{id="mapped",} 0.0
# HELP tomcat_sessions_expired_sessions_total  
# TYPE tomcat_sessions_expired_sessions_total counter
tomcat_sessions_expired_sessions_total 0.0
# HELP jvm_classes_unloaded_classes_total The total number of classes unloaded since the Java virtual machine has started execution
# TYPE jvm_classes_unloaded_classes_total counter
jvm_classes_unloaded_classes_total 1.0
# HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool
# TYPE jvm_buffer_count_buffers gauge
jvm_buffer_count_buffers{id="direct",} 11.0
jvm_buffer_count_buffers{id="mapped",} 0.0
# HELP system_cpu_usage The "recent cpu usage" for the whole system
# TYPE system_cpu_usage gauge
system_cpu_usage 0.0939447637893599
# HELP jvm_gc_max_data_size_bytes Max size of old generation memory pool
# TYPE jvm_gc_max_data_size_bytes gauge
jvm_gc_max_data_size_bytes 2.841116672E9

# 此处省略超多字...

Как видите, это данные индикатора мониторинга программы, организованные в упомянутом выше формате Metrics.

metric name>{<label name>=<label value>, ...}

2. Установка и настройка Прометея

Пожалуйста, обратитесь к официальной документации для установки. Не много содержания, но подробно. Вы можете выбрать бинарную установку или способ докера. Я не буду здесь вдаваться в подробности.

Настроить Прометей

Далее нам нужно настроить Prometheus для сбора наших демо-проектов./actuator/prometheusданные индикатора.

# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']
  # demo job
  -  job_name: 'springboot-actuator-prometheus-test' # job name
     metrics_path: '/actuator/prometheus' # 指标获取路径
     scrape_interval: 5s # 间隔
     basic_auth: # Spring Security basic auth 
       username: 'actuator'
       password: 'actuator'
     static_configs:
     - targets: ['10.60.45.113:8080'] # 实例的地址,默认的协议是http

Сосредоточьтесь на конфигурации здесь:

  # demo job
  -  job_name: 'springboot-actuator-prometheus-test' # job name
     metrics_path: '/actuator/prometheus' # 指标获取路径
     scrape_interval: 5s # 间隔
     basic_auth: # Spring Security basic auth 
       username: 'actuator'
       password: 'actuator'
     static_configs:
     - targets: ['10.60.45.113:8080'] # 实例的地址,默认的协议是http

контрольная работа

После завершения настройки запустим Prometheus для проверки.Если вы находитесь в режиме докера, вprometheus.ymlВыполните следующую команду в каталоге, где находится файл, чтобы запустить Prometheus:

docker run -d -p 9090:9090 \
    -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
    prom/prometheus --config.file=/etc/prometheus/prometheus.yml
  • доступhttp://ip:9090, вы можете увидеть следующий интерфейс:

image.png

  • нажмитеInsert metric at cursorдля выбора индикаторов мониторинга нажмитеGraphчтобы отобразить индикатор в виде графика, нажмитеExecuteкнопку, чтобы увидеть результат, подобный следующему изображению:

image.png

Вы также можете ввести PromQL в поле ввода для более сложных запросов.

PromQL — это настраиваемый язык запросов Prometheus.Через PromQL пользователи могут легко выполнять статистический анализ при мониторинге выборочных данных.

  • Настроить горячую перезагрузку
curl -X POST http://ip:9090/-/reload

3. Установка и настройка Grafana

Как видите, панель мониторинга, которая идет в комплекте с Prometheus, очень «простая». Итак, Grafana представлена ​​для достижения более удобной и удобной для производства визуализации мониторинга.

1. Старт

$ docker run -d --name=grafana -p 3000:3000 grafana/grafana 

2. Войти

доступhttp://ip:3000/login, начальная учетная запись/пароль:admin/admin, при первом входе вас попросят сменить пароль.

3. Настройте источник данных

  • нажмитеConfigurationсерединаAdd Data Source, вы увидите следующий интерфейс:

image.png

  • Здесь выбираем Prometheus в качестве источника данных, здесь настраиваем адрес доступа Prometheus, нажимаемSave & Test:

image.png

4. Создайте панель мониторинга

  • Нажмите на панель навигации+и нажмите Панель инструментов, вы увидите интерфейс, подобный следующему:

image.png

  • нажмитеAdd Query, вы можете увидеть интерфейс, похожий на следующий:

image.png

существуетMetricsВведите здесь запрашиваемый индикатор Значение индикатора см. в приложении Spring Boot./actuator/prometheusконечная точка, напримерjvm_memory_used_bytes,jvm_threads_states_threads,jvm_threads_live_threadsПодождите, Grafana даст вам лучшие подсказки, и вы можете использоватьPromQLРеализовать более сложные вычисления, такие как агрегирование, суммирование, усреднение и т. д. Если вы хотите нарисовать несколько линий, нажмитеAdd Queryкнопка,

  • Нажмите на тот, что нижеVisualization, вы можете выбрать тип визуализации и некоторые связанные конфигурации. Я не буду здесь вдаваться в подробности и предоставлю читателю возможность исследовать его самостоятельно.

image.png

  • Затем нажмите «Далее»GeneralВыполните базовую настройку, не повторяя:

image.png

5. Рынок информационных панелей

На данный момент, я думаю, умные читатели должны научиться визуализировать данные индикатора. Но многим следует подумать, что если индикаторов много, то на самом деле довольно громоздко настраивать.

Существуют ли готовые шаблоны DashBoard общего назначения?

перейти кGrafana Lab - Dashboards, введите ключевое слово для поиска на указанной информационной панели. Вы можете получить то, что хотите😎😎.

Кроме того, эти существующие информационные панели также позволяют нам быстрее осваивать некоторые конфигурации панелей и использовать их.

image.png

6. Представьте приборную панель

Вот две панели инструментов, которые, как мне кажется, проще в использовании:

  • JVM (Micrometer)

  • Spring Boot Statistics

    Я должен упомянуть об этом. Когда я впервые представил его, он был недействителен. Интересно, столкнется ли читатель с той же проблемой, что и я. Если да, перейдите к настройкам панели инструментов и измените переменные.$applicationи$instanceдве переменныеDefinition.

И я лично рекомендую выполнять некоторые пользовательские операции на этих двух панелях управления или комбинировать две панели.

  • Введенная операция очень проста, предпочтительно вам нужноGrafana Lab - DashboardsВыберите свой любимый дашборд, а затем запишите его ID

image.png

  • просто нажмитеImportкнопка:

image.png

  • После ввода ID завершаем настройку, нажимаемImportкнопка:

image.png

  • Эффект следующий:

image.png

Практическая часть вторая

Во второй практической части основноеКак настроить индикаторы мониторинга (например, некоторые из наших бизнес-данных, которые также называются скрытыми точками) и как использовать Alertmanager для завершения мониторинга предупреждений..

1. Пользовательские (бизнес) индикаторы мониторинга

Смоделированный спрос: существует служба заказов, которая отслеживает [сумму заказа в режиме реального времени], [частоту невыполнения заказов в течение 10 минут].

1. Создайте класс управления мониторингом PrometheusPrometheusCustomMonitor

Здесь мы настраиваем три метрики:

  • requests_error_total: Количество невыполненных заказов
  • order_request_count: Общее количество размещенных заказов
  • order_amount_sum: Статистика суммы заказа
@Component
public class PrometheusCustomMonitor {

    /**
     * 记录请求出错次数
     */
    private Counter requestErrorCount;

    /**
     * 订单发起次数
     */
    private Counter orderCount;

    /**
     * 金额统计
     */
    private DistributionSummary amountSum;

    private final MeterRegistry registry;

    @Autowired
    public PrometheusCustomMonitor(MeterRegistry registry) {
        this.registry = registry;
    }

    @PostConstruct
    private void init() {
        requestErrorCount = registry.counter("requests_error_total", "status", "error");
        orderCount = registry.counter("order_request_count", "order", "test-svc");
        amountSum = registry.summary("order_amount_sum", "orderAmount", "test-svc");
    }

    public Counter getRequestErrorCount() {
        return requestErrorCount;
    }

    public Counter getOrderCount() {
        return orderCount;
    }

    public DistributionSummary getAmountSum() {
        return amountSum;
    }
}

2. Добавлено/orderинтерфейс

когдаflag="1", создайте исключение, чтобы имитировать сбой при размещении заказа. Статистика в интерфейсеorder_request_countиorder_amount_sum.

@RestController
public class TestController {

    @Resource
    private PrometheusCustomMonitor monitor;
	
    //....

    @RequestMapping("/order")
    public String order(@RequestParam(defaultValue = "0") String flag) throws Exception {
        // 统计下单次数
        monitor.getOrderCount().increment();
        if ("1".equals(flag)) {
            throw new Exception("出错啦");
        }
        Random random = new Random();
        int amount = random.nextInt(100);
        // 统计金额
        monitor.getAmountSum().record(amount);
        return "下单成功, 金额: " + amount;
    }
}

PS: В реальном проекте при сборе данных бизнес-мониторинга рекомендуется использовать метод АОП для записи и не вторгаться в бизнес-код. Не пишите, как в моем демо.

3. Добавлен глобальный обработчик исключенийGlobalExceptionHandler

Подсчитайте количество невыполненных заказовrequests_error_total:

@ControllerAdvice
public class GlobalExceptionHandler {

    @Resource
    private PrometheusCustomMonitor monitor;

    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public String handle(Exception e) {
        monitor.getRequestErrorCount().increment();
        return "error, message: " + e.getMessage();
    }
}

контрольная работа:

Запустите проект, посетитеhttp://localhost:8080/orderиhttp://localhost:8080/order?flag=1Смоделируйте успех и неудачу размещения заказа, а затем мы посещаемhttp://localhost:8080/actuator/prometheus, вы можете видеть, что наш пользовательский индикатор был/prometheusКонечная точка выставлена:

# HELP requests_error_total  
# TYPE requests_error_total counter
requests_error_total{application="springboot-actuator-prometheus-test",status="error",} 41.0
# HELP order_request_count_total  
# TYPE order_request_count_total counter
order_request_count_total{application="springboot-actuator-prometheus-test",order="test-svc",} 94.0
# HELP order_amount_sum  
# TYPE order_amount_sum summary
order_amount_sum_count{application="springboot-actuator-prometheus-test",orderAmount="test-svc",} 53.0
order_amount_sum_sum{application="springboot-actuator-prometheus-test",orderAmount="test-svc",} 2701.0

4. Добавьте соответствующую панель мониторинга в Grafana

Здесь я добавляю панель инструментов в качестве демонстрации.Некоторые шаги были упомянуты ранее и здесь опущены:

  • Во-первых, создать частоту отказов заказов в течение 10 минут.
sum(rate(requests_error_total{application="springboot-actuator-prometheus-test"}[10m])) / sum(rate(order_request_count_total{application="springboot-actuator-prometheus-test"}[10m])) * 100

image.png

  • Затем рассчитывается общая сумма заказа:

image.png

  • Конечный результат

image.png

2. Добавьте мониторинг

Правила симуляции тревоги:

  1. Является ли служба оффлайн
  2. Превышает ли частота отказов заказов в течение 10 минут более 10 %

1. Разверните Alertmanager

Это развернуто как двоичный пакет.

  • Адрес загрузки последней версии Alertmanager можно получить с официального сайта Prometheus https://prometheus.io/download/

  • После завершения загрузки он будет содержать файл конфигурации alertmanager.yml по умолчанию после распаковки, и мы добавим в него конфигурацию отправки электронной почты.

# 全局配置
global:
  resolve_timeout: 5m
  smtp_smarthost: 'xxxxxx'
  smtp_from: 'xxxx@xx.com'
  smtp_auth_username: 'xxxx@xx.com'
  smtp_auth_password: 'XXXXXX'
# 路由配置
route:
  receiver: 'default-receiver' # 父节点
  group_by: ['alertname'] # 分组规则
  group_wait: 10s # 为了能够一次性收集和发送更多的相关信息时,可以通过group_wait参数设置等待时间
  group_interval: 1m  #定义相同的Group之间发送告警通知的时间间隔
  repeat_interval: 1m
  routes: # 子路由,根据match路由
  - receiver: 'rhf-mail-receiver'
    group_wait: 10s
    match: # 匹配自定义标签
      team: rhf    
# 告警接收者配置
receivers:
- name: 'default-receiver'
  email_configs:
  - to: 'xxxx@xx.com'
- name: 'rhf-mail-receiver'
  email_configs:
  - to: 'xxxx@xx.com'

В настоящее время официальные встроенные сторонние интеграции уведомлений включают: электронную почту, программное обеспечение для обмена мгновенными сообщениями (например, Slack, Hipchat), push-сообщения мобильных приложений (например, Pushover) и инструменты автоматизированной работы и обслуживания (например, Pagerduty, Opsgenie, Victorops). . Метод уведомления Alertmanager также может поддерживать Webhook, с помощью которого разработчики могут добиться более персонализированной расширенной поддержки (DingTalk, корпоративный WeChat и т. д.).

Связанное чтение расширения конфигурации:

  • запускать

Alertmanager сохранит данные в локальном хранилище, путь хранения по умолчаниюdata/. Поэтому перед запуском Alertmanager необходимо создать соответствующую директорию:

./alertmanager

Пользователи также могут использовать параметры для изменения соответствующей конфигурации при запуске Alertmanager.--config.fileИспользуется для указания пути к файлу конфигурации alertmanager,--storage.pathИспользуется для указания пути хранения данных.

  • Проверяем запущенный статус, после запуска заходим9093порт:

image.png

AlertВ меню вы можете просмотреть содержание сигналов тревоги, полученных Alertmanager.SilencesВ меню вы можете создавать тихие правила через пользовательский интерфейс.StatusПод меню вы можете увидеть информацию о конфигурации Alertmanager.

  • Настроить горячую перезагрузку
curl -X POST http://ip:9093/-/reload

2. Установите правила оповещения

Создайте новый в каталоге Prometheustest-svc-alert-rule.yamlЧтобы установить правила тревоги, выполните следующие действия:

groups:
- name: svc-alert-rule
  rules:
  - alert: svc-down # 服务是否下线
    expr: sum(up{job="springboot-actuator-prometheus-test"}) == 0
    for: 1m
    labels: # 自定义标签
      severity: critical
      team: rhf # 我们小组的名字,对应上面match 的标签匹配
    annotations:
      summary: "订单服务已下线,请检查!!"
  - alert: order-error-rate-high # 10分钟内下单失败率是否大于10%
    expr: sum(rate(requests_error_total{application="springboot-actuator-prometheus-test"}[10m])) / sum(rate(order_request_count_total{application="springboot-actuator-prometheus-test"}[10m])) > 0.1
    for: 1m
    labels:
      severity: major
      team: rhf
    annotations:
      summary: "订单服务响应异常!!"
      description: "10分钟订单错误率已经超过10% (当前值: {{ $value }} !!!"

В реальных проектах можно использоватьruleкаталог для хранения всех правил предупреждений, а затемrule/*.yamlспособ настройки

3. Настроить Прометей

существуетprometheus.ymlфайл со ссылкойtest-svc-alert-rule.yamlНастройте правила предупреждений и включите Alertmanager.

alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # alertmanage default start port 9093
      - localhost:9093  
rule_files:
  - /data/prometheus-stack/prometheus/rule/*.yml

4. Тест

Теперь, когда наша конфигурация завершена, загрузите конфигурацию Prometheus. Затем попробуйте вызвать условие оповещения.

  • Служба тестирования находится в автономном режиме, и служба тестирования остановлена ​​вручную.

image.png

  • Исключение тестового заказа

    image.png

  • существуетhttp://ip:9093Интерфейс может видеть сработавшую тревогу

    image.png

резюме

На этом наш модуль мониторинга и сигнализации микросервиса Spring Boot завершен. Надеюсь принести вам некоторую прибыль.

Соответствующий исходный код может бытьGithubсм. выше.

Если эта статья была вам полезна, я надеюсь, что вы можете поставить лайк, это самая большая мотивация для меня 🤝🤝🤗🤗.

Ссылаться на