it-swarm.com.de

AWS Elastic Beanstalk, ein Cronjob

Ich würde gerne wissen, ob es einen Weg gibt, einen Cronjob/eine Aufgabe zu erstellen, um jede Minute auszuführen. Derzeit sollte jede meiner Instanzen diese Aufgabe ausführen können.

Dies ist, was ich in den Config-Dateien ohne Erfolg versucht habe:

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

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

Irgendwelche Ideen?

80
Onema

So fügte ich Elastic Beanstalk einen Cron-Job hinzu:

Erstellen Sie im Stammverzeichnis Ihrer Anwendung einen Ordner mit dem Namen .ebextensions, falls er noch nicht vorhanden ist. Erstellen Sie dann eine Konfigurationsdatei im Ordner .ebextensions. Ich benutze example.config zu Illustrationszwecken. Dann fügen Sie dies 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 sicher, dass Sie beim Kopieren in Ihren Texteditor Leerzeichen anstelle von Tabulatoren verwenden. Andernfalls erhalten Sie einen YAML-Fehler, wenn Sie dies auf EB drücken.

So erstellen Sie einen Befehl mit dem Namen 01_some_cron_job. Befehle werden in alphabetischer Reihenfolge ausgeführt, so dass 01 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.

Die Taste leader_only stellt sicher, dass der Befehl nur in 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 anschließend eine Datei mit dem Namen some_cron_job.txt im Ordner .ebextensions. Sie werden Ihre Cron-Jobs in dieser Datei ablegen.

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 zu jeder vollen Stunde als Root-Benutzer ausgeführt und die Ausgabe nach/dev/null verworfen./usr/bin/php ist der Pfad zu PHP. Ersetzen Sie dann ein PHP-Skript mit dem 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 einen Zeilenumbruch enthält, genau wie im Kommentar angegeben. Sonst läuft Cron nicht.

Update: Es gibt ein Problem mit dieser Lösung, wenn Elastic Beanstalk Ihre Instanzen skaliert. Angenommen, Sie haben eine Instanz, in der der Cron-Job ausgeführt wird. Sie erhalten mehr Traffic, so dass Sie mit Elastic Beanstalk auf zwei Instanzen skaliert werden können. Mit leader_only wird sichergestellt, dass zwischen den beiden Instanzen nur ein Cron-Job ausgeführt wird. Ihr Verkehr 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 ausgeführt, da sie nur in der ersten Instanz ausgeführt wurden, die beendet wurde. Siehe die Kommentare unten.

Update 2: Verdeutlichen Sie dies anhand der folgenden Kommentare: AWS bietet jetzt Schutz gegen die automatische Instanzbeendigung. Aktivieren Sie es einfach in Ihrer Anführerinstanz und Sie können loslegen. - Nicolás Arévalo 28. Oktober 16 um 09:23

82
Anarchtica

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

Laut aktuellen Dokumenten ist man in der Lage, periodische Aufgaben auszuführen in ihrer sogenannten Worker Tier .

Zitieren der Dokumentation:

AWS Elastic Beanstalk unterstützt periodische Aufgaben für Worker-Tiers in Umgebungen, in denen eine vordefinierte Konfiguration mit einem Lösungsstapel ausgeführt wird, der den Namen "v1.2.0" im Containernamen enthält. Sie müssen eine neue Umgebung erstellen.

Interessant ist auch der Teil über cron.yaml:

Um periodische Tasks aufzurufen, muss Ihr Anwendungsquellenpaket eine Datei cron.yaml auf der 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 einige wichtige Beispiele aus unserer Erfahrung (Plattform Node.js):

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

In Bezug auf die Antwort von jamieb und die genannten Erwähnungen können Sie die Eigenschaft 'leader_only' verwenden, um sicherzustellen, dass nur eine EC2-Instanz den Cron-Job ausführt.

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

sie können leader_only verwenden. Eine Instanz wird als Anführer in einer Auto Scaling-Gruppe ausgewählt. Wenn der Wert für leader_only auf true gesetzt ist, wird der Befehl nur in der Instanz ausgeführt, die als Leader markiert ist.

Ich bemühe mich, eine ähnliche Sache auf meiner Ebbe zu erreichen, also werde ich meinen Beitrag aktualisieren, wenn ich ihn löse.

UPDATE:

Ok, ich habe jetzt 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 stelle dann ein, dass die Crontab aus der temporären Datei gelesen wird. Anschließend lösche ich die temporäre Datei. Hoffe das hilft.

31
beterthanlife

Wie bereits erwähnt, besteht der grundlegende Fehler bei der Einrichtung einer Crontab-Konfiguration darin, dass dies nur bei der Bereitstellung geschieht. Da der Cluster automatisch skaliert und wieder heruntergefahren wird, ist es vorzuziehen, dass der erste Server ebenfalls ausgeschaltet ist. Außerdem würde es kein Failover geben, was für mich kritisch war.

Ich recherchierte und sprach mit unserem AWS-Kontospezialisten, um die Ideen zu verbreiten und die Lösung zu bestätigen, die ich gefunden hatte. Sie können dies mit OpsWorks erreichen, obwohl es ein bisschen wie ein Haus ist, um eine Fliege zu töten. Es ist auch möglich, Data Pipeline mit Task Runner zu verwenden. Dies hat jedoch nur eingeschränkte Fähigkeiten in den Skripts, die ausgeführt werden können, und ich musste PHP - Skripts mit Zugriff auf den gesamten Code ausführen können Base. Sie können auch eine EC2-Instanz außerhalb des ElasticBeanstalk-Clusters reservieren, haben jedoch kein Failover mehr.

Hier ist also das, was ich mir ausgedacht habe, was anscheinend unkonventionell ist (wie der AWS-Vertreter kommentiert) und als Hack angesehen werden kann, aber es funktioniert und ist mit einem Fail-over solide. Ich habe eine Kodierungslösung mit dem SDK gewählt, die ich in PHP zeigen werde, obwohl Sie dieselbe Methode in jeder beliebigen Sprache verwenden können.

// 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 durch das und wie es funktioniert ... Sie rufen Skripts von Crontab aus, wie Sie es normalerweise bei jeder EC2-Instanz tun würden. Jedes Skript enthält dies am Anfang (oder enthält bei jeder Verwendung eine einzelne Datei), die ein ElasticBeanstalk-Objekt erstellt und eine Liste aller Instanzen abruft. 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 nachgesehen und die zurückgegebene Liste scheint konsistent zu sein, was technisch gesehen nur eine Minute dauern muss, da jede Instanz den geplanten cron ausführt. Wenn es sich ändert, ist es egal, da es nur für dieses kleine Fenster relevant ist.

Dies ist in keiner Weise elegant, entspricht aber unseren spezifischen Anforderungen - die Kosten sollten nicht durch einen zusätzlichen Service erhöht werden oder es muss eine dedizierte EC2-Instanz vorhanden sein, die bei einem Ausfall zu einem Failover führen würde. Unsere cron-Skripts führen Wartungsskripts aus, die in SQS abgelegt werden. Jeder Server im Cluster hilft bei der Ausführung. Zumindest kann dies eine alternative Option sein, wenn es Ihren Bedürfnissen entspricht. 

-Davey

11
user1599237

Ich habe mit einem AWS-Supportmitarbeiter gesprochen, und so haben wir das für mich gemacht. 2015 Lösung:

Erstellen Sie eine Datei in Ihrem .ebextensions-Verzeichnis mit Ihrer_Dateiname.config . In der Eingabe für die Konfigurationsdatei:

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

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

 /usr/local/bin/test_cron.sh || Ausfahrt
 echo "Cron läuft am" Datum ">> /tmp/cron_example.log
 # Jetzt erledigen Sie Aufgaben, die nur in einer Instanz ausgeführt werden sollten ...

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

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

 # Finden Sie den Namen unserer Auto Scaling Group .
 ASG = `aws ec2 description-tags --filters" Name = Ressourcen-ID, Werte = $ INSTANCE_ID "\ 
 --region $ REGION - Ausgabetext | awk '/ aws: Autoskalierung: Gruppenname/{print $ 5}' `

 # Finden Sie die erste Instanz in der Gruppe 
 FIRST = `aws autoscaling beschreiben-auto-scaling-groups --auto-scaling-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, aber es wird von cron weiterhin ausgeführt. Ihr Cron wird jetzt zweimal auf demselben Rechner ausgeführt.
  2. Wenn sich Ihre Umgebung vergrößert, gibt es mehrere Instanzen, die alle Ihr cron-Skript ausführen. Dies bedeutet, dass Ihre Mail-Shots wiederholt werden oder Ihre Datenbankarchive dupliziert werden

Problemumgehung:

  1. Stellen Sie sicher, dass ein .ebextensions-Skript, das einen cron erstellt, auch die .bak-Dateien in nachfolgenden Bereitstellungen entfernt.
  2. Besitzen Sie ein Hilfsskript, das folgende Aktionen ausführt: - Ruft die aktuelle Instanz-ID aus den Metadaten ab. - Ruft das aktuelle Auto .__ ab. Name der Skalierungsgruppe aus den EC2-Tags - Ruft die Liste der EC2-Tags ab Instanzen in dieser Gruppe, alphabetisch sortiert. - Nimmt den ersten Instanz aus dieser Liste. - Vergleicht die Instanz-ID aus Schritt 1 mit der ersten Instanz-ID aus Schritt 4 ... Ihre Cron-Skripts können dann dieses Hilfsskript verwenden, um zu bestimmen, ob sie ausgeführt werden sollen.

Vorbehalt: 

  • Die für die Beanstalk-Instanzen verwendete IAM-Rolle erfordert die Berechtigungen ec2: DescribeTags und die automatische Skalierung: DescribeAutoScalingGroups
  • Bei den ausgewählten Instanzen handelt es sich um die von Auto Scaling als InService angezeigten Instanzen. Dies bedeutet nicht notwendigerweise, dass sie vollständig hochgefahren sind und bereit sind, Ihren Cron auszuführen.

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

8
Ken

Wenn Sie Rails verwenden, können Sie den Edelstein always-elasticbeanstalk verwenden. Sie können Cron-Jobs entweder für alle oder nur für eine Instanz ausführen. Es prüft jede Minute, um sicherzustellen, dass es nur eine "Leader" -Instanz gibt, und es wird automatisch ein Server zu "Leader" befördert, wenn es keinen gibt. Dies ist erforderlich, da Elastic Beanstalk während der Bereitstellung nur das Konzept der Führungslinie hat und während des Skalierens jederzeit eine Instanz herunterfahren kann.

UPDATE Ich habe auf AWS OpsWorks umgestellt und verwalte diesen Edelstein nicht mehr. Wenn Sie mehr Funktionalität benötigen, als in den Grundlagen von Elastic Beanstalk verfügbar ist, empfehle ich Ihnen dringend, zu OpsWorks zu wechseln.

7
dignoe

Sie möchten wirklich keine Cron-Jobs auf Elastic Beanstalk ausführen. Da Sie über mehrere Anwendungsinstanzen verfügen, kann dies zu Race-Bedingungen und anderen Problemen führen. Ich habe eigentlich kürzlich darüber gebloggt (4. oder 5. Tipp weiter unten auf der Seite). Die Kurzversion: Verwenden Sie je nach Anwendung eine Jobwarteschlange wie SQS oder eine Drittanbieterlösung wie iron.io .

6
jamieb

Eine besser lesbare Lösung, die files anstelle von container_commands verwendet:

 Dateien: 
 "/etc/cron.d/my_cron":
 Modus: "000644" 
 Besitzer: root 
 Gruppe: Wurzel 
 Inhalt: | 
 # Überschreibt die Standard-E-Mail-Adresse 
 MAILTO = "[email protected]" 
 # 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: plain 
 Befehle: 
 # Sicherungsdatei löschen, die mit Elastic Beanstalk erstellt wurde. clear_cron_backup: 
 Befehl: rm -f /etc/cron.d/watson.bak

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

4
Tamlyn

Jemand wunderte sich über die automatischen Skalierungsprobleme von leader_only, wenn neue Leader auftauchten. Ich kann anscheinend nicht herausfinden, wie ich auf ihre Kommentare antworten soll, aber siehe diesen Link: http://blog.paulopoiati.com/2013/08/25/running-cron-in-elastic-beanstalk-auto- Skalierungsumgebung/

2
Random5000
1
poiati

Mein 1-Cent-Beitrag für 2018

Hier ist der richtige Weg (mit Django/python und Django_crontab app): 

in .ebextensions Ordner erstellen Sie eine Datei wie diese 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_commands statt commands sein.

1
Ronaldo Bahia

hier ist eine Korrektur, wenn 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

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

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

0
foxybagga

Verwenden Sie den Instanzschutz, um zu steuern, ob Auto Scaling eine bestimmte Instanz beim Skalieren in beenden kann. Sie können die Instanzschutzeinstellung für eine Auto Scaling-Gruppe oder eine einzelne Auto Scaling-Instanz aktivieren. Wenn Auto Scaling eine Instanz startet, erbt die Instanz die Instanzschutzeinstellung der Gruppe Auto Scaling. 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
Dele

2017: Wenn Sie Laravel5 + verwenden

Du brauchst nur 2 Minuten um es zu konfigurieren:

  • erstellen Sie eine Arbeiterebene
  • installieren Sie Laravel-AWS-Arbeiter

    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 ein Teil Ihres Repos sein .__ oder Sie können diese Datei direkt vor der Bereitstellung in EB hinzufügen. Wichtig ist, dass diese Datei zu diesem Zeitpunkt vorhanden ist von Bereitstellung):

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

Das ist es!

Ihre gesamte Aufgabe in App\Console\Kernel wird jetzt 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

0
Sebastien Horin

Wir haben also eine Weile mit diesem Problem zu kämpfen, und nach einigen Diskussionen mit einem AWS-Mitarbeiter habe ich endlich die Lösung gefunden, die meiner Meinung nach die beste Lösung ist.

Die Verwendung einer Worker-Ebene mit cron.yaml ist definitiv die einfachste Lösung. Was in der Dokumentation jedoch nicht klar ist, ist, dass dadurch der Job an das end der SQS-Warteschlange gestellt wird, die Sie zum Ausführen Ihrer Jobs verwenden. Wenn Ihre Cron-Jobs zeitabhängig sind (wie viele), ist dies nicht akzeptabel, da dies von der Größe der Warteschlange abhängt. Eine Option ist die Verwendung einer vollständig separaten Umgebung, nur um Cron-Jobs auszuführen, aber ich denke, das ist übertrieben.

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

Instanzschutz kann auch zu Problemen führen - was ist, wenn diese Instanz eingesperrt/eingefroren wird?

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

Wir können die vorhandene Funktionalität wiederverwenden, anstatt zu versuchen, unsere eigene Funktionalität 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 problemlos an jede andere Sprache anpassen, die das AWS SDK enthält. Im Wesentlichen wird der aktuelle Anführer überprüft und anschließend der Zustand überprüft, um sicherzustellen, dass er in einem guten Zustand ist. Es wird so lange wiederholt, bis sich ein aktueller Anführer in einem guten Zustand befindet und wenn die aktuelle Instanz der Anführer ist, führen Sie den Job aus.

0
Cidolfas

Basierend auf den Grundsätzen der Antwort von user1599237 , bei der Sie die Cron-Jobs auf allen Instanzen ausführen lassen, dann aber zu Beginn der Jobs bestimmen, ob sie ausgeführt werden dürfen, habe ich eine andere Lösung getroffen.

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

Es hat keine Nachteile, nur positive:

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

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

Die folgende Lösung wurde 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->system ist ein eigenes Modul. Ersetzen Sie es durch das, was Sie gerade 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 von mir verwendete Datenbankschema:

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`)
)
0
TheStoryCoder

Ich hatte eine andere Lösung dafür, wenn eine PHP-Datei durch cron ausgeführt werden muss und wenn Sie NAT -Instanzen gesetzt hätten, können Sie cronjob auf NAT setzen und die PHP-Datei über wget ausführen.

0
prasoon