初めてのAWS Lambda(どんな環境で動いているのかみてみた)

月曜日から3日間、AWSアーキテクト研修でした。そこではじめてLambdaに接しまして、ひととおり驚いてきたところです。自分なりのまとめです(ご存知の方には釈迦に説法)。

Lambdaとは?

恥ずかしながら私は「サーバーレス」という言葉を聞いてもいまいちピンと来ていませんでした。ですが、これは文字通りなんですよね。プログラムが動作する環境なんてどうだっていいんです。OSがなんだとか、ミドルウェアがなんだとか、メモリがどれくらいでCPUがいくらで、NWがどうで、トポロジーがなんでとか、そんなことはどうでもいいんです。
とにかく、「トリガー」と呼ばれる”きっかけ”を契機に、コード(プログラム)が動くんです。JavaならJVMがおもむろに立ち上がって、アップロードしておいたjarが実行されるんです。「トリガー」はAWSサービスと高度に統合されていて、例えば

  • ファイルストレージサービスであるところのS3(もはや単なるストレージの域を超越していますが)にファイルがアップロードされた
  • メッセージがキューにputされた
  • API Gatewayにリクエストがきた
  • EC2インスタンスが起動した
  • 3時になった
  • おなかがすいた(とAmazon echoに話した)
  • e.t.c.

MDBならぬTDB(Trigger Driven Bean)でしょうか。Beanである必要もないので、TBC(Trigger Driven Code)とでも言ったほうがいいのかもしれません。

うごかしてみる

研修の間、実習時間に余裕があったので、研修端末に入っていたEclipseで簡単なコードを書いて動かしてみました。テストはAWS Consoleからキックできるので、特に「トリガー」を定義しなくても動かすだけなら簡単に試せます。

お作法

基本的にどんなJavaプログラムでも必要なライブラリを組み込んでおけば動きますが、コールするメソッドにはお約束があるようです。それは引数です。第一引数にObjectをもらい、第二引数にContextをもらいます。メソッド名はなんでもいいです。型もなんでもいいです(ただし第一引数と戻り値の型ともにSeriarizableである必要あり。プリミティブ型もOK)。

第一引数に入るのは、具体的には「トリガー」からの情報です。メッセージがキューにputされたことをトリガーとするのであれば、そのメッセージ自体を渡してあげたり。戻り値は同期呼び出しであればほぼそのまんまでしょう。インタフェース要件に従って、Serializeして返してあげればよいだけです。

第二引数のContextですが、これはjavax.naming.Contextではなく、com.amazonaws.services.lambda.runtime.Contextです。というわけで、AWSが提供するjarファイルをビルドパスに追加する必要があります。1

作る

まだ意味のあるコードを書くほどの技量もアイディアもないので、インフラ屋っぽくどんな環境(システムプロパティ、環境変数、渡されたContextオブジェクト)で動いているのかみてみることにしました。

SystemInfo.class
package net.mognet.aws.lambda;

import java.util.Map;
import java.util.Properties;

import com.amazonaws.services.lambda.runtime.Context;

public class SystemInfo {

    public static String printSystemInfo(int i, Context context) {
            StringBuilder sb = new StringBuilder();
            //ヘッダを追加
            sb.append("name,value\n");

            //システムプロパティ取得
            Properties prop = System.getProperties();
            for(Object key : prop.keySet()) {
                String name = (String) key;
                String value  = prop.getProperty(name);
                sb.append(name + "," + value + "\n");
            }
            //環境変数取得
            Map<String, String> env = System.getenv();
            for(String key : env.keySet()) {
                String value = env.get(key);
                sb.append(key + "," + value + "\n");
            }
            //Contextの情報を取得
            sb.append("context" + "," + context.toString());
            return sb.toString();
    }

    public static void main(String[] args) {
            System.out.println(printSystemInfo(1, null));
    }
}

mainはテスト用です。1個目の引数こそ本来は大事なんでしょうけど今回は何もしません。

乗せる

AWSコンソールを開いてLambdaの関数を作ります(関数という単位で動きます。複数の関数をオーケストレーションするサービスもあるようです(詳細未調査))。
スクリーンショット 2018-01-31 21.39.16.png
適当に名前とランタイム(今回はJava8)を選んで「関数の作成」を押します。
標準出力はCloudWatchLogsへ流れるので、事前にCloudWatchLogsへのWrite権限のあるロールを作って必要に応じてここでアタッチしてください。
スクリーンショット 2018-01-31 21.39.50.png
スクリーンショット 2018-01-31 21.39.57.png
本来ならここでトリガーを選んで云々となりますが、とにかくテストしてみたいだけなので、その辺の条件だけいれます。
スクリーンショット 2018-01-31 21.40.05.png
関数コードのところで、「アップロード」からjarファイルをアップロード、大事なのが「ハンドラ」でここに実行するメソッドを入力します。書き方が決まっていて、”.”表記でクラスのフルパスの後ろに”::“をつけてメソッド名です。
今回は”net.mognet.aws.lambda.SystemInfo::printSystemInfo“となります。ついでに環境変数もつけてみました。一旦「保存」すると実際にファイルがアップロードされます。
スクリーンショット 2018-01-31 21.41.03.png
次にテストの準備です。テストケース(入力設定=第一引数設定)です。画面上部の「テストイベントの設定」を選びます。
スクリーンショット 2018-01-31 21.41.19.png
実行するメソッドpublic static String printSystemInfoの第一引数がintなので、1とだけ書いて終わりです。下の方にある「保存」を押します。これでテスト準備完了です。

いざ実行!

おもむろに「テスト」を押します。
スクリーンショット 2018-01-31 21.42.15.png
動きました。今回はログ出力(標準出力)なしなので、ログは見ませんが開始と終了のメッセージが出ていました。String型のメソッドを実行したので、returnした文字列がそのまま画面上に表示されています(改行コードは改行してほしかったけど実行結果表示コンソールとしてはこれが正しいあり方ですね)。

付録

付録で実行結果を載せておきます。

name value
java.runtime.name OpenJDK Runtime Environment
sun.boot.library.path /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/amd64
java.vm.version 25.141-b16
java.vm.vendor Oracle Corporation
java.vendor.url http://java.oracle.com/
path.separator :
java.vm.name OpenJDK 64-Bit Server VM
file.encoding.pkg sun.io
user.country US
sun.java.launcher SUN_STANDARD
sun.os.patch.level unknown
java.vm.specification.name Java Virtual Machine Specification
user.dir /
java.runtime.version 1.8.0_141-b16
java.awt.graphicsenv sun.awt.X11GraphicsEnvironment
java.endorsed.dirs /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/endorsed
os.arch amd64
java.io.tmpdir /tmp
line.separator
java.vm.specification.vendor Oracle Corporation
os.name Linux
sun.jnu.encoding UTF-8
java.library.path /lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
java.specification.name Java Platform API Specification
java.class.version 52.0
sun.management.compiler HotSpot 64-Bit Tiered Compilers
os.version 4.9.77-31.58.amzn1.x86_64
user.home /home/sbx_user1066
user.timezone UTC
java.awt.printerjob sun.print.PSPrinterJob
file.encoding UTF-8
java.specification.version 1.8
java.class.path /var/runtime/lib/LambdaJavaRTEntry-1.0.jar
user.name sbx_user1066
java.vm.specification.version 1.8
sun.java.command /var/runtime/lib/LambdaJavaRTEntry-1.0.jar
java.home /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre
sun.arch.data.model 64
user.language en
java.specification.vendor Oracle Corporation
awt.toolkit sun.awt.X11.XToolkit
java.vm.info mixed mode, sharing
java.version 1.8.0_141
java.ext.dirs /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/lib/jfr.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.141-1.b16.32.amzn1.x86_64/jre/classes
java.vendor Oracle Corporation
file.separator /
java.vendor.url.bug http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding UnicodeLittle
sun.cpu.endian little
sun.cpu.isalist
PATH /usr/local/bin:/usr/bin/:/bin
AWS_XRAY_DAEMONADDRESS 169.254.79.2
LAMBDA_TASK_ROOT /var/task
AWS_LAMBDA_FUNCTION_MEMORY_SIZE 128
TZ :UTC
AWS_SECRET_ACCESS_KEY secret
AWS_EXECUTION_ENV AWS_Lambda_java8
AWS_DEFAULT_REGION ap-northeast-1
AWS_LAMBDA_LOG_GROUP_NAME /aws/lambda/SystemInfo
XFILESEARCHPATH /usr/dt/app-defaults/%L/Dt
_HANDLER net.mognet.aws.lambda.SystemInfo::printSystemInfo
LANG en_US.UTF-8
LAMBDA_RUNTIME_DIR /var/runtime
AWS_SESSION_TOKEN tokenString
AWS_ACCESS_KEY_ID accessKeyId
LD_LIBRARY_PATH /lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib
X_AMZN_TRACEID Root=1-5a71b98c-393aaa7b51f5612a348586c0;Parent=3ff8164301e3ccd4;Sampled=0
AWS_SECRET_KEY secretKey
hogehoge gehogeho
AWS_REGION ap-northeast-1
AWS_LAMBDA_LOG_STREAM_NAME 2018/01/31/[$LATEST]29640ec0ac8e426ab2b0a041b3a1b1f4
AWS_XRAY_DAEMON_ADDRESS 169.254.79.2:2000
AWS_XRAY_DAEMONPORT 2000
NLSPATH /usr/dt/lib/nls/msg/%L/%N.cat
AWS_XRAY_CONTEXT_MISSING LOG_ERROR
AWS_LAMBDA_FUNCTION_VERSION $LATEST
AWS_ACCESS_KEY accessKey
AWS_LAMBDA_FUNCTION_NAME SystemInfo
context lambdainternal.api.LambdaContext@604ed9f0

アクセスキー等の情報も環境変数に乗っていましたのでそこはマスクしてます。そういう仕様だということは理解しておくべきかもしれません。この辺のキーを使ってAWS API呼び出したりするのかな?あと、ちゃんと設定した環境変数も出て来てます(あたりまえですが)。
OpenJDK on Amazon Linuxで動かしているみたいですね。こればっかりは実際に本稼働したときにどうなるかわかりませんけれども。あくまでこのテスト実行時はこうでした、というだけです。なんといってもサーバーレスですので、繰り返しになりますが実行環境(HW、OS、MW等々)はどうでもいいです。というか、どうでもいい前提でコードを書いてください、というのがLambda的な使い方と認識しました。

参考

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


  1. EclipseにはAWSのツールキットプラグインがあるので、この環境をセットアップしておくだけでも可です。 

続きを読む

Windows Server で動的コンテンツを EC2 + RDS で作ってみるの巻

目的

検証用のアプリケーションを作成する

目標

Web 動的コンテンツから、RDS Mysql へデータ書き込みを行う

環境

EC2 t2.miclo WindowsServer2016
Eclipse 4.7 Oxygen の Windows 64bit Full Edition Java
Tomcat 8

その他、下のサイト参照
https://qiita.com/hatakkkk/items/cb8dd22041d75952c8d7

Eclipse 動かすのに t2.miclo は小さいかも。。。

JDBC の導入

お勉強用に下のサイトを参考にした
http://web.sfc.wide.ad.jp/~tinaba/tutorials/mysql-j/
https://qiita.com/ononoy/items/4961ce6d5b12aff6c108
https://www.qoosky.io/techs/d2beb9dc80

Mysql の JDBCドライバについて、Aamazon から情報が提供されているようなので、下のサイトを確認する。
https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/java-rds.html

Mysql のJDBC ドライバをインストールする

下のサイトから、”mysql-connector-java-5.1.45.zip” をダウンロードする
https://dev.mysql.com/downloads/file/?id=474258

「あれ?ダウンロードが始まらないな?」というときは、
Web サイトの中央下付近にある文章”No thanks, just start my download.” をクリック

今日はここまで。

続きを読む

【聴講メモ】Developers Festa Sapporo 2017

2017年11月10日(金)に開催されたDevelopers Festa Sapporo 2017を聴講してきました。

JavaOne 2017 フィードバック~今後のJava開発・運用のために知っておくべき事総まとめ~

伊藤 敬(日本オラクル株式会社)


2017/10/1~2017/10/5までサンフランシスコで開催されたJavaOne 2017の内容についてのお話し。
こういうクラスができてとかいう技術的な話しはほとんどなく、リリースサイクルとかオープン化な話が多かった。
Javaから離れて結構久しいので、何か軽量化されるぐらいしか知らなかったんですが、半年後にはJava10がリリースされるとかいう話しは結構衝撃的。

Java SE 9

▼過去最多の機能追加

20(Java7)→55(Java8)→91(Java9)
リリースが延びたのも要因の1つだそう…

▼リリースモデルの転換

従来
・OpenJDKとOracleJDKに機能差分がある
 私、これ聞くまでOpenJDK自体知りませんでした…

・機能リリースは2年に1度(ただし目標)

・それぞれの機能リリースごとの長期サポート
 Java8まではおおむね8年サポートしてるんだそう

・更新リリースは3か月ごと
 セキュリティパッチに加えて限定的機能更新も含まれていたそう

これから
・OpenJDKとOracleJDKに機能差分をなくしていく
 すぐにではなく徐々に的な雰囲気

・機能リリースは6か月に1度(固定)
 リリース日は決めて、そこに間に合った機能を載せていくイメージ
 次出るバージョンはJava10→11→12のナンバリング
 18.3→18.9→19.3と西暦年+月の組み合わせにしてたのが最近方針転換されるらしい

・長期サポートはOracleJDKのみ
 ここの話が、最近Javaから離れている自分にはよく理解できなかった。
 長期サポート(LTS:Long Time Support)は、6か月ごとの機能リリースではなく、その中の特定のリリース(おおむね3年ごと)が対象となると認識しました。
 それのスタートが2018年9月で、次の長期サポートターゲットが201年9月ということらしい。
 商用利用で頻繁にバージョンアップをしたくない企業向けなんですかね…

・更新リリースは3か月ごと
 セキュリティパッチのみで機能更新は含まない

▼転換期故の弊害

公式アップデート終了はJava10より9の方が早い
Java8:2018.9
Java9:2018.3
Java10:2018.9

今Java8を使っている場合は、次のターゲットは10にした方が良い

▼その他

Java8から後継バージョンへの自動更新は行われない

Java SE9の32bit版バイナリの提供予定はない
官公庁への影響は大きいだろうとのこと(実際説明に行って叱責を受けることが多いそう…)

jlinkを利用したパッケージングとデプロイ
 クライアントに入っているJREに依存しないアプリを作れるんだそう
 (Developers Festaなんだから、こういう話しをもっと聞きたかったですね…)

Java EE 8

▼JavaEEが抱える問題点
・軽量でない
・業界トレンドとのギャップ

▼Eclipse Foundationへの移管

Oracleはもう関わらないというわけではなく、仕様策定の主管をEclipse Foundationへということ。

Eclipseでの開発プロジェクト名はEE4J(Eclipse Enterprise for Java)

Other

▼fn.project.io
Java対応のFaaS(Function as a Service)
いわゆる、Oracleのサーバレスプラットフォームのようです

蛇足

じゃんけんでTシャツいただきましたw
Mサイズで自分には若干小さかったので妻へのプレゼントとなりました。

image.png

The State of Serverless Computing

西谷 圭介(アマゾン ウェブ サービス ジャパン株式会社)


昨年に続いての聴講。
序盤でサーバレスとは?に簡単に触れ、後はアーキテクチャパターンについての実用的なお話しでした。
西谷さんが使っていた、スライドが暗くなって、指し示した一部分だけが明るくなるアイテムが気になって途中から話半分でしたw

サーバレスとは?

コンピューティングの進化の中、未だ残る制限
物理サーバー

仮想サーバー(オンプレ)

仮想サーバー(クラウド)

と進化していく中で、サーバーの管理業務からは解放されていない

サーバーは管理しない方が簡単
ファンクションは短命
なので
・サーバレスはよりセキュア
・コストを抑えられる
 アイドル時の支払いなし
 ただし、実行回数の分は掛かる(コスト効率はいいが、何でもかんでも安くなるわけではない)

サーバレスは管理業務からの解放

アーキテクチャーパターン

・Web Application
・Backends
・Data Processing
 →一番使われるパターン(RealTime、MapReduce、Batch)
・Chatbots、Amazon Alexa
・IT Automation
 →一番ハードルが低い(CIなど)

ベストプラクティス

The Twelve-Factor App (日本語訳)

この中からいくつか出てきましたが、印象に残ったのは
・ステートレス
・開発/本番で一致した状態

この話の流れでLambdaについての技術的なお話し
・FUnctionのコンピューティングリソース設定
 メモリ設定の見極めが肝心

・コールドスタートとウォームスタート
 安定的にリクエストが来ていれば最初しかコールドスタートは発生しない
 コールドスタートのスピードを許容できないのであれば、Lambdaは正解ではない(使うな)

アンチパターン

▼AWS Lambda + RDBMS

・コネクション数の問題
・VPCコールドスタートの問題
・DynamoDBを使うべし

▼IP固定したがり問題

▼Serverless != Monitorless
・監視はちゃんとしよう

IoT 時代を生き抜くエンジニアに必要な技術とは

松下 享平(株式会社ソラコム)


こちらは別記事にまとめました。

以上

続きを読む

Serverless FrameworkでEC2をスケジュール起動/停止するテンプレート(Lambda[java])

Serverless Framework

はじめに

  • コンテナ付いてる昨今は、久しくServerless Framework触って無かったのですが、EC2を8時に起動して、20時半に停止する要件が浮上したので、サクッとslsで作りました。
  • ソースはGithubで公開してます。
  • 至極簡単な内容なので、速攻実現出来ると思ってます。

環境のセットアップ

Serverless FrameworkでDeploy

ソースの取得

  • 以下のGithubからソースを取得します。
$ git clone https://github.com/ukitiyan/operation-ec2-instance.git

STS(Eclipse)にインポート

  • STSを起動して、Project Explorer -> 右クリック -> Maven -> Existing Maven Projectsで先程Githubから取得した「operation-ec2-instance」フォルダを選択します。

serverless.yml の修正 + Build

  • serverless.ymlのL37 周辺の設定を適宜修正します。

    • rate: AWS Lambda – Scheduled EventのCron書式
      を参考に UTC で記載
    • instanceId: 対象インスタンスのinstanceIdを記載
    • scheduleは、縦に増やせるので複数インスタンスに対応できます。(それを踏まえて環境変数でinstanceIdを指定してません)
serverless.yml
- schedule:
    rate: cron(30 11 * * ? *)
    input:
      goal: stop
      instanceId: i-XXXXXXXXXXXXXXXXX
- schedule:
    rate: cron(0 23 * * ? *)
    input:
      goal: start
      instanceId: i-XXXXXXXXXXXXXXXXX
  • プロジェクトを右クリック -> Run As -> Maven Install でビルドします。
  • target配下にoperation-ec2-instance.1.0.0.jarが出来上がります。

Deploy

  • 例のごとく、以下のコマンド一発です。
$ cd operation-ec2-instance
$ serverless deploy -v
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
・
・
Serverless: Stack update finished...
Service Information
service: operation-ec2-instance
stage: prod
region: ap-northeast-1
api keys:
  None
endpoints:
  None
functions:
  aws-java-maven-prod-hello: arn:XXXXXXXX
  • 以下のJsonでコンソールから test するか、設定時間になるまで待って、問題ないか気にしておきましょう。
{
  "goal": "stop",
  "instanceId": "i-XXXXXXXXXXXXXXXXX"
}

まとめ

  • まぁ、やっつけですが。速攻実現出来ました。
  • STSで完結するのが、アプリ屋にとっては本当うれしいです。

続きを読む

いまからはじめるJavaでAWS Lambda(ラムダ) 前編

いまからはじめるJavaでAWS Lambda(ラムダ)

AWS Lambda関数をJava8で記述し、AWS上 Lambda Functionとしてアップロードし、それをJavaクライアントから呼び出す例をステップ・バイ・ステップで紹介します。

想定読者

  • Javaが書けて、はじめてAWS Lambdaをつかう人
  • いままではnode.jsでLambda関数を作っていたが、わけあってJavaでつくってみようとおもう人(=私のような)

記事構成

TL;DR 前編・後編で書きます

  • 【前編】 JavaでLambda関数(クラウド側)と、クライアント(ローカル側)をお手軽に作る←本稿
  • 【後編】 Lambda関数(クラウド側)の同期型、非同期型の呼び出しタイプ(Invocation Type)と、Javaクライアント側の同期、非同期呼び出し、API Gatewayでの公開

AWS Lambda(ラムダ)とは

  • 自前でサーバーを作ったり、管理したりする必要が無く、コードをAWS Lambdaにあげるだけで各種リクエスト(イベント)の処理が可能な素敵なサービスです。

 http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html
 https://aws.amazon.com/jp/lambda/faqs/

  • AlexaのスキルもAWS Lambdaで作成することができます。

JavaとAWS Lambda

  • JavaでAWS Lambdaの処理(Lambda関数)を記述することが可能
  • Javaクライアントから、直接Lambda関数を呼び出すことも可能
  • Javaに限らないが、API Gatewayというサービスと連携させると、Lambda関数にendpointをつくって公開することができ、Web APIのようにGETやPOSTといったHTTP(S)経由でも呼び出すことが可能

目次

以下のステップを1つずつ説明しつつ実施していきます

  1. AWS Lambda関数をJavaでコーディングする
  2. アップロード用のjarファイルを作る
  3. jarファイルをAWS Lambdaに登録して実行可能(呼び出し可能)にする
  4. AWSコンソール上でテストする
  5. ローカルJavaから、AWS Lambda関数を呼び出しする

1.AWS Lambda関数をコーディングする

Java用ライブラリ読み込み

aws-lambda-java-coreライブラリを追加します。mavenを使う場合は、以下の記述を追加します

maven
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-lambda-java-core</artifactId>
    <version>1.1.0</version>
</dependency>

Lambda関数の中身をコーディングする

ここでは、関数を呼び出すと結果が返るリクエストレスポンス型(RequestResponse)のLambda関数をつくります。

今回はaws-lambda-java-coreライブラリが用意しているRequestHandlerインタフェースを実装して、POJOを入出力できるLambda関数を実装します。

以下は、姓(lastName)と名(firstName)を入力すると、フルネームを出力してくれるLambda関数のサンプルです。

MyLambda.java
package lambda.cloud;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

import lambda.cloud.MyLambda.Input;
import lambda.cloud.MyLambda.Output;

public class MyLambda implements RequestHandler<Input, Output> {

  @Override
  public Output handleRequest(Input in, Context context) {

    final Output out = new Output();
    out.in = in;
    out.fullName = in.firstName + "_" + in.lastName;

    return out;
  }

  public static class Input {
    public String firstName;
    public String lastName;
  }

  public static class Output {
    public Input in;
    public String fullName;

  }

}

以下のように、handleRequestメソッドを実装するだけです。引数 Input はリクエスト、戻り値 Output がレスポンスを示します。

 public Output handleRequest(Input in, Context context) {

2. アップロード用のjarファイルを作る

次にLambda関数としてAWS Lambdaで使えるようにするためにはコードをjarファイル(またはzip)にワンパッケージ化してAWS Lambdaにアップロードする必要があります。

このjarファイルは、いまつくったコードの他、依存しているライブラリなどをひとまとめに統合しておく必要があります。

ひとまとめに統合したjarファイル(つまりfat jar)をつくるためにmaven pom.xmlのplugins以下にmaven-shade-pluginを追加しておきます。

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

pom.xml

pom.xml全体は、以下のようになります

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>lambda.cloud</groupId>
    <artifactId>mylambda</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <name>AWS lambda example</name>
    <description>example of AWS lambda
    </description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

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

        </plugins>
    </build>

</project>

ソースコードダウンロード

上記サンプルのフルソースコードは以下にあります
https://github.com/riversun/aws_lambda_example_basic_client.git

Eclipseでcloneする場合
File>Import>Git>Projects from Git>Clone URI
https://github.com/riversun/aws_lambda_example_basic_client.git を指定
import as general projectを選択してインポート
– インポート後に、プロジェクト上を右クリックしたメニューでConfigure>Convert to Maven projectを実行

これでEclipse上でMavenプロジェクトとなるので、以後取り扱いが楽になります。

mavenをつかってアップロード用のjarを作成する

  • (1)コマンドラインでjarを作る場合

pom.xmlのあるディレクトリで以下のコマンドを実行します

mvn packgage
  • (2)Eclipse上でjarを作る場合

 Step 1. 右クリックメニューでRun As>Maven buildを選択する
 

 Step 2. Edit configurationsダイアログで、Goalspackage shade:shade と入力しRun
 

(1)(2)いずれの場合でも、/target以下にすべての依存コード・ライブラリを含んだ mylambda-1.0.0.jarが生成されます。

これがAWS Lambdaアップロード用のjarファイルとなります。

3. jarファイルをアップロードしてAWS Lambdaに登録する

AWSコンソールからLambda関数を登録する手順をみていきます

(1)AWS Lambdaを開く
Lambdaサービスを開きます。
img01.png

(2)新しいLambda関数を作る
まだLambda関数をつくっていない場合は以下のような画面になります。

関数の作成(Create function)をクリックします。

img02.png

(3)Lambda関数を[一から作成]する

以下のように設計図(blue print)一覧から選択する画面が出ますが、一から作成(Author from scratch)を選択します。

img03.png

(4)基本的情報画面で名前、ロールを設定する

ここでは、myFunctionという名前のLambda関数をつくっていきます

  • 名前(Name)は「myFunction
  • ロール(Role)は「テンプレートから新しいロールを作成(Create new role from template)
  • ロール名(Role name)は「myRole

入力できたら、関数の作成(Create function)をクリックします

img04.png

(5) jarファイルをアップロードする

img06.png

STEP 1
まず、画面上部にある、ARNを確認しておきます
画面に表示されている、arn:aws:lambda:ap-northeast-1:000000000000:function:myFunction部分をメモしておきます

ご存知ARN(Amazon Resource Name)はAWS リソースを一意に識別するためのもので、Lambda関数実行時にその特定のために使います。

STEP 2
ランタイム(runtime)からJava8を選択する

STEP 3
ハンドラ(Handler)にlambda.cloud.MyLambdaと入力する
ハンドラ名は、パッケージ名.クラス名::メソッド名 のフォーマットで記述します。
さきほど作ったコードにあわせて、パッケージ名が、lambda.cloud、クラス名がMyLambdalambda.cloud.MyLambdaを入力しています。この例では、メソッド名は省略しても動作します。

STEP 4
アップロード(upload)をクリックしてさきほど作ったmylambda-1.0.0.jarをアップロードします。

はい、ここまでで、さきほど自作したLambda関数がAWS Lambdaに登録されました。

4. AWSコンソール上でテストする

アップロードが終わったら、コンソール上からテストしてみます。

(1)テスト用イベントの準備

Lambda関数は 何らかのイベントをトリガーにして起動する という考え方があり、たとえば、S3バケットにオブジェクトが作成されたというイベントをトリガーとして、Lambda関数を発火させる、という使い方があります。

そこで、Lambda関数を実行するための入力のことをイベントと呼びます。

ここでは、イベントをコンソール上から発火させ、Lambda関数の動作を確認します。

画面上部にある、テストイベントの設定(Configure test events)を選択します。

img07.png

すると以下のように、テストイベントの設定(Configure test events)画面が開きますので、ここでテスト用のイベントを作成します。

image08b.jpg

イベント名(Event name)MyEventとして、その下にあるエディットボックスはLambda関数をリクエストするときの本文です。

さきほどつくったPOJOで入出力するLambda関数は、実際の呼び出しではPOJOが自動的にJSONにマップされ、JSONで入出力されます。

そこで以下のようにJSON形式でイベントの本文を入力します

{
  "firstName": "john",
  "lastName": "doe"
}

入力したら、画面下にある作成を押します。

(2)テスト用イベントの実行

いま作成したMyEventを早速実行します。画面右上のテストを押します。

img09.png

ちなみに、「ハンドラーで指定されたファイル名がデプロイパッケージのファイル名と一致しないため、Lambda 関数 「myFunction」はインラインで編集できません。」というメッセージが表示されても気にしなくてOkです。
Lambdaコンソールでは、Java、C# などのコンパイル済み言語のインラインエディタは提供されていません。

(3)実行結果を確認する

テストを押してしばらくすると、結果画面が表示されます。

成功のときは、実行結果: 成功(Execution result:succeeded)と表示されます。その下の▼詳細(details)を押して展開すると結果の詳細を確認できます。

img10.png

Lambda関数の出力も、さきほどのPOJO Outputクラスが以下のようなJSONに変換されます。

入出力用のPOJO
public static class Input {
    public String firstName;
    public String lastName;
  }

  public static class Output {
    public Input in;
    public String fullName;

  }
実行結果
{
  "in": {
    "firstName": "john",
    "lastName": "doe"
  },
  "fullName": "john_doe"
}

5. ローカルJavaから、AWS Lambda関数を呼び出す

さきほど作ったLambda関数 myFunction をJavaプログラムから呼び出します。

ライブラリを読み込む

JavaからAWS Lambda関数をたたく場合には、aws-java-sdk-lambdaライブラリを追加します。mavenを使う場合は、以下を追加します

maven
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-lambda</artifactId>
    <version>1.11.210</version>
</dependency>

Javaクライアント

コードは以下のようになります。(各種設定値はダミーです)

Javaクライアント
public class ExampleLambdaClient {

  public static void main(String[] args) {
    ExampleLambdaClient client = new ExampleLambdaClient();
    client.invokeLambdaFunction();

  }

  private void invokeLambdaFunction() {

    final String AWS_ACCESS_KEY_ID = "ANMNRR35KPTR7PLB3C7D";
    final String AWS_SECRET_ACCESS_KEY = "UKA6EsKY25LJQBEpUvXyYkj8aWKEDnynEZigVPhz";

    AWSCredentials credentials = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);

    // ARN
    String functionName = "arn:aws:lambda:ap-northeast-1:000000000000:function:myFunction";

    String inputJSON = "{"firstName":"john","lastName": "doe"}";

    InvokeRequest lmbRequest = new InvokeRequest()
        .withFunctionName(functionName)
        .withPayload(inputJSON);

    lmbRequest.setInvocationType(InvocationType.RequestResponse);

    AWSLambda lambda = AWSLambdaClientBuilder.standard()
        .withRegion(Regions.AP_NORTHEAST_1)
        .withCredentials(new AWSStaticCredentialsProvider(credentials)).build();

    InvokeResult lmbResult = lambda.invoke(lmbRequest);

    String resultJSON = new String(lmbResult.getPayload().array(), Charset.forName("UTF-8"));

    System.out.println(resultJSON);

  }
}

AWSCredentials credentials = new BasicAWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);
  • アクセスキーID(AWS_ACCESS_KEY_ID)とシークレットアクセスキー(AWS_SECRET_ACCESS_KEY)をつかってcredentialsをつくります。

InvokeRequest lmbRequest = new InvokeRequest()
        .withFunctionName(functionName)
        .withPayload(inputJSON);
  • リクエストを生成します。#withFunctionNameでは関数名を指定します。ARN(arn:aws:lambda:ap-northeast-1:000000000000:function:myFunction)を指定するか、関数名(myFunction)を指定します。ここではARNを指定しました

  • #withPayloadでリクエストの本文(JSON)を指定します。
    ここではJSON文字列 “{“firstName”:”john”,”lastName”: “doe”}”;を指定してます。


lmbRequest.setInvocationType(InvocationType.RequestResponse);
  • 呼び出しタイプ(invocation type)を指定します
    ここでは InvocationType.RequestResponse を指定しています。これでRequestResponse型の呼び出しタイプになります。RequestResponseにしておくと、処理結果を受け取ることができます。

InvokeResult lmbResult = lambda.invoke(lmbRequest);
String resultJSON = new String(lmbResult.getPayload().array(), Charset.forName("UTF-8"));
  • Lambda関数を呼び出して、結果(JSON)を受け取ります。

Javaクライアントのソースコード

クライアント側のフルソースコードは以下においてあります
https://github.com/riversun/aws_lambda_example_basic_client.git

まとめ

  • Javaでも比較的簡単にAWS lambdaの関数を記述できました
  • Javaのクライアントから直接 Lambda関数を呼び出せました

サンプルはEclipse(Oxygen)で作成しましたが、特段プラグインなど必要ありません
(AWS SDK Plugin for Eclipseも不要。あれば、もっと手軽になりますが)

  • 後編では、Lambda関数およびJavaクライアント側の同期/非同期についてや、API Gatewayについて書きます。

おまけ

  • 2009年頃ですがコードだけ書いたら後はおまかせ、課金は使った分だけというサービスの元祖「Google App Engine (GAE)」が登場したときは衝撃をうけました。独特の制約が多かったものの、とてもお世話になりました。

  • 今度はその元祖!?が、Google Cloud Functionsを投入しています。今現在はβで実績はこれからだとおもいますが、今後はそちらも選択肢に入ってきそうです。

続きを読む

AWSのS3サービスにMavenリポジトリを構築

始めに

Apache Mavenを利用する場合、インハウスリポジトリを構築すると便利なので、これまでWebDAVが使えるWebサーバに環境構築していました。
AWSではS3に構築することが出来るので、費用面からしても断然有利なので、今回はこれを使ってみましょう。

準備環境

準備した環境は以下の通り。
Windows7 Pro
Java 1.8
Maven 3.3.3
Eclipse4.6
Spring Boot 1.5.6.RELEASE(デモ用)

AWS作業

AWSは既存のアカウントでも良いですが、今回はセキュリティ対策のため、S3にアクセス可能なアカウントを用意しました。
アクセスポリシーは、こんな感じで良いと思います。
your-bucketのところは、各自で書き換えてください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::your-bucket",
                "arn:aws:s3:::your-bucket/*"
            ]
        }
    ]
}

Java&Eclipse作業

適当なMavenプロジェクトを作成します。
私はSpring Bootプロジェクトで試しました。
以下は抜粋ですので、必要に応じて書き換えてください。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <!-- (省略) -->

  <repositories>
    <repository>
      <id>s3-repos</id>
      <name>AWS S3 Repository</name>
      <url>s3://your-bucket/</url>
    </repository>
  </repositories>

  <distributionManagement>
    <repository>
      <id>aws-snapshot</id>
      <name>AWS S3 Repository</name>
      <url>s3://your-bucket/snapshot</url>
    </repository>
  </distributionManagement>

  <dependencies>
    <dependency>
  <!-- (省略) -->
    </dependency>
  </dependencies>


  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <!-- Windows環境におけるJunit実行時の文字化け対応 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <junitArtifactName>junit:junit</junitArtifactName>
          <encoding>UTF-8</encoding>
          <inputEncoding>UTF-8</inputEncoding>
          <outputEncoding>UTF-8</outputEncoding>
          <argLine>-Dfile.encoding=UTF-8</argLine>
          <!-- <skipTests>true</skipTests> -->
        </configuration>
      </plugin>
    </plugins>
    <extensions>
      <extension>
        <groupId>org.springframework.build</groupId>
        <artifactId>aws-maven</artifactId>
        <version>5.0.0.RELEASE</version>
      </extension>
    </extensions>
  </build>
</project>

あとは、Mavenコマンドで使用するユーザ設定を行います。
Eclipseの場合は、ウィンドウ→設定→Maven→ユーザー設定か、Maven実行時にユーザ設定を指定します。
Maven実行時のパラメータ設定でも良いかと思います。
S3へのアクセスにプロキシ設定が必要な場合は、プロキシサーバの設定も適宜追加します。

setting.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
  <proxies>
    <proxy>
      <id>http_proxy</id>
      <active>true</active>
      <protocol>http</protocol>
      <host>xx.xx.xx.xx</host>
      <port>xx</port>
    </proxy>
    <proxy>
      <id>https_proxy</id>
      <active>true</active>
      <protocol>https</protocol>
      <host>xx.xx.xx.xx</host>
      <port>xx</port>
    </proxy>
    <proxy>
      <id>s3_proxy</id>
      <active>true</active>
      <protocol>s3</protocol>
      <host>xx.xx.xx.xx</host>
      <port>xx</port>
    </proxy>
  </proxies>
  <servers>
    <server>
      <id>aws-release</id>
      <username>アクセスキーID</username>
      <password>シークレットアクセスキー</password>
    </server>
    <server>
      <id>aws-snapshot</id>
      <username>アクセスキーID</username>
      <password>シークレットアクセスキー</password>
    </server>
  </servers>
</settings>

実行結果

mvn deplyコマンドを実行して、S3に登録を行います。
snapshotかreleaseかは、アクセスするリポジトリ名を切り替えればOKかと思います。
他にいいやり方があるとは思いますが、とりあえずこれで。

[INFO] --- maven-deploy-plugin:2.8.2:deploy (default-deploy) @ ews ---
[INFO] Uploading: s3://your-bucket/snapshot/com/example/1.0.0/sample-1.0.0.jar
[INFO] Configuring Proxy. Proxy Host: xx.xx.xx.xx Proxy Port: xx
[INFO] Uploaded: s3://your-bucket/snapshot/com/example/1.0.0/sample-1.0.0.jar (2000 KB at 1000.0 KB/sec)
[INFO] Uploading: s3://your-bucket/snapshot/com/example/1.0.0/sample-1.0.0.pom
[INFO] Uploaded: s3://your-bucket/snapshot/com/example/1.0.0/sample-1.0.0.pom (5 KB at 1.0 KB/sec)
[INFO] Downloading: s3://your-bucket/snapshot/com/example/maven-metadata.xml
[INFO] Uploading: s3://your-bucket/snapshot/com/example/maven-metadata.xml
[INFO] Uploaded: s3://your-bucket/snapshot/com/example/maven-metadata.xml (300 B at 0.1 KB/sec)

考察

インターネットで調べたものの、手順がよく分からなかったので、実際にやってみました。
他のプロジェクトからうまく引っ張れるかどうか試していませんが、jarファイルを一般公開したくないなぁって言う場合には、かなり使えるんじゃ無いでしょうか。

続きを読む