97 votes

AWS Elastic Beanstalk, exécuter une tâche planifiée

Je voudrais savoir s'il existe un moyen de configurer une tâche cron à exécuter toutes les minutes. Actuellement, n'importe laquelle de mes instances devrait être capable d'exécuter cette tâche.

Voici ce que j'ai essayé de faire dans les fichiers de configuration sans succès :

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

Je ne suis pas vraiment sûr que ce soit la bonne manière de le faire

Des idées ?

1 votes

Le commandement est-il correct? Je veux dire... cela pourrait être : commande: echo "*/1 * * * * root php /etc/httpd/myscript.php" > /etc/cron.d/something Peu importe, je vous suggère d'utiliser le drapeau leader_only, sinon toutes les machines lanceront ce job cron en même temps

0 votes

Oui! en utilisant certainement le drapeau leader_only, je vais essayer de changer la commande.

109voto

Anarchtica Points 346

Voici comment j'ai ajouté une tâche cron à Elastic Beanstalk :

Créez un dossier à la racine de votre application appelé .ebextensions s'il n'existe pas déjà. Ensuite, créez un fichier de configuration à l'intérieur du dossier .ebextensions. J'utiliserai example.config à des fins d'illustration. Ensuite, ajoutez ceci à 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

Ceci est un fichier de configuration YAML pour Elastic Beanstalk. Assurez-vous que lorsque vous copiez ceci dans votre éditeur de texte, votre éditeur de texte utilise des espaces au lieu de tabulations. Sinon, vous obtiendrez une erreur YAML lorsque vous pousserez ceci vers EB.

Ce que cela fait, c'est créer une commande appelée 01_some_cron_job. Les commandes sont exécutées par ordre alphabétique donc le 01 s'assure qu'elle est exécutée en premier.

La commande prend ensuite le contenu d'un fichier appelé some_cron_job.txt et l'ajoute à un fichier appelé some_cron_job dans /etc/cron.d.

La commande change ensuite les permissions sur le fichier /etc/cron.d/some_cron_job.

La clé leader_only assure que la commande est exécutée uniquement sur l'instance ec2 considérée comme le leader. Plutôt que de s'exécuter sur chaque instance ec2 que vous pourriez avoir en cours d'exécution.

Ensuite, créez un fichier appelé some_cron_job.txt à l'intérieur du dossier .ebextensions. Vous placerez vos tâches cron dans ce fichier.

Par exemple :

# Le saut de ligne à la fin de ce fichier est extrêmement important. Cron ne s'exécutera pas sans cela.
* * * * * root /usr/bin/php some-php-script-here > /dev/null

Ainsi, cette tâche cron s'exécutera toutes les minutes de toutes les heures de tous les jours en tant qu'utilisateur root et rejettera la sortie vers /dev/null. /usr/bin/php est le chemin vers php. Remplacez ensuite some-php-script-here par le chemin vers votre fichier php. Cela suppose évidemment que votre tâche cron doit exécuter un fichier PHP.

Assurez-vous également que le fichier some_cron_job.txt a un saut de ligne à la fin du fichier comme le dit le commentaire. Sinon, cron ne s'exécutera pas.

Mise à jour : Il y a un problème avec cette solution lorsque Elastic Beanstalk met à l'échelle vos instances. Par exemple, disons que vous avez une instance avec la tâche cron en cours d'exécution. Vous recevez une augmentation du trafic donc Elastic Beanstalk vous met à l'échelle jusqu'à deux instances. La clé leader_only garantira que vous n'avez qu'une seule tâche cron en cours d'exécution entre les deux instances. Votre trafic diminue et Elastic Beanstalk vous met à l'échelle jusqu'à une instance. Mais au lieu de terminer la deuxième instance, Elastic Beanstalk termine la première instance qui était leader. Vous n'avez désormais aucune tâche cron en cours d'exécution puisqu'elles étaient uniquement en cours d'exécution sur la première instance qui a été terminée. Voir les commentaires ci-dessous.

Mise à jour 2 : Pour clarifier les commentaires ci-dessous : AWS a maintenant une protection contre la terminaison automatique des instances. Activez simplement cette protection sur votre instance leader et vous êtes prêt à partir. – Nicolás Arévalo 28 oct. 2016 à 9:23

14 votes

J'ai utilisé votre suggestion depuis un certain temps et récemment j'ai rencontré un problème où d'une manière ou d'une autre le leader a été changé, ce qui a entraîné plusieurs instances exécutant le cron. Pour résoudre ce problème, j'ai changé 01_some_cron_job en 02_some_cron_job et ajouté 01_remove_cron_jobs avec ceci: command: "rm /etc/cron.d/cron_jobs || exit 0". De cette manière, après chaque déploiement, seul le leader aura le fichier cron_jobs. Si les leaders changent, il suffit de redéployer et les crons seront corrigés pour s'exécuter une seule fois de plus.

5 votes

Je suggérerais de ne pas compter sur la propriété leader_only. Elle n'est utilisée que pendant le déploiement et si vous réduisez l'échelle ou si votre instance "leader" échoue, vous risquez de rencontrer des problèmes référence

0 votes

Je suis d'accord, je n'ai pas encore trouvé de bonne solution automatisée à ce problème. Actuellement, mes tâches cron m'envoient des e-mails régulièrement pour être sûr qu'elles fonctionnent toujours. À peine idéal. La meilleure solution est de réaliser que les tâches cron sont un moyen pour atteindre un but, pas un objectif en soi. Repenser la logique de votre application pour utiliser quelque chose conçu pour des systèmes évolutifs (comme SWF) est le meilleur plan à long terme.

32voto

beterthanlife Points 635

Concernant la réponse de jamieb et comme le mentionne alrdinleal, vous pouvez utiliser la propriété 'leader_only' pour vous assurer qu'une seule instance EC2 exécute la tâche cron.

Citation tirée de http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html:

vous pouvez utiliser leader_only. Une instance est choisie pour être le leader dans un groupe de mise à l'échelle automatique. Si la valeur leader_only est définie sur true, la commande s'exécute uniquement sur l'instance marquée comme le leader.

J'essaie de réaliser une chose similaire sur mon eb, donc je mettrai à jour mon message si je résous le problème.

MISE À JOUR:

Ok, j'ai maintenant des cronjobs fonctionnels en utilisant la configuration eb suivante:

files:
  "/tmp/cronjob" :
    mode: "000777"
    owner: ec2-user
    group: ec2-user
    content: |
      # supprimer les paniers expirés
      */10 * * * * /usr/bin/wget -o /dev/null http://blah.elasticbeanstalk.com/basket/purge > $HOME/basket_purge.log 2>&1
      # nettoyer les fichiers créés par le cronjob ci-dessus
      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

Essentiellement, je crée un fichier temporaire avec les cronjobs puis je définis le crontab pour qu'il lise à partir du fichier temporaire, puis je supprime le fichier temporaire ensuite. J'espère que cela aide.

3 votes

Comment veilleriez-vous à ce que l'instance exécutant ce crontab ne soit pas terminée par le redimensionnement automatique ? Par défaut, il termine l'instance la plus ancienne.

1 votes

C'est un problème que je n'ai pas encore été en mesure de résoudre. Il me semble que c'est une lacune dans la fonctionnalité d'Amazon que les commandes leader_only ne sont pas appliquées à un nouveau leader lorsque celui en cours est terminé par EB. Si vous trouvez une solution, merci de la partager!

7 votes

Alors j'ai (enfin) découvert comment empêcher le leader d'être arrêté par mise à l'échelle automatique - des politiques de terminaison personnalisées pour la mise à l'échelle automatique. Voir docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/…

12voto

user1599237 Points 31

Comme mentionné ci-dessus, le défaut fondamental de l'établissement de toute configuration de crontab est qu'il ne se produit qu'au déploiement. Lorsque le cluster est automatiquement mis à l'échelle et réduit, il est préférable d'être également le premier serveur arrêté. De plus, il n'y aurait pas de basculement, ce qui était essentiel pour moi.

J'ai fait des recherches, puis j'ai discuté avec notre spécialiste de compte AWS pour rebondir des idées et valider la solution que j'ai trouvée. Vous pouvez réaliser cela avec OpsWorks, bien que ce soit un peu comme utiliser une maison pour tuer une mouche. Il est également possible d'utiliser Data Pipeline avec Task Runner, mais cette méthode a une capacité limitée dans les scripts qu'elle peut exécuter, et j'avais besoin de pouvoir exécuter des scripts PHP, avec accès à l'ensemble de la base de code. Vous pourriez également dédier une instance EC2 en dehors du cluster ElasticBeanstalk, mais alors vous n'avez plus de basculement.

Voici donc ce que j'ai créé, qui apparemment est non conventionnel (comme l'a commenté le représentant d'AWS) et peut être considéré comme un hack, mais cela fonctionne et est solide avec un basculement. J'ai choisi une solution de codage en utilisant le SDK, que je vais montrer en PHP, bien que vous pourriez utiliser la même méthode dans n'importe quelle langue que vous préférez.

// contient les valeurs des variables utilisées (clé, secret, env)
require_once('cron_config.inc'); 

// Chargement du SDK AWS PHP pour se connecter à ElasticBeanstalk
use Aws\ElasticBeanstalk\ElasticBeanstalkClient;

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

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

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

En parcourant cela et comment cela fonctionne... Vous appelez des scripts depuis crontab comme vous le feriez normalement sur chaque instance EC2. Chaque script inclut ceci au début (ou inclut un fichier unique pour chacun, comme je le fais), qui établit un objet ElasticBeanstalk et récupère une liste de toutes les instances. Il utilise uniquement le premier serveur de la liste, et vérifie s'il correspond à lui-même, auquel cas il continue, sinon il se termine et se ferme. J'ai vérifié et la liste retournée semble être cohérente, ce qui techniquement n'a besoin d'être cohérent que pendant une minute environ, car chaque instance exécute le crontab programmé. Si cela change, cela n'aurait pas d'importance, car encore une fois cela n'est pertinent que pour cette courte période.

Ce n'est pas élégant en aucun cas, mais cela répond à nos besoins spécifiques - qui n'étaient pas d'augmenter les coûts avec un service supplémentaire ou d'avoir une instance EC2 dédiée, et d'avoir un basculement en cas de panne. Nos scripts cron exécutent des scripts de maintenance qui sont placés dans SQS et chaque serveur du cluster aide à les exécuter. Au moins cela peut vous offrir une option alternative si cela correspond à vos besoins.

-Davey

0 votes

J'ai constaté que php_uname('n') renvoie le nom DNS privé (par exemple ip-172.24.55.66), ce qui n'est pas l'identifiant de l'instance que vous recherchez. Au lieu d'utiliser php_uname(), j'ai fini par utiliser ceci : $instanceId = file_get_contents("http://instance-data/latest/meta-data/ins‌tance-id"); Ensuite, utilisez simplement cette variable $instanceId pour faire la comparaison.

1 votes

Y a-t-il une garantie que le tableau Instances présente le même ordre à chaque appel de Describe? Je suggérerais d'extraire le champ ['Id'] de chaque entrée dans un tableau et de les trier en PHP, avant de vérifier si la première entrée triée est votre instanceId actuelle.

0 votes

En vous basant sur cette réponse, j'ai conçu cette solution : stackoverflow.com/questions/14077095/… - elle est très similaire mais n'a AUCUNE chance d'exécution double.

7voto

dignoe Points 163

Si vous utilisez Rails, vous pouvez utiliser le gem whenever-elasticbeanstalk. Cela vous permet d'exécuter des tâches cron sur toutes les instances ou seulement une. Il vérifie chaque minute pour s'assurer qu'il n'y a qu'une seule instance "leader" et promouvra automatiquement un serveur en "leader" s'il n'y en a pas. C'est nécessaire car Elastic Beanstalk n'a le concept de leader que pendant le déploiement et peut arrêter n'importe quelle instance à tout moment lors du redimensionnement.

MISE À JOUR J'ai changé pour utiliser AWS OpsWorks et je ne maintiens plus ce gem. Si vous avez besoin de plus de fonctionnalités que celles disponibles dans les bases de Elastic Beanstalk, je vous recommande vivement de passer à OpsWorks.

0 votes

Est-ce que cela vous dérangerait de nous dire comment vous l'avez résolu en utilisant OpsWorks? Utilisez-vous des couches personnalisées qui gèrent les tâches cron?

0 votes

Oui, j'ai une couche d'administration/cron qui ne s'exécute que sur un serveur. J'ai mis en place un livre de recettes personnalisé qui contient tous mes travaux cron. AWS a un guide sur docs.aws.amazon.com/opsworks/latest/userguide/….

0 votes

@dignoe si vous attribuez un seul serveur pour exécuter des tâches planifiées en utilisant OpsWorks, la même chose en utilisant Elastic Beanstalk, je peux utiliser un environnement avec un seul serveur pour exécuter des tâches planifiées. Même avec un répartiteur de charge, les instances maximales et minimales définies à un, pour conserver toujours au moins une instance de serveur.

6voto

jamieb Points 2892

Vous ne voulez vraiment pas exécuter des tâches cron sur Elastic Beanstalk. Comme vous aurez plusieurs instances d'application, cela peut causer des conditions de concurrence et d'autres problèmes étranges. J'ai en fait récemment blogué à ce sujet (4ème ou 5ème conseil en bas de la page). La version courte : en fonction de l'application, utilisez une file d'attente de tâches comme SQS ou une solution tierce comme iron.io.

0 votes

SQS ne garantit pas que le code ne sera exécuté qu'une seule fois. J'aime le site iron.io, je vais le vérifier.

0 votes

Aussi dans votre article de blog, vous recommandez d'utiliser InnoDB sur RDS. J'utilise une table sur RDS pour stocker mes tâches et j'utilise la fonction InnoDB "SELECT...FOR UPDATE" pour m'assurer qu'un seul serveur exécute ces tâches. Comment votre application contacte-t-elle SQS sans un cron job ou une interaction utilisateur?

2 votes

@JamesAlday Cette question SO est assez ancienne. Depuis que j'ai écrit le commentaire ci-dessus, AWS a introduit une manière élégante de gérer les tâches cron sur Elastic Beanstalk en élisant l'un des serveurs en cours d'exécution comme maître. Cela dit, il semble que vous utilisiez incorrectement cron + MySQL comme file d'attente de tâches. J'aurais besoin de connaître beaucoup de choses sur votre application avant de pouvoir offrir des recommandations concrètes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X