AWS Elastic Beanstalk, che esegue un cronjob


89

Vorrei sapere se esiste un modo per impostare un cronjob / task da eseguire ogni minuto. Attualmente una qualsiasi delle mie istanze dovrebbe essere in grado di eseguire questa attività.

Questo è quello che ho provato a fare nei file di configurazione senza successo:

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

Non sono proprio sicuro che questo sia il modo corretto per farlo

Qualche idea?


1
Il comando è giusto? Voglio dire ... potrebbe essere: command: echo "* / 1 * * * * root php /etc/httpd/myscript.php"> /etc/cron.d/something Ad ogni modo, ti suggerirei di usare il leader_only flag, altrimenti tutte le macchine attiveranno questo cron job contemporaneamente
aldrinleal

Sì! sicuramente usando il flag leader_only, proverò a cambiare il comando.
Onema

Risposte:


96

Ecco come ho aggiunto un cron job a Elastic Beanstalk:

Crea una cartella nella radice della tua applicazione chiamata .ebextensions se non esiste già. Quindi crea un file di configurazione all'interno della cartella .ebextensions. Userò example.config a scopo illustrativo. Quindi aggiungilo a 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

Questo è un file di configurazione YAML per Elastic Beanstalk. Assicurati, quando lo copi nel tuo editor di testo, che il tuo editor di testo utilizzi spazi anziché tabulazioni. Altrimenti riceverai un errore YAML quando lo invii a EB.

Quindi ciò che fa è creare un comando chiamato 01_some_cron_job. I comandi vengono eseguiti in ordine alfabetico, quindi 01 si assicura che venga eseguito come primo comando.

Il comando quindi prende il contenuto di un file chiamato some_cron_job.txt e lo aggiunge a un file chiamato some_cron_job in /etc/cron.d.

Il comando modifica quindi le autorizzazioni sul file /etc/cron.d/some_cron_job.

La chiave leader_only garantisce che il comando venga eseguito solo sull'istanza ec2 considerata leader. Piuttosto che eseguire su ogni istanza ec2 che potresti avere in esecuzione.

Quindi crea un file chiamato some_cron_job.txt all'interno della cartella .ebextensions. Inserirai i tuoi cron job in questo file.

Quindi per esempio:

# 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

Quindi questo cron job verrà eseguito ogni minuto di ogni ora di ogni giorno come utente root e scarterà l'output in / dev / null. / usr / bin / php è il percorso per php. Quindi sostituisci some-php-script-here con il percorso del tuo file php. Questo ovviamente presuppone che il tuo cron job debba eseguire un file PHP.

Inoltre, assicurati che il file some_cron_job.txt abbia una nuova riga alla fine del file proprio come dice il commento. Altrimenti cron non verrà eseguito.

Aggiornamento: si verifica un problema con questa soluzione quando Elastic Beanstalk aumenta le istanze. Ad esempio, supponiamo di avere un'istanza con il processo cron in esecuzione. Ottieni un aumento del traffico, quindi Elastic Beanstalk ti ridimensiona fino a due istanze. Il leader_only ti assicurerà di avere un solo cron job in esecuzione tra le due istanze. Il tuo traffico diminuisce e Elastic Beanstalk ti riduce a un'istanza. Ma invece di terminare la seconda istanza, Elastic Beanstalk termina la prima istanza che era il leader. Ora non hai cron job in esecuzione poiché erano in esecuzione solo sulla prima istanza terminata. Vedere i commenti di seguito.

Aggiornamento 2: lo chiarisco solo dai commenti seguenti: AWS ora ha la protezione contro la chiusura automatica delle istanze. Abilitalo sulla tua istanza leader e sei a posto. - Nicolás Arévalo 28 ottobre 16 alle 9:23


12
Sto usando il tuo suggerimento da un po 'di tempo e di recente ho riscontrato un problema in cui in qualche modo il leader è cambiato, risultando in più istanze che eseguono cron. Per risolvere questo problema, ho cambiato 01_some_cron_jobper 02_some_cron_jobe ha aggiunto 01_remove_cron_jobscon la seguente: command: "rm /etc/cron.d/cron_jobs || exit 0". In questo modo, dopo ogni distribuzione solo il leader avrà il cron_jobsfile. Se i leader cambiano, puoi semplicemente ridistribuirli e i cron saranno fissati per funzionare ancora una volta.
Willem Renzema

4
Suggerirei di non fare affidamento sulla leader_onlyproprietà. Viene utilizzato solo durante la distribuzione e se si ridimensiona o se l'istanza "leader" non riesce, è necessario fare riferimento
arnaslu

2
Non farlo. È troppo inaffidabile. L'unico modo per farlo funzionare è eseguire una microistanza ed eseguire cron job da lì utilizzando CURL. Ciò garantisce che solo un'istanza lo esegua e che il leader su cui sono installati i cron non venga terminato.
Ben Sinclair

1
Ho provato a risolvere questo problema con un piccolo script ruby, lo puoi trovare qui: github.com/SocialbitGmbH/AWSBeanstalkLeaderManager
Thomas Kekeisen

8
AWS ora dispone della protezione contro la chiusura automatica delle istanze. Abilitalo sulla tua istanza leader e sei a posto.
Nicolás Arévalo

58

Questo è il modo ufficiale per farlo ora (2015+). Prova prima questo, è di gran lunga il metodo più semplice attualmente disponibile e anche il più affidabile.

Secondo i documenti attuali, è possibile eseguire attività periodiche sul cosiddetto livello di lavoro .

Citando la documentazione:

AWS Elastic Beanstalk supporta attività periodiche per i livelli dell'ambiente di lavoro in ambienti che eseguono una configurazione predefinita con uno stack di soluzioni che contiene "v1.2.0" nel nome del contenitore. Devi creare un nuovo ambiente.

Interessante anche la parte su cron.yaml :

Per richiamare attività periodiche, il bundle di origine dell'applicazione deve includere un file cron.yaml a livello di root. Il file deve contenere informazioni sulle attività periodiche che si desidera pianificare. Specificare queste informazioni utilizzando la sintassi crontab standard.

Aggiornamento: siamo riusciti a ottenere questo lavoro. Ecco alcuni importanti trucchi della nostra esperienza (piattaforma Node.js):

  • Quando si utilizza il file cron.yaml , assicurarsi di disporre di awsebcli più recente , poiché le versioni precedenti non funzioneranno correttamente.
  • È anche fondamentale creare un nuovo ambiente (almeno nel nostro caso lo era), non solo clonare quello vecchio.
  • Se vuoi assicurarti che CRON sia supportato sulla tua istanza del livello di lavoro EC2, ssh in esso ( eb ssh) ed esegui cat /var/log/aws-sqsd/default.log. Dovrebbe riportare come aws-sqsd 2.0 (2015-02-18). Se non hai la versione 2.0, qualcosa è andato storto durante la creazione del tuo ambiente e devi crearne uno nuovo come indicato sopra.

2
A proposito di cron.yaml, c'è un fantastico post sul blog: Esecuzione di cron job su Amazon Web Services (AWS) Elastic Beanstalk - Medium
jwako

5
Grazie per questo - domanda da principiante - Ho bisogno che il mio cron controlli il database della mia app web due volte all'ora per i prossimi eventi del calendario e invii un'e-mail di promemoria quando lo fa. Qual è la migliore configurazione qui, dovrei fare in modo che l'URL cron.yaml punti a un percorso sulla mia app Web? O devo concedere alla mia app ambiente di lavoro l'accesso al database? Così poco là fuori su questo!
cristiano

5
@christian Per come lo facciamo, abbiamo la stessa app in esecuzione in due ambienti diversi (quindi non è necessaria alcuna configurazione speciale): worker e server web comune. L'ambiente di lavoro ha alcuni percorsi speciali abilitati impostando una variabile ENV che la nostra app cerca. In questo modo, puoi impostare percorsi speciali solo per i lavoratori nel tuo cron.yaml pur avendo il lusso di una base di codice condivisa con la normale app. La tua app di lavoro può accedere facilmente alle stesse risorse del server web: database, modelli, ecc.
xaralis

1
@JaquelinePassos v1.2.0 è la versione stack della soluzione. Dovrebbe consentire di scegliere quale versione dello stack di soluzioni si desidera creare durante la creazione di un nuovo ambiente. Qualunque cosa più recente della v1.2.0 dovrebbe funzionare. Per quanto riguarda l'URL, dovrebbe essere l'URL su cui è in ascolto l'applicazione, non un percorso di file. Non è possibile eseguire comandi di gestione Django, fa solo richieste HTTP.
xaralis

4
Una cosa che non mi è chiara è se esiste un modo per evitare di dover allocare una macchina EC2 aggiuntiva solo per eseguire i cron job tramite cron.yaml. Idealmente dovrebbe essere eseguito sulla stessa macchina di quella che sta servendo le richieste HTTP (cioè livello web).
Wenzel Jakob

31

Per quanto riguarda la risposta di jamieb, e come menzionato da alrdinleal, puoi utilizzare la proprietà "leader_only" per assicurarti che solo un'istanza EC2 esegua il cron job.

Citazione tratta da http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html :

puoi usare leader_only. Un'istanza viene scelta come leader in un gruppo Auto Scaling. Se il valore leader_only è impostato su true, il comando viene eseguito solo sull'istanza contrassegnata come leader.

Sto cercando di ottenere una cosa simile sul mio eb, quindi aggiornerò il mio post se lo risolvo.

AGGIORNARE:

Ok, ora ho cronjob funzionanti usando la seguente configurazione eb:

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

In sostanza, creo un file temporaneo con cronjobs e poi imposto il crontab per leggere dal file temporaneo, quindi elimino il file temporaneo in seguito. Spero che questo ti aiuti.


3
Come assicurereste che l'istanza che esegue questo crontab non venga terminata dal ridimensionamento automatico? Per impostazione predefinita, termina l'istanza più vecchia.
Sebastien

1
È un problema che non sono ancora riuscito a risolvere. Mi sembra un difetto nella funzionalità di Amazon che i comandi leader_only non vengano applicati a un nuovo leader quando quello corrente viene terminato da EB. Se ti viene in mente qualcosa, condividilo!
beterthanlife

7
Così ho (finalmente) scoperto come impedire che il leader venga terminato con la scalabilità automatica - politiche di terminazione con scalabilità automatica personalizzate. Vedi docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/…
beterthanlife

1
@Nate Probabilmente l'hai già capito, ma in base alla mia lettura dell'ordine in cui vengono eseguiti, i "comandi" vengono eseguiti prima di "container_commands" in modo da creare il file, quindi eliminarlo, quindi provare a eseguire il crontab .
clearf

1
@Sebastien per mantenere l'intance più vecchia, ecco cosa faccio: 1 - cambia la protezione di terminazione dell'intance in ENBABLE. 2 - Vai su Auto Scale Group e trova il tuo ID ambiente EBS, fai clic su MODIFICA e cambia le politiche di terminazione in "NewestInstance"
Ronaldo Bahia

12

Come accennato in precedenza, il difetto fondamentale nello stabilire una configurazione crontab è che avviene solo durante la distribuzione. Quando il cluster viene ridimensionato automaticamente e quindi nuovamente disattivato, è preferibile essere anche il primo server spento. Inoltre non ci sarebbe stato alcun failover, che per me è stato fondamentale.

Ho svolto alcune ricerche, poi ho parlato con il nostro specialista dell'account AWS per far rimbalzare le idee e convalidare la soluzione che avevo trovato. Puoi farlo con OpsWorks , anche se è un po 'come usare una casa per uccidere una mosca. È anche possibile utilizzare Data Pipeline con Task Runner , ma ha capacità limitate negli script che può eseguire, e dovevo essere in grado di eseguire script PHP, con accesso all'intera base di codice. Puoi anche dedicare un'istanza EC2 al di fuori del cluster ElasticBeanstalk, ma non avrai più il failover.

Quindi ecco cosa mi è venuto in mente, che apparentemente non è convenzionale (come ha commentato il rappresentante di AWS) e può essere considerato un hack, ma funziona ed è solido con il failover. Ho scelto una soluzione di codifica utilizzando l'SDK, che mostrerò in PHP, sebbene tu possa fare lo stesso metodo in qualsiasi lingua tu preferisca.

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

Quindi, esaminando questo e come funziona ... Chiami gli script da crontab come faresti normalmente su ogni istanza EC2. Ogni script include questo all'inizio (o include un singolo file per ciascuno, come lo uso), che stabilisce un oggetto ElasticBeanstalk e recupera un elenco di tutte le istanze. Utilizza solo il primo server nell'elenco e controlla se corrisponde a se stesso, che se lo fa continua, altrimenti muore e si chiude. Ho controllato e l'elenco restituito sembra essere coerente, che tecnicamente deve essere coerente solo per un minuto circa, poiché ogni istanza esegue il cron pianificato. Se dovesse cambiare, non avrebbe importanza, poiché di nuovo è rilevante solo per quella piccola finestra.

Questo non è affatto elegante, ma si adatta alle nostre esigenze specifiche, che non erano aumentare i costi con un servizio aggiuntivo o avere un'istanza EC2 dedicata, e avrebbe avuto un failover in caso di guasto. I nostri script cron eseguono script di manutenzione che vengono inseriti in SQS e ogni server nel cluster aiuta l'esecuzione. Almeno questo potrebbe darti un'opzione alternativa se si adatta alle tue esigenze.

-Davey


Ho scoperto che php_uname ('n') restituisce il nome DNS privato (ad esempio ip-172.24.55.66), che non è l'ID istanza che stai cercando. Invece di usare php_uname (), ho finito per usare questo: $instanceId = file_get_contents("http://instance-data/latest/meta-data/instance-id"); Quindi usa semplicemente quella $ instanceId var per fare il confronto.
Valorum

1
C'è qualche garanzia che l'array Instances presenti lo stesso ordine su ogni chiamata Describe? Suggerirei di estrarre il campo ['Id'] di ciascuna voce in un array e di ordinarli in PHP, prima di verificare se la prima voce ordinata è il tuo instanceId corrente.
Gabriel

Sulla base di questa risposta ho creato questa soluzione: stackoverflow.com/questions/14077095/… - è molto simile ma NON ha possibilità di doppia esecuzione.
TheStoryCoder

11

Ho parlato con un agente di supporto AWS ed è così che abbiamo fatto in modo che funzionasse per me. Soluzione 2015:

Crea un file nella tua directory .ebextensions con your_file_name.config. Nell'input del file di configurazione:

File:
  "/etc/cron.d/cron_example":
    modalità: "000644"
    proprietario: root
    gruppo: root
    contenuto: |
      * * * * * root /usr/local/bin/cron_example.sh

  "/usr/local/bin/cron_example.sh":
    modalità: "000755"
    proprietario: root
    gruppo: root
    contenuto: |
      #! / bin / bash

      /usr/local/bin/test_cron.sh || Uscita
      echo "Cron in esecuzione a" `date` >> /tmp/cron_example.log
      # Ora esegui attività che dovrebbero essere eseguite solo su 1 istanza ...

  "/usr/local/bin/test_cron.sh":
    modalità: "000755"
    proprietario: root
    gruppo: root
    contenuto: |
      #! / bin / bash

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

      # Trova il nome del nostro gruppo Auto Scaling.
      ASG = `aws ec2 description-tags --filters" Name = resource-id, Values ​​= $ INSTANCE_ID "\
        --region $ REGION --output text | awk '/ aws: autoscaling: groupName / {print $ 5}' `

      # Trova la prima istanza nel gruppo
      FIRST = `aws autoscaling descrivere-auto-scaling-groups --auto-scaling-group-names $ ASG \
        --region $ REGION --output text | awk '/ InService $ / {print $ 4}' | ordina | testa -1`

      # Verifica se sono la stessa cosa.
      ["$ FIRST" = "$ INSTANCE_ID"]

comandi:
  rm_old_cron:
    comando: "rm * .bak"
    cwd: "/etc/cron.d"
    ignoreErrors: true

Questa soluzione presenta 2 inconvenienti:

  1. Nelle distribuzioni successive, Beanstalk rinomina lo script cron esistente come .bak, ma cron lo eseguirà comunque. Il tuo Cron ora viene eseguito due volte sulla stessa macchina.
  2. Se il tuo ambiente si ingrandisce, ottieni diverse istanze, tutte in esecuzione con lo script cron. Ciò significa che i messaggi di posta vengono ripetuti o gli archivi del database duplicati

Soluzione:

  1. Assicurati che qualsiasi script .ebextensions che crea un cron rimuova anche i file .bak nelle distribuzioni successive.
  2. Avere uno script di supporto che esegue le seguenti operazioni: - Ottiene l'ID istanza corrente dai metadati - Ottiene il nome del gruppo Auto Scaling corrente dai tag EC2 - Ottiene l'elenco delle istanze EC2 in quel gruppo, in ordine alfabetico. - Prende la prima istanza da quella lista. - Confronta l'ID istanza del passaggio 1 con il primo ID istanza del passaggio 4. Gli script cron possono quindi utilizzare questo script di supporto per determinare se devono essere eseguiti.

Avvertimento:

  • Il ruolo IAM utilizzato per le istanze Beanstalk richiede ec2: DescribeTags e autoscaling: autorizzazioni DescribeAutoScalingGroups
  • Le istanze scelte sono quelle mostrate come InService by Auto Scaling. Questo non significa necessariamente che siano completamente avviati e pronti per eseguire il tuo cron.

Non è necessario impostare i ruoli IAM se si utilizza il ruolo beanstalk predefinito.


7

Se stai usando Rails, puoi usare la gemma ogni volta che elasticbeanstalk . Ti consente di eseguire cron job su tutte le istanze o solo su una. Controlla ogni minuto per assicurarsi che ci sia solo un'istanza "leader" e promuoverà automaticamente un server a "leader" se non ce ne sono. Ciò è necessario poiché Elastic Beanstalk ha solo il concetto di leader durante la distribuzione e può chiudere qualsiasi istanza in qualsiasi momento durante il ridimensionamento.

AGGIORNAMENTO Sono passato a utilizzare AWS OpsWorks e non manterrò più questo gioiello. Se hai bisogno di più funzionalità di quelle disponibili nelle basi di Elastic Beanstalk, ti ​​consiglio vivamente di passare a OpsWorks.


Ti dispiacerebbe dirci come hai risolto il problema utilizzando OpsWorks? Stai eseguendo livelli personalizzati che fanno i cron-job?
Tommie

Sì, ho un livello admin / cron che funziona solo su un server. Ho impostato un libro di cucina personalizzato che contiene tutti i miei lavori di cron. AWS ha una guida su docs.aws.amazon.com/opsworks/latest/userguide/… .
dignoe

@dignoe se assegni un server per eseguire cron job utilizzando OpsWorks, la stessa cosa utilizzando Elastic Beanstalk, posso utilizzare un ambiente con un server per eseguire cron job. Anche con Load Balancer, le istanze max e min impostate su uno, per conservare sempre almeno un'istanza del server.
Jose Nobile

6

Non vuoi davvero eseguire cron job su Elastic Beanstalk. Poiché avrai più istanze dell'applicazione, ciò può causare condizioni di competizione e altri problemi dispari. In realtà ho recentemente scritto su questo blog (4 ° o 5 ° suggerimento in fondo alla pagina). La versione corta: A seconda dell'applicazione, utilizzare una coda di lavoro come SQS o una soluzione di terze parti come iron.io .


SQS non garantisce che il codice verrà eseguito una sola volta. Mi piace il sito iron.io, lo controllerò.
Nathan H

Inoltre nel tuo post sul blog consigli di utilizzare InnoDB su RDS. Uso una tabella su RDS per memorizzare le mie attività e utilizzo la funzione "SELEZIONA ... PER AGGIORNAMENTO" di InnoDB per assicurarmi che solo un server esegua tali attività. In che modo la tua app contatta SQS senza un cron job o l'interazione dell'utente?
James Alday

1
@JamesAlday Questa domanda SO è piuttosto vecchia. Da quando ho scritto il commento sopra, AWS ha introdotto un modo elegante per gestire i cron job su Elastic Beanstalk eleggendo uno dei server in esecuzione come master. Detto questo, sembra che tu stia abusando di cron + MySQL come coda di lavoro. Tuttavia, avrei bisogno di sapere molto sulla tua app prima di poter offrire consigli concreti.
jamieb

Ho uno script che viene eseguito tramite cron che controlla una tabella per i lavori da eseguire. L'utilizzo delle transazioni impedisce a più server di eseguire lo stesso lavoro. Ho esaminato SQS ma hai bisogno di un server master che esegua tutti gli script invece di distribuirlo e devi ancora scrivere la logica per assicurarti di non eseguire lo stesso script più volte. Ma sono ancora confuso su come eseguire le attività senza l'interazione dell'utente o cron: cosa fa sì che la tua app esegua le attività in coda?
James Alday

4

2017: se stai usando Laravel5 +

Hai solo bisogno di 2 minuti per configurarlo:

  • creare un livello di lavoro
  • installa laravel-aws-worker

    composer require dusterio/laravel-aws-worker

  • aggiungi un cron.yaml alla cartella principale:

Aggiungi cron.yaml alla cartella principale della tua applicazione (questo può essere una parte del tuo repository o potresti aggiungere questo file subito prima della distribuzione su EB - l'importante è che questo file sia presente al momento della distribuzione):

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

Questo è tutto!

Tutte le tue attività App\Console\Kernelverranno ora eseguite

Istruzioni e spiegazioni dettagliate: https://github.com/dusterio/laravel-aws-worker

Come scrivere attività all'interno di Laravel: https://laravel.com/docs/5.4/scheduling


3

Una soluzione più leggibile che utilizza filesinvece di container_commands:

File:
  "/etc/cron.d/my_cron":
    modalità: "000644"
    proprietario: root
    gruppo: root
    contenuto: |
      # sovrascrive l'indirizzo email predefinito
      MAILTO = "esempio@gmail.com"
      # esegui un comando Symfony ogni cinque minuti (come ec2-user)
      * / 10 * * * * ec2-user / usr / bin / php / var / app / current / app / console fare: qualcosa
    codifica: semplice
comandi:
  # elimina il file di backup creato da Elastic Beanstalk
  clear_cron_backup:
    comando: rm -f /etc/cron.d/watson.bak

Nota che il formato differisce dal solito formato crontab in quanto specifica l'utente come eseguire il comando.


Un problema qui è che le istanze EC2 di Elastic Beanstalk non hanno i servizi SMTP impostati per impostazione predefinita, quindi l'opzione MAILTO qui potrebbe non funzionare.
Justin Finkelstein

3

Il mio 1 centesimo di contributo per il 2018

Ecco il modo giusto per farlo (usando django/pythone django_crontabapp):

all'interno della .ebextensionscartella crea un file come questo 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

Deve essere container_commandsinvece dicommands



2

L'ultimo esempio di Amazon è il più semplice ed efficiente (attività periodiche):

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

dove crei un livello di lavoro separato per eseguire i tuoi cron job. Crea il file cron.yaml e posizionalo nella cartella principale. Un problema che ho avuto (dopo che cron non sembrava essere in esecuzione) è stato scoprire che il mio CodePipeline non aveva l'autorità per eseguire una modifica dynamodb. Sulla base di ciò dopo aver aggiunto l'accesso FullDynamoDB in IAM -> ruoli -> tua pipeline e ridistribuito (beanstalk elastico) ha funzionato perfettamente.



1

Quindi abbiamo lottato con questo per un po 'e dopo qualche discussione con un rappresentante AWS sono finalmente arrivato a quella che penso sia la soluzione migliore.

L'utilizzo di un livello di lavoro con cron.yaml è sicuramente la soluzione più semplice. Tuttavia, ciò che la documentazione non chiarisce è che questo metterà il lavoro alla fine della coda SQS che stai utilizzando per eseguire effettivamente i tuoi lavori. Se i tuoi cron job sono sensibili al tempo (come molti lo sono), questo non è accettabile, poiché dipenderebbe dalla dimensione della coda. Un'opzione è usare un ambiente completamente separato solo per eseguire cron job, ma penso che sia eccessivo.

Alcune delle altre opzioni, come controllare se sei la prima istanza nell'elenco, non sono ideali. Cosa succede se l'attuale prima istanza è in fase di chiusura?

La protezione delle istanze può anche presentare problemi: cosa succede se quell'istanza viene bloccata / congelata?

Ciò che è importante capire è come AWS stesso gestisce la funzionalità cron.yaml. C'è un demone SQS che utilizza una tabella Dynamo per gestire "l'elezione del leader". Scrive frequentemente su questa tabella e se il leader corrente non ha scritto in breve tempo, l'istanza successiva assumerà il ruolo di leader. Questo è il modo in cui il daemon decide quale istanza attivare il lavoro nella coda SQS.

Possiamo riutilizzare la funzionalità esistente invece di provare a riscrivere la nostra. Puoi vedere la soluzione completa qui: https://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27

È in Ruby, ma puoi adattarlo facilmente a qualsiasi altro linguaggio che disponga dell'SDK AWS. In sostanza, controlla l'attuale leader, quindi controlla lo stato per assicurarsi che sia in buono stato. Continuerà fino a quando non ci sarà un leader corrente in buono stato e, se l'istanza corrente è il leader, eseguire il lavoro.


0

Per controllare se Auto Scaling può terminare una particolare istanza durante il ridimensionamento, utilizza la protezione dell'istanza. Puoi abilitare l'impostazione di protezione dell'istanza su un gruppo Auto Scaling o su una singola istanza Auto Scaling. Quando Auto Scaling avvia un'istanza, l'istanza eredita l'impostazione di protezione dell'istanza del gruppo Auto Scaling. Puoi modificare l'impostazione di protezione dell'istanza per un gruppo Auto Scaling o un'istanza Auto Scaling in qualsiasi momento.

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


0

Avevo un'altra soluzione a questo se un file php deve essere eseguito tramite cron e se avessi impostato delle istanze NAT, puoi mettere cronjob sull'istanza NAT ed eseguire il file php tramite wget.


0

ecco una soluzione in caso tu voglia farlo in PHP. Hai solo bisogno di cronjob.config nella tua cartella .ebextensions per farlo funzionare in questo modo.

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

envvars ottiene le variabili d'ambiente per i file. È possibile eseguire il debug dell'output su tmp / sendemail.log come sopra.

Spero che questo aiuti qualcuno perché sicuramente ci ha aiutato!


0

Basato sui principi della risposta dell'utente1599237 , in cui si lascia che i cron job eseguiti su tutte le istanze ma poi all'inizio dei job si determina se devono essere consentiti l'esecuzione, ho creato un'altra soluzione.

Invece di guardare le istanze in esecuzione (e dover memorizzare la tua chiave e il tuo segreto AWS) sto usando il database MySQL a cui mi sto già connettendo da tutte le istanze.

Non ha svantaggi, solo aspetti positivi:

  • nessuna istanza o spese extra
  • soluzione solida come una roccia - nessuna possibilità di doppia esecuzione
  • scalabile: funziona automaticamente man mano che le istanze vengono ridimensionate
  • failover: funziona automaticamente nel caso in cui un'istanza abbia un errore

In alternativa, puoi anche utilizzare un filesystem comunemente condiviso (come AWS EFS tramite il protocollo NFS) invece di un database.

La seguente soluzione è creata all'interno del framework PHP Yii ma puoi adattarla facilmente per un altro framework e linguaggio. Anche il gestore delle eccezioni Yii::$app->systemè un mio modulo. Sostituiscilo con quello che stai utilizzando.

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

Questo è lo schema del database che sto usando:

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`)
)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.