AWS弹性魔豆,运行一个cronjob(AWS Elastic Beanstalk, running

2019-07-17 16:41发布

我想知道是否有一种方法来建立一个cronjob /任务来执行的每一分钟。 目前我的任何情况下都应该能够运行这个任务。

这是我曾尝试在没有成功的配置文件要做到:

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

我真的不知道,如果这是这样做的正确方法

有任何想法吗?

Answer 1:

这就是我如何添加一个cron作业弹性魔豆:

创建你的应用程序调用.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

这是弹性魔豆一个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实例运行,你可能已经运行。

然后创建一个名为some_cron_job.txt的.ebextensions文件夹内的文件。 你会把你的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。 然后更换一些的PHP脚本,这里的路到你的PHP文件。 这显然是假设你的cron作业需要运行的PHP文件。

此外,确保some_cron_job.txt文件已经在文件的最后一个换行符就像评论说。 否则的cron将不会运行。

更新:没有与此解决方案时,弹性魔豆扩展你的情况的问题。 例如,假设你有cron作业运行一个实例。 你会得到一个增加流量,弹性魔豆扩展你到两个实例。 该leader_only将确保你只有两个实例之间运行一个cron作业。 你的流量会降低,弹性扩展魔豆你到一个实例。 但是,而不是终止第二个实例,弹性魔豆终止第一个实例,这是领导者。 现在,您不必运行,因为他们只在被终止的第一个实例运行的所有cron作业。 请参阅下面的注释。

更新2:只是把从下面的评论中这样明确的:AWS现在已经对自动终止的实例保护。 只要能在你的领导者的实例,你是好去。 - 尼古拉斯·阿雷瓦洛10月28日在'16 9:23



Answer 2:

这是做它现在(2015+)正式办法。 请先试试这个,这是由目前可用的和最可靠以及远最简单的方法。

根据目前的文件,一个是能够在他们所谓的运行周期任务 的工人层 。

引用文档:

AWS弹性青苗支持工人的环境中层级环境中运行与包含在容器名称“V1.2.0”一个解决方案堆栈的预定义配置周期性任务。 您必须创建一个新的环境。

同样有趣的是约cron.yaml部分:

要调用周期性的任务,你的应用程序源代码包必须包括根级cron.yaml文件。 该文件必须包含关于要安排周期性任务的信息。 指定使用标准的crontab的语法信息。

更新:我们能够得到这个工作。 以下是我们的经验(Node.js的平台)一些重要的陷阱:

  • 当使用cron.yaml文件,请确保您有最新的awsebcli ,因为旧版本将无法正常工作。
  • 这也是创造新的环境(至少在我们的情况下,它是)非常重要,而不仅仅是复制旧的。
  • 如果你想确保cron是支持你的EC2工人层实例,SSH进去( eb ssh ),并运行cat /var/log/aws-sqsd/default.log 。 它应该报告aws-sqsd 2.0 (2015-02-18) 。 如果你没有2.0的版本,一些创建环境时出了错,你需要如上所述创建新的。


Answer 3:

关于jamieb的回应,并作为alrdinleal提到,您可以使用“leader_only”属性,以确保只有一个EC2实例运行cron作业。

报价取自http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html :

您可以使用leader_only。 一个实例选择是自动伸缩群中的佼佼者。 如果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从临时文件读取,然后删除临时文件之后。 希望这可以帮助。



Answer 4:

正如上面提到的,与建立任何的crontab配置中的根本缺陷是,它只会发生在部署。 由于集群获得自动缩放起来,然后背下来,这是青睐,也被关闭的第一台服务器。 此外就没有失效过,这对我来说是至关重要的。

我做了一些研究,然后用我们的AWS帐户专家交谈,反弹我想出了思路和有效的解决方案。 你可以做到这一点OpsWorks ,虽然它有点像用房子杀死一只苍蝇。 也可以使用数据管道与任务运行 ,但是这有它可以执行脚本的能力有限,我需要能够运行PHP脚本,以访问整个代码库。 你也可以专门指定一个EC2实例ElasticBeanstalk集群之外,但你有没有再次故障转移。

所以这是我想出了,这显然是非常规(作为AWS代表评论),并可以被认为是一个黑客,但它的工作原理是固体的故障切换。 我选择使用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");
}

所以通过这个行走,它是如何运作......您拨叫的crontab脚本,你通常会在每一个EC2实例。 每个脚本包括该开头(或包括用于每个单个文件,我使用它),它建立一个ElasticBeanstalk对象和检索所有实例的列表。 它仅使用第一台服务器列表中,并检查它是否与自身匹配,而如果它继续下去,否则它死并关闭了。 我已经检查并返回列表似乎是一致的,这在技术上只需要为一分钟左右一致,为每个实例执行预定的cron。 如果不改变,它不会有问题,因为它再次仅是相关的小窗口。

这不是优雅的以任何方式,但适合我们的具体需求 - 这是在不增加成本与附加服务或必须有一个专门的EC2实例,并且会有故障转移在任何故障的情况下。 我们的cron脚本运行其中获得放入SQS和集群中的每个服务器可以帮助执行维护脚本。 至少如果它适合你的需求,这可能给你一个备用选项。

-Davey



Answer 5:

我跟一个AWS支持代理,这就是我们如何得到这个工作对我来说。 2015年的解决方案:

创建你的.ebextensions与your_file_name.config目录中的文件。 在配置文件中输入:

files:
  "/etc/cron.d/cron_example":
    mode: "000644"
    owner: root
    group: root
    content: |
      * * * * * root /usr/local/bin/cron_example.sh

  "/usr/local/bin/cron_example.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/bin/bash

      /usr/local/bin/test_cron.sh || exit
      echo "Cron running at " `date` >> /tmp/cron_example.log
      # Now do tasks that should only run on 1 instance ...

  "/usr/local/bin/test_cron.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/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)}'`

      # Find our Auto Scaling Group name.
      ASG=`aws ec2 describe-tags --filters "Name=resource-id,Values=$INSTANCE_ID" \
        --region $REGION --output text | awk '/aws:autoscaling:groupName/ {print $5}'`

      # Find the first instance in the Group
      FIRST=`aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $ASG \
        --region $REGION --output text | awk '/InService$/ {print $4}' | sort | head -1`

      # Test if they're the same.
      [ "$FIRST" = "$INSTANCE_ID" ]

commands:
  rm_old_cron:
    command: "rm *.bak"
    cwd: "/etc/cron.d"
    ignoreErrors: true

该解决方案有2个缺点:

  1. 在随后的部署,魔豆重命名现有的cron脚本.bak的,但cron将会仍然运行它。 您现在的Cron在同一台机器上执行了两次。
  2. 如果您的环境大大加快了,你会得到一些情况下,所有正在运行的脚本的cron。 这意味着你的邮件截图是重复的,或者您的数据库存档复制

解决方法:

  1. 确保任何.ebextensions脚本创建一个cron也消除了对后续部署的.bak文件。
  2. 具有助手脚本执行以下操作: - 从元数据获取当前实例ID - 从EC2标签获取当前的自动缩放集团名称 - 获取该组中,按字母顺序排序EC2实例的列表。 - 从列表中注意到的第一个实例。 - 从第4步第1步中与第一实例ID比较实例ID,然后在你的cron脚本可以使用此脚本工具,以确定它们是否应该执行。

警告:

  • 使用魔豆实例的IAM角色需要EC2:DescribeTags和自动缩放:DescribeAutoScalingGroups权限
  • 选自实例是那些由自动缩放显示为在职。 这并不一定意味着他们是完全启动,并准备执行您的Cron。

你不会有,如果你使用的是默认的魔豆的角色设置IAM角色。



Answer 6:

如果你正在使用Rails,你可以使用任何时候,而且elasticbeanstalk宝石 。 它可以让你在所有实例或只是一个运行cron作业。 它会检查每分钟以确保只有一个“领头羊”的实例,并会自动提升一个服务器为“领导者”,如果有没有。 这是必要的,因为弹性青苗只有领导的部署过程中的概念,可以在任何时候任何情况下关闭,而缩放。

UPDATE我切换到使用AWS OpsWorks和我不再坚持这种宝石。 如果你需要更多的功能比在弹性魔豆的基本可用,我强烈建议切换到OpsWorks。



Answer 7:

你真的不希望在弹性魔豆运行cron作业。 既然你有多个应用实例,这可能会导致竞争条件和其他奇怪的问题。 事实上,我最近在博客这个 (第四或第五尖向下页)。 简短的版本:根据不同的应用,使用像SQS或像第三方解决方案作业队列iron.io 。



Answer 8:

使用一个更可读的溶液files代替container_commands

files:
  "/etc/cron.d/my_cron":
    mode: "000644"
    owner: root
    group: root
    content: |
      # override default email address
      MAILTO="example@gmail.com"
      # run a Symfony command every five minutes (as ec2-user)
      */10 * * * * ec2-user /usr/bin/php /var/app/current/app/console do:something
    encoding: plain
commands:
  # delete backup file created by Elastic Beanstalk
  clear_cron_backup:
    command: rm -f /etc/cron.d/watson.bak

注意的格式从它指定用户运行的命令作为通常的crontab格式不同。



Answer 9:

有人想知道关于leader_only自动缩放问题时,新的领导人出现。 我似乎无法弄清楚如何回复他们的评论,但是看到这个链接: http://blog.paulopoiati.com/2013/08/25/running-cron-in-elastic-beanstalk-auto-scaling-环境/



Answer 10:

这里是解决方案的完整说明:

http://blog.paulopoiati.com/2013/08/25/running-cron-in-elastic-beanstalk-auto-scaling-environment/



Answer 11:

2017年:如果您正在使用Laravel5 +

你只需要2分钟,配置它:

  • 创建工作一线
  • 安装laravel-AWS-工人

    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



Answer 12:

我的1个2018年贡献的百分之

这里是(用做正确的方式django/pythondjango_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



Answer 13:

要控制在缩放时自动缩放是否能终止特定情况下,例如使用保护。 您可以启用的自动缩放团体或个人自动缩放实例的实例保护设置。 当自动缩放启动一个实例,该实例会继承自动缩放组的情况下保护设置。 您可以更改在任何时候自动缩放组或自动缩放实例的实例保护设置。

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



Answer 14:

我有另一种解决方案这一点,如果一个PHP文件需要通过cron的运行,如果您设置任何NAT实例,那么你可以把上的cronjob NAT实例,并通过wget的运行PHP文件。



Answer 15:

所以,我们一直在挣扎了一会儿后用AWS代表的一些讨论,我终于想出了什么,我认为是最好的解决办法。

使用具有cron.yaml工人层绝对是最简单的解决。 然而,什么样的文件没有说清楚的是,这将使工作在你使用的实际运行作业的SQS队列的末尾 。 如果您的cron作业时间敏感(因为许多人),这是不能接受的,因为这将取决于队列的大小。 一种选择是使用一个完全独立的环境,这样才能运行cron作业,但我认为这是矫枉过正。

一些其他的选项,如检查,看看是否你在列表中的第一个实例,是不理想的两种。 如果当前的第一个实例是在关闭的过程是什么?

例如保护也可以来的问题 - 如果这是什么情况下被锁定/冻结?

什么理解重要的是AWS本身如何管理cron.yaml功能。 有一种使用一个表迪纳摩处理“领导人选举”的SQS守护进程。 它写到此表频繁,如果现任领导人还没有一会儿写的,下一个实例将接任的领导者。 这是守护进程如何决定火作业放到SQS队列哪个实例。

我们可以重新利用现有的功能,而不是试图改写我们自己。 :你可以在这里看到完整的解决方案https://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27

这是一个在Ruby中,但你可以很容易地适应具有AWS SDK任何其他语言。 从本质上讲,它会检查当前的领导者,然后检查状态,以确保它是在一个良好的状态。 它会循环,直到有一个很好的状态的现任领导人,如果当前实例是领导者,执行作业。



Answer 16:

这里是一个修复柜面你想这样做在PHP。 你只需要cronjob.config在.ebextensions文件夹,把它像这样工作。

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输出。

希望这可以帮助别人,因为它确实帮助我们!



Answer 17:

基于从答案的原则user1599237 ,你让cron作业在所有情况下运行,但随后而不是在工作的开始确定它们是否应该被允许运行,我做了另一种解决方案。

而不是看着正在运行的实例(并具有存储您的AWS密钥和密码),我使用我已经从所有实例连接到MySQL数据库。

它没有缺点,只有阳性:

  • 没有多余的实例或费用
  • 坚如磐石的解决方案 - 没有执行双重机会
  • 可扩展性 - 为您的实例被放大和缩小自动工作
  • 故障切换 - 自动工作的情况下,一个实例发生故障

或者,也可以使用一个共同共享的文件系统(如AWS EFS通过NFS协议),而不是一个数据库。

下面的解决方案是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`)
)


文章来源: AWS Elastic Beanstalk, running a cronjob