サービスの orphan(孤児) を purge (処分) してくれるツール
まずは構成について再確認。
CloudController <-> NATS <-> Service Gateway <-> Service Node
Gateway は、自分自身がNodeにリクエストしたサービスのプロセス(mongod とか redis とか)を sqlite3 のデータベースで管理している。
$ ls -al
total 20
drwxr-xr-x 4 vcap vcap 4096 2011-12-13 21:58 .
drwxr-xr-x 4 vcap vcap 4096 2011-12-05 16:47 ..
drwxr-xr-x 3 vcap vcap 4096 2011-12-06 17:49 instances
drwxr-xr-x 3 vcap vcap 4096 2011-12-06 17:49 logs
-rw-r--r-- 1 vcap vcap 4096 2011-12-13 21:58 mongodb_node.db
$ ls -al instances/
total 12
drwxr-xr-x 3 vcap vcap 4096 2011-12-06 17:49 .
drwxr-xr-x 4 vcap vcap 4096 2011-12-13 21:58 ..
drwxr-xr-x 3 vcap vcap 4096 2011-12-13 21:58 8791ad25-f445-4769-977b-c60518a14788
$ sqlite3 mongodb_node.db
sqlite> .schema
CREATE TABLE "vcap_services_mongo_db_node_provisioned_services" ("name" VARCHAR(50) NOT NULL, "port" INTEGER, "password" VARCHAR(50) NOT NULL, "plan" INTEGER NOT NULL, "pid" INTEGER, "memory" INTEGER, "admin" VARCHAR(50) NOT NULL, "adminpass" VARCHAR(50) NOT NULL, "db" VARCHAR(50) NOT NULL, PRIMARY KEY("name"));
CREATE UNIQUE INDEX "unique_vcap_services_mongo_db_node_provisioned_services_port" ON "vcap_services_mongo_db_node_provisioned_services" ("port");
sqlite> select * from vcap_services_mongo_db_node_provisioned_services;
8791ad25-f445-4769-977b-c60518a14788|25001|eacb0e8c-7282-4e4c-8790-be29460703cd|1|8888|2048|admin|37b3cd6a-e291-4f36-af88-7d916d3feec4|db
Service Gateway は、sqlite3のDBに存在するけれども、インスタンスとしてとして起動していないprovisioned serviceに対しては、とにかくインスタンスの起動を試みる。このsqlite3のDBが、CloudControllerのDBと一致しない状態になったとき、具体的には、「sqlite3のDBには存在するけれど、CloudControllerのDBには存在しない」 provisioned service 情報が存在すると、"CloudControllerの管理対象化にないがインスタンスとしては存在する" = 孤児 のインスタンスが生まれ得る(ここでいうインスタンスは、プロセスであったりデータベースであったりとサービスによって様々)。
あるいはこの逆で、「sqlite3のDBには存在しないけれど、CloudControllerのDBには存在する」 = 孤児のバインディングが生まれ得る。
- Service Gateway と NATS が切り離されたとき、(Cloud Controller はService Nodeの操作までを create-service, delete-service のトランザクションスコープにいれないので)
- Redis (これはバグっぽい:後述)
- ...etc
$ cd VCAP_HOME/services/tools/purge_orphan $ ruby bin/purge_orphan.rb -h
- Usage: purge_orphan.rb [options] <orphan_file>
- -c, --config [ARG] Configuration File -k, --check Request to check orphan -h, --help Help
-k は orphan の情報をチェックする時につかう。Service Gateway のステータスサーバーが orphan 情報をステータスとして保持しているので、それを強制アップデートする際につかう。
-c は Service Gateway のHTTPサーバー(!= ステータスサーバー) の情報などを記述する設定ファイル。
超重要。Cloud Foundry の各ノードで動く、ステータス情報をかえすHTTP Daemon(RubyのThinでかかれている) ステータスサーバーのBasic認証用のユーザー名、パスワード および ポートは通常ランダムに決められて、NATSを通じて知ることができる。この辺の詳細は VCAP::Component#register() のソース参照。
こんな感じのスクリプトで取得できる。
$ cat status_info.rb
require 'rubygems'
require 'nats/client'
NATS.start do
NATS.request('vcap.component.discover') do |msg|
puts msg
end
end
{"type":"MongoaaS-Provisioner","index":169658655,"uuid":"169658655-2e3e584580abf6aa51c9e84acf49ff7d","host":"192.168.1.31:10007","credentials":["10f05b73741bc0bffaba02b68f43064e","passw0rd"],"start":"2011-12-13 21:58:44 +0900","uptime":"0d:21h:23m:28s"}
あるいは、Cloud Controller などであれば
status: user: thin password: thin port: 12345
のような設定をymlに書いておけば http://thin:thin@localhost:12345/varz とかで取得できる。
Service Gateway はそれできないの? → なぜかできないorz → パッチ: cloudfoundry-attic/vcap-services#13
$ curl http://{user}:{pass}@localhost:{port}/varz
{
"type": "MongoaaS-Provisioner",
"start": "2011-12-13 21:58:44 +0900",
"num_cores": 2,
"nodes": {
"mongodb_node_169658655": 1323858367,
"mongodb_node_169658656": 1323858370
},
"prov_svcs": {
"8791ad25-f445-4769-977b-c60518a14788": {
"service_id": "8791ad25-f445-4769-977b-c60518a14788"
},
}
"orphan_instances": {
// ここに 孤児のインスタンス情報がはいる
},
"orphan_bindings": {
// ここに 孤児のバインディング情報がはいる
},
}
上記ステータス情報を更新するときにつかう。
$ curl http://{user}:{pass}@localhost:{port}/varz > varz.json
$ purge_orphan varz.json
これで orphan_instances とか orphan_bindings を綺麗に掃除してくれる。
_gateway.yml に check_orphan_interval という設定ができるので purge_orphan -k は自動でできる。
purge_orphan は自動でやる? 正常なトランザクションでも orphan_bindings が生まれ得るであろうから(ソース未確認)、インスタンスの生成速度と、purge_orphan の実行インターバルとの兼ね合い。Gatewayでは orphan のダブルチェックをしており、double_check_orphan_interval というパラメーターでダブルチェックの間隔も制御できる。
Service Gateway は、sqlite3のDBに存在するけれども、インスタンスとしてとして起動していないprovisioned serviceに対しては、とにかくインスタンスの起動を試みる
「インスタンスとして起動しているかどうか」は、各Serviceの実装による。プロセスであれば ps だし、データベースであれば管理用テーブルへのSELECTだし.... ということで、 base/lib/base.rb には skeleton だけある。
# Subclass must overwrite this method to enable check orphan instance feature. # Otherwise it will not check orphan instance # The return value should be a list of instance name(handle["service_id"]). def all_instances_list [] end # Subclass must overwrite this method to enable check orphan binding feature. # Otherwise it will not check orphan bindings # The return value should be a list of binding credentials # Binding credential will be the argument for unbind method # And it should have at least username & name property for base code # to find the orphans def all_bindings_list [] end
さてRedis。
$ grep -R 'all_' . $