AWS Elastic Beanstalk, запуск cronjob

89

Я хотел бы знать, есть ли способ настроить задание / задачу cron для выполнения каждую минуту. В настоящее время любой из моих экземпляров может выполнять эту задачу.

Вот что я безуспешно пытался сделать в файлах конфигурации:

container_commands:
  01cronjobs:
    command: echo "*/1 * * * * root php /etc/httpd/myscript.php"

Я не совсем уверен, что это правильный способ сделать это

Любые идеи?

Онема
источник
1
Правильная команда? Я имею в виду ... это может быть: command: echo "* / 1 * * * * root php /etc/httpd/myscript.php"> /etc/cron.d/something В любом случае я бы посоветовал вам использовать leader_only, иначе все машины запустят это задание cron одновременно
aldrinleal
Да! определенно используя флаг leader_only, попробую изменить команду.
Onema 02

Ответы:

97

Вот как я добавил задание cron в Elastic Beanstalk:

Создайте папку в корне вашего приложения с именем .ebextensions, если она еще не существует. Затем создайте файл конфигурации в папке .ebextensions. Я буду использовать example.config в целях иллюстрации. Затем добавьте это в example.config

container_commands:
  01_some_cron_job:
    command: "cat .ebextensions/some_cron_job.txt > /etc/cron.d/some_cron_job && chmod 644 /etc/cron.d/some_cron_job"
    leader_only: true

Это файл конфигурации YAML для Elastic Beanstalk. При копировании в текстовый редактор убедитесь, что в текстовом редакторе используются пробелы вместо табуляции. В противном случае вы получите ошибку YAML, когда нажмете это на EB.

Это создает команду 01_some_cron_job. Команды запускаются в алфавитном порядке, поэтому 01 гарантирует, что он будет запущен как первая команда.

Затем команда берет содержимое файла с именем some_cron_job.txt и добавляет его в файл с именем some_cron_job в /etc/cron.d.

Затем команда изменяет разрешения для файла /etc/cron.d/some_cron_job.

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

Затем создайте файл с именем some_cron_job.txt внутри папки .ebextensions. Вы разместите свои задания cron в этом файле.

Так например:

# The newline at the end of this file is extremely important.  Cron won't run without it.
* * * * * root /usr/bin/php some-php-script-here > /dev/null

Таким образом, это задание cron будет запускаться каждую минуту каждого часа каждого дня от имени пользователя root и сбрасывать вывод в / dev / null. / usr / bin / php - это путь к php. Затем замените some-php-script-here на путь к вашему файлу php. Это, очевидно, предполагает, что ваше задание cron должно запускать файл PHP.

Также убедитесь, что файл some_cron_job.txt имеет новую строку в конце файла, как сказано в комментарии. В противном случае cron не запустится.

Обновление: существует проблема с этим решением, когда Elastic Beanstalk масштабирует ваши экземпляры. Например, допустим, у вас есть один экземпляр с запущенным заданием cron. Вы получаете увеличение трафика, поэтому Elastic Beanstalk масштабирует вас до двух экземпляров. Leader_only гарантирует, что между двумя экземплярами будет выполняться только одно задание cron. Ваш трафик уменьшается, а Elastic Beanstalk сокращает вас до одного экземпляра. Но вместо завершения второго экземпляра Elastic Beanstalk завершает работу первого экземпляра, который был лидером. Теперь у вас нет запущенных заданий cron, поскольку они выполнялись только на первом прерванном экземпляре. См. Комментарии ниже.

Обновление 2: просто поясняю это из комментариев ниже: теперь AWS имеет защиту от автоматического завершения инстанса. Просто включите его на своем экземпляре лидера, и все готово. - Николас Аревало 28 окт.

Анархтика
источник
12
Я использовал ваше предложение в течение некоторого времени и недавно столкнулся с проблемой, когда каким-то образом лидер переключался, в результате чего несколько экземпляров запускали cron. Чтобы решить эту проблему, я изменил 01_some_cron_jobк 02_some_cron_jobи добавил 01_remove_cron_jobsсо следующим: command: "rm /etc/cron.d/cron_jobs || exit 0". Таким образом, после каждого развертывания cron_jobsфайл будет только у лидера . Если лидеры меняются, вы можете просто передислоцироваться, и кроны будут исправлены, чтобы бежать еще раз.
Виллем Рензема 02
4
Я бы посоветовал не полагаться на leader_onlyсобственность. Он используется только во время развертывания, и если вы уменьшите масштаб или ваш «ведущий» экземпляр выйдет из строя, у вас обязательно
появится
2
Не делай этого. Это слишком ненадежно. Единственный способ заставить это работать - запустить микро-экземпляр и запустить оттуда задания cron с помощью CURL. Это гарантирует, что только один экземпляр запускает его, и лидер, у которого установлены crons, не завершается.
Бен Синклер
1
Я попытался исправить это с помощью небольшого скрипта
Ruby
8
AWS теперь имеет защиту от автоматического завершения работы инстансов. Просто включите его на своем экземпляре лидера, и все готово.
Николас Аревало
58

Это официальный способ сделать это сейчас (2015+). Пожалуйста, сначала попробуйте этот способ, это, безусловно, самый простой из доступных в настоящее время и самый надежный метод.

Согласно текущим документам, можно запускать периодические задачи на так называемом рабочем уровне .

Ссылаясь на документацию:

AWS Elastic Beanstalk поддерживает периодические задачи для уровней рабочей среды в средах с заранее определенной конфигурацией со стеком решений, содержащим «v1.2.0» в имени контейнера. Вы должны создать новую среду.

Также интересна часть о cron.yaml :

Чтобы вызывать периодические задачи, исходный пакет приложения должен включать файл cron.yaml на корневом уровне. Файл должен содержать информацию о периодических задачах, которые вы хотите запланировать. Укажите эту информацию, используя стандартный синтаксис crontab.

Обновление: мы смогли получить эту работу. Вот несколько важных ошибок из нашего опыта (платформа Node.js):

  • При использовании файла cron.yaml убедитесь, что у вас установлена ​​последняя версия awsebcli , поскольку более старые версии не будут работать должным образом.
  • Также жизненно важно создать новую среду (по крайней мере, в нашем случае это было), а не просто клонировать старую.
  • Если вы хотите убедиться, что CRON поддерживается в вашем экземпляре EC2 Worker Tier, введите ssh в него ( eb ssh) и запустите cat /var/log/aws-sqsd/default.log. Он должен сообщать как aws-sqsd 2.0 (2015-02-18). Если у вас нет версии 2.0, при создании среды что-то пошло не так, и вам нужно создать новую, как указано выше.
xaralis
источник
2
О cron.yaml есть потрясающая запись в блоге: Запуск заданий cron на Amazon Web Services (AWS) Elastic Beanstalk - Medium
jwako
5
Спасибо за этот - вопрос новичка - мне нужно, чтобы мой cron дважды в час проверял базу данных моего веб-приложения на предмет предстоящих событий календаря и отправлял напоминание по электронной почте, когда это произойдет. Какая здесь наилучшая настройка? Должен ли я указать URL-адрес cron.yaml на маршрут в моем веб-приложении? Или я должен предоставить своему рабочему приложению env доступ к базе данных? Так мало об этом!
Кристиан,
5
@christian Как мы это делаем, у нас одно и то же приложение работает в двух разных средах (поэтому особой конфигурации не требуется) - рабочий и общий веб-сервер. В рабочей среде есть некоторые специальные маршруты, включенные путем установки переменной ENV, которую ищет наше приложение. Таким образом, вы можете установить специальные маршруты только для рабочих в своем cron.yaml, имея при этом роскошь общей базы кода с обычным приложением. Ваше рабочее приложение может легко получить доступ к тем же ресурсам, что и веб-сервер: база данных, модели и т. Д.
xaralis
1
@JaquelinePassos v1.2.0 - это версия стека решений. Он должен позволить вам выбрать, какую версию стека решений вы хотите создать при создании новой среды. Подойдет все, что новее v1.2.0. Что касается URL-адреса, это должен быть URL-адрес, который прослушивает ваше приложение, а не путь к файлу. Невозможно запускать команды управления Django, он выполняет только HTTP-запросы.
xaralis
4
Мне не совсем ясно, есть ли способ избежать выделения дополнительной машины EC2 только для запуска заданий cron через cron.yaml. В идеале он должен работать на том же компьютере, что и тот, который обслуживает HTTP-запросы (т.е. веб-уровень).
Венцель Якоб
31

Что касается ответа jamieb, и, как упоминает alrdinleal, вы можете использовать свойство leader_only, чтобы гарантировать, что только один экземпляр EC2 выполняет задание cron.

Цитата взята из http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html :

вы можете использовать leader_only. Один экземпляр выбирается лидером в группе автоматического масштабирования. Если для параметра leader_only установлено значение true, команда запускается только на экземпляре, помеченном как лидер.

Я пытаюсь добиться того же на моем eb, поэтому обновлю свой пост, если я его решу.

ОБНОВИТЬ:

Хорошо, теперь у меня есть рабочие cronjobs со следующей конфигурацией eb:

files:
  "/tmp/cronjob" :
    mode: "000777"
    owner: ec2-user
    group: ec2-user
    content: |
      # clear expired baskets
      */10 * * * * /usr/bin/wget -o /dev/null http://blah.elasticbeanstalk.com/basket/purge > $HOME/basket_purge.log 2>&1
      # clean up files created by above cronjob
      30 23 * * * rm $HOME/purge*
    encoding: plain 
container_commands:
  purge_basket: 
    command: crontab /tmp/cronjob
    leader_only: true
commands:
  delete_cronjob_file: 
    command: rm /tmp/cronjob

По сути, я создаю временный файл с заданиями cron, а затем устанавливаю crontab для чтения из временного файла, а затем удаляю временный файл. Надеюсь это поможет.

лучше чем жизнь
источник
3
Как вы можете гарантировать, что экземпляр, на котором запущен этот crontab, не будет остановлен автоматическим масштабированием? По умолчанию он завершает самый старый экземпляр.
Себастьен
1
Это проблема, которую я еще не смог решить. Мне кажется недостатком функциональности amazon то, что команды leader_only не применяются к новому лидеру, когда текущий прерывается EB. Если вы что-то придумали, поделитесь!
beterthanlife
7
Итак, я (наконец) обнаружил, как предотвратить отключение лидера с помощью автоматического масштабирования - настраиваемых политик завершения автоматического масштабирования. См. Docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/…
beterthanlife
1
@Nate Вы, наверное, уже поняли это, но, судя по тому, в каком порядке они выполняются, "команды" выполняются перед "container_commands", поэтому вы должны создать файл, затем удалить его, а затем попытаться запустить crontab .
clearf
1
@Sebastien, чтобы сохранить самую старую интанс, вот что я делаю: 1 - меняю защиту прерывания интанса на ENBABLE. 2 - Перейдите в Auto Scale Group и найдите свой идентификатор среды EBS, нажмите «ИЗМЕНИТЬ» и измените политику завершения на «NewestInstance»
Роналду Баия,
12

Как упоминалось выше, основной недостаток установки любой конфигурации crontab заключается в том, что это происходит только при развертывании. Поскольку кластер автоматически масштабируется вверх, а затем обратно, предпочтительно, чтобы он также был первым выключенным сервером. Вдобавок не будет переключения при отказе, что для меня было критично.

Я провел небольшое исследование, а затем поговорил с нашим специалистом по аккаунтам AWS, чтобы обсудить идеи и проверить решение, которое я придумал. Вы можете добиться этого с помощью OpsWorks , хотя это немного похоже на использование дома, чтобы убить муху. Также можно использовать Data Pipeline с Task Runner , но это имеет ограниченные возможности в сценариях, которые он может выполнять, и мне нужно было иметь возможность запускать сценарии PHP с доступом ко всей базе кода. Вы также можете выделить экземпляр EC2 вне кластера ElasticBeanstalk, но тогда у вас не будет повторного переключения при отказе.

Итак, вот что я придумал, что, по-видимому, нетрадиционно (как прокомментировал представитель AWS) и может считаться взломом, но он работает и надежен с отказом. Я выбрал решение для кодирования с использованием SDK, которое я покажу на PHP, хотя вы можете использовать тот же метод на любом языке, который вам больше нравится.

// contains the values for variables used (key, secret, env)
require_once('cron_config.inc'); 

// Load the AWS PHP SDK to connection to ElasticBeanstalk
use Aws\ElasticBeanstalk\ElasticBeanstalkClient;

$client = ElasticBeanstalkClient::factory(array(
    'key' => AWS_KEY,
    'secret' => AWS_SECRET,
    'profile' => 'your_profile',
    'region'  => 'us-east-1'
));

$result = $client->describeEnvironmentResources(array(
    'EnvironmentName' => AWS_ENV
));

if (php_uname('n') != $result['EnvironmentResources']['Instances'][0]['Id']) {
    die("Not the primary EC2 instance\n");
}

Итак, рассмотрим это и то, как это работает ... Вы вызываете скрипты из crontab, как обычно, на каждом экземпляре EC2. Каждый скрипт включает это в начале (или включает по одному файлу для каждого, как я его использую), который устанавливает объект ElasticBeanstalk и извлекает список всех экземпляров. Он использует только первый сервер в списке и проверяет, совпадает ли он с самим собой, что, если он это делает, продолжает, в противном случае он умирает и закрывается. Я проверил, и возвращенный список кажется согласованным, что технически должно быть согласованным только в течение минуты или около того, поскольку каждый экземпляр выполняет запланированный cron. Если это действительно изменится, это не имеет значения, поскольку опять же, это актуально только для этого маленького окна.

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

-Дэйви

user1599237
источник
Я обнаружил, что php_uname ('n') возвращает частное DNS-имя (например, ip-172.24.55.66), которое не является идентификатором экземпляра, который вы ищете. Вместо того, чтобы использовать php_uname (), я использовал это: $instanceId = file_get_contents("http://instance-data/latest/meta-data/instance-id"); Затем просто используйте этот $ instanceId var для сравнения.
Valorum
1
Есть ли гарантия, что массив Instances представляет одинаковый порядок при каждом вызове Describe? Я бы предложил извлечь поле ['Id'] каждой записи в массив и отсортировать их в PHP, прежде чем вы проверяете, является ли первая отсортированная запись вашим текущим instanceId.
Габриэль
Основываясь на этом ответе, я сделал следующее решение: stackoverflow.com/questions/14077095/… - он очень похож, но НЕ имеет шансов на двойное выполнение.
TheStoryCoder
11

Я поговорил с агентом службы поддержки AWS, и вот как мы заставили меня работать. Решение 2015 года:

Создайте файл в каталоге .ebextensions с именем your_file_name.config. В ввод файла конфигурации:

файлы:
  "/etc/cron.d/cron_example":
    режим: «000644»
    владелец: root
    группа: корень
    содержание: |
      * * * * * корень /usr/local/bin/cron_example.sh

  "/usr/local/bin/cron_example.sh":
    режим: «000755»
    владелец: root
    группа: корень
    содержание: |
      #! / bin / bash

      /usr/local/bin/test_cron.sh || Выход
      echo "Cron работает в" `date` >> /tmp/cron_example.log
      # Теперь выполняем задачи, которые должны выполняться только на 1 экземпляре ...

  "/usr/local/bin/test_cron.sh":
    режим: «000755»
    владелец: root
    группа: корень
    содержание: |
      #! / bin / bash

      МЕТАДАННЫЕ = / opt / aws / bin / ec2-metadata
      INSTANCE_ID = `$ METADATA -i | awk '{print $ 2}' `
      REGION = `$ METADATA -z | awk '{print substr ($ 2, 0, длина ($ 2) -1)}' '

      # Найдите название нашей группы автоматического масштабирования.
      ASG = `aws ec2 describe-tags --filters" Name = resource-id, Values ​​= $ INSTANCE_ID "\
        --region $ REGION --выходной текст | awk '/ aws: autoscaling: groupName / {print $ 5}' '

      # Находим первый экземпляр в группе
      FIRST = `aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $ ASG \
        --region $ REGION --выходной текст | awk '/ InService $ / {print $ 4}' | сортировать | голова -1`

      # Проверьте, одинаковы ли они.
      ["$ FIRST" = "$ INSTANCE_ID"]

команды:
  rm_old_cron:
    команда: "rm * .bak"
    cwd: "/etc/cron.d"
    ignoreErrors: true

У этого решения есть 2 недостатка:

  1. При последующих развертываниях Beanstalk переименовывает существующий сценарий cron в .bak, но cron все равно будет его запускать. Ваш Cron теперь выполняется дважды на одной машине.
  2. Если ваша среда масштабируется, вы получите несколько экземпляров, все из которых будут запускать ваш cron-скрипт. Это означает, что ваши почтовые снимки повторяются или архивы вашей базы данных дублируются.

Обходной путь:

  1. Убедитесь, что любой сценарий .ebextensions, создающий cron, также удаляет файлы .bak при последующих развертываниях.
  2. Имейте вспомогательный сценарий, который выполняет следующие действия: - Получает текущий идентификатор экземпляра из метаданных - Получает текущее имя группы автоматического масштабирования из тегов EC2 - Получает список экземпляров EC2 в этой группе, отсортированный в алфавитном порядке. - Берет первый экземпляр из этого списка. - Сравнивает идентификатор экземпляра из шага 1 с первым идентификатором экземпляра из шага 4. Затем ваши скрипты cron могут использовать этот вспомогательный скрипт, чтобы определить, должны ли они выполняться.

Предостережение:

  • Роль IAM, используемая для экземпляров Beanstalk, требует разрешения ec2: DescribeTags и автомасштабирования: DescribeAutoScalingGroups.
  • Выбранные из них экземпляры показаны как InService при автоматическом масштабировании. Это не обязательно означает, что они полностью загружены и готовы к запуску вашего cron.

Вам не нужно будет устанавливать роли IAM, если вы используете роль beanstalk по умолчанию.

Кен
источник
7

Если вы используете Rails, вы можете использовать гем when-elasticbeanstalk . Он позволяет запускать задания cron на всех экземплярах или только на одном. Он ежеминутно проверяет наличие только одного экземпляра «лидера» и автоматически повышает один сервер до «лидера», если его нет. Это необходимо, поскольку Elastic Beanstalk имеет понятие лидера только во время развертывания и может закрыть любой экземпляр в любое время при масштабировании.

ОБНОВЛЕНИЕ Я перешел на использование AWS OpsWorks и больше не обслуживаю этот драгоценный камень. Если вам нужно больше функциональных возможностей, чем доступно в основах Elastic Beanstalk, я настоятельно рекомендую перейти на OpsWorks.

dignoe
источник
Не могли бы вы рассказать нам, как вы решили эту проблему с помощью OpsWorks? Вы запускаете пользовательские слои, которые выполняют задания cron?
Tommie
Да, у меня есть слой администратора / cron, который работает только на одном сервере. Я создал специальную кулинарную книгу, в которой хранятся все мои задания cron. У AWS есть руководство по адресу docs.aws.amazon.com/opsworks/latest/userguide/… .
dignoe
@dignoe, если вы назначаете один сервер для запуска заданий cron с помощью OpsWorks, то же самое с Elastic Beanstalk, я могу использовать среду с одним сервером для запуска заданий cron. Даже с балансировщиком нагрузки максимальное и минимальное количество экземпляров установлено на единицу, чтобы всегда сохранять как минимум один экземпляр сервера.
Хосе Нобиле
6

Вы действительно не хотите запускать задания cron на Elastic Beanstalk. Поскольку у вас будет несколько экземпляров приложения, это может вызвать состояния гонки и другие странные проблемы. Я на самом деле недавно писал об этом в блоге (4-я или 5-я подсказка на странице). Короткая версия: В зависимости от приложения, использовать очереди заданий как SQS или решение третьей стороны , как iron.io .

Джемиб
источник
SQS не гарантирует, что код будет запущен только один раз. Мне нравится сайт iron.io, собираюсь заценить.
Nathan H
Также в своем сообщении в блоге вы рекомендуете использовать InnoDB на RDS. Я использую таблицу в RDS для хранения своих задач и использую функцию InnoDB «SELECT ... FOR UPDATE», чтобы убедиться, что только один сервер выполняет эти задачи. Как ваше приложение связывается с SQS без задания cron или взаимодействия с пользователем?
Джеймс Алдай,
1
@JamesAlday Этот ТАК вопрос довольно старый. Поскольку я написал вышеупомянутый комментарий, AWS представила элегантный способ обработки заданий cron на Elastic Beanstalk, выбрав один из работающих серверов в качестве главного. Сказав это, похоже, что вы неправильно используете cron + MySQL в качестве очереди заданий. Однако мне нужно много узнать о вашем приложении, прежде чем я смогу дать конкретные рекомендации.
jamieb
У меня есть сценарий, который запускается через cron, который проверяет таблицу на предмет запускаемых заданий. Использование транзакций не позволяет нескольким серверам выполнять одно и то же задание. Я изучил SQS, но вам нужен главный сервер, который запускает все сценарии, а не распространяет его, и вам все равно нужно написать логику, чтобы гарантировать, что вы не запускаете один и тот же сценарий несколько раз. Но я все еще не понимаю, как вы запускаете задачи без взаимодействия с пользователем или cron - что заставляет ваше приложение запускать задачи в очереди?
Джеймс Алдей,
5

2017: Если вы используете Laravel5 +

На его настройку потребуется всего 2 минуты:

  • создать рабочий уровень
  • установить laravel-aws-worker

    composer require dusterio/laravel-aws-worker

  • добавьте cron.yaml в корневую папку:

Добавьте cron.yaml в корневую папку вашего приложения (он может быть частью вашего репо или вы можете добавить этот файл прямо перед развертыванием в EB - важно, чтобы этот файл присутствовал во время развертывания):

version: 1
cron:
 - name: "schedule"
   url: "/worker/schedule"
   schedule: "* * * * *"

Это оно!

Вся ваша задача App\Console\Kernelтеперь будет выполнена

Подробные инструкции и объяснения: https://github.com/dusterio/laravel-aws-worker

Как писать задачи внутри Laravel: https://laravel.com/docs/5.4/scheduling

Себастьян Хорин
источник
3

Более читаемое решение, использующее filesвместо container_commands:

файлы:
  "/etc/cron.d/my_cron":
    режим: «000644»
    владелец: root
    группа: корень
    содержание: |
      # переопределить адрес электронной почты по умолчанию
      MAILTO = "example@gmail.com"
      # запускать команду Symfony каждые пять минут (как пользователь ec2)
      * / 10 * * * * ec2-user / usr / bin / php / var / app / current / app / console do: something
    кодировка: обычная
команды:
  # удалить файл резервной копии, созданный Elastic Beanstalk
  clear_cron_backup:
    команда: rm -f /etc/cron.d/watson.bak

Обратите внимание, что формат отличается от обычного формата crontab тем, что в нем указывается, от имени какого пользователя следует запускать команду.

Тамлин
источник
Одна из проблем заключается в том, что в экземплярах Elastic Beanstalk EC2 по умолчанию не настроены службы SMTP, поэтому параметр MAILTO может не работать.
Джастин Финкельштейн
3

Мой 1 цент взноса за 2018 год

Вот это правильный способ сделать это (используя django/pythonи django_crontabприложение):

внутри .ebextensionsпапки создайте такой файл 98_cron.config:

files:
  "/tmp/98_create_cron.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/bin/sh
      cd /
      sudo /opt/python/run/venv/bin/python /opt/python/current/app/manage.py crontab remove > /home/ec2-user/remove11.txt
      sudo /opt/python/run/venv/bin/python /opt/python/current/app/manage.py crontab add > /home/ec2-user/add11.txt 

container_commands:
    98crontab:
        command: "mv /tmp/98_create_cron.sh /opt/elasticbeanstalk/hooks/appdeploy/post && chmod 774 /opt/elasticbeanstalk/hooks/appdeploy/post/98_create_cron.sh"
        leader_only: true

Это должно быть container_commandsвместоcommands

Роналду Баия
источник
2

Последний пример от Amazon - самый простой и эффективный (периодические задачи):

https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html

где вы создаете отдельный рабочий уровень для выполнения любых ваших заданий cron. Создайте файл cron.yaml и поместите его в корневую папку. Одна из моих проблем (после того, как казалось, что cron не запускается) заключалась в том, что у моего CodePipeline не было полномочий для выполнения модификации Dynamodb. Исходя из этого, после добавления доступа к FullDynamoDB в разделе IAM -> роли -> yourpipeline и повторного развертывания (эластичный beanstalk) он работал отлично.

Джош
источник
1

Вот полное объяснение решения:

http://blog.paulopoiati.com/2013/08/25/running-cron-in-elastic-beanstalk-auto-scaling-environment/

пойати
источник
не используйте leader_only для создания уникального экземпляра в ASG. ASG никогда не гарантирует, что вы сможете сохранить этот конкретный экземпляр, но гарантирует только то количество экземпляров в обслуживании. Экземпляр-лидер может завершиться из-за неудачной проверки работоспособности EB.
mst
1

Итак, мы долго боролись с этим, и после некоторого обсуждения с представителем AWS я, наконец, пришел к тому, что я считаю лучшим решением.

Использование рабочего уровня с cron.yaml - определенно самое простое решение. Однако документация не разъясняет, что это поместит задание в конец очереди SQS, которую вы используете для фактического выполнения своих заданий. Если ваши задания cron чувствительны ко времени (как и многие из них), это неприемлемо, так как это будет зависеть от размера очереди. Один из вариантов - использовать полностью отдельную среду только для запуска заданий cron, но я думаю, что это излишне.

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

Защита экземпляра также может иметь проблемы - что, если этот экземпляр заблокируется / заморожен?

Важно понимать, как сама AWS управляет функциональностью cron.yaml. Существует демон SQS, который использует таблицу Dynamo для обработки «выбора лидера». Он часто записывает в эту таблицу, и если текущий лидер не записал в течение короткого времени, следующий экземпляр станет лидером. Таким образом демон решает, какой экземпляр запустить задание в очередь SQS.

Мы можем перепрофилировать существующую функциональность, а не пытаться переписать нашу собственную. Вы можете увидеть полное решение здесь: https://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27

Это в Ruby, но вы можете легко адаптировать его к любому другому языку, на котором есть AWS SDK. По сути, он проверяет текущего лидера, а затем проверяет состояние, чтобы убедиться, что оно в хорошем состоянии. Он будет зацикливаться до тех пор, пока текущий лидер не будет в хорошем состоянии, и если текущий экземпляр является лидером, выполнить задание.

Cidolfas
источник
0

Чтобы контролировать, может ли автоматическое масштабирование завершать работу определенного экземпляра при масштабировании, используйте защиту экземпляра. Вы можете включить параметр защиты экземпляра для группы Auto Scaling или для отдельного экземпляра Auto Scaling. Когда Auto Scaling запускает экземпляр, он наследует настройку защиты экземпляра из группы Auto Scaling. Вы можете изменить настройку защиты экземпляра для группы Auto Scaling или Auto Scaling в любое время.

http://docs.aws.amazon.com/autoscaling/latest/userguide/as-instance-termination.html#instance-protection

Деле
источник
0

У меня было другое решение для этого, если файл php необходимо запустить через cron, и если вы установили какие-либо экземпляры NAT, вы можете поместить cronjob в экземпляр NAT и запустить файл php через wget.

Prasoon
источник
0

вот исправление, если вы хотите сделать это в PHP. Вам просто нужен cronjob.config в папке .ebextensions, чтобы он работал так.

files:
  "/etc/cron.d/my_cron":
    mode: "000644"
    owner: root
    group: root
    content: |
        empty stuff
    encoding: plain
commands:
  01_clear_cron_backup:
    command: "rm -f /etc/cron.d/*.bak"
  02_remove_content:
    command: "sudo sed -i 's/empty stuff//g' /etc/cron.d/my_cron"
container_commands:
  adding_cron:
    command: "echo '* * * * * ec2-user . /opt/elasticbeanstalk/support/envvars && /usr/bin/php /var/app/current/index.php cron sendemail > /tmp/sendemail.log 2>&1' > /etc/cron.d/my_cron"
    leader_only: true

envvars получает переменные среды для файлов. Вы можете отлаживать вывод в tmp / sendemail.log, как указано выше.

Надеюсь, это кому-то поможет, ведь это, безусловно, помогло нам!

foxybagga
источник
0

На основе принципов ответа от пользователя 1599237 , где вы позволяете заданиям cron запускаться во всех экземплярах, но вместо этого в начале заданий определяете, следует ли им разрешить запуск, я сделал другое решение.

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

Минусов нет, только положительные:

  • без лишних трат или расходов
  • твердый раствор породы - нет шансов на двойное исполнение
  • масштабируемость - работает автоматически при масштабировании ваших экземпляров вверх и вниз
  • аварийное переключение - автоматически работает в случае отказа экземпляра

В качестве альтернативы вы также можете использовать общую файловую систему (например, AWS EFS по протоколу NFS) вместо базы данных.

Следующее решение создано в PHP-фреймворке Yii, но вы можете легко адаптировать его для другого фреймворка и языка. Также обработчик исключенийYii::$app->system - это мой собственный модуль. Замените его тем, что вы используете.

/**
 * Obtain an exclusive lock to ensure only one instance or worker executes a job
 *
 * Examples:
 *
 * `php /var/app/current/yii process/lock 60 empty-trash php /var/app/current/yii maintenance/empty-trash`
 * `php /var/app/current/yii process/lock 60 empty-trash php /var/app/current/yii maintenance/empty-trash StdOUT./test.log`
 * `php /var/app/current/yii process/lock 60 "empty trash" php /var/app/current/yii maintenance/empty-trash StdOUT./test.log StdERR.ditto`
 * `php /var/app/current/yii process/lock 60 "empty trash" php /var/app/current/yii maintenance/empty-trash StdOUT./output.log StdERR./error.log`
 *
 * Arguments are understood as follows:
 * - First: Duration of the lock in minutes
 * - Second: Job name (surround with quotes if it contains spaces)
 * - The rest: Command to execute. Instead of writing `>` and `2>` for redirecting output you need to write `StdOUT` and `StdERR` respectively. To redirect stderr to stdout write `StdERR.ditto`.
 *
 * Command will be executed in the background. If determined that it should not be executed the script will terminate silently.
 */
public function actionLock() {
    $argsAll = $args = func_get_args();
    if (!is_numeric($args[0])) {
        \Yii::$app->system->error('Duration for obtaining process lock is not numeric.', ['Args' => $argsAll]);
    }
    if (!$args[1]) {
        \Yii::$app->system->error('Job name for obtaining process lock is missing.', ['Args' => $argsAll]);
    }

    $durationMins = $args[0];
    $jobName = $args[1];
    $instanceID = null;
    unset($args[0], $args[1]);

    $command = trim(implode(' ', $args));
    if (!$command) {
        \Yii::$app->system->error('Command to execute after obtaining process lock is missing.', ['Args' => $argsAll]);
    }

    // If using AWS Elastic Beanstalk retrieve the instance ID
    if (file_exists('/etc/elasticbeanstalk/.aws-eb-system-initialized')) {
        if ($awsEb = file_get_contents('/etc/elasticbeanstalk/.aws-eb-system-initialized')) {
            $awsEb = json_decode($awsEb);
            if (is_object($awsEb) && $awsEb->instance_id) {
                $instanceID = $awsEb->instance_id;
            }
        }
    }

    // Obtain lock
    $updateColumns = false;  //do nothing if record already exists
    $affectedRows = \Yii::$app->db->createCommand()->upsert('system_job_locks', [
        'job_name' => $jobName,
        'locked' => gmdate('Y-m-d H:i:s'),
        'duration' => $durationMins,
        'source' => $instanceID,
    ], $updateColumns)->execute();
    // The SQL generated: INSERT INTO system_job_locks (job_name, locked, duration, source) VALUES ('some-name', '2019-04-22 17:24:39', 60, 'i-HmkDAZ9S5G5G') ON DUPLICATE KEY UPDATE job_name = job_name

    if ($affectedRows == 0) {
        // record already exists, check if lock has expired
        $affectedRows = \Yii::$app->db->createCommand()->update('system_job_locks', [
                'locked' => gmdate('Y-m-d H:i:s'),
                'duration' => $durationMins,
                'source' => $instanceID,
            ],
            'job_name = :jobName AND DATE_ADD(locked, INTERVAL duration MINUTE) < NOW()', ['jobName' => $jobName]
        )->execute();
        // The SQL generated: UPDATE system_job_locks SET locked = '2019-04-22 17:24:39', duration = 60, source = 'i-HmkDAZ9S5G5G' WHERE job_name = 'clean-trash' AND DATE_ADD(locked, INTERVAL duration MINUTE) < NOW()

        if ($affectedRows == 0) {
            // We could not obtain a lock (since another process already has it) so do not execute the command
            exit;
        }
    }

    // Handle redirection of stdout and stderr
    $command = str_replace('StdOUT', '>', $command);
    $command = str_replace('StdERR.ditto', '2>&1', $command);
    $command = str_replace('StdERR', '2>', $command);

    // Execute the command as a background process so we can exit the current process
    $command .= ' &';

    $output = []; $exitcode = null;
    exec($command, $output, $exitcode);
    exit($exitcode);
}

Вот схема базы данных, которую я использую:

CREATE TABLE `system_job_locks` (
    `job_name` VARCHAR(50) NOT NULL,
    `locked` DATETIME NOT NULL COMMENT 'UTC',
    `duration` SMALLINT(5) UNSIGNED NOT NULL COMMENT 'Minutes',
    `source` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`job_name`)
)
TheStoryCoder
источник