В начале моего изучения Docker и Kubernetes мне нехватало простого и понятного примера, с которым можно было бы «поиграться», изучая особенности этой среды. Этой статьей хотелось бы закрыть этот пробел. Здесь я расскажу об интеграции .NET Core приложения с Telegraf и Grafana, о том, как шлются метрики и как деплоить в Docker и Kubernetes. Примеры в статье расчитаны на тех, кто начинает изучать данную область, но базовыми понятиями желательно обладать, чтобы полностью понять статью. В ней описано, как развернуть контейнер, в котором есть StatsD, InfluxDB и Grafana, а также, как отправлять метрики различных типов из приложения.
Примеры в этой статье выполнялись на Kubernetes, идущем с Docker for Windows. Но если у вас Windows Home Edition, то вам не удастся его поставить. Если у вас Windows Professional, то проблем возникнуть не должно. Тут есть пара советов по установке kubernetes на Windows. На Linux должно всё работать нормально, проверял на Ubuntu, 18.04 LTS.
Демонстрация
Сперва, давайте посмотрим, как всё, о чем буду рассказывать, выглядит в действии. В этом примере вы запустите два приложения: одно отправляет запросы другому, второе выполняет некоторую сложную задачу, и оба приложения отправляют некоторые StatsD метрики, которые вы увидите в Grafana. Перед выполнением шагов, описанных здесь, вам необходимо убедиться, что Kubernetes установлен на вашем компьютере. Далее просто следуйте приведенным ниже инструкциям, и вы увидите некоторый результат. Правда, некоторые настройки вам нужно будет выполнить самостоятельно.
$ git clone https://github.com/xtrmstep/DockerNetSample
$ cd .DockerNetSample
$ kubectl apply -f .srcStatsDServerk8s-deployment.yaml
$ .build.ps1
$ .run.ps1
Теперь в браузере можно загрузить URL localhost:3003 и, используя учетные данные root/root, можно получить доступ к интерфейсу Grafana. Метрики уже отправляются, и вы можете попробовать добавить свой дэшбоард. После некоторой настройки вы можете получить что-то вроде этого.
Когда вы решите всё остановить и очистить ресурсы, просто закройте два окна с рабочими процессами и выполните следующие команды, которые очистят объекты в Kubernetes:
$ kubectl delete svc stats-tcp
$ kubectl delete svc stats-udp
$ kubectl delete deployment stats
Теперь давайте поговорим, что произошло.
Вы задеплоили StatsD, InfluxDb и Grafana на локальный Kubernetes
Не так давно открыл для себя, что DockerHub имеет много полезных и уже подготовленных образов. В своём примере я использую один из таких. Репозиторий с образом вы можете найти здесь. Этот образ содержит InfluxDB, Telegraf (StatsD) и Grafana, которые уже настроены для совместной работы. Есть два распространенных способа деплоя образов: первый — это используя docker-compose в Docker, и второй — это деплой на Kubernetes. Оба способа дают возможность деплоить сразу несколько компонетов (образов), описывать настройки сети и другие параметры. Я кратко расскажу о docker-compose, но более детально остановлюсь на развертывании в Kubernetes. Кстати, недавно стало возможным деплоить docker-compose в Kubernetes.
Развертывание с помощью Docker-Compose
Docker-compose распространяется вместе с Docker для Windows. Но вам нужно проверить, какую версию вы можете использовать в своем YAML. Для этого надо узнать версию установленного Docker и по матрице совместимости найти нужную версию. Я установил Docker версии 19.03.5, поэтому я могу использовать файл версии 3.x. Но я буду использовать 2 для совместимости с прошлыми версиями Docker. Вся необходимая информация для написание docker-compose файлы уже есть в описании репозитория: имя образа и порты.
version: '2'
services:
stats:
image: samuelebistoletti/docker-statsd-influxdb-grafana:latest
ports:
- "3003:3003"
- "3004:8888"
- "8086:8086"
- "8125:8125/udp"
В секции ports я делаю видимыми порты из контейнера через порты хост-системы. Если я этого не сделаю, я не смогу получить доступ к ресурсам в контейнере из хост-системы, потому что они будут видны только внутри Docker. Грубо говоря, я не смогу загрузить Grafana в браузере. Подробнее о маппинге портов вы можете почитать здесь. После составления файла, можно его задеплоить. По умолчанию, docker-compose ищет файл docker-compose.yaml в текущей папке, поэтому вы можете запустить его с минимальными параметрами, просто выполнив команду docker-compose up. Это развернет контейнер в Docker. Я буду использовать дополнительные параметры для явного указания файла и запуска контейнера в фоновом режиме.
$ docker-compose -f docker-compose.yaml up -d
$ docker-compose stop
Развёртывание в Kubernetes
На первый взгляд, развертывание в Kubernetes выглядит несколько сложнее, поскольку вам необходимо определить Deployment, Service и другие параметры. Я прибегаю к небольшой хитрости, которая экономит мне время на написание YAML-файлов для Kubernetes. Сначала я деплою все в минимальной конфигурации в кластер, используя kubectl. А затем экспортирую нужные мне объекты, как YAML и уже потом дописываю необходимые настройки.
Заметка о Kubernetes, устанавливаемом на виртуальных машинах в GCP
Я пытался использовать Kubernetes, который развернут на Compute Engine в GCP и столкнулся с проблемой при развертывании сервиса LoadBalancer. Он остаётся в состоянии pending и не получает внешний IP-адрес. Это обстоятельство препятствует доступу к сервису из Интернета, даже если вы настроили сеть. Для этого есть решение, которое требует установки ingress сервиса и использования NodePort, согласно совету на Stackoverflow.
Развёртывание с kubectl
Итак, давайте создадим развертывание из образа. Имя stats — это имя развертывания, которое я сам дал для этого объекта. Вы можете использовать другое имя.
$ kubectl run stats --image=samuelebistoletti/docker-statsd-influxdb-grafana:latest --image-pull-policy=Always
Эта команда создаст Deployment, который, в свою очередь, создаст Pod и ReplicaSet в k8s. Чтобы получить доступ к Grafana, мне нужно создать сервис и выставить порты. Это можно сделать с помощью службы NodePort или LoadBalancer. В большинстве случаев вы будете создавать службы LoadBlancer. Почитать больше об этом можно тут.
$ kubectl expose deployment stats --type=LoadBalancer --port=3003 --target-port=3003
Эта команда также сопоставит порт хоста 3003 (--port) с портом в контейнере (--target-port). После выполнения команды вы сможете получить доступ к Grafana, загрузив URL localhost:3003 в браузере. Вы можете проверить созданные объекты в k8s с помощью этой команды:
$ kubectl get all
Вы должны увидеть что-то, как на это картинке:
Экспорт YAML конфигурации
На данный момент, развернутая система ещё не то, что мне нужно, но я могу использовать её, как черновик. Экспортируем конфигурацию YAML:
$ kubectl get deployment,service stats -o yaml --export > exported.yaml
Экспортированный файл будет содержать определения Deployment и Service с текущей конфигурацией. Мне нужно будет удалить ненужные настройки и добавить маппинг портов. Окончательный минималистский вариант может выглядеть следующим образом:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: stats
spec:
replicas: 1
selector:
matchLabels:
run: stats
template:
metadata:
labels:
run: stats
spec:
containers:
- image: samuelebistoletti/docker-statsd-influxdb-grafana:latest
imagePullPolicy: Always
name: stats
---
apiVersion: v1
kind: Service
metadata:
name: stats-tcp
spec:
type: LoadBalancer
ports:
- name: grafana
protocol: TCP
port: 3003
targetPort: 3003
- name: influxdb-admin
protocol: TCP
port: 3004
targetPort: 8888
- name: influxdb
protocol: TCP
port: 8086
targetPort: 8086
selector:
run: stats
---
apiVersion: v1
kind: Service
metadata:
name: stats-udp
spec:
type: LoadBalancer
ports:
- name: telegraf
protocol: UDP
port: 8125
targetPort: 8125
selector:
run: stats
Заметка об использовании протоколов TCP/UDP
Вы не сможете создать службу типа LoadBalancer, которая поддерживает протоколы TCP и UDP. Это известное ограничение, и сообщество пытается найти какое-то решение. Между тем вы можете создать два отдельных сервиса для каждого из типов протоколов.
Перед использованием нового файла очистите существующие ресурсы в Kubernetes, а затем используйте команду kubectl apply.
$ kubectl delete svc stats
$ kubectl delete deployment stats
$ kubectl apply -f k8s-deployment.yaml
Вы только что задеплоили образ в Kubernetes с правильными настройками портов. Используя URL, упоминавшийся выше, вы можете открыть Grafana, но теперь еще можно слать и метрики StatsD. В следующем разделе я немного расскажу про метрики.
StatsD протокол
Протокол StatsD очень прост, вы, даже, можете создать свою собственную клиентскую библиотеку, если это очень нужно. Здесь вы можете прочитать больше о датаграммах StatsD. Этот протокол поддерживает такие метрики, как счетчики (counter), время (timing), измерения (gauge) и т.д.
counter.name:1|c
timing.name:320|ms
gauge.name:333|g
Counters используются для подсчета количества некоторых событий. Обычно, вы просто всегда увеличиваете некоторый счетчик (bucket в StatsD). Агрегация делается на более низком уровне, поэтому когда вы будете получать значение за секунды, минуты, вам не придется заниматься дополнительными подсчетами, и в Grafana вы увидите число в секундах, минутах и так далее, как пожелаете.
Timing используется для измерения продолжительности какого-либо процесса. Например, этот показатель просто идеально подходит для измерения продолжительности веб-запроса.
Gauge используется, чтобы сделать замер некоторого текущего состояния ресурса. Например, количества доступной память или число свободных потоков.
Метрики в .NET Core сервисе
Вам понадобится пакет NuGet JustEat.StatsD. Он имеет хорошее описание на GitHub. Так что, просто следуйте его инструкциям, чтобы сделать свою собственную конфигурацию и зарегистрироваться в IoC.
В качестве примера, давайте возьмем API, где некоторый метод, когда вызван, ставит расчет в очередь потоков, используя ThreadPool. Логика API допускает только определенное количество параллельно выполняемых вычислений. Допустим, вы хотите знать следующее о вашем сервисе:
- Сколько запросов приходит?
- Сколько запросов ожидает, прежде чем ThreadPool выдаст поток?
- Сколько времени занимает операция?
- Насколько быстро заканчиваются свободные потоки в API?
Вот как может выглядеть сбор метрик в коде:
public override async Task<FactorialReply> Factorial(FactorialRequest request, ServerCallContext context)
{
// Obtain the number of available threads in ThreadPool
ThreadPool.GetAvailableThreads(out var availableThreads, out _);
// The number of available threads is the example of Gauge metric
// Send gauge metric to StatsD (using JustEat.StatsD nuget)
_stats.Gauge(availableThreads, "GaugeAvailableThreads");
// Increment a counter metric for incoming requests
_stats.Increment("CountRequests");
// The method _stats.Time() will calculate the time while the _semaphoreSlim.WaitAsync() were waiting
// and send the metric to StatsD
await _stats.Time("TimeWait", async f => await _semaphoreSlim.WaitAsync());
try
{
// Again measure time length of calculation and send it to StatsD
var result = await _stats.Time("TimeCalculation", async t => await CalculateFactorialAsync(request.Factor));
// Increment a counter of processed requests
_stats.Increment("CountProcessed");
return await Task.FromResult(new FactorialReply
{
Result = result
});
}
finally
{
_semaphoreSlim.Release();
}
}
В коде количество одновременных вычислений контролируется классом SemaphoreSlim. Если число параллельных выполнений превысит максимальное, оно остановит выполнение и будет ждать завершения какого-либо другого потока.
Автор: Aleksandr Goida