EMRにてcustom jarを使ったhadoop job stepでs3+ORC+KMSを利用する

要件のニッチさがすごい気はするけどドハマりしたので。
EMRのhadoopのバージョン(正確にはjets3tのバージョン)があがれば特に苦労せん気もするけどね。

ただ、apache的にはs3://は非推奨で、EMR的にはs3://推奨というおっぺけぺーな状態ではあるので今後どうするべきかはまた別個の話になるかねえ。

というわけで本題。

「s3上でkmsにてserver side encryptionされているorcファイルを入力としてmapreduceを動かし、同じくkmsでs3に吐き出す」

細かい部分はぐぐれば出てくるので、重要な部分だけを。

・EMRのcore-site

  {
    "classification":"core-site",
    "properties":{
      "fs.s3.awsAccessKeyId":"access key",
      "fs.s3.impl":"com.amazon.ws.emr.hadoop.fs.EmrFileSystem",
      "fs.s3.awsSecretAccessKey":"secret key"
    },
    "configurations":[]
  }

アクセスキーとシークレットキーのほかにfs.s3.implを設定する。
custom jar側のconfに設定してもいけるみたいだけど未検証。

・java側
よく見かけるアレ、

conf.set("fs.s3n.impl", "org.apache.hadoop.fs.s3native.NativeS3FileSystem");
conf.set("fs.s3n.awsAccessKeyId", "access key");
conf.set("fs.s3n.awsSecretAccessKey", "secret key");

こいつを

//conf.set("fs.s3n.impl", "org.apache.hadoop.fs.s3native.NativeS3FileSystem");
conf.set("fs.s3.awsAccessKeyId", "access key");
conf.set("fs.s3.awsSecretAccessKey", "secret key");

こうする。要するに、impl設定を外して、s3nになってたらs3にする。
もちろんコメントアウトでなくて削除でも問題なし。
addInputPathとかsetOutputPathに渡すPathも、

FileInputFormat.addInputPath(jobConf, new Path("s3://bucket/key"));

みたいにs3nでなくs3にする。

これでいけました。
Emrでのhadoop実行がどのように実施されているか、をちゃんと知っていればハマらなかったのかもしれない。

続きを読む

EMR5.7でAmazonDynamoDBClientBuilderが上手く使えない対応

環境

EMRは5.7。
aws-java-sdk-dynamodbのバージョンは下記。

build.sbt
libraryDependencies += "com.amazonaws" % "aws-java-sdk-dynamodb" % "1.11.170"

sbt assembly で固めたFAT-JARをEMR上にデプロイ&実行するとエラーが発生。
ローカルでは上手く動くので原因がわからず少し困った。

事象

EMR5.7にて AmazonDynamoDBClientBuilder を利用すると NoClassDefFoundError
AmazonDynamoDBClientBuilder はパスを通している(FAT-JARに含まれている)ので、ClassNotFoundException では無いだろうと思っていたが、原因が特定できず戸惑った。

エラー1
17/08/14 10:17:39 INFO Client:
    (略)
java.lang.NoClassDefFoundError: Could not initialize class com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder
        at SparkApp$$anonfun$main$1$$anonfun$1.apply(SparkApp.scala:58)
        at SparkApp$$anonfun$main$1$$anonfun$1.apply(SparkApp.scala:51)
        at org.apache.spark.rdd.RDD$$anonfun$mapPartitions$1$$anonfun$apply$23.apply(RDD.scala:797)
        at org.apache.spark.rdd.RDD$$anonfun$mapPartitions$1$$anonfun$apply$23.apply(RDD.scala:797)
        at org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:38)
        at org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:323)
        at org.apache.spark.rdd.RDD.iterator(RDD.scala:287)
        (略)

しばらく動かしたりログを探すと、IllegalAccessErrorがでていた。
どうやらこれが原因なようだ。

エラー2
17/08/14 11:04:29 INFO Client:
    (略)
java.lang.IllegalAccessError: tried to access class com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientConfigurationFactory from class com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder
        at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder.<clinit>(AmazonDynamoDBClientBuilder.java:27)
        at SparkApp$$anonfun$main$1$$anonfun$1.apply(SparkApp.scala:58)
        at SparkApp$$anonfun$main$1$$anonfun$1.apply(SparkApp.scala:51)
        at org.apache.spark.rdd.RDD$$anonfun$mapPartitions$1$$anonfun$apply$23.apply(RDD.scala:797)
        at org.apache.spark.rdd.RDD$$anonfun$mapPartitions$1$$anonfun$apply$23.apply(RDD.scala:797)
        at org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:38)
        at org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:323)
        at org.apache.spark.rdd.RDD.iterator(RDD.scala:287)

調査

調べるといくつか出てきた。

どうやら、ERM5.7はaws-java-sdkのバージョンが 1.10.75.1 までしか対応していないとのこと。現在(※2017/08/15)は1.11系が出ているので、けっこう古い。というか本当なのか?

ということで、公式のドキュメントを調べると…

AWS SDK for Java が 1.10.75 にアップグレード
* http://docs.aws.amazon.com/ja_jp/emr/latest/ReleaseGuide/emr-whatsnew.html

と書いてあり、1.10.75 までは対応していることは見つけたが、それ以降のバージョンについては述べていない。うーむ、信頼できるのか?

対応

ということで、半信半疑ながらSDKのバージョンをダウングレードした。

build.sbt
libraryDependencies += "com.amazonaws" % "aws-java-sdk-dynamodb" % "1.10.75.1"

そうすると、AmazonDynamoDBClientBuilderが利用できないので、今ではdeplicatedになっているAmazonDynamoDBClientを利用して書き換えすることに。

OLD_dynamodbサンプル.scala
    val client = AmazonDynamoDBClientBuilder.standard
      .withRegion("ap-northeast-1")
      .build
    val dynamoDB = new DynamoDB(client)

こちら↑をすこし編集するのみで動いてくれた。

NEW_dynamodbサンプル.scala
    val client = new AmazonDynamoDBClient()
    client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1))
    val dynamoDB = new DynamoDB(client)

実行結果

実行すると…
上手く動いてくれた。さすがStackOverFlow!

なぜ IllegalAccessError が出てしまうかだが、ERM上でもaws-java-sdkがロードされていると思うので、かみ合わせが悪いのか?このあたりよく分かっていない。

まとめ

EMRでDynamoDBに書き込む時は、SDKのバージョンに気をつけよう。

続きを読む

ElasticseharchのEC2 Discovery Pluginでクラスタ組むのが捗るはなし

こんばんはー

今回、ElasticsearchのクラスタをAWS上に組む際に便利なブラグインを使ってみたいと思いますー
その名も「EC2 Discovery Plugin」☆彡

調べているとAWS Cloud Pluginの使い方を書いている人は結構いるのですが、5系から提供されているEC2 Discovery Pluginはあんまいないのかなーって思いました。
なので、EC2 Discovery Pluginについてつらつら書きたいと思います( ゚Д゚)ゞビシッ

そもそもEC2 Discovery Pluginは何が便利なん?

Elasticsearchのデフォルトで用意されているZen Discoveryを利用してクラスタを組む際は、Unicastでホスト名やIPアドレス指定で組みます。
ただ、クラウド上の運用を考えるとUnicastはつらい。。
そこで、EC2 Discovery Pluginを使うとSecurityGroupを元にクラスタが組まれるのですー(d゚ω゚d)オゥイェー
Multicast Discovery pluginというのもあったのですが、5系から廃止されました

早速ですが、構成の説明したら実装方法を説明したいと思いますー

インスタンス構成

  • InstanceType:t2.medium
  • OS:Amazon Linux AMI release 2017.03
  • Node: 3台
  • ClusterName: es-cluster

こんな感じの流れで書いちゃいますー

  1. AWS準備

  2. Install Oracle JDK 1.8

  3. Install Elasticsearch

  4. Install Kibana(1台のみ)

  5. Install X-pack Elasticsearch & kibana(1台のみ)

  6. Install EC2 Discovery Plugin

  7. 動作確認

AWS準備

SecurityGroup

ElasticsearchをインストールするインスタンスにアタッチするSecurityGroupです。
ソースは、環境に合わせて設定してください。

Type Protocol Port Source Description
Custom TCP 9200 xx Elasticsearchアクセスポート
Custom TCP 9300 xx ノード間のコミュニケーションポート
Custom TCP 5601 xx KibanaのHTTP用ポート
Custom TCP 22 xx SSH用ポート

IAM Role

以下のPolicyを作成し、インスタンスにアタッチしているIAM RoleにPolicyをアタッチしてください。

{
    "Statement": [
        {
            "Action": [
                "ec2:DescribeInstances"
            ],
            "Effect": "Allow",
            "Resource": [
                "*"
            ]
        }
    ],
    "Version": "2012-10-17"
}

AWS Configure

サーバにログインしたらAWS Configureの実行をします。
Access KeyとSecret keyは利用しないため、何も入力しません。

$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]: ap-northeast-1
Default output format [None]: json

下準備が終わったので、ここからElasticsearch周りを進めたいと思いますー

Install Oracle JDK 1.8

$ java -version
java version "1.7.0_141"
OpenJDK Runtime Environment (amzn-2.6.10.1.73.amzn1-x86_64 u141-b02)
OpenJDK 64-Bit Server VM (build 24.141-b02, mixed mode)

### Install Java1.8
$ sudo yum -y install java-1.8.0-openjdk-devel

### alternativesコマンドでJavaのバージョンを切り替える
$ sudo alternatives --config java

There are 2 programs which provide 'java'.

  Selection    Command
-----------------------------------------------
*+ 1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
   2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter to keep the current selection[+], or type selection number: 2

### バージョン確認
$ java -version
openjdk version "1.8.0_141"
OpenJDK Runtime Environment (build 1.8.0_141-b16)
OpenJDK 64-Bit Server VM (build 25.141-b16, mixed mode)

Install Elasticsearch

Elasticsearch5.5.1を入れちゃいたいと思いますー

Install Elasticsearch

### PGP Keyをダウンロード&インストール
$ sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch

### リポジトリの登録
$ sudo vim /etc/yum.repos.d/elastic.repo
[elasticsearch-5.x]
name=Elasticsearch repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

### Elasticsearchインストール
$ sudo yum -y install elasticsearch

### サービス起動登録
$ sudo chkconfig --add elasticsearch

### 確認
$ sudo chkconfig --list | grep elasticsearch
elasticsearch   0:off   1:off   2:on    3:on    4:on    5:on    6:off

### elasticsearch.yml設定変更
$ sudo vim /etc/elasticsearch/elasticsearch.yml
+ cluster.name: es-cluster
+ network.host: 0.0.0.0
+ http.port: 9200

### サービス起動
$ sudo service elasticsearch start
Starting elasticsearch:                                    [  OK  ]

### 起動確認
$ curl http://localhost:9200
{
  "name" : "fDpNQ4m",
  "cluster_name" : "es",
  "cluster_uuid" : "mayxoDENThSmrUltkXyRWg",
  "version" : {
    "number" : "5.5.1",
    "build_hash" : "19c13d0",
    "build_date" : "2017-07-18T20:44:24.823Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

Install Kibana

モニタリングしたいのでKibana5.5.1をインストールします。

### Install Kibana
$ sudo yum install -y kibana
### サービス登録
$ sudo chkconfig --add kibana
$ chkconfig --list | grep kibana
kibana          0:off   1:off   2:on    3:on    4:on    5:on    6:off
### Kibana設定
$ sudo vim /etc/kibana/kibana.yml
+ server.host: 0.0.0.0
+ elasticsearch.url: "http://ES_IP_ADDR"
### サービス停止状態のままにしておきます(X-Pack入れたあとに設定変更後に起動します)
$ service kibana status
kibana is not running

Install X-Pack on Elasticsearch

Elasticsearchのノード状態などをモニタリングしたいため、X-Pack5.5をインストールします。

Install X-Pack on Elasticsearch

$ sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install x-pack
-> Downloading x-pack from elastic
[=================================================] 100%  
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@     WARNING: plugin requires additional permissions     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.io.FilePermission \.pipe* read,write
* java.lang.RuntimePermission accessClassInPackage.com.sun.activation.registries
* java.lang.RuntimePermission getClassLoader
* java.lang.RuntimePermission setContextClassLoader
* java.lang.RuntimePermission setFactory
* java.security.SecurityPermission createPolicy.JavaPolicy
* java.security.SecurityPermission getPolicy
* java.security.SecurityPermission putProviderProperty.BC
* java.security.SecurityPermission setPolicy
* java.util.PropertyPermission * read,write
* java.util.PropertyPermission sun.nio.ch.bugLevel write
* javax.net.ssl.SSLPermission setHostnameVerifier
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.

Continue with installation? [y/N]y
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@        WARNING: plugin forks a native controller        @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
This plugin launches a native controller that is not subject to the Java
security manager nor to system call filters.

Continue with installation? [y/N]y
-> Installed x-pack

動作確認するのですが、Securityが有効になったため、エラーが返ってきます。
なので、今回は、Securityを無効にします。
(モニタリング用途で使いたいだけなので―)

ちなみに、X-Packについては、クラスメソッドさんが詳しく書いてますー
クラスメソッド:Elastic Stack X-Pack

X-Pack Security無効化

無効化することで、レスポンスが返ってきます。

### Security無効化
$ vim /etc/elasticsearch/elasticsearch.yml
+ xpack.security.enabled: false

### 動作確認
$ curl -u elastic http://localhost:9200
{
  "name" : "fDpNQ4m",
  "cluster_name" : "es",
  "cluster_uuid" : "mayxoDENThSmrUltkXyRWg",
  "version" : {
    "number" : "5.5.1",
    "build_hash" : "19c13d0",
    "build_date" : "2017-07-18T20:44:24.823Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

### 再起動
$ service kibana restart
kibana stopped.
kibana started

Install X-Pack on Kibana

KibanaのX-Pack5.5もインストールします。

Install X-Pack on Kibana

$ sudo /usr/share/kibana/bin/kibana-plugin install x-pack
Attempting to transfer from x-pack
Attempting to transfer from https://artifacts.elastic.co/downloads/kibana-plugins/x-pack/x-pack-5.5.1.zip
Transferring 119276972 bytes....................
Transfer complete
Retrieving metadata from plugin archive
Extracting plugin archive
Extraction complete

Install EC2 Discovery Plugin

ここからEC2 Discovery Pluginをインストールします。
やっとここまできましたね!

$ sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install discovery-ec2
-> Downloading discovery-ec2 from elastic
[=================================================] 100%  
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@     WARNING: plugin requires additional permissions     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.lang.RuntimePermission accessDeclaredMembers
* java.lang.RuntimePermission getClassLoader
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.

Continue with installation? [y/N]y
-> Installed discovery-ec2

Elasticsearch.ymlに設定します。

$ sudo vim /etc/elasticsearch/elasticsearch.yml
cluster.name: es-cluster
discovery.zen.hosts_provider: ec2
discovery.ec2.groups: "SECURITY_GROUP_NAME" or "SECURITY_GROUP_ID"
discovery.ec2.availability_zones: [ "ap-northeast-1a", "ap-northeast-1c" ]
cloud.aws.region: ap-northeast-1

### サービス再起動
service elasticsearch restart

### クラスタ状態確認
$ curl http://localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "es-cluster",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

2台目&3台目いくよ!!

2台目も上記と同じで設定を実施しますので割愛しちゃいますm(_ _)m
SecurityGroupは、1台目と同じのを使ってください。
また、ElasticsearchのX-Packは、3台すべてに入れてください。
Kibanaは、インストールしないです。

もろもろ設定して、Elasticsearchを再起動するとログにクラスタが組み込まれたことがわかります。

クラスタに組み込まれているかをログから確認

### Master側のログ
[20xx-xx-xxxx:xx:xx][INFO ][o.e.c.s.ClusterService   ] [fDpNQ4m] added {{gdRR6r1}{gdRR6r17T5iaGPmaO9wgTA}{6l3QNArhSkyBVu08jTfwYQ}{IP_ADDR}{IP_ADDR:9300},}, reason: zen-disco-receive(from master [master {dKgmQfLLg}{IP_ADDR}{IP_ADDR:9300} committed version [15]])

### Node側のログでマスタを検出したことがわかる
$ tail -f /var/log/elasticsearch/es.log
[20xx-xx-xxxx:xx:xx][INFO ][o.e.c.s.ClusterService   ] [gdRR6r1] detected_master {dKgmQfL}{dKgmQfLASM-35x0X0ulSXg}{XE4jgXEzQwqgkSfRypgoLg}{IP_ADDR}{IP_ADDR:9300}, added {{fDpNQ4m}{fDpNQ4mrRR2vN7wiTVjfIg}{aqPbmbhbTkm1X40gH1tylw}{IP_ADDR}{IP_ADDR:9300},{dKgmQfL}{dKgmQfLASM-35x0X0ulSXg}{XE4jgXEzQwqgkSfRypgoLg}{IP_ADDR}{IP_ADDR:9300},}, reason: zen-disco-receive(from master [master {dKgmQfL}{dKgmQfLASM-35x0X0ulSXg}{XE4jgXEzQwqgkSfRypgoLg}{IP_ADDR}{IP_ADDR:9300} committed version [15]])

Kibanaのモニタリングで確認するよ

Kibana [http://KIBANA_IP_ADDR:5601] にアクセスします。

「Monitoring」をクリックし、ElasticsearchにNodeが3台あることがわかりますね。
ステータス:Green

kibana01.png

「Nodes」をクリックし、Nodeの状態を確認できます。

kibana02.png

無事にクラスタを組むことができましたヽ(*゚д゚)ノ

補足#01

AWS Configureの設定とIAM Roleにポリシーをアタッチしないと以下のエラーがでます。
ちなみに、作成したクラスタ名でログが作成されちゃいます。

$ sudo /var/log/elasticsearch/es-cluster.log
[20xx-xx-xxxx:xx:xx][INFO ][o.e.d.e.AwsEc2UnicastHostsProvider] [node_id] Exception while retrieving instance list from AWS API: Unable to execute HTTP request: connect timed out

補足#02

既に存在しているNodeIDのAMIから起動すると、NodeIDがかぶっているためクラスタに組み込むことができないです。。
その際のログが以下です。

### AMIから起動したノード
$ tail -f /var/log/elasticsearch/es.log
[20xx-xx-xxxx:xx:xx][INFO ][o.e.d.z.ZenDiscovery     ] [gdRR6r1] failed to send join request to master [{fDpNQ4m}{fDpNQ4mrRR2vN7wiTVjfIg}{eHfV5HLkRrKo8_FXfgyHDA}{IP_ADDR}{IP_ADDR:9300}{ml.enabled=true}], reason [RemoteTransportException[[fDpNQ4m][IP_ADDR:9300][internal:discovery/zen/join]]; nested: IllegalArgumentException[can't add node {gdRR6r1}{gdRR6r17T5iaGPmaO9wgTA}{hzzXQXB8TM-xQVn9Uj8e2A}{IP_ADDR}{IP_ADDR:9300}{ml.enabled=true}, found existing node {gdRR6r1}{gdRR6r17T5iaGPmaO9wgTA}{9pWjYpL5Tq23yIte7WzMiw}{IP_ADDR}{IP_ADDR:9300}{ml.enabled=true} with the same id but is a different node instance]; ]

さいごに

EC2 Discovery Pluginいかがでしたか?
簡単にクラスタを組むことができたんじゃないかなと思います。

ただ、個人的には、運用を考えると自動復旧できる構成にする必要があると思ってます。
今の状態だとElasticsearchのクラスタ化する時にNodeIDがかぶっちゃうので、AMIからの自動復旧がむずかしい状態です。
なので、Elasticsearchをインストールする前のAMIからプロビジョニングして、クラスタに組み込む必要があるかなと。
(Elasticsearchインストール時にNodeIDが振られる)

うーん。。NodeIDを変更する方法が他にあればいいのですが、誰か知っている人いたら教えてくださいm(_ _)m

てことで、今回は、ElasticsearchのプラグインのEC2 Discovery Pluginについて書かせて頂きました。
ありがとうございましたー

続きを読む

dynamodb-autoscaling検証

概要

先日のアップデートでdynamodbでautoscalingの機能が実装されました。
https://aws.amazon.com/jp/blogs/news/new-auto-scaling-for-amazon-dynamodb/

NoSQLデータベースのベンチマークツールであるycsbを用いてdynamodb-autoscalingの動作を検証できます。
今回は新規に追加された項目「ターゲット使用率」がどのような影響を及ぼすかについて検証します。

環境準備

環境準備に伴い、下記を参考にさせていただきました。

http://qiita.com/noralife/items/35a956f682b1aca475f6
http://dev.classmethod.jp/cloud/aws/attack_to_dynamodb_using_ycsb/
http://imai-factory.hatenablog.com/entry/2013/04/05/010858

amazon linux version

[ec2-user@dynamo-ec2 ycsb-0.12.0]$ cat /etc/system-release
Amazon Linux AMI release 2017.03

openjdk-develインストール

$ sudo yum -y install java-1.7.0-openjdk-devel

ycsb導入

・ダウンロード
$ wget https://github.com/brianfrankcooper/YCSB/releases/download/0.12.0/ycsb-0.12.0.tar.gz

・展開、移動
$ tar xfz ycsb-0.12.0.tar.gz
$ cd ycsb-0.12.0/

・ファイル用のディレクトリ準備
$ mkdir -p dynamodb/conf/

・ベンチマーク対象テーブル(testtable)のプライマリキーを確認しておく
$ aws dynamodb describe-table --table-name testtable | jq -r '.Table.AttributeDefinitions[].AttributeName'
name

YCSB概要

テストデータをdynamodbに読み込ませて

./bin/ycsb.sh load dynamodb -P workloads/dyamodb -P dynamodb/conf/dynamodb.properties

テストデータを使って負荷をかける。

./bin/ycsb.sh run dynamodb -P workloads/dyamodb -P dynamodb/conf/dynamodb.properties

設定ファイルは下記の3つ

■dynamodb/conf/dynamodb.properties
対象のDynamoDBの情報を設定。変更する部分はプライマリキーくらいかと。
クレデンシャル情報が記載されたファイルもこの中で指定する。

$ cat dynamodb/conf/dynamodb.properties
dynamodb.awsCredentialsFile = dynamodb/conf/AWSCredentials.properties
dynamodb.primaryKey = name
dynamodb.endpoint = http://dynamodb.ap-northeast-1.amazonaws.com

■dynamodb/conf/AWSCredentials.properties
クレデンシャル情報を記載。

$ cat dynamodb/conf/AWSCredentials.properties
accessKey = XXXXXXXXXXXXXXXXXXXX
secretKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

■workloads/(任意のファイル名)
投入するデータ、実施するベンチマークのステータスを設定。
値はテンプレートworkloads/workload_templateを参考。
※operationcountは1,000,000以上が良いかと。10,000で実行したらスケーリングする前に終わってしまいました……

$ cat workloads/dyamodb
workload=com.yahoo.ycsb.workloads.CoreWorkload #デフォルト

recordcount=1000 #テーブルにセットするレコード数
operationcount=2000000 #load時のオペレーション数

insertstart=0 #デフォルト
fieldcount=10 #フィールド数、デフォルト
fieldlength=100 #レコードの長さ、デフォルト

readallfields=true #デフォルト
writeallfields=false #デフォルト

table=testtable #テーブル名

fieldlengthdistribution=constant #デフォルト

#load時のオペレーション比率(read80%, update15%, insert5%)
readproportion=0.8
updateproportion=0.15
insertproportion=0.05

#以下テンプレートの値のまま設定
readmodifywriteproportion=0
scanproportion=0
maxscanlength=1000
scanlengthdistribution=uniform
insertorder=hashed
requestdistribution=zipfian
hotspotdatafraction=0.2
hotspotopnfraction=0.8

measurementtype=histogram
histogram.buckets=1000
timeseries.granularity=1000

検証開始

初期のdynamodbキャパシティ設定

1000レコード書き込むので、書き込み容量ユニット(以下WCU)を10確保。
WCU1だとスロットルが発生して時間がかかります……。

初期設定.PNG

テーブルにテストデータを読み込ませる

1回だけ。10分ほどかかります。

$ ./bin/ycsb.sh load dynamodb -P workloads/dyamodb -P dynamodb/conf/dynamodb.properties

auto-scaling設定

いよいよauto-scaling設定です。
RCU/WCUを下げるオペレーションは最大9回/日になっていますが条件があります。
↓の記事を参考にさせていただきました。
http://qiita.com/mokrai/items/6864b8a723a2728565fc
検証する場合は無駄に下げないように初期値を考慮しましょう。

as1.PNG

・ターゲット使用率
スケーリングの基準になる値です。以降で検証します。

・IAMロール
[新しいロール]を選択すれば問題ありません。
下記の管理ポリシーが付与されたロールが作成されます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:DescribeTable",
                "dynamodb:UpdateTable",
                "cloudwatch:PutMetricAlarm",
                "cloudwatch:DescribeAlarms",
                "cloudwatch:GetMetricStatistics",
                "cloudwatch:SetAlarmState",
                "cloudwatch:DeleteAlarms"
            ],
            "Resource": "*"
        }
    ]
}

ロールからもわかるように、auto-scalingはcloudwatchの値を評価の基準としています。
auto-scaling設定中は動的なcloudwatchメトリクスが作成され、auto-scaling設定解除後に削除されます。

ターゲット使用率70%の場合

下記設定でcloudwatchの推移を確認します。

auto-scaling設定 RCU WCU
ターゲット使用率(%) 70 70
min(ユニット) 10 10
max(ユニット) 10000 10000

キャパシティの推移

推移は下記の通り

RCU
70rcu.PNG

WCU
70wcu.PNG

ターゲット使用率20%の場合

下記設定でcloudwatchの推移を確認します。

auto-scaling設定 RCU WCU
ターゲット使用率(%) 20 20
min(ユニット) 10 10
max(ユニット) 10000 10000

20rcu.PNG

20wcu.PNG

終わりに

負荷のかけ方が一定の場合はキャパシティーの増減が安定したら変化しないので、
そのあたりの調整が必要だと感じました。
cloudwatchメトリクスが動的な値で作成され、それらが基準になってスケーリングされています。
サポートに確認したところ、閾値を超えてのキャパシティ設定もできるとのことですが、その場合は次のタイミングでスケーリングされてしまうとのことです(未検証)。

続きを読む

JavaによるLambdaをCodepipelineで自動リリースする最小構成サンプル

TL;DR

以下の文書では、node.js でのLambda関数のリリースを自動化する例が出ています。
http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/automating-deployment.html

本文書では、これとほぼ同じようなアプリをJavaで書き、そのリリースを自動化します。それぞれの要素の意味や役割の説明は最低限に留めます。 (筆者もよくわかってません。えっへん。) 最小の動作サンプルの例示を行います。

本文書ではマネージメントコンソール上で色々手作業で設定を入れさせています。今後のUIの変更で、手順が変わっているかもしれません。ご容赦を。

前提

  • githubにコードが配置してあること。
  • 更新を検出するブランチが決めてあること。
  • Gradleでビルドを行います。
  • 中間ファイルを置くためのS3バケットがあること。例として、auto-release-sampleとします。

準備

ロールの作成

IAM マネージメントコンソールを開き、以下のようにロールを作成します。

項目 備考
名前 cloudformation-lambda-execution-role
ロールタイプ AWS CloudFormation 作成時は「AWS サービスロール」から選択する。作成後のロールの画面では、信頼関係 タブに表示される
アタッチされたポリシー AWSLambdaExecute ロールの画面では、アクセス許可 タブ=>管理ポリシー に表示される

一度ロールを作った後、作成したロールをマネージメントコンソールで開き、アクセス許可 タブ ->インラインポリシー にて、作成するには、ここをクリックしてください。をクリックする。  カスタムポリシー -> 選択 と進んだ後下記内容を記載する。ポリシー名は適宜設定する。

{
    "Statement": [
        {
            "Action": [
                "s3:GetObject",
                "s3:GetObjectVersion",
                "s3:GetBucketVersioning"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::codepipeline*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "lambda:*"
            ],
            "Resource": [
                "*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "iam:GetRole",
                "iam:CreateRole",
                "iam:DeleteRole"
            ],
            "Resource": [
                "*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "iam:AttachRolePolicy",
                "iam:DetachRolePolicy"
            ],
            "Resource": [
                "*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "iam:PassRole"
            ],
            "Resource": [
                "*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "cloudformation:CreateChangeSet"
            ],
            "Resource": [
                "*"
            ],
            "Effect": "Allow"
        }
    ],
    "Version": "2012-10-17"
}

注:必要最低限の権限になってはいないかもです。今後の本記事の更新でなるべく削って行きたいです。

githubにファイルを配置する

アプリケーションのソースコードをgithubに保存します。

具体的なファイルの中身はサンプルリポジトリを参照してください。
https://github.com/kazurof/minimum-java-lambda-with-codepipeline
ファイルのディレクトリ構成を記載しておきます。

│  build.gradle
│  buildspec.yml
│  gradlew
│  gradlew.bat
│  minimum-lambda-java-model.yaml
│
│
├─gradle
│  └─wrapper
│          gradle-wrapper.jar
│          gradle-wrapper.properties
│
└─src
    └─main
        └─java
            └─codepipelinesample
                    Main.java

補足

buildspec.yml には、利用するS3リポジトリ名が記載されます。(先に、auto-release-sampleとしたもの。)実際に動かしてみる場合は適宜あなたが用意したリポジトリ名に変更してください。

CodePipeline の作成

AWS CodePipeline のマネージメントコンソールを開き、パイプラインを以下のように作成する。

パイプライン名

パイプライン名はわかればなんでも良い。

ソース

項目 備考
ソースプロバイダ GitHub GitHubを選択すると、GitHubリポジトリのURLとブランチを指定し、OAuth認証を行い、CodePipelineから指定したGitHubのリポジトリにアクセスできるようにする。
アクションカテゴリー ソース 作成時は入力を求められない。自動でつけられるものでOK
アクション名 Source 作成時は入力を求められない。自動でつけられる名前でOK
出力アーティファクト名 MyApp 作成時は入力を求められない。自動でつけられる名前でOK

ビルド

項目 備考
ビルドプロバイダ AWS CodeBuild
アクションカテゴリー ビルド 作成時は入力を求められない。
アクション名 CodeBuild 作成時は入力を求められない。

CodeBuildの新しいプロジェクトを作るを選択する。プロジェクト名はわかればなんでも良い。

ビルドプロジェクトの作成

項目
ビルド環境 Ubuntu Java8
ビルド仕様 ソースコードのルートディレクトリの buildspec.yml を使用

注:
– この時、AWS CodeBuild のサービスロールがユーザーに代わり自動的に作成されます。code-build-<ビルドプロジェクト名>-service-role という形になります。
– このビルドのアクションにおいて、以下項目が以下のように自動設定されます。

項目
入力アーティファクト MyApp
出力アーティファクト MyAppBuild

「ビルドプロジェクトの保存」を押下、「次のステップ」を押下してください。

デプロイ

項目 備考
デプロイプロバイダ AWS CloudFormation
アクションモード 変更セットの作成または置換
スタックの名前 MyBetaStack
変更セット名 MyChangeSet
テンプレートファイル packaged-minimum-lambda-java-model.yaml
Capabilities(特徴) CAPABILITY_IAM
ロール名 cloudformation-lambda-execution-role この作業手順の先頭で作ったロールを指定する。
アクションカテゴリー デプロイ 自動で設定される

AWS サービスロールの作成

ロールを新たに作ります。

当該画面の文言を転載:
「AWS CodePipeline がアカウントでリソースを使用するアクセス許可を付与するため、IAM にサービスロールを作成します。」

  • 「ロールの作成」を押下。
  • 「許可」を押下。
  • 「次のステップ」を押下。

ロール名は、「AWS-CodePipeline-Service」になるはずです。(すでにある場合はインラインポリシーに追加される挙動の様子。未確認です。)

パイプラインの確認

今まで入力した内容の確認画面が表示される。「パイプラインの作成」を押下します。

サービスロールを修正

Codebuild 向けに作成されたサービスロールが、用意したS3バケット(auto-release-sample)にアクセスできるように修正する。

今までの手順で、code-build-<ビルドプロジェクト名>-service-roleなるロールが生成されているのでそれを修正する。

  • IAM マネジメントコンソールから、ロール を選択する。
  • code-build-<ビルドプロジェクト名>-service-role を選択する。
  • アクセス許可=> インラインポリシー=> 「表示するインラインポリシーはありません。作成するには、ここをクリックしてください。」 をクリックする。
  • 「Policy Generator」 を選択して 「Select」 を選択する。
  • 「AWS Service」 で、「Amazon S3」 を選択する。
  • 「Actions」 で、「PutObject」 を選択する。
  • 「Amazon Resource Name (ARN)」 に arn:aws:s3:::auto-release-sample* と入力する。(末尾のアスタリスク必要!)
  • 「ステートメントを追加」 を選択して 「Next Step」 を選択する。
  • 「ポリシーの適用」 を選択する。

CodePipelineにLambdaを更新するステップを追加する

  • AWS CodePipeline マネジメントコンソールに移動 =>作成したpipelineを選択
  • 編集ボタンをクリック
  • Stagingの鉛筆アイコンをクリック
  • 既存のアクションの最後にある [+ Action] アイコンを選択する。
  • 以下のように入力
項目
アクションカテゴリー デプロイ
アクション名 execute_cs (わかればなんでも良い)
デプロイプロバイダ AWS CloudFormation
アクションモード 変更セットの実行
スタックの名前 MyBetaStack
変更セット名 MyChangeSet
  • 「更新」を押して保存
  • 「パイプラインの変更を保存」を押してパイプライン全体を保存

自動リリースとLambdaの実行

以上でCodepipeline 作成は終了です。gitリポジトリのmasterブランチに何か修正を入れてみてください。Codepipelineのマネジメントコンソールで、パイプラインが動作するところを確認できます。

正しく終了したら、Lambdaのマネージメントコンソールを開いてください。JavaによるLambda関数が作成されているはずです。テスト実行ができます。

感想

関係する要素技術多いせいか、手順長いですね。。。:sweat_smile:

続きを読む

Go/Java SE環境のElastic BeanstalkでHTTPをHTTPSにリダイレクトする

まとめるまでもなかったのかもしれませんが自分がかなりハマってしまったので書いておきたいと思います。

前提

基本的なSSLのセットアップができている前提で進めていきます。Elastic Beanstalk (EB) の環境にElastic Load Balancer (ELB) が紐付けされており、ELBに対して適切なSSL証明書とHTTPS/ポートの設定されているか今一度ご確認ください。

リダイレクトの設定

ELBは受け取ったリクエストのプロトコルをX-Forwarded-Protoヘッダに格納してあるため以下の設定をnginxのサーバブロック内に読み込ませればHTTP→HTTPSのリダイレクトが実現できます。

if ($http_x_forwarded_proto != 'https') {
    rewrite ^ https://$host$request_uri? permanent;
}

nginxの設定

では肝心のnginxの設定はどうやって上書きするのか。Go/Java SE環境では簡単にnginxの設定をいじくるための仕組みが導入されています。どちらも設定方法は同じです。

.ebextensions/nginx/nginx.confというファイルを作ってEBでデプロイするアプリケーションのルートディレクトリに置いておくとこのnginx.confがEC2上の/etc/nginx/nginx.confに上書きされます。

もう一つ、.ebextensions/nginx/conf.d/*.confにファイルを置いておくとこれらのファイルが/etc/nginx/conf.d/elasticbeanstalkコピーされ、nginx.confを上書きしていない場合はその中のserver{}ブロックの中で、あるいはnginx.confを上書きしている場合はinclude conf.d/elasticbeanstalk/*.conf;と記述すればそこで読み込んでくれますが、なぜか上記のリダイレクト設定を記述したファイルを.ebextensions/nginx/conf.d/02_proxy.confとして置くとnginxの設定ファイル読み込み時にif文が許されず弾かれます。

注意

CodeBuildを噛ましている場合、artifactを出力する際に.ebextensionsを出力するのを忘れてしまうと当然コピーされないのでご注意を。

nginx.confの準備

そんなわけでincludeされるファイルに記述してもうまくいかないので既存のnginxの設定ファイルに上書きしてしまいます。server{}ブロック内に上記のリダイレクト設定を書き込むと以下のようになります。

# Elastic Beanstalk Nginx Configuration File

user                    nginx;
error_log               /var/log/nginx/error.log warn;
pid                     /var/run/nginx.pid;
worker_processes        auto;
worker_rlimit_nofile    33193;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    include       conf.d/*.conf;

    map $http_upgrade $connection_upgrade {
        default     "upgrade";
    }

    server {
        listen        80 default_server;
        access_log    /var/log/nginx/access.log main;

        client_header_timeout 60;
        client_body_timeout   60;
        keepalive_timeout     60;
        gzip                  off;
        gzip_comp_level       4;
        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        # Redirect to https
        if ($http_x_forwarded_proto != 'https') {
           rewrite ^ https://$host$request_uri? permanent;
        }

        # Include the Elastic Beanstalk generated locations
        include conf.d/elasticbeanstalk/*.conf;
    }
}

上記をコピペして.ebextensions/nginx/nginx.confとして保存してやればHTTP→HTTPSリダイレクトが有効になるはずです。

続きを読む

サーバーレスアプリケーションを管理する(SERVER LESS)

前置き

だんだんと Lambda function が増えてきたので管理してみようと思いました。今回は Serverless を使ってみます。

インストール

ますはインストールします。

事前準備

以下をインストールします。

Serverless framework をインストール

この記事 を参考に、各プロジェクトごとにインストールをします。

package.json
{
  "name": "project-name",
  "devDependencies": {
    "serverless": "^1.16.0"
  },
  "scripts": {
    "sls": "serverless"
  }
}

エラーが出るとき

インストールしたときに怒られちゃいました・・・

Error: Cannot find module ‘/path/to/project/node_modules/serverless/node_modules/tabtab/src/cli.js’

issueを参考に、オプションをつけて再度インストールしたところ成功!
無事インストールが完了しました!

$ sls --version
1.16.0

サービスを作成する

Serverless framework では、サーバーレスアプリケーションを service という単位で切り分けるようです。
まずは Serverless framework が提供しているテンプレートを利用して作成してみまんす。

$ sls create --help
Plugin: Create
create ........................ Create new Serverless service
    --template / -t (required) ......... Template for the service. Available templates: "aws-nodejs", "aws-python", "aws-python3", "aws-groovy-gradle", "aws-java-maven", "aws-java-gradle", "aws-scala-sbt", "aws-csharp", "azure-nodejs", "openwhisk-nodejs", "openwhisk-python", "openwhisk-swift", "google-nodejs", "plugin" and "hello-world"
    --path / -p ........................ The path where the service should be created (e.g. --path my-service)
    --name / -n ........................ Name for the service. Overwrites the default name of the created service.

ふむふむ、テンプレートがたくさん良いされてますな。今回は AWS Lambda で python3 なので、 aws-python3 を選択します。

$ sls create -t aws-python3 -p service
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/path/to/project/service"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  ___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.16.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-python3"

でけた!ファイルが何個かできてますね。

ls -la srvice
total 24
drwxr-xr-x  5 user  staff   170  6 22 10:50 .
drwxr-xr-x  6 user  staff   204  6 22 10:47 ..
-rw-r--r--  1 user  staff   192  6 22 10:47 .gitignore
-rw-r--r--  1 user  staff   490  6 22 10:47 handler.py
-rw-r--r--  1 user  staff  2787  6 22 10:47 serverless.yml

何ができるのかな?

$ sls

Commands
* Serverless documentation: http://docs.serverless.com
* You can run commands with "serverless" or the shortcut "sls"
* Pass "--help" after any <command> for contextual help

config ........................ Configure Serverless
config credentials ............ Configures a new provider profile for the Serverless Framework
create ........................ Create new Serverless service
install ....................... Install a Serverless service from GitHub
package ....................... Packages a Serverless service
deploy ........................ Deploy a Serverless service
deploy function ............... Deploy a single function from the service
deploy list ................... List deployed version of your Serverless Service
deploy list functions ......... List all the deployed functions and their versions
invoke ........................ Invoke a deployed function
invoke local .................. Invoke function locally
info .......................... Display information about the service
logs .......................... Output the logs of a deployed function
login ......................... Login or sign up for the Serverless Platform
logout ........................ Logout from the Serverless Platform
metrics ....................... Show metrics for a specific function
remove ........................ Remove Serverless service and all resources
rollback ...................... Rollback the Serverless service to a specific deployment
rollback function ............. Rollback the function to a specific version
slstats ....................... Enable or disable stats

Plugins
AwsCommon, AwsCompileAlexaSkillEvents, AwsCompileApigEvents, AwsCompileCloudWatchEventEvents, AwsCompileCloudWatchLogEvents, AwsCompileCognitoUserPoolEvents, AwsCompileFunctions, AwsCompileIoTEvents, AwsCompileS3Events, AwsCompileSNSEvents, AwsCompileScheduledEvents, AwsCompileStreamEvents, AwsConfigCredentials, AwsDeploy, AwsDeployFunction, AwsDeployList, AwsInfo, AwsInvoke, AwsInvokeLocal, AwsLogs, AwsMetrics, AwsPackage, AwsProvider, AwsRemove, AwsRollback, AwsRollbackFunction, Config, Create, Deploy, Info, Install, Invoke, Login, Login, Logs, Metrics, Package, Platform, Remove, Rollback, SlStats

お、ローカルでのテスト用コマンドもあるみたいですね。試してみましょ。

テスト

まずはテンプレートで作成されたファンクションをテストしてみまんす。

$ cd service
$ sls invoke local -f hello

すると・・・

Error: spawn python3.6 ENOENT

まーた怒られちゃいました・・・どうやらローカル環境のpythonが3.6じゃないようですね。
pyenv と pyenv-virtualenv で解決しました!

デプロイ

では実際にAWSへデプロイしてみます!

その前に

デプロイの前にAWSのクレデンシャル設定をします。

$ aws configure

すでに複数のクレデンシャルを管理している場合は

$ export AWS_PROFILE="profileName" && export AWS_REGION=ap-northeast-1

いけます!

デプロイ!

これを実行するだけ!

$ sls deploy
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (1.35 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.........
Serverless: Stack update finished...
Service Information
service: service
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  None
functions:
  update: service

AWSコンソールでLambdaを確認すると・・・できてました!

Tips

環境でリソースを使い分ける

今回はテストコードでしたが、実際はステージングや本番で同じコードを利用した運用がしたいと思います。
例えば・・・

DB更新したいからVPCにLambdaをおきたいけど、ステージングと本番でVPCわけちゃってるやー

こんな時、デプロイの設定で配置するVPCがわけられたら素敵ですよね!そんな時はこうします!

18 # ステージによって使い分けたい設定を記述
19 custom:
26   vpc:
27     staging:
28       securityGroupIds:
29         - sg-sgsgsgsg
30       subnetIds:
31         - subnet-aaaaaaaa
32         - subnet-cccccccc
33 
34 provider:
35   # ステージの値をデプロイ時のパラメータで上書き(デフォルトはdev)
37   stage: ${opt:stage, self:custom.defaultStage}
38   # ステージの値を利用して値を使い分ける
42   vpc: ${self:custom.vpc.${self:provider.stage}}

こんな感じでserverless.ymlに設定します。

あとはデプロイコマンドにパラメータを追加すればOK!

$ sls deploy --stage staging
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (1.35 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.........
Serverless: Stack update finished...
Service Information
service: service
stage: staging
region: ap-northeast-1
api keys:
  None
endpoints:
  None
functions:
  update: update-pageviews-staging-update

stageが前回はdevでしたが、今回はstagingに変わってますね!この仕組みを利用すれば環境変数などもステージにより使い分けることができますので、より捗ります!

最後に

バージョン管理からデプロイまで管理できることにより、非常にサーバーレスヘの敷居が低くなりました。サーバレスアプリケーションは開発が簡単な分、運用への意識が低くなり、どうしても「今ってどのバージョンが動いてるんだっけ?」「これっていつ誰が作ったの?」など、逆にコストがかかることもありましたが、サーバーレスフレームワークを利用することにより、今までと変わらない開発、テスト、運用が適用できるようになりますね!

続きを読む

Amazon Rekognition を使ってみるの巻

はじめに

Amazon Rekognition を教科書通りに使ってみた
EC2 インスタンスからS3 の画像をAmazon Rekognition へ投入して、分析結果を出力する。

環境

Windows 10
AWS アカウント
EC2 インスタンス
S3
ReKognition

EC2 をセットアップする

aws configure の設定
aws configure --profile adminuser
上のコマンドを実行して、対話形式で、キー情報を入力

S3 に画像をアップロード

下のサイトを参考にどうぞ
http://dev.classmethod.jp/cloud/aws/cm-advent-calendar-2015-aws-re-entering-s3/

java プログラムの作成

サンプルプログラムの作成
vi DetectLabelsExample.java

Qiita をうまく使いこなせておらずきたないですが、、、
下のサンプルコードをどうぞ

import com.amazonaws.services.rekognition.AmazonRekognition;
import com.amazonaws.services.rekognition.AmazonRekognitionClientBuilder;
import com.amazonaws.AmazonClientException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.rekognition.model.AmazonRekognitionException;
import com.amazonaws.services.rekognition.model.DetectLabelsRequest;
import com.amazonaws.services.rekognition.model.DetectLabelsResult;
import com.amazonaws.services.rekognition.model.Image;
import com.amazonaws.services.rekognition.model.Label;
import com.amazonaws.services.rekognition.model.S3Object;
import java.util.List;

public class DetectLabelsExample {

public static void main(String[] args) throws Exception {
String photo = “YOUR Photo.jpg”;
String bucket = “YOUR bucket NAME”;
AWSCredentials credentials;
try {
credentials = new ProfileCredentialsProvider(“adminuser”).getCredentials();
} catch(Exception e) {
throw new AmazonClientException(“Cannot load the credentials from the credential profiles file. “
+ “Please make sure that your credentials file is at the correct “
+ “location (/Users/userid/.aws/credentials), and is in a valid format.”, e);
}
AmazonRekognition rekognitionClient = AmazonRekognitionClientBuilder
.standard()
.withRegion(Regions.US_WEST_2)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();

DetectLabelsRequest request = new DetectLabelsRequest()
.withImage(new Image()
.withS3Object(new S3Object()
.withName(photo).withBucket(bucket)))
.withMaxLabels()
.withMinConfidence(77F);

try {
DetectLabelsResult result = rekognitionClient.detectLabels(request);
List labels = result.getLabels();
 System.out.println(“Detectedls for ” + photo);

for (Label label: labels) {
System.out.println(label.getName() + “: ” + label.getConfidence().toString());

}
} catch(AmazonRekognitionException e) {
e.printStackTrace();
}
}

javac DetectLabelsExample.java

下のような結果が出ます。
$ java DetectLabelsExample
Detected labels for photo.jpg
Human: 99.30579
People: 99.30792
Person: 99.30792
Bicycle: 99.07324
Bike: 99.07324
Mountain Bike: 99.07324
Vehicle: 99.07324

続きを読む

Spring Cloud AWSのResourceハンドリング機能でAWS S3リソースを検索する

AWSのSDKでS3上のリソースを操作できるが、リソース名を指定し検索してくれる機能がないようです。Spring Cloud AWSのResourceハンドリングを使えば、指定のAntパターンにマッチングするリソースの検索が可能です。

完成後画面のイメージ↓
2017-07-26_174655.jpg

検証環境

・Spring Boot(Thymeleaf)1.4.3

必要なライブラリ

pom.xml
    ...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-aws-context</artifactId>
      <version>1.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-aws-autoconfigure</artifactId>
        <version>1.2.1.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk</artifactId>
      <version>1.11.163</version>
    </dependency>
    ...

AWS S3に接続するための設定

application.propertiesにアクセスキー、シークレットキー、リージョンを指定する。
また、ローカルからのアクセスであるため、リージョンの自動検知を「false」に指定する。

application.properties
cloud.aws.credentials.accessKey=AKIAXXXXXXXXXA3Q
cloud.aws.credentials.secretKey=T9JBXXXXXXXXXXXXXXXXXXXXGfLAQ
cloud.aws.region.static=ap-northeast-1
cloud.aws.region.auto=false

上記の設定値を読み込むために、AWSコンフィグクラスを作成する。

AWSConfig

@Configuration
public class AWSConfig {

    @Value("${cloud.aws.credentials.accessKey}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secretKey}")
    private String secretKey;

    @Value("${cloud.aws.region.static}")
    private String region;

    @Bean
    public BasicAWSCredentials basicAWSCredentials() {
        return new BasicAWSCredentials(accessKey, secretKey);
    }

    @Bean
    public AmazonS3 amazonS3Client(AWSCredentials awsCredentials) {
        return AmazonS3ClientBuilder.standard().withRegion(Regions.fromName(region)).build();
    }
}

上記を作成することで、DIコンテナからResourceLoaderとAmazonS3のインスタンスを取得することが可能になります。

アプリケーションレイヤ

普通のControllerクラスと画面表示用のフォームクラスを作成する。

AwsS3Controller
    @RequestMapping(value="/aws/s3/search", method=RequestMethod.POST)
    public String search(Model model, S3FileForm fileForm) throws IOException {

        S3FileForm s3FileForm = storageService.search(fileForm.getFileName());
        model.addAttribute("s3FileForm", s3FileForm);

        return "/aws/s3/downloadFromS3";
    }
S3FileForm
public class S3FileForm implements Serializable {

    private String fileName;

    private String downloadKey;

    private String[] checkedItems;

    private List<S3File> fileList;

    //...get set省略
}
S3File
public class S3File implements Serializable {

    private String bucketName;

    private String key;

    private String contentType;

    private Date lastModifiedDate;

    //...get set省略
}

ドメインレイヤ

サービスクラスにてResourcePatternResolverを使って、Antパターンs3://cinpo/**/*testにマッチングするリソース名を検索し、対象リソース一覧を取得する。さらに、リソース名でAmazonS3オブジェクトを生成し、最終更新日などのキー情報を取得する。
s3://**/*.txtを使えばアクセス可能な全バケットを対象に検索が可能です。

S3StorageService
/**
 * AWS S3操作用サービス
 * @author
 *
 */
@Service
public class S3StorageService {

    private final String BUCKET_NAME = "cinpo";

    @Autowired
    private ResourceLoader resourceLoader;

    @Autowired
    private ResourcePatternResolver resourcePatternResolver;

    @Autowired
    private AmazonS3 amazonS3;

    /**
     * リソース検索
     * 
     * @param fileName
     */
    public S3FileForm search(String fileName) {

        // 画面表示用フォーム
        S3FileForm s3FileForm = new S3FileForm();

            Resource[] resources = null;
            try {
                // Antパターン(s3://cinpo/**/*test.*)を指定し、対象一覧を取得する。
                resources = this.resourcePatternResolver.getResources("s3://" + BUCKET_NAME + "/**/*" + fileName + ".*");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            List<S3File> s3FileList = new ArrayList<S3File>();

            // リソース毎にS3Objectを取得する。
            for (Resource resource : resources) {
                S3Object s3Object = amazonS3.getObject(new GetObjectRequest("s3.cinpo", resource.getFilename()));

                // 画面表示項目の設定
                S3File s3File = new S3File();
                s3File.setBucketName(s3Object.getBucketName());
                s3File.setKey(s3Object.getKey());
                s3File.setContentType(s3Object.getObjectMetadata().getContentType());
                s3File.setLastModifiedDate(s3Object.getObjectMetadata().getLastModified());

                s3FileList.add(s3File);
            }
            s3FileForm.setFileList(s3FileList);

        return s3FileForm;
    }
}

HTML

最後に、検索・一覧表示用の画面を作成する。

downloadFromS3
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">

    <head>
        <meta charset="UTF-8" />
        <link th:substituteby="common/header :: common_header"/>
        <title>S3からファイルダウンロード</title>
    </head>
    <body>
        <h1>File Download</h1>
        <p><a href="/index" th:href="@{/index}">Back to home</a></p>
        <form action="#" th:action="@{/aws/s3/search}" th:object="${s3FileForm}" method="post">
            <table>
                <tr>
                    <td width="100px"><label for="fileName">検索キー</label>:</td>
                    <td><input type="text" id="fileName" th:field="*{fileName}" size="30px" /></td>
                </tr>
                <tr>
                    <td colspan="2"><input type="submit" value="Search" /></td>
                </tr>
            </table>
        </form>
        <div th:if="${s3FileForm.fileList != null}">
            <form action="#" th:action="@{/aws/s3/download}" th:object="${s3FileForm}" method="post">
                <table border="1">
                    <tr>
                        <th width="20px">Download</th>
                        <th width="10px">Check</th>
                        <th>Bucket</th>
                        <th>Key</th>
                        <th>Content Type</th>
                        <th>Last Modified Date</th>
                    </tr>
                    <tr th:each="file:${s3FileForm.fileList}">
                        <td>
                            <button type="submit" name="downloadKey" th:field="*{downloadKey}" th:value="${file.key}">Download</button>
                        </td>
                        <td><input type="checkbox" th:field="*{checkedItems}" th:value="${file.key}" /></td>
                        <td th:text="${file.bucketName}"></td>
                        <td th:text="${file.key}"></td>
                        <td th:text="${file.contentType}"></td>
                        <td th:text="${file.lastModifiedDate}"></td>
                    </tr>
                </table>
                <div><input type="submit" value="Download Selected" /></div>
            </form>
        </div>
    </body>
</html>

参考サイド:Spring Cloud AWS

続きを読む