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: формирование дашбордов». На занятии:

  • Проанализируем возможности по формированию дашбордов.

  • Разберем, как использовать дашборды и тиражирование, а также переменные.

  • Посмотрим формирование дашбордов для различных окружений.

Комментарии (0)