サーバからslackへ通知する

2回目の登場のあすてるです。 サーバ上で叩いたコマンドっていつ終わるかわからないものがありますよね? それらを完了したらslackで通知させようと思った話です。

slackでの設定

incoming hookを使用します。 https://my.slack.com/services/new/incoming-webhook にアクセスします

今回はランダムに送る程で Choose a channek... で #randam を選択して 下のAdd Incomingg WebHooks inteagration を押して進みます。

Webhook URLというものが出てくるのでこれをメモしておきましょう。 例として下記がでてきたとします。 https://hooks.slack.com/services/T0hogehoge/B0piyopiyo/HogePiyofooooooooooo

一番下の Save Settings を押して保存します。 これでslack上での作業は終了です。

サーバでの設定

ここからはサーバ上での操作です。 適当な場所にスクリプトを置きます。

#!/bin/bash
url='https://hooks.slack.com/services/T0hogehoge/B0piyopiyo/HogePiyofooooooooooo'
username='end_command'

to="$1"
message="$2"
emoji=':end:'

payload="payload={\"channel\": \"${to}\", \"username\": \"${username}\", \"text\": \"${message}\", \"icon_emoji\": \"${emoji}\"}"
curl -m 5 --data-urlencode "${payload}" $url

urlには先ほどメモしたWebhook URLを入れてください。 chmod 755 などして実行権限をつけておくと良いでしょう。

やってみる

第1引数にuser名かChannel名をいれて第2引数に通知したいコメントを入力します。 例

$ ./slack.sh "@astel" "test-message"
ok%
$ ./slack.sh "#random" "test-message"
ok%

okと表示され、メッセージが届いていることが確認できました。 aliasなどで設定しておくと便利かもしれません。

実際は&&で渡して使用します。

おわりに

簡単にセットアップできました。 userのアイコンを今回は:end:にしましたが自分で設定して面白いものもつくれそうです。 zabbixの障害通知やcronでセットしてbotなどにも使えそうですね。 問題としてはそもそも通知する前にこけてしまった場合は && だと動かないのでそこらへんは考える必要がありそうです。

Scalaのforとは

最近リファクタリングとか微妙な不具合修正やってるほさかです。 Scala の for は Java のものとはだいぶ様子が違います。そこで仕様を読みながらもう一度forについて学びます。

ここではscala-lang.orgを 読みながら自分なりの説明を書いてますので、気になる方はscala-lang.orgを読みましょう。

for とは

2種類のforがあります。 for内包表記forループ です。

for ( enum ) e

forループは 列挙enum の各要素に対して 式e を実行します。

REPLの実行例

scala> for (x <- List(1, 2, 3)) print(x)
123
for ( enum ) yield e

for内包表記は 列挙enum の各要素に対して 式e を実行し、その結果を集めて返します。

REPLの実行例

scala> for (x <- List(1, 2, 3)) yield -x
res0: List[Int] = List(-1, -2, -3)

列挙はまず ジェネレーター から始まります。その後には、さらに ジェネレーター値の定義ガード を書けます。

ジェネレーター p <- e は式eのうちパターンpにマッチするものをバインドします。 マッチしない物は無視されます。

値の定義valを省略する以外はいつもどおりです。

ガード if e は列挙のバインドを e が真のものに限定します。

これらはそれぞれ map flatMap withFilter foreach で実装されています。 これらのメソッドを実装したものであればジェネレーターにできます。

変換ルール

for内包表記 for (p <- e) yield e′e.map { case p => e′ } に変換されます。 forループ for (p <- e) e′e.foreach { case p => e′ } に変換されます。

for内包表記 for (p <- e; p′ <- e′;…) yield e″ (は空かジェネレーターか値の定義かガード) は e.flatMap { case p => for (p′ <- e′;…) yield e″ } に変換されます。

forループ for (p <- e; p′ <- e′;…) e″ (は空かジェネレーターか値の定義かガード) は e.foreach { case p => for (p′ <- e′;…) e″ } に変換されます。

ガード if g をともなう ジェネレーター p <- e は 一つのジェネレーター p <- e.withFilter((x1,…,xn) => g) (x1,…,xn は p の変数) に変換されます。

値の定義 p′ = e′ をともなう ジェネレーター p <- e は 値のペアを持つジェネレーターをともなって変換されます。 (p, p′) <- for (x@p <- e) yield { val x′@p′ = e′; (x, x′) }

おわり

forやScalaに限らず疑問があれば仕様を読めば良いと思います。

Scalaのspecializedアノテーションを使いこなすための基礎知識

こんにちは、アドテクエンジニアーのトデス子です。ふだんスカラを使っているのでスカラの話をします。

ScalaJavaと同様、型パラメータを使用したコードは内部的にObject型を通して使用されます。 そのため、IntDoubleといったプリミティブ型を指定した場合は boxing/unboxingのオーバーヘッドが発生します。

このオーバヘッドは多くの場合大した問題になりませんが、数値計算などの特定領域においては パフォーマンスのボトルネックになるケースがあります。

Scalaにおいては、@specializedアノテーションを使用することでこのオーバヘッドを軽減する機構があります。 この記事では、この機構の詳細と使用時の注意点などについて紹介します。

続きを読む

Monocleのコミッターになりました

ご無沙汰しています。最近カレーがマイブームのプロダクトグループ所属エンジニアのあおいの(@AoiroAoino)です。 私事ですが、前回書いた記事にも登場したMonocleというライブラリのコミッターになりました。 で、早速なんか記事書いてと言われました()ので、今回はとりあえず代表的な(?) Lensについて、適当に書こうかなと思います。

Lens とは?

例えば、こんな感じのデータ構造と、その適当なインスタンスがあったとします。

// 適当なデータ共
case class Job(id: Int)
case class Player(name: String, job: Job)
case class Game(player: Player, stage: Int)

val game1 = Game(Player("Aoino", Job(3)), 1)

このネストしたgame1の一番深いところ、AoinoさんのJobのidを書き換えようとした時にみなさんどうしますか?

copyメソッドを使って

// copyメソッドで頑張る
game1.copy( player = game1.player.copy( job = game1.player.job.copy( id = 20 ) ) )

とするか、case class Game(...) の中に

// それっぽいメソッドを定義する
def setJob(newId: Int): Game = this match {
  case Game(Player(name, Job(id)), stage) => Game(Player(name, Job(newId)), stage)
}

ってメソッドを定義する感じですかね?

前者はcopyメソッドのネストで辛いし、後者はcase class Game内にPlayerJobが持つフィールドごとにsetXXXって一々定義するの、うーん、微妙ですね。 いっそmutableにでもしてしまった方が、直感的でわかりやすくかけたりしますよね。

// もしvarだったら...
game1.player1.job.id = 5

とはいえ、もちろんimmutableのが良いし、でもって欲張ってmutableのような表現が欲しくなっちゃいますね。そこでLensの登場です。

とてもざっくり誤解を恐れずに言うと、Lensとはgetter/setterの性質を持った関数(みたいなもの)であり、取得したり変更したいフィールドに対する参照のようなものです。 故に、Functional Reference とかって名称で呼ばれていたりもするみたいです。

さて、ではひとまずJobに対してLensを定義してみましょう。
※MonocleでLensを定義(生成)する方法は複数ありますが、次節で詳しく説明します。

// Job.idに対するLens
val idLens: Lens[Job, Int] =
  Lens[Job, Int](_.id)(i => job => job.copy(id = i))

このidLensがお待ちかねの「Jobのidに対するLens」です。 なので、この参照に対するgetメソッドに、Jobインスタンスを渡すとidの値が得られますし、setmodifyで値を書き換えることができます。

// Job.idのLensを使ってみる
val job1 = Job(1)
val job2 = Job(2)

// get
idLens.get(job1) //=> 1
idLens.get(job2) //=> 2

// set
idLens.set(100)(job1) //=> Job(100)

// modify
idLens.modify(_ + 10)(job2) //=> Job(12)

さてさて、Jobに関しては良さそうですが、元々の話ではネストしているgame1Jobidを書き換えたいっていう話でしたね?ひとまずはGameからJobに至るまでのLensを定義しておきましょう。

// GameからJobに至るまでのLens共を定義
// Player.job
val jobLens: Lens[Player, Job] =
  Lens[Player, Job](_.job)(j => player => player.copy(job = j))

// Game.player
val playerLens: Lens[Game, Player] =
  Lens[Game, Player](_.player)(p => game => game.copy(player = p))

単純にgetするだけであれば、playerLens.getしてjobLens.getしてidLens.getすればいいですね。

// ネストしたデータに対するget
idLens.get( jobLens.get( playerLens.get(game1) ))
  //=> 3

で、setは.....こんな感じ.....かな.....

playerLens.set(jobLens.set(idLens.set(100)(jobLens.get(playerLens.get(game1))))(playerLens.get(game1)))(game1)
   //=> Game(Player(Aoino,Job(100)),1)

んー、しかしながら、このままだとcopyやヘルパーメソッドを定義した時と比べて大変辛い..... あれ?でも特にgetの形、見覚えありませんか?はいそうです、関数合成の話でよく見かけるパターンですね。そう、Lensも同様に合成することができるんです。

※そもそも元になったHaskellの場合、Lens は(実装方法にもよりますが?)関数として表現され、合成関数として表されます。 しかし、残念ながらMonocleの場合、Lensはデータ構造として表現されているので、厳密には「関数の合成」ではないですけれど。

MonocleでLensを合成する場合はcomposeLensメソッドを使用します。関数を合成する時と同様、順番には注意です。

// composeLens版
// get
(playerLens composeLens jobLens composeLens idLens) get game1
  //=> 3

// set
(playerLens composeLens jobLens composeLens idLens).set(20)(game1)
  //=> Game(Player(Aoino,Job(20)),1)

// modify
(playerLens composeLens jobLens composeLens idLens).modify(_ + 7)(game1)
  //=> Game(Player(Aoino,Job(10)),1)

またはcomposeLensエイリアスメソッドである^|->を使用するともう少し短くかけます。

// ^|-> 版
// get
(playerLens ^|-> jobLens ^|-> idLens) get game1
  //=> 3

// set
(playerLens ^|-> jobLens ^|-> idLens).set(20)(game1)
  //=> Game(Player(Aoino,Job(20)),1)

// modify
(playerLens ^|-> jobLens ^|-> idLens).modify(_ + 7)(game1)
  //=> Game(Player(Aoino,Job(10)),1)

さてさて、ご覧の通りスッキリ書けましたね? Lensの合成は冒頭で述べた通り「取得したり変更したいフィールドに対する参照のようなもの」であり、その参照してるフィールドに対して、値を取得したり、書き換えたりができるというのがお分かり頂けたかと思います。

このように、Lens はネストしたデータに対するimmutableで簡潔な操作を提供してくれる素敵な概念です。

Monocleを試してみよう

さて、今度はMonocleでLensを定義する方法に焦点を当ててみようと思います。 前節ではgetter/setterを渡して自前で頑張っていましたが、Lensを定義する方法はv1.2.0-M1現在、三種類あります。

自前で頑張る

前節で説明のためにgetter/setterを渡して自前で作った方法です。

// 自前で頑張る
import monocle.Lens

val idLens: Lens[Job, Int] =
  Lens[Job, Int](_.id)(i => job => job.copy(id = i))

カリー化されていますが、第一引数がgetter、第二引数がsetterです。一応setterの方は引数の順番に注意が必要です。 定義に忠実なので理解しやすいですが、いざ自分で定義しようとするといささか面倒な方法です。

GenLensマクロを使う

恐らく、MonocleでLensを定義しようとした際に一番使われている生成方法です。 内部的には上記自前で頑張るコードをマクロで生成してるだけです。

// 基本的なGenLensの使い方
import monocle.Lens
import monocle.macros.GenLens

val idLens:  Lens[Job, Int]     = GenLens[Job, Int](_.id)
val jobLens: Lens[Player, Job]  = GenLens[Player, Job](_.job)
val idLens:  Lens[Game, Player] = GenLens[Game, Player](_.player)

また、以下のような使い方もできます。

// 分割して定義する
import monocle.Lens
import monocle.macros.GenLens

val gen: GenLens[Game] = GenLens[Game]

val playerLens: Lens[Game, Player] = gen(_.player)
val stage:      Lens[Game, Int]    = gen(_.stage)
// ネストした case class に対するLensをいきなり作る
import monocle.Lens
import monocle.macros.GenLens

val idLens: Lens[Game, Int] = GenLens[Game](_.player.job.id)

自前で定義するよりも楽で、次に紹介する@Lensesよりも扱いやすいので、基本的にはGenLensを使えばいいと思います。

@Lensesマクロアノテーションを使う

case class定義時に@Lensesアノテーションをつけると、コンパニオンオブジェクト内に各プロパティに対するLensが定義されます。 macro paradise を使用した機能なので、(例えばsbtで開発を行う場合は)下記プラグインを追加する必要があります。

addCompilerPlugin("org.scalamacros" %% "paradise" % "2.1.0-M5" cross CrossVersion.full)

使い方は以下の通り。

// @Lensesマクロアノテーションの使い方
@Lenses
case class Game(player: Player, stage: Int)

// コンパニオンオブジェクト内に player, stage に対するLensが生成される
Game.player.get(game1) //=> Player(Aoino,Job(3))
Game.stage.get(game1)  //=> 1

大変強力な機能ですが、そもそも macro paradise が experimental な機能であり、また、case class定義時にしか使用できない為、 以外と扱いにくいのが現状です。あくまで実験的な機能と捉えるのがいいでしょう。

おまけ

MonocleのREADMEに従って、build.sbtファイルを書けばすぐにでも使い始めることができますが、もっと簡単にMonocleを試せるようgiter8のテンプレートを作ってみました。
※giter8の導入方法に関しては、giter8のREADMEを参照してください。

使い方は簡単で、

# テンプレート生成
$ g8 aoiroaoino/monocle-template

コマンドラインで実行し、名前やScala, Monocleのバージョンなどを指定してやるだけです。 何も指定しない場合、ScalaやMonocleのバージョンはデフォルトで最新を指定するようになっているので、とりあえずnameだけ指定してやればいいと思います。

まとめ

今回はLensについてのみざっくりと適当に書きましたが、ほかにもPrismやTraversalなどの概念があり、Optics(LensやらPrismやらの総称)は大変興味深く非常に面白い概念です。 これがHaskellだけでなく、Scalaでも使えるなんてとてもワクワクしますね!以上、あおいの(@AoiroAoino)がお送りしました。

(´-`).oO(あ、個人的に弊社では、MonocleやOpticsに興味のあるエンジニアさんを募集しています!

公式のDockerコンテナでRedis Clusterを構築する

はじめまして、@chaltosです。
今回はDockerコンテナでRedisクラスタを構築した際の手順について書きます。
set, getなどが問題なく実行できればよい」という状況での作業メモ的なものなので、レプリケーションやバックアップなどはまったく考慮してません。

環境

OS Mac OS X 10.9.2
Docker 1.8.2
DockerMachine 0.4.1

やってみる

Redis Cluster Tutorialによると、配布されているソースコードにはredis-tribやcreate-clusterというユーティリティが同梱されていて、以下のようなコマンドでノードの追加やスロットの割当てができるらしいです。便利ですね。

$ ./redis-trib.rb create 127.0.0.1:7000 127.0.0.1:7001 ...
$ create-cluster start
$ create-cluster create

ですが、公式のDockerコンテナではRedisのビルド後にソースコードを削除しているため、これらに頼ることはできません。
仕方ないので手動でやります。

設定ファイル

デフォルトの設定ではクラスタリングできないので、まずは設定ファイルを用意します。

cluster-enabled      yes
cluster-config-file  nodes.conf
cluster-node-timeout 5000

イメージの作成・起動

--volumeオプションで設定ファイルのあるディレクトリをマウントします。
上記の設定ファイルでredis-serverを実行すると、/data/nodes.confが生成されます。
このファイルの所有者はredis:redisです。
--volume $(pwd):/dataとすると/dataの所有者が1000:staffになってしまってnodes.confを生成できないので、--volume $(pwd):/data/confとしておきます。

$ docker create --name node_6379 -p 6379:6379 --volume $(pwd):/data/conf redis:3 redis-server /data/conf/redis.conf
$ docker create --name node_6380 -p 6380:6379 --volume $(pwd):/data/conf redis:3 redis-server /data/conf/redis.conf
$ docker create --name node_6381 -p 6381:6379 --volume $(pwd):/data/conf redis:3 redis-server /data/conf/redis.conf
$ docker start node_6379 node_6380 node_6381

クラスタリング

スロットの割当

まず各ノードにスロットを割当てます。

$ docker exec node_6379 redis-cli cluster addslots {0..5500}
OK
$ docker exec node_6380 redis-cli cluster addslots {5501..11000}
OK
$ docker exec node_6381 redis-cli cluster addslots {11001..16383}
OK

OKっぽいですね、確認しましょう。

$ docker exec node_6379 redis-cli cluster nodes
cf2e950621381be04ea1031ae80cb945f9743b83 :6379 myself,master - 0 0 0 connected 0-5500
$ docker exec node_6380 redis-cli cluster nodes
0969adb771d38a49e70f235b39665156ded5a43a :6379 myself,master - 0 0 0 connected 5501-11000
$ docker exec node_6381 redis-cli cluster nodes
d9c4125051222bbc36cd0d0172e406baa9d89579 :6379 myself,master - 0 0 0 connected 11001-16383

ちゃんと割当てできてるように見えます。

ノードの追加

クラスタにノードを追加していきます。

$ docker exec node_6380 redis-cli cluster meet `docker-machine ip $DOCKER_MACHINE_NAME` 6379
OK
$ docker exec node_6381 redis-cli cluster meet `docker-machine ip $DOCKER_MACHINE_NAME` 6379
OK

OKっぽいですね。

$ docker exec node_6379 redis-cli cluster nodes
cf2e950621381be04ea1031ae80cb945f9743b83 :6379 myself,master - 0 0 0 connected 0-5500
$ docker exec node_6380 redis-cli cluster nodes
0969adb771d38a49e70f235b39665156ded5a43a :6379 myself,master - 0 0 0 connected 5501-11000
$ docker exec node_6381 redis-cli cluster nodes
d9c4125051222bbc36cd0d0172e406baa9d89579 :6379 myself,master - 0 0 0 connected 11001-16383

変わってません。

やりなおす

どうもNAT越しだとうまくいかない(?)ようです。

--net hostを追加して、ブリッジしないようにしてみます。
ついでに各ノードで異なるポートを使用するようにしておきます。

$ for n in `seq 6379 6381`; do mkdir $n && echo "port $n" > $n/redis.conf && cat redis.conf >> &n/redis.conf; done
$ docker stop node_6379 node_6380 node_6381
$ docker rm node_6379 node_6380 node_6381
$ docker create --name node_6379 --volume $(pwd)/6379:/data/conf --net host redis:3 redis-server /data/conf/redis.conf
$ docker create --name node_6380 --volume $(pwd)/6380:/data/conf --net host redis:3 redis-server /data/conf/redis.conf
$ docker create --name node_6381 --volume $(pwd)/6381:/data/conf --net host redis:3 redis-server /data/conf/redis.conf
$ docker start node_6379 node_6380 node_6381
$ docker exec node_6379 redis-cli -p 6379 cluster addslots {0..5500}
$ docker exec node_6380 redis-cli -p 6380 cluster addslots {5501..11000}
$ docker exec node_6381 redis-cli -p 6381 cluster addslots {11001..16383}
$ docker exec node_6380 redis-cli -p 6380 cluster meet `docker-machine ip $DOCKER_MACHINE_NAME` 6379
$ docker exec node_6381 redis-cli -p 6381 cluster meet `docker-machine ip $DOCKER_MACHINE_NAME` 6379
$ docker exec node_6379 redis-cli -p 6379 cluster nodes
ca0a4f11069103e902ea83de9023b34a7aa01d06 192.168.99.100:6379 myself,master - 0 0 0 connected 0-5500
465ab243a99efec4e7cfa00491304b8dcffd0701 192.168.99.100:6381 master - 0 1444015783288 2 connected 11001-16383
c185ffd045e2ce324dce43ae5374253df8da2af2 192.168.99.100:6380 master - 0 1444015784307 1 connected 5501-11000
$ docker exec node_6380 redis-cli -p 6380 cluster nodes
465ab243a99efec4e7cfa00491304b8dcffd0701 192.168.99.100:6381 master - 0 1444015806729 2 connected 11001-16383
ca0a4f11069103e902ea83de9023b34a7aa01d06 192.168.99.100:6379 master - 0 1444015805713 0 connected 0-5500
c185ffd045e2ce324dce43ae5374253df8da2af2 192.168.99.100:6380 myself,master - 0 0 1 connected 5501-11000
$ docker exec node_6381 redis-cli -p 6381 cluster nodes
c185ffd045e2ce324dce43ae5374253df8da2af2 192.168.99.100:6380 master - 0 1444015823353 1 connected 5501-11000
465ab243a99efec4e7cfa00491304b8dcffd0701 192.168.99.100:6381 myself,master - 0 0 2 connected 11001-16383
ca0a4f11069103e902ea83de9023b34a7aa01d06 192.168.99.100:6379 master - 0 1444015824374 0 connected 0-5500

先ほどとは違い、どのノードも他のノードを認識できてるようです。

実際にsetとかgetしてみる

-cは必須です。忘れるとリダイレクトせずにエラーを返します。

$ docker exec -it node_6379 redis-cli -c -p 6379
127.0.0.1:6379> set hoge 1
OK
127.0.0.1:6379> set hoge2 2
-> Redirected to slot [11000] located at 192.168.99.100:6380
OK
192.168.99.100:6380> set hoge3 3
-> Redirected to slot [15065] located at 192.168.99.100:6381
OK
192.168.99.100:6381> get hoge
-> Redirected to slot [1525] located at 192.168.99.100:6379
"1"
192.168.99.100:6379> get hoge2
-> Redirected to slot [11000] located at 192.168.99.100:6380
"2"
192.168.99.100:6380> get hoge3
-> Redirected to slot [15065] located at 192.168.99.100:6381
"3"

きちんと他のノードにリダイレクトしてくれてますね。