Предотвратить убийство некоторых стручков при возможном уменьшении?

Мне нужно масштабировать набор модулей, которые запускают воркеры на основе очередей. Задания для рабочих могут выполняться в течение длительного времени (часы) и не должны прерываться. Количество модулей зависит от длины рабочей очереди. Масштабирование будет осуществляться либо с помощью горизонтального автомасштабирования с использованием настраиваемых показателей, либо с помощью простого контроллера, который изменяет количество реплик.

Проблема с любым решением заключается в том, что при уменьшении масштаба нет контроля над тем, какие поды будут завершены. В любой момент времени большинство работников, вероятно, работают над короткими заданиями, простаивают или (реже) обрабатывают длительные задания. Я бы хотел избежать убийства долго работающих рабочих, простаивающие или короткие рабочие задания могут быть уволены без проблем.

Что можно было бы сделать с минимальной сложностью? Одна вещь, о которой я могу думать, - это делать это на основе использования ЦП стручками. Не идеально, но вполне могло быть достаточно. Другой метод может заключаться в том, что рабочие каким-то образом выставляют приоритет, указывающий, являются ли они предпочтительным модулем для удаления. Однако этот приоритет может меняться каждый раз, когда работник берется за новую работу.

В конце концов, все рабочие места будут сокращаться, и эта проблема исчезнет, ​​но на данный момент это более долгосрочная цель.


person Stragulus    schedule 24.04.2019    source источник
comment


Ответы (2)


Во время процесса прекращения пода, Kubernetes отправляет сигнал SIGTERM в контейнер вашего модуля. Вы можете использовать этот сигнал для корректного завершения работы приложения. Проблема в том, что Kubernetes не ждет вечно, пока ваше приложение завершится, и в вашем случае вашему приложению может потребоваться много времени для выхода.
В этом случае я рекомендую вам использовать preStop hook, который выполняется до того, как Kubernetes отправит сигнал KILL в контейнер. здесь о том, как использовать обработчики:

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: nginx
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
      preStop:
        exec:
          command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]
person victortv    schedule 24.04.2019
comment
Это не сработает для предполагаемого решения, поскольку длительные задания не могут возобновиться из сохраненного состояния; им придется начинать все сначала. - person Stragulus; 24.04.2019
comment
Вам не обязательно сохранять состояние в команде preStop, на самом деле вы можете делать все, что захотите, в настраиваемом скрипте внутри контейнера. Пример: command: ["/bin/sh","/myscript.sh;]. Что вы можете сделать, так это проверить внутри этого скрипта, свободен ли работник или занят. В случае занятости, подождите некоторое время и еще раз проверьте статус рабочего. После достижения состояния ожидания скрипт завершится, и Kubernetes убьет модуль. Пожалуйста, поправьте меня, если я не правильно понял, что вы имели в виду. - person victortv; 24.04.2019
comment
Я вижу две проблемы с этим подходом: в документации k8s говорится: «Пользователи должны сделать свои обработчики ловушек как можно более легкими». Однако бывают случаи, когда имеют смысл долго выполняющиеся команды, например, при сохранении состояния до остановки контейнера. - это могло заблокировать на долгое время (часы). Во-вторых, по истечении льготного периода он также откажется от «крючка». Хотя это число можно было бы установить очень высоким, это снова кажется плохой практикой. Есть ли у вас опыт использования этой стратегии и хорошо ли она работает? - person Stragulus; 25.04.2019

Существует своего рода обходной путь, который может дать некоторый контроль над завершением работы модуля. Не совсем уверен, что это лучшая практика, но, по крайней мере, вы можете попробовать и проверить, подходит ли это вашему приложению.

  1. Увеличьте Deployment льготный период на terminationGracePeriodSeconds: 3600, где 3600 - время в секундах самой длинной возможной задачи в приложении. Это гарантирует, что модули не будут отключены к концу льготного периода. Прочтите документы о процессе завершения работы модуля в деталь.
  2. Определите обработчик preStop. Более подробную информацию о хуках жизненного цикла можно найти в документации, а также в в пример. В моем случае я использовал приведенный ниже сценарий для создания файла, который позже будет использоваться в качестве триггера для завершения работы модуля (возможно, есть более элегантные решения).
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "touch /home/node/app/preStop"]
    
    
  3. Остановите работу приложения, как только будет выполнено условие. Когда приложение закрывается, модуль также завершает свою работу. Невозможно завершить процесс с помощью сценария оболочки PID 1 из preStop, поэтому вам нужно добавить некоторую логику в приложение для его завершения. В моем случае это приложение NodeJS, есть планировщик, который запускается каждые 30 секунд и проверяет, выполняются ли два условия. !isNodeBusy определяет, разрешено ли завершать приложение и fs.existsSync('/home/node/app/preStop') была ли запущена preStop ловушка. Логика вашего приложения может быть другой, но вы поняли основную идею.
    schedule.scheduleJob('*/30 * * * * *', () => {
      if(!isNodeBusy && fs.existsSync('/home/node/app/preStop')){
        process.exit();
      }
    });
    

Имейте в виду, что этот обходной путь работает только с voluntary disruptions и явно бесполезен с involuntary disruptions. Дополнительная информация в документах.

person Juniper    schedule 01.08.2020