AWS Elastic Beanstalk, ejecutando un cronjob

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 si esta es la forma correcta de hacerlo

¿Algunas ideas?

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 para fines ilustrativos. A continuación, 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. Cuando copie esto en su editor de texto, asegúrese de que el editor de texto use espacios en lugar de tabs. De lo contrario, obtendrás un error YAML cuando lo envíes 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.

El comando luego toma los contenidos de un archivo llamado some_cron_job.txt y lo agrega a un archivo llamado some_cron_job en /etc/cron.d.

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

La clave leader_only asegura que el comando solo se ejecute en la instancia de ec2 que se considera el líder. En lugar de ejecutar en cada instancia de ec2 puede que tenga funcionando.

Luego crea 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 cada día como el usuario raíz y descartará la salida a / 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 supone 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 lo indica el comentario. De lo contrario, cron no se ejecutará.

Actualización: hay un problema con esta solución cuando Elastic Beanstalk amplía sus instancias. Por ejemplo, digamos que tiene una instancia con la tarea cron ejecutándose. Obtienes un aumento en el tráfico, por lo que Elastic Beanstalk te permite escalar hasta dos instancias. The leader_only se asegurará de que solo tenga una tarea cron ejecutándose entre las dos instancias. Su tráfico disminuye y Elastic Beanstalk lo reduce a una instancia. Pero en vez de terminar la segunda instancia, Elastic Beanstalk termina la primera instancia que era el líder. Ahora no tiene ninguna tarea cron en ejecución, ya que solo se ejecutaban en la primera instancia que se finalizó. Ver los comentarios a continuación.

Actualización 2: Simplemente dejar esto en claro a partir de los comentarios a continuación: AWS ahora tiene protección contra la terminación automática de la instancia. Simplemente habilítelo en su instancia de líder y estará listo. – Nicolás Arévalo 28 de octubre de 16 a 9:23

Esta es la manera oficial de hacerlo ahora (2015+). Por favor, intente esto primero, es por mucho el método más fácil actualmente disponible y más confiable también.

De acuerdo con 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 stack 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 progtwigr. Especifique esta información usando la syntax crontab estándar.

Actualización: pudimos obtener este trabajo. Aquí hay algunos consejos importantes de nuestra experiencia (plataforma Node.js):

  • Al usar 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) y no solo clonar el anterior.
  • Si desea asegurarse de que CRON sea compatible con su instancia EC2 Worker Tier, ssh ingrese en ella ( eb ssh ) y ejecute cat /var/log/aws-sqsd/default.log . Debe 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.

Con respecto a la respuesta de jamieb, y como menciona Alrdinleal, puede usar la propiedad ‘leader_only’ para asegurarse de que solo una instancia de EC2 ejecute el trabajo de cron.

Cita tomada 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 escalamiento automático. Si el valor de leader_only se establece en true, el comando se ejecuta solo en la instancia que está marcada como el líder.

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

ACTUALIZAR:

Ok, ahora tengo cronjobs trabajando usando la siguiente configuración de 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 cronjobs y luego configuro crontab para que lea desde el archivo temporal, luego borro el archivo temporal luego. Espero que esto ayude.

Como se mencionó anteriormente, el error fundamental con el establecimiento de cualquier configuración de crontab es que solo ocurre en la implementación. A medida que el clúster se escala automáticamente, y luego vuelve a bajar, se prefiere que también sea el primer servidor apagado. Además, no habría fallas, lo que para mí fue crítico.

Investigué un poco, 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 podría dedicar una instancia de EC2 fuera del clúster ElasticBeanstalk, pero luego no volverá a fallar.

Así que 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 el fracaso. Elegí una solución de encoding usando el SDK, que mostraré en PHP, aunque podría hacer el mismo método en el idioma que prefiera.

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

Así que repasando esto y cómo funciona … Usted llama a los scripts de crontab como lo haría normalmente en cada instancia de EC2. Cada secuencia de comandos 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, que si lo hace, de lo contrario, muere y se cierra. Lo he comprobado y la lista devuelta parece ser consistente, que técnicamente solo necesita ser consistente durante un minuto más o menos, ya que cada instancia ejecuta el cron progtwigdo. 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 adecuaba a nuestras necesidades específicas, que no era boost el costo con un servicio adicional o tener una instancia EC2 dedicada, y tendría un fail-over en caso de falla. Nuestros scripts cron ejecutan scripts de mantenimiento que se colocan en SQS y cada servidor en el clúster ayuda a ejecutar. Al menos, esto puede darle una opción alternativa si se ajusta a sus necesidades.

-Davey

Si usa Rails, puede usar la gem ever-elasticbeanlk . Le permite ejecutar tareas cron en todas las instancias o solo en una. Comprueba cada minuto para asegurarse de que solo haya una instancia de “líder” y automáticamente promocionará 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 se escala.

ACTUALIZACIÓN Cambié a usar AWS OpsWorks y ya no mantengo esta joya. Si necesita más funcionalidades de las disponibles en los fundamentos de Elastic Beanstalk, le recomiendo cambiar a OpsWorks.

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

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

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

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

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

   "/usr/local/bin/test_cron.sh":
     modo: "000755"
     propietario: raíz
     grupo: raíz
     contenido: |
       #! / 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)}' `

       # Encuentra nuestro nombre del Grupo de escalamiento automático.
       ASG = `aws ec2 describe-tags --filters" Nombre = resource-id, Values ​​= $ INSTANCE_ID "\
         --region $ REGION - texto de salida |  awk '/ aws: autoscaling: groupName / {print $ 5}' `

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

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

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

Esta solución tiene 2 inconvenientes:

  1. En implementaciones posteriores, Beanstalk cambia el nombre del script cron existente como .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 script cron. Esto significa que sus mensajes de correo se repiten, o los archivos de su base de datos duplicados

Solución:

  1. Asegúrese de que cualquier script .ebextensions que crea 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 escalamiento automático de los Tags EC2 – Obtiene la lista de Instancias EC2 en ese Grupo, ordenados alfabéticamente. – Toma la primera instancia de esa lista. – Compara la ID de instancia del paso 1 con la primera ID de instancia desde el paso 4. Sus scripts cron pueden usar esta secuencia de comandos auxiliar para determinar si deberían ejecutarse.

Advertencia:

  • El rol de IAM utilizado para las instancias de Beanstalk necesita ec2: DescribeTags y autoescala: Describe los permisos de AutoScalingGroups
  • Las instancias elegidas son aquellas mostradas como InService por Auto Scaling. Esto no significa necesariamente que estén completamente iniciados y listos para ejecutar tu cron.

No debería establecer las funciones de IAM si está utilizando la función de beanstalk predeterminada.

Realmente no desea ejecutar trabajos cron en Elastic Beanstalk. Dado que tendrá múltiples instancias de aplicación, esto puede causar condiciones de carrera y otros problemas extraños. De hecho, publiqué recientemente este blog (4to o 5to consejo en la página). La versión corta: según la aplicación, utilice una cola de trabajos como SQS o una solución de terceros como iron.io.

Una solución más legible que usa files lugar de container_commands :

 archivos:
   "/etc/cron.d/my_cron":
     modo: "000644"
     propietario: raíz
     grupo: raíz
     contenido: |
       # anular la dirección de correo electrónico predeterminada
       MAILTO = "example@gmail.com"
       # ejecutar un comando Symfony cada cinco minutos (como usuario ec2)
       * / 10 * * * * ec2-user / usr / bin / php / var / app / current / app / console do: algo
     encoding: simple
 comandos:
   # borrar archivo de copia de seguridad 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 como.

Alguien se preguntaba sobre los problemas de escalamiento automático del líder cuando surgen nuevos líderes. Parece que no puedo entender cómo responder a sus comentarios, pero vea este enlace: http://blog.paulopoiati.com/2013/08/25/running-cron-in-elastic-beanstalk-auto-scaling- ambiente/

Para controlar si Auto Scaling puede terminar una instancia particular al escalar, use la protección de instancias. Puede habilitar la configuración de protección de instancia en un grupo de Escalado automático o una instancia de escalado automático individual. Cuando Auto Scaling inicia una instancia, la instancia hereda la configuración de protección de instancia del grupo Auto Scaling. Puede cambiar la configuración de protección de instancias para un grupo de escalamiento automático o una instancia de escalamiento automático en cualquier momento.

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

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 de NAT y ejecutar el archivo php a través de wget.

2017: si estás usando Laravel5 +

Solo necesitas 2 minutos para configurarlo:

  • crea 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 repository 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!

Toda su tarea en App\Console\Kernel ahora se ejecutará

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

Así que hemos estado luchando con esto por un tiempo y después de un debate con un representante de AWS finalmente he llegado a 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 en claro es que esto colocará el trabajo al final de la cola de SQS que está utilizando para ejecutar realmente sus trabajos. Si sus trabajos cron son sensibles al tiempo (como muchos lo son), esto no es aceptable, ya que dependerá del tamaño de la cola. Una opción es utilizar un entorno completamente separado solo para ejecutar trabajos cron, pero creo que eso es exagerado.

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

La protección de instancia también puede venir con problemas, ¿qué ocurre si esa instancia se bloquea / congela?

Lo que es importante entender es cómo AWS administra la funcionalidad cron.yaml. Hay un daemon de SQS que usa una tabla de Dynamo para manejar la “elección de líder”. Escribe en esta tabla con frecuencia, y si el líder actual no ha escrito en un momento, la siguiente instancia asumirá como 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. Esencialmente, verifica al líder actual, luego verifica el estado para asegurarse de que esté en buen estado. Continuará hasta que haya un líder actual en buen estado, y si la instancia actual es el líder, ejecuta el trabajo.

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

 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 

los envvars obtienen 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 sin duda nos ayudó.

Mi 1 centavo de contribución para 2018

Esta es la forma correcta de hacerlo (usando las aplicaciones django/python y django_crontab ):

dentro de la carpeta .ebextensions 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 

Necesita ser container_commands lugar de commands