2016/12/26

Scala Play Application for Production


開發時 activator run 啟動的是 DEV mode,會一直檢查程式有沒有修改過,如果有修改,就會自動編譯並 reload,但這個功能會增加 overhead,在 PROD mode 就不需要了。另外 PROD 環境產生的 error page,也不需要像 DEV mode 一樣有太多錯誤的細節。


Production 相關設定


Play 需要一個 secret key 用來對 session cookie 簽章,還有內建的加密 utilities。


application.conf


play.crypto.secret = "newsecret"

該密碼預設為 "changeme",如果沒有修改這個密碼,在 PROD mode 就會發生錯誤,但在 DEV mode 就沒有檢查。




如果想要在 DEV 跟 PROD mode 使用不同的設定檔,可以在 conf 目錄中增加一個 prod-application.conf 新的設定檔,在啟動 PROD mode 時,加上設定檔的附加參數。


準備一個新的 prod-application.conf,內容為


include "application.conf"

play.crypto.secret = "newsecret"

另外同時準備一個 prod-logback.xml,新的 logback 設定檔,將 slick db sql statement 的 log 隱藏掉。




修改 build.sbt ,增加 JavaServerAppPackaging plugin,以及一些 production package 的設定


lazy val root = (project in file(".")).enablePlugins(PlayScala, JavaServerAppPackaging)

// production settings
maintainer := "charley <charley@maxkit.com.tw>"
packageSummary := "Play Slick Sample"
packageDescription := """A fun package description of our software,
  with multiple lines."""

// RPM SETTINGS
rpmVendor := "maxkit"
//rpmLicense := Some("BSD")
rpmChangelogFile := Some("changelog.txt")



Play 預設是使用 Netty,Netty 提供了一些參數可以調整,我們可以在 application.conf 中,調整這些參數。


play.server {

  # The server provider class name
  provider = "play.core.server.NettyServerProvider"

  netty {

    # The number of event loop threads. 0 means let Netty decide, which by default will select 2 times the number of
    # available processors.
    eventLoopThreads = 0

    # The maximum length of the initial line. This effectively restricts the maximum length of a URL that the server will
    # accept, the initial line consists of the method (3-7 characters), the URL, and the HTTP version (8 characters),
    # including typical whitespace, the maximum URL length will be this number - 18.
    maxInitialLineLength = 4096

    # The maximum length of the HTTP headers. The most common effect of this is a restriction in cookie length, including
    # number of cookies and size of cookie values.
    maxHeaderSize = 8192

    # The maximum length of body bytes that Netty will read into memory at a time.
    # This is used in many ways.  Note that this setting has no relation to HTTP chunked transfer encoding - Netty will
    # read "chunks", that is, byte buffers worth of content at a time and pass it to Play, regardless of whether the body
    # is using HTTP chunked transfer encoding.  A single HTTP chunk could span multiple Netty chunks if it exceeds this.
    # A body that is not HTTP chunked will span multiple Netty chunks if it exceeds this or if no content length is
    # specified. This only controls the maximum length of the Netty chunk byte buffers.
    maxChunkSize = 8192

    # Whether the Netty wire should be logged
    log.wire = false

    # The transport to use, either jdk or native.
    # Native socket transport has higher performance and produces less garbage but are only available on linux 
    transport = "jdk"

    # Netty options. Possible keys here are defined by:
    #
    # http://netty.io/4.0/api/io/netty/channel/ChannelOption.html
    #
    # Options that pertain to the listening server socket are defined at the top level, options for the sockets associated
    # with received client connections are prefixed with child.*
    option {

      # Set the size of the backlog of TCP connections.  The default and exact meaning of this parameter is JDK specific.
      # SO_BACKLOG = 100

      child {
        # Set whether connections should use TCP keep alive
        # SO_KEEPALIVE = false

        # Set whether the TCP no delay flag is set
        # TCP_NODELAY = false
      }

    }

  }
}

activator 常用指令


ex: activator clean


  • clean: 刪除 target 目錄中的編譯結果
  • update: 下載的 libraries
  • compile: 編譯專案,產生到 target 目錄中
  • eclipse: 產生 eclipse 專案 project file,使用前要先執行 compile
  • run: 啟動 project,也可以用 activator "run 8888" 啟動到 TCP Port 8888
  • publish: 產生專案的 jar,發佈到 build.sbt 中設定的 repository
  • publish-local: 發佈到本機的 repository/local 目錄
  • stage: 產生 production 專案的 script,通常放在 target/universal/stage 目錄中

產生 stage project


activator stage

完整地產生新的 stage project 要執行


activator clean update compile stage

產生 production project tar.gz file


activator universal:packageZipTarball

PROD mode


以下兩種方式可以啟動 Production Mode


-Dconfig.resource 的部分,會在 class path 尋找設定檔
-Dconfig.file 則是依照 file system 尋找設定檔


-J-Xms512M -J-Xmx1G -J-server 這幾個是啟動 application 的 JVM 參數


target/universal/stage/bin/test6 -Dconfig.resource=prod-application.conf -Dlogger.resource=prod-logback.xml -Dhttp.port=9000 -J-Xms512M -J-Xmx1G -J-server

target/universal/stage/bin/test6 -Dconfig.file=conf/prod-application.conf -Dlogger.file=conf/prod-logback.xml -Dhttp.port=9000 -J-Xms512M -J-Xmx1G -J-server

啟動後,會在 target/universal/stage/RUNNING_PID 產生 process ID 的檔案


如果希望在背景執行 application,就要搭配 nohup 啟動 server


nohup target/universal/stage/bin/test6 -Dconfig.resource=prod-application.conf -Dlogger.resource=prod-logback.xml -Dhttp.port=9000 -J-Xms512M -J-Xmx1G -J-server < /dev/null > /dev/null 2>&1 &

可以用 kill 的方式關掉背景執行的 server


kill -SIGTERM `cat target/universal/stage/RUNNING_PID`

kill -SIGKILL `cat target/universal/stage/RUNNING_PID`

start.sh stop.sh scripts


為了方便,我們把上面的啟動方式,做成一個 script,放在 conf 目錄中,這樣就會在 activator stage 時,同時複製到 target/universal/stage


conf/start.sh


#!/bin/bash

#export JAVA_OPTS=""
#export JAVA_OPTS="$JAVA_OPTS -Xms512M -Xmx1G -server"

console() {
    target/universal/stage/bin/test6 -Dconfig.resource=prod-application.conf -Dlogger.resource=prod-logback.xml -Dhttp.port=9000 -J-Xms512M -J-Xmx1G -J-server
}

server() {
    nohup target/universal/stage/bin/test6 -Dconfig.resource=prod-application.conf -Dlogger.resource=prod-logback.xml -Dhttp.port=9000 -J-Xms512M -J-Xmx1G -J-server < /dev/null > /dev/null 2>&1 &
}

case "$1" in

console)
    console
;;
server)
    server
;;
*)
    echo "Usage: $0 {console|server}"
;;
esac

exit 0

conf/stop.sh


#!/bin/bash

kill -SIGTERM `cat target/universal/stage/RUNNING_PID`

# kill -SIGKILL `cat target/universal/stage/RUNNING_PID`

然後就能用這樣的方式,啟動或停止 server


target/universal/stage/conf/start.sh server
target/universal/stage/conf/stop.sh

SSL


如果要讓 server 可以支援 HTTPS,我們需要做以下設定


Property Purpose Default Value
https.port https port number
https.keyStore 儲存 private key 及 certificate 的檔案路徑
https.keyStoreType key store type JavaKeyStore(JKS)
https.keyStorePassword password blank password
https.keyStoreAlgorithm key store algorithm platform's default algorithm

也可以自己實作 SSLEngine,參考 Configuring HTTPS,製作新的 class CustomSSLEngineProvider(appProvider: ApplicationProvider) extends SSLEngineProvider 。


References


Mastering Play Framework for Scala


The Application Secret


第三章 : Play建置部署與常用指令


overriding-configuration


Start and stop a Scala application in production