AWS Elastic Beanstalk mit einem Cronjob


89

Ich würde gerne wissen, ob es eine Möglichkeit gibt, einen Cronjob / eine Aufgabe so einzurichten, dass sie jede Minute ausgeführt wird. Derzeit sollte jede meiner Instanzen diese Aufgabe ausführen können.

Folgendes habe ich in den Konfigurationsdateien erfolglos versucht:

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

Ich bin mir nicht sicher, ob dies der richtige Weg ist

Irgendwelche Ideen?


1
Ist der Befehl richtig? Ich meine ... es könnte sein: Befehl: echo "* / 1 * * * * root php /etc/httpd/myscript.php"> /etc/cron.d/something In beiden Fällen würde ich vorschlagen, dass Sie das verwenden Leader_only Flag, sonst werden alle Maschinen diesen Cron-Job sofort
starten

Ja! Wenn ich definitiv das Leader_only-Flag verwende, werde ich versuchen, den Befehl zu ändern.
Onema

Antworten:


96

So habe ich Elastic Beanstalk einen Cron-Job hinzugefügt:

Erstellen Sie im Stammverzeichnis Ihrer Anwendung einen Ordner mit dem Namen .ebextensions, falls dieser noch nicht vorhanden ist. Erstellen Sie dann eine Konfigurationsdatei im Ordner .ebextensions. Ich werde example.config zur Veranschaulichung verwenden. Fügen Sie dies dann zu example.config hinzu

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

Dies ist eine YAML-Konfigurationsdatei für Elastic Beanstalk. Stellen Sie beim Kopieren in Ihren Texteditor sicher, dass Ihr Texteditor Leerzeichen anstelle von Tabulatoren verwendet. Andernfalls wird ein YAML-Fehler angezeigt, wenn Sie diesen an EB senden.

Dazu wird ein Befehl mit dem Namen 01_some_cron_job erstellt. Befehle werden in alphabetischer Reihenfolge ausgeführt, sodass die 01 sicherstellt, dass sie als erster Befehl ausgeführt wird.

Der Befehl nimmt dann den Inhalt einer Datei mit dem Namen some_cron_job.txt und fügt ihn einer Datei mit dem Namen some_cron_job in /etc/cron.d hinzu.

Der Befehl ändert dann die Berechtigungen für die Datei /etc/cron.d/some_cron_job.

Der key_only-Schlüssel stellt sicher, dass der Befehl nur auf der ec2-Instanz ausgeführt wird, die als Leader betrachtet wird. Anstatt auf jeder ec2-Instanz zu laufen, die Sie möglicherweise ausgeführt haben.

Erstellen Sie dann eine Datei mit dem Namen some_cron_job.txt im Ordner .ebextensions. Sie werden Ihre Cron-Jobs in dieser Datei platzieren.

Also zum Beispiel:

# 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

Dieser Cron-Job wird also jede Minute jeder Stunde eines jeden Tages als Root-Benutzer ausgeführt und verwirft die Ausgabe nach / dev / null. / usr / bin / php ist der Pfad zu PHP. Ersetzen Sie dann some-php-script-here durch den Pfad zu Ihrer PHP-Datei. Dies setzt natürlich voraus, dass Ihr Cron-Job eine PHP-Datei ausführen muss.

Stellen Sie außerdem sicher, dass die Datei some_cron_job.txt am Ende der Datei eine neue Zeile enthält, wie im Kommentar angegeben. Sonst läuft cron nicht.

Update: Bei dieser Lösung tritt ein Problem auf, wenn Elastic Beanstalk Ihre Instanzen skaliert. Angenommen, Sie haben eine Instanz, in der der Cron-Job ausgeführt wird. Sie erhalten eine Zunahme des Datenverkehrs, sodass Elastic Beanstalk Sie auf zwei Instanzen skaliert. Leader_only stellt sicher, dass zwischen den beiden Instanzen nur ein Cron-Job ausgeführt wird. Ihr Datenverkehr nimmt ab und Elastic Beanstalk reduziert Sie auf eine Instanz. Anstatt die zweite Instanz zu beenden, beendet Elastic Beanstalk die erste Instanz, die der Anführer war. Sie haben jetzt keine Cron-Jobs mehr ausgeführt, da sie nur auf der ersten Instanz ausgeführt wurden, die beendet wurde. Siehe die Kommentare unten.

Update 2: Machen Sie dies nur anhand der folgenden Kommentare deutlich: AWS ist jetzt vor automatischer Instanzbeendigung geschützt. Aktivieren Sie es einfach auf Ihrer Leader-Instanz und Sie können loslegen. - Nicolás Arévalo 28. Oktober 16 um 9:23 Uhr


12
Ich verwende Ihren Vorschlag seit einiger Zeit und bin kürzlich auf ein Problem gestoßen, bei dem der Anführer irgendwie gewechselt hat, was dazu führte, dass mehrere Instanzen den Cron ausführten. Um dieses Problem zu lösen, wechselte ich 01_some_cron_jobzu 02_some_cron_jobund fügte hinzu , 01_remove_cron_jobsmit dem folgenden: command: "rm /etc/cron.d/cron_jobs || exit 0". Auf diese Weise verfügt nach jeder Bereitstellung nur der Leiter über die cron_jobsDatei. Wenn sich die Anführer ändern, können Sie sie einfach neu bereitstellen, und die Cron werden so repariert, dass sie nur noch einmal ausgeführt werden.
Willem Renzema

4
Ich würde vorschlagen, sich nicht auf leader_onlyEigentum zu verlassen. Es wird nur während des Einsatzes verwendet und wenn Sie skalieren oder Ihr „Führer“ Instanz versagt Sie sind verpflichtet , Probleme haben Referenz
arnaslu

2
Tu das nicht. Es ist zu unzuverlässig. Die einzige Möglichkeit, dies zum Laufen zu bringen, besteht darin, eine Mikroinstanz auszuführen und von dort aus Cron-Jobs mit CURL auszuführen. Dies garantiert, dass nur eine Instanz es ausführt und der Leader, auf dem Cron installiert ist, nicht beendet wird.
Ben Sinclair

1
Ich habe versucht, dies mit einem kleinen Ruby-Skript zu beheben. Sie finden es hier: github.com/SocialbitGmbH/AWSBeanstalkLeaderManager
Thomas Kekeisen

8
AWS ist jetzt vor automatischer Instanzbeendigung geschützt. Aktivieren Sie es einfach auf Ihrer Leader-Instanz und Sie können loslegen.
Nicolás Arévalo

58

Dies ist der offizielle Weg, dies jetzt zu tun (2015+). Bitte versuchen Sie dies zuerst, es ist bei weitem die einfachste derzeit verfügbare Methode und auch die zuverlässigste.

Nach aktuellen Dokumenten kann man periodische Aufgaben auf der sogenannten Worker-Ebene ausführen .

Zitieren der Dokumentation:

AWS Elastic Beanstalk unterstützt regelmäßige Aufgaben für Ebenen der Arbeitsumgebung in Umgebungen, in denen eine vordefinierte Konfiguration mit einem Lösungsstapel ausgeführt wird, der den Containernamen "v1.2.0" enthält. Sie müssen eine neue Umgebung erstellen.

Interessant ist auch der Teil über cron.yaml :

Um regelmäßige Aufgaben aufzurufen, muss Ihr Anwendungsquellpaket eine cron.yaml-Datei auf Stammebene enthalten. Die Datei muss Informationen zu den periodischen Aufgaben enthalten, die Sie planen möchten. Geben Sie diese Informationen mit der Standard-Crontab-Syntax an.

Update: Wir konnten diese Arbeit bekommen. Hier sind einige wichtige Fallstricke aus unserer Erfahrung (Node.js Plattform):

  • Stellen Sie bei Verwendung der Datei cron.yaml sicher, dass Sie über die neuesten awsebcli verfügen , da ältere Versionen nicht ordnungsgemäß funktionieren.
  • Es ist auch wichtig, eine neue Umgebung zu schaffen (zumindest in unserem Fall) und nicht nur eine alte zu klonen.
  • Wenn Sie sicherstellen möchten, dass CRON auf Ihrer EC2 Worker Tier-Instanz unterstützt wird, ssh in it ( eb ssh) und führen Sie es aus cat /var/log/aws-sqsd/default.log. Es sollte als melden aws-sqsd 2.0 (2015-02-18). Wenn Sie keine 2.0-Version haben, ist beim Erstellen Ihrer Umgebung ein Fehler aufgetreten, und Sie müssen wie oben angegeben eine neue erstellen.

2
Über cron.yaml gibt es einen großartigen Blog-Beitrag: Ausführen von Cron-Jobs auf Amazon Web Services (AWS) Elastic Beanstalk - Medium
jwako

5
Vielen Dank für diese Rookie-Frage. Mein Cron muss zweimal pro Stunde die Datenbank meiner Web-App auf bevorstehende Kalenderereignisse überprüfen und eine Erinnerungs-E-Mail senden, wenn dies der Fall ist. Was ist hier das beste Setup? Sollte die URL cron.yaml auf eine Route in meiner Web-App verweisen? Oder sollte ich meiner Worker-Env-App Zugriff auf die Datenbank gewähren? So wenig da draußen!
Christian

5
@christian So wie wir es machen, läuft dieselbe App in zwei verschiedenen Umgebungen (daher ist keine spezielle Konfiguration erforderlich) - Worker und gemeinsamer Webserver. In der Worker-Umgebung sind einige spezielle Routen aktiviert, indem eine ENV-Variable festgelegt wird, nach der unsere App sucht. Auf diese Weise können Sie in Ihrer cron.yaml spezielle Nur-Arbeiter-Routen festlegen und gleichzeitig den Luxus einer gemeinsam genutzten Codebasis mit der normalen App genießen. Ihre Worker-App kann problemlos auf dieselben Ressourcen zugreifen wie Webserver: Datenbank, Modelle usw.
Xaralis

1
@JaquelinePassos v1.2.0 ist eine Lösungsstapelversion. Hier sollten Sie auswählen können, welche Version des Lösungsstapels Sie beim Erstellen einer neuen Umgebung erstellen möchten. Alles, was neuer als v1.2.0 ist, sollte funktionieren. In Bezug auf die URL sollte es sich um die URL handeln, die Ihre Anwendung abhört, nicht um einen Dateipfad. Es ist nicht möglich, Django-Verwaltungsbefehle auszuführen, sondern nur HTTP-Anforderungen.
Xaralis

4
Eine Sache, die mir nicht klar ist, ist, ob es eine Möglichkeit gibt, zu vermeiden, dass eine zusätzliche EC2-Maschine zugewiesen werden muss, um die Cron-Jobs über cron.yaml auszuführen. Im Idealfall wird es auf demselben Computer ausgeführt, auf dem HTTP-Anforderungen (z. B. Webschicht) verarbeitet werden.
Wenzel Jakob

31

In Bezug auf die Antwort von jamieb und wie bereits erwähnt, können Sie die Eigenschaft 'Leader_only' verwenden, um sicherzustellen, dass nur eine EC2-Instanz den Cron-Job ausführt.

Zitat aus http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html :

Sie können führer_nur verwenden. Eine Instanz wird ausgewählt, um in einer Auto Scaling-Gruppe führend zu sein. Wenn der Wert Leader_only auf true festgelegt ist, wird der Befehl nur auf der Instanz ausgeführt, die als Leader markiert ist.

Ich versuche, eine ähnliche Sache auf meinem eb zu erreichen, also werde ich meinen Beitrag aktualisieren, wenn ich es löse.

AKTUALISIEREN:

Ok, ich habe jetzt arbeitende Cronjobs mit der folgenden eb-Konfiguration:

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

Im Wesentlichen erstelle ich eine temporäre Datei mit den Cronjobs und setze die Crontab so, dass sie aus der temporären Datei liest. Anschließend lösche ich die temporäre Datei. Hoffe das hilft.


3
Wie würden Sie sicherstellen, dass die Instanz, auf der diese Crontab ausgeführt wird, nicht durch die automatische Skalierung beendet wird? Standardmäßig wird die älteste Instanz beendet.
Sebastien

1
Das ist ein Problem, das ich noch nicht lösen konnte. Es scheint mir ein Fehler in der Funktionalität von Amazon zu sein, dass führer_nur Befehle nicht auf einen neuen Leiter angewendet werden, wenn der aktuelle von EB beendet wird. Wenn Sie sich etwas einfallen lassen, teilen Sie es bitte mit!
beterthanlife

7
Also habe ich (endlich) herausgefunden, wie verhindert werden kann, dass der Leader durch automatische Skalierung beendet wird - benutzerdefinierte Richtlinien für die automatische Skalierung. Siehe docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/…
beterthanlife

1
@Nate Sie haben dies wahrscheinlich inzwischen herausgefunden, aber basierend auf meiner Lektüre der Reihenfolge, in der diese ausgeführt werden, werden "Befehle" vor "container_commands" ausgeführt, sodass Sie die Datei erstellen, dann löschen und dann versuchen, die Crontab auszuführen .
clearf

1
@Sebastien Um die älteste Intance beizubehalten, gehe ich wie folgt vor: 1 - Ändern Sie den Beendigungsschutz der Intance in ENBABLE. 2 - Gehen Sie zur Auto Scale Group und suchen Sie Ihre EBS-Umgebungs-ID, klicken Sie auf BEARBEITEN und ändern Sie die Kündigungsrichtlinien in "NewestInstance"
Ronaldo Bahia

12

Wie oben erwähnt, besteht der grundlegende Fehler beim Einrichten einer Crontab-Konfiguration darin, dass dies nur bei der Bereitstellung geschieht. Wenn der Cluster automatisch vergrößert und dann wieder heruntergefahren wird, wird bevorzugt, dass auch der erste Server ausgeschaltet wird. Außerdem würde es kein Failover geben, was für mich kritisch war.

Ich habe einige Nachforschungen angestellt und dann mit unserem AWS-Kontospezialisten gesprochen, um Ideen auszutauschen und die von mir entwickelte Lösung zu validieren. Sie können dies mit OpsWorks erreichen , obwohl es ein bisschen so ist, als würde man ein Haus benutzen, um eine Fliege zu töten. Es ist auch möglich, Data Pipeline mit Task Runner zu verwenden , dies hat jedoch nur begrenzte Möglichkeiten in den Skripten, die ausgeführt werden können, und ich musste in der Lage sein, PHP-Skripte mit Zugriff auf die gesamte Codebasis auszuführen. Sie können auch eine EC2-Instanz außerhalb des ElasticBeanstalk-Clusters zuweisen, haben dann aber kein erneutes Failover.

Hier ist also, was ich mir ausgedacht habe, was anscheinend unkonventionell ist (wie der AWS-Vertreter kommentierte) und als Hack angesehen werden kann, aber es funktioniert und solide mit Failover ist. Ich habe eine Codierungslösung mit dem SDK ausgewählt, die ich in PHP zeigen werde, obwohl Sie dieselbe Methode in jeder Sprache ausführen können, die Sie bevorzugen.

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

Gehen Sie dies durch und wie es funktioniert ... Sie rufen Skripte von crontab auf, wie Sie es normalerweise auf jeder EC2-Instanz tun würden. Jedes Skript enthält dies am Anfang (oder enthält jeweils eine einzelne Datei, wie ich es verwende), wodurch ein ElasticBeanstalk-Objekt erstellt und eine Liste aller Instanzen abgerufen wird. Es verwendet nur den ersten Server in der Liste und prüft, ob es mit sich selbst übereinstimmt. Wenn dies der Fall ist, wird es fortgesetzt, andernfalls stirbt es und wird geschlossen. Ich habe es überprüft und die zurückgegebene Liste scheint konsistent zu sein. Technisch gesehen muss sie nur etwa eine Minute lang konsistent sein, da jede Instanz das geplante Cron ausführt. Wenn es sich ändert, spielt es keine Rolle, da es wiederum nur für dieses kleine Fenster relevant ist.

Dies ist keineswegs elegant, entspricht jedoch unseren spezifischen Anforderungen - was nicht darin bestand, die Kosten durch einen zusätzlichen Service zu erhöhen oder eine dedizierte EC2-Instanz zu haben, und im Falle eines Ausfalls ein Failover hätte. Unsere Cron-Skripte führen Wartungsskripte aus, die in SQS abgelegt werden, und jeder Server im Cluster hilft bei der Ausführung. Zumindest kann dies Ihnen eine alternative Option bieten, wenn es Ihren Anforderungen entspricht.

-Davey


Ich habe festgestellt, dass php_uname ('n') den privaten DNS-Namen (z. B. ip-172.24.55.66) zurückgibt, bei dem es sich nicht um die gesuchte Instanz-ID handelt. Anstatt php_uname () zu verwenden, habe ich Folgendes verwendet: Verwenden Sie $instanceId = file_get_contents("http://instance-data/latest/meta-data/instance-id"); dann einfach diese $ instanceId var, um den Vergleich durchzuführen .
Valorum

1
Gibt es eine Garantie dafür, dass das Instances-Array bei jedem Describe-Aufruf dieselbe Reihenfolge aufweist? Ich würde vorschlagen, das Feld ['Id'] jedes Eintrags in ein Array zu extrahieren und in PHP zu sortieren, bevor Sie überprüfen, ob der erste sortierte Eintrag Ihre aktuelle Instanz-ID ist.
Gabriel

Basierend auf dieser Antwort habe ich diese Lösung gemacht: stackoverflow.com/questions/14077095/… - es ist sehr ähnlich, hat aber KEINE Chance auf doppelte Ausführung.
TheStoryCoder

11

Ich habe mit einem AWS-Support-Mitarbeiter gesprochen, und so haben wir das für mich zum Laufen gebracht. Lösung 2015:

Erstellen Sie eine Datei in Ihrem Verzeichnis .ebextensions mit Ihrem_Dateinamen.config. In der Konfigurationsdatei geben Sie Folgendes ein:

Dateien:
  "/etc/cron.d/cron_example":
    Modus: "000644"
    Besitzer: root
    Gruppe: root
    Inhalt: |
      * * * * * root /usr/local/bin/cron_example.sh

  "/usr/local/bin/cron_example.sh":
    Modus: "000755"
    Besitzer: root
    Gruppe: root
    Inhalt: |
      #! / bin / bash

      /usr/local/bin/test_cron.sh || Ausfahrt
      Echo "Cron läuft um" `Datum` >> /tmp/cron_example.log
      # Führen Sie jetzt Aufgaben aus, die nur auf einer Instanz ausgeführt werden sollen ...

  "/usr/local/bin/test_cron.sh":
    Modus: "000755"
    Besitzer: root
    Gruppe: root
    Inhalt: |
      #! / bin / bash

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

      # Suchen Sie den Namen unserer Auto Scaling-Gruppe.
      ASG = `aws ec2 description-tags --filters" Name = Ressourcen-ID, Werte = $ INSTANCE_ID "\
        --region $ REGION --ausgabetext | awk '/ aws: autoscaling: groupName / {print $ 5}' `

      # Suchen Sie die erste Instanz in der Gruppe
      FIRST = `aws autoscaling beschreiben-automatische-skalierung-gruppen --auto-skalierung-gruppennamen $ ASG \
        --region $ REGION --ausgabetext | awk '/ InService $ / {print $ 4}' | sortieren | Kopf -1`

      # Testen Sie, ob sie gleich sind.
      ["$ FIRST" = "$ INSTANCE_ID"]

Befehle:
  rm_old_cron:
    Befehl: "rm * .bak"
    cwd: "/etc/cron.d"
    ignoreErrors: true

Diese Lösung hat zwei Nachteile:

  1. Bei nachfolgenden Bereitstellungen benennt Beanstalk das vorhandene Cron-Skript in .bak um, Cron führt es jedoch weiterhin aus. Ihr Cron wird jetzt zweimal auf demselben Computer ausgeführt.
  2. Wenn Ihre Umgebung skaliert, erhalten Sie mehrere Instanzen, auf denen Ihr Cron-Skript ausgeführt wird. Dies bedeutet, dass Ihre E-Mail-Aufnahmen wiederholt oder Ihre Datenbankarchive dupliziert werden

Problemumgehung:

  1. Stellen Sie sicher, dass jedes .ebextensions-Skript, das ein Cron erstellt, auch die .bak-Dateien bei nachfolgenden Bereitstellungen entfernt.
  2. Verfügen Sie über ein Hilfsskript, das Folgendes ausführt: - Ruft die aktuelle Instanz-ID aus den Metadaten ab. - Ruft den aktuellen Namen der automatischen Skalierungsgruppe aus den EC2-Tags ab. - Ruft die Liste der EC2-Instanzen in dieser Gruppe alphabetisch sortiert ab. - Nimmt die erste Instanz aus dieser Liste. - Vergleicht die Instanz-ID aus Schritt 1 mit der ersten Instanz-ID aus Schritt 4. Ihre Cron-Skripte können dann mithilfe dieses Hilfsskripts bestimmen, ob sie ausgeführt werden sollen.

Vorbehalt:

  • Die für die Beanstalk-Instanzen verwendete IAM-Rolle benötigt ec2: DescribeTags und Autoscaling: DescribeAutoScalingGroups-Berechtigungen
  • Die ausgewählten Instanzen werden von der automatischen Skalierung als InService angezeigt. Dies bedeutet nicht unbedingt, dass sie vollständig hochgefahren und bereit sind, Ihren Cron auszuführen.

Sie müssten die IAM-Rollen nicht festlegen, wenn Sie die Standard-Beanstalk-Rolle verwenden.


7

Wenn Sie Rails verwenden, können Sie den Edelstein für immer elastische Bohnenstiele verwenden . Sie können Cron-Jobs entweder auf allen oder nur auf einer Instanz ausführen. Es überprüft jede Minute, ob es nur eine "Leader" -Instanz gibt, und befördert automatisch einen Server zum "Leader", wenn es keine gibt. Dies ist erforderlich, da Elastic Beanstalk nur das Konzept des Leader während der Bereitstellung hat und jede Instanz während der Skalierung jederzeit herunterfahren kann.

UPDATE Ich habe auf AWS OpsWorks umgestellt und pflege dieses Juwel nicht mehr. Wenn Sie mehr Funktionen benötigen, als in den Grundlagen von Elastic Beanstalk verfügbar sind, empfehle ich dringend, zu OpsWorks zu wechseln.


Würde es Ihnen etwas ausmachen, uns zu erzählen, wie Sie es mit OpsWorks gelöst haben? Führen Sie benutzerdefinierte Ebenen aus, die die Cron-Jobs ausführen?
Tommie

Ja, ich habe eine Admin / Cron-Ebene, die nur auf einem Server ausgeführt wird. Ich habe ein benutzerdefiniertes Kochbuch erstellt, das alle meine Cron-Jobs enthält. AWS hat einen Leitfaden unter docs.aws.amazon.com/opsworks/latest/userguide/… .
Dignoe

@dignoe Wenn Sie einen Server zum Ausführen von Cron-Jobs mit OpsWorks zuweisen, kann ich mit Elastic Beanstalk eine Umgebung mit einem Server zum Ausführen von Cron-Jobs verwenden. Selbst mit Load Balancer sind die maximalen und minimalen Instanzen auf eins gesetzt, um immer mindestens eine Serverinstanz zu erhalten.
Jose Nobile

6

Sie möchten wirklich keine Cron-Jobs auf Elastic Beanstalk ausführen. Da Sie mehrere Anwendungsinstanzen haben, kann dies zu Rennbedingungen und anderen merkwürdigen Problemen führen. Ich habe kürzlich darüber gebloggt (4. oder 5. Tipp auf der Seite). Die Kurzversion: Je nach Anwendung, verwenden Sie eine Job - Warteschlange wie SQS oder eine Lösung von Drittanbietern wie iron.io .


SQS garantiert nicht, dass der Code nur einmal ausgeführt wird. Ich mag die Website iron.io, ich werde sie mir ansehen.
Nathan H

Auch in Ihrem Blog-Beitrag empfehlen Sie die Verwendung von InnoDB auf RDS. Ich verwende eine Tabelle auf RDS, um meine Aufgaben zu speichern, und verwende die InnoDB-Funktion "SELECT ... FOR UPDATE", um sicherzustellen, dass nur ein Server diese Aufgaben ausführt. Wie kontaktiert Ihre App SQS ohne Cron-Job oder Benutzerinteraktion?
James Alday

1
@ JamesAlday Diese SO-Frage ist ziemlich alt. Seit ich den obigen Kommentar geschrieben habe, hat AWS eine elegante Methode zur Bearbeitung von Cron-Jobs auf Elastic Beanstalk eingeführt, indem einer der laufenden Server als Master ausgewählt wurde. Trotzdem klingt es so, als würden Sie cron + MySQL als Jobwarteschlange missbrauchen. Ich müsste viel über Ihre App wissen, bevor ich konkrete Empfehlungen geben kann.
Jamieb

Ich habe ein Skript, das über cron ausgeführt wird und eine Tabelle auf auszuführende Jobs überprüft. Durch die Verwendung von Transaktionen wird verhindert, dass mehrere Server denselben Job ausführen. Ich habe mich mit SQS befasst, aber Sie benötigen einen Master-Server, auf dem alle Skripte ausgeführt werden, anstatt sie zu verteilen, und Sie müssen weiterhin Logik schreiben, um sicherzustellen, dass Sie nicht dasselbe Skript mehrmals ausführen. Aber ich bin immer noch verwirrt darüber, wie Sie Aufgaben ohne Benutzerinteraktion oder Cron ausführen lassen - was veranlasst Ihre App, die Aufgaben in der Warteschlange auszuführen?
James Alday

4

2017: Wenn Sie Laravel5 + verwenden

Sie brauchen nur 2 Minuten, um es zu konfigurieren:

  • Erstellen Sie eine Worker-Ebene
  • installiere laravel-aws-worker

    composer require dusterio/laravel-aws-worker

  • Fügen Sie dem Stammordner eine cron.yaml hinzu:

Fügen Sie cron.yaml zum Stammordner Ihrer Anwendung hinzu (dies kann Teil Ihres Repos sein oder Sie können diese Datei direkt vor der Bereitstellung auf EB hinzufügen - wichtig ist, dass diese Datei zum Zeitpunkt der Bereitstellung vorhanden ist):

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

Das ist es!

Alle Ihre Aufgaben App\Console\Kernelwerden nun ausgeführt

Detaillierte Anweisungen und Erklärungen: https://github.com/dusterio/laravel-aws-worker

So schreiben Sie Aufgaben in Laravel: https://laravel.com/docs/5.4/scheduling


3

Eine lesbare Lösung unter Verwendung filesanstelle von container_commands:

Dateien:
  "/etc/cron.d/my_cron":
    Modus: "000644"
    Besitzer: root
    Gruppe: root
    Inhalt: |
      # Standard-E-Mail-Adresse überschreiben
      MAILTO = "example@gmail.com"
      # Führen Sie alle fünf Minuten einen Symfony-Befehl aus (als ec2-Benutzer).
      * / 10 * * * * ec2-user / usr / bin / php / var / app / current / app / console tun: etwas
    Kodierung: einfach
Befehle:
  # Löschen Sie die von Elastic Beanstalk erstellte Sicherungsdatei
  clear_cron_backup:
    Befehl: rm -f /etc/cron.d/watson.bak

Beachten Sie, dass sich das Format vom üblichen Crontab-Format dadurch unterscheidet, dass es den Benutzer angibt, unter dem der Befehl ausgeführt werden soll.


Ein Problem hierbei ist, dass in Elastic Beanstalk EC2-Instanzen standardmäßig keine SMTP-Dienste eingerichtet sind, sodass die Option MAILTO hier möglicherweise nicht funktioniert.
Justin Finkelstein

3

Mein 1 Cent Beitrag für 2018

Hier ist der richtige Weg, dies zu tun (mit django/pythonund django_crontabApp):

Innerhalb des .ebextensionsOrdners erstellen Sie eine Datei wie folgt 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

Es muss container_commandsstatt seincommands



2

Das neueste Beispiel von Amazon ist das einfachste und effizienteste (regelmäßige Aufgaben):

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

Hier erstellen Sie eine separate Worker-Ebene, um einen Ihrer Cron-Jobs auszuführen. Erstellen Sie die Datei cron.yaml und legen Sie sie in Ihrem Stammordner ab. Ein Problem, das ich hatte (nachdem cron anscheinend nicht ausgeführt wurde), war die Feststellung, dass meine CodePipeline nicht befugt war, eine Dynamodb-Änderung durchzuführen. Basierend darauf hat es nach dem Hinzufügen des FullDynamoDB-Zugriffs unter IAM -> Rollen -> Yourpipeline und dem erneuten Bereitstellen (elastische Bohnenstange) perfekt funktioniert.


1

Verwenden Sie Leader_only nicht, um eine eindeutige Instanz in ASG zu erstellen. ASG garantiert niemals, dass Sie diese bestimmte Instanz behalten können, sondern garantiert nur die Anzahl der im Dienst befindlichen Instanzen. Die Leader-Instanz wird möglicherweise aufgrund einer fehlgeschlagenen EB-Integritätsprüfung beendet.
mst

1

Wir haben also eine Weile damit zu kämpfen und nach einigen Diskussionen mit einem AWS-Vertreter habe ich endlich das gefunden, was ich für die beste Lösung halte.

Die Verwendung einer Worker-Ebene mit cron.yaml ist definitiv die einfachste Lösung. In der Dokumentation wird jedoch nicht klargestellt, dass der Job dadurch am Ende der SQS-Warteschlange steht, mit der Sie Ihre Jobs tatsächlich ausführen. Wenn Ihre Cron-Jobs zeitkritisch sind (wie viele), ist dies nicht akzeptabel, da dies von der Größe der Warteschlange abhängt. Eine Möglichkeit besteht darin, eine völlig separate Umgebung zu verwenden, um nur Cron-Jobs auszuführen, aber ich denke, das ist übertrieben.

Einige der anderen Optionen, z. B. die Überprüfung, ob Sie die erste Instanz in der Liste sind, sind ebenfalls nicht ideal. Was ist, wenn die aktuelle erste Instanz gerade heruntergefahren wird?

Der Instanzschutz kann auch mit Problemen verbunden sein - was ist, wenn diese Instanz gesperrt / eingefroren wird?

Es ist wichtig zu verstehen, wie AWS selbst die Funktionalität von cron.yaml verwaltet. Es gibt einen SQS-Daemon, der eine Dynamo-Tabelle verwendet, um "Leader-Wahlen" durchzuführen. Es schreibt häufig in diese Tabelle, und wenn der aktuelle Anführer in kurzer Zeit nicht geschrieben hat, übernimmt die nächste Instanz die Leitung. Auf diese Weise entscheidet der Dämon, welche Instanz den Job in der SQS-Warteschlange auslösen soll.

Wir können die vorhandene Funktionalität neu verwenden, anstatt zu versuchen, unsere eigene neu zu schreiben. Die vollständige Lösung finden Sie hier: https://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27

Das ist in Ruby, aber Sie können es leicht an jede andere Sprache anpassen, die das AWS SDK enthält. Im Wesentlichen wird der aktuelle Anführer und anschließend der Status überprüft, um sicherzustellen, dass er sich in einem guten Zustand befindet. Es wird eine Schleife ausgeführt, bis sich ein aktueller Leader in einem guten Zustand befindet. Wenn die aktuelle Instanz der Leader ist, führen Sie den Job aus.


0

Verwenden Sie den Instanzschutz, um zu steuern, ob die automatische Skalierung eine bestimmte Instanz beim Skalieren beenden kann. Sie können die Instanzschutzeinstellung für eine Auto Scaling-Gruppe oder eine einzelne Auto Scaling-Instanz aktivieren. Wenn die automatische Skalierung eine Instanz startet, erbt die Instanz die Instanzschutzeinstellung der Gruppe "Automatische Skalierung". Sie können die Instanzschutzeinstellung für eine Auto Scaling-Gruppe oder eine Auto Scaling-Instanz jederzeit ändern.

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


0

Ich hatte eine andere Lösung dafür, wenn eine PHP-Datei über Cron ausgeführt werden muss und wenn Sie NAT-Instanzen festgelegt haben, können Sie Cronjob auf NAT-Instanz setzen und PHP-Datei über Wget ausführen.


0

Hier ist ein Fix für den Fall, dass Sie dies in PHP tun möchten. Sie brauchen nur cronjob.config in Ihrem .ebextensions-Ordner, damit es so funktioniert.

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

Der Envvars ruft die Umgebungsvariablen für die Dateien ab. Sie können die Ausgabe in der Datei tmp / sendemail.log wie oben beschrieben debuggen.

Hoffe das hilft jemandem, da es uns sicherlich geholfen hat!


0

Basierend auf den Prinzipien der Antwort von user1599237 , bei der Sie die Cron-Jobs auf allen Instanzen , aber stattdessen zu Beginn der Jobs bestimmen, ob sie ausgeführt werden dürfen, habe ich eine andere Lösung gefunden.

Anstatt die laufenden Instanzen zu betrachten (und Ihren AWS-Schlüssel und Ihr Geheimnis zu speichern), verwende ich die MySQL-Datenbank, mit der ich bereits von allen Instanzen aus eine Verbindung herstelle.

Es hat keine Nachteile, nur positive:

  • Keine zusätzliche Instanz oder Kosten
  • felsenfeste Lösung - keine Chance auf doppelte Ausführung
  • skalierbar - funktioniert automatisch, wenn Ihre Instanzen vergrößert und verkleinert werden
  • Failover - funktioniert automatisch, wenn eine Instanz einen Fehler aufweist

Alternativ können Sie anstelle einer Datenbank auch ein gemeinsam genutztes Dateisystem (wie AWS EFS über das NFS-Protokoll) verwenden.

Die folgende Lösung wird im PHP-Framework Yii erstellt , Sie können sie jedoch problemlos für ein anderes Framework und eine andere Sprache anpassen. Auch der Exception Handler Yii::$app->systemist ein eigenes Modul. Ersetzen Sie es durch das, was Sie verwenden.

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

Dies ist das Datenbankschema, das ich verwende:

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`)
)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.