confdの簡易版みたいなやつを作ってる話

皆さんアプリケーションの設定値の管理ってどうやってますか?

例えばLaravelだと以下のような.envファイルを用意すると思います。

APP_NAME=MyApp
APP_ENV=local
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=mydb
DB_USERNAME=mydbuser
DB_PASSWORD=mydbpassword
OTHER_APP_API=http://example.com/api/v1/hoge

APP_NAME等アプリケーション固有のものもあれば、DB_HOSTのように環境(ローカル環境、テスト環境、ステージング環境、本番環境...etc)によって値を変えたいものもあります。社内の特定のAPIに依存している場合はOTHER_APP_APIのように書くこともあるでしょう。

アプリケーション毎・環境毎に設定ファイルを用意すると、設定値の更新が必要になった場合、依存するアプリケーション全てに更新作業が発生してしまいます。 特にマイクロサービス寄りのアプリケーションの場合この傾向は顕著です。

これを回避するため、etcd等のKVSに設定値を保存しておき、各アプリケーションはそちらを参照する方法があります。

confd

etcd等のデータストアから値を取り出し、設定テンプレートに反映して設定ファイルを出力するconfdというツールがあります。

github.com

以下のようなテンプレートファイルと、tomlで書かれたテンプレートリソースという2つのファイルを用意することで、テンプレートに値を埋めて出力してくれます。

テンプレートファイル

upstream {{getv "/subdomain"}} {
{{range getvs "/upstream/*"}}
    server {{.}};
{{end}}
}

server {
    server_name  {{getv "/subdomain"}}.example.com;
    location / {
        proxy_pass        http://{{getv "/subdomain"}};
        proxy_redirect    off;
        proxy_set_header  Host             $host;
        proxy_set_header  X-Real-IP        $remote_addr;
        proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
   }
}

{{getv "/subdomain"}} と書くと /subdomain というkeyで保存された値を埋めてくれます。

テンプレートリソース

[template]
prefix = "/yourapp"
src = "nginx.tmpl"
dest = "/tmp/yourapp.conf"
owner = "nginx"
mode = "0644"
keys = [
  "/subdomain",
  "/upstream",
]
check_cmd = "/usr/sbin/nginx -t -c {{.src}}"
reload_cmd = "/usr/sbin/service nginx reload"

keys にはテンプレートファイル中で使用するkeyを並べます。

confdには値の変更をwatchする機能もあり、check_cmdreload_cmdでファイルの検証と反映のコマンドを書くことができます。

confgen

confdは多数のバックエンドにも対応しており非常に有用なのですが、テンプレートリソースを用意するのが少し面倒だなと感じました。

watch機能に関しても、実運用において特定の設定値に依存している各アプリケーションが一斉に切り替わってほしいという要件はほとんど経験したことが無く、そこまで必要かな?という印象です。(逆にアプリケーション側のタイミングで更新したいというケースが多かった気がする)

仮に必要だったとしても、watchに関しては他のツールにおまかせして「KVSから値を取得し、設定ファイルを出力させる」ことだけに特化したツールが欲しくなり、作ってるところです。

github.com

使い方

confgenはgoで書かれたコマンドラインツールです。

confdと同じように、以下のような設定ファイルテンプレートを用意します。 テキストファイルだったらなんでも構いません。

db {
  host = "{{v "/myapp/database/host"}}"
}

バックエンドとして以下のKVSに対応しています。

  • etcd(v3)
  • consul
  • zookeeper

事前に以下の値をKVSに保存しておきます。

Key: "/myapp/database/host"
Value: "localhost"

コマンドは以下のように書きます。

$ confgen -file [テンプレートファイルパス] -backend [etcd|consul|zookeeper] -node [バックエンドノード]

etcdなら以下のように書きます。

$ confgen -file example/application.conf.tmpl -backend etcd -node localhost:2379

実行すると、標準出力に

db {
  host = "localhost"
}

と出力されます。 ファイルに出力したい場合は> application.conf のようにしてあげてください。

テンプレートリソースが不要になり、動的に差し込みたい部分を{{v "/key"}}とするだけで良いので既存のアプリケーションからの移行も楽にできるのではないかと思います。 実際の利用ではアプリケーションのデプロイ時にCI等でconfgenを実行しファイルを出力したり環境変数に利用したりすることになるかと思います。

今後

  • バックエンドには認証機能を持つものがほとんどなので、それらにも対応する必要がありそう。
  • 配列の展開等、テンプレート表現力を豊かにするか否か。してもいいけどgoテンプレートの書き方を利用者に覚えてもらう必要がある。
  • 機密情報の扱いをどうするか(confgenの責務ではないと思うけど)
  • バックエンドに値を入れるフローのベストプラクティスはなんだろう(これもconfgenの責務ではない)

最後に各バックエンドの紹介とローカルで爆速構築する手順を自分用メモとして残しておきます。

etcd

etcdとは

分散キーバリューストア。golang製。

公式

ローカルで動かすには

起動

$ etcd

操作

※ etcdのバージョンがv3.3以下なら環境変数ETCDCTL_API=3 を追加しておくこと。v3.4からはこれがデフォルトになる。

保存

$ etcdctl put mykey "this is awesome"

取得

$ etcdctl get mykey

値だけ取得

$ etcdctl get foo --print-value-only
bar

削除

$ etcdctl del mykey

一覧

$ etcdctl get "" --prefix

出力フォーマットを変える

$ etcdctl --write-out="simple" get mykey

"fields", "json", "protobuf", "simple", "table" が使える

Consul

Consulとは

  • サービスディスカバリ
  • ヘルスチェック
  • キーバリューストア

等の機能を持ったツール。

よくKVSとしてetcdと比較されるが、それ以外の機能もある欲張りなやつ。(むしろサービスディスカバリがメインだと思う)

クライアント・サーバ構成でクラスタリングできる。両方共consulというバイナリ一つで動作する。(golang製)

f:id:romiogaku:20190203213835p:plain

わかりやすいスライド

https://www.slideshare.net/ssuser07ce9c/consul-58146464

公式

チュートリアルが充実している。

ローカルで動かすには

起動

$ consul agent -dev

devモードで起動。

操作

保存

$ consul kv put mykey "this is awesome"
Success! Data written to: mykey

取得

$ consul kv get mykey
this is awesome

詳細表示

$ consul kv get -detailed mykey
CreateIndex      64
Flags            0
Key              mykey
LockIndex        0
ModifyIndex      64
Session          -
Value            this is awesome

メタ番号(flag)も付与できる

$ consul kv put -flags=42 hoge fuga
Success! Data written to: hoge

$ consul kv get -detailed hoge
CreateIndex      69
Flags            42
Key              hoge
LockIndex        0
ModifyIndex      69
Session          -
Value            fuga

全てのkey-value表示

$ consul kv get -recurse
hoge:fuga
mykey:this is awesome

削除

$ consul kv delete mykey
Success! Deleted key: mykey

prefixで始まる全てを削除

$ consul kv delete -recurse hoge
Success! Deleted keys with prefix: hoge

※prefixを省略すると全て削除。

WebUI

$ consul agent -dev -ui

-uiをつけて起動すると http://localhost:8500/ui でWebUIにアクセスできる。

ZooKeeper

ZooKeeperとは

分散システムでよく利用される分散コーディネーションサービス。 Hadoop使うとセットでついてくる例のアレ。

高いパフォーマンスを出せるため、大規模分散システムに向いている。 単純な設定保存のためのKVSとして使うだけならオーバースペックだと思う。

ドキュメント

http://oss.infoscience.co.jp/hadoop/zookeeper/docs/current/index.html

ローカルで動かすには

macならbrew install zookeeperでインストールできる。

公式がdockerイメージを作成しているのでそれを使うのも良い。まんま公式のパクリだが以下をcloneしてdocker-compose upすれば3ノードで立ち上がる。

github.com

接続は

のどれか。

操作

f:id:romiogaku:20190203213517j:plain

ZooKeeperの提供する名前空間は一般的なファイルシステムによく似ている。

ノードはデータを持てると同時に複数の子を持てる。ファイルが同時にディレクトリにもなれるファイルシステムのようなものになっている。

以下

$ bin/zkCli

でZooKeeperに接続した状態とする。

ノード確認

[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]

ノード作成

[zk: localhost:2181(CONNECTED) 1] create /mykey "this is awesome"
Created /mykey

ノード情報取得

[zk: localhost:2181(CONNECTED) 2] get /mykey
this is awesome
cZxid = 0x200000012
ctime = Thu Jan 31 08:25:32 GMT 2019
mZxid = 0x200000012
mtime = Thu Jan 31 08:25:32 GMT 2019
pZxid = 0x200000012
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 15
numChildren = 0

データ更新

[zk: localhost:2181(CONNECTED) 3] set /mykey "updated."
cZxid = 0x200000012
ctime = Thu Jan 31 08:25:32 GMT 2019
mZxid = 0x200000013
mtime = Thu Jan 31 08:28:01 GMT 2019
pZxid = 0x200000012
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0

削除

[zk: localhost:2181(CONNECTED) 4] delete /mykey

GUI

qiita.com