AWS Elastic Beanstalk, executando um cronjob

89

Gostaria de saber se existe uma maneira de configurar um cronjob / tarefa para executar a cada minuto. Atualmente, qualquer uma das minhas instâncias deve ser capaz de executar esta tarefa.

Isto é o que tentei fazer nos arquivos de configuração sem sucesso:

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

Não tenho certeza se esta é a maneira correta de fazer isso

Alguma ideia?

Onema
fonte
1
O comando está certo? Quer dizer ... poderia ser: command: echo "* / 1 * * * * root php /etc/httpd/myscript.php"> /etc/cron.d/something De qualquer maneira, sugiro que você use o sinalizador leader_only, caso contrário, todas as máquinas iniciarão este cron job de uma vez
aldrinleal
Sim! definitivamente usando o sinalizador leader_only, tentarei alterar o comando.
Onema

Respostas:

96

Foi assim que adicionei um cron job ao Elastic Beanstalk:

Crie uma pasta na raiz do seu aplicativo chamada .ebextensions se ainda não existir. Em seguida, crie um arquivo de configuração dentro da pasta .ebextensions. Usarei example.config para fins ilustrativos. Em seguida, adicione isso 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

Este é um arquivo de configuração YAML para Elastic Beanstalk. Certifique-se de que, ao copiar isso para o editor de texto, ele use espaços em vez de tabulações. Caso contrário, você obterá um erro YAML ao enviar para EB.

Então o que isso faz é criar um comando chamado 01_some_cron_job. Os comandos são executados em ordem alfabética, então o 01 garante que seja executado como o primeiro comando.

O comando então pega o conteúdo de um arquivo chamado some_cron_job.txt e o adiciona a um arquivo chamado some_cron_job em /etc/cron.d.

O comando então altera as permissões no arquivo /etc/cron.d/some_cron_job.

A chave leader_only garante que o comando seja executado apenas na instância ec2 que é considerada líder. Em vez de executar em todas as instâncias ec2 que você possa ter em execução.

Em seguida, crie um arquivo chamado some_cron_job.txt dentro da pasta .ebextensions. Você colocará seus cron jobs neste arquivo.

Então, por exemplo:

# 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

Portanto, este cron job será executado a cada minuto de cada hora de todos os dias como o usuário root e descartará a saída para / dev / null. / usr / bin / php é o caminho para o php. Em seguida, substitua some-php-script-here pelo caminho para seu arquivo php. Obviamente, isso pressupõe que seu cron job precisa executar um arquivo PHP.

Além disso, certifique-se de que o arquivo some_cron_job.txt tenha uma nova linha no final do arquivo, exatamente como o comentário diz. Caso contrário, o cron não será executado.

Atualização: há um problema com esta solução quando o Elastic Beanstalk escala suas instâncias. Por exemplo, digamos que você tenha uma instância com o cron job em execução. Você obtém um aumento no tráfego, então o Elastic Beanstalk escala você em até duas instâncias. O leader_only garantirá que você tenha apenas um cron job em execução entre as duas instâncias. Seu tráfego diminui e o Elastic Beanstalk reduz você a uma instância. Mas em vez de encerrar a segunda instância, o Elastic Beanstalk encerra a primeira instância que era o líder. Agora você não tem nenhum cron jobs em execução, pois eles só foram executados na primeira instância que foi encerrada. Veja os comentários abaixo.

Atualização 2: Apenas deixando isso claro com os comentários abaixo: AWS agora tem proteção contra encerramento automático de instâncias. Basta habilitá-lo em sua instância líder e você estará pronto para prosseguir. - Nicolás Arévalo 28 de outubro de 16 às 9:23

Anarchtica
fonte
12
Venho usando sua sugestão há algum tempo e recentemente encontrei um problema em que de alguma forma o líder trocou, resultando em várias instâncias executando o cron. Para resolver essa questão, eu mudei 01_some_cron_jobpara 02_some_cron_jobe adicionado 01_remove_cron_jobscom o seguinte: command: "rm /etc/cron.d/cron_jobs || exit 0". Dessa forma, após cada implantação, apenas o líder terá o cron_jobsarquivo. Se os líderes mudarem, você pode simplesmente reimplantar e os crons serão consertados para serem executados apenas mais uma vez.
Willem Renzema
4
Eu sugeriria contra confiar na leader_onlypropriedade. Ele só é usado durante a implantação e se você reduzir ou sua instância "líder" falhar, você terá problemas de referência
arnaslu
2
Não faça isso. É muito pouco confiável. A única maneira de fazer isso funcionar é executando uma microinstância e executando tarefas cron a partir daí usando CURL. Isso garante que apenas uma instância o execute e o líder que tem o crons instalado não seja encerrado.
Ben Sinclair
1
Tentei consertar isso com um pequeno script ruby, você pode encontrá-lo aqui: github.com/SocialbitGmbH/AWSBeanstalkLeaderManager
Thomas Kekeisen
8
AWS agora tem proteção contra o encerramento automático da instância. Basta habilitá-lo em sua instância líder e você estará pronto para prosseguir.
Nicolás Arévalo
58

Esta é a forma oficial de fazer isso agora (2015+). Experimente primeiro, é de longe o método mais fácil disponível atualmente e também mais confiável.

De acordo com os documentos atuais, é possível executar tarefas periódicas em sua chamada camada de trabalho .

Citando a documentação:

O AWS Elastic Beanstalk oferece suporte a tarefas periódicas para níveis de ambiente de trabalho em ambientes que executam uma configuração predefinida com uma pilha de solução que contém "v1.2.0" no nome do contêiner. Você deve criar um novo ambiente.

Também interessante é a parte sobre cron.yaml :

Para invocar tarefas periódicas, o pacote de origem do aplicativo deve incluir um arquivo cron.yaml no nível raiz. O arquivo deve conter informações sobre as tarefas periódicas que você deseja agendar. Especifique essas informações usando a sintaxe padrão do crontab.

Atualização: Conseguimos este trabalho. Aqui estão algumas dicas importantes de nossa experiência (plataforma Node.js):

  • Ao usar o arquivo cron.yaml , certifique-se de ter o awsebcli mais recente , porque as versões anteriores não funcionarão corretamente.
  • Também é vital criar um novo ambiente (pelo menos no nosso caso foi), não apenas clonar o antigo.
  • Se você quiser ter certeza de que o CRON é compatível com sua instância EC2 Worker Tier eb ssh, execute ssh nele ( ) e execute cat /var/log/aws-sqsd/default.log. Deve relatar como aws-sqsd 2.0 (2015-02-18). Se você não tem a versão 2.0, algo deu errado ao criar seu ambiente e você precisa criar um novo conforme mencionado acima.
xaralis
fonte
2
Sobre cron.yaml, há uma postagem de blog incrível: Executando tarefas cron no Amazon Web Services (AWS) Elastic Beanstalk - Médio
jwako
5
Obrigado por isso - pergunta de novato - preciso que meu cron verifique o banco de dados do meu aplicativo da web duas vezes por hora para os próximos eventos de calendário e envie um e-mail de lembrete quando isso acontecer. Qual é a melhor configuração aqui, devo fazer com que o URL cron.yaml aponte para uma rota em meu aplicativo da Web? Ou devo dar ao meu aplicativo ambiente de trabalho acesso ao banco de dados? Tão pouco aí sobre isso!
cristão de
5
@christian Da forma como fazemos, temos o mesmo aplicativo rodando em dois ambientes diferentes (portanto, nenhuma configuração especial necessária) - trabalhador e um servidor web comum. O ambiente de trabalho tem algumas rotas especiais habilitadas pela configuração de uma variável ENV que nosso aplicativo procura. Dessa forma, você pode definir rotas especiais apenas para trabalhadores em seu cron.yaml, enquanto se dá ao luxo de compartilhar a base de código com o aplicativo normal. Seu aplicativo de trabalho pode acessar facilmente os mesmos recursos que um servidor web: banco de dados, modelos etc.
xaralis
1
@JaquelinePassos v1.2.0 é a versão do solution stack. Ele deve permitir que você escolha qual versão da pilha de solução deseja criar ao criar um novo ambiente. Qualquer coisa mais recente que a v1.2.0 deve servir. Com relação ao URL, deve ser o URL em que seu aplicativo escuta, não um caminho de arquivo. Não é possível executar comandos de gerenciamento do Django, ele apenas faz solicitações HTTP.
xaralis
4
Uma coisa que não está clara para mim é se existe uma maneira de evitar ter que alocar uma máquina EC2 extra apenas para executar os cron jobs via cron.yaml. O ideal é que ele seja executado na mesma máquina que está atendendo às solicitações HTTP (ou seja, nível da web).
Wenzel Jakob
31

Com relação à resposta de Jamieb, e como alrdinleal menciona, você pode usar a propriedade 'leader_only' para garantir que apenas uma instância EC2 execute o cron job.

Citação tirada de http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html :

você pode usar leader_only. Uma instância é escolhida para ser a líder em um grupo de Auto Scaling. Se o valor leader_only for definido como true, o comando será executado apenas na instância marcada como líder.

Estou tentando conseguir algo semelhante no meu eb, então atualizarei meu post se eu resolver isso.

ATUALIZAR:

Ok, agora tenho cronjobs funcionando usando a seguinte configuração do 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

Basicamente, eu crio um arquivo temporário com os cronjobs e, em seguida, defino o crontab para ler do arquivo temporário e, em seguida, excluo o arquivo temporário. Espero que isto ajude.

Beterthanlife
fonte
3
Como você garantiria que a instância que está executando este crontab não seja encerrada pelo escalonamento automático? Por padrão, ele encerra a instância mais antiga.
Sebastien
1
Esse é um problema que ainda não consegui resolver. Parece-me uma falha na funcionalidade do amazon que os comandos leader_only não sejam aplicados a um novo líder quando o atual é encerrado por EB. Se você tiver algo, por favor, compartilhe!
beterthanlife
7
Então, (finalmente) descobri como evitar que o líder seja encerrado por escalonamento automático - políticas de encerramento de escalonamento automático personalizadas. Consulte docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/…
beterthanlife
1
@Nate Você provavelmente já percebeu isso, mas com base na minha leitura da ordem em que são executados, os "comandos" são executados antes de "container_commands", então você criaria o arquivo, excluí-lo-ia e tentaria executar o crontab .
claro em
1
@Sebastien para manter a intância mais antiga, aqui está o que eu faço: 1 - alterar a proteção de terminação da intância para ENBABLE. 2 - Vá para o Grupo de Escala Automática e encontre seu ID de Ambiente EBS, clique em EDITAR e altere as Políticas de Rescisão para "Instância mais recente"
Ronaldo Bahia
12

Conforme mencionado acima, a falha fundamental em estabelecer qualquer configuração crontab é que isso só acontece na implantação. À medida que o cluster é escalado automaticamente para cima e, em seguida, para baixo, ele também é o primeiro servidor a ser desligado. Além disso, não haveria failover, o que para mim era crítico.

Fiz algumas pesquisas e conversei com nosso especialista em contas da AWS para trocar ideias e validar a solução que encontrei. Você pode fazer isso com OpsWorks , embora seja um pouco como usar uma casa para matar uma mosca. Também é possível usar o Data Pipeline com o Task Runner , mas isso tem capacidade limitada nos scripts que pode executar, e eu precisava ser capaz de executar scripts PHP, com acesso a toda a base de código. Você também pode dedicar uma instância EC2 fora do cluster ElasticBeanstalk, mas não haverá failover novamente.

Então aqui está o que eu inventei, que aparentemente não é convencional (como o representante da AWS comentou) e pode ser considerado um hack, mas funciona e é sólido com failover. Escolhi uma solução de codificação usando o SDK, que mostrarei em PHP, embora você possa fazer o mesmo método em qualquer linguagem de sua preferência.

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

Então, examinando isso e como ele funciona ... Você chama scripts do crontab como faria normalmente em cada instância do EC2. Cada script inclui isso no início (ou inclui um único arquivo para cada, conforme eu o uso), que estabelece um objeto ElasticBeanstalk e recupera uma lista de todas as instâncias. Ele usa apenas o primeiro servidor da lista e verifica se ele corresponde a si mesmo, o que, se isso acontecer, continua; caso contrário, ele morre e fecha. Verifiquei e a lista retornada parece ser consistente, o que tecnicamente só precisa ser consistente por um minuto ou mais, conforme cada instância executa o cron programado. Se mudar, não importa, já que, novamente, só é relevante para aquela pequena janela.

Isso não era nada elegante, mas atendia às nossas necessidades específicas - que não era aumentar o custo com um serviço adicional ou ter que ter uma instância EC2 dedicada, e teria failover em caso de alguma falha. Nossos scripts cron executam scripts de manutenção que são colocados no SQS e cada servidor no cluster ajuda a executar. Pelo menos isso pode lhe dar uma opção alternativa se for adequado às suas necessidades.

-Davey

user1599237
fonte
Descobri que php_uname ('n') retorna o nome DNS privado (por exemplo, ip-172.24.55.66), que não é o ID da instância que você está procurando. Em vez de usar php_uname (), acabei usando o seguinte: $instanceId = file_get_contents("http://instance-data/latest/meta-data/instance-id"); Então, apenas use aquele $ instanceId var para fazer a comparação.
Valorum
1
Existe alguma garantia de que o array Instances apresenta a mesma ordem em cada chamada Describe? Eu sugeriria extrair o campo ['Id'] de cada entrada em uma matriz e classificá-los em PHP, antes de verificar se a primeira entrada classificada é o seu instanceId atual.
Gabriel
Com base nessa resposta, fiz esta solução: stackoverflow.com/questions/14077095/… - é muito semelhante, mas NÃO tem chance de execução dupla.
TheStoryCoder
11

Falei com um agente de suporte da AWS e é assim que fizemos isso funcionar para mim. Solução 2015:

Crie um arquivo em seu diretório .ebextensions com your_file_name.config. Na entrada do arquivo de configuração:

arquivos:
  "/etc/cron.d/cron_example":
    modo: "000644"
    proprietário: root
    grupo: root
    conteúdo: |
      * * * * * root /usr/local/bin/cron_example.sh

  "/usr/local/bin/cron_example.sh":
    modo: "000755"
    proprietário: root
    grupo: root
    conteúdo: |
      #! / bin / bash

      /usr/local/bin/test_cron.sh || Saída
      echo "Cron em execução em" `date` >> /tmp/cron_example.log
      # Agora execute tarefas que devem ser executadas apenas em 1 instância ...

  "/usr/local/bin/test_cron.sh":
    modo: "000755"
    proprietário: root
    grupo: root
    conteúdo: |
      #! / bin / bash

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

      # Encontre o nome do nosso Grupo de Auto Scaling.
      ASG = `aws ec2 describe-tags --filters" Name = resource-id, Values ​​= $ INSTANCE_ID "\
        --region $ REGION - texto de saída | awk '/ aws: autoscaling: groupName / {print $ 5}' `

      # Encontre a primeira instância do grupo
      FIRST = `aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $ ASG \
        --region $ REGION - texto de saída | awk '/ InService $ / {print $ 4}' | classificar | cabeça -1`

      # Teste se eles são iguais.
      ["$ FIRST" = "$ INSTANCE_ID"]

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

Esta solução tem 2 desvantagens:

  1. Em implementações subsequentes, o Beanstalk renomeia o script cron existente como .bak, mas o cron ainda o executará. Seu Cron agora executa duas vezes na mesma máquina.
  2. Se o seu ambiente aumentar, você terá várias instâncias, todas executando o seu cron script. Isso significa que seus emails são repetidos ou seus arquivos de banco de dados duplicados

Gambiarra:

  1. Certifique-se de que qualquer script .ebextensions que crie um cron também remova os arquivos .bak nas implantações subsequentes.
  2. Tenha um script auxiliar que faz o seguinte: - Obtém o ID da Instância atual dos Metadados - Obtém o nome do Grupo de Auto Scaling atual dos Tags EC2 - Obtém a lista de Instâncias EC2 naquele Grupo, classificada em ordem alfabética. - Obtém a primeira instância dessa lista. - Compara a ID da instância da etapa 1 com a primeira ID da instância da etapa 4. Seus scripts cron podem então usar este script auxiliar para determinar se devem ser executados.

Embargo:

  • O papel IAM usado para as instâncias do Beanstalk precisa de permissões ec2: DescribeTags e escalonamento automático: DescribeAutoScalingGroups
  • As instâncias escolhidas são aquelas mostradas como InService pelo Auto Scaling. Isso não significa necessariamente que eles estejam totalmente inicializados e prontos para executar seu cron.

Você não teria que definir as funções IAM se estiver usando a função beanstalk padrão.

Ken
fonte
7

Se você estiver usando Rails, você pode usar a gem always-elasticbeanstalk . Ele permite que você execute tarefas cron em todas as instâncias ou em apenas uma. Ele verifica a cada minuto para garantir que haja apenas uma instância "líder" e irá promover automaticamente um servidor a "líder" se não houver nenhuma. Isso é necessário porque o Elastic Beanstalk tem apenas o conceito de líder durante a implantação e pode desligar qualquer instância a qualquer momento durante o dimensionamento.

ATUALIZAÇÃO Mudei para o AWS OpsWorks e não estou mais mantendo esta joia. Se você precisar de mais funcionalidade do que está disponível no básico do Elastic Beanstalk, eu recomendo mudar para o OpsWorks.

digno
fonte
Você se importaria de nos dizer como você resolveu isso usando o OpsWorks? Você está executando camadas personalizadas que fazem os cron-jobs?
Tommie
Sim, eu tenho uma camada admin / cron que só roda em um servidor. Eu configurei um livro de receitas personalizado que contém todos os meus trabalhos do cron. A AWS tem um guia em docs.aws.amazon.com/opsworks/latest/userguide/… .
dignoe
@dignoe se você atribuir um servidor para executar tarefas cron usando OpsWorks, a mesma coisa usando Elastic Beanstalk, posso usar um ambiente com um servidor para executar tarefas cron. Mesmo com o Load Balancer, as instâncias máximas e mínimas definidas como um, para conservar sempre uma instância do servidor, pelo menos.
Jose Nobile
6

Você realmente não quer executar tarefas cron no Elastic Beanstalk. Como você terá várias instâncias de aplicativo, isso pode causar condições de corrida e outros problemas estranhos. Na verdade, recentemente escrevi sobre isso (4ª ou 5ª dica na página). A versão curta: Dependendo da aplicação, use uma fila trabalho como SQS ou uma solução de terceiros como iron.io .

Jamieb
fonte
SQS não garante que o código será executado apenas uma vez. Gosto do site iron.io, vou dar uma olhada.
Nathan H
Também na postagem do seu blog, você recomenda o uso do InnoDB no RDS. Eu uso uma tabela no RDS para armazenar minhas tarefas e uso o recurso "SELECT ... FOR UPDATE" do InnoDB para garantir que apenas um servidor execute essas tarefas. Como seu aplicativo contata o SQS sem um cron job ou interação do usuário?
James Alday
1
@JamesAlday Essa pergunta do SO é bem antiga. Desde que escrevi o comentário acima, a AWS apresentou uma maneira elegante de lidar com tarefas cron no Elastic Beanstalk, elegendo um dos servidores em execução como mestre. Dito isso, parece que você está usando o cron + MySQL indevidamente como uma fila de tarefas. Eu precisaria saber muito sobre seu aplicativo antes de oferecer recomendações concretas.
jamieb
Eu tenho um script que é executado via cron que verifica uma tabela para trabalhos a serem executados. O uso de transações evita que vários servidores executem o mesmo trabalho. Eu pesquisei o SQS, mas você precisa de um servidor mestre que execute todos os scripts em vez de distribuí-lo e ainda precisa escrever uma lógica para garantir que não execute o mesmo script várias vezes. Mas ainda estou confuso sobre como você faz com que as tarefas sejam executadas sem a interação do usuário ou cron - o que aciona seu aplicativo para executar as tarefas na fila?
James Alday
4

2017: Se você estiver usando o Laravel5 +

Você só precisa de 2 minutos para configurá-lo:

  • criar uma camada de trabalho
  • instalar laravel-aws-worker

    composer require dusterio/laravel-aws-worker

  • adicione um cron.yaml à pasta raiz:

Adicione cron.yaml à pasta raiz do seu aplicativo (isso pode ser uma parte do seu repo ou você pode adicionar este arquivo antes de implantar no EB - o importante é que esse arquivo esteja presente no momento da implantação):

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

É isso aí!

Todas as suas tarefas App\Console\Kernelserão agora executadas

Instruções e explicações detalhadas: https://github.com/dusterio/laravel-aws-worker

Como escrever tarefas dentro do Laravel: https://laravel.com/docs/5.4/scheduling

Sebastien Horin
fonte
3

Uma solução mais legível usando em filesvez de container_commands:

arquivos:
  "/etc/cron.d/my_cron":
    modo: "000644"
    proprietário: root
    grupo: root
    conteúdo: |
      # substituir endereço de e-mail padrão
      MAILTO = "[email protected]"
      # executa um comando Symfony a cada cinco minutos (como usuário ec2)
      * / 10 * * * * ec2-user / usr / bin / php / var / app / current / app / console fazer: algo
    codificação: simples
comandos:
  # excluir arquivo de backup criado por Elastic Beanstalk
  clear_cron_backup:
    comando: rm -f /etc/cron.d/watson.bak

Observe que o formato difere do formato usual do crontab porque especifica o usuário para executar o comando.

Tamlyn
fonte
Um problema aqui é que as instâncias do Elastic Beanstalk EC2 não têm serviços SMTP configurados por padrão, então a opção MAILTO aqui pode não funcionar.
Justin Finkelstein
3

Meu 1 centavo de contribuição para 2018

Aqui é o caminho certo para fazê-lo (usando django/pythone django_crontabapp):

dentro da .ebextensionspasta, crie um arquivo como este 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

Precisa ser em container_commandsvez decommands

Ronaldo Bahia
fonte
2

O exemplo mais recente da Amazon é o mais fácil e eficiente (tarefas periódicas):

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

onde você cria uma camada de trabalho separada para executar qualquer um dos seus cron jobs. Crie o arquivo cron.yaml e coloque-o na pasta raiz. Um problema que tive (depois que o cron não parecia estar em execução) foi descobrir que meu CodePipeline não tinha autoridade para realizar uma modificação do dynamodb. Com base nisso, depois de adicionar o acesso FullDynamoDB em IAM -> funções -> seu pipeline e reimplantar (beanstalk elástico), funcionou perfeitamente.

Josh
fonte
1

Então, estamos lutando com isso há algum tempo e, após alguma discussão com um representante da AWS, finalmente descobri o que considero a melhor solução.

Usar uma camada de trabalho com cron.yaml é definitivamente a solução mais fácil. No entanto, o que a documentação não deixa claro é que isso colocará o trabalho no final da fila SQS que você está usando para realmente executar seus trabalhos. Se seus cron jobs são sensíveis ao tempo (como muitos são), isso não é aceitável, pois dependeria do tamanho da fila. Uma opção é usar um ambiente completamente separado apenas para executar tarefas cron, mas acho isso um exagero.

Algumas das outras opções, como verificar se você é a primeira instância da lista, também não são ideais. E se a primeira instância atual estiver em processo de encerramento?

A proteção de instância também pode apresentar problemas - e se essa instância for bloqueada / congelada?

O que é importante entender é como a própria AWS gerencia a funcionalidade cron.yaml. Existe um daemon SQS que usa uma tabela Dynamo para lidar com a "eleição de líder". Ele escreve nesta tabela com frequência e, se o líder atual não escrever há um curto período, a próxima instância assumirá como líder. É assim que o daemon decide qual instância disparar a tarefa na fila SQS.

Podemos redefinir a finalidade da funcionalidade existente em vez de tentar reescrever a nossa própria. Você pode ver a solução completa aqui: https://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27

Isso está em Ruby, mas você pode adaptá-lo facilmente a qualquer outra linguagem que tenha o SDK da AWS. Basicamente, ele verifica o líder atual e, em seguida, verifica o estado para ter certeza de que está em bom estado. Ele fará um loop até que haja um líder atual em bom estado e, se a instância atual for o líder, execute o trabalho.

Cidolfas
fonte
0

Para controlar se o Auto Scaling pode encerrar uma instância específica durante o dimensionamento, use a proteção de instância. Você pode ativar a configuração de proteção da instância em um grupo do Auto Scaling ou em uma instância individual do Auto Scaling. Quando o Auto Scaling inicia uma instância, a instância herda a configuração de proteção de instância do grupo Auto Scaling. Você pode alterar a configuração de proteção de instância para um grupo Auto Scaling ou uma instância Auto Scaling a qualquer momento.

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

Dele
fonte
0

Eu tinha outra solução para isso se um arquivo php precisa ser executado por meio do cron e se você definiu qualquer instância NAT, então você pode colocar o cronjob na instância NAT e executar o arquivo php através do wget.

prasoon
fonte
0

aqui está uma correção caso você queira fazer isso em PHP. Você só precisa de cronjob.config em sua pasta .ebextensions para fazê-lo funcionar assim.

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

o envvars obtém as variáveis ​​de ambiente para os arquivos. Você pode depurar a saída no tmp / sendemail.log como acima.

Espero que isso ajude alguém, pois certamente nos ajudou!

foxybagga
fonte
0

Com base nos princípios da resposta do usuário 1599237 , em que você permite que os cron jobs sejam executados em todas as instâncias, mas, em vez disso, no início dos jobs, determine se eles devem ser executados, fiz outra solução.

Em vez de olhar para as instâncias em execução (e ter que armazenar sua chave e segredo da AWS), estou usando o banco de dados MySQL ao qual já estou me conectando de todas as instâncias.

Não tem desvantagens, apenas pontos positivos:

  • nenhuma instância ou despesas extras
  • solução sólida como uma rocha - sem chance de execução dupla
  • escalável - funciona automaticamente conforme suas instâncias são aumentadas ou reduzidas
  • failover - funciona automaticamente no caso de falha de uma instância

Como alternativa, você também pode usar um sistema de arquivos comumente compartilhado (como AWS EFS por meio do protocolo NFS) em vez de um banco de dados.

A solução a seguir foi criada dentro da estrutura PHP Yii, mas você pode adaptá-la facilmente para outra estrutura e linguagem. Além disso, o manipulador de exceções Yii::$app->systemé um módulo meu. Substitua-o pelo que você estiver usando.

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

Este é o esquema de banco de dados que estou 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`)
)
TheStoryCoder
fonte