「Cloud Foundry 百日行」第91日目は、多人数で遊べるシューティングゲーム shooter.io です。
Goで書かれたサーバ shooter-server と、HTTPサーバ shooter-html5 が連携して動くアプリです。
なお、今回のアプリはサーバー間の通信にWebSocketが使われているため、Proxy等の設定によってはアプリが正常に動作しない可能性もありますので、ご了承下さい。
基本情報
-
公式サイト
http://shooter.io/ -
ソースコード
https://github.com/xiam/shooter-server
https://github.com/xiam/shooter-html5
今回の手順の概要は以下の通りです。
- 1) shooter-server のデプロイ
- 1.1) ソースコードの入手
- 1.2) Serviceの作成
- 1.3) 事前準備
- 1.4) Cloud Foundryへのデプロイ
- 2) shooter-html のデプロイ
- 2.1) ソースコードの入手
- 2.2) 事前準備
- 2.3) Cloud Foundryへのデプロイ
- 3) 動作確認
1. shooter-server のデプロイ
1.1. ソースコードの入手
$ 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
Go言語で書かれたアプリです。
README によると、以降のサーバ起動手順は、
make
cd src
go get -d
make
MONGO_HOST="<MONGODB_HOST>" ./shooter-server -listen <LISTEN_IP>:<LISTEN_PORT>
MONGODB_HOST
→ MongoDBのある場所のIPアドレス。デフォルトの値は “127.0.0.1” 。(参考)LISTEN_IP
:LISTEN_PORT
→ HTTPサーバからの接続待ち受けIPアドレス/PORT。デフォルトの値は “127.0.0.1:3223” 。(参考)
となっているようです。
さて、恐らくですが、 go-buildpack を使った場合は、2つのディレクトリに渡って make
を実行し、かつその間に go get
も適切なタイミングで実行し・・・といった処理はできない可能性が高いです。
もちろんGo言語とgo-buildpackの仕様を共にしっかりと理解している方であれば可能であるかもしれませんが、残念ながら私には今回の執筆期間ではそのような手段を編み出すことはできませんでした。
しかし幸いなことにGo言語には、binaryさえ作ってしまえば外部のライブラリ等に依存せずに実行ができるという強みがあります。
ということで、今回はこれを活かし、Goのバイナリを事前にローカルの環境で作ってしまい、その後Cloud Foundryで動作させる、という手段をとってみようと思います。
使うBuildpackは binary-buildpack です。
1.2. Serviceの作成
さて、 shooter-server はゲームスコアなどの保存にMongoDBを使うとのことなので、 こちらの記事 で作成したMongoDB Serviceを使います。
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
バインドも済ませておきましょう。
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. 事前準備
MongoDB接続周りの修正
1.2で作成したMongoDBに接続する箇所の修正を行います。
scores.go
を見てみると、このアプリはデータベースのHost名とDatabase名のみで接続を行っています。
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)
}
・・・
しかし、 cf env
で見ることができるServiceの環境変数中の uri
の値を見て分かる通り、Cloud Foundry上で作成したMongoDBのServiceにはUsername、Passwordが設定されているため、このままでは正しく接続することはできません。
shooter-server$ cf env shooter-server
{
"VCAP_SERVICES": {
"Mongo DB": [
{
"credentials": {
"uri": "mongodb://ad55761d-bd50-40f1-ae0f-875cc1c92e96:password@192.168.15.91:27017/7f7607ab-db63-465a-95c9-2b2318c5d68d"
},
"label": "Mongo DB",
"name": "shooter-mongo",
"plan": "Default Mongo Plan",
"tags": [
"mongodb",
"document"
]
}
]
}
}
そこで今回は、「起動時に環境変数 MONGO_URI
でURI( mongodb://<username>:<password>@<host>:<port>/<database>
)を指定すればDBとの接続を行う」という方針で修正を行います。もちろん従来の MONGO_HOST
が指定されている場合の動作にも影響が及ばないようにしましょう。
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 {
これでOKです。
環境変数 MONGO_URI
にMongoDBのデータベース情報を参照するURIが書かれていればそれを用いるようになりました。
また、従来通り MONGO_HOST
にHost名だけを指定している場合はその場所を参照し、MongoDBに関する環境変数が何も与えられていない場合は、localhostを探しに行くという設定も生きています。
バイナリファイルの作成
今回はbinary-buildpackを使うので、ローカル環境でGoのbinaryを作ってしまいます。
手順は README の通りです。
shooter-server$ go version
go version go1.4 linux/amd64
goのversionは1.4を用いています。
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
shooter-serverというbinaryファイルができていればOKです。
shooter-server/src$ ls shooter-server
shooter-server
manifestファイルの作成
今回デプロイに必要なのはこのbinaryファイルだけなので、余分なファイルをアップロードしないためにもbinaryファイルとmanifestファイルのみが入ったディレクトリを別に作っておきましょう。
go-server$ ls
manifest.yml shooter-server
今回はgo-serverという別の適当なディレクトリを用意しました。
なお、manifestファイルの中身はこのようになっています。
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:password@192.168.15.91:27017/7f7607ab-db63-465a-95c9-2b2318c5d68d"
services:
- shooter-mongo
command
ではbinaryの起動とあわせて、ListenするPORT番号を環境変数から取得しています。
環境変数 MONGO_URI
には cf env
で見ることができるMongoDB接続のためのuriをそのまま記述しました。
なお、今回検証を行った環境では 192.168.15.91.xip.io を指定した場合にWebSocketが通るようになるので、manifest中で domain
を設定しています。
1.4. Cloud Foundryへのデプロイ
では、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
OKのようです。
発行されたURLは後ほど使います。
2. shooter-html5 のデプロイ
2.1. ソースコードの入手
さて、続いてHTTPのサーバ、 shooter-html5 のデプロイに移ります。
まずはソースコードの入手から。
$ git clone https://github.com/xiam/shooter-html5
$ cd shooter-html5/src/
shooter-html5/src$ ls
assets css index.html js
HTML+JavaScriptのアプリです。
2.2. 事前準備
shooter-server への接続先設定
まずは先ほどデプロイした shooter-server のURLを、WebSocketの接続先として登録します。
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;
Symbolic Linkの修正
今回のアプリのソースコードのファイル構成を眺めてみると、2箇所程Symbolic Linkが張られている所があります。
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
ローカルで起動させている分にはこのままでも良いのですが、実は、Cloud FoundryのCLIは、pushの際にSymbolic Linkを無視する仕様になっています。
※参考
- https://github.com/cloudfoundry/cli/issues/108
- https://github.com/cloudfoundry/cli/blob/3ce3f3857de21435d986f51b74f474ac8267520a/cf/app_files/app_files.go#L95-L97
一時凌ぎな解決策ではありますが、ここではSymbolic Linkを外してHard Linkに変更しておきましょう。
もちろん中身をCopyしたファイルを用意しておくのもOKです。
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
manifestファイルの作成
こちらもmanifestファイルを作っておきます。
shooter-html5/src$ vi manifest.yml
---
applications:
- name: shooter
domain: 192.168.15.91.xip.io
buildpack: staticfile_buildpack
memory: 16M
2.3. Cloud Foundryへのデプロイ
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
OKですね。
3. 動作確認
発行されたURLにアクセスします。
名前を登録して START >>
を押すとゲーム開始です。
操作方法はシンプルで、
- 上下左右キーで移動
- SPACEキーで射撃
これだけです。
自機のライフが0になるまで敵を撃墜し続け最終スコアを競う、というゲームのようです。
おまけ
さて、今回は2つのCloud Foundryアプリケーションが連携して動くものをご紹介しました。
折角なので、一回の cf push
で両方のデプロイが終わるような設定にしてみましょう。
方法としては簡単で、1つのmanifestファイルから2つのアプリケーションを呼び出す、という方法を取ります。
適当なディレクトリ配下に、 shooter-serverのbinaryが入ったgo-serverのディレクトリと shooter-html5 のディレクトリを配置します。
$ tree -L 3
.
├── go-server
│ ├── manifest.yml
│ └── shooter-server
└── shooter-html5
├── LICENSE
├── README.md
└── src
├── assets
├── css
├── index.html
├── js
└── manifest.yml
必要なmanifestファイルは以下の通りです。
$ 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:password@192.168.15.91: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
path
という項目を使い、デプロイを行うソースコードが入ったディレクトリを相対パスで指定しています。
もう個別のmanifestファイルは特に必要ないので消しておきましょう。
$ rm go-server/manifest.yml
$ rm shooter-html5/src/manifest.yml
ファイル構成は以下のようになっているはずです。
$ tree -L 3
.
├── go-server
│ └── shooter-server
├── manifest.yml
└── shooter-html5
├── LICENSE
├── README.md
└── src
├── assets
├── css
├── index.html
└── js
この状態でデプロイを行います。
$ 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
cf push
コマンド1回で、2つのデプロイが実行されるようになりました。
今回使用したソフトウェア
- 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 - shooter-html5
https://github.com/xiam/shooter-html5/tree/df65c5267a3bb6b820bde52b89b20e6a7d84d934