我想知道是否有办法设置一个 cronjob/task 每分钟执行一次。目前,我的任何实例都应该能够运行此任务。
这是我在配置文件中尝试做的但没有成功:
container_commands:
01cronjobs:
command: echo "*/1 * * * * root php /etc/httpd/myscript.php"
我不确定这是否是正确的方法
有任何想法吗?
我想知道是否有办法设置一个 cronjob/task 每分钟执行一次。目前,我的任何实例都应该能够运行此任务。
这是我在配置文件中尝试做的但没有成功:
container_commands:
01cronjobs:
command: echo "*/1 * * * * root php /etc/httpd/myscript.php"
我不确定这是否是正确的方法
有任何想法吗?
在应用程序的根目录下创建一个名为 .ebextensions 的文件夹(如果该文件夹尚不存在)。然后在 .ebextensions 文件夹中创建一个配置文件。我将使用 example.config 进行说明。然后将其添加到 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
这是 Elastic Beanstalk 的 YAML 配置文件。确保将其复制到文本编辑器中时,文本编辑器使用空格而不是制表符。否则,当您将其推送到 EB 时,您会收到 YAML 错误。
所以它的作用是创建一个名为 01_some_cron_job 的命令。命令按字母顺序运行,因此 01 确保它作为第一个命令运行。
然后该命令获取名为 some_cron_job.txt 的文件的内容,并将其添加到 /etc/cron.d 中名为 some_cron_job 的文件中。
然后该命令更改 /etc/cron.d/some_cron_job 文件的权限。
leader_only 键确保该命令仅在被视为领导者的 ec2 实例上运行。而不是在您可能正在运行的每个 ec2 实例上运行。
然后在 .ebextensions 文件夹中创建一个名为 some_cron_job.txt 的文件。你将把你的 cron 作业放在这个文件中。
例如:
# 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
因此,这个 cron 作业将以 root 用户身份在每天每一小时的每一分钟运行,并将输出丢弃到 /dev/null。/usr/bin/php 是 php 的路径。然后将 some-php-script-here 替换为您的 php 文件的路径。这显然是假设您的 cron 作业需要运行 PHP 文件。
此外,请确保 some_cron_job.txt 文件在文件末尾有一个换行符,就像评论说的那样。否则 cron 将无法运行。
更新: 当 Elastic Beanstalk 扩展您的实例时,此解决方案存在问题。例如,假设您有一个正在运行 cron 作业的实例。您的流量会增加,因此 Elastic Beanstalk 最多可将您扩展到两个实例。leader_only 将确保您在两个实例之间只运行一个 cron 作业。您的流量会减少,而 Elastic Beanstalk 会将您缩减为一个实例。但是,Elastic Beanstalk 不会终止第二个实例,而是终止作为领导者的第一个实例。您现在没有运行任何 cron 作业,因为它们只在第一个终止的实例上运行。 请参阅下面的评论。
更新 2: 请从以下评论中明确说明:AWS 现在具有防止自动实例终止的保护。只需在您的领导者实例上启用它,您就可以开始了。– Nicolás Arévalo 2016 年 10 月 28 日在 9:23
根据当前的文档,可以在他们所谓的工作层上运行定期任务。
引用文档:
AWS Elastic Beanstalk 支持在运行预定义配置的环境中对工作线程环境层执行定期任务,该配置的解决方案堆栈在容器名称中包含“v1.2.0”。您必须创建一个新环境。
同样有趣的是关于cron.yaml的部分:
要调用定期任务,您的应用程序源包必须在根级别包含一个 cron.yaml 文件。该文件必须包含有关您要安排的定期任务的信息。使用标准 crontab 语法指定此信息。
更新:我们能够完成这项工作。以下是我们经验中的一些重要问题(Node.js 平台):
eb ssh
),然后运行cat /var/log/aws-sqsd/default.log
. 它应该报告为aws-sqsd 2.0 (2015-02-18)
. 如果您没有 2.0 版本,则在创建环境时出现问题,您需要按上述方式创建新环境。关于 jamieb 的响应,正如 alrdinleal 所提到的,您可以使用“leader_only”属性来确保只有一个 EC2 实例运行 cron 作业。
引自http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html:
你可以使用leader_only。一个实例被选为 Auto Scaling 组中的领导者。如果 leader_only 值设置为 true,则该命令仅在标记为领导者的实例上运行。
我试图在我的 eb 上实现类似的事情,所以如果我解决它会更新我的帖子。
更新:
好的,我现在有使用以下 eb 配置的工作 cronjobs:
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
本质上,我使用 cronjobs 创建了一个临时文件,然后将 crontab 设置为从临时文件中读取,然后删除临时文件。希望这可以帮助。
如上所述,建立任何 crontab 配置的根本缺陷是它只发生在部署时。随着集群自动扩展,然后又缩减,最好也成为第一个关闭的服务器。此外,不会有故障转移,这对我来说至关重要。
我做了一些研究,然后与我们的 AWS 客户专家交谈,以提出想法并验证我提出的解决方案。您可以使用OpsWorks完成此操作,尽管这有点像用房子杀死苍蝇。也可以将Data Pipeline 与 Task Runner一起使用,但这限制了它可以执行的脚本的能力,并且我需要能够运行 PHP 脚本,并且可以访问整个代码库。您还可以在 ElasticBeanstalk 集群之外专门使用一个 EC2 实例,但随后您将无法再进行故障转移。
所以这就是我想出的,这显然是非常规的(正如 AWS 代表评论的那样),可能被认为是一种 hack,但它可以工作并且在故障转移方面很可靠。我选择了使用 SDK 的编码解决方案,我将在 PHP 中展示它,尽管您可以使用任何您喜欢的语言执行相同的方法。
// 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");
}
因此,逐步了解它以及它是如何运行的......您可以像通常在每个 EC2 实例上一样从 crontab 调用脚本。每个脚本在开头都包含这个(或者在我使用它时,每个脚本都包含一个文件),它建立一个 ElasticBeanstalk 对象并检索所有实例的列表。它只使用列表中的第一个服务器,并检查它是否与自己匹配,如果匹配,它会继续,否则它会死掉并关闭。我检查过,返回的列表似乎是一致的,从技术上讲,它只需要保持一分钟左右的一致,因为每个实例都执行预定的 cron。如果它确实发生了变化,那也没关系,因为它再次只与那个小窗口相关。
这绝不是优雅的,但适合我们的特定需求 - 这不是增加额外服务的成本或必须拥有专用的 EC2 实例,并且在发生任何故障时将进行故障转移。我们的 cron 脚本运行维护脚本,这些脚本被放置到 SQS 中,并且集群中的每个服务器都帮助执行。如果它符合您的需要,至少这可能会给您一个替代选择。
-戴维
我与 AWS 支持代理进行了交谈,这就是我们如何让它为我工作的方式。2015年解决方案:
使用 your_file_name.config 在 .ebextensions 目录中创建一个文件。在配置文件中输入:
文件: “/etc/cron.d/cron_example”: 模式:“000644” 所有者:根 组:根 内容:| * * * * * 根 /usr/local/bin/cron_example.sh “/usr/local/bin/cron_example.sh”: 模式:“000755” 所有者:根 组:根 内容:| #!/bin/bash /usr/local/bin/test_cron.sh || 出口 echo "Cron 在 "`date` >> /tmp/cron_example.log 运行 # 现在执行只能在 1 个实例上运行的任务 ... “/usr/local/bin/test_cron.sh”: 模式:“000755” 所有者:根 组:根 内容:| #!/bin/bash METADATA=/opt/aws/bin/ec2-元数据 INSTANCE_ID=`$METADATA -i | awk '{打印 $2}'` 地区=`$元数据-z | awk '{print substr($2, 0, length($2)-1)}'` # 找到我们的 Auto Scaling 组名称。 ASG=`aws ec2 describe-tags --filters "Name=resource-id,Values=$INSTANCE_ID" \ --region $REGION --输出文本 | awk '/aws:autoscaling:groupName/ {print $5}'` # 查找组中的第一个实例 FIRST=`aws 自动缩放 describe-auto-scaling-groups --auto-scaling-group-names $ASG \ --region $REGION --输出文本 | awk '/InService$/ {打印 $4}' | 排序 | 头-1` # 测试它们是否相同。 [“$FIRST”=“$INSTANCE_ID”] 命令: rm_old_cron: 命令:“rm *.bak” cwd:“/etc/cron.d” 忽略错误:真
该解决方案有两个缺点:
解决方法:
警告:
如果您使用默认 beanstalk 角色,则无需设置 IAM 角色。
如果您使用的是 Rails,则可以使用ever-elasticbeanstalk gem。它允许您在所有实例或仅一个实例上运行 cron 作业。它每分钟检查一次以确保只有一个“leader”实例,如果没有,它将自动将一台服务器提升为“leader”。这是必需的,因为 Elastic Beanstalk 在部署期间仅具有领导者的概念,并且可能在扩展时随时关闭任何实例。
更新 我改用 AWS OpsWorks 并且不再维护这个 gem。如果您需要的功能超出了 Elastic Beanstalk 基础知识中的可用功能,我强烈建议您切换到 OpsWorks。
您真的不想在 Elastic Beanstalk 上运行 cron 作业。由于您将拥有多个应用程序实例,这可能会导致竞争条件和其他奇怪的问题。实际上,我最近在博客上写过这个(页面下方的第 4 或第 5 个提示)。简短版本:根据应用程序,使用 SQS 之类的作业队列或iron.io 之类的第三方解决方案。
你只需要 2 分钟来配置它:
安装 laravel-aws-worker
composer require dusterio/laravel-aws-worker
将 cron.yaml 添加到根文件夹:
将 cron.yaml 添加到应用程序的根文件夹(这可以是您的存储库的一部分,或者您可以在部署到 EB 之前添加此文件 - 重要的是此文件在部署时存在):
version: 1
cron:
- name: "schedule"
url: "/worker/schedule"
schedule: "* * * * *"
App\Console\Kernel
现在将执行您的所有任务
详细说明和解释:https ://github.com/dusterio/laravel-aws-worker
如何在 Laravel 中编写任务:https ://laravel.com/docs/5.4/scheduling
使用files
代替的更易读的解决方案container_commands
:
文件: “/etc/cron.d/my_cron”: 模式:“000644” 所有者:根 组:根 内容:| # 覆盖默认电子邮件地址 MAILTO="example@gmail.com" # 每五分钟运行一次 Symfony 命令(作为 ec2-user) */10 * * * * ec2-user /usr/bin/php /var/app/current/app/console do:something 编码:普通 命令: # 删除 Elastic Beanstalk 创建的备份文件 clear_cron_backup: 命令:rm -f /etc/cron.d/watson.bak
请注意,该格式与通常的 crontab 格式不同,因为它指定了运行命令的用户。
我2018年的1分钱
这是正确的方法(使用django/python
和django_crontab
应用程序):
在.ebextensions
文件夹内创建一个像这样的文件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
它需要container_commands
代替commands
当新的领导者出现时,有人想知道leader_only 的自动缩放问题。我似乎不知道如何回复他们的评论,但请参阅此链接:http ://blog.paulopoiati.com/2013/08/25/running-cron-in-elastic-beanstalk-auto-scaling-环境/
来自亚马逊的最新示例是最简单和最有效的(周期性任务):
https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html
您可以在其中创建一个单独的工作层来执行您的任何 cron 作业。创建 cron.yaml 文件并将其放在您的根文件夹中。我遇到的一个问题(在 cron 似乎没有执行之后)是发现我的 CodePipeline 无权执行 dynamodb 修改。基于此,在 IAM -> 角色 -> yourpipeline 下添加 FullDynamoDB 访问并重新部署(弹性 beantalk)后,它运行良好。
所以我们已经为此苦苦挣扎了一段时间,在与 AWS 代表讨论后,我终于想出了我认为最好的解决方案。
使用带有 cron.yaml 的工作层绝对是最简单的解决方法。但是,文档没有明确说明的是,这会将作业置于您用于实际运行作业的 SQS 队列的末尾。如果您的 cron 作业对时间敏感(很多都是),这是不可接受的,因为它取决于队列的大小。一种选择是使用完全独立的环境来运行 cron 作业,但我认为这太过分了。
其他一些选项(例如检查您是否是列表中的第一个实例)也不理想。如果当前的第一个实例正在关闭过程中怎么办?
实例保护也可能带来问题 - 如果该实例被锁定/冻结怎么办?
重要的是要了解 AWS 本身如何管理 cron.yaml 功能。有一个 SQS 守护进程使用 Dynamo 表来处理“领导选举”。它频繁地写入这张表,如果当前的leader短时间内没有写入,下一个实例将接替成为leader。这是守护进程决定哪个实例将作业触发到 SQS 队列的方式。
我们可以重新利用现有功能,而不是尝试重写我们自己的功能。你可以在这里看到完整的解决方案:https ://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27
那是在 Ruby 中,但您可以轻松地将其调整为具有 AWS 开发工具包的任何其他语言。本质上,它检查当前领导者,然后检查状态以确保其处于良好状态。它将循环直到有一个处于良好状态的当前领导者,如果当前实例是领导者,则执行作业。
以下是解决方案的完整说明:
http://blog.paulopoiati.com/2013/08/25/running-cron-in-elastic-beanstalk-auto-scaling-environment/
根据user1599237的回答原则,您让 cron 作业在所有实例上运行,但是在作业开始时确定是否应该允许它们运行,我提出了另一个解决方案。
我没有查看正在运行的实例(并且必须存储您的 AWS 密钥和秘密),而是使用我已经从所有实例连接到的 MySQL 数据库。
它没有缺点,只有优点:
或者,您也可以使用通常共享的文件系统(例如通过 NFS 协议的AWS EFS )而不是数据库。
以下解决方案是在 PHP 框架Yii中创建的,但您可以轻松地将其调整为其他框架和语言。异常处理程序Yii::$app->system
也是我自己的一个模块。用你正在使用的任何东西替换它。
/**
* 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);
}
这是我正在使用的数据库架构:
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`)
)
如果您想在 PHP 中执行此操作,这是一个修复程序。您只需要 .ebextensions 文件夹中的 cronjob.config 就可以让它像这样工作。
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 获取文件的环境变量。您可以如上所述在 tmp/sendemail.log 上调试输出。
希望这对某人有所帮助,因为它肯定对我们有所帮助!
要控制 Auto Scaling 是否可以在缩减时终止特定实例,请使用实例保护。您可以在 Auto Scaling 组或单个 Auto Scaling 实例上启用实例保护设置。当 Auto Scaling 启动实例时,该实例会继承 Auto Scaling 组的实例保护设置。您可以随时更改 Auto Scaling 组或 Auto Scaling 实例的实例保护设置。
如果需要通过 cron 运行 php 文件并且设置了任何 NAT 实例,那么我有另一个解决方案,那么您可以将 cronjob 放在 NAT 实例上并通过 wget 运行 php 文件。