技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

Kuromoji 形態素解析を使ってみた

ぶっちゃけこれ

www.atilika.org

Scala2.11.6 +SBT 0.13.x で使ってみたログ。

まずは build.sbt

libraryDependencies ++= Seq(
  "org.atilika.kuromoji" % "kuromoji" % "0.7.7"
)

にこんなん書いて、

import org.atilika.kuromoji.{Token, Tokenizer}

object SelfTokenizer {
  import scala.collection.JavaConversions._

  val tokenizer = {
    val result = Tokenizer.builder()
    result.mode(Tokenizer.Mode.SEARCH)
    result.build()
  }

  def tokenize(str: String): List[Token] = this.synchronized {
    if (str == null || str.isEmpty) List.empty[Token] else tokenizer.tokenize(str).toList
  }
}

とすれば完成。一応 Java のライブラリで、crawler4j と組み合わせで使うと幸せに。

Tokenizer.Mode で幾つかモードを指定できて、結果が違う。
今回のこれは、検索エンジンに特化するように、SEARCH を指定。

Token のフィールドは、「Surface(原文)」「PartOfSpeech(品詞情報)」だけ分かってれば良さそう。
形態素解析ライブラリを触ってみる【Igo】 - 謎言語使いの徒然 より手軽でいい感じ(結構最近でもメンテナンスしているので、信用できそう)

Markdown で書くWiki、MDWiki

MDwiki

見たままだけど、HTML5だけで書くことができるWiki

ただし、DBを持たないので動的書き換えはできません。同じディレクトリ配下に md ファイルを乗っけて、html ファイルをフロントエンドにすることでHTMLファイルとして表示するコンバーターみたいな使い方だ。

物がシンプルなので、組み合わせに非常に向いているように思います。

色々応用できそうで夢が広がりますね。

ライセンスは GPL3

Xitrum web framework 弄ってみた

何か ScalaJPXitrum という単語があったので使ってみた。

まず新しいプロジェクトを作ろうか。

curl -L -o xitrum-new.zip https://github.com/xitrum-framework/xitrum-new/archive/master.zip

後は解凍して終わり。

インストールが必要ないのは楽でいいね。

中身を見れば大体分かる。

README.rst
build.sbt
screenshot.png

./config:
akka.conf
application.conf
flash_socket_policy.xml
logback.xml
ssl_example.crt
ssl_example.key
xitrum.conf

./project:
build.properties
plugins.sbt

./public:
404.html
500.html
app.css
favicon.ico
robots.txt
whale.png

./sbt:
sbt
sbt-launch-0.13.5.jar
sbt.bat

./script:
runner
runner.bat
scalive
scalive-1.2.jar
scalive.bat

./src:
main

./src/main:
scala
scalate

./src/main/scala:
quickstart

./src/main/scala/quickstart:
Boot.scala
action

./src/main/scala/quickstart/action:
DefaultLayout.scala
Errors.scala
SiteIndex.scala

./src/main/scalate:
quickstart

./src/main/scalate/quickstart:
action

./src/main/scalate/quickstart/action:
DefaultLayout.jade
NotFoundError.jade
ServerError.jade
SiteIndex.jade

そのままだよね。

デフォルトでは model に該当するクラスは用意されていない。好きに作ればいいのかな?

構成が殆んどプレーンな sbt なので、ScalaActiveRecord 突っ込めば色々楽ができるだろうね。

Controller

この辺を見る。

  • ./src/main/scala/quickstart/action

まぁファイル名を見れば分かるでしょう。

DefaultLayout.scala : Controller 規定

package quickstart.action

import xitrum.Action

trait DefaultLayout extends Action {
  override def layout = renderViewNoLayout[DefaultLayout]()
}

Action クラスの継承がコントローラクラスの条件のようだ。layout は View 名を省略したときに自動で適用される View テンプレートの選択ロジックに見える。

trait なのが気になるがまぁいい。

Errors.scala

package quickstart.action

import xitrum.annotation.{Error404, Error500}

@Error404
class NotFoundError extends DefaultLayout {
  def execute() {
    respondView()
  }
}

@Error500
class ServerError extends DefaultLayout {
  def execute() {
    respondView()
  }
}

DefaultLayout を継承してコントローラを作成している。execute メソッドがコントローラ本体。この構成はぱっと見 PHPSmarty にも見える。

コントローラクラスの定義が class である事を考えると、アクセスごとにインスタンスを生成している可能性がある?

てなことで、SiteIndex.scala の中身が

package quickstart.action

import xitrum.annotation.GET
import java.rmi.server.UID

@GET("")
class SiteIndex extends DefaultLayout {

  lazy val uid = new UID()

  def execute() {
    println(s"Instance UNIQUE ID: $uid")
    respondView()
  }
}

で、起動(sbt run)してログを,,,,,ってぇ!

8GB メモリのカスタム MacBookAir でまさかの OutOfMemory ... byobu + Chromeでタブ3枚でダメか…。

仕方ないので 16GB の窓8.1で再チャレンジ。

凄まじく大量の resolve... 落ちた原因はこれか。

で、起動して早速 http://localhost:8000 に...

[WARN] Caught exception
java.lang.StringIndexOutOfBoundsException: String index out of range: 1
        at java.lang.String.charAt(String.java:658) ~[na:1.7.0_45]
        at java.util.regex.Matcher.appendReplacement(Matcher.java:762) ~[na:1.7.0_45]
        at java.util.regex.Matcher.replaceAll(Matcher.java:906) ~[na:1.7.0_45]
        at java.lang.String.replaceAll(String.java:2162) ~[na:1.7.0_45]
        at xitrum.util.ClassFileLoader.loadClassData(ClassFileLoader.scala:39) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.util.ClassFileLoader.findClass(ClassFileLoader.scala:26) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.util.ClassFileLoader.loadClass(ClassFileLoader.scala:17) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Dispatcher$.devDispatch(Dispatcher.scala:139) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Dispatcher$.dispatch(Dispatcher.scala:68) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Dispatcher.channelRead0(Dispatcher.scala:176) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Dispatcher.channelRead0(Dispatcher.scala:154) ~[xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.MethodOverrider.channelRead0(MethodOverrider.scala:45) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.MethodOverrider.channelRead0(MethodOverrider.scala:19) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.UriParser.channelRead0(UriParser.scala:33) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.UriParser.channelRead0(UriParser.scala:16) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.WebJarsServer.channelRead0(WebJarsServer.scala:33) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.WebJarsServer.channelRead0(WebJarsServer.scala:23) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.PublicFileServer.channelRead0(PublicFileServer.scala:33) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.PublicFileServer.channelRead0(PublicFileServer.scala:23) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.BaseUrlRemover.channelRead0(BaseUrlRemover.scala:23) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.BaseUrlRemover.channelRead0(BaseUrlRemover.scala:11) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.Request2Env.sendUpstream(Request2Env.scala:283) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Request2Env.channelRead0(Request2Env.scala:96) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Request2Env.channelRead0(Request2Env.scala:44) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:163) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:787) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:507) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:464) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:378) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:350) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at java.lang.Thread.run(Thread.java:744) [na:1.7.0_45]

|||orz 俺が書いた部分が問題なのかと思って、修正した部分を削ったが状況は変わらず…。

ぱっと見 routing の名前解決で落ちてるのか?

てことで

package quickstart.action

import xitrum.annotation.GET
import java.rmi.server.UID

@GET("/")
class SiteIndex extends DefaultLayout {

  def execute() {
    respondView()
  }
}

でもダメだった

org.fusesource.scalate.TemplateException: temp file rename failed

で起動失敗…2014/06/29 20:54 現在の master で発生…。

今のバージョン特有の問題なのか…?

バージョン番号は xitrum-new の 1.0-SNAPSHOT ...

SNAPSHOT だしなぁ

routing は慣れれば楽だが、class 数が爆発しそうな気がしてはいる。事実上の routing は annotation で指定している。

レスポンスの仕様は ここ 見ればわかるけど、結構綺麗だ。

テンプレートエンジンは scalate を使用。

同様に、このテンプレートの仕様を眺めていると、1アクション1テンプレート構成を徹底しているらしい。つまり、1アクションの中で複数のテンプレートを使いたければ、Routing アノテーションを付与しない複数の Action クラスを定義して、respondView[HomeAction_NormalUser]()とか叩くらしい。

一つのアクションで複数のテンプレートを返すという挙動は設計的にどうなのとは思うけど、これはこれで冗長だなぁ。

Playframework の implicit マジックの雨霰とどっちがいいかは好みが分かれそうな。

HikariCP + ScalaActiveRecord + MySQL5.1 をやってみた

何故にメモかというと、HikariCP がそもそもサンプル無さ過ぎて地獄を見たから。

まずは基本的な ScalaActiveRecord アプリを作る

Play のサンプルアプリケーションをまずは作る。

    play new HikariSample

ウィザードくらいは任せた。

で、速攻、 ScalaActiveRecord を突っ込む。

build.sbt はこれ

    name := "HikariSample"
    
    version := "1.0-SNAPSHOT"
    
    libraryDependencies ++= Seq(
      jdbc,
      anorm,
      cache,
      "com.github.aselab" %% "scala-activerecord" % "0.2.3",
      "com.github.aselab" %% "scala-activerecord-play2" % "0.2.3",
      "mysql" % "mysql-connector-java" % "5.1.22"
    )     
    
    play.Project.playScalaSettings

そしたら、下記の様にまずは書く。

conf/play.plugins

    9999:com.github.aselab.activerecord.ActiveRecordPlugin

conf/application.conf

    # 下記を追加
    db.activerecord.driver=com.mysql.jdbc.Driver
    db.activerecord.url="jdbc:mysql://localhost:3306/testdb"
    db.activerecord.user="root"
    db.activerecord.password="test"
    activerecord.schema=models.Tables

で、モデルは下記の通り。

    package models
    
    import com.github.aselab.activerecord._
    import com.github.aselab.activerecord.dsl._
    
    case class Todo(name: String, description: String) extends ActiveRecord
    
    object Todo extends ActiveRecordCompanion[Todo]
    
    object Tables extends ActiveRecordTables {
      val todos = table[Todo]
      
      on(todos)(s => declare(
        s.description is(dbType("text"))
      ))
    }

controlllers/Application.scala

    package controllers

    import play.api._
    import play.api.mvc._
    import play.api.data._
    import play.api.data.Forms._
    import models._
    import com.github.aselab.activerecord.dsl._

    object Application extends Controller {

      def form = Form(mapping(
        "name" -> nonEmptyText,
        "description" -> nonEmptyText
      )(Todo.apply)(Todo.unapply))

      def index = Action { implicit request =>
        Ok(views.html.index(form, Todo.all.toList))
      }

      def post = Action { implicit request =>
        val req = form.bindFromRequest()
        if (!req.hasErrors) {
          req.value.foreach(v => {
            v.save()
          })
        }
        Ok(views.html.index(req, Todo.all.toList))
      }
    }

conf/routes

    GET     /                           controllers.Application.index
    POST    /                           controllers.Application.post
    GET     /assets/*file               controllers.Assets.at(path="/public", file)
    

そして、views/index.scala.html を

    @(form: Form[models.Todo], todos: List[models.Todo])

    @main("Welcome to Play") {

        @for(todo <- todos){
        &lt;div&gt;
            &lt;strong&gt;@todo.name&lt;/strong&gt;&amp;nbsp;
            &lt;span&gt;@todo.description&lt;/span&gt;
        &lt;/div&gt;
        }

        @helper.form(action = routes.Application.index()) {
            @helper.inputText(form("name"))
            @helper.inputText(form("description"))
            &lt;input type="submit" value="送信"/&gt;
        }
    }

実験環境を作る

実験環境を色々するのめんどいので、Vagrant するか。Vagrant 知らない人はここ参照。*1

HikariSample ディレクトリの外で適当なディレクトリを掘って Vagrant コマンド。

    vagrant init test https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box

デプロイめんどいから、適当に共有かけるか。

Vagrantfile 開いてごりごり

    # -*- mode: ruby -*-
    # vi: set ft=ruby :

    # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
    VAGRANTFILE_API_VERSION = "2"

    Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
      config.vm.box = "test"

      config.vm.box_url = "https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box"

      config.vm.network "private_network", ip: "192.168.33.10"

      config.vm.synced_folder "/Users/azalea/Documents/HikariSample", "/vagrant_data"
    end
    

で、実行環境を整えて

    vagrant up
    vagrant ssh
    sudo su -
    yum install -y mysql-server wget
    yum install -y java-1.7.0-openjdk-devel
    service mysqld start
    mysql -u root
    create database testdb
    SET PASSWORD FOR root@localhost=PASSWORD('test');
    exit

後は起動するだけ。

play とか sbt めんどいので、sbt dist しておく。

    cd /vagrant_data/
    cd /opt
    cp /vagrant_data/target/universal/hikarisample-1.0-SNAPSHOT.zip ./
    unzip hikarisample-1.0-SNAPSHOT.zip
    cd hikarisample-1.0-SNAPSHOT
    ./bin/hikarisample

http://192.168.33.100/ でアクセスできれば OK

HikariCP に乗り換える

HikariCP ってなんぞ?と思ったらここをクリック

要するに BoneCP がいつまで経ってもバグだらけだから嫌がって作ったそうな。

ScalaActiveRecord の Play plugin は playframwork からコネクションを拾ってるから、バックエンドが変わるくらいは問題なく動作できる。

まずは build.sbt を編集。

    name := "HikariSample"

    version := "1.0-SNAPSHOT"

    resolvers += Resolver.url("Edulify Repository", url("http://edulify.github.io/modules/releases/"))(Resolver.ivyStylePatterns)

    libraryDependencies ++= Seq(
      jdbc,
      anorm,
      cache,
      "com.github.aselab" %% "scala-activerecord" % "0.2.3",
      "com.github.aselab" %% "scala-activerecord-play2" % "0.2.3",
      "com.edulify" % "play-hirakicp_2.10" % "1.0.0",
      "mysql" % "mysql-connector-java" % "5.1.22"
    )     

    play.Project.playScalaSettings

次に application.conf でデフォルトの DBPlugin(BoneCP) を停止 & DB設定。

    dbplugin=disabled
    # db.activerecord.driver=com.mysql.jdbc.Driver
    # db.activerecord.url="jdbc:mysql://localhost:3306/testdb"
    # db.activerecord.user="root"
    # db.activerecord.password="test"

    db.activerecord.hikaricp.file="conf/hikaricp.prod.properties"

hikaricp.prod.properties には下記設定

    dataSourceClassName=com.mysql.jdbc.jdbc2.optional.MysqlDataSource
    dataSource.url=jdbc:mysql://localhost:3306/testdb
    dataSource.user=root
    dataSource.password=test
    dataSource.cachePrepStmts=true
    dataSource.prepStmtCacheSize=250
    dataSource.prepStmtCacheSqlLimit=2048
    dataSource.useServerPrepStmts=true

    connectionTestQuery=SELECT 1
    connectionInitSql=SELECT 1
    maximumPoolSize=20

お次は conf/play.plugin に追加。

    1500:com.edulify.play.hikaricp.HikariCPPlugin

これで準備は整った。

ちなみに、maximumPoolSize はMySQL の設定にあわせておく事。 (デフォルトでは 150 あったはず)

これで dist して再度起動。

    [root@vagrant-centos65 hikarisample-1.0-SNAPSHOT]# ./bin/hikarisample 
    Play server process ID is 3020
    [info] application - Loading Hikari configuration from Play configuration.
    [info] application - Loading from file configured by db.default.hikaricp.file that is Some(conf/hikaricp.prod.properties)
    [info] application - Loading Hikari configuration from conf/hikaricp.prod.properties
    [info] application - Properties: {dataSource.useServerPrepStmts=true, dataSource.user=root, dataSource.password=test, dataSourceClassName=com.mysql.jdbc.jdbc2.optional.MysqlDataSource, connectionTestQuery=SELECT 1, maximumPoolSize=20, connectionInitSql=SELECT 1, dataSource.prepStmtCacheSqlLimit=2048, dataSource.prepStmtCacheSize=250, dataSource.cachePrepStmts=true, dataSource.url=jdbc:mysql://localhost:3306/testdb}
    [error] n.s.e.Cache - Unable to set localhost. This prevents creation of a GUID. Cause was: vagrant-centos65.vagrantup.com: vagrant-centos65.vagrantup.com: Name or service not known
    java.net.UnknownHostException: vagrant-centos65.vagrantup.com: vagrant-centos65.vagrantup.com: Name or service not known
            at java.net.InetAddress.getLocalHost(InetAddress.java:1473) ~[na:1.7.0_55]
            at net.sf.ehcache.Cache.<clinit>(Cache.java:214) ~[net.sf.ehcache.ehcache-core-2.6.6.jar:na]
            at net.sf.ehcache.config.ConfigurationHelper.createCache(ConfigurationHelper.java:296) [net.sf.ehcache.ehcache-core-2.6.6.jar:na]
            at net.sf.ehcache.config.ConfigurationHelper.createDefaultCache(ConfigurationHelper.java:219) [net.sf.ehcache.ehcache-core-2.6.6.jar:na]
            at net.sf.ehcache.CacheManager.configure(CacheManager.java:722) [net.sf.ehcache.ehcache-core-2.6.6.jar:na]
            at net.sf.ehcache.CacheManager.doInit(CacheManager.java:439) [net.sf.ehcache.ehcache-core-2.6.6.jar:na]
    Caused by: java.net.UnknownHostException: vagrant-centos65.vagrantup.com: Name or service not known
            at java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method) ~[na:1.7.0_55]
            at java.net.InetAddress$1.lookupAllHostAddr(InetAddress.java:901) ~[na:1.7.0_55]
            at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1293) ~[na:1.7.0_55]
            at java.net.InetAddress.getLocalHost(InetAddress.java:1469) ~[na:1.7.0_55]
            at net.sf.ehcache.Cache.<clinit>(Cache.java:214) ~[net.sf.ehcache.ehcache-core-2.6.6.jar:na]
            at net.sf.ehcache.config.ConfigurationHelper.createCache(ConfigurationHelper.java:296) [net.sf.ehcache.ehcache-core-2.6.6.jar:na]
    [info] application - Starting HikariCP connection pool...
    [info] application - database [activerecord] connected at jdbc:mysql://localhost:3306/testdb
    [info] play - Application started (Prod)
    [info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

てなことで起動した。

*1:Version若干古いけど、使い方変わらんから大目に見て

サーバに色々デプロイしようとしてハマった記録

Chef でローカル仮想マシン上に目的のサービスがデプロイできるようになったので、リモートサーバへのデプロイを実行してみた。

その時、かなり色々つまづいたのでメモ。

iptables は一時的でも切るしかない

ssh ポート以外にも何かのポートを使っているらしい。

iptables が入ってると、細かい原因もログも残らないまま「Connection refused」で悩むことになる。

しかも ssh は通る上に、knife solo cook [アカウント@][ホスト] [ホスト.json] -p [SSHポート] を指定するものだから、SSHしか使わないと勘違いしやすい。

MySQLServer は過去データ、設定も含め消してから

http://community.opscode.com/cookbooks/mysql

yum コマンドでインストールした MySQLServer は yum remove mysql-server で消してもDBファイルが残る。

しかも MySQL のアカウントは DB に保存されているので、Chef で MySQL を突っ込むと復活した挙句、root パスワードが一致しないとか、root@127.0.0.1 アカウントを変更できない(消してたりした場合)で先に進まなくなる。

しかもこの時出る ruby スタックにはSQLファイルの中身は表示されないので、気づくまでハマる。

カン所は STD エラーの方を眺めて見ると、sql ファイルを生成して食わせているのが分かるので、ssh で入ってファイルを眺めれば分かる。

SELinux の有無は確認しよう

入ってるかどうか分からずに chef を実行すると色々エラーを喰らわせられる。

環境を確認しないと痛い目を見る。

結論

色々いじっている環境に後から Chef を突っ込むのは色々ハマる。

大分長い間運用している状態なら、いい加減サーバを組み立て直した方が良いかもしれない。

後、CentOS の cookbook は案外 6.x 以上のものがあるので、5.x 系ならサーバ移動を考えるべきかもしれない。

まぁ何にしろ当座はこれで動かしてみようか。

Chef で CentOS に Playframework2.2.x アプリをデプロイする

CentOS5.10 で動作確認。多分 CentOS 6.x もそんな変わらない筈。

段階を踏もうか。

  1. Play アプリをリリース用にコンパイルする
  2. Chef を使ってJavaを突っ込む
  3. Chef でデプロイ用 Cookbook を作って配置する
  4. 起動スクリプトを書いてデプロイする
  5. knife solo cook xxxx でぶち込む

という順番。

Play アプリをリリース用にコンパイルする

これは簡単。

アプリケーションホームディレクトリで play clean dist と叩くだけ。

すると、コンパイルした上で、配布用の zip を target/universal ディレクトリに applicationName-Version.zip なファイルが出来上がる。

デプロイするときにバージョン名は地味に邪魔だけど、まぁこれは Chef 側の設定でどうにかする。

尚、この zip を解凍して bin/applicationName を叩くとサーバが起動する。

残念な事に、デーモン起動ではないので、終了までコントロールを奪ってくれる素敵仕様だ。

これはログ設定で標準出力には吐かず、ログにだけメッセージを吐くようにし、bin/applicationName &で逃れる。

PID ファイルとかは Play が勝手に吐くので気にしない。

Chef を使って Java を突っ込む

まずは chef リポジトリ(kitchen)を作る。

前提:chef + knife solo + Berkshelf インストール済み。Ubuntu ホストはこっち参照

knife solo init chef とでも叩くと、chef ディレクトリが作られて、そこに kitchen が作られる。

とりあえずは Java を突っ込もうか。

chef/Berksfile が作られてる筈なので、これを開いて編集。

source "https://api.berkshelf.com"

cookbook 'yum'
cookbook 'java'

コンソール上で下記を叩く。

cd chef
berks vendor cookbooks

すると、berkshelf が Java の cookbook を落としてくる。

次に、chef/nodes ディレクトリに、インストール設定を突っ込む。

vi nodes/default.json
{
  "java":{
    "jdk_version":"7"
  },
  "run_list":[
    "recipe[java]"
  ]
}

これで knife solo cook [user]@[server] default.json と実行すれば、OSはともかく Java7 が入る。

詳細オプションは opscode community 見てほしい。

Chef でデプロイ用 Cookbook を作って配置する

次にデプロイ用 cookbook を作ろうか。

まず、chef ディレクトリ上で下記のコマンドを叩こう。

knife cookbook create app -o site-cookbooks/

すると、chef/site-cookbooks/app ディレクトリが作られている筈。

このディレクトリが cookbook の一つの単位。

ここの、chef/site-cookbooks/app/files/default ディレクトリに、最初の手順で作成した applicationName-version.zip をデプロイしてしまおう。

で、もちろんこの「-version」の部分はバージョンアップの度に変わるので、設定ファイルに吐き出してしまう。

vi chef/site-cookbooks/app/attributes/default.rb

default['app'][:version] = "0.1-Beta"

もちろん、これは前述の nodes/default.json で下記のように書いてもいい。

{
  "java":{
    "jdk_version":"7"
  },
  "app":{
    "version":"0.1-Beta"
  },
  "run_list":[
    "recipe[java]",
    "app"
  ]
}

次に、このファイルをデプロイするコードを書く。

参考:ChefでCookbookを作成するときのちょっとしたコツ 9選 - インフラエンジニアway - Powered by HEARTBEATS

vi chef/site-cookbooks/app/recipes.default.rb

#
# Cookbook Name:: app
# Recipe:: default
#
# Copyright 2014, white-azalea.net
#
# All rights reserved - Do Not Redistribute
#

# ディレクトリは無ければ作る
directory "/opt" do
  owner "root"
  group "root"
  mode 00600
  action :create
end

directory "/opt/app" do
  owner "root"
  group "root"
  mode 00644
  action :create
end

# node プロパティに設定が入っている
fileName = "applicationName-" + node.app.version + ".zip"
deployName = "/opt/app/" + fileName

# cookbook_file で files 内のファイルをデプロイ
# あれば何もしない
cookbook_file deployName do
  source fileName
  mode "0755"
  action :create
  not_if { File.exist?(deployName) }
end

# 解凍、execute で shell 叩ける
# 解凍済みなら README があるので、その場合は何もしない
execute ("unzip " + deployName) do
  cwd    "/opt/app/"
  action :run
  not_if { File.exist?("/opt/app/applicationName-" + node.app.version + "/README") }
end

起動スクリプトを書く

これが一番難儀した。

参考:

CentOSでデーモンの起動スクリプトを書く | taichino.com

Technical Memorandum: CentOS-5 起動スクリプトのスケルトン

まずは、chef/site-cookbooks/app/templates/default/appservice.erb を下記の内容で作成した。

#!/bin/bash
# chkconfig: 345 98 20
# description: application service start script.
# processname: appservice
#
#       /etc/rc.d/init.d/appservice
#
#       Sample application service start script.
#

# Source function library.
. /etc/init.d/functions

SERVER_PATH="/opt/uptext/applicationName-<%= node['app']['version'] %>"
SERVER_BIN=$SERVER_PATH + "/bin/applicationName"
PID_PATH=$SERVER_PATH + "/RUNNING_PID"

start() {
        echo -n "Starting applicationName: "
        if [ -f $PID_PATH ]
        then
          echo "Server already running"
        else
          `$SERVER_BIN &`
        fi
        return 0
}

stop() {
        echo -n "Shutting down applicationName: "
        kill `cat $PID_PATH`
        return 0
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        start
        ;;
    *)
        echo "Usage: appservice {start|stop|restart}"
        exit 1
        ;;
esac
exit $?

基本的に erb なので分かるはず。

尚、「chkconfig」からの3行のコメントはおまじない。これを設定しないと、自動起動に登録できません。

次に、この設定をデプロイと起動、OS起動時の自動起動まで指定する。

chef/site-cookbooks/app/recipes.default.rb に下記を追記。

# appservice を起動スクリプトディレクトリに配置
template "/etc/init.d/appservice" do
  group  "root"
  owner  "root"
  mode   0744
  source "appservice.erb"
end

# appservice を起動
service "appservice" do
  action :start
end

# OS 起動時に appservice を起動するよう指定
service "appservice" do
  action :enable
end

knife solo cook xxxx でぶち込む

後は分かるはず。

CentOS5.10 の Vagrant box を作り直し

https://dl.dropboxusercontent.com/u/717203/centos5.10-final.box これ。

名称は変わってない。

キチンと halt とかその他が走るようになってる。

6/23 追記:メンテナンスだるいので削除しました