読者です 読者をやめる 読者になる 読者になる

HBase徹底入門はCloudera Managerユーザーの必読書

book hbase

仕事でOpenTSDBを使っていることもあり、HBase徹底入門を購入しました。

HBase徹底入門 Hadoopクラスタによる高速データベースの実現

HBase徹底入門 Hadoopクラスタによる高速データベースの実現

まだざっとしか読んでいませんが、

  • HBaseの概要/アーキテクチャの解説
  • HBaseのインストールとアプリケーション開発
  • スキーマ設計のポイント
  • Cloudera Managerによるクラスタ環境構築
  • Cloudera Managerによる運用監視
  • トラブルシューティング

などなど、HBaseに限らず、CDH5ベースでHadoopクラスタを運用している人(もしくはこれから運用しようとしている人)にとっては必読の書です。
少なくとも俺得であることは間違いありません。

今までなんとなくHadoopに尻込みしていた人におすすめです。

Lazybonesによるプロジェクトテンプレート管理(1): Lazybones概要/Hello Lazybones

groovy gradle

Lazybonesとは?

Lazybonesはプロジェクトテンプレートからプロジェクトのひな形を自動作成するツールです。

Railsのscaffoldや、Mavenarchetype:generateに近いメージですが、特定のフレームワークやビルドツールに依存しない汎用的なテンプレート管理ツールになっています。(元々はRatpackにプロジェクト新規作成用のコマンドラインツールが提供されていないことを不満に思って作成したそうです。)
とはいえ、Lazybonesのテンプレート作成機能がGradleベースであることや、post-installスクリプトをGroovyで記述することなどから、Gradleプロジェクトのテンプレート管理ツールとして使われることを想定していると考えてよいのではないかと思います。

Lazybones概要

  • テンプレートはzip形式のアーカイブで、基本的にはプロジェクトのひな形となるファイル・ディレクトリをアーカイブに含めます。
  • テンプレートはBintrayから配布できます(バージョン管理可能)。
  • テンプレートには静的なファイルだけでなく、テンプレートエンジンによって生成する動的なファイルを含めることができます。テンプレートエンジンの処理はpost-installスクリプト(Groovyスクリプト)に記述します。
  • post-installスクリプトには対話処理を含めることができます。例えば、自動生成するクラスファイルのパッケージ名などをユーザーに入力させることができます。
  • サブテンプレート機能によって、プロジェクトの初期生成後にファイルを追加生成できます。(例えば後からコントローラークラスを追加するなど。)

テンプレート配布方法

デフォルトではBintrayの pledbrook/lazybones-templates リポジトリが検索対象となります。
コンフィグレーションでカスタムリポジトリを追加することができるので、例えば自分のBintrayリポジトリを検索対象に追加することも可能です。
また、Bintrayにアップロードせずに、テンプレートのzipファイルをWebサーバーやファイルサーバーにアップロードしておき、URLを直接指定する方法もあります。(いちいちURLを指定するのが面倒なら、コンフィグレーションでURLに別名をつけることもできます。)
テンプレートの開発方法は後日紹介しようと思いますが、興味のある方はこちらを参照してみてください。静的なファイルのみであれば、テンプレート作成はそれほど難しくないです。
Template developers guide · pledbrook/lazybones Wiki · GitHub

インストール方法

2014/12/28現在の最新バージョンは0.8です。
GVMが利用可能であれば、以下でインストールするのが簡単でよいでしょう。

$ gvm install lazybones

バイナリのzipファイルを展開してもよいです。以下からダウンロードして展開し、bin/以下にパスを通してください。
https://bintray.com/pledbrook/lazybones-templates/lazybones/0.8/view/files

使ってみる

以下のコマンドで公式リポジトリのテンプレート一覧を取得できます。

$ lazybones list
Available templates in pledbrook/lazybones-templates

aem-multimodule-project
afterburnerfx
afterburnergfx
angular-grails
asciidoctor-gradle
dropwizard
gaelyk
gradle-plugin
gradle-quickstart
groovy-app
groovy-lib
java-basic
lazybones-project
nebula-plugin
ratpack
ratpack-lite
spring-boot-actuator

試しにSpring Bootのプロジェクトを生成してみましょう。
テンプレートの詳細情報を確認するには「info」コマンドを利用します。

$ lazybones info spring-boot-actuator
Fetching package information for 'spring-boot-actuator' from Bintray
Name: spring-boot-actuator
Latest: 1.0.1.RELEASE
Owner: pledbrook
Versions: 0.1, 1.0.1.RELEASE, 0.2

提供されているバージョンが確認できましたので、1.0.1.RELEASEからプロジェクトを生成してみます。
プロジェクトの生成には「create」コマンドを利用します。「help <コマンド名>」で各コマンドの詳細が確認できます。

$ lazybones help create
Creates a new project from a template.

USAGE: create <template> <version>? <dir>

where template = The name of the project template to use.
version = (optional) The version of the project template to use. Uses
the latest version of the template by default.
dir = The name of the directory in which to create the project
structure. This can be '.' to mean 'in the current
directory.'

Option Description
------ -----------
-P Add a substitution variable for file
filtering.
-h, --help Displays usage.
--spaces Sets the number of spaces to use for
indent in files.
--with-git Creates a git repository in the new
project.

プロジェクトを生成したいディレクトリに移動し、以下を実行して「mybootapp」ディレクトリ以下にプロジェクトを新規生成します。

$ lazybones create spring-boot-actuator 1.0.1.RELEASE mybootapp

もしくは、以下のようにしてもかまいません。

$ mkdir mybootapp
$ cd mybootapp
$ lazybones create spring-boot-actuator 1.0.1.RELEASE .

プロジェクトのファイル一式が生成され、最後にREADME.mdの内容が表示されます。

# Spring Boot Actuator Sample

You have just created a simple Spring Boot project in Groovy incorporating the
Actuator. This includes everything you need to run the application. In this
case, that's a simple JSON endpoint.

In this project you get:

* A Gradle build file
* An application class, `SampleApplication`, implementing a single JSON endpoint
* A JUnit test case for `SampleApplication`

You can build and run this sample using Gradle (>1.6):

```
$ gradle run
```

If you want to run the application outside of Gradle, then first build the JARs
and then use the `java` command:

```

生成されたファイル一式は以下のようになっています。

$ tree
.
├── README.md
├── build.gradle
└── src
├── main
│   ├── groovy
│   │   └── sample
│   │   └── SampleApplication.groovy
│   └── resources
│   └── application.properties
└── test
└── groovy
└── sample
└── SampleApplicationTests.groovy

README.mdに従って実行してみます。(このテンプレートにはGradle Wrapperが含まれていないので、別途Gradleを導入しておく必要があります。)

$ gradle run
:compileJava UP-TO-DATE
:compileGroovy
:processResources
:classes
:run

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.0.1.RELEASE)

2014-12-28 01:05:53.866 INFO 51749 --- [ main] sample.SampleApplication : Starting SampleApplication on nobusue-MacBookPro.local with PID 51749 (/Users/nobusue/work/mybootapp/build/classes/main started by nobusue)
2014-12-28 01:05:53.924 INFO 51749 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6ae0286d: startup date [Sun Dec 28 01:05:53 JST 2014]; root of context hierarchy
・・・
[org.springframework.boot:type=Endpoint,name=configurationPropertiesReportEndpoint]
2014-12-28 01:05:57.278 INFO 51749 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
2014-12-28 01:05:57.279 INFO 51749 --- [ main] sample.SampleApplication : Started SampleApplication in 4.282 seconds (JVM running for 4.92)
> Building 80% > :run

無事Spring Bootアプリケーションが起動しました。

まとめ

spring-boot-actuatorテンプレートではアプリケーションのクラス名などは決め打ちになっていますが、post-installスクリプトを追加すればユーザーの入力に従ってクラス名を変更することなどが可能です。そのため、GitHubなどでテンプレートプロジェクトを公開するより、より使いやすい形で提供することができそうです。
なぜか日本語情報の少ないLazybonesですが、シンプルながら要所をおさえた作りになっており、なかなか使えそうです。
次回以降、テンプレート作成と公開について紹介していく予定です。

GroovyでApache Sparkアプリケーションを作る #gadvent

spark groovy gradle

このエントリは G*Advent Calendar(Groovy,Grails,Gradle,Spock...) Advent Calendar 2014 - Qiita の12/20担当分です。

Apache Sparkとは?

Hadoopエコシステムにおける次世代の分散処理基盤として注目されています。インメモリ処理とDAGによるタスクスケジューリングを特徴とし、分散処理に必要な耐障害性を備えています。また、RDDという共通のプログラミングモデルの上で機械学習やストリーミング処理が統一的に扱えるため、複雑なビッグデータ処理を実装するのに有利です。
概要をつかむにはこのへんの資料がよいかと思います。

Groovyから使ってみようと思った動機

公式サイト Apache Spark™ - Lightning-Fast Cluster Computing を見ていただくとわかりますが、Spark自体はScalaで開発されており、アプリケーション開発は Scala / Java / Python で行えるようにAPIが提供されています。(機能の実装状況には差異があり、例えばPythonではSpark1.2でやっとStreamingがサポートされたり、と言った状況です。)
せっかくJavaAPIがあるのでぜひ使いたいところなのですが、Scala/Pythonに比べて以下のようなビハインドがあります。

  • ラムダが使えないのでコードが冗長(Java8を使えばマシにはなりますが、ClouderaがJava8をサポートするまで自分は使えません・・)
  • 対話型シェル(REPL)がない

JavaAPIをGroovyから使うことでこれらの課題に対処できないか試行錯誤していますので、まだ道半ばではありますが現在までの状況をまとめておきます。

コードの簡略化

いくらか成果が上がったので、本エントリで記載します。ただし、Sparkの挙動に起因する癖がありますので注意が必要です。

REPL(Groovy Shell)

こちらはSparkの挙動に起因する癖によって壁にぶつかりました。現時点では解決策が見つかっていません。
具体的にはタスク実行時に以下の例外が発生します。

ERROR org.apache.spark.SparkException:
Task not serializable
at org.apache.spark.util.ClosureCleaner$.ensureSerializable (ClosureCleaner.scala:166)

GroovyでSpark Wordcountを実行してみる

Quick Start - Spark 1.2.0 Documentation にあるWord CountのJavaサンプルをGroovyで書き換えてみました。
プロジェクト全体はnobusue/groovy-spark-sample · GitHubにあげてあります。

Sparkアプリケーション本体

テキストファイルを読み込んで、指定した単語を含む行数をカウントするだけの簡単なサンプルです。適当なテキストファイルを用意して、「sc.textFile("YOUR_TEXT_FILE_PATH")」のところを置き換えてください。

import org.apache.spark.*
import org.apache.spark.api.java.*
import org.apache.spark.api.java.function.*

public class SparkGroovySample {

  public static void main(String[] args) {

    def conf = new SparkConf().setMaster("local[2]").setAppName("WordCount")
    def sc   = new JavaSparkContext(conf)
    def file = sc.textFile("YOUR_TEXT_FILE_PATH").cache()

    def filterFunc = new Function<String,Boolean>() {
      public Boolean call(String s) {
        return s.contains('spark')
    }}

    def filterFunc2 = { it.contains('hadoop') } as Function

    def countsOfSpark = file.filter(filterFunc).count()
    def countsOfHadoop = file.filter(filterFunc2).count()

    println "Count of Spark:${countsOfSpark}, Count of Hadoop:${countsOfHadoop}"
  }
}

例えばSparkのREADME.mdに対して上記を実行すると、

Count of Spark:8, Count of Hadoop:10

みたいになるはずです。
フィルタ関数の定義は、SparkのJava APIでは以下のようにFunctionインターフェースを実装する必要があります。

    def filterFunc = new Function<String,Boolean>() {
      public Boolean call(String s) {
        return s.contains('spark')
    }}

Groovyの場合はクロージャから変換することで多少楽ができます。

    def filterFunc2 = { it.contains('hadoop') } as Function

注意点

コードを眺めているだけでは分かりづらいのですが、sc.textFile()で読み込んだファイルはRDD(JavaRDD)というオブジェクトに格納されています。
RDDに対する操作はワーカーノードで分散処理されるため、filter()などの操作で利用するオブジェクトはすべてシリアライズしてリモートに送信できなければいけません。上記サンプルをGroovyスクリプトではなくGroovyクラスとして実装しているのはこの条件を満たすためです。(実際に試してみると、Groovyスクリプトとして実装した場合にはfilterFunc()がシリアライザブルでないと判断されて例外が発生します。)

おまけ:ログ設定

SparkはLog4jを使っており、デフォルトではSpark自体の動作に関するログがINFOレベルで大量に出力されます。普段は必要ないので、以下のようにして消しておきましょう。

# Set everything to be logged to the console
#log4j.rootCategory=INFO, console
log4j.rootCategory=WARN, console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n

まとめ

素直にScalaPythonを勉強した方が楽かもしれませんが、それぞれ一長一短があるのでJava(Groovy)で使う方法も引き続き試行錯誤していきます。

GroovyでAWS SDK for Javaを使う #gadvent

groovy gradle AWS

このエントリは G*Advent Calendar(Groovy,Grails,Gradle,Spock...) Advent Calendar 2014 - Qiita の12/13担当分です。

AWS SDK for Javaとは?

パブリッククラウドサービスであるAmazon Web ServicesにはWebAPIが提供されていますが、生のAPIでは使いにくいため、各言語用からAPIを利用するためのライブラリが提供されています。
AWS SDK for Javaは読んで字のごとくJava用のライブラリです。
AWS SDK for Java | アマゾン ウェブ サービス(AWS 日本語)

Groovyから使うと何が嬉しいの?

http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/index.html を眺めてみていただくとわかりますが、AWS SDK for JavaAPIドメイン指向に設計されておりクラス構造が複雑です。
実際に使おうとするといろいろ試行錯誤が必要になるので、Groovyを使ってあらかじめAPIの挙動を調べておくと便利です。(もちろん、そのままGroovyでプロダクションコードを書いてもいいでしょう。)

例) EC2のインスタンス一覧を取得する

簡単な例として、EC2の指定リージョンのインスタンス一覧を取得してみましょう。
いろいろ試行錯誤するにはインタラクティブシェルの方がやりやすいので、
Gradle Groovy Shellプラグインを使って依存ライブラリ込みのREPLを起動する #gadvent - nobusueの日記
で紹介したGradle Groovy Shellプラグインを利用します。

事前準備

AWSAPIを利用するためには、Access KeyとSecret Keyを取得しておく必要があります。APIからのアクセス専用にIAMアカウントを新しく作成し、EC2のみ権限を与えておくとよいかと思います。

ビルドスクリプト作成

適当なディレクトリで build.gradle 作成します。

apply plugin: 'com.github.tkruse.groovysh'
apply plugin: 'java'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.tkruse.gradle:gradle-groovysh-plugin:1.0.2'
    }
}

repositories {
    jcenter()
}

dependencies {
  compile 'com.amazonaws:aws-java-sdk:1.9.10'
}

Groovy Shell起動

以下のコマンドでGroovy Shellを起動します。

$ gradle -q shell
This is a gradle Application Shell.
You can import your application classes and act on them.
Groovy Shell (2.3.6, JVM: 1.7.0_72)
Type ':help' or ':h' for help.
-------------------------------------------------------------------------------
groovy:000>

このGroovy ShellはAWS SDK for Javaのライブラリがダウンロードされ、クラスパスに追加された状態になっています。

EC2インスタンス一覧取得

Groovy Shellで以下を入力します。ここでは例としてOregonリージョン(us-west-2)を指定しています。

groovy:000> import com.amazonaws.services.ec2.*
groovy:000> import com.amazonaws.auth.*
groovy:000> import com.amazonaws.regions.*
groovy:000> credentials = new BasicAWSCredentials("<YOUR_ACCESS_KEY>","<YOUR_SECRET_KEY>")
groovy:000> ec2 = new AmazonEC2Client(credentials)
groovy:000> ec2.setRegion(Region.getRegion(Regions.US_WEST_2))
groovy:000> result = ec2.describeInstances()
groovy:000> result.each{ println "Instance ID: ${it.reservations.instances.instanceId}" }
Instance ID: [[i-50aaf05b]]

実際には実行毎に結果(レスポンス)がダンプされますので、見失わないようにしてください。例えば、最後の行の実行結果の後には以下が出力されます。

===> {Reservations: [{ReservationId: r-a8f079a3,OwnerId: 574167580182,Groups: [],GroupNames: [],Instances: [{InstanceId: i-50aaf05b,ImageId: ami-d13845e1,State: {Code: 16,Name: running},PrivateDnsName: ip-10-0-0-200.us-west-2.compute.internal,PublicDnsName: ec2-xx-xx-xx-xx.us-west-2.compute.amazonaws.com,StateTransitionReason: ,KeyName: xxx,AmiLaunchIndex: 0,ProductCodes: [],InstanceType: t2.micro,LaunchTime: Sun Aug 31 09:12:33 JST 2014,Placement: {AvailabilityZone: us-west-2a,GroupName: ,Tenancy: default},Monitoring: {State: disabled},SubnetId: subnet-xxxxxxx,VpcId: vpc-xxxxxx,PrivateIpAddress: 10.0.0.200,PublicIpAddress: xx.xx.xx.xx,Architecture: x86_64,RootDeviceType: ebs,RootDeviceName: /dev/xvda,BlockDeviceMappings: [{DeviceName: /dev/xvda,Ebs: {VolumeId: vol-eb315aea,Status: attached,AttachTime: Sun Aug 31 09:12:36 JST 2014,DeleteOnTermination: true}}],VirtualizationType: hvm,ClientToken: xxxxxxxxxx,Tags: [{Key: Name,Value: jenkins}],SecurityGroups: [{GroupName: xxx,GroupId: sg-xxxxxxxx}, {GroupName: xxx,GroupId: sg-xxxxxxx}],SourceDestCheck: true,Hypervisor: xen,NetworkInterfaces: [{NetworkInterfaceId: eni-34684351,SubnetId: subnet-xxxxxx,VpcId: vpc-xxxxxx,Description: Primary network interface,OwnerId: 574167580182,Status: in-use,MacAddress: 02:7b:60:ba:d1:ee,PrivateIpAddress: 10.0.0.200,PrivateDnsName: ip-10-0-0-200.us-west-2.compute.internal,SourceDestCheck: true,Groups: [{GroupName: xxx,GroupId: sg-xxxxxxx}, {GroupName: xxx,GroupId: sg-xxxxxxx}],Attachment: {AttachmentId: eni-attach-5aafb86d,DeviceIndex: 0,Status: attached,AttachTime: Sun Aug 31 09:12:33 JST 2014,DeleteOnTermination: true},Association: {PublicIp: xx.xx.xx.xx,PublicDnsName: ec2-xx-xx-xx-xx.us-west-2.compute.amazonaws.com,IpOwnerId: amazon},PrivateIpAddresses: [{PrivateIpAddress: 10.0.0.200,PrivateDnsName: ip-10-0-0-200.us-west-2.compute.internal,Primary: true,Association: {PublicIp: xx.xx.xx.xx,PublicDnsName: ec2-xx-xx-xx-xx.us-west-2.compute.amazonaws.com,IpOwnerId: amazon}}]}],EbsOptimized: false,}]}],}

毎回ダンプが出るのはややうっとおしいですが、レスポンスのどこに必要な情報が含まれているか確認するだけなら、これを眺めるだけで問題が解決する場合もあったりします。

Groovy Shellは「Ctrl+D」で終了します。

Groovy Shell以外で実行する場合の注意事項

「ec2 = new AmazonEC2Client(credentials)」のように変数定義を省略しているのはGroovy Shellの制約によるものです。
通常のGroovyスクリプトで実行する場合は「def ec2 = new AmazonEC2Client(credentials)」のようにしてください。

2015/1/29追記

Groovy2.4でgroovyshのinterpreterModeが追加されました。
http://jira.codehaus.org/browse/GROOVY-6623

groovysh起動後に

:set interpreterMode true

を実行すれば、groovysh上でも普通に「def x=3」とかで変数定義できるようになりました。めでたい。

まとめ

AWS SDKで悩んだら、とりあえずGroovyで試してみるのがおすすめです。

Gradle Groovy Shellプラグインを使って依存ライブラリ込みのREPLを起動する #gadvent

gradle groovy

このエントリは G*Advent Calendar(Groovy,Grails,Gradle,Spock...) Advent Calendar 2014 - Qiita の12/8担当分です。

Gradle Groovy Shellプラグインとは?

Groovyには"groovysh"(Groovy Shell)という機能があります。これはGroovyのインタラクティブシェルを起動するもので、いわゆるREPL相当の機能です。
それなりに便利な機能なのですが、残念ながらGroovyの標準ライブラリ以外を読み込むことができず、ライブラリを追加する場合には自力でクラスパスを通す必要があります。

Gradle Groovy Shellプラグインを利用すると、Gradleを利用して依存関係を解決した状態でgroovyshを起動することができます。

Gradle Groovy Shellプラグインの利用方法

Gradleのビルドスクリプトでgradle-groovysh-pluginを追加するだけです。
詳細はこちらを参照いただくとよいかと思います。

tkruse/gradle-groovysh-plugin · GitHub

V1.0.2での注意点

2014/12/8時点での最新バージョン(1.0.2)では、Gradle Groovy Shellプラグインのみを適用するとビルドエラーになります。
Javaプラグインのプロパティを参照していることが原因のようです。Javaプラグインもあわせて適用するようにしてください。

例) Twitter4Jを使ってみる

ここでは例として Twitter4J - A Java library for the Twitter API を使ってみましょう。
事前に https://apps.twitter.com/ でConsumer KeyやAccess Tokenを取得しておいてください。

適当なディレクトリで build.gradle 作成します。

apply plugin: 'com.github.tkruse.groovysh'
apply plugin: 'java'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.tkruse.gradle:gradle-groovysh-plugin:1.0.2'
    }
}

repositories {
    jcenter()
}

dependencies {
    compile 'org.twitter4j:twitter4j-core:4.0+'
}

そして、次のコマンドでGroovy Shellを起動します。

$ gradle -q shell
This is a gradle Application Shell.
You can import your application classes and act on them.
Groovy Shell (2.3.6, JVM: 1.7.0_72)
Type ':help' or ':h' for help.
-------------------------------------------------------------------------------
groovy:000>

"groovy:000>"というプロンプトが表示されたら、おもむろにGroovyコードをタイプしていきましょう。(完全ではありませんが、TABキーによる補完もそれなりに効きます。)
なお、groovyshでは"def"で定義した変数は参照できないので、何もつけずに定義するようにしてください。

groovy:000> import twitter4j.*
===> twitter4j.*

groovy:000> import twitter4j.conf.*
===> twitter4j.*, twitter4j.conf.*

groovy:000> cb = new ConfigurationBuilder()
===> twitter4j.conf.ConfigurationBuilder@6c01e903

groovy:000> cb.setOAuthConsumerKey("<YOUR_CONSUMER_KEY>")
===> twitter4j.conf.ConfigurationBuilder@6c01e903

groovy:000> cb.setOAuthConsumerSecret("<YOUR_CONSUMER_SECRET>")
===> twitter4j.conf.ConfigurationBuilder@6c01e903

groovy:000> cb.setOAuthAccessToken("<YOUR_OAUTH_TOKEN>")
===> twitter4j.conf.ConfigurationBuilder@6c01e903

groovy:000> cb.setOAuthAccessTokenSecret("<YOUR_OAUTH_SECRET>")
===> twitter4j.conf.ConfigurationBuilder@5a8fddea

groovy:000> tf = new TwitterFactory(cb.build())
===> twitter4j.TwitterFactory@6bffa4b9

groovy:000> twitter = tf.getInstance()
===> TwitterImpl{INCLUDE_MY_RETWEET=PostParameter{name='include_my_retweet', value='true', file=null, fileBody=null}}

groovy:000> query = new Query("gradle")
===> Query{query='gradle', lang='null', locale='null', maxId=-1, count=-1, since='null', sinceId=-1, geocode='null', until='null', resultType='null', nextPageQuery='null'}

groovy:000> result = twitter.search(query)
[Mon Dec 08 02:35:38 JST 2014]Request: 
[Mon Dec 08 02:35:38 JST 2014]GET https://api.twitter.com/1.1/search/tweets.json?q=gradle&with_twitter_user_id=true&include_entities=true
・・・

groovy:000> result.tweets.each{ println "@${it.user.screenName}: ${it.text}" }
@csterwa: RT @danveloper: What if bootstrapping a cloud full of @NetflixOSS was as easy as typing "initCloud"? Now it is. https://t.co/44OVUgfKG4
@IndieGameDevBot: RT @lastpoke: Way to download Utility classes with Gradle http://t.co/suUwF0xSyj #Android #AndroidDev #lastpoke
・・・

Ctrl+Dで終了します。

2015/1/29追記

Groovy2.4でgroovyshのinterpreterModeが追加されました。
http://jira.codehaus.org/browse/GROOVY-6623

groovysh起動後に

:set interpreterMode true

を実行すれば、groovysh上でも普通に「def x=3」とかで変数定義できるようになりました。めでたい。

まとめ

簡単ですが、Gradle Groovy Shellプラグインについて紹介しました。
Groovy Shellプラグインは主にGradleのビルドスクリプト開発を支援するために使われることが多いようですが、それ専用ではなくもう少し汎用的に作られているということがわかります。他の言語処理系におけるREPLに比べるとやや機能不足な感は否めませんが、JavaのREPLが登場するまではこれで凌ぐというのもアリかと思います。

また、Groovyには言語機能としてGrape(@Grab)が用意されているのですが、Grapeはスクリプト実行以外の使い方ではうまく動かない場合もありますので、うまく使い分けるとよいのではないでしょうか。

CDH5対応のSparkをビルドする方法

spark cdh

このエントリはSpark, SQL on Hadoop etc. Advent Calendar 2014 - Qiitaの12/6担当分です。

CDH5対応のSparkバイナリはどこに?

Sparkアプリケーションの開発を行っていると「Hadoopクラスタに接続してxxする」というケースがあると思います。いちいちアプリケーションのJARをアップロードするのは面倒なので、できれば「手元のマシン(Macとか)からリモートのHadoopクラスタに接続してxx」したいところです。
しかし、Hadoopエコシステムのプロダクトはライブラリの依存関係がシビアなため、バージョンやディストリビューションが異なると接続できないことが多いです。(しかも状況によって発生するエラーが異なるので原因究明が大変です。特に、自分のようにHadoopの経験が浅い人間は素直にディストリビューションの標準構成に従っておいたほうが心安らかに暮らせます。)
ところが、残念ながら Downloads | Apache Spark ではCDH5対応のバイナリが配布されていません。
f:id:nobusue:20141206053210p:plain
「んじゃ、Pre-built for CHD4でためしてみっか」というのは時間の無駄なのでやめておきましょう。以下のようなエラーが出てつながりません。

java.io.IOException: Failed on local exception: com.google.protobuf.InvalidProtocolBufferException: Message missing required fields: callId, status; Host Details : local host is: "nobusue-MacBookPro.local/10.0.0.1"; destination host is: "192.168.56.101":8020; 

ではどうするか。
Clouderaのドキュメントによると「ClouderaのリポジトリからRPMDEBなどのパッケージをインストールする」というのが正しい手順のようです。しかし、これでは手元の環境には導入できません。

CDH5版Sparkのソースを入手する

Apache Spark公式で配布されているソースからビルドする場合、CDH5対応のプロファイルが提供されていないため、ビルドオプションをまとめて指定することはできないようです。Hadoopのバージョンなど個別に指定しなければならないとなると面倒ですよね。
幸い、Clouderaがパッケージを生成するためのソースコードを配布しています。こちらですと、デフォルトでCDH5対応のバイナリが生成されるようにビルドスクリプトが修正されていますので楽です。(Clouderaさんに感謝!)
http://archive.cloudera.com/cdh5/cdh/5/
CDHのバージョンごとにソースやバイナリのアーカイブがありますので、利用する環境にあわせて選択しましょう。例えばCDH5.2.1だとこんな感じです。
f:id:nobusue:20141206055409p:plain
"spark-1.1.0-cdh5.2.1-src.tar.gz"が目的のソースアーカイブです。
よく見ると"spark-1.1.0-cdh5.2.1.tar.gz"なんてのもありますが、これはハズレなので間違えないように。

"spark-1.1.0-cdh5.2.1.tar.gz"はどうしてハズレなのか

このファイルはコンパイル済みバイナリの「JARのみ」を配布するためのアーカイブのようです。
実際にダウンロードしてみればわかりますが、spark-submitやspark-shellなどのコマンド実行に必要なシェルスクリプトなどが欠けており、そのあたりを自分で補う必要があるため、かえって手間がかかります。(Spark User MLでも同じような質問が出てました。)

CDH5版Sparkをソースからビルドする

基本的には Redirecting... と同じです。
JDK6以上 / Maven3.0.4以上が必要ですのでインストールしておいてください。また、ドキュメントには記載がありませんがScalaも必要です。

ダウンロードしてきたソースアーカイブを展開し、以下のコマンドを実行します。

$ SCALA_HOME=<YOUR_SCALA_HOME> MAVEN_OPTS="-Xmx2g -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=512m" mvn -DskipTests clean package

かなり時間がかかりますので気長に待ってください。(自分の環境では30分ほどかかりました。)
また、たまにClouderaのMavenリポジトリがダウンしていてビルドが失敗することがありますが、時間をおいてから再度実行すれば大丈夫だと思います。

2014/12/25追記

配布物のルートにある「make-distribution.sh」を使えば、MAVEN_OPTSなどの設定は省略できます。
また、バイナリアーカイブが必要な場合は「--tgz」を追加すれば生成できます。ファイル名は「spark--bin-.tgz」になります。

$ ./make-distribution.sh --skip-java-test --tgz --name 2.5.0

Java7以上でビルド刷る場合、「--skip-java-test」を指定しておかないと警告がでます。

モジュール構成に関する注意事項

ビルドを実行する前にpom.xmlを確認して、自分が使いたいモジュールが含まれているかチェックしておくことをおすすめします。
例えば spark-1.0.0-cdh5.1.3-src.tar.gz の場合、は以下のようになっています。

<modules>
  <module>core</module>
  <module>bagel</module>
  <module>graphx</module>
  <module>mllib</module>
  <module>tools</module>
  <module>streaming</module>
  <module>repl</module>
  <module>assembly</module>
  <module>external/twitter</module>
  <module>external/kafka</module>
  <module>external/flume</module>
  <module>external/zeromq</module>
  <module>external/mqtt</module>
  <module>examples</module>
</modules>

あれっ、何か変じゃないですか? (つд⊂)ゴシゴシ
そう、SparkSQLが見当たりません。。。どうもサポート対象外なので除外されているようです。
参考) New in CDH 5.1: Apache Spark 1.0 | Cloudera Engineering Blog

これだと「HDFSはCDH5.1だけどSparkSQLを使いたい」という場合に困りますよね。そういう場合にはに以下を追加してください。

  <module>sql/catalyst</module>
  <module>sql/core</module>
  <module>sql/hive</module>

これでspark-shellなどから普通にSparkSQLが使えるようになります。

ビルド実行時のログの最初の方で、ビルド対象のモジュールの一覧が出ます。念のため確認しておいてください。

[INFO] Reactor Build Order:
[INFO]
[INFO] Spark Project Parent POM
[INFO] Spark Project Core
[INFO] Spark Project Bagel
[INFO] Spark Project GraphX
[INFO] Spark Project Streaming
[INFO] Spark Project ML Library
[INFO] Spark Project Tools
[INFO] Spark Project Catalyst
[INFO] Spark Project SQL
[INFO] Spark Project Hive
[INFO] Spark Project REPL
[INFO] Spark Project Assembly
[INFO] Spark Project External Twitter
[INFO] Spark Project External Kafka
[INFO] Spark Project External Flume Sink
[INFO] Spark Project External Flume
[INFO] Spark Project External ZeroMQ
[INFO] Spark Project External MQTT
[INFO] Spark Project Examples

実行してみる

ビルドが問題なく完了すれば、あとはApache Spark公式から落としてきたバイナリと同じように使えます。
テストとしてリモートのHDFSにファイルを書き出してみましょう。

$ bin/spark-shell
Spark assembly has been built with Hive, including Datanucleus jars on classpath
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 1.1.0
      /_/

Using Scala version 2.10.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_72)
Type in expressions to have them evaluated.
Type :help for more information.
14/12/06 07:10:51 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Spark context available as sc.

scala> val people = sc.textFile("examples/src/main/resources/people.txt")
people: org.apache.spark.rdd.RDD[String] = examples/src/main/resources/people.txt MappedRDD[1] at textFile at <console>:12

scala> people.saveAsTextFile("hdfs://192.168.56.101:8020/tmp/people")

例外が出なければ正常に書き込まれているはずです。HueなどでHDFSを確認してみてください。
f:id:nobusue:20141206071744p:plain

すぐに使えるCDH5の環境が手元になくて、手っ取り早く用意したい場合には Cloudera QuickStart VM を利用するのがよいと思います。
ローカル環境のSparkからQuickStart VMHDFSに接続する場合は、こちらなどを参考にネットワークを設定してみてください。

小ネタでしたが、以上です。
次は adachij2002 さんです。よろしくお願いします。

Docker Remote APIを使ってみる #apijp

docker boot2docker webapi

このエントリは「Web API Advent Calendar 2014」の12/3担当です。次は「YosAwed」さんです。

最近話題のDockerですが、みなさんもう使ってますよね?

Dockerはクライアント-サーバーアーキテクチャを採用しており、DockerクライアントとDockerサーバーがRemote API経由で接続されています。つまり、「docker ps」などのコマンドはすべてサーバーに送られて処理されているということです。
このRemote API、実は(ほぼ)RESTになっていて、Dockerクライアント以外からでも利用することが可能です。そこで、Remote APIを直接使う方法についてご紹介したいと思います。

テストした環境

Mac OS X(Yosemite)のBoot2Dockerを利用しました。Linux環境でもDockerサーバーの設定だけ追加すれば大丈夫なはずです。

$ docker version
Client version: 1.3.2
Client API version: 1.15
Go version (client): go1.3.3
Git commit (client): 39fa2fa
OS/Arch (client): darwin/amd64
Server version: 1.3.2
Server API version: 1.15
Go version (server): go1.3.3
Git commit (server): 39fa2fa

$ boot2docker version
Boot2Docker-cli version: v1.3.2
Git commit: e41a9ae

Docker Remote APIを使うための設定

LinuxのDockerサーバーは、デフォルトではUnixドメインソケット(unix:///var/run/docker.sock)をリスンするように設定されています。
このままではリモートからTCP接続できないので、Dockerサーバーの起動時オプションに '-H tcp://0.0.0.0:2376' のようにしてTCPのリスンポートを追加する必要があります。

設定方法としては、dockerコマンドの起動時オプションで指定するか、設定ファイルに追記するなど、いくつかやり方があります。
例えばUbuntu14.04では /etc/init/docker.conf に

DOCKER_OPTS=' -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock'

を設定し、

$ sudo service docker restart

で反映します。
詳しくはDockerの公式ドキュメントを参照してください。
https://docs.docker.com/articles/basics/#bind-docker-to-another-hostport-or-a-unix-socket

Boot2Dockerの場合

Boot2Dockerの場合はもともとTCPで接続するようになっているので、特に設定は必要ありません。
DockerサーバーのTCP接続先は「boot2docker shellinit」で確認できます。

$ boot2docker shellinit
Writing /Users/nobusue/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/nobusue/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/nobusue/.boot2docker/certs/boot2docker-vm/key.pem
    export DOCKER_CERT_PATH=/Users/nobusue/.boot2docker/certs/boot2docker-vm
    export DOCKER_TLS_VERIFY=1
    export DOCKER_HOST=tcp://192.168.59.103:2376

TCP接続先はBoot2DockerのVMを再起動すると変わることがあるので、以下のコマンドで環境変数を毎回設定しましょう。

$ $(boot2docker shellinit 2>/dev/null)

また、Boot2Docker VMIPアドレスを毎回手打ちするのは面倒なので、以下のコマンドでhostsに登録しておきましょう。(Mac限定)

$ sudo sed -i -e '/dockerhost/d' /etc/hosts
$ echo $(boot2docker ip 2>/dev/null) dockerhost | sudo tee -a /etc/hosts

これでBoot2Docker VMを「dockerhost」で参照できます。
以降はこの設定を前提として記載しますので、未設定の方は適宜IPアドレスに読み替えてください。

Docker Remote APIでイメージ一覧を取得する

当然ですが、事前にDockerサーバーを起動しておいてください。(Boot2Dockerの方は boot2docker up しておいてください。)
また、wgetcurlなど、RESTのリクエストを送信できるコマンドをインストールしておいてください。
(筆者の環境ではcurlは証明書の処理でエラーが発生したため、wgetを利用しています。また、JSONを見やすく整形するためにjqコマンドを利用します。)

Dockerを初めて使う方は「docker pull」で適当なイメージをダウンロードしておいてくださいね。その上でイメージ一覧を確認しておきましょう。

$ docker images
REPOSITORY                   TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
oddpoet/zookeeper            latest              33a69da2484d        5 days ago          909.3 MB
nobusue/kafka                0.8.1.1             29836f0457d5        5 days ago          808.1 MB
nobusue/java7                u72                 140a4d7d1cea        5 days ago          500.6 MB

それではRESTで同じ情報が取れるか確認してみましょう。

まず、DockerサーバーのTCP接続先を確認します。
ここでは、「DOCKER_HOST=tcp://192.168.59.103:2376」を前提としてコマンドを実行例を記載します。

次に、おもむろにRESTリクエストを送信してみましょう。

$ wget http://dockerhost:2376/images/json -O - -q | jq .

Linux環境ではJSONが表示されたと思いますが、Boot2Docker環境では何も表示されないはずです。

実は、Boot2Docker環境ではTLS暗号化が行われており、証明書と秘密鍵の指定が必要です。以下のようにしてください。

$ wget --no-check-certificate --certificate=$DOCKER_CERT_PATH/cert.pem --private-key=$DOCKER_CERT_PATH/key.pem https://dockerhost:2376/images/json -O - -q | jq .

正常に実行できれば、こんな感じのレスポンスが返ってきます。

[
  {
    "Created": 1417099585,
    "Id": "33a69da2484d70d6e9b9e590b6286a4fe6fba5ab91a7444ba6d970a7c97e10d8",
    "ParentId": "47f7486b7c978fda52628680274d3e0aeaa504c027b48fca1b898755ef21eedd",
    "RepoTags": [
      "oddpoet/zookeeper:latest"
    ],
    "Size": 0,
    "VirtualSize": 909336287
  },
  {
    "Created": 1417088378,
    "Id": "29836f0457d5f82b748c42d3ed8f798086f8116b09b700e3c1f36d058404de62",
    "ParentId": "d53de445c803da52a60af0cde9f8448f4f1b34422129e5234c1e2ffa6ec09b67",
    "RepoTags": [
      "nobusue/kafka:0.8.1.1"
    ],
    "Size": 0,
    "VirtualSize": 808064814
  },
  {
    "Created": 1417080614,
    "Id": "140a4d7d1cea6982b3d62921d3edb509f2602c8bda69f99a2c6ca0e055f39f1c",
    "ParentId": "0d7c4caeafc408f94009cc6f8d6cf52a8412a81dd6158ee40dde18715ce115de",
    "RepoTags": [
      "nobusue/java7:u72"
    ],
    "Size": 0,
    "VirtualSize": 500624405
  }
]

「docker images」コマンドと同等の情報が得られることが確認できました。

Docker Remote APIでコンテナ一覧を取得する

次に、コンテナ一覧を取得してみます。

先に適当なコンテナを起動しておきましょう。

$ docker run -ti -d ubuntu
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
91d01d7df230        ubuntu:latest       "/bin/bash"         7 seconds ago       Up 6 seconds                            cranky_ardinghelli   

RESTでコンテナ一覧を取得してみます。

$ wget --no-check-certificate --certificate=$DOCKER_CERT_PATH/cert.pem --private-key=$DOCKER_CERT_PATH/key.pem https://dockerhost:2376/containers/json -O - -q | jq .
[
  {
    "Command": "/bin/bash",
    "Created": 1417537292,
    "Id": "91d01d7df23053c8bac8b3ccdd8a02015027da69a0b57a8a1e8b6b66f1477c7f",
    "Image": "ubuntu:latest",
    "Names": [
      "/cranky_ardinghelli"
    ],
    "Ports": [],
    "Status": "Up 22 seconds"
  }
]

簡単ですね!

その他のAPIの使い方

公式リファレンスを参照してください。
Docker Remote API - Docker Documentation
なお、Remote APIにはバージョンがあり、DockerサーバーとDockerクライアントでAPIバージョンが異なると接続できません。
自分でRESTを叩く場合には問題ないかもしれませんが、独自のクライアントを実装する場合などは注意してください。

(補足)Unixドメインソケットを覗き見する

socatコマンドでドメインソケットを覗き見できます。デバッグのときなどにどうぞ。

How to set the name of a Docker container using REST API - Stack Overflow