SIer だけど技術やりたいブログ

AWS Lambda と LINE Notify で毎朝のゴミ出しを促す

唐突ですが、我が家の課題に『ゴミ出しを忘れる』というのがあります。たまに、家に帰って扉開けたらやばいニオイがして、激しく後悔します。ということで、LINEへ毎朝のゴミ出しの通知をしてみました。

という建前の AWS Lambda と LINE Notify 触ってみた系の記事です。
結論を先に言うと、LINEに定期的に通知するだけのボットが完成します。

目次

AWS Lambda とは

AWSが提供するマネージドサービスの一つ。
AWS Lambda (サーバーレスでコードを実行・自動管理) | AWS

コードをアップロードさえすれば、インフラを管理することなくコードを実行できるのが利点。AWS Lambda上の様々なイベントを契機にできる。

  • S3のアップロード
  • CloudWatch Logsに書き込まれた特定ログ
  • Dynamo DBのデータ更新
  • CloudWatch Eventsによる定期イベント(cron的な)

LINE Notify とは

LINEのチャンネルにメッセージを通知できる連携サービス。

LINE Notifynotify-bot.line.me

実装

こんなイメージで連携させる。

LINE Notify でパーソナルアクセストークンを発行する

今回はパーソナルアクセストークンを利用する。
トークンを付けてリクエストすることで、チャンネルに任意のメッセージを送れる。

[超簡単]LINE notify を使ってみる - Qiitaqiita.com

AWS Lambda で実行するコードを作成する

おおまかに以下の手順。

  • mavenで依存関係を追加する
  • ハンドラを実装する
  • fat jarを作る

AWS Toolkit for Eclipse』を入れると、コード作成が簡素化できるらしい。
が、今回はお試しなので、すべて自前で作る。

mavenで依存関係を追加する

awsのcoreライブラリを追加する。

    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-lambda-java-core</artifactId>
      <version>1.2.0</version>
    </dependency>
ハンドラを実装する

以下を参考に、Javaのコードを書く。

Lambda 関数ハンドラー (Java) - AWS Lambda

package garbage.notification;

import ...

public class App implements RequestHandler<Integer, String> {
  private static final String ACCESS_TOKEN = "SECRET_ACCESS_TOKEN";
  private static final String API = "https://notify-api.line.me/api/notify";

  @Override
  public String handleRequest(Integer count, Context context) {
    Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tokyo"));

    switch (cal.get(Calendar.DAY_OF_WEEK)) {
      case Calendar.SUNDAY:
        break;
      case Calendar.MONDAY:
        post("今日は生ごみの日");
        break;
      case Calendar.TUESDAY:
        post("今日は燃やさないゴミの日(第2,第4の週なら)");
        break;
      case Calendar.WEDNESDAY:
        post("今日は生ごみの日");
        break;
      case Calendar.THURSDAY:
        break;
      case Calendar.FRIDAY:
        post("今日は生ごみの日");
        break;
      case Calendar.SATURDAY:
        post("今日はプラスチックゴミの日");
        break;
    }
    return "success";
  }

RequestHandler<T1, T2> は AWS Lambdaのハンドラのインタフェース。

  • T1は 入力の型

    • AWSの各イベントに特化したクラスがある。例えばS3のバケット名やアップロードされたオブジェクト名を取得できるS3Eventクラスが存在する
    • aws-lambda-java-events の依存関係が必要
  • T2は 出力の型

T2 handleRequest(T1, Context) はハンドラメソッド。

  • 引数の Context には AWSの実行環境に関する情報が格納される

    • requestIDなど

今回は時刻による定期実行イベントのため、リクエストもレスポンスも適当なプリミティブ型を指定しておく。

次に、HTTPのPOST部分を作成する。
特に変わったところはないので説明は省略する。

  public static void post(String msg) {
    RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());

    try {
      MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
      params.add("message", msg);

      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(new MediaType(MediaType.APPLICATION_FORM_URLENCODED, Charset.forName("utf-8")));
      headers.add("Authorization", "Bearer " + ACCESS_TOKEN);

      RequestEntity req = new RequestEntity(params, headers, HttpMethod.POST, new URI(API));
      restTemplate.exchange(req, String.class);
    } catch (URISyntaxException e) {
      e.printStackTrace();
    }
  }

}
fat jarを作る

以下を参考に、mavenを利用してfat jarを作る。
IDE なしで Maven を使用した .jar デプロイパッケージの作成 (Java) - AWS Lambda

AWSの公式では maven-shade-plugin が例に挙げられているけど maven-assembly-plugin でも動いた。classファイルがすべて含まれた fat jar 形式であることが重要なんでしょう。たぶん。

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.3</version>
        <configuration>
          <createDependencyReducedPom>false</createDependencyReducedPom>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

https://aws.amazon.com/jp/lambda/pricing/

定期実行するイベントを作成する

CloudWatchサービスに移動し、時刻起動のイベントを作成する。
cron式は UTC 時間のため、 JST(日本時間) から -9 時間したものを指定する。

AWS Lambda の設定をする

関数を作成する。

関数コードを設定する
  • 実行環境にjava8を指定する。
  • 作成した fat jar をアップロードする
  • ハンドラを指定する(インタフェースを継承したクラスのFQDNを指定する)
  • タイムアウト時間を延ばす

CloudWatchEvents を指定する
  • 作成した定期実行イベントを指定する

テストする

『テストイベントの設定』をクリックする。

今回はリクエストの型をIntegerとしたため、数値を指定する。
実際にはアプリケーションコードで引数を使ってないので、意味はない。

テストを実行すると、結果が表示される。

また、LINEにも通知されている。

CloudWatch Eventsの入力値を設定する

CloudWatchで作成したイベントの編集にうつり、ターゲット > 入力の設定 で入力値を設定する。

お金の話

無料枠内で使える時間は、利用するメモリに依存するらしく、128MBであれば 3,200,000s が目安になる。ということは 3,200,000s / 15s = 213333 回は実行できるので、当分はお金面の心配はなさそう。

料金体系の詳細はリファレンスを参照してください。
料金 - AWS Lambda(サーバーレスでコードを実行)|AWS

感想

  • こんな簡単なコードでも、ビルド~アップロードが手間
  • メモリ使用量と実行時間に比例してお金がかかるようなので、Javaが向いてるのかはちょっと疑問
  • タイムアウト時間の上限が数分程度なので、バッチ処理をキックするような用途はたぶん間違い
  • (簡易的な自動化のためと割り切れば)Pythonのほうが AWS Lamdba のコンソールでコードを書ける分、ラクそう
  • 動き始めてからは実行環境を気にしなくていい点は素晴らしい
  • LINEに通知した位でゴミ出しができれば苦労はしない
  • ゴミ出しという行為そのものを無くしたい