Kubernetes де-факто стал стандартом при разработке контейнерных приложений, поскольку он предоставляет огромный набор функциональных возможностей "из коробки", которые помогают разработчикам создавать масштабируемые и отказоустойчивые системы.
Все выглядит прекрасно, если вы разрабатываете что-то с нуля, но всем известно, что для большинства компаний это не так! Со временем многие унаследованные системы превратились в гигантских монолитных монстров, которые работают не на контейнерах, а на виртуальных машинах (ВМ). Рефакторить такие системы очень сложно по разным причинам:
Технические причины (например, зависит от устаревших операционных систем или ядер)
Бизнес-причины (например, время до выхода на рынок, стоимость преобразования)
Трудности с вендорами (например, вендор не предоставляет решение в формате контейнера).
Или, трудно поверить, но новые приложения действительно могут быть разработаны для работы на ВМ вместо контейнеров.
Это не должно помешать вам использовать Kubernetes (K8s), а KubeVirt - подходящий инструмент для того, чтобы перенести эти ВМ в мир K8s. KubeVirt - это надстройка по управлению ВМ для K8s. Его цель - обеспечить общую основу для решений виртуализации поверх K8s. С помощью KubeVirt можно управлять ВМ как ресурсом K8s, подобно подам. Вы можете объявлять, запускать, останавливать, удалять, масштабировать и... контролировать их! Таким же образом, как и в K8s.
Сейчас я расскажу не о самом KubeVirt, а о том, как контролировать ВМ аналогично тому, как вы контролируете контейнеры в K8s. У KubeVirt имеется замечательное руководство пользователя, если вы захотите узнать о нем больше.
Итак, что же является стандартом при мониторинге приложений внутри Kubernetes?
Если вы работаете с Kubernetes достаточно долго, то заметите, что он сам по себе может собирать некоторые метрики о ресурсах, потребляемых подами, такие как использование процессора и памяти. Часто этого бывает недостаточно, и приложения обычно разрабатываются так, чтобы предоставлять дополнительные метрики, которые помогают следить за тем, чтобы они работали так, как ожидалось.
Если мы переносим ВМ на K8s, перед нами встает та же проблема! Процессора и памяти недостаточно, чтобы определить, правильно ли она работает или нет. Обычно нам нужно проверить множество других показателей, например, использование диска и свопа, а так как ВМ не являются эфемерными, как контейнеры, мы должны следить за тем, работает она или нет.
Prometheus - это известное решение для мониторинга и оповещения, включающее в себя множество удобных интеграций с K8s, ставшее самым популярным вариантом при обсуждении выбора мониторинга контейнерных приложений. В основном он используется с Prometheus-Operator, который предоставляет некоторые дополнительные компоненты, значительно облегчающие настройку, и именно его мы будем применять в нашем руководстве.
Сообщество Prometheus также разработало node-exporter, который в основном используется для мониторинга узлов K8s, а также будет использоваться для мониторинга наших ВМ KubeVirt.
Итак, приступим!
Окружающая среда
В данном руководстве будет использоваться следующий набор инструментов:
Helm v3 - для развертывания Prometheus-Operator.
minikube - предоставит нам кластер K8s, однако вы можете выбрать любого другого поставщика K8s.
kubectl - для развертывания различных ресурсов K8s.
virtctl - для взаимодействия с ВМ KubeVirt, может быть загружен из репозитория KubeVirt.
Деплой Prometheus Operator
После того, как у вас есть кластер K8s, с minikube или любым другим провайдером, первым шагом будет деплой Prometheus Operator. Причина в том, что KubeVirt CR, будучи установленным на кластере, определит, существует ли уже ServiceMonitor CR. Если да, то он создаст ServiceMonitors, настроенные на мониторинг всех компонентов KubeVirt (virt-controller, virt-api и virt-handler) "из коробки".
Хотя мониторинг самого KubeVirt не рассматривается в этом руководстве, хорошей практикой является развертывание Prometheus Operator перед установкой KubeVirt.
Чтобы развернуть Prometheus Operator, вам нужно сначала создать его пространство имен, например, monitoring
:
$ kubectl create ns monitoring
Затем разверните оператор в новом пространстве имен:
$ helm fetch stable/prometheus-operator
$ tar xzf prometheus-operator*.tgz
$ cd prometheus-operator/ && helm install -n monitoring -f values.yaml kubevirt-prometheus stable/prometheus-operator
После того как все развернуто, можно удалить все, что было загружено с помощью helm:
$ cd ..
$ rm -rf prometheus-operator*
Следует помнить об имени релиза, которое мы добавили здесь: kubevirt-prometheus
. Имя релиза будет использоваться при объявлении нашего ServiceMonitor
позже.
Деплой KubeVirt Operators и KubeVirt CustomResources
Итак, следующим шагом будет деплой самого KubeVirt. Начнем с его оператора.
Возьмем последнюю версию, затем воспользуемся kubectl create
для деплоя манифеста непосредственно с Github:
$ export KUBEVIRT_VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases | grep tag_name | grep -v -- - | sort -V | tail -1 | awk -F':' '{print $2}' | sed 's/,//' | xargs)
$ kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml
Перед развертыванием KubeVirt CR убедитесь, что все реплики kubevirt-operator готовы; это можно сделать с помощью:
$ kubectl rollout status -n kubevirt deployment virt-operator
После этого мы можем развернуть KubeVirt и аналогичным образом дождаться готовности всех его компонентов:
$ kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml
$ kubectl rollout status -n kubevirt deployment virt-api
$ kubectl rollout status -n kubevirt deployment virt-controller
$ kubectl rollout status -n kubevirt daemonset virt-handler
Если мы хотим мониторить ВМ, которые могут перезапускаться, необходимо, чтобы наши node-exporter были персистентными, и, следовательно, нужно установить для них персистентное хранилище. CDI будет компонентом, отвечающим за это, поэтому мы также развернем его оператор и пользовательский ресурс. Как всегда, подождем, пока нужные компоненты будут готовы, прежде чем приступить к работе:
$ export CDI_VERSION=$(curl -s https://github.com/kubevirt/containerized-data-importer/releases/latest | grep -o "v[0-9]\.[0-9]*\.[0-9]*")
$ kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/$CDI_VERSION/cdi-operator.yaml
$ kubectl rollout status -n cdi deployment cdi-operator
$ kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/$CDI_VERSION/cdi-cr.yaml
$ kubectl rollout status -n cdi deployment cdi-apiserver
$ kubectl rollout status -n cdi deployment cdi-uploadproxy
$ kubectl rollout status -n cdi deployment cdi-deployment
Развертывание ВМ с персистентным хранилищем
Теперь у нас есть все необходимое. Давайте настроим ВМ.
Начнем с PersistentVolumes, которые необходимы для ресурсов DataVolume в CDI. Поскольку я использую minikube без провайдера динамического хранилища, то создам 2 PV со ссылкой на PVC, которые будут на них претендовать. Обратите внимание на claimRef
в каждом из PV.
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-volume
spec:
storageClassName: ""
claimRef:
namespace: default
name: cirros-dv
accessModes:
- ReadWriteOnce
capacity:
storage: 2Gi
hostPath:
path: /data/example-volume/
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-volume-scratch
spec:
storageClassName: ""
claimRef:
namespace: default
name: cirros-dv-scratch
accessModes:
- ReadWriteOnce
capacity:
storage: 2Gi
hostPath:
path: /data/example-volume-scratch/
Вы можете создать манифест YAML с приведенным выше содержимым и создать PV с помощью kubectl apply -f your-pv-manifest.yaml
.
С установленным персистентным хранилищем можно создать нашу ВМ со следующим манифестом:
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachine
metadata:
name: monitorable-vm
spec:
running: true
template:
metadata:
name: monitorable-vm
labels:
prometheus.kubevirt.io: "node-exporter"
spec:
domain:
resources:
requests:
memory: 1024Mi
devices:
disks:
- disk:
bus: virtio
name: my-data-volume
volumes:
- dataVolume:
name: cirros-dv
name: my-data-volume
dataVolumeTemplates:
- metadata:
name: "cirros-dv"
spec:
source:
http:
url: "https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img"
pvc:
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "2Gi"
Это довольно большой манифест с массой информации, поэтому давайте разобьем его на части. Используем API, добавленный KubeVirt, и создаем новый ресурс VirtualMachine
с названием monitorable-vm
.
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachine
metadata:
name: monitorable-vm
В спецификации ВМ мы указываем KubeVirt выполнить автозапуск виртуальной машины после ее создания:
spec:
running: true
Параметр template
используется для создания VirtualMachineInstance
(или сокращенно VMI), который представляет собой работающую ВМ.
template:
metadata:
name: monitorable-vm
labels:
prometheus.kubevirt.io: "node-exporter"
spec:
domain:
resources:
requests:
memory: 1024Mi
devices:
disks:
- disk:
bus: virtio
name: my-data-volume
machine:
type: ""
volumes:
- dataVolume:
name: cirros-dv
name: my-data-volume
Способ объявления VirtualMachineInstance
очень похож на то, как объявляются поды, мы добавляем некоторые ресурсы, диски и тома. Но, обратите внимание на параметры, поскольку некоторые из них немного отличаются от спецификации подов.
Здесь важно заметить, что мы обозначили наш VMI с помощью prometheus.kubevirt.io: "node-exporter"
, эта метка будет использоваться нашим будущим Service для идентификации того, что мы хотим отслеживать именно эту ВМ.
DataVolume
будет получен из dataVolumeTemplate
, который мы создали ниже.
dataVolumeTemplates:
- metadata:
name: "cirros-dv"
spec:
source:
http:
url: "https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img"
pvc:
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "2Gi"
DataVolume
— это абстракция PersistentVolumeClaim
, которая импортирует образ операционной системы в это персистентное хранилище.
Обратите внимание на имя dataVolume
, которое должно совпадать с тем, которое мы использовали в шаблоне VMI. Также заметьте, что мы извлекаем образ непосредственно из интернета, но для этого существуют и другие способы. И последнее замечание, этот dataVolume
создаст 2 PVC с идентичными характеристиками, оба PVC будут носить имя dataVolume
, но у одного из них добавится суффикс -scratch
. Помните, что имя должно совпадать с параметром claimRef
, добавленным к ранее созданным PV.
Ну, а теперь создайте свой YAML манифест и запустите его с помощью:
kubectl create -f your-vm-manifest.yaml
PVC будут созданы, затем CDI создаст под с названием importer-cirros-dv
для импорта нашего образа в PVC. После его завершения будет создан ресурс нашей VirtualMachine
. Поскольку мы указали KubeVirt запустить нашу ВМ сразу после ее создания, вы также обнаружите ресурс VirtualMachineInstance
. И, наконец, VirtualMachineInstances
KubeVirt создают новый компонент под названием virt-launcher . Это не что иное, как под, который запускает процесс виртуализации, так что... мы все еще запускаем контейнеры... Виртуальные машины запускаются внутри контейнера virt-launcher
Если хотите проверить, что все на месте, должно быть что-то похожее на это:
$ kubectl get vm,vmi,pods
NAME AGE VOLUME
virtualmachine.kubevirt.io/monitorable-vm 1m
NAME AGE PHASE IP NODENAME
virtualmachineinstance.kubevirt.io/monitorable-vm 1m Running 172.17.0.20 kubevirt
NAME READY STATUS RESTARTS AGE
pod/virt-launcher-monitorable-vm-vfk5f 1/1 Running 0 1m
Установка node-exporter внутри виртуальной машины
После запуска VirtualMachineInstance
мы можем подключиться к его консоли с помощью команды virtctl console monitorable-vm
. Если требуется ввести пользователя и пароль, укажите свои учетные данные соответствующим образом. Если вы используете образ диска из этого руководства, то пользователь и пароль - cirros
и gocubsgo
соответственно.
Нижеследующий скрипт установит node-exporter и настроит ВМ на постоянный запуск экспортера при загрузке.
$ curl -LO -k https://github.com/prometheus/node_exporter/releases/download/v1.0.1/node_exporter-1.0.1.linux-amd64.tar.gz
$ gunzip -c node_exporter-1.0.1.linux-amd64.tar.gz | tar xopf -
$ ./node_exporter-1.0.1.linux-amd64/node_exporter &
$ sudo /bin/sh -c 'cat > /etc/rc.local <<EOF
#!/bin/sh
echo "Starting up node_exporter at :9100!"
/home/cirros/node_exporter-1.0.1.linux-amd64/node_exporter 2>&1 > /dev/null &
EOF'
$ sudo chmod +x /etc/rc.local
P.S.: Если вы используете другой исходный образ, пожалуйста, настройте node-exporter на запуск во время загрузки соответствующим образом.
Скрейпинг node-exporter ВМ с помощью настройки Prometheus
Настроить Prometheus на скрейпинг node-exporter (или других приложений) очень просто. Все, что нам нужно, это создать новые Service
и ServiceMonitor
:
apiVersion: v1
kind: Service
metadata:
name: monitorable-vm-node-exporter
labels:
prometheus.kubevirt.io: "node-exporter"
spec:
ports:
- name: metrics
port: 9100
targetPort: 9100
protocol: TCP
selector:
prometheus.kubevirt.io: "node-exporter"
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kubevirt-node-exporters-servicemonitor
namespace: monitoring
labels:
prometheus.kubevirt.io: "node-exporter"
release: monitoring
spec:
namespaceSelector:
any: true
selector:
matchLabels:
prometheus.kubevirt.io: "node-exporter"
endpoints:
- port: metrics
interval: 15s
Давайте разберем все по порядку, чтобы убедиться, что мы все настроили правильно. Начнем с Service
:
spec:
ports:
- name: metrics
port: 9100
targetPort: 9100
protocol: TCP
selector:
prometheus.kubevirt.io: "node-exporter"
Согласно спецификации, мы создаем новый порт с названием metrics
, который будет перенаправлен на каждый под с меткой prometheus.kubevirt.io: "node-exporter"
, на порту 9100, который является номером порта по умолчанию для node-exporter.
apiVersion: v1
kind: Service
metadata:
name: monitorable-vm-node-exporter
labels:
prometheus.kubevirt.io: "node-exporter"
Мы также маркируем сам Service с помощью prometheus.kubevirt.io: "node-exporter"
, который будет использоваться объектом ServiceMonitor
.
Теперь давайте посмотрим на нашу спецификацию ServiceMonitor
:
spec:
namespaceSelector:
any: true
selector:
matchLabels:
prometheus.kubevirt.io: "node-exporter"
endpoints:
- port: metrics
interval: 15s
Поскольку ServiceMonitor будет развернут в пространстве имен monitoring
, а наш сервис находится в пространстве имен default
, необходимо, чтобы namespaceSelector.any=true
.
Мы также указываем нашему ServiceMonitor, что Prometheus должен соскрейпить конечные точки из сервисов, маркированных prometheus.kubevirt.io:node-exporter"
и порты которых называются metrics
. К счастью, именно это мы и сделали с нашим Service
.
И последнее, на что следует обратить внимание. Конфигурация Prometheus может быть настроена на наблюдение за несколькими ServiceMonitors. Посмотреть, за какими ServiceMonitors следит наш Prometheus, можно с помощью следующей команды:
# Look for Service Monitor Selector
kubectl describe -n monitoring prometheuses.monitoring.coreos.com monitoring-prometheus-oper-prometheus
Убедитесь, что наш ServiceMonitor имеет все метки, необходимые для Prometheus's Service Monitor Selector. Как правило, селектором является имя релиза, которое мы задали при развертывании нашего Prometheus с помощью helm!
Эта часть может быть действительно сложной. Если у вас возникли проблемы, пожалуйста, посмотрите на Prometheus-operator troubleshooting.
Тестирование
Вы можете выполнить быстрый тест, перенаправив (пробросив) порты через веб-интерфейс Prometheus и выполнив некоторые PromQL:
kubectl port-forward -n monitoring prometheus-monitoring-prometheus-oper-prometheus-0 9090:9090
Для проверки того, что все работает, зайдите на localhost:9090/graph
и выполните PromQL up{pod=~"virt-launcher.*"}
. Prometheus должен вернуть данные, которые собираются из node-exporter monitorable-vm
.
Чтобы посмотреть, как ведут себя метрики, можно поиграть с virtctl
, остановить и запустить ВМ. Заметьте, что при остановке ВМ с помощью virtctl stop monitorable-vm
, VirtualMachineInstance
уничтожается и, таким образом, уничтожается и под. В результате наш Service не сможет найти конечную точку пода, и тогда она будет удалена из целей Prometheus.
С таким поведением оповещения, подобные приведенному ниже, не будут работать, поскольку наша цель буквально исчезла, а не отключилась.
- alert: KubeVirtVMDown
expr: up{pod=~"virt-launcher.*"} == 0
for: 1m
labels:
severity: warning
annotations:
summary: KubeVirt VM {{ $labels.pod }} is down.
НО если у ВМ постоянно происходит крэш без остановки, то под не убивается, а цель по-прежнему будет отслеживаться. Node-exporter никогда не запустится или будет постоянно падать вместе с ВМ, поэтому такое оповещение может сработать:
- alert: KubeVirtVMCrashing
expr: up{pod=~"virt-launcher.*"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: KubeVirt VM {{ $labels.pod }} is constantly crashing before node-exporter starts at boot.
Хакинг
Для вашего удобства почти полностью автоматизированный сценарий можно найти по адресу https://github.com/ArthurSens/kubevirt-VM-monitoring. Скрипт deploy-everything.sh
установит Prometheus, KubeVirt, CDI в ваш кластер k8s, а также настроит PersistentVolume, VirtualMachine, Service и ServiceMonitor. Затем он подключится к консоли VirtualMachine, где вам нужно будет установить node-exporter с помощью shell-скрипта, который можно найти по адресу install-node-exporter.sh
.
Вот и все! Теперь вы знаете, как перенести ваши устаревшие виртуальные машины в кластер Kubernetes, настроить некоторые основные ресурсы и следить за ними, как за контейнерными приложениями!
Обратите внимание, что node-exporter - это только пример, вы можете следовать той же логике для windows ВМ с помощью windows-exporter или ваших собственных приложений, которые работают внутри ваших ВМ. Все, что вам нужно, это чтобы они передавали метрики через HTTP в формате Prometheus, затем настройте Service
и ServiceMonitor
на правильный номер порта.
Материал подготовлен для будущих студентов курса «Observability: мониторинг, логирование, трейсинг». Всех желающих приглашаем на открытый урок «Grafana: формирование дашбордов». На занятии:
Проанализируем возможности по формированию дашбордов.
Разберем, как использовать дашборды и тиражирование, а также переменные.
Посмотрим формирование дашбордов для различных окружений.