続 カッコの付け方

AWSを始めとしたクラウドコンピューティング全般と、唯一神emacsにおける()の付け方についてだらだら書きます

AWS EBS SSD (gp2 general purpose) バーストクレジット計算式

「EBS (gp2) のクレジットの回復レートってどんなの?」の質問に即答できなかったので、書いておきます。 結論、数式としては

クレジット 0 -> Fullまでの時間 = 5,400,000(IO) ÷ ベースラインIOPS

となります。

概要と用語

簡潔に

  • EBSは 3000 IOPSまでのバーストクレジットがある
  • 1GBあたり 3 IOPSのベースラインIOPS ただし、 33.333GB ?(つまり 100IOPS以下)の場合でも最低100IOPS
  • バーストの継続時間は、 3000IOPS と、ベースラインのIOPSの差に反比例する 言い換えると ベースラインが 3000IOPSに近ければ長く、遠ければそれだけ短い
  • 1TB超の場合はベースラインが 3000IOPSとなるので、そもそもバーストしない

上記は理解できていると思います。 用語も改めて

  • ベースラインIOPS = バーストなしで利用可能なIOPS上限
  • バーストクレジット = ベースラインを越えるIOPSを利用した時に消費され、ベースライン以下のIOPSしか消費していなければ貯まる

公式ドキュメント

http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html

http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/images/gp2-burst-bucket.png

解説もなにもありません、この絵の通りです。 EBSはOS起動時にIOPSが派手に発生することを見込んで、CPUクレジットとは異なり、いきなり 5,400,000 クレジットから始まります。このクレジットを消費することによって、バーストします。

Volume size (GiB) Baseline performance (IOPS) Maximum burst duration @ 3,000 IOPS (seconds) Seconds to fill empty credit balance
1 100 1862 54,000
100 300 2,000 18,000
214 (Min. size for max. throughput) 642 2,290 8,412
250 750 2,400 7,200
500 1,500 3,600 3,600
750 2,250 7,200 2,400
1,000 3,000 N/A* N/A*
3,334 (Min. size for max. IOPS) 10,000 N/A* N/A*
16,384 (16 TiB, max. volume size) 10,000 N/A* N/A*

上記抜粋表のの 1GB を見て下さい 単純な算数ですが 54,000 * 1000 = 5,400,000 となります。 100GBの場合、1GBの3倍のベースラインですので、54,000 / 3 = 18,000 となっています。

数学的な説明

5,400,000 という数字ですが、単位として示すなら 5,400,000 IO (英語的にはIOsが正しいですが、紛らわしいので IOとします) と読み替えられます。算数の 速さ・時間・距離(地方によっては、は・じ・き と言うらしい)の関係のように、単位変換を式で表すと

まずIOPSは文字通り1秒あたりのIO数ですので IOPS = IO / 1sec とします。そのあと、
Sec = 5,400,000 IO ÷ BaseLine IO / 1 sec 変換して
Sec = 5,400,000 IO × 1 sec / BaseLine IO

5,400,000 の IO と 掛け算の分母である BaseLine の IOが相殺されて
Sec = 5,400,000 / BaseLine
となります。

AmazonLinux や CentOS6 で upstart root以外で実行

もうオワコンである upstart で結構苦戦しました。やりたい事は単純に root以外のユーザーで実行したいということだけ

upstart ?

sysVと systemd の間にあったデーモン管理Tool(というか機構?)です。こいつは sysV とは違って systemdと共存出来ないので、オワコンです。が、残念なことにAmazonLinuxはこれに依存しているサービスが沢山あります。 AmazonLinuxのイケてないところは後述します。

やりたい事

root以外でデーモン起動

setuid / setgid があるじゃん!

残念ながら、 Cent6(RHEL6)をベースにしている AmazonLinuxは古すぎてsetuid/setgid に対応していません。その気になれば差し替えも出来るでしょうが、upstart のような根幹に関わる部分を差し替えるのは難しいはずです。少なくとも再起動は伴うと思いますので、やりません

方法

ubuntu - Running upstart jobs as unprivileged users - Super User

色々ありますが、単に su/sudo でやる方法でも、私の目的のスクリプト(中でデーモンプロセスが動く)は上手く動かなかった。挙動としてはsu コマンドの部分のみプロセスを見ているような動き。 コマンドその物に chmod コマンドで setuidもありですが、今回はシェルスクリプトなので不可能。シェルスクリプトで setuidが使えない理由はセキュリティー上の問題です。結構重要なことですが、詳しくはググって下さい。

結論 setuidgid コマンドを使う

daemon-tools の一部である setuidgid コマンドを使います。古いツールですが、パッチさえ当てればビルド出来ます。

ビルド

kaztr.hatenadiary.com

抜粋

wget http://cr.yp.to/daemontools/daemontools-0.76.tar.gz
tar zxvf daemontools-0.76.tar.gz
admin/daemontools-0.76
wget http://qmail.org/moni.csi.hu/pub/glibc-2.3.1/daemontools-0.76.errno.patch
patch -p1 < daemontools-0.76.errno.patch 
package/compile

使う

upstart のサービス毎の設定ファイルにて

exec setuidgid <ユーザ名> xxxx

AmazonLinuxのイケてないところ

他所のディストリビューションが 全て systemd に行ったのに、未だに sysV upstartなのは、centos6 をベースにしたから。 AmazonLinuxはkernelだけは上げていますが、grub, init系など は centos6 の古典時代のまま。 さらに悪い事に、ECS Agent 等は upstart で動かしているなど、この古い基版の上にAWS謹製のものを作っています。 思うに、今更 死んだ upstart に触りくないし、触ってほしくない。

なんでスパッと切り替えられないかの最大の要因は たぶんAmazonLinuxはローリング・リリースしているから。つまり yum updateだけでメジャーバージョンアップ完了。当然カーネルは無理だが、その他のものはこれで問題ない。しかし、 grub, init系を弄るとなると、結構怖い変更かつ、OS再起動は必須になるはず。だからやらなかったのだとおもうし出来る気がしない。

なので、次のバージョンから 最低でもベースを Cent7 か何かに切り替えた AmazonLinuxと、今のAmazonLinuxの2本立てとなるのが嬉しい。でもなんとなくそうしない気がする、2本立てのリソース裂きを嫌って。

まとめ

upstart root以外ユーザーなら setuidgidが良さげ、

AmazonLinuxへ一言 さっさと systemd化してくれ。。

Google API Client Library 入門

今まで、Cloud Monitoring のカスタムメトリックスを打つために、サンプルを改造する程度の理解しか無かったのですが、ある程度自分の頭で整理出来たので残します。GCP(Google)におけるAPIクライアントライブラリについて、入門からAWSとの違いについて。

Google API Client Library概要

Google(GCP) の各種サービスのAPIを呼び出すためのライブラリです。 ここで言うGCPには、GAE, GCEは勿論、G-Suite(旧Apps) や Mapsも含みます。

主に何をやってくれる?

まず、基本的な部分の説明ですが、APIを呼び出すためには

  1. 認証 (ヘッダなどに込める)
  2. https による RESTfulな操作

が必要です。といいますか、これだけあれば他は不要です。 つまり、AWSだろうがGoogleだろうが、 curl(にかぎらず httpsが喋れるクライアント) があれば、あとは認証情報さえどうにかすれば完了です。 Google API Client Library (API Clientと略) は、シンプルに 上記機能を提供します。特に面倒な 1. 認証 が大きな役目であり、それ以外の機能はとても 薄い実装 といえます。

理論よりさきに実装

書いていて先にコードを書いたほうがいいと思ったので晒します。(文章の流れは無視)

import httplib2
from apiclient import discovery
from oauth2client.client import GoogleCredentials

credentials = GoogleCredentials.get_application_default()
service = discovery.build('monitoring', 'v3', credentials=credentials)
req = service.projects().timeSeries().list(
        name=project_resource,
        filter=filter_strings,
        interval_endTime=end_time.isoformat(),
        interval_startTime=start_time.isoformat(),
        fields="timeSeries.points.value"
        )
ret = req.execute()

project_resource など、service.projects().timeSeries().list() の中の引数は実際のコードの抜粋なので参考程度にしてください。これはmonitoring.projects.timeSeries.listのコールとなります。

https://developers.google.com/apis-explorer/#search/monitoring.projects.timeseries.list/m/monitoring/v3/monitoring.projects.timeSeries.list

コード解説

credentials = GoogleCredentials.get_application_default() 特に触れなくていいいと思います、
service = discovery.build('monitoring', 'v3', credentials=credentials) これも問題無いでしょう、
req = service.projects().timeSeries().list(... がなんとも気持ち悪いコードだと思います(AWS脳には)。なんでわざわざメソッドになるの?ということですが、ここが Client APIの肝です。IPythonなどのREPLを使えばわかりますが、
service.projects(). まで打って 補完しても候補が出てきません。メソッドなので当たり前といえばそうですが、service.projects() を評価して初めて timeSeries リソース(クラス)が存在することが分かりのです。試しに().のチェーンをやめて、1つずつ変数に込めて見て下さい。
つまり、動的にAPIコールのためのクラスを生成しているのです。

また、メソッドの引数(APIパラメータと引数の名前マップ) を確認したいなら

 .__DOC__

を参照すればわかります。IPythonなら ? でも見えます。

Google API Client Library概要 再び

厚い実装・薄い実装

やり玉にAWSを上げると、AWSSDKは非常にぶ厚い実装です。上記で述べた認証は勿論、各種サービス毎に、1API毎に細かくclass-methodを実装し、各サービスを網羅しています。

それに対して GoogleAPI Client は API Clientのソースコード自体に、各サービスが持っている APIを細かく実装するということは行っておらず、classやresourceをダイナミックに取得・生成することにより、ものすごくコンパクトな実装になっています。

  1. 認証についてはどちらも同じ
  2. API操作部分については、 AWSが泥臭く全サービス毎に実装しているのに対して、GoogleAPI clientの中で自動生成している

優劣は一旦置いておいて、
AWS SDKAWS CLI とほぼ同じ領域をカバーする実装であるのに対し、
gcloudコマンド と Google API Client は 全くカバーする範囲が異なります。

gcloud に近い実装としては、 API Client ではなく Google Cloud Client Library というものが対応しています。

github.com

ただしこちらはまだ開発中であるためか、GCE等のサービスには対応していなかったりします。とりあえずAWS SDKに近いのはこちらで、AWS脳のひとはすんなり入れると思います。

薄い実装の利点

APIを作る側(この場合はGoogle)にとってはとてもメリットが大きいです。ここでもAWS SDKと比較しますが、APIの数だけSDKのclass-methodとして実装しているので、APIの仕様ドキュメントは勿論、AWS SDKでもほぼ同じ内容のドキュメントを用意、さらに各種言語毎に作成することになり、かなりのリソースを割くことになります。無論、潜在的なバグも多く含むことになります。
対して、API Clientのような実装の場合、各APIのドキュメントはありますが、API Client側にひとつひとつの対応class-methodに対応したドキュメントを用意する必要がありません。出来ないといったほうが正しいかもしれません。APIs Explorer を使えばOAuth2 で実際に打てるので、四の五の言わずに打って試せ!そしてそれをコードにしろ! というスタンスかもしれません。

また、API本体に仕様変更があっても、API Client側の実装はまず変更不要となるはずです。(使う側は堪ったもんではないですが)

この考え方・実装は、自分がAPIの提供側となった時、とても参考になると思う。

薄い実装の欠点

一言で言うと取っ付きにくい
私個人の感覚ですが、AWS SDKを使ったことがある人で、APIなんて curlさえあれば呼べると分かっていない人(つまりSDKなんて必須でないことをわかっている人以外)、SDKとは丁寧なドキュメントがあるのが正しいと思っている人は、まずそう感じると思います。それをGoogle自身も感じているため Google Cloud Client Library を作っているのだと思います。私も、取っ付きにくいと感じました。

また、根本的な話ですが、GCPは結構頻繁にAPIを弄ります。破壊的な変更は v1,v2 などと旧バージョンを残した上で、エンドポイントが変わる(増える)ことがほとんどですが、パラメータが増える等の対応があった場合、確かに API Client自体は影響受けないですが、API使う側の実装は当然変更となります。ただし、この点は 厚い実装 でも同じですし、厚い実装 と違って新機能のためにわざわざ API Clientのバージョンを上げる必要が無い点は利点に述べた通りです。

まとめ

  • Google API Client Library は主に認証の面倒を見てくれる
  • Google API Client Library の API を呼ぶ部分の実装はかなり 薄い
  • AWS SDK のような 厚い実装が良いなら Google Cloud Client Library を待つ?

自分がAPIを提供する側だったら、この薄い実装 の方がいいなとは思いました。最近は自動生成とかできますけどね。。

Google Cloud SQL 2nd Generation Proxy接続する

8月下旬、 Google Cloud SQL 2nd Generation がGAとなりました。すでに検証はしていますが、今回は Cloud SQL Proxy を中心に書きます。

iga-ninja.hatenablog.com

Cloud SQL Proxy とは?

前回の記事でも記載しましたが、Cloud SQL への接続は全てグローバルIPアドレス となります。これに嫌悪するひとは多いと思いますが、この点はどうすることも出来ません。で、GCEのVMからアクセスする場合、は2つの方法どちらかをとることになります。

  1. 特定IPアドレスからのみアクセスを受け付ける(グローバルIPのみローカルIPは意味ない)
  2. Cloud SQL Proxy を使う

1を採用したでも、GCEの特定VMからのみだときまっていたり、スケールアウトは考える必要がなく、固定台数でのみサービスする場合は、あまり問題はありませんが、スケールアウトするならば新規VM追加の度にIPアドレスを追加していく必要があります。他の方法としては、NATやHAProxy, MaxScale などで、どこかのIPアドレスに束ねてしまう方法ですが、新たなspofを産んでしまうので、あまりよい手とは言えません。

そこで、2の Cloud SQL Proxyです ちなみに、 MySQL Proxy とは全く関係ありません

動作について

Cloud SQL Proxy のやることは、Cloud SQLへの接続(or 認証だけ?)を、一般的な IPアドレス(TCP)経由ではなく、Cloud SQL APIベースで行うものだと推測しています。
だから、IPアドレスにより許可は要らない。
では、プログラム側からどのように参照するかですが、これは UNIX Socket となります。後で手順を書きますが、
Proxy の役目は Cloud SQL への接続を API ベースから UNIX Socket に変換する と言って良いはずです。

構成

上記 Cloud SQL Proxy の役割を理解すると、自ずと構成は Cloud SQL へアクセスする全VMインスタンスで Cloud SQL Proxyを動かす となります。どのみち UNIX Socket なんで、必然的にこうなりますよね。また Docker用の Cloud SQL Proxy もあるようです。これはDockerイメージかな?多分他のコンテナは、 Proxy コンテナへリンクをはって、Proxyコンテナ経由で接続するではなかろうかと

また、各VMインスタンスには、Cloud SQL のスコープ(AWSでいうEC2ロール)を付けておくことを激しく推奨します。スコープ無しでも出来ると思いますが、面倒だと思います。

手順

簡単です。GCSからダウンロードして実行するだけです。多分 Go製かな? https://cloud.google.com/sql/docs/compute-engine-access

wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64
mv cloud_sql_proxy.linux.amd64 cloud_sql_proxy
chmod +x cloud_sql_proxy
sudo mkdir /cloudsql; sudo chmod 777 /cloudsql
sudo ./cloud_sql_proxy -dir=/cloudsql &

この実行により、 /cloudsql/ ディレクトリの下に、各Cloud SQL への接続用 Unix Socketができ、 mysqlでアクセスするなら

mysql -uroot -p -S /cloudsql/[INSTANCE_CONNECTION_NAME]

で接続できます。 [INSTANCE_CONNECTION_NAME] ってなんじゃい?知いますが、大して難しい話ではなくて、-S で指しているのは UNIX Socket の場所です。 Cloud SQL Proxyが INSTAANCE_CONNECTION_NAME(Cloud SQLのね!) で UNIX Socketを作るってだけの話です。

自動起動

ただのアプリーケションなので、自動起動設定が必要です。勝手にデーモン化はするみたいですが。
systemd が使えるなら systemdで
ないなら upstart で設定すればよろし。

HAProxyで RR負荷分散

backend の指定は UNIX Socket でも出来るらしいです。(> 1.5)
動くことは確認出来ました。レプリラグがひどいものを排除する場合は、xinetd などで httpから確認になると思います。

inokara.hateblo.jp

まとめ

Cloud SQL 2nd gen 使うなら Cloud SQL Proxy 一択

メリットが圧倒的に多いので、上記のみとなります。

jsonの戻り値とか、複雑なオブジェクトからスマートにデータを抽出[JMESPath 最強説]

「パイセン、JSONで帰ってくる複雑な構造のオブジェクトをいい感じにするやつしらないっすか?」
「ああ、複雑な構造ね。しらん、専用の関数用意するとか?」
「やっぱ、そっすよねー」
全国津々浦々でこんな会話が繰り広げられてると思います。私も2回ぐらいこのやり取りをしました。この長年の問題に終止符、JMESPath最強じゃね?

JMESPathとは

以前、一回書いてます iga-ninja.hatenablog.com

この時私は勘違いしておりました、JMESPathはJSONだけを捌くライブラリではなく、JSONから素直にコンバート出来る各言語のオブジェクト を捌くことが出来ます。pythonで言うなら list と dictで出来てるものなら JMESPathでクエリ出来ます。# setとかは知りません。

似たようなのに jq コマンドがありますが、JMESPathのほうがいいです、多分。jq 詳しく知らないので、機能・性能面では評価出来ないですが、どう考えても動作環境の広さから言えばこっちが上です。能書きは最後の方に書きます。

JMESPathでこんなことができる!

前回も書きましたが

JMESPath Tutorial — JMESPath

ここ見てもらえばだいたい分かります。しかも試せます。そして私も勘違いしてましたが、このXPathぽい表記が JSON文字列ではなく、各言語のオブジェクト(JSONに変換出来る)に対して通用します。READMEの抜粋ですがこれは、 pythonのdictに対して問い合わせしています。

>>> import jmespath
>>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}})
'baz'

チュートリアルは、ナナメ読みせず、きちんと読んだほうがいいです。

もっと難しい問い合わせ

もっと複雑な問い合わせについて、ちょっと解説します。
http://jmespath.org/examples.html#working-with-nested-data

ここ見てください。awsの ec2_describe_instances の戻りっぽいJSONですが

{
  "reservations": [
    {
      "instances": [
        {"type": "small",
         "state": {"name": "running"},
         "tags": [{"Key": "Name",
                   "Values": ["Web"]},
                  {"Key": "version",
                   "Values": ["1"]}]},
        {"type": "large",
         "state": {"name": "stopped"},
         "tags": [{"Key": "Name",
                   "Values": ["Web"]},
                  {"Key": "version",
                   "Values": ["1"]}]}
      ]
    }, {
      "instances": [
        {"type": "medium",
         "state": {"name": "terminated"},
         "tags": [{"Key": "Name",
                   "Values": ["Web"]},
                  {"Key": "version",
                   "Values": ["1"]}]},
        {"type": "xlarge",
         "state": {"name": "running"},
         "tags": [{"Key": "Name",
                   "Values": ["DB"]},
                  {"Key": "version",
                   "Values": ["1"]}]}
      ]
    }
  ]
}

reservations[].instances[].[tags[?Key=='Name'].Values[] | [0], type, state.name]

[
  [
    "Web",
    "small",
    "running"
  ],
  [
    "Web",
    "large",
    "stopped"
  ],
  [
    "Web",
    "medium",
    "terminated"
  ],
  [
    "DB",
    "xlarge",
    "running"
  ]
]

が帰ってきます。最後の [tags[?Key==`Name`].Values[] | [0], type, state.name] が特にヤヴァイ。select name, type, state from .. みたいに、欲しい項目を複数抜き出してます。プロジェクションってやつです。昔、情報処理とかで勉強した 射影 ですな。 ちょっと気おつけて欲しいのは このサンプルでは ?Key=='Name' と シングルクオートでNameを囲っているが、pythonとかで使うときは

?Key==`Name`

バッククオートで区切ること

pythonから使ってみる

import jmespath

dt = {
  "reservations": [
    {
      "instances": [
        {"type": "small",
         "state": {"name": "running"},
         "tags": [{"Key": "Name",
                   "Values": ["Web"]},
                  {"Key": "version",
                   "Values": ["1"]}]},
        {"type": "large",
         "state": {"name": "stopped"},
         "tags": [{"Key": "Name",
                   "Values": ["Web"]},
                  {"Key": "version",
                   "Values": ["1"]}]}
      ]
    }, {
      "instances": [
        {"type": "medium",
         "state": {"name": "terminated"},
         "tags": [{"Key": "Name",
                   "Values": ["Web"]},
                  {"Key": "version",
                   "Values": ["1"]}]},
        {"type": "xlarge",
         "state": {"name": "running"},
         "tags": [{"Key": "Name",
                   "Values": ["DB"]},
                  {"Key": "version",
                   "Values": ["1"]}]}
      ]
    }
  ]
}


jmespath.search(
    'reservations[].instances[].[tags[?Key==`Name`].Values[] | [0], type, state.name]',
    dt
)

>>[['Web', 'small', 'running'],
 ['Web', 'large', 'stopped'],
 ['Web', 'medium', 'terminated'],
 ['DB', 'xlarge', 'running']]

pythonでのサンプルの解説

まず、

reservations[].instances[].[tags[?Key==`Name`].Values[] | [0], type, state.name]

について解説します。(後でアレンジも試します)

最初の部分

reservations[].instances[]

は特に問題無いと思います。実行したらこんな感じ

In [33]: jmespath.search('reservations[].instances[]', dt)
Out[33]:
[{'state': {'name': 'running'},
  'tags': [{'Key': 'Name', 'Values': ['Web']},
   {'Key': 'version', 'Values': ['1']}],
  'type': 'small'},
 {'state': {'name': 'stopped'},
  'tags': [{'Key': 'Name', 'Values': ['Web']},
   {'Key': 'version', 'Values': ['1']}],
  'type': 'large'},
 {'state': {'name': 'terminated'},
  'tags': [{'Key': 'Name', 'Values': ['Web']},
   {'Key': 'version', 'Values': ['1']}],
  'type': 'medium'},
 {'state': {'name': 'running'},
  'tags': [{'Key': 'Name', 'Values': ['DB']},
   {'Key': 'version', 'Values': ['1']}],
  'type': 'xlarge'}]

次の構文ですが、最初にこのクエリのやりたいことは、 instances配下にある、

  • tagsのNameタグのValue(配列なので Key=Nameのもの)
  • type
  • stateのname(dict なので、pythonで書くと state['name']の値)

だけを抜き出したいということです。チュートリアルを終わった段階で、いきなりこのクエリを書ける人はまず、いないと思います。
問題は、複数の要素を抜き出したいが、すべての要素がフラットに並んでいないという点です。フラットに並んでいるとは、わかりやすい例だとこんな感じ

{
  "people": [
    {"first": "James", "last": "d"},
    {"first": "Jacob", "last": "e"},
    {"first": "Jayden", "last": "f"},
  ]
}

こういう例だと、そもそもJMESPath要らなくね? という話ですが、 (people 以外にもう一個ゴミ要素があるとかなら意味ある) people[*].[first,last] とでもすれば複数のキーの値を抜くことは出来ます。

では、まず失敗例から。tagsの下の Key=name のうちの Values (配列) に着目し、これを狙い打ちます。

In [39]: jmespath.search('reservations[].instances[].tags[?Key==`Name`].Values[]', dt)
Out[39]: [['Web'], ['Web'], ['Web'], ['DB']]

これだと tags.Key='Name'のValuesは引けますが、 reservations[].instances[].tags まで掘り下げているので、どうやっても typeやstateは出てきません。

ちなみに.Valuesのケツの[] は flattenです。無いとこうなります。

In [40]: jmespath.search('reservations[].instances[].tags[?Key==`Name`].Values', dt)
Out[40]: [[['Web']], [['Web']], [['Web']], [['DB']]]

じゃあ、どうすればいいか。reservations[].instances[].tags まで掘り下げないで reservations[].instances[...] でマルチセレクト処理すれば良いということです! お手本の
[tags[?Key=='Name'].Values[] | [0], type, state.name] に着目、 これは

  • tags は tags[?Key==`Name`].Values[] | [0]
  • type は 素直に type
  • state は state.name

抜いています。更に解説を進めますと。
tags[?Key==`Name`].Values[] | [0] のパイプよりも前はもう説明いらないとおもいますが、なんでわざわざ | [0] としているかに注目。Valuesはlist型なので、 ['Web','01'] みたいな値が帰ってくることもありえます、その場合先頭を抜くという意味になります。(これはサンプルなので、参考程度に) あとの type, stateは解説不要ですね!

ちょっとアレンジ

In [46]: jmespath.search('reservations[].instances[].[tags[?Key==`Name`].Values[] | [0], type, state.name ]', dt)
Out[46]:
[['Web', 'small', 'running'],
 ['Web', 'large', 'stopped'],
 ['Web', 'medium', 'terminated'],
 ['DB', 'xlarge', 'running']]

これでも十分なんですが、どうせなら dict の listがかえってくるようにしたいです。どうやればよいのか、、って簡単ですよね!今までの解説を読んでたら、linstance[]の下からマルチセレクトしているだけなので、ここで{}で囲って適当にkeyをつければ終わりです。

In [48]: jmespath.search('reservations[].instances[].{_name: tags[?Key==`Name`].Values[] | [0], _type: type, _state: state.name }', dt)
Out[48]:
[{'_name': 'Web', '_state': 'running', '_type': 'small'},
 {'_name': 'Web', '_state': 'stopped', '_type': 'large'},
 {'_name': 'Web', '_state': 'terminated', '_type': 'medium'},
 {'_name': 'DB', '_state': 'running', '_type': 'xlarge'}]

このサンプルの落とし穴

instances[] からの マルチセレクトであるため、 Nameタグが存在しない場合、除外する という動きになっていません。試しにデータの一部の tag.Key Nameを NNameとかにいじってみてください。多分こうなるはずです。

In [54]: dt2
Out[54]:
{'reservations': [{'instances': [{'state': {'name': 'running'},
     'tags': [{'Key': 'Name', 'Values': ['Web']},
      {'Key': 'version', 'Values': ['1']}],
     'type': 'small'},
    {'state': {'name': 'stopped'},
     'tags': [{'Key': 'Name', 'Values': ['Web']},
      {'Key': 'version', 'Values': ['1']}],
     'type': 'large'}]},
  {'instances': [{'state': {'name': 'terminated'},
     'tags': [{'Key': 'Name', 'Values': ['Web']},
      {'Key': 'version', 'Values': ['1']}],
     'type': 'medium'},
    {'state': {'name': 'running'},
     'tags': [{'Key': 'NName', 'Values': ['DB']},
      {'Key': 'version', 'Values': ['1']}],
     'type': 'xlarge'}]}]}

In [55]: jmespath.search('reservations[].instances[].[tags[?Key==`Name`].Values[] | [0], type, state.name ]', dt2)Out[55]:
[['Web', 'small', 'running'],
 ['Web', 'large', 'stopped'],
 ['Web', 'medium', 'terminated'],
 [None, 'xlarge', 'running']]

最後がNone で抜けてきてます。これを避けるには、更にパイプを使って listの最初の要素がNoneで無いものを抽出するようにするか、これぐらいならpython側でやっちゃうかの2択かなと思います。ココらへんを選べるのがプログラム言語から使える利点ですね。

能書き

対応言語

github.com

python, ruby, php, js, go, lua LWの主要言語ほぼ全部やないか! 言語オタクもここまで来たら凄いわ! しかも jp というコマンドまであるし、goでね。去年の記事では rubyは対応してなかったけど、対応してる。

あとは c/c++, C#, java, swift とかの heavy系
Rust, Elixir とかの 変態関数型言語

ここまで来たら言語オタクから言語変態紳士へクラスチェンジ。

動作環境の優位性

AWSを使っている人なら、少なくとも pythonのJMESPathは絶対に入っているといえる。なぜなら aws-cliが依存しているから。また、Rubist曰くもAWS-SDK2が依存しているらしく、AWSに携わっている人なら、まずJMESPathは入ってる、はず。

ただ、非AWSな人にとっても、各言語でネイティブ対応できていて、バイナリ依存とかも無いので、インストール自体はさくっと簡単に出来る、はず。

なんでこんなにAWSなの?と思ったけど、作者がAWSの中の人?(オーガには入ってる)なので、AWSとは切っても切れない。けどAWS関係なしにこのライブラリはイケてる。

jqとの比較

jq のほうが歴史は古いと思いますが、JMESPathは各言語におそらく言語ネイティブ(俗にゆう pure-javaみたいな)で対応している点が優れていると思います。

あと、作者が一人であるという点も、私はプラスに考えています。なぜならこのJMESPathの仕様自体は、彼がこの世で一番知っているのだから。もちろんバグもどこかにあるでしょうが、JMESPathの仕様に対する実装レベルが(どの言語でも)一定であると想像します。

まとめ & 上達のコツ

  • AWS人ならJMESPath 最高、嫌でも入ってるし!
  • プログラマなら jq より jp のほうが明るい未来(と思う)
  • 学習は、とにかく叩いて試してみる。遅くてもチュートリアルは完全に理解するほうが、結果お得
  • 正規表現と同じ、1手ですべてを解決しようとしない。ちょっとずつ書き足す
  • パイプは素直にパイプ、わからなければパイプがなんとかしてくれる!
  • 今回触れなかったが、UDFも作れるらしい!

個人的な感想ですが、今更 jq を使う理由が見つからないです。過去の遺産(資産)とか関係なしのはなしですよ、もちろん。

GCE Autoscaling in 2016夏

Google Cloud Platform Japan 公式ブログ: GCE ユーザーに高可用性を提供する Regional Managed Instance Groups がベータに 最近 Regional Managed Instance Groups という機能がβになりました。Regional とはなんぞや?ということですが、

この機能のもとでマルチゾーンの設定を行うと、Compute Engine が自動的に VM インスタンスを同一リージョン内の 3 つのゾーンに均等に分散させます。こうしておけば、1 つのゾーンの機能停止といった稀な障害が発生した場合でも、残る 2 つのゾーンに配置された VM インスタンスはサービスの提供を続けることができます。

ということです。逆に言うと今まではゾーン毎にManaged Instance Group (長いので MIGと略)を作る必要があったのですが、本機能でだいぶ楽になります。この機能とAutoscalingについて書きます。

f:id:iga-ninja:20160807111047j:plain

Autoscaling

GCEのAutoscalingは、AWSのそれとはかなり異なります。他のパブリッククラウドの知識は理解の妨げになるので、真っさらな頭でやりましょう。

ASの構造

  1. インスタンステンプレート
  2. インスタンスグループ
  3. (Cloud Monitoring)
  4. AutoScaler

この順番で成り立っている感じです。(Cloud MonitoringはCustom Metricsを使う場合)AWSの場合は

  1. AutoScaling Group launch config
  2. AutoScaling Group
  3. CloudWatch

です。GCEのAutoScalerはそれ自身が閾値基準によるScale-out/in判定を行っている感じです。一個ずつ軽く説明

インスタンステンプレート

AWS launch config 相当です。ようは Scale-out時に立ち上がってくるインスタンスの設定 (Network, Tag, Image, MetaData等) を指定しておく物です。インスタンステンプレートでは リージョンは指定しません。

また、Image (OSイメージ)ですが、これはかなり楽できます。まず、AWSと異なり、Imageは全リージョン共通ですので、1イメージを全世界に撒く必要すらありません。さらにImageには family という属性があり、これを一致させてImage作成すると、 familyでイメージを指定した場合、そのfamilyの中の常に最新版のImageを指す 事ができます。
つまり、AWSだと、AMI作成してから Launch Config のイメージ差し替えが必須でしたが、その1手が全く不要になります。というより、API的にImageだけ差し替えとか出来ないので、これを使わないとかなり面倒な話になります。

なお、family で Imageを指定する方法は、残念ながら Developers Consoleでは存在しないようです。gcloud 必須です。下記の --image-family を指定します。

gcloud compute instance-templates create  |  Cloud SDK  |  Google Cloud Platform

インスタンスグループ

タイトルに出てきた Regional Managed Instance Groupsの話です。インスタンスグループは、インスタンステンプレートを参照します。また AotoScalerも参照?しているはずです。Developers Consoleからみるとこんな感じです。

f:id:iga-ninja:20160807111156p:plain

今回の変更で マルチゾーンが可能になりました。GCEのゾーンはAWSと異なり、2msのペナが無いので、使わない理由がありません。

f:id:iga-ninja:20160807111221p:plain

ヘルスチェックについて

自分も誤解していたので、ヘルスチェックについて。まずAWS識者の為の一言ですが
ELBベースのヘルスチェックは、GCEには存在しません。またヘルスチェックを行うのはLBではありません。

GCEのヘルスチェックはそれ単体で独立しています。HTTPの場合は、ローカルリンクアドレス (169.254,169.254) から飛んで来ます。 よって、ヘルスチェックを使い回すことが出来ます。

AutoScaler

驚くほど少ないパラメータしか指定出来ません。まず現状では出来ないこと

gcloud compute instance-groups managed set-autoscaling  |  Cloud SDK  |  Google Cloud Platform

  • スケール Out/In のルールを分離出来ない
  • スケール Inしないという戦略はとれない
  • 閾値超過したとき、何台追加するかを指定できない (step)

出来ることは

  • インスタンス最大/最小台数指定
  • CoolDown期間の指定 (AS発動間隔)
  • CPUベースのAS発動
  • カスタムメトリックスベースの発動
  • LB利用率ベースの..

次に、それらをGCEはどうさばいているか。

  • スケール Out/In のターゲットとして CPU 60% とだけ指定する
  • CPU 60% を超えた場合、上昇速度に応じて Setpを決める ( 緩やかな上昇なら1台、急激に上昇したら一気に5台とか多分そんな実装っぽい)
  • CPU 60% を下回ったら即座に スケール Inとか、そんなにバカじゃない。ある程度余裕をもって勝手にスケールIn

一言でいうと こまけぇこたぁいいんだよ!! いい感じにやっとくから

デプロイのやり方

Image差し替えの方法です。

新規Image作成

Image作成は結構面倒です。GCEは動いているDisk -> Imageが出来ないので、

  1. Snapshot
  2. Snapshot -> Disk作成
  3. Disk -> Image作成

という結構な面倒なことになります。この時、前述の Family を指定していれば、インスタンステンプレートの差し替え不要。

デプロイ前の AutoScaling Stop

他所でも言及されていますが、 Stopは出来ますが、再開は出来ません。set-autoscaling で再設定が必要です。

まとめ

  • GCEのAutoScalingは細かくユーザーが指定出来ないが、動かしてみたところかなり賢い。正に こまけぇこたぁいいんだよ!!
  • Regional Managed Instance GroupsでZone毎にいちいち作らなくても良くなった。
  • Image の Familyは使うべき。でもgcloudでのみ指定可能。

今回のアップデートでかなり使いやすくなりました。また、AutoScaling自体、かなり大きな変更が入っているはずなので、他所の古い記事とは整合しない部分が多数あります。あとは、AWSのようにLifeCycle-Hookなどが現状存在せす、Shutdown-ScriptというOS外で指定できる機能があります(SLAは無い、走る保証は無い)。細かい色々不足している機能はありますが、ユーザーがいじれる部分を極限まで減らし、本当に必要なものだけを高いレベルで実現出来ているのは、さすがGoogleだと思います。

nginx + php-fpm の組み合わせのErrorログ出力を理解する

ngix + php-fpm の組み合わせで特に Errorログについて整理しときます。

初心者向け補足

プール向け設定ファイル = /etc/php-fpm.d/www.conf
全体の設定ファイル = /etc/php-fpm.conf

php-fpmのログまとめ

error.log

php-fpm 全体で出す Errorログです。これは global ディレクティブでしか設定出来ません。 このファイルは、デフォルトでは各プールの標準エラーなどを出力されることはありません。各プールのエラーを出力したいならば catch_workers_output = yes を、プールの設定ファイルに記載します。 ただし、このパラーメータには注釈があります。

; Redirect worker stdout and stderr into main error log. If not set, stdout and
; stderr will be redirected to /dev/null according to FastCGI specs.
; Note: on highloaded environement, this can cause some delay in the page
; process time (several ms).
; Default Value: no

パフォーマンスに若干影響するということなので、気になる人はデフォルトの noが良いでしょう。

このファイルはphp-fpm マスタープロセスがつかみます。ので、logrotate時にシグナルを打つ必要があります。

$pool.access.log

プール毎 に設定できる access.logです。 このファイルはphp-fpm ワーカープロセスがつかみます。ので、logrotate時にシグナルを打つ必要があります。

xxx-slow.log

プール毎 に設定できる slow-logです。 php-fpm ワーカープロセスが出しているはずですが、ファイルはつかんでいません。

www-error.log

rpmデフォルトの設定を参考に書きます。プール毎 に設定できるのですが、これは php-fpm が出力しているのではなく、言語としての php が出力しています。 プール毎に設定できるので、これを使うのがベストプラクティスだと思います。

ファイルの出力は、エラーが発生したタイミングで発生します。また、php-fpmが出力していないので、出力するユーザーはワーカプロセスの実行ユーザー = プール向け設定ファイルで設定するユーザー&グループとなります。例えば nginx + php-fpm の組み合わせ centosなどの場合、 /var/log/php-fpm の所有権が apache となっているため、 www.confで 実行ユーザーをnginxなどとしていると ファイル出力出来ません。

nginx側のログ

ワーカープロセスの標準エラーは、実は nginxのerror.logにそのまま出力されます。しかし、日付等が記載されず、そのままなので、その点不便です。

おまけ php-fpm のログ出力パーミッションを変更したい

/var/log/php-fpm/xxx.log のうち、php-fpm自身が出力するものはすべて 600で吐き出され、しかも rootがオーナーです。これを設定ファイルでかえることは残念ながら出来ないようです。パッチは出ています。

https://github.com/sandyskies/php-filemode-patch