毎分実行する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
これはElasticBeanstalkの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ユーザーとして毎日1時間ごとに毎分実行され、出力を/ dev/nullに破棄します。/ usr / bin/phpはphpへのパスです。次に、some-php-script-hereをphpファイルへのパスに置き換えます。これは明らかに、cronジョブがPHPファイルを実行する必要があることを前提としています。
また、コメントにあるように、some_cron_job.txtファイルのファイルの最後に改行が含まれていることを確認してください。そうしないと、cronは実行されません。
更新: Elastic Beanstalkがインスタンスをスケールアップする場合、このソリューションには問題があります。たとえば、cronジョブが実行されているインスタンスが1つあるとします。トラフィックが増加するため、ElasticBeanstalkは最大2つのインスタンスにスケールアップします。Leader_onlyは、2つのインスタンス間で実行されているcronジョブが1つだけであることを保証します。トラフィックが減少し、ElasticBeanstalkが1つのインスタンスにスケールダウンします。ただし、Elastic Beanstalkは、2番目のインスタンスを終了する代わりに、リーダーであった最初のインスタンスを終了します。終了した最初のインスタンスでのみ実行されていたため、cronジョブは実行されていません。 以下のコメントを参照してください。
アップデート2: 以下のコメントからこれを明確にするだけです:AWSは、インスタンスの自動終了に対する保護を備えています。リーダーインスタンスで有効にするだけで、準備完了です。–ニコラスアレバロ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」プロパティを使用して、1つのEC2インスタンスのみがcronジョブを実行するようにすることができます。
http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/customize-containers-ec2.htmlから引用:
Leader_onlyを使用できます。AutoScalingグループのリーダーとして1つのインスタンスが選択されます。Leader_only値がtrueに設定されている場合、コマンドはリーダーとしてマークされているインスタンスでのみ実行されます。
私は私のebで同様のことを達成しようとしているので、それを解決したら私の投稿を更新します。
アップデート:
わかりました。次のebconfigを使用してcronジョブを実行しています。
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で実現できますが、家を使ってハエを殺すのと少し似ています。Task Runnerでデータパイプラインを使用することもできますが、実行できるスクリプトの機能が制限されているため、コードベース全体にアクセスしてPHPスクリプトを実行できる必要がありました。ElasticBeanstalkクラスターの外部でEC2インスタンスを専用にすることもできますが、その場合、フェイルオーバーは再度行われません。
これが私が思いついたものです。これは明らかに型にはまらないものであり(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");
}
したがって、これとその動作について説明します...すべてのEC2インスタンスで通常行うように、crontabからスクリプトを呼び出します。各スクリプトには、最初にこれが含まれています(または、私が使用しているように、それぞれに1つのファイルが含まれています)。これにより、ElasticBeanstalkオブジェクトが確立され、すべてのインスタンスのリストが取得されます。リストの最初のサーバーのみを使用し、それ自体が一致するかどうかを確認します。一致する場合は続行します。一致しない場合は、停止して終了します。確認したところ、返されたリストは一貫しているようです。技術的には、各インスタンスがスケジュールされたcronを実行するため、1分ほど一貫している必要があります。それが変更されたとしても、それは問題ではありません。これも、その小さなウィンドウにのみ関連するからです。
これは決してエレガントではありませんが、特定のニーズに適していました。これは、追加のサービスでコストを増やすことや、専用の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 running at" `date` >> /tmp/cron_example.log #1つのインスタンスでのみ実行する必要があるタスクを実行します... "/usr/local/bin/test_cron.sh": モード:「000755」 所有者:ルート グループ:ルート コンテンツ:| #!/ 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)}' ` #AutoScalingグループ名を見つけます。 ASG = `aws ec2 describe-tags --filters" Name = resource-id、Values = $ INSTANCE_ID "\ --region $REGION--出力テキスト| awk'/ aws:autoscaling:groupName / {print $ 5}' ` #グループ内の最初のインスタンスを検索します FIRST = `aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $ ASG \ --region $REGION--出力テキスト| awk'/ InService $ / {print $ 4}' | 並べ替え| 頭-1` #それらが同じかどうかをテストします。 ["$ FIRST" = "$ INSTANCE_ID"] コマンド: rm_old_cron: コマンド: "rm * .bak" cwd: "/etc/cron.d" ignoreErrors:true
このソリューションには2つの欠点があります。
回避策:
警告:
デフォルトのbeanstalkロールを使用している場合は、IAMロールを設定する必要はありません。
Railsを使用している場合は、everyness-elasticbeanstalkgemを使用できます。これにより、すべてのインスタンスまたは1つだけでcronジョブを実行できます。「リーダー」インスタンスが1つしかないことを毎分チェックし、存在しない場合は1つのサーバーを「リーダー」に自動的にプロモートします。Elastic Beanstalkにはデプロイ中のリーダーの概念しかなく、スケーリング中にいつでもインスタンスをシャットダウンできるため、これが必要です。
更新 AWSOpsWorksの使用に切り替えましたが、このgemを維持していません。Elastic Beanstalkの基本で利用できるよりも多くの機能が必要な場合は、OpsWorksに切り替えることを強くお勧めします。
ElasticBeanstalkで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" #5分ごとにSymfonyコマンドを実行する(ec2-userとして) * / 10 * * * * ec2-user / usr / bin / php / var / app / current / app / console do:something エンコーディング:プレーン コマンド: #ElasticBeanstalkによって作成されたバックアップファイルを削除します 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-環境/
そのため、私たちはしばらくの間これに苦労してきましたが、AWSの担当者と話し合った後、私はついに最善の解決策を思いつきました。
cron.yamlでワーカー層を使用するのが間違いなく最も簡単な修正です。ただし、ドキュメントで明確にされていないのは、これにより、実際にジョブを実行するために使用しているSQSキューの最後にジョブが配置されるということです。cronジョブが時間に敏感な場合(多くの場合)、キューのサイズに依存するため、これは受け入れられません。1つのオプションは、cronジョブを実行するためだけに完全に別の環境を使用することですが、それはやり過ぎだと思います。
あなたがリストの最初のインスタンスであるかどうかを確認するなど、他のオプションのいくつかも理想的ではありません。現在の最初のインスタンスがシャットダウン中の場合はどうなりますか?
インスタンス保護にも問題が発生する可能性があります-そのインスタンスがロックアップ/フリーズした場合はどうなりますか?
理解しておくべき重要なことは、AWS自体がcron.yaml機能をどのように管理するかです。Dynamoテーブルを使用して「リーダー選出」を処理するSQSデーモンがあります。このテーブルに頻繁に書き込みます。現在のリーダーが短時間書き込みを行わなかった場合は、次のインスタンスがリーダーとして引き継ぎます。これは、デーモンがジョブをSQSキューに起動するインスタンスを決定する方法です。
独自の機能を書き直すのではなく、既存の機能を再利用できます。あなたはここで完全な解決策を見ることができます:https ://gist.github.com/dorner/4517fe2b8c79ccb3971084ec28267f27
これはRubyにありますが、AWSSDKを備えた他の言語に簡単に適応させることができます。基本的に、現在のリーダーをチェックしてから、状態をチェックして、状態が良好であることを確認します。良好な状態の現在のリーダーが存在するまでループし、現在のインスタンスがリーダーである場合は、ジョブを実行します。
Amazonの最新の例は、最も簡単で効率的な(定期的なタスク)です。
https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html
ここで、cronジョブを実行するための個別のワーカー層を作成します。cron.yamlファイルを作成し、ルートフォルダーに配置します。私が抱えていた問題の1つは、(cronが実行されていないように見えた後)、CodePipelineにdynamodbの変更を実行する権限がないことを発見したことです。これに基づいて、IAM->ロール-> yourpipelineの下にFullDynamoDBアクセスを追加し、再デプロイ(エラスティックBeanstalk)した後、完全に機能しました。
ソリューションの完全な説明は次のとおりです。
http://blog.paulopoiati.com/2013/08/25/running-cron-in-elastic-beanstalk-auto-scaling-environment/
スケールイン時にAutoScalingが特定のインスタンスを終了できるかどうかを制御するには、インスタンス保護を使用します。AutoScalingグループまたは個々のAutoScalingインスタンスでインスタンス保護設定を有効にできます。Auto Scalingがインスタンスを起動すると、インスタンスはAutoScalingグループのインスタンス保護設定を継承します。AutoScalingグループまたはAutoScalingインスタンスのインスタンス保護設定はいつでも変更できます。
phpファイルをcronで実行する必要があり、NATインスタンスを設定している場合は、NATインスタンスにcronjobを配置し、wgetでphpファイルを実行することができます。
これは、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の出力をデバッグできます。
それが確かに私たちを助けたので、これが誰かを助けることを願っています!
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`)
)