97
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GCEのメタデータサーバーを愛でる ~GCEメタデータのすべて~

Posted at

GCPで1番好きなサービスはGAEです。でも2番目はメタデータサーバーな大橋です。
書く書く詐欺をし続けてとうとう書くことになりました。

皆さんGCEのメタデータサーバー使ってますか?
使ってない? 使ってないなら今日から使いましょう。もったいない事この上ないです。

今回は私の大好きなメタデータサーバーの概要や使い方について書いていきたいとも思います。

まとめ

3行でMetadata Serverをまとめると

  • GCEの様々な情報が乗っているHTTP(curlなども可)アクセス可能なデータサーバ
  • Key-Value形式で好きなデータを投入可能
  • 更新検知が可能

です。
これだけシンプルですが非常に強力で、利用シーンがとても多いです。

概要

メタデータサーバーはGCEのインスタンスや、プロジェクトのメタデータを保存しているサーバです。
Web APIが用意されており、GCEのインスタンスからであれば認証無しでアクセスが可能です。

もしGCEをよく触る方であれば、Startup ScriptShutdown ScriptSSH-keyの設定などをしたことがあるかもしれませんが、
これらの情報はすべてメタデータサーバに保存されており、
それぞれのタイミングでGCEのインスタンスがメタデータサーバにアクセスし、情報を取得・利用しています。

メタデータサーバへアクセスするURLは決まっており

のいずれかとなっています。 これらのホスト名はインスタンスが起動する際に/etc/hostsに書き込まれます。

よくGCEのネットワーク周りを設定する方だと
このIPアドレス(169.254.169.254)がL4LBのヘルスチェックの際に利用されるIPだと気がつくかもしれません
メタデータサーバはそういった方法でも利用されています。

GCEの様々な情報が乗っているHTTP(curlなども可)アクセス可能なデータサーバ

Project metadataとInstance metadata

メタデータにはProject metadataInstance 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 ScriptShutdown ScriptSSH-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 instancesgcloud 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を利用する際、

  1. wait-for-changeリクエストを投げる
  2. 更新を待つ
  3. レスポンスが戻る
  4. 処理をする
  5. 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()

こんな使い方

これらの仕組みを利用すると、オートスケール環境での複数サーバへのアプリケーションデプロイに利用できます。

例えば

  1. プロジェクトメタデータにversionというメタデータを用意し、アプリケーションバージョン番号を設定しておく。
  2. 各インスタンスがStartup Script時にversionメタデータに対して、wait-for-changeリクエストを投げて更新検知
  3. アプリケーションが更新されたら、CIなどでインスタンスから見れる場所に配置 (例えばDockerであればCloud Registry等)、versionメタデータを更新する
  4. 各インスタンスがversionメタデータが更新されたことを検知し取得したアプリケーションバージョン番号から新しいアプリケーションを取得・デプロイ
  5. 再度次の更新を待つ

の様な流れが作れます。
簡単なコードを書くと以下の感じです。

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としてはベストな選択肢なのではないでしょうか?

97
80
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
97
80

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?