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->system
es un módulo propio. Reemplácelo con lo que esté usando.
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 (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;
}
}
}
$updateColumns = false;
$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();
if ($affectedRows == 0) {
$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();
if ($affectedRows == 0) {
exit;
}
}
$command = str_replace('StdOUT', '>', $command);
$command = str_replace('StdERR.ditto', '2>&1', $command);
$command = str_replace('StdERR', '2>', $command);
$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`)
)