Build container, start, and run test. First image build will take some time as Docker downloads the base Ubuntu image. Subsequent builds will use much quicker:
$ make clean image run && sleep 10 && make test
docker kill rapache && docker rm rapache
rapache
rapache
docker build -t myrepo/predict-service:latest .
Sending build context to Docker daemon 365.6 kB
Step 1 : FROM ubuntu
---> b549a9959a66
....
Successfully built eb7446fb1c90
docker run -d --name rapache -p 80:80 myrepo/predict-service:latest
c69975971c4df33272ffac7110af190f52efaad397751779ed7580e57c3d2e47
curl --data 'x1=1&x2=2&x3=3' http://192.168.99.100/predict/test/1
{"score":{"1":12.9322980080557},"status":"Ok","error":"","name":"test","version":1,"date":null}
A standalone Rook service implementation is provided in src/predictRook.R and can be run without Apache httpd as follows:
- Start webservice:
$ src/predictRook.R
Loading required package: Rook
Loading required package: rjson
Loading required package: futile.logger
INFO [2016-04-08 11:50:17] [45109] Starting prediction web service
INFO [2016-04-08 11:50:17] Loading models from directory ../model
INFO [2016-04-08 11:50:17] [45109] Loaded model test__1 from file ../model/test__1.rds
INFO [2016-04-08 11:50:17] Starting web service
starting httpd help server ... done
DEBUG [2016-04-08 11:50:20] Request for model test, version 1
DEBUG [2016-04-08 11:50:20] Received request (http://127.0.0.1:8000/custom/predict/test/1?) with POST parameters: x1,x2,x3
DEBUG [2016-04-08 11:50:20] Using model test, version 1
DEBUG [2016-04-08 11:50:20] [45109] Using prediction features: x1,x2,x3
DEBUG [2016-04-08 11:50:20] Request (http://127.0.0.1:8000/custom/predict/test/1?) returns: {"score":{"1":12.9322980080557},"status":"Ok","error":"","name":"test","version":1}
- Test the service:
$ curl --data 'x1=1&x2=2&x3=3' http://127.0.0.1:8000/custom/predict/test/1
{"score":{"1":12.9322980080557},"status":"Ok","error":"","name":"test","version":1}
##Adding a New Model An R model must be prepared before it can be deployed within the fraudscore service. The model object must implement a predict() function as well as have attributes name and version which will be used to identify the model via the webservice REST parameters. The saved RDS model file will be read from the model directory when the service initializes:
> m <- lm(y ~ x1 + x2 + x3)
> predict(m, newdata=data.frame(x1=1, x2=2, x3=3))
1
12.93
> m$name <- 'test'
> m$version <- 1
> saveRDS(m, file="test__1.rds")
The fraudscore web service is implementated as a Rook service and deployed under Apache httpd via the RApache (mod_R) module. Below are the steps for deploying the service on Ubuntu Server:
- Install apache2 and rapache:
$ sudo apt-get install apache2
$ sudo add-apt-repository ppa:opencpu/rapache
$ sudo apt-get update
$ sudo apt-get install libapache2-mod-r-base
-
Install R modules Rook, and dependencies.
-
Configure httpd by linking
rapache.conf
into the configuration directory:
$ sudo ln -s rapache.conf /etc/apache2/sites/enabled
-
Edit settings in
rapache.conf
to point to the socure implementation files and set the number of required workers (StartServers
andMaxRequestWorkers
). The configuration contains settings for the mpm_prefork module necessary to maintain single threaded access to each R model. -
Restart httpd:
$ sudo service apache2 restart
-
Test rapache configuration here
-
Test fraudscore service via
curl
:
$ curl --data 'x1=1&x2=2&x3=3' http://127.0.0.1:8000/custom/predict/test/1
{"score":{"1":12.9322980080557},"status":"Ok","error":"","name":"test","version":1}
##Debugging The service writes to various log files that can be used to asses the health of the system and diagnose issues:
-
/var/log/apache2/access.log
: logs incoming requests to the webservice. The trailing field is the time taken to service the request in microseconds. -
/var/log/apache2/error.log
: logs Apache and RApache errors messages if there are problems starting the Apache server or unhandled errors in the R service implementation. -
/tmp/rapache.log
: log statements generated by the R service implementation itself. On startup, Apache will fork StartServers child processes, each will create a copy of the R interpreter and load a copy of the model. Each log statement is prefixed with the pid of the child process. This behaviour can be seen in the snippet below:INFO [2014-06-25 20:40:15] [13291] Starting fraudscore web service (socure.base=/opt/webservice) INFO [2014-06-25 20:40:15] [13292] Starting fraudscore web service (socure.base=/opt/webservice) INFO [2014-06-25 20:40:48] [13291] Loaded model from file /opt/webservice/socure_model_predictor.rda INFO [2014-06-25 20:40:48] [13292] Loaded model from file /opt/webservice/socure_model_predictor.rda
A Dockerfile can be used to create a fraudscore server image.
$ docker build -t socure/fraudscore .
....
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
socure/fraudscore latest e2cad5f170f8 13 seconds ago 627.1 MB
$ docker run -d --name rapache -p 80:80 socure/fraudscore
...
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
eceaac8b4afa socure/fraudscore "/usr/sbin/apachectl 23 minutes ago Up 23 minutes 0.0.0.0:80->80/tcp rapache
$ docker exec -i -t rapache /bin/bash
root@eceaac8b4afa:/# export TERM=xterm
root@eceaac8b4afa:/# tail /tmp/rapache.log
INFO [2015-06-19 22:17:08] [22][xxx-yyy-zzz] Predicting fraudscore
INFO [2015-06-19 22:17:08] [22][xxx-yyy-zzz] Sending response: {"fraudscore":0.101921292367321,"status":"Ok","error":"","name":"generic","version":"20150527","date":"12:48:06 05/29/15 2015"}
INFO [2015-06-19 22:17:08] [23] Request for model generic, version 20150527
INFO [2015-06-19 22:17:08] [23][xxx-yyy-zzz] Using model generic, version 20150527
INFO [2015-06-19 22:17:08] [23][xxx-yyy-zzz] Predicting fraudscore
INFO [2015-06-19 22:17:08] [23][xxx-yyy-zzz] Sending response: {"fraudscore":0.101921292367321,"status":"Ok","error":"","name":"generic","version":"20150527","date":"12:48:06 05/29/15 2015"}
INFO [2015-06-19 22:17:08] [13] Request for model generic, version 20150527
INFO [2015-06-19 22:17:08] [13][xxx-yyy-zzz] Using model generic, version 20150527
INFO [2015-06-19 22:17:08] [13][xxx-yyy-zzz] Predicting fraudscore
INFO [2015-06-19 22:17:08] [13][xxx-yyy-zzz] Sending response: {"fraudscore":0.101921292367321,"status":"Ok","error":"","name":"generic","version":"20150527","date":"12:48:06 05/29/15 2015"}
root@eceaac8b4afa:/#
$ docker kill rapache
rapache
$ docker rm rapache
rapache
The fraudscore service can be deployed as a Docker application under
AWS Elastic Beanstalk. First an application archive is created using
the Makefile
. The resulting archive can be uploaded via the AWS
Elastic Beanstalk and deployed in the same way as a Java WAR file.
$ git pull
Already up-to-date.
$ make clean fraudscore.zip
rm fraudscore.zip
zip fraudscore.zip Dockerfile fraudscore.R rapache.conf RSourceOnStartup.R fraudscore.logrotate model/generic__1.rds model/generic__20150527.rds model/kabbage__20140711.rds model/transferwise__20150507.rds
adding: Dockerfile (deflated 62%)
adding: fraudscore.R (deflated 69%)
adding: rapache.conf (deflated 59%)
adding: RSourceOnStartup.R (deflated 48%)
adding: fraudscore.logrotate (deflated 40%)
adding: model/generic__1.rds (deflated 1%)
adding: model/generic__20150527.rds (deflated 1%)
adding: model/kabbage__20140711.rds (deflated 2%)
adding: model/transferwise__20150507.rds (deflated 0%)
$ ssh ec2-54-92-162-114.compute-1.amazonaws.com
_____ _ _ _ ____ _ _ _
| ____| | __ _ ___| |_(_) ___| __ ) ___ __ _ _ __ ___| |_ __ _| | | __
| _| | |/ _` / __| __| |/ __| _ \ / _ \/ _` | '_ \/ __| __/ _` | | |/ /
| |___| | (_| \__ \ |_| | (__| |_) | __/ (_| | | | \__ \ || (_| | | <
|_____|_|\__,_|___/\__|_|\___|____/ \___|\__,_|_| |_|___/\__\__,_|_|_|\_\
Amazon Linux AMI
[ec2-user@ip-10-35-205-170 ~]$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e3e0c17a520a aws_beanstalk/staging-app:latest "/usr/sbin/apachectl 5 hours ago Up 5 hours 80/tcp jovial_lumiere
[ec2-user@ip-10-35-205-170 ~]$ sudo docker exec -it e3e0c17a520a /bin/bash
root@e3e0c17a520a:/# tail /tmp/rapache.log
INFO [2015-06-22 18:24:11] [28] Loaded model transferwise__20150507 from file /opt/webservice/model/transferwise__20150507.rds
INFO [2015-06-22 18:24:11] [28] Starting web service
INFO [2015-06-22 18:24:12] [29] Loaded model transferwise__20150507 from file /opt/webservice/model/transferwise__20150507.rds
INFO [2015-06-22 18:24:12] [29] Starting web service
INFO [2015-06-22 18:24:12] [23] Loaded model transferwise__20150507 from file /opt/webservice/model/transferwise__20150507.rds
INFO [2015-06-22 18:24:12] [23] Starting web service
INFO [2015-06-22 18:24:12] [21] Loaded model transferwise__20150507 from file /opt/webservice/model/transferwise__20150507.rds
INFO [2015-06-22 18:24:12] [21] Starting web service
INFO [2015-06-22 18:24:13] [17] Loaded model transferwise__20150507 from file /opt/webservice/model/transferwise__20150507.rds
INFO [2015-06-22 18:24:14] [17] Starting web service
TODO: update to DockerMachine
Docker can be used on OSX by using Boot2Docker, a purpose-built virtual machine and associated utility scripts.
- Create a new Boot2Docker VM. This only needs to be done once:
$ boot2docker init
- Start the Boot2Docker VM:
$ boot2docker start
Waiting for VM and Docker daemon to start...
.o
Started.
Writing /Users/jkamerman/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/jkamerman/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/jkamerman/.boot2docker/certs/boot2docker-vm/key.pem
To connect the Docker client to the Docker daemon, please set:
export DOCKER_HOST=tcp://192.168.59.103:2376
export DOCKER_CERT_PATH=/Users/jkamerman/.boot2docker/certs/boot2docker-vm
export DOCKER_TLS_VERIFY=1
- Display the environment variables for the Docker client:
$ boot2docker shellinit
Writing /Users/jkamerman/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/jkamerman/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/jkamerman/.boot2docker/certs/boot2docker-vm/key.pem
export DOCKER_CERT_PATH=/Users/jkamerman/.boot2docker/certs/boot2docker-vm
export DOCKER_TLS_VERIFY=1
export DOCKER_HOST=tcp://192.168.59.103:2376
- To set the environment variables in your shell do the following:
$ eval "$(boot2docker shellinit)"
Writing /Users/jkamerman/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/jkamerman/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/jkamerman/.boot2docker/certs/boot2docker-vm/key.pem
- Run the hello-world container to verify your setup.
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from hello-world
a8219747be10: Pull complete
91c95931e552: Already exists
hello-world:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:aa03e5d0d5553b4c3473e89c8619cf79df368babd18681cf5daeb82aab55838d
Status: Downloaded newer image for hello-world:latest
Hello from Docker.
This message shows that your installation appears to be working correctly.
- Use the IP address of the Boot2Docker VM to test the application:
$ echo $DOCKER_HOST
tcp://192.168.59.103:2376
$ curl --data $(cat test/test.post) http://192.168.59.103/fraudscore/generic/20150527
{"fraudscore":0.101921292367321,"status":"Ok","error":"","name":"generic","version":"20150527","date":"12:48:06 05/29/15 2015"}