Translator’s note: This is the 20th article of the series “Cloud Foundry 100-day Challenge selection”. “#091” in the title means that it is 91st (published on November 2, 2015) in the original Japanese series.
Original Author: Hiroaki UKAJI (GitHub)
The 91st topic of “Cloud Foundry 100-Day Challenge” is shooter.io, a multi-player shooter game.
This is an application that runs with coordinated servers, between the shooter-server, written in Go, and an HTTP server shooter-html5.
Because this application uses WebSocket, please be noted that the application may not function properly if your environment is behind a proxy not configured to work with WebSocket.
Basic Information
- Official site
http://shooter.io/ - Source code
https://github.com/xiam/shooter-server
https://github.com/xiam/shooter-html5 - Other reference information
shooter.io – a multi-player shooter that is fun to play with friends (Translator’s note: In Japanese)
The summary of procedures is as follows:
- 1) Deploying shooter-server
- 1.1) Retrieving Source Code
- 1.2) Creating Service
- 1.3) Preparation
- 1.4) Deploying onto Cloud Foundry
- 2) Deploying shooter-html5
- 2.1) Retrieving Source Code
- 2.2) Preparation
- 2.3) Deploying onto Cloud Foundry
- 3) Checking Application Behavior
1. Deploying shooter-server
1.1. Retrieving Source Code
$ git clone https://github.com/xiam/shooter-server
$ cd shooter-server/
shooter-server$ ls
LICENSE Makefile README.md shooter.png src
shooter-server$ ls src/
agent.go control.go entity fn.go main.go player.go scores.go ship
bullet diff fire.go item Makefile powerup.go sector.go
This is an application written in Go.
According to the README, the subsequent procedures to start the server up are:
make
cd src
go get -d
make
MONGO_HOST="[MONGODB_HOST]" ./shooter-server -listen [LISTEN_IP]:[LISTEN_PORT]
MONGODB_HOST
is the IP address where MongoDB is located; the default value is “127.0.0.1”. (reference)LISTEN_IP
:LISTEN_PORT
are the IP address and port that listen to the HTTP server. The default value is “127.0.0.1:3223”. (reference)
We assume that it is not possible to run make
over two directories and simultaneously run go get
at the appropriate timing, etc., when using go-buildpack.
Of course this may be achieved if you have sound understandings of the Go language and the specifications of go-buildpack, but we unfortunately could not come up with such a procedure in the time available to complete this article.
However, fortunately, there is an advantage in Go that allows us to run, independent of outside libraries etc., as long as we build one executable binary.
As such, we will leverage this characteristic, and first build Go binary locally, and then run it on Cloud Foundry later.
We will use the binary-buildpack as our buildpack.
1.2. Creating Service
The shooter-server apparently uses MongoDB to store game scores etc., so we will use the MongoDB service that we created in this article.
shooter-server$ cf marketplace
Getting services from marketplace in org ukaji / space default as ukaji...
OK
service plans description
Mongo DB Default Mongo Plan* A simple mongo implementation
PostgreSQL Basic PostgreSQL Plan* PostgreSQL on shared instance.
p-mysql 100mb, 1gb MySQL databases on demand
p-redis shared-vm, dedicated-vm Redis service to provide a key-value store
* These service plans have an associated cost. Creating a service instance will incur this cost.
TIP: Use 'cf marketplace -s SERVICE' to view descriptions of individual plans of a given service.
shooter-server$ cf create-service "Mongo DB" "Default Mongo Plan" shooter-mongo
Creating service instance shooter-mongo in org ukaji / space default as ukaji...
OK
Let’s go ahead to complete the binding procedure.
shooter-server$ cf push shooter-server --no-start
..
shooter-server$ cf bind-service shooter-server shooter-mongo
Binding service shooter-mongo to app shooter-server in org ukaji / space default as ukaji...
OK
1.3. Preparation
Making Modifications Related to MongoDB Connection
We will make modifications to the part that relates to the connection to MongoDB created in section 1.2.
When we take a look at scores.go
, we find that this application runs the connection solely based on the database’s host name and database name.
shooter-server$ cat src/scores.go
..
settings = db.Settings{
Host: host,
Database: defaultDatabase,
}
if sess, err = db.Open("mongo", settings); err != nil {
log.Fatal("db.Open: ", err)
}
..
However, as is obvious when seeing the uri
value within the environment variable VCAP_SERVICES
that can be confirmed in cf env
, we will not be able to connect under the current configuration, as the MongoDB service created on Cloud Foundry requires username and password.
shooter-server$ cf env shooter-server
{
"VCAP_SERVICES": {
"Mongo DB": [
{
"credentials": {
"uri": "mongodb://ad55761d-bd50-40f1-ae0f-875cc1c92e96:[email protected]:27017/7f7607ab-db63-465a-95c9-2b2318c5d68d"
},
"label": "Mongo DB",
"name": "shooter-mongo",
"plan": "Default Mongo Plan",
"tags": [
"mongodb",
"document"
]
}
]
}
}
Therefore, this time, we will make modifications based on the design of “Connect to the database if the URI (mongodb://<username>:<password>@<host>:<port>/<database>
) is specified in the environment variable MONGO_URI
at launch”. Of course, we will make sure not to influence any functions in situations where the standard MONGO_HOST
is specified.
shooter-server$ vi src/scores.go
..
import (
"log"
"os"
"strings"
"time"
"upper.io/db"
"upper.io/db/mongo"
)
..
var settings mongo.ConnectionURL
var sess db.Database
var scores db.Collection
..
func init() {
var err error
var mongoURI = ""
host := os.Getenv("MONGO_HOST")
uri := os.Getenv("MONGO_URI")
if uri != "" {
mongoURI = uri
} else if host != "" {
mongoURI = host
} else {
mongoURI = defaultHost
}
parsedURI, _ := mongo.ParseURL(mongoURI)
settings = mongo.ConnectionURL {
Address: parsedURI.Address,
Database: parsedURI.Database,
User: parsedURI.User,
Password: parsedURI.Password,
}
if sess, err = db.Open("mongo", settings); err != nil {
log.Fatal("db.Open: ", err)
}
log.Printf("Connected to mongo://%s.\n", mongoURI)
..
shooter-server$ git diff --no-prefix src/scores.go
diff --git src/scores.go src/scores.go
index 1d5b173..bb6666f 100644
--- src/scores.go
+++ src/scores.go
@@ -20,7 +20,7 @@ import (
"strings"
"time"
"upper.io/db"
- _ "upper.io/db/mongo"
+ "upper.io/db/mongo"
)
type mark struct {
@@ -29,7 +29,7 @@ type mark struct {
Created time.Time `json:"-" bson:"created"`
}
-var settings db.Settings
+var settings mongo.ConnectionURL
var sess db.Database
var scores db.Collection
@@ -41,22 +41,32 @@ const (
func init() {
var err error
+ var mongoURI = ""
host := os.Getenv("MONGO_HOST")
+ uri := os.Getenv("MONGO_URI")
- if host == "" {
- host = defaultHost
- }
+ if uri != "" {
+ mongoURI = uri
+ } else if host != "" {
+ mongoURI = host
+ } else {
+ mongoURI = defaultHost
+ }
- settings = db.Settings{
- Host: host,
- Database: defaultDatabase,
- }
+ parsedURI, _ := mongo.ParseURL(mongoURI)
+
+ settings = mongo.ConnectionURL {
+ Address: parsedURI.Address,
+ Database: parsedURI.Database,
+ User: parsedURI.User,
+ Password: parsedURI.Password,
+ }
if sess, err = db.Open("mongo", settings); err != nil {
log.Fatal("db.Open: ", err)
}
- log.Printf("Connected to mongo://%s/%s.\n", host, defaultDatabase)
+ log.Printf("Connected to mongo://%s.\n", mongoURI)
scores, err = sess.Collection("scores")
if err != nil {
We are all set.
Now, if the environment variable MONGO_URI
contains a URI that refers to the database information of MongoDB, this information will be used.
Additionally, if only the host name is specified in MONGO_HOST
as is standard, this location will be referenced, and will look for localhost if there is no environment variable related to MongoDB; this setting is also still valid.
Building Go Binary
We are using the binary-buildpack this time, so we will go ahead and build Go binary in the local environment.
The procedures can be found in the README.
shooter-server$ go version
go version go1.4 linux/amd64
We are using the version 1.4 of Go.
shooter-server$ make
mkdir -p $GOPATH/src/
ln -sf $PWD/src $GOPATH/src/shooter.io
shooter-server$ cd src/
shooter-server/src$ go get -d
shooter-server/src$ make
go build -o shooter-server
We are all set if a binary file “shooter-server” is generated.
shooter-server/src$ ls shooter-server
shooter-server
Creating Manifest File
This binary file is all we need to deploy this time, so we will create a separate directory containing only the binary and a manifest file, in order to avoid uploading unnecessary files.
go-server$ ls
manifest.yml shooter-server
We separately prepared an arbitrary directory named “go-server”.
By the way, the content of the manifest file looks like this:
go-server$ vi manifest.yml
---
applications:
- name: shooter-server
domain: 192.168.15.91.xip.io
buildpack: binary_buildpack
memory: 16M
command: ./shooter-server -listen :$PORT
env:
MONGO_URI: "mongodb://ad55761d-bd50-40f1-ae0f-875cc1c92e96:[email protected]:27017/7f7607ab-db63-465a-95c9-2b2318c5d68d"
services:
- shooter-mongo
In command
, we are launching the executable binary, with the listening PORT
number obtained from the environment variable.
For the environment variable MONGO_URI
, we scripted the uri for MongoDB connection found in cf env
, as is.
We also set the domain
in manifest, because WebSocket connection is allowed for 192.168.15.91.xip.io in the environment used in this post.
1.4. Deploying onto Cloud Foundry
Then we go ahead to deploy it on Cloud Foundry.
go-server$ cf push
..
-----> Uploading droplet (2.2M)
1 of 1 instances running
App started
OK
App shooter-server was started using this command `./shooter-server -listen :$PORT`
Showing health and status for app shooter-server in org ukaji / space default as ukaji...
OK
requested state: started
instances: 1/1
usage: 16M x 1 instances
urls: shooter-server.192.168.15.91.xip.io
last uploaded: Thu Oct 29 09:45:48 UTC 2015
stack: cflinuxfs2
buildpack: binary_buildpack
state since cpu memory disk details
#0 running 2015-10-29 06:46:01 PM 28.3% 5M of 16M 0 of 1G
It looks fine.
The URL produced will be used later.
2. Deploying shooter-html5
2.1. Retrieving Source Code
Next, let’s proceed to deploying shooter-html5, the HTTP server.
We begin by obtaining the source code.
$ git clone https://github.com/xiam/shooter-html5
$ cd shooter-html5/src/
shooter-html5/src$ ls
assets css index.html js
This is an HTML+JavaScript application.
2.2. Preparation
Configuring Connection to shooter-server
We take the URL for shooter-server deployed earlier, and register it as the target for WebSocket connection.
shooter-html5/src$ vi js/main.js
// Websocker server address.
var WEBSOCKET_SERVICE = 'ws://shooter-server.192.168.15.91.xip.io/w/';
..
shooter-html5/src$ git diff --no-prefix js/main.js
diff --git src/js/main.js src/js/main.js
index b114c65..5e4cbbd 100644
--- src/js/main.js
+++ src/js/main.js
@@ -1,5 +1,5 @@
// Websocker server address.
-var WEBSOCKET_SERVICE = 'ws://shooter.io/w/';
+var WEBSOCKET_SERVICE = 'ws://shooter-server.192.168.15.91.xip.io/w/';
// Frames configuration.
var FRAMES_PER_SECOND = 24;
Modifying Symbolic Links
When we look at how the files are organized in the source code for this application, we find two symbolic links.
shooter-html5/src$ tree js
js
├── controller.js
├── entity.js
├── fire.js
├── game.js
├── isMobile.js -> isMobile.min.js
├── isMobile.min.js
├── jquery.js -> jquery.min.js
├── jquery.min.js
├── json2.js
├── layer.js
├── license-sm2.txt
├── lifebar.js
├── main.js
├── powerup.js
├── radar.js
├── require.js
├── score.js
├── screen.js
├── ship.js
├── sm2.js
├── sound.js
├── swf
│ └── soundmanager2.swf
├── util.js
└── ws.js
This is fine when we are launching locally, but Cloud Foundry’s CLI is set to ignore symbolic links when pushing.
(Reference)
- https://github.com/cloudfoundry/cli/issues/108
- https://github.com/cloudfoundry/cli/blob/3ce3f3857de21435d986f51b74f474ac8267520a/cf/app_files/app_files.go#L95-L97
Although this is a temporary fix, we will remove the symbolic links and replace them with hard links.
It is also OK to copy those files.
shooter-html5/src$ unlink js/isMobile.js
shooter-html5/src$ ln js/isMobile.min.js js/isMobile.js
shooter-html5/src$ unlink js/jquery.js
shooter-html5/src$ ln js/jquery.min.js js/jquery.js
Creating Manifest File
We also create a manifest file here.
shooter-html5/src$ vi manifest.yml
---
applications:
- name: shooter
domain: 192.168.15.91.xip.io
buildpack: staticfile_buildpack
memory: 16M
2.3. Deploying onto Cloud Foundry
Let’s deploy with cf push
.
shooter-html5/src$ cf push
..
-----> Uploading droplet (4.3M)
1 of 1 instances running
App started
OK
App shooter was started using this command `sh boot.sh`
Showing health and status for app shooter in org ukaji / space default as ukaji...
OK
requested state: started
instances: 1/1
usage: 16M x 1 instances
urls: shooter.192.168.15.91.xip.io
last uploaded: Thu Oct 29 07:01:49 UTC 2015
stack: cflinuxfs2
buildpack: staticfile_buildpack
state since cpu memory disk details
#0 running 2015-10-29 04:01:59 PM 0.0% 5.2M of 16M 0 of 1G
It looks fine.
3. Checking Application Behavior
Let’s access the produced URL.
We register a name, and the game begins when we push START >>
.
The controls are simple:
- Move with arrow keys
- Shoot with the SPACE bar
That’s it.
The objective of the game is to shoot down enemies until you have no lives left, and compete for the highest final score.
Extras
This time, we introduced an example where two Cloud Foundry applications work in conjunction.
While we are at it, let us try to deploy both with one cf push
.
The procedure is simple; we call two applications with one manifest file.
Let’s place both the shooter-html5 directory and the go-server directory which contains the executable binary of shooter-server under an arbitrary directory.
$ tree -L 3
.
├── go-server
│ ├── manifest.yml
│ └── shooter-server
└── shooter-html5
├── LICENSE
├── README.md
└── src
├── assets
├── css
├── index.html
├── js
└── manifest.yml
The necessary manifest file is the following:
$ vi manifest.yml
---
applications:
- name: shooter-server
domain: 192.168.15.91.xip.io
path: go-server/
buildpack: binary_buildpack
memory: 16M
command: ./shooter-server -listen :$PORT
env:
MONGO_URI: "mongodb://ad55761d-bd50-40f1-ae0f-875cc1c92e96:[email protected]:27017/7f7607ab-db63-465a-95c9-2b2318c5d68d"
services:
- shooter-mongo
- name: shooter
domain: 192.168.15.91.xip.io
path: shooter-html5/src/
buildpack: staticfile_buildpack
memory: 16M
We use the item path
, and specify the directory containing the source code that runs the deployment with a relative path.
We no longer need individual manifest files, so we will delete them.
$ rm go-server/manifest.yml
$ rm shooter-html5/src/manifest.yml
The file structure should look like this:
$ tree -L 3
.
├── go-server
│ └── shooter-server
├── manifest.yml
└── shooter-html5
├── LICENSE
├── README.md
└── src
├── assets
├── css
├── index.html
└── js
We deploy with the above settings.
$ cf push
..
-----> Uploading droplet (2.2M)
1 of 1 instances running
App started
OK
App shooter-server was started using this command `./shooter-server -listen :$PORT`
Showing health and status for app shooter-server in org ukaji / space default as ukaji...
OK
requested state: started
instances: 1/1
usage: 16M x 1 instances
urls: shooter-server.192.168.15.91.xip.io
last uploaded: Fri Oct 30 02:28:48 UTC 2015
stack: cflinuxfs2
buildpack: binary_buildpack
state since cpu memory disk details
#0 running 2015-10-30 11:29:01 AM 19.3% 4.6M of 16M 0 of 1G
..
-----> Uploading droplet (4.3M)
1 of 1 instances running
App started
OK
App shooter was started using this command `sh boot.sh`
Showing health and status for app shooter in org ukaji / space default as ukaji...
OK
requested state: started
instances: 1/1
usage: 16M x 1 instances
urls: shooter.192.168.15.91.xip.io
last uploaded: Fri Oct 30 02:29:23 UTC 2015
stack: cflinuxfs2
buildpack: staticfile_buildpack
state since cpu memory disk details
#0 running 2015-10-30 11:29:35 AM 0.0% 5.2M of 16M 0 of 1G
Now, two deployments are being run with one cf push
command.
Software Used in This Post
- cf-release (v211)
https://github.com/cloudfoundry/cf-release/tree/v211
( https://github.com/cloudfoundry/cf-release/tree/2121dc6405e0f036efa4dba963f7f49b07e76ffa ) - bosh-lite
https://github.com/cloudfoundry/bosh-lite/tree/552dc6869600c5350eb7ffb4fb9c9c5e79e3889d - CF CLI (v6.12.0-8c65bbd-2015-06-30T00:10:31+00:00)
https://github.com/cloudfoundry/cli/releases/tag/v6.12.0 - spring-boot-cf-service-broker-mongo
https://github.com/nota-ja/spring-boot-cf-service-broker-mongo/tree/cf-100-day-challenge-068-with-env-specific-configs - shooter-server
https://github.com/xiam/shooter-server/tree/20e918f27dd8f4cc24bcbecf91d06e3f7fd492f5- Revised version used in this article when pushing
https://github.com/hiroakiukaji/shooter-server/tree/eb18601ad2f9c64b3a92da4a087b9ec0918bfa5b
- Revised version used in this article when pushing
- shooter-html5
https://github.com/xiam/shooter-html5/tree/df65c5267a3bb6b820bde52b89b20e6a7d84d934- Revised version used in this article when pushing
https://github.com/hiroakiukaji/shooter-html5/tree/42ef058732f88f39738de1224b2c22581ed984b9
- Revised version used in this article when pushing