AWS Elastic Beanstalk, prowadzący cronjob

89

Chciałbym wiedzieć, czy istnieje sposób na skonfigurowanie cronjob / task do wykonywania co minutę. Obecnie każda z moich instancji powinna mieć możliwość uruchomienia tego zadania.

Oto, co próbowałem zrobić w plikach konfiguracyjnych bez powodzenia:

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

Nie jestem pewien, czy jest to właściwy sposób

Jakieś pomysły?

Onema
źródło
1
Czy polecenie jest właściwe? To znaczy ... może to być: polecenie: echo "* / 1 * * * * root php /etc/httpd/myscript.php"> /etc/cron.d/something W każdym razie sugerowałbym użycie flaga Leader_only, w przeciwnym razie wszystkie maszyny uruchomią to zadanie crona naraz
aldrinleal,
Tak! zdecydowanie używając flagi Leader_only, spróbuję zmienić polecenie.
Onema

Odpowiedzi:

97

Oto jak dodałem zadanie crona do Elastic Beanstalk:

Utwórz folder w katalogu głównym aplikacji o nazwie .ebextensions, jeśli jeszcze nie istnieje. Następnie utwórz plik konfiguracyjny w folderze .ebextensions. W celach ilustracyjnych użyję example.config. Następnie dodaj to do 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

To jest plik konfiguracyjny YAML dla Elastic Beanstalk. Podczas kopiowania tego do edytora tekstu upewnij się, że używa on spacji zamiast tabulatorów. W przeciwnym razie otrzymasz błąd YAML, gdy przekażesz to do EB.

Więc to, co to robi, to utworzenie polecenia o nazwie 01_some_cron_job. Polecenia są uruchamiane w kolejności alfabetycznej, więc 01 upewnia się, że jest uruchomione jako pierwsze polecenie.

Następnie polecenie pobiera zawartość pliku o nazwie some_cron_job.txt i dodaje ją do pliku o nazwie some_cron_job w /etc/cron.d.

Następnie polecenie zmienia uprawnienia w pliku /etc/cron.d/some_cron_job.

Klucz Leader_only zapewnia, że ​​polecenie zostanie uruchomione tylko na instancji ec2, która jest uważana za lidera. Zamiast uruchamiać na każdej instancji ec2, którą możesz mieć uruchomioną.

Następnie utwórz plik o nazwie some_cron_job.txt w folderze .ebextensions. W tym pliku umieścisz swoje zadania cron.

Na przykład:

# 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

Więc to zadanie crona będzie uruchamiane co minutę o każdej godzinie każdego dnia jako użytkownik root i odrzuca dane wyjściowe do / dev / null. / usr / bin / php to ścieżka do pliku php. Następnie zamień część-php-script-here na ścieżkę do twojego pliku php. To oczywiście zakłada, że ​​twoje zadanie cron musi uruchamiać plik PHP.

Upewnij się również, że plik some_cron_job.txt ma znak nowej linii na końcu pliku, tak jak mówi komentarz. W przeciwnym razie cron nie będzie działać.

Aktualizacja: występuje problem z tym rozwiązaniem, gdy Elastic Beanstalk skaluje Twoje instancje. Na przykład, powiedzmy, że masz jedno wystąpienie z uruchomionym zadaniem cron. Zyskujesz wzrost ruchu, więc Elastic Beanstalk skaluje Cię do dwóch instancji. Leader_only zapewni, że między dwiema instancjami będzie działać tylko jedno zadanie cron. Twój ruch spada, a Elastic Beanstalk skaluje Cię do jednego wystąpienia. Ale zamiast zakończyć drugą instancję, Elastic Beanstalk kończy pierwszą instancję, która była liderem. Nie masz teraz uruchomionych żadnych zadań cron, ponieważ działały one tylko w pierwszej instancji, która została zakończona. Zobacz komentarze poniżej.

Aktualizacja 2: Po prostu wyjaśniam to na podstawie poniższych komentarzy: AWS ma teraz ochronę przed automatycznym zakończeniem instancji. Po prostu włącz go na swojej instancji lidera i gotowe. - Nicolás Arévalo 28 października 2016 o 9:23

Anarchtica
źródło
12
Korzystam z Twojej sugestii od jakiegoś czasu i ostatnio napotkałem problem polegający na tym, że w jakiś sposób lider się przełączył, co spowodowało wiele instancji uruchomienia crona. Aby rozwiązać ten problem, zmieniłem 01_some_cron_jobsię 02_some_cron_jobi dodał 01_remove_cron_jobsz poniższych sytuacji: command: "rm /etc/cron.d/cron_jobs || exit 0". W ten sposób po każdym wdrożeniu tylko lider będzie miał cron_jobsplik. Jeśli przywódcy się zmienią, możesz po prostu rozmieścić ponownie, a crony zostaną naprawione tak, aby działały jeszcze raz.
Willem Renzema
4
Sugerowałbym, aby nie polegać na leader_onlywłasności. Jest używany tylko podczas wdrażania i jeśli
skalujesz w
2
Nie rób tego. To zbyt niewiarygodne. Jedynym sposobem, w jaki udało mi się to uruchomić, jest uruchomienie instancji mikro i uruchomienie z niej zadań cron przy użyciu CURL. Gwarantuje to, że uruchomi go tylko jedna instancja, a lider, który ma zainstalowane crony, nie zostanie zakończony.
Ben Sinclair
1
Próbowałem to naprawić za pomocą małego rubinowego skryptu, możesz go znaleźć tutaj: github.com/SocialbitGmbH/AWSBeanstalkLeaderManager
Thomas Kekeisen
8
AWS ma teraz ochronę przed automatycznym zakończeniem instancji. Po prostu włącz go na swojej instancji lidera i gotowe.
Nicolás Arévalo
58

To jest oficjalny sposób, aby to zrobić teraz (2015+). Spróbuj najpierw tego, jest to zdecydowanie najłatwiejsza obecnie dostępna metoda, a także najbardziej niezawodna.

Zgodnie z obecnymi dokumentami można uruchamiać okresowe zadania na tak zwanym poziomie pracownika .

Cytując dokumentację:

AWS Elastic Beanstalk obsługuje okresowe zadania dla warstw środowiska roboczego w środowiskach ze wstępnie zdefiniowaną konfiguracją ze stosem rozwiązań zawierającym „v1.2.0” w nazwie kontenera. Musisz stworzyć nowe środowisko.

Interesująca jest również część dotycząca cron.yaml :

Aby wywoływać zadania okresowe, pakiet źródłowy aplikacji musi zawierać plik cron.yaml na poziomie głównym. Plik musi zawierać informacje o zadaniach okresowych, które chcesz zaplanować. Podaj te informacje, używając standardowej składni crontab.

Aktualizacja: udało nam się uzyskać tę pracę. Oto kilka ważnych problemów z naszego doświadczenia (platforma Node.js):

  • Korzystając z pliku cron.yaml , upewnij się, że masz najnowszą wersję awsebcli , ponieważ starsze wersje nie będą działać poprawnie.
  • Niezbędne jest też stworzenie nowego środowiska (przynajmniej w naszym przypadku tak było), a nie tylko klonowanie starego.
  • Jeśli chcesz się upewnić, że CRON jest obsługiwany w Twojej instancji EC2 Worker Tier, ssh do niego ( eb ssh) i uruchom cat /var/log/aws-sqsd/default.log. Powinien zgłosić się jako aws-sqsd 2.0 (2015-02-18). Jeśli nie masz wersji 2.0, coś poszło nie tak podczas tworzenia środowiska i musisz utworzyć nowe, jak opisano powyżej.
xaralis
źródło
2
O cron.yaml, jest niesamowity wpis na blogu: Uruchamianie zadań crona w Amazon Web Services (AWS) Elastic Beanstalk - Medium
jwako
5
Dzięki za to - pytanie debiutanta - potrzebuję, aby mój cron sprawdzał bazę danych mojej aplikacji internetowej dwa razy na godzinę pod kątem nadchodzących wydarzeń w kalendarzu i wysyłał e-mail z przypomnieniem, kiedy to nastąpi. Jaka jest najlepsza konfiguracja tutaj, czy adres URL cron.yaml powinien wskazywać trasę w mojej aplikacji internetowej? A może powinienem dać mojej aplikacji pracowniczej env dostęp do bazy danych? Tak mało na ten temat!
chrześcijański
5
@christian Tak, jak to robimy, mamy tę samą aplikację działającą w dwóch różnych środowiskach (dzięki czemu nie jest wymagana żadna specjalna konfiguracja) - pracująca i wspólny serwer WWW. Środowisko robocze ma specjalne trasy włączone przez ustawienie zmiennej ENV, której szuka nasza aplikacja. W ten sposób możesz ustawić specjalne trasy tylko dla pracowników w swoim cron.yaml, mając jednocześnie luksus współdzielenia kodu z normalną aplikacją. Twoja aplikacja dla pracowników może łatwo uzyskać dostęp do tych samych zasobów, co serwer WWW: bazy danych, modele itp.
xaralis
1
@JaquelinePassos v1.2.0 to wersja stosu rozwiązań. Powinien pozwolić Ci wybrać wersję stosu rozwiązań, którą chcesz utworzyć podczas tworzenia nowego środowiska. Wszystko, co jest nowsze niż wersja 1.2.0, powinno wystarczyć. Jeśli chodzi o adres URL, powinien to być adres URL, na którym nasłuchuje Twoja aplikacja, a nie ścieżka do pliku. Nie jest możliwe uruchamianie poleceń zarządzania Django, obsługuje on tylko żądania HTTP.
xaralis
4
Jedna rzecz, która nie jest dla mnie jasna, to to, czy istnieje sposób, aby uniknąć konieczności przydzielania dodatkowej maszyny EC2 tylko po to, aby uruchamiać zadania cron za pośrednictwem cron.yaml. Idealnie byłoby, gdyby działał na tej samej maszynie, co ta, która obsługuje żądania HTTP (tj. Warstwa WWW).
Wenzel Jakob
31

Jeśli chodzi o odpowiedź jamieba i jak wspomina alrdinleal, możesz użyć właściwości „Leader_only”, aby upewnić się, że tylko jedna instancja EC2 uruchamia zadanie cron.

Cytat pochodzi z http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html :

możesz użyć Leader_only. Jedna instancja jest liderem w grupie automatycznego skalowania. Jeśli wartość Lead_only jest ustawiona na true, polecenie działa tylko w instancji oznaczonej jako linia wiodąca.

Próbuję osiągnąć podobną rzecz na moim eb, więc zaktualizuję mój post, jeśli go rozwiążę.

AKTUALIZACJA:

Ok, teraz mam działające cronjobs przy użyciu następującej konfiguracji 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

Zasadniczo tworzę plik tymczasowy za pomocą cronjobs, a następnie ustawiam crontab na odczyt z pliku tymczasowego, a następnie usuwam plik tymczasowy. Mam nadzieję że to pomoże.

beterthanlife
źródło
3
W jaki sposób można zapewnić, że instancja obsługująca tę tabelę crontab nie zostanie zakończona przez automatyczne skalowanie? Domyślnie kończy najstarszą instancję.
Sebastien
1
To problem, którego nie udało mi się jeszcze rozwiązać. Wydaje mi się, że wadą funkcjonalności amazon jest to, że polecenia lider_only nie są stosowane do nowego lidera, gdy obecny jest zakończony przez EB. Jeśli coś wymyślisz, udostępnij to!
beterthanlife
7
Więc (w końcu) odkryłem, jak zapobiec rozwiązaniu lidera przez automatyczne skalowanie - niestandardowe zasady kończenia automatycznego skalowania. Zobacz docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/…
beterthanlife
1
@Nate Prawdopodobnie już to rozgryzłeś, ale opierając się na moim odczytaniu kolejności ich uruchamiania, „polecenia” są uruchamiane przed „container_commands”, więc możesz utworzyć plik, a następnie go usunąć, a następnie spróbować uruchomić tabelę crontab .
wyczyść
1
@Sebastien aby zachować najstarszą intencję, oto co robię: 1 - zmieniam ochronę terminacji intencji na ENBABLE. 2 - Przejdź do grupy Auto Scale i znajdź swój identyfikator środowiska EBS, kliknij EDYTUJ i zmień zasady zakończenia na „Najnowszy stan”
Ronaldo Bahia
12

Jak wspomniano powyżej, podstawową wadą przy ustalaniu konfiguracji crontab jest to, że dzieje się to tylko podczas wdrażania. Ponieważ klaster jest automatycznie skalowany w górę, a następnie z powrotem w dół, preferowane jest, aby był również pierwszym wyłączonym serwerem. Ponadto nie byłoby awarii, co było dla mnie krytyczne.

Zrobiłem kilka badań, a następnie porozmawiałem z naszym specjalistą ds. Kont AWS, aby odrzucić pomysły i zweryfikować rozwiązanie, które wymyśliłem. Możesz to osiągnąć za pomocą OpsWorks , chociaż to trochę tak, jak użycie domu do zabicia muchy. Możliwe jest również użycie Data Pipeline z Task Runner , ale ma to ograniczone możliwości w skryptach, które może wykonywać, i musiałem być w stanie uruchamiać skrypty PHP, z dostępem do całej bazy kodu. Możesz również zadedykować instancję EC2 poza klastrem ElasticBeanstalk, ale wtedy nie ma ponownie przełączenia awaryjnego.

Oto, co wymyśliłem, co najwyraźniej jest niekonwencjonalne (jak skomentował przedstawiciel AWS) i można je uznać za hack, ale działa i jest solidne z przełączaniem awaryjnym. Wybrałem rozwiązanie do kodowania przy użyciu SDK, które pokażę w PHP, chociaż możesz zrobić tę samą metodę w dowolnym preferowanym języku.

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

A więc przechodząc przez to i jak to działa ... Skrypty wywołujesz z crontab tak jak zwykle na każdej instancji EC2. Każdy skrypt zawiera to na początku (lub zawiera pojedynczy plik dla każdego, ponieważ go używam), który ustanawia obiekt ElasticBeanstalk i pobiera listę wszystkich instancji. Używa tylko pierwszego serwera na liście i sprawdza, czy pasuje do siebie, a jeśli tak, kontynuuje, w przeciwnym razie umiera i zamyka się. Sprawdziłem i zwrócona lista wydaje się być spójna, co z technicznego punktu widzenia musi być spójne tylko przez około minutę, ponieważ każda instancja wykonuje zaplanowany cron. Jeśli to się zmieni, nie będzie to miało znaczenia, ponieważ znowu ma znaczenie tylko dla tego małego okna.

Nie jest to w żaden sposób eleganckie, ale odpowiadało naszym specyficznym potrzebom - które nie polegały na zwiększaniu kosztów za pomocą dodatkowej usługi lub konieczności posiadania dedykowanej instancji EC2, a także w przypadku awarii. Nasze skrypty cron uruchamiają skrypty konserwacyjne, które są umieszczane w SQS, a każdy serwer w klastrze pomaga w wykonywaniu. Przynajmniej może to dać ci alternatywną opcję, jeśli pasuje do twoich potrzeb.

-Davey

user1599237
źródło
Zauważyłem, że php_uname ('n') zwraca prywatną nazwę DNS (np. Ip-172.24.55.66), która nie jest identyfikatorem instancji, którego szukasz. Zamiast używać php_uname (), skończyło się na tym: $instanceId = file_get_contents("http://instance-data/latest/meta-data/instance-id"); Następnie po prostu użyj tego $ instanceId var, aby zrobić porównanie.
Valorum
1
Czy istnieje gwarancja, że ​​tablica Instances przedstawia tę samą kolejność przy każdym wywołaniu Describe? Proponuję wyodrębnić pole ['Id'] każdego wpisu do tablicy i posortować je w PHP, zanim sprawdzisz, czy pierwszy posortowany wpis jest twoim bieżącym instanceId.
Gabriel
Na podstawie tej odpowiedzi stworzyłem takie rozwiązanie: stackoverflow.com/questions/14077095/… - jest bardzo podobne, ale NIE ma szans na podwójne wykonanie.
TheStoryCoder
11

Rozmawiałem z agentem wsparcia AWS i tak to zadziałało. Rozwiązanie 2015:

Utwórz plik w swoim katalogu .ebextensions z nazwą twoja_nazwa_pliku.config. W pliku wejściowym pliku konfiguracyjnego:

akta:
  „/etc/cron.d/cron_example”:
    tryb: „000644”
    właściciel: root
    grupa: root
    treść: |
      * * * * * root /usr/local/bin/cron_example.sh

  „/usr/local/bin/cron_example.sh”:
    tryb: „000755”
    właściciel: root
    grupa: root
    treść: |
      #! / bin / bash

      /usr/local/bin/test_cron.sh || wyjście
      echo "Cron działa o" `date` >> /tmp/cron_example.log
      # Teraz wykonaj zadania, które powinny działać tylko na 1 instancji ...

  „/usr/local/bin/test_cron.sh”:
    tryb: „000755”
    właściciel: root
    grupa: root
    treść: |
      #! / bin / bash

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

      # Znajdź nazwę naszej grupy automatycznego skalowania.
      ASG = ʻaws ec2 opis-tagi --filters "Nazwa = identyfikator-zasobu, Wartości = $ INSTANCE_ID" \
        --region $ REGION --output text | awk '/ aws: autoscaling: groupName / {print $ 5}' ''

      # Znajdź pierwszą instancję w grupie
      FIRST = ʻaws autoskalowanie opis-automatyczne-skalowanie-grupy --auto-skalowanie-nazwy-grup $ ASG \
        --region $ REGION --output text | awk '/ InService $ / {print $ 4}' | sort | głowa -1`

      # Sprawdź, czy są takie same.
      ["$ FIRST" = "$ INSTANCE_ID"]

polecenia:
  rm_old_cron:
    polecenie: „rm * .bak”
    cwd: „/etc/cron.d”
    ignoreErrors: true

To rozwiązanie ma 2 wady:

  1. Przy kolejnych wdrożeniach Beanstalk zmienia nazwę istniejącego skryptu cron na .bak, ale cron nadal będzie go uruchamiał. Twój Cron wykonuje teraz dwa razy na tej samej maszynie.
  2. Jeśli twoje środowisko skaluje się, otrzymasz kilka instancji, wszystkie z uruchomionym skryptem cron. Oznacza to, że zrzuty poczty są powtarzane lub archiwa bazy danych są zduplikowane

Obejście:

  1. Upewnij się, że każdy skrypt .ebextensions, który tworzy plik cron, usuwa również pliki .bak podczas kolejnych wdrożeń.
  2. Miej skrypt pomocniczy, który wykonuje następujące czynności: - Pobiera bieżący identyfikator instancji z metadanych - Pobiera bieżącą nazwę grupy automatycznego skalowania ze znaczników EC2 - Pobiera listę instancji EC2 w tej grupie, posortowaną alfabetycznie. - Pobiera pierwszą instancję z tej listy. - Porównuje identyfikator instancji z kroku 1 z pierwszym identyfikatorem instancji z kroku 4. Twoje skrypty cron mogą następnie użyć tego skryptu pomocniczego, aby określić, czy powinny zostać wykonane.

Ostrzeżenie:

  • Rola IAM używana dla instancji Beanstalk wymaga uprawnień ec2: DescribeTags i autoskalowania: DescribeAutoScalingGroups
  • Wybrane spośród instancji to te, które są wyświetlane jako InService przez automatyczne skalowanie. Nie musi to oznaczać, że są one w pełni uruchomione i gotowe do uruchomienia twojego crona.

Nie musisz ustawiać ról uprawnień, jeśli używasz domyślnej roli łodygi fasoli.

Rozpoznać
źródło
7

Jeśli używasz Railsów, możesz użyć klejnotu When-Elasticbeanstalk . Pozwala na uruchamianie zadań crona na wszystkich wystąpieniach lub tylko na jednym. Sprawdza co minutę, aby upewnić się, że jest tylko jedna instancja „lidera” i automatycznie awansuje jeden serwer na „lidera”, jeśli takiego nie ma. Jest to potrzebne, ponieważ Elastic Beanstalk ma koncepcję lidera tylko podczas wdrażania i może wyłączyć dowolną instancję w dowolnym momencie podczas skalowania.

AKTUALIZACJA Przerzuciłem się na AWS OpsWorks i nie zajmuję się już tym klejnotem. Jeśli potrzebujesz większej funkcjonalności niż ta dostępna w podstawach Elastic Beanstalk, bardzo polecam przejście na OpsWorks.

dignoe
źródło
Czy mógłbyś nam powiedzieć, jak rozwiązałeś to za pomocą OpsWorks? Czy korzystasz z niestandardowych warstw, które wykonują zadania cron?
Tommie,
Tak, mam warstwę admin / cron, która działa tylko na jednym serwerze. Skonfigurowałem niestandardową książkę kucharską, która przechowuje wszystkie moje zadania cron. AWS ma przewodnik pod adresem docs.aws.amazon.com/opsworks/latest/userguide/… .
dignoe
@dignoe, jeśli przypiszesz jeden serwer do uruchamiania zadań cron za pomocą OpsWorks, to samo przy użyciu Elastic Beanstalk, mogę użyć środowiska z jednym serwerem do uruchamiania zadań cron. Nawet przy Load Balancer, maksymalna i minimalna liczba wystąpień ustawiona na jeden, aby zawsze zachować przynajmniej instancję serwera.
Jose Nobile
6

Naprawdę nie chcesz uruchamiać zadań cron na Elastic Beanstalk. Ponieważ będziesz mieć wiele instancji aplikacji, może to powodować warunki wyścigu i inne dziwne problemy. I rzeczywiście niedawno napisał o tym (4. lub 5. końcówki w dół strony). Krótka wersja: w zależności od aplikacji użyj kolejki zadań, takiej jak SQS lub rozwiązania innej firmy, np . Iron.io.

jamieb
źródło
SQS nie gwarantuje, że kod zostanie uruchomiony tylko raz. Podoba mi się strona iron.io, zamierzam to sprawdzić.
Nathan H
Również w swoim poście na blogu zalecasz używanie InnoDB w RDS. Używam tabeli w RDS do przechowywania moich zadań i używam funkcji InnoDB „WYBIERZ ... DLA AKTUALIZACJI”, aby upewnić się, że tylko jeden serwer wykonuje te zadania. W jaki sposób Twoja aplikacja kontaktuje się z SQS bez zadania cron lub interakcji z użytkownikiem?
James Alday,
1
@JamesAlday To pytanie SO jest dość stare. Odkąd napisałem powyższy komentarz, AWS wprowadził elegancki sposób obsługi zadań cron na Elastic Beanstalk, wybierając jeden z działających serwerów jako master. Powiedziawszy to, wygląda na to, że niewłaściwie używasz cron + MySQL jako kolejki zadań. Zanim jednak przedstawię konkretne zalecenia, musiałbym dużo wiedzieć o Twojej aplikacji.
jamieb
Mam skrypt, który działa przez cron, który sprawdza tabelę pod kątem zadań do wykonania. Korzystanie z transakcji zapobiega wykonywaniu tego samego zadania przez wiele serwerów. Zajrzałem do SQS, ale potrzebujesz głównego serwera, który uruchamia wszystkie skrypty zamiast go dystrybuować, a nadal musisz napisać logikę, aby upewnić się, że nie uruchamiasz tego samego skryptu wiele razy. Ale nadal nie wiem, jak sprawić, by zadania były uruchamiane bez interakcji z użytkownikiem lub crona - co powoduje, że Twoja aplikacja uruchamia zadania w kolejce?
James Alday,
5

2017: Jeśli używasz Laravel5 +

Potrzebujesz tylko 2 minut, aby to skonfigurować:

  • utwórz poziom pracownika
  • zainstaluj laravel-aws-worker

    composer require dusterio/laravel-aws-worker

  • dodaj plik cron.yaml do folderu głównego:

Dodaj cron.yaml do folderu głównego aplikacji (może to być część repozytorium lub możesz dodać ten plik tuż przed wdrożeniem do EB - ważne jest, aby ten plik był obecny w momencie wdrożenia):

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

Otóż ​​to!

Całe twoje zadanie App\Console\Kernelzostanie teraz wykonane

Szczegółowe instrukcje i wyjaśnienia: https://github.com/dusterio/laravel-aws-worker

Jak pisać zadania w Laravel: https://laravel.com/docs/5.4/scheduling

Sebastien Horin
źródło
3

Bardziej czytelne rozwiązanie wykorzystujące fileszamiast container_commands:

akta:
  „/etc/cron.d/my_cron”:
    tryb: „000644”
    właściciel: root
    grupa: root
    treść: |
      # zastąp domyślny adres e-mail
      MAILTO = „[email protected]”
      # uruchamiaj polecenie Symfony co pięć minut (jako ec2-user)
      * / 10 * * * * ec2-user / usr / bin / php / var / app / current / app / console do: coś
    kodowanie: zwykłe
polecenia:
  # usuń plik kopii zapasowej utworzony przez Elastic Beanstalk
  clear_cron_backup:
    polecenie: rm -f /etc/cron.d/watson.bak

Zwróć uwagę, że format różni się od zwykłego formatu tabeli crontab tym, że określa użytkownika, który ma uruchomić polecenie jako.

Tamlyn
źródło
Jednym z problemów jest to, że instancje Elastic Beanstalk EC2 nie mają domyślnie skonfigurowanych usług SMTP, więc opcja MAILTO może nie działać.
Justin Finkelstein
3

Mój 1 cent za 2018 rok

Oto właściwy sposób, aby to zrobić (za pomocą django/pythoni django_crontabaplikacji):

wewnątrz .ebextensionsfolderu utwórz taki plik 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

Musi być container_commandszamiastcommands

Ronaldo Bahia
źródło
2

Najnowszy przykład z Amazona jest najłatwiejszy i najbardziej wydajny (zadania okresowe):

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

gdzie tworzysz oddzielną warstwę roboczą, aby wykonywać dowolne zadania cron. Utwórz plik cron.yaml i umieść go w folderze głównym. Jednym z problemów, które miałem (po tym, jak cron nie wydawał się wykonywać), było stwierdzenie, że mój CodePipeline nie ma uprawnień do wykonania modyfikacji dynamodb. Na tej podstawie po dodaniu dostępu FullDynamoDB pod IAM -> role -> yourpipeline i ponownym wdrożeniu (elastyczna łodyga fasoli) działało idealnie.

Josh
źródło
1

Więc walczyliśmy z tym przez jakiś czas i po krótkiej dyskusji z przedstawicielem AWS w końcu wymyśliłem to, co uważam za najlepsze rozwiązanie.

Korzystanie z warstwy roboczej z cron.yaml jest zdecydowanie najłatwiejszym rozwiązaniem. Jednak dokumentacja nie wyjaśnia, że ​​spowoduje to umieszczenie zadania na końcu kolejki SQS, której używasz do rzeczywistego wykonywania zadań. Jeśli twoje zadania cron są wrażliwe na czas (jak wiele z nich), jest to niedopuszczalne, ponieważ zależałoby to od rozmiaru kolejki. Jedną z opcji jest użycie całkowicie oddzielnego środowiska tylko do uruchamiania zadań cron, ale myślę, że to przesada.

Niektóre inne opcje, takie jak sprawdzenie, czy jesteś na pierwszym miejscu na liście, też nie są idealne. Co się stanie, jeśli obecna pierwsza instancja jest w trakcie zamykania?

Ochrona instancji może również wiązać się z problemami - co, jeśli ta instancja zostanie zablokowana / zamrożona?

Ważne jest, aby zrozumieć, w jaki sposób sam AWS zarządza funkcjonalnością cron.yaml. Istnieje demon SQS, który używa tabeli Dynamo do obsługi „wyborów lidera”. Często pisze do tej tabeli, a jeśli obecny lider nie pisał przez krótką chwilę, następna instancja przejmie rolę lidera. W ten sposób demon decyduje, która instancja ma uruchomić zadanie w kolejce SQS.

Możemy zmienić przeznaczenie istniejącej funkcjonalności, zamiast próbować przepisać naszą własną. Pełne rozwiązanie można zobaczyć tutaj: https://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27

To jest w Rubim, ale możesz łatwo dostosować go do dowolnego innego języka, który ma AWS SDK. Zasadniczo sprawdza obecnego lidera, a następnie sprawdza stan, aby upewnić się, że jest w dobrym stanie. Będzie się zapętlać, dopóki aktualny lider nie będzie w dobrym stanie, a jeśli bieżąca instancja jest liderem, wykonaj zadanie.

Cidolfas
źródło
0

Aby kontrolować, czy automatyczne skalowanie może przerywać określone wystąpienie podczas skalowania, użyj ochrony wystąpienia. Możesz włączyć ustawienie ochrony instancji w grupie automatycznego skalowania lub pojedynczej instancji automatycznego skalowania. Gdy Automatyczne skalowanie uruchamia instancję, dziedziczy ona ustawienie ochrony instancji z grupy Automatyczne skalowanie. W dowolnym momencie możesz zmienić ustawienie ochrony instancji dla grupy Auto Scaling lub Auto Scaling.

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

Dele
źródło
0

Miałem inne rozwiązanie tego problemu, jeśli plik php musi być uruchomiony przez cron i jeśli ustawiłeś jakieś instancje NAT, możesz umieścić cronjob na instancji NAT i uruchomić plik php przez wget.

prasoon
źródło
0

tutaj jest poprawka na wypadek, gdybyś chciał to zrobić w PHP. Potrzebujesz tylko cronjob.config w swoim folderze .ebextensions, aby działał w ten sposób.

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 pobiera zmienne środowiskowe dla plików. Możesz debugować dane wyjściowe w tmp / sendemail.log, jak powyżej.

Mam nadzieję, że to komuś pomoże, ponieważ z pewnością pomogło nam!

foxybagga
źródło
0

W oparciu o zasady odpowiedzi użytkownika1599237 , gdzie pozwalasz crona uruchamiać się na wszystkich instancjach, ale zamiast tego na początku zadań , czy powinny one być uruchamiane, stworzyłem inne rozwiązanie.

Zamiast patrzeć na uruchomione instancje (i muszę przechowywać klucz i tajny klucz AWS) używam bazy danych MySQL, z którą już się łączę ze wszystkich instancji.

Nie ma wad, tylko zalety:

  • bez dodatkowej instancji lub wydatków
  • solidne rozwiązanie - nie ma szans na podwójną realizację
  • skalowalne - działa automatycznie, gdy Twoje instancje są skalowane w górę iw dół
  • przełączanie awaryjne - działa automatycznie w przypadku awarii instancji

Alternatywnie możesz również użyć powszechnie współdzielonego systemu plików (takiego jak AWS EFS przez protokół NFS) zamiast bazy danych.

Poniższe rozwiązanie zostało stworzone we frameworku PHP Yii, ale można je łatwo dostosować do innego frameworka i języka. Również Yii::$app->systemmoduł obsługi wyjątków jest moim własnym modułem. Zastąp go tym, czego używasz.

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

Oto schemat bazy danych, którego używam:

CREATE TABLE `system_job_locks` (
    `job_name` VARCHAR(50) NOT NULL,
    `locked` DATETIME NOT NULL COMMENT 'UTC',
    `duration` SMALLINT(5) UNSIGNED NOT NULL COMMENT 'Minutes',
    `source` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`job_name`)
)
TheStoryCoder
źródło