ほんじゃらねっと

遊びと学びと仕事をテクノロジーで楽しくする

作成した Node.js スクリプトをサーバーレスな AWS Lambda で手軽に定期実行する

以前作成した映画情報取得用スクリプトのように

blog.honjala.net

定期的に実行したいスクリプトを作成した場合、

そのスクリプトをどこで動かすか、というのが悩みどころになる。

いくつか方法はあるのだけど、

ローカルのPCでタスクスケジューラなりcronなりで動かす方法は

無料だし手軽にできるけど、実行したい時間にPCを起動しておく必要がある。

さくらインターネット等のVPSを借りてcronで動かす方法は

費用はそんなに高くないけど、ファイアウォールの設定等

安心して使える環境を作るまでのハードルが結構高い。

一昔前はそこで頑張ってVPSを立てるか、諦めるかしかなかったのだけど、

今はそんな時のためのサーバーレス(=サーバ管理のいらない)なサービスが使える。

今回は AWS の AWS Lambda を使って、

作成したスクリプトを定期実行する環境を作ってみたい。

AWS Lambda とは

aws.amazon.com

AWS Lambda は上記の説明に記載があるとおり、

なにがしかのスクリプトを作って、

それを実行する条件となるトリガーを設定しておくと、

そのトリガーが発動した時にスクリプトを実行してくれるサービス。

トリガーは例えば、

「指定したテーブルのデータが更新された」「指定した日時になった」

みたいなものが多数用意されているので、そこから選択する。

HTTPリクエストをトリガーとしてJSONやHTMLを返すような

Webアプリ的な使い方もできるけど、

Webアプリのように常時立ち上がっているものではなく、

呼び出されて初めて起動し、実行される。

イベントが発生しなければ費用も発生しない。

そして、 AWS Lambda の場合は100万回/月の実行まで無料で利用可能。

つまり、月に数回から数十回定期的に実行するようなスクリプトなら

無料枠内で試せるので、自動化を始めるには非常に適したサービスになっている。

ハードルがあるとしたら、

AWSにLambda以外にもサービスがたくさんあったり、色々細かく設定できすぎて、

慣れるまで何をどうしたらよいか分からない、というところくらい。

まずは読み進める前にAWSのアカウントを取得して、

操作用のIAMアカウントを作成し、

AWSのサービス管理画面にログインするところまで頑張ろう。

AWS Lambda 関数を作成する

AWSのサービス管理画面にログインできたら、

下記の手順で関数を実行できる環境を作る。

  1. スクリプトを作成する
  2. AWS Lambda の関数作成画面でスクリプトを登録する
  3. テストする
  4. トリガーを追加する

スクリプトを作成する

AWS Lambda 用のスクリプトは、

関数作成画面で直接編集する方法と、

ローカルで作成して必要なファイルをzipでまとめてアップロードする方法がある。

標準以外のライブラリを使用する場合は後者のzipアップロードの方法を

使う必要があるので、こちらで進める。

AWS Lambda はイベントハンドラとなる関数を定義しておくことで、

トリガーが発火した時にその関数を実行してくれる仕組みになっている。

上述の「映画情報取得用スクリプト」の記事で作成した

メイン実行用ファイルは下記のように作成していたが、

const slack = require('./slack');
const movieScrape = require('./movie_scrape');

async function main() {

    const scheduleList = await movieScrape.fetch();
    const scheduleListText = movieScrape.stringifyList(scheduleList);
    const slackMessageText = "<映画情報>\n\n" + scheduleListText;
    
    const token = "<APIトークン>";
    await slack.postMessage(token, "#general", slackMessageText);
}

main();

AWS Lambda で実行する場合は下記のようにメイン関数をexportする形の

メイン実行用ファイルを作成する。

const slack = require('./slack');
const movieScrape = require('./movie_scrape');

exports.handler = async (event) => {

    const scheduleList = await movieScrape.fetch();
    const scheduleListText = movieScrape.stringifyList(scheduleList);
    const slackMessageText = "<映画情報>\n\n" + scheduleListText;
    
    const token = "<APIトークン>";
    await slack.postMessage(token, "#general", slackMessageText);

    return {
        statusCode: 200,
        body: ''
    };
}

このファイルを「lambda.js」として作成したとしよう。

作成したら、これを必要なファイルとともにzipファイルにまとめる。

必要なのは、自作のjsファイルとnode_modulesフォルダ。

Macなら下記のようなコマンドで「fetch_tv_movies.zip」というファイルを作成できる。

zip -r fetch_tv_movies.zip lambda.js slack.js movie_scrape.js node_modules

うまく実行できなかったら何度もzip化することになるので、

シェルスクリプトにでもまとめておく。

zipファイル名は適当で良いけどlambda関数名と合わせておくのが良いかも。

AWS Lambda の関数作成画面でスクリプトを登録する

AWS Lambda 関数を登録する準備が整ったので、

AWS マネジメントコンソールにIAMアカウントでログインして登録処理を行う。

ログインして Lambda メニューを選択し、

f:id:piro_suke:20180924223058p:plain

Lambda の管理画面に遷移して「関数の作成」ボタンをクリックすると

関数の作成画面に遷移する。

そこで下記のように好きな関数名をつけて、ランタイムに「Node.js 8.10」を選択して、

f:id:piro_suke:20180924223941p:plain

実行ロールについて今回は特にログ保存以外のAWSサービスを使用しないので

「テンプレートから新しいロールを作成」を選択し、

「lambda_basic」みたいな名前をつけて

関数を作成する。

f:id:piro_suke:20180924224144p:plain

関数の作成に成功すると、トリガーやスクリプトの登録ができる設定画面に遷移する。

とりあえず必要なのが関数コードの登録。

f:id:piro_suke:20180924224352p:plain

ここでzipファイルをアップロードする。

その際、ハンドラには先程作成した

メイン実行用ファイル名(lambda.jsのlambda)と

ハンドラ名(exports.handlerのhandler)を組み合わせて

「lambda.handler」を設定しておく。

今回のスクリプトはWebスクレイピングとAPI接続を伴うので、

「基本設定」でタイムアウトを「30秒」に延ばしておく。

f:id:piro_suke:20180924224821p:plain

テストする

トリガーを追加する前にアップロードしたスクリプトがちゃんと実行できるか、

テストしておこう。

テストは「テストイベント」設定でスクリプトに渡すパラメータを指定しておき、

それを実行する形で行う。

画面右上の「テスト」メニューでテストイベントの設定ができる。

今回のスクリプトはパラメータを受け取らないので、

「NoParamTest」みたいな名前で「{}」を保存しておけばOK。

作成したテストイベントはプルダウン選択できるようになるので、

「NoParamTest」を選択して「テスト」ボタンを実行すると関数が実行される。

それで成功メッセージが表示されてSlackに投稿が行われたら問題なし。

f:id:piro_suke:20180924225443p:plain

トリガーを追加する

最後に、この関数を実行するためのトリガーを登録する。

ひとまず、「指定時実行」と「URLアクセス時の実行」の

2つの方法を押さえておけば大抵事足りると思うので、この2つの方法について書いておく。

トリガーの登録は編集画面上部の「トリガーの追加」ブロックで行う。

「指定時実行」する場合は「CloudWatch Events」を選択する。

選択すると下に編集フォームが表示されるので、ここで細かい設定を行う。

例えば「毎週月曜0時0分」に実行したい場合、下記のように設定する。

ルール名は分かりやすい名前をつけておく。

f:id:piro_suke:20180924230409p:plain

時間はUTCなので実際に必要な時間の9時間前にセットしておく。

月曜0時なら日曜15時かな。

f:id:piro_suke:20180924230508p:plain

スケジュール式の設定方法は下記のヘルプページに記載されている。

ルールのスケジュール式 - Amazon CloudWatch Events

これで追加しておけばOK。

次に「URLアクセス時に実行」する場合は「API Gateway」を使用する。

選択すると下記のような編集フォームが表示される。

f:id:piro_suke:20180924230835p:plain

API項目で「新規APIの作成」を選択するとセキュリティ項目が表示され、

APIへのアクセスにどのような認証を必要とするかを選択できる。

「AWSIAM」はIAM認証だと思うけど試してない。

「オープン」は認証なし。APIのURLにアクセスするだけで実行できる。

「APIキー使用でのオープン」は正しいAPIキー付きでアクセスした場合だけ実行できる。

今回は「APIキー使用でのオープン」を選択するとする。

アクセス用のURLとAPIキーは追加時に生成される。

APIキー付きのアクセスはリクエスト時のヘッダーに

「x-api-key」という名称でAPIキーをつけてアクセスする。

curlなら下記のような形

curl https://<APIのURL> --header 'x-api-key:<APIキー>'

これで実行してSlackに投稿されたら登録に成功してる。

これでトリガー登録完了で、Lambda関数の登録完了。

おわり

説明は長くなったけど、1つ作ってしまえば他のスクリプトの登録も

そんなに難しくないと思う。

データベースにデータを保存したり、ファイルを入出力する場合は

追加の設定が必要だけど、ちょっとしたデータ収集、変換と通知なら対応できるはず。

AWS Lambda実践ガイド

AWS Lambda実践ガイド