Deploying an Akka Application
Deploying a Scala app is not simple. One solution is to build a
big fat jar with all the dependencies using sbt-assembly, or just
run the app with sbt in production.
If you are using Akka, there is a very good alternative to these options.
Ingredients
SBT
I am using sbt with the akka-sbt-plugin to generate all the jars I
need to upload to the server.
Akka microkernel
You can use the included akka microkernel mechanism to deploy
your app, which makes everything so much easier. You will just need to
create a Bootable class that acts as a Main class and the
microkernel will take care of the rest for you.
supervisord
Running akka in a microkernel is just like running a normal app, it
runs in the foreground, so you need to use daemonize your app. If you
don't want to mess with init.d/upstart/systemd scripts, I recommend
you use supervisord and save yourself lots of trouble and painful
debugging.
fabric and rsync
You will need to upload the files to a production server and run some commands there. I chose to use fabric because it is very lightweight, but you could use capistrano or some equivalent
Preparing your app
First you need to make your app run in an akka microkernel. This is
fairly easy and you just need to create a Bootable class like this:
class RiepeteKernel extends Bootable {
val system = ActorSystem("riepeteActorSystem")
def startup() = {
system.actorOf(RiepeteServer.props(), "RiepeteServer")
}
def shutdown() = {
system.shutdown()
}
}
You need to define both a startup and a shutdown method. In the
startup method you just need to spin up whatever actors your app
needs.
Preparing your build
Create a directory named plugins.sbt with the following content:
addSbtPlugin("com.typesafe.akka" % "akka-sbt-plugin" % "2.3-M2")
This loads the akka-sbt-plugin into sbt.
You then need to create a build definition for your project. Create a
<sbt root>/project/Build.scala with the following contents:
import akka.sbt.AkkaKernelPlugin
import akka.sbt.AkkaKernelPlugin.{Dist, distJvmOptions, outputDirectory}
import sbt.Keys._
import sbt._
import akka.sbt.AkkaKernelPlugin.distMainClass
object RiepeteKernelBuild extends Build {
val Organization = "io.simao"
val Version = "0.0.1"
val ScalaVersion = "2.11.1"
lazy val RiepeteKernel = Project(
id = "riepete-kernel",
base = file("."),
settings = defaultSettings ++ AkkaKernelPlugin.distSettings ++ Seq(
libraryDependencies ++= Dependencies.all,
distJvmOptions in Dist := "-Xms256M -Xmx1024M",
outputDirectory in Dist := file("target/riepete-dist"),
// Use the name of the Bootable class you created earlier
distMainClass in Dist := "akka.kernel.Main io.simao.riepete.server.RiepeteKernel"
)
)
lazy val buildSettings = Defaults.defaultSettings ++ Seq(
organization := Organization,
version := Version,
scalaVersion := ScalaVersion,
crossPaths := false,
organizationName := "simao.io",
organizationHomepage := Some(url("https://simao.io"))
)
lazy val defaultSettings = buildSettings ++ Seq(
// compile options
scalacOptions ++= Seq("-encoding", "UTF-8", "-deprecation", "-unchecked"),
javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
)
}
object Dependencies {
object Versions {
val Akka = "2.3.3"
}
val all = List(
"com.typesafe.akka" %% "akka-kernel" % Versions.Akka,
"com.typesafe.akka" %% "akka-slf4j" % Versions.Akka,
"com.typesafe.akka" %% "akka-actor" % Versions.Akka,
"io.argonaut" %% "argonaut" % "6.0.4"
)
}
Of course you'll need to adapt the contents to match your app.
Now running sbt dist will create a riepete-dist directory in <sbt
root>/target containing all the dependencies and a bash script that
can be used to run the app. You can test your application runs
executing bin/start.
Create a supervisord config file
You will now need to configure supervisord so it knows how to run
your app.
Supervisord needs to run the java process itself to work properly,
so we won't be using the bin/start script generated by sbt dist.
Create a file similar to this in your supervisord config directory,
usually /etc/supervisord.d:
[program:riepete]
command=java -Xms256M -Xmx1024M -cp /opt/riepete/config:/opt/riepete/lib/* -Dakka.home=/opt/riepete akka.kernel.Main io.simao.riepete.server.RiepeteKernel
user=riepete
redirect_stderr=true
directory=/opt/riepete
You will again need to change these values to match your app name and the directory where it is deployed.
Writing a deploy script
To deploy a new version of the app we just need to rsync our
target/riepete-dist directory into /opt/riepete in our production
server.
from fabric.api import local, run, cd, put, env, path
from fabric.contrib.project import rsync_project
env.use_ssh_config = True
def sync_bins():
rsync_project("/opt/riepete", "./target/riepete-dist/", delete=True,
exclude=[".git", "log", "config/application.conf", "config/riepete.conf"],
extra_opts="--exclude-from .gitignore")
def compile_project():
local("sbt dist")
def restart():
run('sudo supervisorctl restart riepete')
def deploy():
target_dir = '/opt/riepete'
with cd(target_dir):
compile_project()
sync_bins()
restart()
Deploying
After installing supervisord on the production server and creating
the app directory, you can deploy the app using:
fab -H <your-hostname> deploy
You can login into the production server and run supervisorctl tail
-f <app name> to check the standard output.
comments powered by Disqus