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
記事の執筆者にステッカーを贈る
有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。
さらに詳しく →Feedbacks
ログインするとコメントを投稿できます。
関連記事
- Scala 文字列の処理書式指定 object Main { def main(args: Array[String]): Unit = { println("%d + %d = %d".format(1, 1, 2)) //=> 1 + 1 = 2 } } 文字列の比較 ヒアドキュメント 他の言語でいう「ヒアドキュメント」のようなものは """ で囲うことで実現できます。 object Main ...
- 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...
- 酢豚の基本的な使い方 (sbt)sbt は Scala および Java を主な対象としたビルドツールです。Scala Build Tool の略ではありませんが、Simple Build Tool という明示的な記述も公式ドキュメントなどには見当りません。以下 sbt の基本的な使用例をまとめます。使用した sbt のバージョンは 0.13 です。 公式ドキュメント [sbt 0.13](http://www.scala-sb...
- Scala 関数のサンプルコード「デフォルト引数」および「Unit 型を返す関数」 object HelloWorld { def main(args: Array[String]): Unit = { def myPrint(myArg: String = "default_value") = println(myArg + "!") val result = myPrint() //=> defau...
- Scala 組み込みの制御構造if-else 条件分岐で知られる if-else は三項演算子のようにも使用されます。 object HelloWorld { def main(args: Array[String]): Unit = { val myVal = if (!args.isEmpty) args(0) else "default" println(myVal) } ...