Перейти к содержанию

Примеры процедур

Статья содержит готовые примеры процедур для задач администрирования кластера. Каждый пример включает:

  • Постановку задачи.

  • Подробное объяснение структуры.

  • Параметры запуска.

  • Рекомендации по использованию.

Используйте эти примеры как шаблоны для создания собственных процедур.

Отслеживание и восстановление состояния ВМ

Задача

Автоматически обнаруживать ВМ в некорректном состоянии и выполнять их перезагрузку в следующих случаях:

  • ВМ «зависает» и не отвечает.

  • Необходимо автоматическое восстановление сервисов.

  • Мониторинг критичных ВМ.

YAML-описание процедуры

vars:
  value:
    type: "number"
  vcl:
    type: "string"
    visibility: "public"
  vmname:
    type: "string"
    visibility: "public"
  state:
    type: "string"
    visibility: "public"
steps:
  save_value:
    action:
      apicall: "scheduler request show --name {{ vmname }} --vcluster {{ vcl }} --kind vm --metrics yes"
      parse:
        - filter: "metrics.libvirt_domain_info_vmstate[].value[] | [-1]"
          op: "store"
          var: "value"
      log:
        after: "{{ value }}"
  reboot-on-error:
    conditions:
      - apicall: "scheduler request show --vcluster {{ vcl }} --name {{ vmname }} --metrics yes --kind vm"
        expect:
        - filter: "metrics.libvirt_domain_info_vmstate[].value[] | [-1]"
          op: "!="
          value: 1
        attempts:
          max: 1
          interval: 15
        on-error:
          - error-type: AttemptsLimitReached
          handler: _exit
    action:
      apicall: "scheduler request state --vcluster {{ vcl }} --name {{ vmname }} --kind vm --state {{ state }}"
  wait-for-reboot:
    conditions:
      - apicall: "scheduler request show --vcluster {{ vcl }} --name {{ vmname }} --metrics yes --kind vm"
        expect:
        - filter: "metrics.libvirt_domain_info_vmstate[].value[] | [-1]"
          op: "=="
          value: 1
        attempts:
          max: 3
          interval: 60

Подробное объяснение

Переменные vars:

  1. value (number, private) — хранит последнее значение метрики состояния ВМ.
  2. vcl (string, public) — имя виртуального кластера.
  3. vmname (string, public) — имя виртуальной машины.
  4. state (string, public) — целевое состояние ВМ.

Шаг 1: save_value

Получить текущее состояние ВМ и сохранить в переменную.

Действие:

  1. Выполняет команду scheduler request show с параметрами:

    • --name {{ vmname }} — имя ВМ;
    • --vcluster {{ vcl }} — имя виртуального кластера;
    • --kind vm — тип ресурса;
    • --metrics yes — с метриками.
  2. JMESPath выражение

        "metrics.libvirt_domain_info_vmstate[].value[] | [-1]"
    

    Берет массив значений метрики libvirt_domain_info_vmstate, выбирает последний элемент ([-1]), сохраняет в переменную value.

  3. Логирует полученное значение.

Шаг 2: reboot-on-error

Проверить, что ВМ не в состоянии 1 (running), и если нет — перезагрузить.

Условия:

  1. Проверяет состояние ВМ той же командой, что в и в save_value.
  2. Ожидает последнее значение метрики ≠ 1.
  3. Попытки: максимум 1 попытка с интервалом 15 секунд.
  4. При ошибке: если достигнут лимит попыток происходит выход из процедуры _exit.

Действие:

Выполняет перезагрузку ВМ

scheduler request state --vcluster {{vcl}} --name {{vmname}} --kind vm --state {{state}}

Шаг 3: wait-for-reboot

Дождаться успешной перезагрузки ВМ.

Условия:

  1. Проверяет состояние ВМ.
  2. Ожидает последнее значение метрики = 1 (running).
  3. Попытки: максимум 3 попытки с интервалом 60 секунд.

Как использовать

Возможны следующие варианты использования:

  1. Запуск процедуры вручную

    checker procedure execute --name vm_monitoring 
                      --vars '{
                        "vcl": "production-vdc",
                        "vmname": "web-server-01",
                        "state": "reboot"
                      }'
    

  2. Планирование проверки каждые 5 минут

    checker procedure schedule add --name vm_monitoring 
                                   --vars '{"vcl": "production-vdc", "vmname": "web-server-01", "state": "reboot"}' 
                                   --cron "*/5 * * * *"
    


Фенсинг узлов кластера

Процедура состоит их двух частей:

  1. Основная процедура Координация процесса.
  2. Процедура fence_node Обработка конкретного узла.

Важно

Процедуры Фенсинг узлов кластера и Обработка конкретного узла предназначены только для пространства ВЦОД управления.
Не делегируйте их в пользовательские ВЦОД — это может привести к сбоям в работе сервисов

Задача

Автоматическое восстановление работоспособности кластера при отказе физических узлов:

  • Обнаружение «упавших» узлов.
  • Параллельный фенсинг всех проблемных узлов.
  • Перераспределение ВМ на рабочие узлы.

Основная процедура Координация процесса

YAML-описание процедуры

config:
  run-on-vip: true

vars:
  exclude_current_node:
    type: boolean
    default: false
    visibility: public
  down_nodes:
    type: array[string]

steps:
  collect-down-nodes:
    conditions:
      # Выходим, если все узлы рабочие, и не требуется мигрировать vm
      - apicall:
          - "cluster nodes list"
          - "scheduler labels list"
        expect:
          - filter: 'length(difference([0][?status==`DOWN`].uuid, [1]."system_drain_node=yes".nodes || `[]`))'
            op: ">"
            value: 0
        attempts:
          min: 2
          max: 2
          interval: 10
        on-error:
          - error-type: AttemptsLimitReached
            handler: _exit
    action:
      apicall:
        - "cluster nodes list"
        - "scheduler labels list"
      parse:
        - filter: 'difference([0][?status==`DOWN`].uuid, [1]."system_drain_node=yes".nodes || `[]`)'
          op: "store"
          var: "down_nodes"
      log: "Found down nodes: {{down_nodes}}"
  process-down-nodes:
    action:
      apicall: 'checker procedure execute --name fence_node --vars ''{ "node": {{*down_nodes}}, "exclude_current_node": {{exclude_current_node}} }'' --json yes'
      parse:
        - filter: "@"
          op: "push"
          var: "@deps" # Добавляем подпроцедуру как зависимую для текущей процедуры
  check-errors: # К этому шагу переходим только когда все подпроцедуры завершились
    conditions:
      - const: "{{@deps_status}}" # Читаем текущие статусы подпроцедур
        expect:
          - filter: "length([?task_status != `SUCCESS`])"
            op: "=="
            value: 0
        attempts:
          max: 1
        log:
          before: "{{*@deps_status | (task_status != `SUCCESS` && join(': ', ['Error', task_name || '', run_error || '', task_message || ''])) || ''}}"

Подробное объяснение

Конфигурация выполняется только на VIP-узле для гарантии доступности

config:
  run-on-vip: true

Переменные:

  1. exclude_current_node (boolean, public, default: false)— исключать ли текущий узел из доступных для миграции ВМ.
  2. down_nodes (array[string], private) — массив UUID "упавших" узлов.

Шаг 1: collect-down-nodes

Найти все узлы со статусом DOWN, которые не находятся на обслуживании.

Условия:

  1. Выполняет две команды параллельно:

  2. cluster nodes list — список всех узлов;

  3. scheduler labels list — список меток для поиска узлов на обслуживании.

  4. JMESPath выражение

    'length(difference([0][?status==`DOWN`].uuid, [1]."system_drain_node=yes".nodes || `[]`)) > 0'
    

    • [0][?status=='DOWN'].uuid — UUID всех узлов со статусом DOWN;
    • [1]."system_drain_node=yes".nodes — узлы с меткой обслуживания;
    • difference(...) — узлы в статусе DOWN, но не на обслуживании;
    • length(...) > 0 — проверка, что такие узлы есть.
  5. Попытки. Ровно 2 попытки с интервалом 10 секунд для того, чтобы убедиться, что узел действительно «упал».

  6. При ошибке, если после 2 попыток нет узлов для фенсинга, происходит выход из процедуры _exit.

Действие:

  1. Повторно выполняет те же команды.
  2. Сохраняет найденные UUID узлов в переменную down_nodes.
  3. Логирует результат "Found down nodes: [uuid1, uuid2, ...]".

Шаг 2: process-down-nodes

Запустить подпроцедуру фенсинга для каждого проблемного узла.

Действие:

  1. Запускает подпроцедуру fence_node для всех узлов параллельно

    checker procedure execute --name fence_node --vars '{"node": [uuid1, uuid2], "exclude_current_node": false}' --json yes
    
  2. Распаковка массива {{*down_nodes}} создаст параметр "node": [uuid1, uuid2].

  3. Параметр --json yes — результат возвращается в машиночитаемом формате.
  4. Сохранение в зависимости: результат UUID подзадачи добавляется в @deps.
  5. Основная процедура будет ждать завершения всех подпроцедур.

Шаг 3: check-errors

Проверить результаты выполнения всех подпроцедур.

Условия:

  1. Использует встроенную переменную {{@deps_status}} — статусы всех зависимостей.
  2. JMESPath выражение

    "length([?task_status != `SUCCESS`]) == 0"
    

    Подсчитывает подпроцедуры со статусом ≠ SUCCESS и ожидает, что таких подпроцедур нет == 0.

  3. Логирование ошибок

    "{{*@deps_status | (task_status != `SUCCESS` && join(': ', ['Error', task_name || '', run_error || '', task_message || ''])) || ''}}"
    

    Для каждой неудачной подпроцедуры формирует сообщение об ошибке в формате "Error: <имя_задачи>: <ошибка>: <сообщение>".

Как использовать

Возможны следующие варианты использования:

  1. Запустить с исключением текущего узла из доступных для миграции

    checker procedure execute --name cluster_fencing 
                              --vars '{"exclude_current_node": true}'
    
  2. Фенсинг по расписанию не рекомендован, тк является критичной операцией и может привести к нежелательным последствиям:

    • если мониторинг узлов даст сбой, процедура может начать фенсить работоспособные узлы;
    • фенсинг перезагружает узлы и мигрирует ВМ, что может нарушить работу сервисов.

Подпроцедура Обработка конкретного узла fence_node

Важно

Процедура Обработка конкретного узла предназначена только для пространства ВЦОД управления.
Не делегируйте ее в пользовательские ВЦОД — это может привести к сбоям в работе сервисов

YAML-описание процедуры

config:
  run-on-vip: true
  timeout: 600 # Максимум 10 мин на работу этой процедуры

vars:
  # Задаем при запуске процедуры
  node:
    type: string
    visibility: public
  exclude_current_node:
    type: boolean
    visibility: public

steps:
  restart-node:
    action:
      apicall: "commands ipmi power reset --node_uuid {{node}}"
  exclude-node:
    conditions:
      - const: "{{exclude_current_node}}"
        expect:
          - filter: "@"
            op: "=="
            value: true
        attempts:
          max: 1
        on-error:
          - error-type: AttemptsLimitReached
            handler: _continue
    action:
      apicall: "scheduler drain add --nodes {{node}}"
  reschedule-vms:
    action:
      apicall: "scheduler request reschedule --node_uuid {{node}}"
      parse:
        - filter: "@"
          op: "push"
          var: "@deps"
  reinclude-node:
    conditions:
      - const: "{{exclude_current_node}}"
        expect:
          - filter: "@"
            op: "=="
            value: true
        attempts:
          max: 1
        on-error:
          - error-type: AttemptsLimitReached
            handler: _continue
    action:
      apicall: "scheduler drain del --nodes {{node}}"
  check-errors:
    conditions:
      - const: "{{@deps_status}}"
        expect:
          - filter: "length([?task_status != `SUCCESS`])"
            op: "=="
            value: 0
        attempts:
          max: 1
        log:
          before: "{{*@deps_status | (task_status != `SUCCESS` && join(': ', ['Error', task_name || '', run_error || '', task_message || ''])) || ''}}"

Подробное объяснение

Конфигурация выполняется на VIP-узле с таймаутом 10 минут (600 с)

config:
  run-on-vip: true
  timeout: 600

Переменные:

  • node (string, public) — UUID узла для фенсинга;
  • exclude_current_node (boolean, public) — параметр из основной процедуры.

Шаг 1: restart-node

Выполнить hard reset узла через IPMI.

Действие:

  1. Отправляет команду

    commands ipmi power reset --node_uuid {{ node }}
    

  2. Не ждёт завершения перезагрузки, только отправляет команду.

  3. Если команда не отправлена, процедура завершается с ошибкой.

Шаг 2: exclude-node

Если exclude_current_node == true, добавить метку drain на узел.

Условия:

  1. Проверяет значение {{ exclude_current_node }}.
  2. Ожидание: значение = true.
  3. При ошибке: если условие не выполняется, то пропуск шага _continue.

Действие:

  1. Выполняет команду

    scheduler drain add --nodes {{ node }}
    

  2. Добавляет метку system_drain_node=yes.

  3. Scheduler не будет размещать ВМ на этой узле.

Шаг 3: reschedule-vms

Запустить миграцию всех ВМ с проблемного узла.

Действие:

  1. Выполняет команду
scheduler request reschedule --node_uuid {{node}}
  1. Отвязывает все ВМ от указанного узла.
  2. Scheduler автоматически перераспределяет их на другие узлы.
  3. Сохранение в зависимости: результат UUID задачи добавляется в @deps.

Текущий шаг будет ждать завершения миграции ВМ.

Шаг 4: reinclude-node

Если ранее добавляли метку drain, необходимо удалить ее.

Условия и действие: Аналогично шагу exclude-node, но для удаления метки:

scheduler drain del --nodes {{node}}

Шаг 5: check-errors

Проверить результат миграции ВМ.

Условия:

  1. Использует {{@deps_status}} — статус задачи миграции ВМ.
  2. Ожидание: все задачи миграции завершены успешно SUCCESS.
  3. Логирование: аналогично основной процедуре, формирует сообщения об ошибках.

Как использовать подпроцедуру отдельно

Ручной запуск фенсинга для конкретного узла кластера

checker procedure execute --name fence_node 
                          --vars '{
                            "node": "uuid_current_node,
                            "exclude_current_node": true
                          }'

Термины и определения содержатся в статьях: