AWS Elastic Beanstalk, exécution d'un cronjob

89

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

C'est 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 sais pas vraiment si c'est la bonne façon de le faire

Des idées?

Onema
la source
1
La commande est-elle correcte? Je veux dire ... ça pourrait être: commande: echo "* / 1 * * * * root php /etc/httpd/myscript.php"> /etc/cron.d/something Quoi qu'il en soit, je vous suggère d'utiliser le leader_only, sinon toutes les machines lanceront cette tâche cron en même temps
aldrinleal
Oui! en utilisant définitivement le drapeau leader_only, je vais essayer de changer la commande.
Onema

Réponses:

96

Voici comment j'ai ajouté un travail cron à Elastic Beanstalk:

Créez un dossier à la racine de votre application appelé .ebextensions s'il n'existe pas déjà. Créez ensuite un fichier de configuration dans le dossier .ebextensions. J'utiliserai example.config à des fins d'illustration. Ajoutez ensuite 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

Il s'agit d'un fichier de configuration YAML pour Elastic Beanstalk. Lorsque vous copiez ceci dans votre éditeur de texte, assurez-vous que votre éditeur de texte utilise des espaces au lieu de tabulations. Sinon, vous obtiendrez une erreur YAML lorsque vous le transmettez à EB.

Donc, cela crée une commande appelée 01_some_cron_job. Les commandes sont exécutées par ordre alphabétique afin que le 01 s'assure qu'il est exécuté en tant que première commande.

La commande prend alors 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 modifie ensuite les autorisations sur le fichier /etc/cron.d/some_cron_job.

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

Créez ensuite un fichier appelé some_cron_job.txt dans le dossier .ebextensions. Vous placerez vos tâches cron dans ce fichier.

Donc par exemple:

# 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

Ainsi, cette tâche cron s'exécutera toutes les minutes de chaque heure de chaque jour 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 de 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 une nouvelle ligne à la fin du fichier, comme le dit le commentaire. Sinon, cron ne fonctionnera pas.

Mise à jour: il y a un problème avec cette solution lorsque Elastic Beanstalk fait évoluer vos instances. Par exemple, disons que vous avez une instance avec le travail cron en cours d'exécution. Vous obtenez une augmentation du trafic donc Elastic Beanstalk vous met à l'échelle jusqu'à deux instances. Le leader_only garantira que vous n'avez qu'un seul travail cron en cours d'exécution entre les deux instances. Votre trafic diminue et Elastic Beanstalk vous réduit à une instance. Mais au lieu de terminer la deuxième instance, Elastic Beanstalk met fin à la première instance qui était le leader. Vous n'avez plus de tâches cron en cours d'exécution car elles ne s'exécutaient que sur la première instance qui a été arrêtée. Voir les commentaires ci-dessous.

Mise à jour 2: Précisez -le simplement à partir des commentaires ci-dessous: AWS dispose désormais d'une protection contre la résiliation automatique des instances. Activez-le simplement sur votre instance leader et vous êtes prêt à partir. - Nicolás Arévalo 28 octobre 16 à 9:23

Anarchtica
la source
12
J'utilise votre suggestion depuis un certain temps et j'ai récemment rencontré un problème où le leader a changé, ce qui a entraîné plusieurs instances exécutant le cron. Pour résoudre cette question, je l' ai changé 01_some_cron_jobà 02_some_cron_jobet a ajouté ce 01_remove_cron_jobsqui suit: command: "rm /etc/cron.d/cron_jobs || exit 0". De cette façon, après chaque déploiement, seul le chef de file aura le cron_jobsfichier. Si les dirigeants changent, vous pouvez simplement redéployer et les crons seront corrigés pour fonctionner à nouveau.
Willem Renzema
4
Je suggérerais de ne pas compter sur la leader_onlypropriété. Il n'est utilisé que pendant le déploiement et si vous
réduisez
2
Ne fais pas ça. C'est trop peu fiable. La seule façon de faire fonctionner cela est d'exécuter une micro-instance et d'exécuter des tâches cron à partir de là en utilisant CURL. Cela garantit qu'une seule instance l'exécute et que le leader qui a installé crons n'est pas arrêté.
Ben Sinclair
1
J'ai essayé de résoudre ce problème avec un petit script ruby, vous pouvez le trouver ici: github.com/SocialbitGmbH/AWSBeanstalkLeaderManager
Thomas Kekeisen
8
AWS dispose désormais d'une protection contre la résiliation automatique des instances. Activez-le simplement sur votre instance leader et vous êtes prêt à partir.
Nicolás Arévalo
58

C'est la façon officielle de le faire maintenant (2015+). Veuillez essayer ceci d'abord, c'est de loin la méthode la plus simple actuellement disponible et la plus fiable également.

Selon la documentation actuelle, on est capable d'exécuter des tâches périodiques sur leur soi-disant niveau de travail .

Citant la documentation:

AWS Elastic Beanstalk prend en charge les tâches périodiques pour les niveaux d'environnement de travail dans les environnements exécutant une configuration prédéfinie avec une pile de solutions qui contient «v1.2.0» dans le nom du conteneur. Vous devez créer un nouvel environnement.

La partie sur cron.yaml est également intéressante :

Pour appeler des tâches périodiques, le bundle source de votre application doit inclure un fichier cron.yaml au niveau racine. Le fichier doit contenir des informations sur les tâches périodiques que vous souhaitez planifier. Spécifiez ces informations à l'aide de la syntaxe crontab standard.

Mise à jour: nous avons pu obtenir ce travail. Voici quelques pièges importants tirés de notre expérience (plateforme Node.js):

  • Lorsque vous utilisez le fichier cron.yaml , assurez-vous de disposer de la dernière version de awsebcli , car les anciennes versions ne fonctionneront pas correctement.
  • Il est également vital de créer un nouvel environnement (du moins dans notre cas, c'était le cas), pas seulement de cloner l'ancien.
  • Si vous voulez vous assurer que CRON est pris en charge sur votre instance EC2 Worker Tier, ssh dedans ( eb ssh) et exécutez cat /var/log/aws-sqsd/default.log. Il devrait signaler comme aws-sqsd 2.0 (2015-02-18). Si vous n'avez pas la version 2.0, un problème est survenu lors de la création de votre environnement et vous devez en créer un comme indiqué ci-dessus.
Xaralis
la source
2
À propos de cron.yaml, il y a un article de blog génial: Exécution de tâches cron sur Amazon Web Services (AWS) Elastic Beanstalk - Medium
jwako
5
Merci pour cette question - rookie - J'ai besoin que mon cron vérifie la base de données de mon application Web deux fois par heure pour les événements à venir du calendrier et envoie un e-mail de rappel le cas échéant. Quelle est la meilleure configuration ici, dois-je faire pointer l'URL cron.yaml vers une route sur mon application Web? Ou devrais-je donner à mon application d'environnement de travail l'accès à la base de données? Si peu de choses là-dessus!
christian
5
@christian La façon dont nous le faisons, nous avons la même application fonctionnant dans deux environnements différents (donc aucune configuration spéciale n'est nécessaire) - un serveur Web de travail et un serveur Web commun. L'environnement de travail a des routes spéciales activées en définissant une variable ENV que notre application recherche. De cette façon, vous pouvez définir des routes spéciales réservées aux travailleurs dans votre cron.yaml tout en bénéficiant du luxe d'une base de code partagée avec l'application normale. Votre application de travail peut facilement accéder aux mêmes ressources que le serveur Web: base de données, modèles, etc.
xaralis
1
@JaquelinePassos v1.2.0 est la version de la pile de solutions. Il devrait vous permettre de choisir la version de la pile de solutions que vous souhaitez créer lors de la création d'un nouvel environnement. Tout ce qui est plus récent que la v1.2.0 devrait faire l'affaire. En ce qui concerne l'URL, il doit s'agir de l'URL sur laquelle votre application écoute, et non d'un chemin de fichier. Il n'est pas possible d'exécuter des commandes de gestion Django, il ne fait que des requêtes HTTP.
xaralis
4
Une chose qui n'est pas claire pour moi est s'il existe un moyen d'éviter d'avoir à allouer une machine EC2 supplémentaire juste pour exécuter les tâches cron via cron.yaml. Idéalement, il fonctionnerait sur la même machine que celle qui traite les requêtes HTTP (c'est-à-dire le niveau Web).
Wenzel Jakob
31

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 comme leader dans un groupe Auto Scaling. Si la valeur leader_only est définie sur true, la commande s'exécute uniquement sur l'instance marquée comme leader.

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

METTRE À JOUR:

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

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

Essentiellement, je crée un fichier temporaire avec les cronjobs, puis je configure le crontab pour qu'il lise à partir du fichier temporaire, puis je supprime le fichier temporaire par la suite. J'espère que cela t'aides.

beterthanlife
la source
3
Comment vous assureriez-vous que l'instance exécutant cette crontab ne soit pas interrompue par la mise à l'échelle automatique? Par défaut, il met fin à l'instance la plus ancienne.
Sebastien
1
C'est un problème que je n'ai pas encore réussi à résoudre. Cela me semble être une faille dans la fonctionnalité d'Amazon que les commandes leader_only ne soient pas appliquées à un nouveau chef lorsque celui-ci est terminé par EB. Si vous trouvez quelque chose, partagez-le!
beterthanlife
7
J'ai donc (enfin) découvert comment empêcher le chef de file d'être résilié par mise à l'échelle automatique - politiques de terminaison de mise à l'échelle automatique personnalisées. Voir docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide
beterthanlife
1
@Nate Vous avez probablement compris cela maintenant, mais d'après ma lecture de l'ordre dans lequel elles s'exécutent, les "commandes" s'exécutent avant "container_commands" afin que vous créiez le fichier, puis supprimiez-le, puis essayez d'exécuter le crontab .
effacer
1
@Sebastien afin de conserver la plus ancienne intance, voici ce que je fais: 1 - changer la protection de terminaison de l'intance en ENBABLE. 2 - Accédez à Auto Scale Group et trouvez votre ID d'environnement EBS, cliquez sur MODIFIER et modifiez les politiques de résiliation en "LatestInstance"
Ronaldo Bahia
12

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

J'ai fait des recherches, puis j'ai discuté avec notre spécialiste des comptes AWS pour faire remonter des idées et valider la solution que j'ai proposée. Vous pouvez accomplir cela avec OpsWorks , même si c'est un peu comme utiliser une maison pour tuer une mouche. Il est également possible d'utiliser Data Pipeline avec Task Runner , mais cela a une capacité limitée dans les scripts qu'il peut exécuter, et j'avais besoin de pouvoir exécuter des scripts PHP, avec accès à toute la base de code. Vous pouvez également dédier une instance EC2 en dehors du cluster ElasticBeanstalk, mais vous n'avez pas de reprise en ligne.

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

// 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");
}

Alors parcourez ceci et comment cela fonctionne ... Vous appelez des scripts depuis crontab comme vous le feriez normalement sur chaque instance EC2. Chaque script l'inclut au début (ou inclut un seul fichier pour chacun, tel que je l'utilise), qui établit un objet ElasticBeanstalk et récupère une liste de toutes les instances. Il n'utilise que le premier serveur de la liste et vérifie s'il correspond à lui-même, et s'il le fait, il continue, sinon il meurt et se ferme. J'ai vérifié et la liste renvoyée semble être cohérente, ce qui, techniquement, ne doit être cohérente que pendant une minute environ, car chaque instance exécute le cron planifié. Si cela change, cela n'a pas d'importance, car encore une fois, cela n'est pertinent que pour cette petite fenêtre.

Ce n'est en aucun cas élégant, mais adapté à nos besoins spécifiques - qui n'était pas d'augmenter les coûts avec un service supplémentaire ou d'avoir une instance EC2 dédiée, et aurait 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 à s'exécuter. Au moins, cela peut vous donner une autre option si elle répond à vos besoins.

-Davey

user1599237
la source
J'ai trouvé que php_uname ('n') renvoie le nom DNS privé (par exemple ip-172.24.55.66), qui n'est pas l'ID d'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/instance-id"); Ensuite, utilisez simplement cette variable $ instanceId pour faire la comparaison.
Valorum
1
Existe-t-il une garantie que le tableau Instances présente le même ordre à chaque appel Description? Je suggère 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.
Gabriel
Sur la base de cette réponse, j'ai fait cette solution: stackoverflow.com/questions/14077095/… - c'est très similaire mais n'a AUCUNE chance de double exécution.
TheStoryCoder
11

J'ai parlé à un agent de support AWS et c'est ainsi que nous avons fait en sorte que cela fonctionne pour moi. Solution 2015:

Créez un fichier dans votre répertoire .ebextensions avec votre_nom_fichier.config. Dans l'entrée du fichier de configuration:

des dossiers:
  "/etc/cron.d/cron_example":
    mode: "000644"
    propriétaire: racine
    groupe: racine
    contenu: |
      * * * * * root /usr/local/bin/cron_example.sh

  "/usr/local/bin/cron_example.sh":
    mode: "000755"
    propriétaire: racine
    groupe: racine
    contenu: |
      #! / bin / bash

      /usr/local/bin/test_cron.sh || sortie
      echo "Cron en cours d'exécution à" `date` >> /tmp/cron_example.log
      # Maintenant, faites des tâches qui ne devraient s'exécuter que sur 1 instance ...

  "/usr/local/bin/test_cron.sh":
    mode: "000755"
    propriétaire: racine
    groupe: racine
    contenu: |
      #! / bin / bash

      METADATA = / opt / aws / bin / ec2-metadata
      INSTANCE_ID = `$ METADATA -i | awk '{print $ 2}' `
      REGION = `$ METADATA -z | awk '{print substr ($ 2, 0, longueur ($ 2) -1)}' `

      # Trouvez le nom de notre groupe Auto Scaling.
      ASG = `aws ec2 describe-tags --filters" Name = resource-id, Values ​​= $ INSTANCE_ID "\
        --region $ REGION - texte de sortie | awk '/ aws: autoscaling: groupName / {print $ 5}' `

      # Trouver la première instance du groupe
      FIRST = `aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $ ASG \
        --region $ REGION - texte de sortie | awk '/ InService $ / {print $ 4}' | trier | tête -1`

      # Testez s'ils sont identiques.
      ["$ FIRST" = "$ INSTANCE_ID"]

commandes:
  rm_old_cron:
    commande: "rm * .bak"
    cwd: "/etc/cron.d"
    ignoreErrors: vrai

Cette solution présente 2 inconvénients:

  1. Lors des déploiements suivants, Beanstalk renomme le script cron existant en .bak, mais cron l'exécutera toujours. Votre Cron s'exécute maintenant deux fois sur la même machine.
  2. Si votre environnement évolue, vous obtenez plusieurs instances, toutes exécutant votre script cron. Cela signifie que vos e-mails sont répétés ou les archives de votre base de données dupliquées

Solution de contournement:

  1. Assurez-vous que tout script .ebextensions qui crée un cron supprime également les fichiers .bak lors des déploiements suivants.
  2. Avoir un script d'assistance qui effectue les opérations suivantes: - Obtient l'ID d'instance actuel à partir des métadonnées - Obtient le nom du groupe Auto Scaling actuel à partir des balises EC2 - Obtient la liste des instances EC2 dans ce groupe, triées par ordre alphabétique. - Prend la première instance de cette liste. - Compare l'ID d'instance de l'étape 1 avec le premier ID d'instance de l'étape 4. Vos scripts cron peuvent ensuite utiliser ce script d'assistance pour déterminer s'ils doivent s'exécuter.

Caveat:

  • Le rôle IAM utilisé pour les instances Beanstalk nécessite les autorisations ec2: DescribeTags et autoscaling: DescribeAutoScalingGroups
  • Les instances choisies sont celles affichées comme InService par Auto Scaling. Cela ne signifie pas nécessairement qu'ils sont entièrement démarrés et prêts à exécuter votre cron.

Vous n'auriez pas à définir les rôles IAM si vous utilisez le rôle beanstalk par défaut.

Ken
la source
7

Si vous utilisez Rails, vous pouvez utiliser la gemme every-sensitivebeanstalk . Il vous permet d'exécuter des tâches cron sur toutes les instances ou sur une seule. Il vérifie toutes les minutes 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. Cela est nécessaire car Elastic Beanstalk n'a le concept de leader que lors du déploiement et peut arrêter n'importe quelle instance à tout moment pendant la mise à l'échelle.

MISE À JOUR J'ai opté pour AWS OpsWorks et je ne gère plus ce joyau. Si vous avez besoin de plus de fonctionnalités que celles disponibles dans les bases d'Elastic Beanstalk, je vous recommande vivement de passer à OpsWorks.

dignoe
la source
Pourriez-vous nous dire comment vous l'avez résolu en utilisant OpsWorks? Exécutez-vous des couches personnalisées qui effectuent les tâches cron?
Tommie
Ouais, j'ai une couche admin / cron qui ne fonctionne que sur un serveur. J'ai mis en place un livre de recettes personnalisé qui contient tous mes travaux cron. AWS propose un guide sur docs.aws.amazon.com/opsworks/latest/userguide/… .
dignoe
@dignoe si vous attribuez un serveur pour exécuter des tâches cron à l'aide d'OpsWorks, la même chose avec Elastic Beanstalk, je peux utiliser un environnement avec un serveur pour exécuter des tâches cron. Même avec Load Balancer, les instances max et min sont définies sur un, pour conserver toujours au moins une instance de serveur.
Jose Nobile
6

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

Jamieb
la source
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.
Nathan H
Dans votre article de blog, vous recommandez également 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 tâche cron ni interaction de l'utilisateur?
James Alday
1
@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 mal cron + MySQL comme file d'attente de travaux. J'aurais besoin d'en savoir beaucoup sur votre application avant de pouvoir proposer des recommandations concrètes.
jamieb
J'ai un script qui s'exécute via cron qui vérifie une table pour les travaux à exécuter. L'utilisation de transactions empêche plusieurs serveurs d'exécuter le même travail. J'ai examiné SQS mais vous avez besoin d'un serveur maître qui exécute tous les scripts au lieu de le distribuer et vous devez toujours écrire une logique pour vous assurer de ne pas exécuter le même script plusieurs fois. Mais je ne sais toujours pas comment exécuter les tâches sans interaction de l'utilisateur ni cron - qu'est-ce qui déclenche votre application pour exécuter les tâches en file d'attente?
James Alday
4

2017: Si vous utilisez Laravel5 +

Il vous suffit de 2 minutes pour le configurer:

  • créer un niveau de travail
  • installer laravel-aws-worker

    composer require dusterio/laravel-aws-worker

  • ajoutez un cron.yaml dans le dossier racine:

Ajoutez cron.yaml au dossier racine de votre application (cela peut faire partie de votre dépôt ou vous pouvez ajouter ce fichier juste avant le déploiement sur EB - l'important est que ce fichier soit présent au moment du déploiement):

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

C'est tout!

Toute votre tâche App\Console\Kernelsera maintenant exécutée

Instructions détaillées et explications: https://github.com/dusterio/laravel-aws-worker

Comment écrire des tâches à l'intérieur de Laravel: https://laravel.com/docs/5.4/scheduling

Sébastien Horin
la source
3

Une solution plus lisible en utilisant filesau lieu de container_commands:

des dossiers:
  "/etc/cron.d/my_cron":
    mode: "000644"
    propriétaire: racine
    groupe: racine
    contenu: |
      # remplacer l'adresse e-mail par défaut
      MAILTO = "[email protected]"
      # exécuter une commande Symfony toutes les cinq minutes (en tant qu'utilisateur ec2)
      * / 10 * * * * ec2-user / usr / bin / php / var / app / current / app / console faire: quelque chose
    encodage: plain
commandes:
  # supprimer le fichier de sauvegarde créé par Elastic Beanstalk
  clear_cron_backup:
    commande: rm -f /etc/cron.d/watson.bak

Notez que le format diffère du format crontab habituel en ce qu'il spécifie l'utilisateur sous lequel exécuter la commande.

Tamlyn
la source
Un problème ici est que les instances Elastic Beanstalk EC2 n'ont pas de services SMTP configurés par défaut, donc l'option MAILTO ici peut ne pas fonctionner.
Justin Finkelstein
3

Mon 1 centime de contribution pour 2018

Voici la bonne façon de le faire (en utilisant django/pythonet en appliquant django_crontab):

à l'intérieur du .ebextensionsdossier, créez un fichier comme celui-ci 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

Il doit être container_commandsau lieu decommands

Ronaldo Bahia
la source
2

Le dernier exemple d'Amazon est le plus simple et le plus efficace (tâches périodiques):

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

où vous créez un niveau de travail distinct pour exécuter l'un de vos travaux cron. Créez le fichier cron.yaml et placez-le dans votre dossier racine. Un problème que j'ai eu (après que cron ne semblait pas s'exécuter) était de constater que mon CodePipeline n'avait pas l'autorité pour effectuer une modification dynamodb. Sur cette base, après l'ajout d'un accès FullDynamoDB sous IAM -> rôles -> yourpipeline et le redéploiement (élastique beanstalk), cela a parfaitement fonctionné.

Josh
la source
1

Nous nous débattons donc avec cela depuis un moment et après quelques discussions avec un représentant AWS, j'ai finalement trouvé ce que je pense être la meilleure solution.

L'utilisation d'un niveau de travail avec cron.yaml est certainement la solution la plus simple. Cependant, ce que la documentation ne précise pas, c'est que cela placera le travail à la fin de la file d'attente SQS que vous utilisez pour exécuter vos travaux. Si vos tâches cron sont sensibles au temps (comme beaucoup le sont), ce n'est pas acceptable, car cela dépendrait de la taille de la file d'attente. Une option consiste à utiliser un environnement complètement séparé juste pour exécuter des tâches cron, mais je pense que c'est exagéré.

Certaines des autres options, comme vérifier si vous êtes la première instance de la liste, ne sont pas non plus idéales. Que faire si la première instance actuelle est en train de s'arrêter?

La protection d'instance peut également entraîner des problèmes - que se passe-t-il si cette instance est verrouillée / gelée?

Il est important de comprendre comment AWS gère lui-même la fonctionnalité cron.yaml. Il existe un démon SQS qui utilise une table Dynamo pour gérer "l'élection du chef". Il écrit fréquemment dans cette table, et si le leader actuel n'a pas écrit depuis peu de temps, l'instance suivante prendra le relais en tant que leader. C'est ainsi que le démon décide quelle instance doit lancer le travail dans la file d'attente SQS.

Nous pouvons réutiliser la fonctionnalité existante plutôt que d'essayer de réécrire la nôtre. Vous pouvez voir la solution complète ici: https://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27

C'est dans Ruby, mais vous pouvez facilement l'adapter à n'importe quel autre langage doté du SDK AWS. Essentiellement, il vérifie le chef actuel, puis vérifie l'état pour s'assurer qu'il est en bon état. Il bouclera jusqu'à ce qu'un leader actuel soit dans un bon état, et si l'instance actuelle est le leader, exécutez le travail.

Cidolfas
la source
0

Pour contrôler si Auto Scaling peut mettre fin à une instance particulière lors de la mise à l'échelle, utilisez la protection d'instance. Vous pouvez activer le paramètre de protection d'instance sur un groupe Auto Scaling ou une instance Auto Scaling individuelle. Lorsque Auto Scaling lance une instance, l'instance hérite du paramètre de protection d'instance du groupe Auto Scaling. Vous pouvez modifier le paramètre de protection d'instance pour un groupe Auto Scaling ou une instance Auto Scaling à tout moment.

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

Dele
la source
0

J'avais une autre solution à cela si un fichier php doit être exécuté via cron et si vous avez défini des instances NAT, vous pouvez mettre cronjob sur l'instance NAT et exécuter le fichier php via wget.

prasoon
la source
0

voici un correctif au cas où vous voudriez faire cela en PHP. Vous avez juste besoin de cronjob.config dans votre dossier .ebextensions pour le faire fonctionner comme ça.

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

les envvars récupèrent les variables d'environnement pour les fichiers. Vous pouvez déboguer la sortie sur le tmp / sendemail.log comme ci-dessus.

J'espère que cela aide quelqu'un car cela nous a sûrement aidés!

foxybagga
la source
0

Sur la base des principes de la réponse de user1599237 , où vous laissez les tâches cron s'exécuter sur toutes les instances, mais au début des tâches, déterminez si elles doivent être autorisées à s'exécuter, j'ai fait une autre solution.

Au lieu de regarder les instances en cours d'exécution (et de devoir stocker votre clé et votre secret AWS), j'utilise la base de données MySQL à laquelle je me connecte déjà à partir de toutes les instances.

Il n'y a pas d'inconvénients, seulement des points positifs:

  • pas d'instance ni de frais supplémentaires
  • solution solide comme le roc - aucune chance de double exécution
  • évolutif - fonctionne automatiquement lorsque vos instances sont mises à l'échelle
  • basculement - fonctionne automatiquement en cas de défaillance d'une instance

Vous pouvez également utiliser un système de fichiers communément partagé (comme AWS EFS via le protocole NFS) au lieu d'une base de données.

La solution suivante est créée dans le framework PHP Yii mais vous pouvez facilement l'adapter à un autre framework et langage. Le gestionnaire d'exceptions Yii::$app->systemest également un module à moi. Remplacez-le par tout ce que vous utilisez.

/**
 * 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);
}

Voici le schéma de base de données que j'utilise:

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
la source