diff --git a/module2/08_docker_containerization_advanced/todo_app/.gitignore b/module2/08_docker_containerization_advanced/todo_app/.gitignore index 8c9f516..9076963 100644 --- a/module2/08_docker_containerization_advanced/todo_app/.gitignore +++ b/module2/08_docker_containerization_advanced/todo_app/.gitignore @@ -1,3 +1,4 @@ Dockerfile docker-compose.yml nginx.conf +prometheus.yml diff --git a/module2/08_docker_containerization_advanced/todo_app/TodoApi/Helpers/TodoHealthCheck.cs b/module2/08_docker_containerization_advanced/todo_app/TodoApi/Helpers/TodoHealthCheck.cs new file mode 100644 index 0000000..53f88b6 --- /dev/null +++ b/module2/08_docker_containerization_advanced/todo_app/TodoApi/Helpers/TodoHealthCheck.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace TodoApi.Helpers +{ + public class TodoHealthCheck : IHealthCheck + { + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken()) + { + return Task.FromResult(new HealthCheckResult(HealthStatus.Healthy)); + } + } +} diff --git a/module2/08_docker_containerization_advanced/todo_app/TodoApi/Program.cs b/module2/08_docker_containerization_advanced/todo_app/TodoApi/Program.cs index a7991cb..d69a8be 100644 --- a/module2/08_docker_containerization_advanced/todo_app/TodoApi/Program.cs +++ b/module2/08_docker_containerization_advanced/todo_app/TodoApi/Program.cs @@ -1,6 +1,8 @@ using Microsoft.EntityFrameworkCore; using System.Text.Json; using System.Text.Json.Serialization; +using Prometheus; +using TodoApi.Helpers; using TodoApi.Model; var builder = WebApplication.CreateBuilder(args); @@ -27,6 +29,15 @@ builder.Services.AddControllers() o.JsonSerializerOptions.Converters.Add(enumConverter); }); +builder.Services.AddMetricServer(options => +{ + options.Port = config.GetRequiredSection("Metrics").GetValue("Port", 9123); +}); + +builder.Services.AddHealthChecks() + .AddCheck(nameof(TodoHealthCheck)) + .ForwardToPrometheus(); + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -35,6 +46,8 @@ app.UseStaticFiles(); app.MapControllers(); +app.UseHttpMetrics(); + app.MapFallbackToFile("index.html"); app.Run(); diff --git a/module2/08_docker_containerization_advanced/todo_app/TodoApi/TodoApi.csproj b/module2/08_docker_containerization_advanced/todo_app/TodoApi/TodoApi.csproj index dab021e..57dcb33 100644 --- a/module2/08_docker_containerization_advanced/todo_app/TodoApi/TodoApi.csproj +++ b/module2/08_docker_containerization_advanced/todo_app/TodoApi/TodoApi.csproj @@ -8,6 +8,9 @@ + + + diff --git a/module2/08_docker_containerization_advanced/todo_app/TodoApi/appsettings.json b/module2/08_docker_containerization_advanced/todo_app/TodoApi/appsettings.json index 70f88e6..36ee201 100644 --- a/module2/08_docker_containerization_advanced/todo_app/TodoApi/appsettings.json +++ b/module2/08_docker_containerization_advanced/todo_app/TodoApi/appsettings.json @@ -8,5 +8,8 @@ "ConnectionStrings": { "PostgreSQL": "Host=localhost;Port=5432;Database=todo;Username=todo;Password=todo" }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Metrics": { + "Port": 9123 + } } diff --git a/module2/10_monitoring_basics/lection.md b/module2/10_monitoring_basics/lection.md index e69de29..0a1f269 100644 --- a/module2/10_monitoring_basics/lection.md +++ b/module2/10_monitoring_basics/lection.md @@ -0,0 +1,10 @@ + +## Ссылки + +- https://prometheus.io/docs/prometheus/latest/getting_started/ +- https://povilasv.me/prometheus-tracking-request-duration/ +- https://www.innoq.com/en/blog/2020/04/scraping-docker-swarm-service-instances-with-prometheus/ +- https://github.com/prometheus/node_exporter +- https://prometheus.io/docs/practices/histograms/ +- https://grafana.com/docs/ +- https://grafana.com/blog/2020/06/23/how-to-visualize-prometheus-histograms-in-grafana/ diff --git a/module2/10_monitoring_basics/tasks.md b/module2/10_monitoring_basics/tasks.md index e69de29..604f169 100644 --- a/module2/10_monitoring_basics/tasks.md +++ b/module2/10_monitoring_basics/tasks.md @@ -0,0 +1,91 @@ +# Задания + +## Prometheus + +1. Вы должны были получить обновленное приложение `todo_app`. + + API-сервис приложения теперь отдает метрики Prometheus на порту `9123`(можно поменять через переменную среды `Metrics__Port`) и по url `/metrics`. + + Таким образом, полное URL для получения метрик, выглядит, например, как `http://todo_api:9123/metrics`. + + Дополните Compose-файл для `todo_app` так, чтобы в сети `todo` запускался контейнер из образа `prom/prometheus`, при этом: + - Напишите для него конфигурацию, то есть файл `prometheus.yml` \ + (который должен в итоге находится в контейнере на месте `/etc/prometheus/prometheus.yml`), \ + таким образом чтобы Prometheus собирал метрики из `todo_api` каждые 5 секунд. + - Прокиньте порт `9090`, на котором находится веб-интерфейс Prometheus по умолчанию, на хост машины с Docker. + - Создайте том данных `todo_prometheus_data`, который бы хранил данные внутренней БД Prometheus, и который бы\ + монтировался на директорию `/prometheus` внутри его контейнера. + + Убедитесь, что веб-интерфейс Prometheus не был доступен извне. Для доступа к нему, сделайте SSH-туннель на вашу локальную машину. + +2. Запустите систему в Compose-режиме, то есть не в режиме кластера, и без дополнительных реплик. + +3. На веб-интерфейсе Prometheus, проверьте, что он собирает метрики из `todo_api`(на вкладке `Targets`) + +4. На веб-интерфейсе Prometheus, выведите значение метрики `dotnet_total_memory_bytes` - общее число байт, находящихся в управляемой(т.е. подотчетной сборщику мусора .NET) памяти API-сервиса. + +5. Выведите максимальное значение этой метрики за последние 15 минут. + +6. Дополните compose-файл так, чтобы в сети `todo` запустить контейнер из образа `bitnami/postgres-exporter`, при этом: + - Установите для этого контейнера переменную среды `DATA_SOURCE_NAME`\ + в значение `postgresql://todo:todo@todo_postgres:5432/todo?sslmode=disable`(при необходимости поменяйте здесь порт, хост, итд.) + - Отредактируйте конфигурацию `prometheus.yml` так, чтобы теперь также собирать метрики из `postgres-exporter`. \ + Postgres Exporter по умолчанию отдает метрики на порту `9187`, и по URL `/metrics` + +7. Проверьте на веб-интерфейсе Prometheus метрику `pg_stat_user_tables_size_bytes` (она показывает количество памяти, занимаемой пользовательскими таблицами в PostgreSQL) + +8. Дополните compose-файл так, чтобы также запускать контейнер из образа `prom/node-exporter`.\ + Учтите, что так как этот exporter используется для мониторинга хост-машин, ему не нужна такая изоляция, как обычным приложениям, и даже наоборот: + - Для этого контейнера необходимо примонтировать корневую директорию хост-машины в read-only режиме, \ + чтобы она была доступна например как директория `/host` внутри контейнера. + - При старте этого контейнера, передайте ему аргумент `--path.rootfs=/host` + - Также, установите значение `--pid` в `host` (`pid: host` в compose-файле) + - Желательно также установить режим сети в `host` (`network_mode: host` в compose-файле) (N.B.: Работает только на Linux, и не работает на Docker Desktop на macOS и Windows) + + node-exporter по умолчанию отдает метрики на порту `9100` и по URL `/metrics`. \ + Доступ к `host`-машине Docker изнутри контейнера Prometheus можно получить по адресу (`host.docker.internal`) + + Дополните конфигурацию `prometheus.yml` так чтобы собирать метрики и из `node-exporter` + +9. Проверьте на веб-интерфейсе Prometheus метрику `node_memory_MemFree_bytes` - она показывает количество свободной оперативной памяти хост-машины(и берет ее из `/proc/meminfo`). + +## Grafana + +1. Дополните compose-файл так, чтобы в сети `todo` запустить контейнер из образа `grafana/grafana`. \ + - Интерфейс grafana по умолчанию доступен на порту 3000, пробростье этот порт на хост машину. + - Также, создайте том данных `todo_grafana_data`, примонтированный в контейнер по пути `/var/lib/grafana` + - Установите для контейнера переменную окружения `GF_USERS_ALLOW_SIGN_UP` в значение `false`, чтобы отключить возможность регистрации пользователей. + +2. Перезапустите систему. Убедитесь что `grafana` доступна извне. Для этого настройте конфигурацию `nginx` на хост-системе, если вы используете его в качестве внешнего прокси. + +3. Зайдите на веб-интерфейс grafana. Логин и пароль по умолчанию - `admin` `admin`(пароль можно поменять установив переменную окружения `GF_SECURITY_ADMIN_PASSWORD`). \ + Поменяйте пароль на свой, если вы не установили его как переменную среды. + +4. Добавьте пользователя с именем `viewer`, и разрешением на просмотр визуализаций(роль `Viewer`). + +5. Добавьте источник данных `Prometheus`, при этом установите его URL в `http://todo_prometheus:9090` (поменяйте хост на имя контейнера, в котором запускается Prometheus) + +6. Создайте новый Dashboard, и добавьте на него новую визуализацию метрики `dotnet_total_memory_bytes`. + - Выберите тип графика как `Time Series` + - Назовите график с визуализацией `.NET Total Memory Bytes` + - Поставьте единицу измерения `bytes(SI)` + - Установите display name для выводимого поля, например в `.NET memory` + - Сделайте так, чтобы на график выводилось значение за последние 5 минут. + - Добавьте вывод порогового значения(`threshold`) в 10 мегабайт, так чтобы график выше этого значения был закрашен желтым. + +7. Сохраните Dashboard под именем `My Dashboard`. Установите график обновления в 5 секунд. + +8. Добавьте визуализацию свободной памяти на хост-машине. Сделайте так чтобы визуализация показывала минимальное за последние 5 минут значение, \ + а сам график выводил показывал отрезок времени в последний час. Сохраните визуализацию и Dashboard. + +9. API-сервис отдает метрику `http_request_duration_seconds`. Она собирает время обработки HTTP-запроса API-сервисом в секундах. \ + `http_request_duration_seconds_bucket` - значения метрики в виде гистограммы. \ + `http_request_duration_seconds_sum` и `http_request_duration_seconds_count` - части этой метрики, отдающие, соответственно сумму времени всех запросов, и общее количество запросов. + + Добавьте визуализацию среднего времени обработки HTTP-запросов API-сервером за последние 5 минут. Сохраните Dashboard. + +10. Перезапустите всю систему, но уже не в Docker Compose, а в Docker Swarm. Сделайте так, чтобы API-сервис работал на трех репликах. + + Убедитесь, что Prometheus правильно собирает метрики со всех трех реплик одновременно(в Targets на интерфейсе Prometheus должны присутствовать все три). + + Отредактируйте визуализации, связанные с API-сервисом так, чтобы они показывали средние значения по всем трем репликам.