module2/10_monitoring_basics #5

Merged
vlpr merged 10 commits from v2 into main 1 year ago

@ -0,0 +1,578 @@
### 1. Многоступенчатая сборка образов
#### 1.1.1
````Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS api_build
WORKDIR /src
COPY ./TodoApi /src
RUN dotnet restore TodoApi.csproj
RUN dotnet build TodoApi.csproj -c Release -o /app/build
FROM api_build AS publish
RUN dotnet publish TodoApi.csproj -c Release -o /app/publish
FROM node:21 AS ui_build
WORKDIR /src
COPY ./todo_ui /src
RUN npm install
RUN npm run build
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
COPY --from=publish /app/publish .
COPY --from=ui_build /src/wwwroot ./wwwroot
ENTRYPOINT ["./TodoApi"]
````
В директории todo_app:
````bash
docker build -t todo-bundle .
````
#### 1.1.2
Создание сети:
````bash
docker network create todo
````
Создание тома для PostgerSQL:
````bash
docker volume create todo_pgdata
````
Запуск PostgreSQL:
````bash
docker run -d --name=todo_postgres --network=todo -e POSTGRES_DB=todo -e POSTGRES_USER=todo -e POSTGRES_PASSWORD=todo --mount type=bind,source=./initdb,target=/docker-entrypoint-initdb.d --mount type=volume,source=todo_pgdata,target=/var/lib/postgresql/data postgres:16
````
#### 1.1.3
````bash
docker run --rm -p 8080:80 --name todo_bundle -e 'ConnectionStrings__PostgreSQL=Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo' --name=todo_bundle --network=todo -t todo-bundle
````
#### 1.2.1
````bash
docker container rm -f todo_bundle && docker image rm -f todo-bundle
````
Докер-файл для API-сервиса
````Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY . /src
RUN dotnet restore TodoApi.csproj
RUN dotnet build TodoApi.csproj -c Release -o /app/build
FROM build AS publish
RUN dotnet publish TodoApi.csproj -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["./TodoApi"]
````
Сборка образа API-сервиса:
(из директории `TodoApi`)
````bash
docker build -t todo-api .
````
Конфигурация Nginx:
````nginx
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
#gzip on;
keepalive_timeout 65;
server {
listen 80;
location / {
root /var/www/todo;
index index.html;
try_files $uri $uri/ /index.html =404;
}
location /api {
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffers 8 64k;
proxy_busy_buffers_size 128k;
proxy_buffer_size 64k;
client_max_body_size 10m;
proxy_http_version 1.1;
proxy_pass http://todo_api;
}
}
}
````
Докер-файл для JS-приложения:
````Dockerfile
FROM node:21 AS ui_build
WORKDIR /build
COPY . /build
RUN npm install
RUN npm run build
FROM nginx AS base
WORKDIR /var/www/todo
COPY --from=ui_build /build/nginx.conf /etc/nginx/nginx.conf
COPY --from=ui_build /build/wwwroot ./
````
Сборка образа UI:
(из директории `todo_ui`)
````bash
docker build -t todo-ui .
````
#### 1.2.2
Запуск API-сервиса:
````bash
docker run -d --name=todo_api --network=todo -e 'ConnectionStrings__PostgreSQL=Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo' todo-api
````
Запуск UI:
````bash
docker run -d -p 8080:80 --name=todo_ui --network=todo todo-ui
````
#### 1.2.3
````Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim AS build
WORKDIR /src
COPY . /src
RUN dotnet restore TodoApi.csproj
RUN dotnet build TodoApi.csproj -c Release -o /app/build
FROM build AS publish
RUN dotnet publish TodoApi.csproj --self-contained -r linux-x64 -c Release -o /app/publish
FROM debian:bullseye-slim AS base
WORKDIR /app
RUN apt-get update && apt-get install libicu67
COPY --from=publish /app/publish .
ENTRYPOINT ["./TodoApi"]
````
В директории `TodoApi`:
````bash
docker build -t todo-api .
````
Запуск, как и выше:
````bash
docker run -d --name=todo_api --network=todo -e 'ConnectionStrings__PostgreSQL=Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo' todo-api
````
### 2. Политики перезапуска
#### 1.
Добавить в `docker run` опцию `--restart=unless-stopped`
#### 2.
````bash
docker run -d --name=todo_postgres --network=todo -e POSTGRES_DB=todo -e POSTGRES_USER=todo -e POSTGRES_PASSWORD=todo --mount type=bind,source=./initdb,target=/docker-entrypoint-initdb.d --mount type=volume,source=todo_pgdata,target=/var/lib/postgresql/data --health-cmd='pg_isready -U todo -d todo' --health-interval=10s --health-retries=5 --health-timeout=5s --health-start-period=10s --restart=unless-stopped postgres:16
````
### 3. Ограничения ресурсов
#### 1.
Добавить опцию `--memory` в `docker run`
#### 2.
Добавить опцию `--cpus` в `docker run`
### 4. Docker compose
#### 1.
`docker compose version`
https://docs.docker.com/compose/install/linux/
#### 1.
````yaml
version: '3.9'
services:
hello:
image: busybox
entrypoint: /bin/echo 'Hello, World!'
````
Запуск, здесь и далее: `docker compose up`
#### 2.
из директории `cats_app` из прошлой лабы:
Dockerfile взять из solutions.md оттуда
````yaml
version: '3.9'
services:
cats_app:
container_name: cats_app
image: cats-app
build:
context: ../
dockerfile: ./cats_app/Dockerfile
networks:
- cats
nginx:
image: nginx
container_name: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- cats_app
ports:
- 5000:80
networks:
- cats
networks:
cats:
driver: bridge
````
#### 3.
см. ниже
#### 4.
````yaml
version: '3.9'
services:
todo_api:
image: todo-api:latest
container_name: todo_api
depends_on:
- todo_postgres
build: ./TodoApi
environment:
ConnectionStrings__PostgreSQL: "Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo"
ASPNETCORE_URLS: 'http://*:80'
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1'
memory: 500M
networks:
- todo
todo_ui:
image: todo-ui:latest
container_name: todo_ui
depends_on:
- todo_api
build: ./todo_ui
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1'
memory: 300M
ports:
- 8080:80
networks:
- todo
todo_postgres:
image: postgres:16
container_name: todo_postgres
environment:
POSTGRES_DB: "todo"
POSTGRES_USER: "todo"
POSTGRES_PASSWORD: "todo"
PGDATA: "/var/lib/postgresql/data"
volumes:
- ./initdb:/docker-entrypoint-initdb.d
- todo_pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U todo -d todo"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
deploy:
resources:
limits:
cpus: '2'
memory: 800M
networks:
- todo
todo_pgadmin:
container_name: todo_pgadmin
image: dpage/pgadmin4:7
depends_on:
- todo_postgres
environment:
PGADMIN_DEFAULT_EMAIL: "todo@example.com"
PGADMIN_DEFAULT_PASSWORD: "todo"
PGADMIN_CONFIG_SERVER_MODE: "False"
volumes:
- todo_admin_data:/var/lib/pgadmin
ports:
- "5050:80"
restart: unless-stopped
networks:
- todo
volumes:
todo_pgdata:
todo_admin_data:
networks:
todo:
driver: bridge
````
### 5. Docker Swarm
#### 1.
````bash
docker swarm init
````
Потом необходимо запомнить токен
#### 2.
````bash
docker node ls
````
#### 3.
````bash
docker swarm join-token worker
````
#### 4.
````bash
docker network create -d overlay todo_swarm
````
#### 5.
````bash
docker volume create todo_swarm_pgdata --sharing onewriter
````
#### 6.
````bash
docker service create --name=todo_postgres --replicas 1 --network=todo_swarm -e POSTGRES_DB=todo -e POSTGRES_USER=todo -e POSTGRES_PASSWORD=todo --mount type=bind,source=./initdb,target=/docker-entrypoint-initdb.d --mount type=volume,source=todo_swarm_pgdata,target=/var/lib/postgresql/data --health-cmd='pg_isready -U todo -d todo' --health-interval=10s --health-retries=5 --health-timeout=5s --health-start-period=10s postgres:16
````
#### 7.
````bash
docker service create --name=todo_api --replicas 1 --network=todo_swarm -e 'ConnectionStrings__PostgreSQL=Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo' -e 'ASPNETCORE_URLS=http://*:80' todo-api
````
#### 8.
````bash
docker service create -p 8080:80 --name=todo_ui --replicas 1 --network=todo_swarm todo-ui
````
#### 9.
Открыть localhost:8080 в браузере
#### 10.
````bash
docker service scale todo_ui=3 todo_api=3
````
#### 11.
````bash
docker service ps todo_ui
````
````bash
docker service ps todo_api
````
#### 12.
````bash
docker service logs todo_api
````
#### 13.
````bash
docker service rm todo_api && docker service rm todo_ui && docker service rm todo_postgreq
````
#### 14
````yaml
version: '3.9'
services:
todo_api:
image: todo-api:latest
depends_on:
- todo_postgres
environment:
ConnectionStrings__PostgreSQL: "Host=todo_postgres;Port=5432;Database=todo;Username=todo;Password=todo"
ASPNETCORE_URLS: 'http://*:80'
deploy:
mode: replicated
replicas: 3
resources:
limits:
cpus: '1'
memory: 500M
networks:
- todo
todo_ui:
image: todo-ui:latest
depends_on:
- todo_api
deploy:
mode: replicated
replicas: 3
resources:
limits:
cpus: '1'
memory: 300M
ports:
- 8080:80
networks:
- todo
todo_postgres:
image: postgres:16
environment:
POSTGRES_DB: "todo"
POSTGRES_USER: "todo"
POSTGRES_PASSWORD: "todo"
PGDATA: "/var/lib/postgresql/data"
volumes:
- ./initdb:/docker-entrypoint-initdb.d
- todo_pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U todo -d todo"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
deploy:
resources:
limits:
cpus: '2'
memory: 800M
networks:
- todo
todo_pgadmin:
image: dpage/pgadmin4:7
depends_on:
- todo_postgres
environment:
PGADMIN_DEFAULT_EMAIL: "todo@example.com"
PGADMIN_DEFAULT_PASSWORD: "todo"
PGADMIN_CONFIG_SERVER_MODE: "False"
volumes:
- todo_admin_data:/var/lib/pgadmin
ports:
- "5050:80"
networks:
- todo
volumes:
todo_pgdata:
todo_admin_data:
networks:
todo:
driver: overlay
````
#### 15.
````bash
docker stack deploy --compose-file docker-compose.yml
````

@ -1,3 +1,4 @@
Dockerfile Dockerfile
docker-compose.yml docker-compose.yml
nginx.conf nginx.conf
prometheus.yml

@ -0,0 +1,12 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace TodoApi.Helpers
{
public class TodoHealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
{
return Task.FromResult(new HealthCheckResult(HealthStatus.Healthy));
}
}
}

@ -1,6 +1,8 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Prometheus;
using TodoApi.Helpers;
using TodoApi.Model; using TodoApi.Model;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -27,6 +29,15 @@ builder.Services.AddControllers()
o.JsonSerializerOptions.Converters.Add(enumConverter); o.JsonSerializerOptions.Converters.Add(enumConverter);
}); });
builder.Services.AddMetricServer(options =>
{
options.Port = config.GetRequiredSection("Metrics").GetValue<ushort>("Port", 9123);
});
builder.Services.AddHealthChecks()
.AddCheck<TodoHealthCheck>(nameof(TodoHealthCheck))
.ForwardToPrometheus();
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
@ -35,6 +46,8 @@ app.UseStaticFiles();
app.MapControllers(); app.MapControllers();
app.UseHttpMetrics();
app.MapFallbackToFile("index.html"); app.MapFallbackToFile("index.html");
app.Run(); app.Run();

@ -8,6 +8,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.11" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.11" />
<PackageReference Include="prometheus-net" Version="8.1.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="8.1.0" />
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="8.1.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -8,5 +8,8 @@
"ConnectionStrings": { "ConnectionStrings": {
"PostgreSQL": "Host=localhost;Port=5432;Database=todo;Username=todo;Password=todo" "PostgreSQL": "Host=localhost;Port=5432;Database=todo;Username=todo;Password=todo"
}, },
"AllowedHosts": "*" "AllowedHosts": "*",
"Metrics": {
"Port": 9123
}
} }

@ -1,7 +1,5 @@
# todo-ui # todo-ui
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup ## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).

@ -0,0 +1,38 @@
<template>
<div class="toast fade" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="5000" ref="elt">
<div class="toast-header text-white bg-danger">
<strong class="me-auto">Error</strong>
<small class=""></small>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
<div v-for="msg in messages" :key="msg">
{{ msg }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Toast } from 'bootstrap'
defineProps({
messages: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['closed'])
const elt = ref()
let toastObject = null
onMounted(() => {
toastObject = new Toast(elt.value)
toastObject.show()
elt.value.addEventListener("hidden.bs.toast", () => {
emit('closed')
})
})
</script>

@ -78,18 +78,24 @@
</div> </div>
</div> </div>
</div> </div>
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<ErrorToast v-for="item in errors" :key="item" :messages="item.messages" @closed="removeToast(item)" />
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed, reactive } from 'vue'
import { onBeforeRouteUpdate, useRoute, RouterLink } from 'vue-router' import { onBeforeRouteUpdate, useRoute, RouterLink } from 'vue-router'
import svc from '../services/todoService' import svc from '../services/todoService'
import { useDebouncedRef } from '../services/composables' import { useDebouncedRef } from '../services/composables'
import TodoItem from './TodoItem.vue'; import TodoItem from './TodoItem.vue';
import ErrorToast from './ErrorToast.vue';
const route = useRoute() const route = useRoute()
const errors = reactive([])
const totalCount = ref(0) const totalCount = ref(0)
const left = ref(0) const left = ref(0)
const search = ref() const search = ref()
@ -111,6 +117,41 @@ const completedFilter = computed(() => {
}) })
const isEmpty = computed(() => !totalCount.value) const isEmpty = computed(() => !totalCount.value)
function handleError(e) {
const messages = []
console.log(e)
const response = e.response
if (response) {
const status = response.status
if(status) {
if(status == 400) {
messages.push('API Error. HTTP 400: Bad request')
} else if (status == 401) {
messages.push('API Error. HTTP 401: Unauthorized')
} else if (status == 403) {
messages.push('API Error. HTTP 403: Forbidden')
} else if (status == 404) {
messages.push('API Error. HTTP 404: Not found')
} else if (status == 500) {
messages.push('API Error. HTTP 500: Internal server error')
} else {
messages.push(`API Error. HTTP ${status}`)
}
} else {
messages.push('API Error. Network error')
}
} else if (e.code == 'ERR_NETWORK') {
messages.push('API Error: Connection refused')
} else if (e.code == 'ECONNABORTED' || e.code == 'ETIMEDOUT') {
messages.push('API Error: Timeout')
} else if (e.code == 'ERR_BAD_RESPONSE') {
messages.push('API Error: Bad response')
} else {
messages.push(`Unidentified error: ${e}`)
}
errors.push({ messages })
}
async function updateStatus() { async function updateStatus() {
try { try {
const status = await svc.status() const status = await svc.status()
@ -122,7 +163,7 @@ async function updateStatus() {
isCompleted.value = false isCompleted.value = false
} }
} catch (e) { } catch (e) {
console.log(e) handleError(e)
} }
} }
@ -137,7 +178,7 @@ async function refresh() {
totalCount.value = rv.totalCount totalCount.value = rv.totalCount
await updateStatus() await updateStatus()
} catch (e) { } catch (e) {
console.log(e) handleError(e)
} finally { } finally {
isLoading.value = false isLoading.value = false
} }
@ -152,7 +193,7 @@ async function deleteItem(id) {
}) })
await updateStatus() await updateStatus()
} catch (e) { } catch (e) {
console.log(e) handleError(e)
} finally { } finally {
isLoading.value = false isLoading.value = false
} }
@ -173,7 +214,7 @@ async function onSubmit() {
await updateStatus() await updateStatus()
} }
catch (e) { catch (e) {
console.log(e) handleError(e)
} finally { } finally {
isLoading.value = false isLoading.value = false
} }
@ -197,7 +238,7 @@ async function onToggle(todo) {
} }
await updateStatus() await updateStatus()
} catch (e) { } catch (e) {
console.log(e) handleError(e)
} finally { } finally {
isLoading.value = false isLoading.value = false
} }
@ -223,7 +264,7 @@ async function onToggleAll() {
await updateStatus() await updateStatus()
} }
catch (e) { catch (e) {
console.log(e) handleError(e)
} finally { } finally {
isLoading.value = false isLoading.value = false
} }
@ -234,13 +275,20 @@ async function deleteCompleted() {
try { try {
await svc.deleteCompleted(); await svc.deleteCompleted();
} catch (e) { } catch (e) {
console.log(e) handleError(e)
} finally { } finally {
isLoading.value = false isLoading.value = false
} }
await refresh() await refresh()
} }
function removeToast(item) {
const idx = errors.findIndex(x => x == item)
if (idx >= 0) {
errors.splice(idx, 1)
}
}
onMounted(refresh) onMounted(refresh)
onBeforeRouteUpdate(async to => { onBeforeRouteUpdate(async to => {

@ -0,0 +1,46 @@
## Основы мониторинга
Одна из главных задач системного администратора или DevOps-инженера это мониторинг состояния и работоспособности подотчетных ему систем.
Инструментов и средств для мониторинга существует огромное множество, и в простейших случаях они могут быть даже самописными. Вы уже сталкивались с такими в приложениях `cats_app` и при использовании healthcheck в Docker. В реальном мире для мониторинга используются внешние сторонние инструменты, такие, как Google Analytics, Zabbix, Elastic Search(и вообще стек ELK), Sentry, и другие, однако на этом занятии мы рассмотрим один из наиболее распространенных стеков программного обеспечения, применяемых для этих задач - Prometheus + Grafana.
## Prometheus
Prometheus - это серверное ПО, собирающее метрики из каких-либо других систем, и хранящее их в своей специализированной базе данных, приспособленной для обработки временных рядов(time series).
Отличие метрик от логов в том, что метрики дают более общую картину о каком-либо процессе, тогда как логи обычно показывают какое-либо событие или же их последовательность.
Prometheus может собирать метрики сам, делая запросы по HTTP(если какая либо платформа и система имеет возможность их таким образом показывать), либо же, получать их через сторонние средства, называемые экспортерами(exporters). Один из наиболее примечательных экспортеров называется `node-exporter`, который собирает информацию об операционной системе, на которой запущен.
После того как метрики были собраны в базу, к ней можно делать запросы на специализированном языке PromQL, специально предназначенном для обработки временных рядов и статистики, точно так же как SQL предназначен для обработки реляционных данных.
Prometheus поддерживает 4 вида метрик "из коробки":
- `Counter` - метрика этого типа используется чтобы замерять какое-либо неубывающее значение, как пример - общее количество HTTP-запросов к серверу.
- `Gauge` - данный вид метрик используется для отображения значений, которые могут как увеличиваться, так и уменьшаться. Пример - количество памяти, занимаемой процессом.
- `Histogram` - такие метрики представляют собой счетчики событий для нескольких заданных диапазонов. Используются, например, для отслеживания времени обработки HTTP-запросов.
- `Summary` - похожи на `Histogram`, но представляют собой препросчитанное разбиение значений по квантилям. Этот тип метрик не поддерживает агрегацию, что делает его слабо приспособленным для распределенных приложений в кластере.
К каждой метрике Prometheus могут быть прикрепленые разные метки(label), которые позволяют, например, идентифицировать источник информации.
После того, как метрики были собраны в TSDB - внутреннюю базу данных Prometheus, их могут собирать и обрабатывать сторонние системы, такие как Grafana(для их визуализации), или, например, Alertmanager(для, к примеру, оповещения о превышении каких-либо значений путем email, slack и т.п.; его использование выходит за рамки данного занятия).
## Grafana
Grafana это система для визуализации данных с открытым исходным кодом. Очень часто используется в связке вместе с Prometheus.
Хотя веб-интерфейс самого Prometheus позволяет создавать базовые визуализации, Grafana добавляет множество удобств - возможность группировки визуализаций в Dashboard, помощь в составлении выражений PromQL, огромное количество видов визуализаций, плагины, и так далее.
По Grafana существует больше количество подробной документации и примеров, как на официальном сайте, так и в других местах интернета.
## Полезные ссылки
- https://prometheus.io/docs/prometheus/latest/getting_started/
- https://habr.com/ru/companies/tochka/articles/683608/
- 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://prometheus.io/docs/visualization/grafana/
- https://grafana.com/docs/
- https://grafana.com/blog/2020/06/23/how-to-visualize-prometheus-histograms-in-grafana/
- https://www.timescale.com/blog/four-types-prometheus-metrics-to-collect/

@ -0,0 +1,11 @@
1. Чем метрики отличаются от логов?
2. Как называется файл конфигурации Prometheus?
3. Какие виды метрик поддерживает Prometheus?
4. Зачем нужны экспортеры(exporters)?
5. Что делает node_exporter, для чего он нужен?
6. Какие типы данных поддерживает PromQL?
7. Что делает функция `rate()` из PromQL и зачем она нужна?
8. Как в PromQL вывести среднее значение какой-либо метрики(напр. типа Gauge, и с названием my_metric), за последние 5 минут?
9. Как сконфигурировать Prometheus в кластере Docker Swarm так, чтобы он собирал метрики из всех репликаций какого-либо сервиса Swarm, доступных по одному доменному имени?
10. Как установить пароль администратора в Grafana?
11. Как экспортировать Dashboard в Grafana и потом импортировать назад?

@ -0,0 +1,91 @@
# Задания
## Prometheus
1. Обновите приложение `todo_app` из задания 08 сделав `git pull` ветки `main`.
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-сервисом так, чтобы они показывали средние значения по всем трем репликам.

@ -7,7 +7,19 @@ Web интерфейс созданного в задании Proxmox досту
Cайты доступены по адресу https://ha1.studX.myoffice.ru/ и https://ha2.studX.myoffice.ru/. При остановке активной машины сайт должен обслуживаться резервным сервером. Cайты доступены по адресу https://ha1.studX.myoffice.ru/ и https://ha2.studX.myoffice.ru/. При остановке активной машины сайт должен обслуживаться резервным сервером.
## 3. docker ## 3. docker
Cайт с демонстрацией развёрнутого в docker приложения доступен по адресу https://docker.studX.myoffice.ru/. Выполните контейнеризацию приложения `todo_app` и предоставьте доступ к веб-интерфейсу по адресу https://docker.studX.myoffice.ru/.
## 4. postfix + dovecot ## 4. monitoring
Grafana должна быть доступна по адресу https://grafana.studX.myoffice.ru/
К ней должен быть доступ на просмотр у пользователя `viewer` (с паролем `viewer`, хотя можете придумать свой)
В ней должны присутствовать три визуализации работы системы `todo_app`:
- Среднее время обработки HTTP-запроса API-сервисом за последние 10 минут
- Средний объем управляемой памяти .NET в репликах API-сервиса за последние 10 минут (метрика `dotnet_total_memory_bytes`)
- Количество свободной оперативной памяти в системе, где работает API-сервис
## 5. postfix + dovecot
Из почтового клиента можно отправить почту с почтового ящика stud@studX.myoffice.ru и прочитать входящие сообщения. Из почтового клиента можно отправить почту с почтового ящика stud@studX.myoffice.ru и прочитать входящие сообщения.
Loading…
Cancel
Save