Lambda上で定期実行する2019.08.19AWS上で動いているシステムがあって、さらに定期実行したいPHPで書かれたスクリプトをどこかで実行することになった。Lambdaで動かすことにしてみた。Lambdaの実行リージョンと同じリージョンにある、そのアカウントが持つEC2インスタンスのIDのリストを取得する」ものを実行することにする。Lambdaをまだ使ったことがなかったので、最初に目的通りできそうか調査を行った。Lambda上で実行したものは最大15分で強制終了させられる。最大15分で終了するインスタンスであると解釈することができる。Linuxが動いているようだ。Batch辺りを使うのだろう。マシンサイジングできるのはメモリ量だけ。メモリ量は128MBから3008MBまで64MB刻みで割り当てることが可能。.NETであり、PHPは用意されていない。カスタムランタイムを作ってやって渡してやればPHPも実行可能になる。Linux上で動くものなら実行できるようにできると考えられる。PHPのカスタムランタイムは自分で作っても良いが、すでに提供してくれているところもあるので、問題なければこれを使えば良い。結局自分で作ることはなかったので作り方は調べていないがこのリンク先にあるshファイルを見れば良いと思う。Eventsを使用すると時刻をトリガーにできるので定期実行可能。Lambdaには無期限の無料利用枠があり、割と頑張らないと無料枠から脱出できない。いうわけで行けそうなので進める。デフォルト構成の調査AWSマネジメントコンソールを使って、Lambda関数を”一から作成”の「カスタムランタイム」の「デフォルトのブートストラップを使用する」で1つ作ってみた。README.mdの計3ファイルが生成されたのが見える。https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-custom.html使いたい(自分で作った、あるいは誰かが提供している)カスタムランタイムはレイヤーに設定する。このLambda関数が実行される際に、実際に実行されるのは「関数コード」でルートにあるbootstrapである。これはREADME.mdではなく後で実行してみてわかったことだが、ルートにbootstrapがなければレイヤーに含まれるbootstrapファイルが実行されるようだ。続けてbootstrapを確認。そのレスポンスヘッダの中にLambda-Runtime-Aws-Request-Idがあるので、その値を取得しINVOCATION_IDとする。任意のスクリプトを実行する。スクリプトの実行結果をPOSTする。ここまでをwhileで延々とループさせる。いうのがbootstrapがやっていることであると読める。AWSマネジメントコンソール上だと「関数コード」で設定できるハンドラというものがある。ここで設定した値は環境変数_HANDLERに入る。ハンドラ名は”hello.handler”が初期設定である。"$EVENT_DATA")行はhello.shのhandler関数を呼んでいることになる。hello.shの中身は以下なので、スクリプトの実行結果はJSONを期待されているようだ。$RESPONSE}結局こちらでやるべきことは以下となる。書かれているものを設定する。7.3なら「arn:aws:lambda:(リージョン):887080169480:layer:php73:3」。bootstrapを修正してPHPのスクリプトを呼ぶようにする。PHPのスクリプトはbootstrapから呼べる場所に置く。Lambda側へ渡したいファイルの作成というわけでbootstrapをPHPのスクリプトを呼ぶように修正してみる。"${LAMBDA_TASK_ROOT}/${_HANDLER}.php"としたので、スクリプトファイルはbootstrapと同じディレクトリに”(ハンドラ名).php”という名前で置くことになる。ファイル名は変化するわけではないのでハンドラ名なんて使わなくても良いのだが、設定必須項目が使われないのもちょっと、ということで。ここでphp.iniも使うようにしている。これは今回のサンプルスクリプトがsimplexml.soとjson.soを使うので、それらをロードする必要があるためである。書かれている通りカスタムランタイム側で用意してくれているので、これらをロードすれば良い。extension_dirでsoファイルが置かれているディレクトリを指定しないとロードできなかった。これもbootstrapと同じディレクトリに配置する。php.iniextension_dir=/opt/lib/php/7.3/modulesextension=simplexmlextension=json説明をbootstrap側に戻す。スクリプトの実行結果はスクリプトの実行時のリターンコードが0かどうかで中身を変えているだけに今回はしてある。他にも元のbootstrapと比べて特に欲しくない情報は出力しないようにしている。Logsに出力されるからである。逆に言えば、スクリプト側ではログ出力したい情報は標準出力か標準エラー出力に書き出すようにしておくと良い。GETした際のレスポンスボディ(EVENT_DATA変数に格納されるもの。JSONである)はまったく使わずに握り潰している。今回のサンプルスクリプトファイルの内容は以下である。Lambdaの実行リージョンと同じリージョンにある、そのアカウントが持つEC2インスタンスのIDのリストを取得する」だけのものとさせてもらっている。"n";}?>このスクリプトのファイル名をscript.phpという名前にしたので、ハンドラ名はscriptとなる。下、「ZIPファイルを使用したインストール」で行っている。script.phpの3ファイルが置かれているディレクトリにawsディレクトリが作られている。私はPHPをほとんど触ったことないのでComposerの使い方とか知らないが、一般にはComposerを使うものだと思われる。PHPはLambda側に置く必要がある。AWSマネジメントコンソールを使うなら「関数コード」にてzipにして渡したりできる。今回はCloudformationを使う。その場合、zipをS3に置いておく必要がある。zipにする時の注意だが、bootstrapはLinuxファイルシステムにおける実行権限がついていなければならない。Windows上で作業する場合は注意すること。WSLを使って作業するなどで問題ないと思われるが。ルートディレクトリがzip書庫内に含まれていてはならない。私は以下のコマンドで圧縮している。PHPが置かれているディレクトリとする。S3Bucketの項に書かれている通り、Cloudformationを実行する(=Lambda関数が作成される)リージョンと同じリージョンのバケットを使用しなければならないことである。バケットは別のAWSアカウントのものでも良いが、その場合はCloudformationを実行するアカウントからsrc.zipがアクセス権限上ダウンロード可能になっていないとならない。簡単にはパブリックアクセス可能にしておくなど。SAMを使った例が出ている。これをやりたいことに合わせて適当に修正したtemplate.ymlというファイルにしたものが以下である。*)"Eventsは5分ごとに定期実行するための設定にしてある。実際には20から30秒程度遅れて実行されるようだ。実行環境の起動に掛かる時間だろうか。通り一般的なcronのものの書式とは違いがあり、年を指定でき、日か曜日の使わない方は”?”にするなどの必要がある。Logsにログを出力するのを許可するもの(CreateLogPolicy)と、スクリプトで必要なEC2インスタンスの一覧を取得するのを許可するもの(ScriptPolicy)を設定してある。カスタムランタイムを使用する場合、通常Runtimeを”provided”にしてLayersに使用するカスタムランタイムを設定する。与えるメモリ量は最小の128MB、強制終了までの時間は10秒に設定している。output.ymlを実行するとCodeUriで指定したディレクトリの中にあるファイルを再帰的にzip圧縮してS3の–s3-bucketで指定したバケットにアップロードしてくれ(ファイル名はzipファイルのmd5のように見える)、Cloudformationに食わせられるテンプレートファイルを–output-template-fileに指定した名前で生成してくれる。このzip作成時にbootstrapに自動的に実行権限を付けてくれたら嬉しかったのだがそうはいかなかった。生成されたoutput.ymlの内容は以下である。(適当なスタック名)を実行すると、Cloudformationを使用したデプロイが実行される。CLIをインストールする必要は実際にはないと思われる。生成されたoutput.ymlはSAMテンプレート形式で記述されており、Cloudformationで実行時にTransformによりCloudformationテンプレート形式に変換される。実行しないと実際に何になるのかがわからないのがちょっと嫌だったので、実行後にAWSマネージメントコンソールのCloudformationのところで見られる変換後のテンプレートを参考に、最初からCloudformationテンプレート形式で書くことにした、というのが先に書いた通り上記のSAMテンプレートを使用する方式を使わなかった理由である。packageを使っていないので、上記「Lambda側へファイルを渡すための準備」の通りに圧縮して作ったsrc.zipをS3(以下の例では「xxxxxxxxxxxx-(リージョン名)」というバケット)にアップロードしてある。いうわけで作成したCloudformationテンプレートが以下である。AWS::Lambda::Permissionの3つになる感じか。(適当なスタック名)を実行すると、こちらでもCloudformationを使用したデプロイが実行される。CloudFormationによってIAMリソースが作成される場合があることを承認します。」のチェックを入れて「スタックの作成」を行えば同じことになる。Logsを見ると5分置きに実行されているのがわかる。