GCPで1番好きなサービスはGAEです。でも2番目はメタデータサーバーな大橋です。
書く書く詐欺をし続けてとうとう書くことになりました。
皆さんGCEのメタデータサーバー使ってますか?
使ってない? 使ってないなら今日から使いましょう。もったいない事この上ないです。
今回は私の大好きなメタデータサーバーの概要や使い方について書いていきたいとも思います。
まとめ
3行でMetadata Serverをまとめると
- GCEの様々な情報が乗っているHTTP(curlなども可)アクセス可能なデータサーバ
- Key-Value形式で好きなデータを投入可能
- 更新検知が可能
です。
これだけシンプルですが非常に強力で、利用シーンがとても多いです。
概要
メタデータサーバーはGCEのインスタンスや、プロジェクトのメタデータを保存しているサーバです。
Web APIが用意されており、GCEのインスタンスからであれば認証無しでアクセスが可能です。
もしGCEをよく触る方であれば、Startup ScriptやShutdown Script、SSH-keyの設定などをしたことがあるかもしれませんが、
これらの情報はすべてメタデータサーバに保存されており、
それぞれのタイミングでGCEのインスタンスがメタデータサーバにアクセスし、情報を取得・利用しています。
メタデータサーバへアクセスするURLは決まっており
- Full URL: http://metadata.google.internal/computeMetadata/v1/
- Shorthand URL: http://metadata/computeMetadata/v1/
- IP address: http://169.254.169.254/computeMetadata/v1/
のいずれかとなっています。 これらのホスト名はインスタンスが起動する際に/etc/hostsに書き込まれます。
よくGCEのネットワーク周りを設定する方だと
このIPアドレス(169.254.169.254
)がL4LBのヘルスチェックの際に利用されるIPだと気がつくかもしれません
メタデータサーバはそういった方法でも利用されています。
GCEの様々な情報が乗っているHTTP(curlなども可)アクセス可能なデータサーバ
Project metadataとInstance metadata
メタデータにはProject metadataとInstance metadataが存在し、
Project metadataはプロジェクト全体のインスタンスから、
Instance metadataはそのインスタンスからのみ取得可能となっています。
Default metadata keys
GCEでは予めKeyが決まっているDefault metadataが存在します。
Default metadataはReadonlyな情報ですが、GCEインスタンスが立ち上がった時点で既に利用可能になっています。
すべてのkeyを紹介すると多いので詳しくは上のリンクを参照していただきたいですが、
このkey
をベースにURIがきまり、メタデータサーバ上からデータを取得できます。
例えばProject metadataのプロジェクトIDであれば
http://metadata/computeMetadata/v1/project/project-id
で、Instance metadataのPublic IPアドレスであれば
http://metadata/computeMetadata/v1/instance/network-interfaces/0/ip
また
http://metadata/computeMetadata/v1/instance/network-interfaces/
などslashで終わるURIでアクセスすると、
ディレクトリに対するアクセスとなり、中に含まれるkeyの一覧が取得できます。
データの取得
KeyのURIを利用してHTTP経由でメタデータを取得するには、HTTPヘッダーに以下を追加します。
Metadata-Flavor: Google
例えばcurl
利用してメタデータを取得する場合は以下のようになります。
curl "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ip" -H "Metadata-Flavor: Google"
クエリ
メタデータサーバへのアクセス時にクエリパラメータを追加すると様々なフォーマットでデータを取得できます。
alt
alt
パラメータを利用する返却値のフォーマットをテキストか**json*にできます。
$ curl "http://metadata.google.internal/computeMetadata/v1/instance/tags" -H "Metadata-Flavor: Google"
["https-server","http-server","front","env","blue"]
$ curl "http://metadata.google.internal/computeMetadata/v1/instance/tags?alt=text" -H "Metadata-Flavor: Google"
https-server
blue
http-server
front
env
recursive
recursive
パラメータを利用するとディレクトリへのアクセスを再帰的に処理して、子供のKeyの値をすべて取得できます。
$ curl "http://metadata.google.internal/computeMetadata/v1/instance/disks/?alt=text&recursive=true" -H "Metadata-Flavor: Go
ogle"
0/device-name instance-1-boot
0/index 0
0/mode READ_WRITE
0/type PERSISTENT
$ curl "http://metadata.google.internal/computeMetadata/v1/instance/disks/?alt=json&recursive=true" -H "Metadata-Flavor: Go
ogle"
[{"deviceName":"instance-1-boot","index":0,"mode":"READ_WRITE","type":"PERSISTENT"}]
カスタムメタデータ
先にも書いたとおり、メタデータにはKey-Value
形式で任意の値が設定可能です。
Startup ScriptやShutdown Script、SSH-keyの設定などはこのカスタムメタデータはKey
が決められたカスタムメタデータといえます。
カスタムメタデータの設定
カスタムメタデータを設定する方法は大きく分けて3種類あります。
- Developer Consoleで設定する
- gcloudコマンドを利用する
- APIを呼び出す
Developer Consoleでの設定は左メニューのメタデータからプロジェクトメタデータが、
GCEインスタンスの編集からインスタンスメタデータが設定できます。
頻繁に更新する必要が無い場合はDeveloper Consoleからの設定で十分だと思います
以降はgcloud
を使った設定方法を説明します。
インスタンスメタデータの設定
インスタンスを立ち上げる際に設定する場合は gcloud compute instances create
コマンドにて--metadata KEY=VALUE
フラグを付与します。
$ gcloud compute create new-hoge-instance --metadata hogekey:fugavalue
またStartup Scriptのようにデータサイズが大きい場合は--metadata-from-file KEY=LOCAL_FILE_PATH
を利用します。
$ gcloud compute create new-hoge-instance --metadata-from-file env_setting:env_setting.json
既に立ち上がっているインスタンスのメタデータを更新する場合は gcloud compute instances add-metadata
コマンドを利用します。
削除する場合はremove-metada
になります。
$ gcloud compute instances add-metadata exist-instance --metadata hogekey:fugavalue
$ gcloud compute instances remove-metadata exist-instance --metadata hogekey:fugavalue
プロジェクトメタデータの設定
ほとんどコマンドはインスタンスのメタデータと一緒です。 gcloud compute instances
をgcloud compute project-info
に変更します。
$ gcloud compute project-info add-metadata --metadata hogehogekey:fugafugavalue
$ gcloud compute project-info remove-metadata --metadata hogehogekey:fugafugavalue
カスタムメタデータの取得
カスタムメタデータを取得する場合はデフォルトメタデータと同じようにHTTP経由で取得できます。URIはattributes/KEY
の形式になります。
$ curl "http://metadata.google.internal/computeMetadata/v1/instance/attributes/hogekey" -H "Metadata-Flavor: Google"
fugavalue
$ curl "http://metadata.google.internal/computeMetadata/v1/project/attributes/hogehogekey" -H "Metadata-Flavor: Google"
fugafugavalue
更新検知
メタデータサーバではメタデータが更新されたことを検知するために「レスポンスをデータが更新されるまで待たせる」(wait-for-change
)事ができます。
これを利用するにはwait_for_change=true
パラメータを付与します。
$ curl "http://metadata.google.internal/computeMetadata/v1/instance/tags?wait_for_change=true" -H "Metadata-Flavor: Google"
上記では tags
が更新されるまでcurl
は戻ってきません。実際にこのインスタンスのタグを更新すると上記リクエストが帰ってくることが確認できると思います。
なおディレクトリに対してwait_for_change=true
を利用する場合recursive=true
パラメータが必須となります。
$ curl "http://metadata.google.internal/computeMetadata/v1/instance/attributes/?recursive=true&wait_for_change=true" -H "Metadata-Flavor: Google"
ETagを利用して確実に更新された事を取得する
wait-for-change
を利用する際、
-
wait-for-change
リクエストを投げる - 更新を待つ
- レスポンスが戻る
- 処理をする
- 1.に戻る
というような更新検知の仕組みを作ることが多いと思いますが、
この際 3-5の間に値が更新されてしまうと、 1.に戻ってきた際この更新を検知することができなくなってしまいます。
この問題を解決するためにはlast_etag
パラメータとETag
ヘッダーを利用します。
メタデータサーバがレスポンスを返却する際に、ETag
HTTPヘッダーを返却します。
この値を次のクエリの際にlast_etag
パラメータとして渡すとこのETag
時点よりデータが最新であればレスポンスが返却されるようになります。
またlast_etag
パラメータに0
を設定すると即時に値が返却されます。
これを利用して以下の様なループを作成すると常に更新検知を行う仕組みを作成できます。
import httplib2
METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/"
def main():
http = httplib2.Http()
tagsUrl = METADATA_URL + "instance/tags?wait_for_change=true"
# 初回は0を設定
last_etag = 0
while True:
resp,content = http.request(uri=tagsUrl + "&last_etag=" + str(last_etag), method="GET", body="", headers={"Metadata-Flavor": "Google"})
if resp != 500:
# エラーがない限りはlast_etagを更新
last_etag = resp["etag"]
print content
if __name__ == "__main__":
main()
こんな使い方
これらの仕組みを利用すると、オートスケール環境での複数サーバへのアプリケーションデプロイに利用できます。
例えば
- プロジェクトメタデータに
version
というメタデータを用意し、アプリケーションバージョン番号を設定しておく。 - 各インスタンスがStartup Script時に
version
メタデータに対して、wait-for-change
リクエストを投げて更新検知 - アプリケーションが更新されたら、CIなどでインスタンスから見れる場所に配置 (例えばDockerであればCloud Registry等)、
version
メタデータを更新する - 各インスタンスが
version
メタデータが更新されたことを検知し取得したアプリケーションバージョン番号から新しいアプリケーションを取得・デプロイ - 再度次の更新を待つ
の様な流れが作れます。
簡単なコードを書くと以下の感じです。
import httplib2
METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/"
def main():
http = httplib2.Http()
tagsUrl = METADATA_URL + "project/attributes/version?wait_for_change=true"
# 初回は0を設定
last_etag = 0
while True:
resp,content = http.request(uri=tagsUrl + "&last_etag=" + str(last_etag), method="GET", body="", headers={"Metadata-Flavor": "Google"})
if resp != 500:
# エラーがない限りはlast_etagを更新
last_etag = resp["etag"]
deploy(content)
def deploy(version):
...
if __name__ == "__main__":
main()
かなり簡単な仕組みで複数サーバへのアプリケーションデプロイが行えるようになります。
またStartup Scriptと混ぜることで、オートスケール環境でも常に足並みの揃ったアプリケーションバージョンのインスタンスを用意することが可能になります。
注意点とイケていない点
経験上の注意点としてカスタムメタデータはデータの保存や更新時に単一のKEY:VALUE
を設定しているわけではなく、
保存されているすべてのカスタムメタデータを毎回保存しにいっているように見えます。
※特にAPIコールの場合は設定したい一部ではなくすべてのカスタムメタデータを送信する。
このため、複数のインスタンスから頻繁にメタデータサーバを更新すると同期例外や、古いデータで更新される可能性があります。
例えば
「オートスケールなどを設定しており、インスタンスが立ち上がったタイミングでプロジェクトメタデータにIPを保存する」
のようなメタデータ運用をした場合複数のインスタンスが同時にメタデータサーバに書き込みをしようとし、
失敗したり古いデータが登録されるケースが有ります。
このため可能であればプロジェクトメタデータの設定を行うのは決められたサーバだけなどにしておくと幸せなことが多いです。
また、データの取得はcurl
経由で可能ですが、データの登録はcurl
経由でやるにはかなりめんどくさい点も行けてないですね
最終的なまとめ
メタデータを利用するとかなりな部分を自動化することが簡単にできるようになります。
また単純なHTTPでアクセスが可能なので扱いやすい言語で自動化ができるようになります。
GCEで利用する簡易なKey-Value Storeとしてはベストな選択肢なのではないでしょうか?