サービスの 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_' . $