Akka HTTP Scala アプリケーションのデーモン化
[最終更新] (2019/06/03 00:43:32)
最近の投稿
注目の記事

概要

こちらで基本的な使用方法をまとめた Akka HTTP Scala アプリケーションをデーモン化します。

参考サイト

インストール

前提

  • Linux マシン
  • fat JAR を生成できるビルドツール (ここでは sbt-assembly を使用します)

Java のインストール

yum または Oracle のサイトからダウンロードしてインストールします。Akka の動作には Java 8 以降が必要です。

sudo yum install java-1.8.0-openjdk-devel

JCVS のインストール

こちらから最新の commons-daemon-x.x.x-src.tar.gz をダウンロードして解凍します。

tar zxvf commons-daemon-1.0.15-src.tar.gz

公式サイトの手順にしたがってビルドします。JCVS はサブディレクトリに格納されています。

The sources are located in the src/native/unix subdirectory.

cd commons-daemon-1.0.15-src/src/native/unix/

必要なソフトウェアをインストールしておきます。

sudo yum install autoconf
sudo yum install gcc-c++

ビルドします。

sh support/buildconf.sh
./configure --with-java=/usr/lib/jvm/java-1.8.0
make
sudo mv jsvc /usr/local/bin/

動作確認します。

$ jsvc -help | head
Usage: jsvc [-options] class [args...]

Where options include:

    -help | --help | -?
        show this help page (implies -nodetach)
    -jvm <JVM name>
        use a specific Java Virtual Machine. Available JVMs:
            'server'
    -client

sbt の設定

最新のバージョンは The Central Repository で検索してください。

libraryDependencies += "commons-daemon" % "commons-daemon" % "1.0.15"

Akka HTTP Scala アプリケーションを用意

build.sbt

lazy val root = (project in file(".")).
  settings(
    name := "myapp",
    version := "1.0",
    scalaVersion := "2.11.7",
    mainClass in assembly := Some("myapp.Main"),
    retrieveManaged := true,
    libraryDependencies ++= Seq(
      "com.typesafe.akka" %% "akka-http-core" % "2.4.2",
      "com.typesafe.akka" %% "akka-http-experimental" % "2.4.2",
      "ch.qos.logback" % "logback-classic" % "1.1.3",
      "org.slf4j" % "slf4j-api" % "1.7.12",
      "commons-daemon" % "commons-daemon" % "1.0.15"
    )
  )

src/main/resources/logback.xml

<configuration>
  <property name="LOG_DIR" value="./log" />
  <!-- <property name="LOG_DIR" value="/var/log/myapp" /> -->
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_DIR}/myapp.log</file>
    <append>true</append>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- daily rollover  -->
      <fileNamePattern>${LOG_DIR}/myapp.%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- keep 90 days' worth of history -->
      <maxHistory>90</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
    </encoder>
  </appender>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>
  <root level="info">
    <appender-ref ref="FILE" />
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

src/main/scala/Main.scala

package myapp

import org.apache.commons.daemon._
import org.slf4j.LoggerFactory

trait ApplicationLifecycle {
  def start(): Unit
  def stop(): Unit
}

class ApplicationDaemon extends Daemon {

  def init(daemonContext: DaemonContext): Unit = {}

  val app: ApplicationLifecycle = new Application
  def start() = app.start()
  def stop() = app.stop()
  def destroy() = app.stop()
}

object Main {
  def main(args: Array[String]): Unit = {   
    val logger = LoggerFactory.getLogger("Main")
    val app = new ApplicationDaemon
    app.start()
    logger.info("Press RETURN to stop...")
    scala.io.StdIn.readLine()
    app.stop()
  }
}

src/main/scala/Application.scala

package myapp

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.Http
import scala.concurrent.Future
import org.slf4j.LoggerFactory

class Application extends ApplicationLifecycle {

  val logger = LoggerFactory.getLogger("Application")
  val applicationName = "myapp"

  implicit val system = ActorSystem(s"$applicationName-system")
  implicit val materializer = ActorMaterializer()
  implicit val ec = system.dispatcher

  var started: Boolean = false
  var bindingFuture: Future[Http.ServerBinding] = null

  def start(): Unit = {
    logger.info(s"Starting $applicationName Service")
    if (!started) {
      val route = path("") {
        get {
          logger.info("ok")
          complete("ok")
        }
      }
      bindingFuture = Http().bindAndHandle(route, "0.0.0.0", 8080)
      logger.info("Server online at http://127.0.0.1:8080/")
      started = true
    }
  }

  def stop(): Unit = {
    logger.info(s"Stopping $applicationName Service")
    if (started) {
      bindingFuture.flatMap(_.unbind()).onComplete(_ => system.terminate())
      started = false
    }
  }
}

ビルド

$ sbt assembly

実行例

$ java -jar target/scala-2.11/myapp-assembly-1.0.jar
Starting myapp Service
Server online at http://127.0.0.1:8080/

$ curl http://127.0.0.1:8080
ok

$ cat log/myapp.log 
2016-03-17 01:14:00,511 INFO [run-main-0] Application [Application.scala:24] Starting myapp Service
2016-03-17 01:14:01,652 INFO [run-main-0] Application [Application.scala:34] Server online at http://127.0.0.1:8080/
2016-03-17 01:14:02,909 INFO [myapp-system-akka.actor.default-dispatcher-5] Application [Application.scala:29] ok

init.d スクリプトの作成

この続きが気になる方は

Akka HTTP Scala アプリケーションのデーモン化

残り文字数は全体の約 23 %
tybot
100 円
関連ページ
    概要 こちらに記載した Akka アクターを用いて実装された汎用 HTTP フレームワークです。Spray の後継です。コアモジュールである akka-http-core は 2016/2/17 に experimental が外れました。akka-http などのいくつかのサブモジュールは 2016/3/1 現在 experimental のままですが、基本的な機能に関しては大きな仕様変更はな