AWS Elastic Beanstalk, ejecutando un cronjob

89

Me gustaría saber si hay una manera de configurar un cronjob / tarea para ejecutar cada minuto. Actualmente, cualquiera de mis instancias debería poder ejecutar esta tarea.

Esto es lo que he intentado hacer en los archivos de configuración sin éxito:

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

No estoy seguro de si esta es la forma correcta de hacerlo.

¿Algunas ideas?

Onema
fuente
1
¿Es correcto el comando? Quiero decir ... podría ser: command: echo "* / 1 * * * * root php /etc/httpd/myscript.php"> /etc/cron.d/something De cualquier manera, le sugiero que use el bandera leader_only, de lo contrario, todas las máquinas iniciarán este trabajo cron a la vez
aldrinleal
¡Si! Definitivamente usando la bandera leader_only, intentaré cambiar el comando.
Onema

Respuestas:

97

Así es como agregué un trabajo cron a Elastic Beanstalk:

Cree una carpeta en la raíz de su aplicación llamada .ebextensions si aún no existe. Luego crea un archivo de configuración dentro de la carpeta .ebextensions. Usaré example.config con fines ilustrativos. Luego agregue esto 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 es un archivo de configuración YAML para Elastic Beanstalk. Asegúrese de que cuando copie esto en su editor de texto, su editor de texto use espacios en lugar de pestañas. De lo contrario, obtendrá un error YAML cuando lo envíe a EB.

Entonces, lo que hace es crear un comando llamado 01_some_cron_job. Los comandos se ejecutan en orden alfabético, por lo que 01 se asegura de que se ejecute como el primer comando.

Luego, el comando toma el contenido de un archivo llamado some_cron_job.txt y lo agrega a un archivo llamado some_cron_job en /etc/cron.d.

Luego, el comando cambia los permisos en el archivo /etc/cron.d/some_cron_job.

La clave leader_only garantiza que el comando solo se ejecute en la instancia ec2 que se considera líder. En lugar de ejecutarse en cada instancia ec2 que pueda tener en ejecución.

Luego cree un archivo llamado some_cron_job.txt dentro de la carpeta .ebextensions. Colocará sus trabajos cron en este archivo.

Así por ejemplo:

# 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

Por lo tanto, este trabajo cron se ejecutará cada minuto de cada hora de todos los días como usuario root y descartará el resultado en / dev / null. / usr / bin / php es la ruta a php. Luego reemplace some-php-script-here con la ruta a su archivo php. Obviamente, esto es asumiendo que su trabajo cron necesita ejecutar un archivo PHP.

Además, asegúrese de que el archivo some_cron_job.txt tenga una nueva línea al final del archivo, tal como dice el comentario. De lo contrario, cron no se ejecutará.

Actualización: hay un problema con esta solución cuando Elastic Beanstalk escala sus instancias. Por ejemplo, digamos que tiene una instancia con el trabajo cron en ejecución. Obtiene un aumento en el tráfico, por lo que Elastic Beanstalk lo escala hasta dos instancias. Leader_only se asegurará de que solo tenga un trabajo cron ejecutándose entre las dos instancias. Su tráfico disminuye y Elastic Beanstalk lo reduce a una instancia. Pero en lugar de terminar la segunda instancia, Elastic Beanstalk termina la primera instancia que era líder. Ahora no tiene ningún trabajo cron en ejecución, ya que solo se estaban ejecutando en la primera instancia que finalizó. Vea los comentarios a continuación.

Actualización 2: Solo aclaro esto a partir de los comentarios a continuación: AWS ahora tiene protección contra la terminación automática de instancias. Solo habilítelo en su instancia de líder y listo. - Nicolás Arévalo 28 de octubre de 2016 a las 9:23

Anarchtica
fuente
12
He estado usando su sugerencia durante algún tiempo y recientemente encontré un problema en el que de alguna manera el líder cambió, lo que resultó en varias instancias al ejecutar cron. Para resolver ese problema, he cambiado 01_some_cron_joba 02_some_cron_joby añadió 01_remove_cron_jobslo siguiente: command: "rm /etc/cron.d/cron_jobs || exit 0". De esa manera, después de cada despliegue, solo el líder tendrá el cron_jobsarchivo. Si los líderes cambian, simplemente puede volver a desplegarse y los crons se arreglarán para que se ejecuten una vez más.
Willem Renzema
4
Sugeriría no depender de la leader_onlypropiedad. Solo se usa durante la implementación y si reduce la escala o su instancia de "líder" falla, es
probable
2
No hagas esto. Es demasiado poco confiable. La única forma de que esto funcione es ejecutando una micro instancia y ejecutando trabajos cron desde allí usando CURL. Esto garantiza que solo una instancia lo ejecuta y el líder que tiene crons instalados no se termina.
Ben Sinclair
1
Traté de solucionar este problema con un pequeño script ruby, lo puede encontrar aquí: github.com/SocialbitGmbH/AWSBeanstalkLeaderManager
Thomas Kekeisen
8
AWS ahora tiene protección contra la terminación automática de instancias. Solo habilítelo en su instancia de líder y listo.
Nicolás Arévalo
58

Esta es la forma oficial de hacerlo ahora (2015+). Intente esto primero, es el método más fácil disponible actualmente y también el más confiable.

Según los documentos actuales, uno puede ejecutar tareas periódicas en su llamado nivel de trabajador .

Citando la documentación:

AWS Elastic Beanstalk admite tareas periódicas para niveles de entorno de trabajo en entornos que ejecutan una configuración predefinida con una pila de soluciones que contiene "v1.2.0" en el nombre del contenedor. Debes crear un nuevo entorno.

También es interesante la parte sobre cron.yaml :

Para invocar tareas periódicas, el paquete fuente de la aplicación debe incluir un archivo cron.yaml en el nivel raíz. El archivo debe contener información sobre las tareas periódicas que desea programar. Especifique esta información utilizando la sintaxis estándar de crontab.

Actualización: pudimos hacer que este trabajo. A continuación, se muestran algunos errores importantes de nuestra experiencia (plataforma Node.js):

  • Cuando use el archivo cron.yaml , asegúrese de tener la última awsebcli , porque las versiones anteriores no funcionarán correctamente.
  • También es vital crear un nuevo entorno (al menos en nuestro caso lo fue), no solo clonar el antiguo.
  • Si desea asegurarse de que CRON sea compatible con su instancia de nivel de trabajador EC2, ssh en él ( eb ssh) y ejecute cat /var/log/aws-sqsd/default.log. Debería informar como aws-sqsd 2.0 (2015-02-18). Si no tiene la versión 2.0, algo salió mal al crear su entorno y necesita crear uno nuevo como se indicó anteriormente.
xaralis
fuente
2
Acerca de cron.yaml, hay una publicación de blog increíble: Ejecución de trabajos cron en Amazon Web Services (AWS) Elastic Beanstalk - Medium
jwako
5
Gracias por esto, pregunta de novato, necesito que mi cron revise la base de datos de mi aplicación web dos veces por hora para ver los próximos eventos del calendario y envíe un correo electrónico recordatorio cuando lo haga. ¿Cuál es la mejor configuración aquí? ¿Debería hacer que la URL cron.yaml apunte a una ruta en mi aplicación web? ¿O debería dar acceso a la base de datos a la aplicación env de mi trabajador? ¡Tan poco en esto!
cristiano
5
@christian De la forma en que lo hacemos, tenemos la misma aplicación ejecutándose en dos entornos diferentes (por lo tanto, no se necesita una configuración especial): trabajador y servidor web común. El entorno de trabajo tiene algunas rutas especiales habilitadas al establecer una variable ENV que busca nuestra aplicación. De esta manera, puede establecer rutas especiales solo para trabajadores en su cron.yaml mientras tiene el lujo de compartir una base de código con la aplicación normal. Su aplicación de trabajador puede acceder fácilmente a los mismos recursos que el servidor web uno: base de datos, modelos, etc.
xaralis
1
@JaquelinePassos v1.2.0 es la versión de la pila de soluciones. Debería permitirle elegir qué versión de la pila de soluciones desea crear al crear un nuevo entorno. Cualquier cosa más nueva que la v1.2.0 debería funcionar. Con respecto a la URL, debe ser la URL en la que escucha su aplicación, no una ruta de archivo. No es posible ejecutar comandos de administración de Django, solo realiza solicitudes HTTP.
xaralis
4
Una cosa que no me queda clara es si hay una manera de evitar tener que asignar una máquina EC2 adicional solo para ejecutar los trabajos cron a través de cron.yaml. Idealmente, se ejecutaría en la misma máquina que la que atiende las solicitudes HTTP (es decir, nivel web).
Wenzel Jakob
31

Con respecto a la respuesta de Jamieb, y como menciona alrdinleal, puede usar la propiedad 'leader_only' para asegurarse de que solo una instancia EC2 ejecute el trabajo cron.

Cita extraída de http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html :

puedes usar leader_only. Se elige una instancia para ser el líder en un grupo de Auto Scaling. Si el valor de leader_only se establece en verdadero, el comando se ejecuta solo en la instancia que está marcada como líder.

Estoy tratando de lograr algo similar en mi eb, así que actualizaré mi publicación si lo resuelvo.

ACTUALIZAR:

Ok, ahora tengo cronjobs funcionando usando la siguiente configuración 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

Esencialmente, creo un archivo temporal con los cronjobs y luego configuro el crontab para que lea desde el archivo temporal, luego elimino el archivo temporal. Espero que esto ayude.

mejor que la vida
fuente
3
¿Cómo se aseguraría de que el escalado automático no termine la instancia que ejecuta este crontab? De forma predeterminada, finaliza la instancia más antigua.
Sebastien
1
Ese es un problema que aún no he podido resolver. Me parece una falla en la funcionalidad de amazon que los comandos de leader_only no se apliquen a un nuevo líder cuando EB finaliza el actual. Si se te ocurre algo, ¡compártelo!
beterthanlife
7
Así que (finalmente) descubrí cómo evitar que el líder sea despedido por autoescalado: políticas de terminación de autoescalado personalizadas. Consulte docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/…
beterthanlife
1
@Nate Probablemente ya te hayas dado cuenta de esto, pero según mi lectura del orden en el que se ejecutan, los "comandos" se ejecutan antes que los "comandos_contenedores", por lo que deberías crear el archivo, luego eliminarlo y luego intentar ejecutar el crontab. .
clearf
1
@Sebastien para mantener el intance más antiguo, esto es lo que hago: 1 - cambio la protección de terminación del intance a ENBABLE. 2 - Vaya a Auto Scale Group y busque su ID de entorno de EBS, haga clic en EDITAR y cambie las Políticas de terminación a "NewestInstance"
Ronaldo Bahia
12

Como se mencionó anteriormente, el defecto fundamental al establecer cualquier configuración crontab es que solo ocurre en la implementación. A medida que el clúster se escala automáticamente hacia arriba y luego hacia abajo, se prefiere que también sea el primer servidor que se apaga. Además, no habría fallas, lo que para mí era fundamental.

Investigué un poco y luego hablé con nuestro especialista en cuentas de AWS para intercambiar ideas y validar la solución que se me ocurrió. Puede lograr esto con OpsWorks , aunque es como usar una casa para matar una mosca. También es posible usar Data Pipeline con Task Runner , pero esto tiene una capacidad limitada en los scripts que puede ejecutar, y necesitaba poder ejecutar scripts PHP, con acceso a todo el código base. También puede dedicar una instancia EC2 fuera del clúster de ElasticBeanstalk, pero luego no tendrá conmutación por error nuevamente.

Entonces, esto es lo que se me ocurrió, que aparentemente no es convencional (como comentó el representante de AWS) y puede considerarse un truco, pero funciona y es sólido con fallas. Elegí una solución de codificación usando el SDK, que mostraré en PHP, aunque puedes hacer el mismo método en el idioma que prefieras.

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

Entonces, repasando esto y cómo funciona ... Llame a los scripts desde crontab como lo haría normalmente en cada instancia EC2. Cada script incluye esto al principio (o incluye un solo archivo para cada uno, como lo uso), que establece un objeto ElasticBeanstalk y recupera una lista de todas las instancias. Utiliza solo el primer servidor de la lista y comprueba si coincide con él mismo, que si lo hace continúa, de lo contrario muere y se cierra. Lo he comprobado y la lista devuelta parece ser coherente, lo que técnicamente solo necesita ser coherente durante aproximadamente un minuto, ya que cada instancia ejecuta el cron programado. Si cambia, no importaría, ya que nuevamente solo es relevante para esa pequeña ventana.

Esto no es elegante de ninguna manera, pero se adapta a nuestras necesidades específicas, que no era aumentar el costo con un servicio adicional o tener una instancia EC2 dedicada, y tendría conmutación por error en caso de falla. Nuestros scripts cron ejecutan scripts de mantenimiento que se colocan en SQS y cada servidor del clúster ayuda a ejecutarse. Al menos, esto puede darle una opción alternativa si se ajusta a sus necesidades.

-Davey

usuario1599237
fuente
Descubrí que php_uname ('n') devuelve el nombre de DNS privado (por ejemplo, ip-172.24.55.66), que no es el ID de instancia que está buscando. En lugar de usar php_uname (), terminé usando esto: $instanceId = file_get_contents("http://instance-data/latest/meta-data/instance-id"); Luego solo use ese $ instanceId var para hacer la comparación.
Valorum
1
¿Hay alguna garantía de que la matriz Instances presente el mismo orden en cada llamada Describe? Sugeriría extraer el campo ['Id'] de cada entrada en una matriz y ordenarlos en PHP, antes de verificar si la primera entrada ordenada es su instanceId actual.
Gabriel
Basándome en esta respuesta, hice esta solución: stackoverflow.com/questions/14077095/… - es muy similar pero NO tiene posibilidad de doble ejecución.
TheStoryCoder
11

Hablé con un agente de soporte de AWS y así es como hicimos que esto funcionara para mí. Solución 2015:

Cree un archivo en su directorio .ebextensions con your_file_name.config. En la entrada del archivo de configuración:

archivos:
  "/etc/cron.d/cron_example":
    modo: "000644"
    propietario: root
    grupo: raíz
    contenido: |
      * * * * * raíz /usr/local/bin/cron_example.sh

  "/usr/local/bin/cron_example.sh":
    modo: "000755"
    propietario: root
    grupo: raíz
    contenido: |
      #! / bin / bash

      /usr/local/bin/test_cron.sh || salida
      echo "Cron ejecutándose en" `date` >> /tmp/cron_example.log
      # Ahora haga tareas que solo deberían ejecutarse en 1 instancia ...

  "/usr/local/bin/test_cron.sh":
    modo: "000755"
    propietario: root
    grupo: raíz
    contenido: |
      #! / bin / bash

      METADATA = / opt / aws / bin / ec2-metadata
      INSTANCE_ID = `$ METADATA -i | awk '{imprimir $ 2}' `
      REGIÓN = `$ METADATA -z | awk '{print substr ($ 2, 0, longitud ($ 2) -1)}' `

      # Busque el nombre de nuestro grupo de Auto Scaling.
      ASG = `aws ec2 describe-tags --filters" Nombre = id-recurso, Valores = $ INSTANCE_ID "\
        --region $ REGION - texto de salida | awk '/ aws: autoescalado: groupName / {print $ 5}' `

      # Encuentra la primera instancia en el grupo
      FIRST = `aws autoescaling describe-auto-scaling-groups --auto-scaling-group-names $ ASG \
        --region $ REGION - texto de salida | awk '/ InService $ / {print $ 4}' | ordenar | cabeza -1`

      # Prueba si son iguales.
      ["$ FIRST" = "$ INSTANCE_ID"]

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

Esta solución tiene 2 inconvenientes:

  1. En implementaciones posteriores, Beanstalk cambia el nombre del script cron existente a .bak, pero cron aún lo ejecutará. Su Cron ahora se ejecuta dos veces en la misma máquina.
  2. Si su entorno se amplía, obtendrá varias instancias, todas ejecutando su secuencia de comandos cron. Esto significa que sus mensajes de correo se repiten o los archivos de su base de datos se duplican

Solución alterna:

  1. Asegúrese de que cualquier script .ebextensions que cree un cron también elimine los archivos .bak en implementaciones posteriores.
  2. Tener un script auxiliar que haga lo siguiente: - Obtiene el ID de instancia actual de los metadatos - Obtiene el nombre actual del grupo de Auto Scaling de las etiquetas EC2 - Obtiene la lista de instancias EC2 de ese grupo, ordenadas alfabéticamente. - Toma la primera instancia de esa lista. - Compara el ID de instancia del paso 1 con el primer ID de instancia del paso 4. Sus scripts cron pueden usar este script auxiliar para determinar si deben ejecutarse.

Consideración:

  • El rol de IAM utilizado para las instancias de Beanstalk necesita los permisos ec2: DescribeTags y autoescalado: DescribeAutoScalingGroups
  • Las instancias elegidas son las que Auto Scaling muestra como InService. Esto no significa necesariamente que estén completamente iniciados y listos para ejecutar su cron.

No tendría que configurar los roles de IAM si usa el rol de beanstalk predeterminado.

Conocido
fuente
7

Si está usando Rails, puede usar la gema always-elasticbeanstalk . Le permite ejecutar trabajos cron en todas las instancias o solo en una. Verifica cada minuto para asegurarse de que solo haya una instancia de "líder" y automáticamente promoverá un servidor a "líder" si no hay ninguno. Esto es necesario ya que Elastic Beanstalk solo tiene el concepto de líder durante la implementación y puede cerrar cualquier instancia en cualquier momento mientras escala.

ACTUALIZACIÓN Cambié a usar AWS OpsWorks y ya no mantengo esta joya. Si necesita más funcionalidad de la que está disponible en los conceptos básicos de Elastic Beanstalk, le recomiendo que cambie a OpsWorks.

dignoe
fuente
¿Te importaría contarnos cómo lo resolviste usando OpsWorks? ¿Está ejecutando capas personalizadas que hacen los trabajos cron?
Tommie
Sí, tengo una capa de administrador / cron que solo se ejecuta en un servidor. Configuré un libro de cocina personalizado que contiene todos mis trabajos cron. AWS tiene una guía en docs.aws.amazon.com/opsworks/latest/userguide/… .
dignoe
@dignoe si asigna un servidor para ejecutar trabajos cron usando OpsWorks, lo mismo con Elastic Beanstalk, puedo usar un entorno con un servidor para ejecutar trabajos cron. Incluso con Load Balancer, las instancias máxima y mínima configuradas en uno, para conservar siempre al menos una instancia de servidor.
Jose Nobile
6

Realmente no desea ejecutar trabajos cron en Elastic Beanstalk. Dado que tendrá varias instancias de aplicación, esto puede causar condiciones de carrera y otros problemas extraños. De hecho, recientemente escribí en un blog sobre esto (cuarto o quinto consejo en la página). La versión corta: Dependiendo de la aplicación, utilizar una cola de trabajos como SQS o una solución de terceros, como iron.io .

Jamieb
fuente
SQS no garantiza que el código solo se ejecutará una vez. Me gusta el sitio iron.io, lo voy a comprobar.
Nathan H
También en la publicación de su blog, recomienda usar InnoDB en RDS. Utilizo una tabla en RDS para almacenar mis tareas y uso la función "SELECCIONAR ... PARA ACTUALIZAR" de InnoDB para asegurarme de que solo un servidor ejecuta esas tareas. ¿Cómo se comunica su aplicación con SQS sin un trabajo cron o la interacción del usuario?
James Alday
1
@JamesAlday Esta pregunta SO es bastante antigua. Desde que escribí el comentario anterior, AWS presentó una forma elegante de manejar trabajos cron en Elastic Beanstalk al elegir uno de los servidores en ejecución como maestro. Habiendo dicho eso, parece que estás haciendo un mal uso de cron + MySQL como cola de trabajos. Sin embargo, necesitaría saber mucho sobre su aplicación antes de poder ofrecer recomendaciones concretas.
Jamieb
Tengo un script que se ejecuta a través de cron que verifica una tabla para ver los trabajos que se ejecutarán. El uso de transacciones evita que varios servidores ejecuten el mismo trabajo. He investigado SQS, pero necesita un servidor maestro que ejecute todos los scripts en lugar de distribuirlos y aún necesita escribir lógica para asegurarse de no ejecutar el mismo script varias veces. Pero todavía estoy confundido acerca de cómo puede hacer que las tareas se ejecuten sin la interacción del usuario o cron: ¿qué hace que su aplicación ejecute las tareas en cola?
James Alday
5

2017: si está utilizando Laravel5 +

Solo necesitas 2 minutos para configurarlo:

  • crear un nivel de trabajador
  • instalar laravel-aws-worker

    composer require dusterio/laravel-aws-worker

  • agregue un cron.yaml a la carpeta raíz:

Agregue cron.yaml a la carpeta raíz de su aplicación (esto puede ser parte de su repositorio o puede agregar este archivo justo antes de implementarlo en EB; lo importante es que este archivo está presente en el momento de la implementación):

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

¡Eso es!

App\Console\KernelAhora se ejecutará toda su tarea en

Instrucciones detalladas y explicaciones: https://github.com/dusterio/laravel-aws-worker

Cómo escribir tareas dentro de Laravel: https://laravel.com/docs/5.4/scheduling

Sebastien Horin
fuente
3

Una solución más legible usando en fileslugar de container_commands:

archivos:
  "/etc/cron.d/my_cron":
    modo: "000644"
    propietario: root
    grupo: raíz
    contenido: |
      # anular la dirección de correo electrónico predeterminada
      MAILTO = "[email protected]"
      # ejecutar un comando de Symfony cada cinco minutos (como ec2-user)
      * / 10 * * * * ec2-user / usr / bin / php / var / app / current / app / console hacer: algo
    codificación: llano
comandos:
  # eliminar el archivo de respaldo creado por Elastic Beanstalk
  clear_cron_backup:
    comando: rm -f /etc/cron.d/watson.bak

Tenga en cuenta que el formato difiere del formato crontab habitual en que especifica el usuario para ejecutar el comando.

Tamlyn
fuente
Un problema aquí es que las instancias EC2 de Elastic Beanstalk no tienen los servicios SMTP configurados de forma predeterminada, por lo que la opción MAILTO aquí podría no funcionar.
Justin Finkelstein
3

Mi 1 centavo de contribución para 2018

Esta es la forma correcta de hacerlo (usando django/pythony django_crontabaplicación):

dentro de la .ebextensionscarpeta crea un archivo 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

Tiene que ser en container_commandslugar decommands

Ronaldo Bahía
fuente
2

El último ejemplo de Amazon es el más fácil y eficiente (tareas periódicas):

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

donde crea un nivel de trabajador separado para ejecutar cualquiera de sus trabajos cron. Crea el archivo cron.yaml y colócalo en tu carpeta raíz. Un problema que tuve (después de que pareciera que cron no se estaba ejecutando) fue encontrar que mi CodePipeline no tenía autoridad para realizar una modificación de dynamodb. En base a eso, después de agregar el acceso FullDynamoDB en IAM -> roles -> yourpipeline y volver a implementar (beanstalk elásticos), funcionó perfectamente.

Josh
fuente
1

Así que hemos estado luchando con esto durante un tiempo y, después de una discusión con un representante de AWS, finalmente se me ocurrió lo que creo que es la mejor solución.

Usar un nivel de trabajador con cron.yaml es definitivamente la solución más fácil. Sin embargo, lo que la documentación no deja claro es que esto colocará el trabajo al final de la cola de SQS que está utilizando para ejecutar sus trabajos. Si sus trabajos cron son sensibles al tiempo (como muchos), esto no es aceptable, ya que dependería del tamaño de la cola. Una opción es usar un entorno completamente separado solo para ejecutar trabajos cron, pero creo que eso es excesivo.

Algunas de las otras opciones, como verificar para ver si eres la primera instancia en la lista, tampoco son ideales. ¿Qué pasa si la primera instancia actual está en proceso de cerrarse?

La protección de instancias también puede tener problemas: ¿qué pasa si esa instancia se bloquea / congela?

Lo que es importante comprender es cómo AWS administra la funcionalidad cron.yaml. Hay un demonio SQS que usa una tabla Dynamo para manejar la "elección del líder". Escribe en esta tabla con frecuencia, y si el líder actual no ha escrito en un corto período de tiempo, la siguiente instancia asumirá el cargo de líder. Así es como el daemon decide qué instancia lanzar el trabajo en la cola de SQS.

Podemos reutilizar la funcionalidad existente en lugar de intentar reescribir la nuestra. Puede ver la solución completa aquí: https://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27

Eso está en Ruby, pero puede adaptarlo fácilmente a cualquier otro idioma que tenga AWS SDK. Básicamente, verifica el líder actual y luego verifica el estado para asegurarse de que esté en buen estado. Se repetirá hasta que haya un líder actual en buen estado y, si la instancia actual es el líder, ejecutará el trabajo.

Cidolfas
fuente
0

Para controlar si Auto Scaling puede terminar una instancia en particular al escalar, use la protección de instancia. Puede habilitar la configuración de protección de la instancia en un grupo de Auto Scaling o una instancia individual de Auto Scaling. Cuando Auto Scaling lanza una instancia, la instancia hereda la configuración de protección de la instancia del grupo de Auto Scaling. Puede cambiar la configuración de protección de la instancia para un grupo de Auto Scaling o una instancia de Auto Scaling en cualquier momento.

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

Siglo de dele
fuente
0

Tenía otra solución para esto si un archivo php necesita ejecutarse a través de cron y si había configurado alguna instancia NAT, entonces puede poner cronjob en la instancia NAT y ejecutar el archivo php a través de wget.

prasoon
fuente
0

aquí hay una solución en caso de que desee hacer esto en PHP. Solo necesita cronjob.config en su carpeta .ebextensions para que funcione así.

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 obtiene las variables de entorno para los archivos. Puede depurar la salida en tmp / sendemail.log como se indicó anteriormente.

¡Espero que esto ayude a alguien ya que seguramente nos ayudó a nosotros!

foxybagga
fuente
0

Según los principios de la respuesta del usuario1599237 , donde deja que los trabajos cron se ejecuten en todas las instancias, pero luego, al comienzo de los trabajos, se determina si se debe permitir que se ejecuten, he creado otra solución.

En lugar de mirar las instancias en ejecución (y tener que almacenar su clave y secreto de AWS), estoy usando la base de datos MySQL a la que ya me estoy conectando desde todas las instancias.

No tiene inconvenientes, solo aspectos positivos:

  • sin instancias ni gastos extra
  • solución sólida como una roca - sin posibilidad de doble ejecución
  • escalable: funciona automáticamente a medida que sus instancias se escalan hacia arriba y hacia abajo
  • Conmutación por error: funciona automáticamente en caso de que una instancia tenga una falla.

Alternativamente, también puede usar un sistema de archivos comúnmente compartido (como AWS EFS a través del protocolo NFS) en lugar de una base de datos.

La siguiente solución se crea dentro del marco PHP Yii, pero puede adaptarla fácilmente para otro marco y lenguaje. Además, el controlador de excepciones Yii::$app->systemes un módulo propio. Reemplácelo con lo que esté 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 es el esquema de base de datos que estoy 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
fuente