モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

工作HardwareHub ロゴ画像 (Laptop端末利用時)
工作HardwareHub ロゴ画像 (Mobile端末利用時)

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

モーダルを閉じる

ステッカーを選択してください

モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2016/03/16
最終更新最終更新
2021/10/07
記事区分記事区分
一般公開

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 スクリプトの作成

事前準備

sudo mkdir /var/log/myapp
sudo mkdir /var/run/myapp
sudo mkdir /usr/local/myapp
sudo cp sbt-dir/src/main/resources/logback.xml /usr/local/myapp/
sudo sed -e 's/.\/log/\/var\/log\/myapp/' /usr/local/myapp/logback.xml
sudo cp sbt-dir/target/scala-2.11/myapp-assembly-1.0.jar /usr/local/myapp/

/etc/init.d/myapp

詳細は OS によって異なります。以下では CentOS 6 の例を記載します。

#!/bin/sh
#
# myapp - sample application
#
# chkconfig: 345 55 45
# description: sample daemon.
#

. /etc/rc.d/init.d/functions

if [ -z "$JAVA_HOME" ]; then
    JAVA_HOME=/usr/lib/jvm/java-1.8.0
fi
JAVA_OPTS="-Xms512m -Xmx1024m"

APP=myapp
DAEMON_USER=root
RETVAL=0

PID=/var/run/$APP/$APP.pid
OUT_LOG=/var/log/$APP/${APP}_out.log
ERR_LOG=/var/log/$APP/${APP}_err.log

APP_HOME=/usr/local/$APP
APP_LOG_CONFIG=$APP_HOME/logback.xml
APP_CLASSPATH=$APP_HOME/myapp-assembly-1.0.jar
APP_CLASS=myapp.ApplicationDaemon

if [ -n "$APP_LOG_CONFIG" ]; then
    JAVA_OPTS="-Dlogback.configurationFile=${APP_LOG_CONFIG} ${JAVA_OPTS}"
fi
JSVC=/usr/local/bin/jsvc

DAEMON_ARGS="-home ${JAVA_HOME} ${JAVA_OPTS} -pidfile ${PID} -user ${DAEMON_USER}"
DAEMON_ARGS="$DAEMON_ARGS -outfile ${OUT_LOG} -errfile ${ERR_LOG}"
DAEMON_ARGS="$DAEMON_ARGS -cp ${APP_CLASSPATH} ${APP_CLASS}"

start () {
    echo -n $"Starting $APP: "
    chown $DAEMON_USER /var/run/$APP
    chown $DAEMON_USER /var/log/$APP
    $JSVC ${DAEMON_ARGS}
    RETVAL=$?
    if [ $RETVAL -eq 0 ]; then
        success
    else
        failure
    fi
    echo
}

stop () {
    echo -n $"Stopping $APP: "
    $JSVC -stop ${DAEMON_ARGS}
    RETVAL=$?
    if [ $RETVAL -eq 0 ]; then
        success
    else
        failure
    fi
    echo
}

restart () {
    stop
    start
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status -p $PID $APP
        ;;
    restart)
        stop
        start
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart}"
        RETVAL=1
        ;;
esac

exit $RETVAL
0
詳細設定を開く/閉じる
アカウント プロフィール画像 (本文下)

Scalaはいいぞ

記事の執筆者にステッカーを贈る

有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

さらに詳しく →
ステッカーを贈る コンセプト画像

Feedbacks

Feedbacks コンセプト画像

    ログインするとコメントを投稿できます。

    関連記事

    • Scala 文字列の処理
      書式指定 object Main { def main(args: Array[String]): Unit = { println("%d + %d = %d".format(1, 1, 2)) //=> 1 + 1 = 2 } } 文字列の比較 ヒアドキュメント 他の言語でいう「ヒアドキュメント」のようなものは """ で囲うことで実現できます。 object Main ...
      したくんしたくん6/18/2018に更新
      いいねアイコン画像0
    • Scala 日付に関する処理
      Date クラスを文字列にフォーマット import java.util.Date object Main { def main(args: Array[String]): Unit = { // format は Date に限らない文字列用の機能です。 println("%d-%d-%d" format (1, 1, 1)) //=> 1-1-1 printl...
      したくんしたくん6/5/2018に更新
      いいねアイコン画像0
    • 酢豚の基本的な使い方 (sbt)
      sbt は Scala および Java を主な対象としたビルドツールです。Scala Build Tool の略ではありませんが、Simple Build Tool という明示的な記述も公式ドキュメントなどには見当りません。以下 sbt の基本的な使用例をまとめます。使用した sbt のバージョンは 0.13 です。 公式ドキュメント [sbt 0.13](http://www.scala-sb...
      ねこねこ6/30/2018に更新
      いいねアイコン画像0
    • Scala 関数のサンプルコード
      「デフォルト引数」および「Unit 型を返す関数」 object HelloWorld { def main(args: Array[String]): Unit = { def myPrint(myArg: String = "default_value") = println(myArg + "!") val result = myPrint() //=> defau...
      したくんしたくん6/26/2018に更新
      いいねアイコン画像0
    • Scala 組み込みの制御構造
      if-else 条件分岐で知られる if-else は三項演算子のようにも使用されます。 object HelloWorld { def main(args: Array[String]): Unit = { val myVal = if (!args.isEmpty) args(0) else "default" println(myVal) } ...
      したくんしたくん10/7/2021に更新
      いいねアイコン画像0