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?
Respuestas:
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
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:
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
fuente
01_some_cron_job
a02_some_cron_job
y añadió01_remove_cron_jobs
lo siguiente:command: "rm /etc/cron.d/cron_jobs || exit 0"
. De esa manera, después de cada despliegue, solo el líder tendrá elcron_jobs
archivo. Si los líderes cambian, simplemente puede volver a desplegarse y los crons se arreglarán para que se ejecuten una vez más.leader_only
propiedad. Solo se usa durante la implementación y si reduce la escala o su instancia de "líder" falla, esEsta 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:
También es interesante la parte sobre cron.yaml :
Actualización: pudimos hacer que este trabajo. A continuación, se muestran algunos errores importantes de nuestra experiencia (plataforma Node.js):
eb ssh
) y ejecutecat /var/log/aws-sqsd/default.log
. Debería informar comoaws-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.fuente
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 :
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:
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.
fuente
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.
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
fuente
$instanceId = file_get_contents("http://instance-data/latest/meta-data/instance-id");
Luego solo use ese $ instanceId var para hacer la comparación.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:
Esta solución tiene 2 inconvenientes:
Solución alterna:
Consideración:
No tendría que configurar los roles de IAM si usa el rol de beanstalk predeterminado.
fuente
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.
fuente
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 .
fuente
2017: si está utilizando Laravel5 +
Solo necesitas 2 minutos para configurarlo:
instalar laravel-aws-worker
composer require dusterio/laravel-aws-worker
agregue un cron.yaml a la carpeta raíz:
¡Eso es!
App\Console\Kernel
Ahora se ejecutará toda su tarea enInstrucciones detalladas y explicaciones: https://github.com/dusterio/laravel-aws-worker
Cómo escribir tareas dentro de Laravel: https://laravel.com/docs/5.4/scheduling
fuente
Una solución más legible usando en
files
lugar decontainer_commands
:Tenga en cuenta que el formato difiere del formato crontab habitual en que especifica el usuario para ejecutar el comando.
fuente
Mi 1 centavo de contribución para 2018
Esta es la forma correcta de hacerlo (usando
django/python
ydjango_crontab
aplicación):dentro de la
.ebextensions
carpeta crea un archivo como este98_cron.config
:Tiene que ser en
container_commands
lugar decommands
fuente
Alguien se preguntaba acerca de los problemas de escalamiento automático de líder_sólo 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/
fuente
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.
fuente
Aquí hay una explicación completa de la solución:
http://blog.paulopoiati.com/2013/08/25/running-cron-in-elastic-beanstalk-auto-scaling-environment/
fuente
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.
fuente
http://docs.aws.amazon.com/autoscaling/latest/userguide/as-instance-termination.html#instance-protection
fuente
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.
fuente
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í.
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!
fuente
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:
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->system
es 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`) )
fuente