ようへいの日々精進XP

よかろうもん

Amazon Elasticsearch Service ドメインを安全(当社比)に操作出来て、且つドメインの設定を toml フォーマットで管理するコマンドラインツールを作った

作ったもの

github.com

eryastic と書いて、「えりゃすてぃっく」と呼んでいる。

なんで作ったの?

  • マネジメントコンソールでの Amazon Elasticsearch Service ドメインの操作をそろそろ卒業したかった
  • 手順書作成を簡素化したかった
  • TOML フォーマットを使ってみたかった

何が出来る?

将来的には、スナップショット作成、レストア機能も取り込む予定。

何が安全なん?

当社比(自分比)だけど、ドメイン作成、削除、更新時には、必ず設定内容を確認するステップを設けた。

例えば、ドメインを削除したい場合。

bash-3.2$ AWS_PROFILE=xxxxx AWS_REGION=ap-northeast-1 bundle exec eryastic domain --delete --domain-name=oreno-es1
I, [2017-04-16T13:05:20.957089 #19624]  INFO -- : 以下の Amazon Elasticsearch Service ドメインを削除します.
+----------------------------------------+------------------------------------------------------------------------------+
| key                                    | value                                                                        |
+----------------------------------------+------------------------------------------------------------------------------+
| domain_id                              | 123456789012/oreno-es1                                                       |
| domain_name                            | oreno-es1                                                                    |
| arn                                    | arn:aws:es:ap-northeast-1:123456789012:domain/oreno-es1                      |
| created                                | true                                                                         |
| deleted                                | false                                                                        |
| endpoint                               | search-oreno-es1-xxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com  |
| processing                             | false                                                                        |
| elasticsearch_version                  | 2.3                                                                          |
| access_policies                        | {                                                                            |
|                                        |   "Version": "2012-10-17",                                                   |
|                                        |   "Statement": [                                                             |
...
|                                        | }                                                                            |
| instance_type                          | t2.micro.elasticsearch                                                       |
| instance_count                         | 1                                                                            |
| dedicated_master_enabled               | false                                                                        |
| zone_awareness_enabled                 | false                                                                        |
| ebs_enabled                            | true                                                                         |
| volume_type                            | gp2                                                                          |
| volume_size                            | 10                                                                           |
| automated_snapshot_start_hour          | 1                                                                            |
| rest.action.multi.allow_explicit_index | true                                                                         |
+----------------------------------------+------------------------------------------------------------------------------+
処理を続行しますか ? [y|n]:

おっと、削除するのは oreno-es01 ではなく、oreno-es02 だった…場合には、n を入力することで処理を中断させることが出来る。

n
W, [2017-04-16T13:09:13.896709 #19624]  WARN -- : 処理を中止します.

デモ

事前に

Eryastic を実行する環境には Amazon Elasticsearch Service を操作出来る権限が必要になります。

今回は以下のように Amazon Elasticsearch Service のアクセスポリシーに Eryastic を実行する環境の IP アドレスを登録しています。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:123456789012:domain/demo-es1/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": [
            "xxx.xxx.xxx.111",
            "xxx.xxx.xxx.222"
          ]
        }
      }
    }
  ]
}

help

$ bundle exec eryastic --help domain
Usage:
  eryastic domain

Options:
  -c, [--create], [--no-create]    # Amazon Elasticsearch Service ドメインを作成する.
  -d, [--delete], [--no-delete]    # Amazon Elasticsearch Service ドメインを削除する.
  -e, [--export], [--no-export]    # Amazon Elasticsearch Service ドメインの設定を export する.
  -l, [--list], [--no-list]        # Amazon Elasticsearch Service ドメインの一覧を取得する.
  -u, [--update], [--no-update]    # Amazon Elasticsearch Service ドメイン構成を更新する.
  -n, [--domain-name=DOMAIN_NAME]  # Amazon Elasticsearch Service ドメイン名を指定する.
  -f, [--config-file=CONFIG_FILE]  # Amazon Elasticsearch Service 設定ファイルを指定する.

Amazon Elasticsearch Service ドメインを操作する.

設定ファイル例

toml フォーマットで以下のように記載する.

[main]
domain_name = "oreno-es2"
elasticsearch_version = "2.3"
access_policies = '''
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:xxxxxxxxxxx:domain/oreno-es/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "xxx.xxx.xxx.xxx"
        }
      }
    }
  ]
}
'''

[elasticsearch_cluster_config]
instance_type = "t2.micro.elasticsearch"
instance_count = 1
dedicated_master_enabled = false
zone_awareness_enabled = false

[ebs_options]
ebs_enabled = true
volume_type = "gp2"
volume_size = 10

[snapshot_options]
automated_snapshot_start_hour = 1

Amazon Elasticsearch Service ドメインの一覧を取得する

  • run
$ AWS_PROFILE=xxxxx AWS_REGION=ap-northeast-1 bundle exec eryastic domain --list
  • output
I, [2017-04-15T22:23:40.059602 #25839]  INFO -- : Amazon Elasticsearch Service ドメインの一覧を取得します.
+-------------+-----------------------------------------------------------------------------+-----------------------+---------+---------+------------+
| domain_name | endpoint                                                                    | elasticsearch_version | created | deleted | processing |
+-------------+-----------------------------------------------------------------------------+-----------------------+---------+---------+------------+
| oreno-es1   | search-oreno-es1-xxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com | 2.3                   | true    | false   | false      |
+-------------+-----------------------------------------------------------------------------+-----------------------+---------+---------+------------+

Amazon Elasticsearch Service ドメインを作成する

  • run
$ AWS_PROFILE=xxxxx AWS_REGION=ap-northeast-1 bundle exec eryastic domain --create --config-file=setting.toml
  • output
I, [2017-04-15T22:25:44.451627 #26254]  INFO -- : 以下の構成で Elasticsearch ドメインを作成します.
+-------------------------------+-------------------------------------------------------------------------------+
| key                           | value                                                                         |
+-------------------------------+-------------------------------------------------------------------------------+
| domain_name                   | oreno-es2                                                                     |
| elasticsearch_version         | 2.3                                                                           |
| access_policies               | {                                                                             |
|                               |   "Version": "2012-10-17",                                                    |
|                               |   "Statement": [                                                              |
...
|                               |   ]                                                                           |
|                               | }                                                                             |
| instance_type                 | t2.micro.elasticsearch                                                        |
| instance_count                | 1                                                                             |
| dedicated_master_enabled      | false                                                                         |
| zone_awareness_enabled        | false                                                                         |
| ebs_enabled                   | true                                                                          |
| volume_type                   | gp2                                                                           |
| volume_size                   | 10                                                                            |
| automated_snapshot_start_hour | 1                                                                             |
+-------------------------------+-------------------------------------------------------------------------------+
処理を続行しますか ? [y|n]:
y
I, [2017-04-15T22:27:30.283862 #26254]  INFO -- : 処理を続行します.
I, [2017-04-15T22:27:31.292834 #26254]  INFO -- : 処理が成功しました.

Amazon Elasticsearch Service ドメインを削除する

  • run
$ AWS_PROFILE=xxxxx AWS_REGION=ap-northeast-1 bundle exec eryastic domain --delete --domain-name=oreno-es1
  • output
I, [2017-04-15T22:29:44.633351 #27057]  INFO -- : 以下の Amazon Elasticsearch Service ドメインを削除します.
+-------------------------------+-------------------------------------------------------------------------------+
| key                           | value                                                                         |
+-------------------------------+-------------------------------------------------------------------------------+
| domain_name                   | oreno-es1                                                                     |
| elasticsearch_version         | 2.3                                                                           |
| access_policies               | {                                                                             |
|                               |   "Version": "2012-10-17",                                                    |
|                               |   "Statement": [                                                              |
...
|                               |   ]                                                                           |
|                               | }                                                                             |
| instance_type                 | t2.micro.elasticsearch                                                        |
| instance_count                | 1                                                                             |
| dedicated_master_enabled      | false                                                                         |
| zone_awareness_enabled        | false                                                                         |
| ebs_enabled                   | true                                                                          |
| volume_type                   | gp2                                                                           |
| volume_size                   | 10                                                                            |
| automated_snapshot_start_hour | 1                                                                             |
+-------------------------------+-------------------------------------------------------------------------------+
処理を続行しますか ? [y|n]:
y
I, [2017-04-15T22:27:30.283862 #26254]  INFO -- : 処理を続行します.
I, [2017-04-15T22:27:31.292834 #26254]  INFO -- : 処理が成功しました.

Amazon Elasticsearch Service ドメインの設定を export する

  • run
$ AWS_PROFILE=xxxxx AWS_REGION=ap-northeast-1 bundle exec eryastic --export --domain-name=oreno-es2 --config-file=oreno-es2.toml
  • output
I, [2017-04-15T22:32:25.935996 #27636]  INFO -- : Amazon Elasticsearch Service ドメイン oreno-es2 設定を export します.
[main]
domain_id = "xxxxxxxxxxxxxxxx/oreno-es2"
arn = "arn:aws:es:ap-northeast-1:xxxxxxxxxxxxxxxx:domain/oreno-es2"
domain_name = "oreno-es2"
endpoint = ""
elasticsearch_version = "2.3"
access_policies = '''
{
  "Version": "2012-10-17",
  "Statement": [
...
  ]
}
'''

[elasticsearch_cluster_config]
instance_type = "t2.micro.elasticsearch"
instance_count = 1
dedicated_master_enabled = false
zone_awareness_enabled = false



[ebs_options]
ebs_enabled = true
volume_type = "gp2"
volume_size = 10


[snapshot_options]
automated_snapshot_start_hour = 1

--config-file で指定したファイルに toml フォーマットにて設定が出力されている.

Amazon Elasticsearch Service ドメイン構成を更新する

  • run
$ AWS_PROFILE=xxxxx AWS_REGION=ap-northeast-1 bundle exec eryastic domain --update --domain-name=oreno-es2 --config-file=oreno-es2.toml
  • output

f:id:inokara:20170416000613p:plain

辛かったこと

TOML フォーマットの制約

  • 設定キーに .(ピリオド)は使えない(Amazon Elasticsearch Service の advanced_optionrest.action.multi.allow_explicit_index というキーがあって泣いた)
  • 厳密に言うと、キーを "(ダブルクォーテーション)で括れば良いが、文字列扱いとなってしまい解析した後の処理がめんどくさい(一旦、諦めた)

自分の Ruby 力の無さ…

  • オブジェクト ID を意識ぜず、Hash が入った変数をガンガン使いまわしていたら、Hash#delete しているメソッドを通った後の変数の中身が変わってて焦った…
  • 結局は Marshal クラスを使って以下のようにディープコピーして回避した… Ruby 面白いけど難しい
      config_tmp = config_parse(config_file)
      %w(domain_id arn endpoint).each do |key|
        config_tmp.delete(key.to_sym) if key.to_sym
      end

      log.info('以下の構成で Elasticsearch ドメインを作成します.')
      config = Marshal.load(Marshal.dump(config_tmp))
      puts display_domain_resources(config_tmp)

2017 年 04 月 13 日(木)

ジョギング

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

なんか

終日バタバタしていた。

意図的ではもちろん無いが、潜在的なバグを作ってしまって申し訳ない気持ちで一杯。他にも色々。

気を引き締めつつ、気負わずやっていきたいと思う。

Datadog APM の Python 用クライアント dd-trace-py をざっくりチュートリアルする 〜 Elasticsearch(Amazon Elasticsearch Service) 編 〜

引き続き

github.com

を弄っています。

Elasticsearch でも APM

Elasticsearch でも簡単に始めることが出来ます。もちろん、Amazon Elasticsearch Service でも無問題(IAM の処理が必要となる場合があります)です。尚、dd-trace-py については導入済みという体で進めます。

本記事作成時の環境

本記事を書くにあたり利用した環境は以下の通りです。

$ python --version
Python 2.7.13

$ pip list --format=columns | egrep 'elasticsearch|ddtrace'
ddtrace           0.8.0
elasticsearch     5.3.0

尚、Amazon Elasticsearch Service のアクセスポリシーは IP アドレスによるアクセス制御で利用します。

Python から Elasticsearch を扱う場合

以下のモジュールを利用します。

github.com

Elastic 謹製です。

サンプルアプリケーション

今回は Elasticsearch に読み込ませた 20 万件程度のレストランデータを検索するコマンドラインツールを作ってみたので、そのコマンドラインツールを dd-trace-py を利用して各処理を追跡して可視化してみたいと思います。

gist.github.com

超ざっくりで Elasticsearch のクエリチューニングは一切行っていませんが、一応、以下のように --name--location オプションで指定したキーワードを含むレストランのリストがダーッと標準出力に表示されます。

$ python restaurant-search.py --name '焼き鳥' --location '福岡' 2> /dev/null
焼き鳥 一郎 | 福岡市博多区美野島2-11-14 |
焼き鳥の屋台 花山 | 福岡市東区箱崎1 |
焼き鳥 たかつ 三苫店 | 福岡市東区三苫1-16-44 |
焼き鳥 木鶏 | 福岡市中央区平尾4-5-15日之出第2平尾ビル |
炭火焼き鳥 ちびっこ大将 | 福岡市中央区高砂2-11-33北村コーポ 1F |
焼き鳥 いわた | 福岡市中央区渡辺通5-24-37レジデンス江崎 2F |
博多水炊きと焼き鳥 鳥善 | 福岡市中央区薬院2-15-2ルミエール薬院1F |

ちなみに、レストランデータは[こちら(https://github.com/livedoor/datasets)のデータを利用させて頂き、stream2esAmazon Elasticsearch Service に放り込んでいます。

Patch

以下のように elasticsearch-py にパッチを当てるだけで Elasticsearch へのクエリを追跡出来るようになります。

from ddtrace import Pin, patch, patch_all
patch(elasticsearch=True)

ソースコードを大幅に手をいれる必要が無いのが嬉しいですね。

tracer.wrap

今回は Elasticsearch へのクエリ以外に、検索処理を担う関数をはじめ、スクリプトの各種関数についてもパフォーマンスをトレースしてみたいと思いますので、tracer.wrap を利用してみたいと思います。

tracer.wrap は関数全体をトレースするデコレータで、以下のように関数の上にちょこんとのっけるだけで、関数の処理結果を Datadog に送信することが出来るようになります。

...
@tracer.wrap('scroll_index', service='sample-app')
def scroll_index(scroll_size, sid):
    try:
        while (scroll_size > 0):
            page = es().scroll(scroll_id=sid, scroll='2m')
            sid = page['_scroll_id']
            scroll_size = len(page['hits']['hits'])
            output(page['hits']['hits'])
    except Exception, e:
        print e
...

ちなみに、デコレータとは対象となる関数をいじることなく、その関数の挙動にてを加える手法という認識で、上記だと scroll_index(scroll_size, sid): という関数に対して、@tracer.wrap という関数でデコレート(装飾)することになります。

さて、今日は何処で呑もうかな、何を食べようかな

やっぱもつ鍋ばい

もつ福岡 で検索してみます。

$ python restaurant-search.py --name 'もつ' --location '福岡' 2> /dev/null
もつもつ天神店 | 福岡市中央区天神335久保田ビル2F |
もつ壙 | 福岡市博多区中洲3-3-3 |
...
...
二十四 永徳屋 博多もつ鍋 | 福岡市博多区美野島1-4-32松永ビル1F |

さすが、もつ鍋王国の博多です。

f:id:inokara:20170412194116p:plain

span = tracer.current_span()
span.set_tag('name', unicode(name, 'utf-8', 'ignore'))

上記のように span.set_tag で Span に対して任意のタグを付与することが出来ます。

福岡で焼肉食べたいばってん、禁煙の席があるといいねえ

もつ福岡 そして 禁煙 で検索します。

$ python restaurant-search.py --name '焼肉' --location '福岡' --keyword '禁煙' 2> /dev/null
焼肉処 國 | 福岡市南区野間2-7-13ヴィラージュ野間 1F | 席数 25席 (掘りごたつ4つとカウンター5席)   駐車場 無  予約 予約可  貸切 可 (20人以下可)   禁煙・喫煙 全面喫煙可  カード 不可

一軒しかヒットしないのは寂しいですが、以下のように検索に利用したキーワードが span のタグとして確認することが出来ます。

f:id:inokara:20170412194714p:plain

Elasticsearch の検索が関数の殆どの時間を使っていることがひと目で判ります。

ということで

dd-trace-py を使うことで、アプリケーションの関数毎のパフォーマンスやデータベースへのアクセス時間ソースコードに殆ど手を加える事無く Datadog で監視出来るというのは嬉しい限りです。

小ネタ道場一本勝負 〜 俺にその JSON を一行でくれよ 〜

その JSON を…

{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "name": "name"
          }
        },
        {
          "term": {
            "description": "keyword"
          }
        },
        {
          "term": {
            "address": "location"
          }
        }
      ]
    }
  }
}

一行で欲しい時がある。

ということで、一本

jq の -c オプションを使えば一瞬で。

$ echo '{
>   "query": {
>     "bool": {
>       "must": [
>         {
>           "term": {
>             "name": "name"
>           }
>         },
>         {
>           "term": {
>             "description": "keyword"
>           }
>         },
>         {
>           "term": {
>             "address": "location"
>           }
>         }
>       ]
>     }
>   }
> }' | jq -c
{"query":{"bool":{"must":[{"term":{"name":"name"}},{"term":{"description":"keyword"}},{"term":{"address":"location"}}]}}}

意図した通りに一行で頂きました。

{"query":{"bool":{"must":[{"term":{"name":"name"}},{"term":{"description":"keyword"}},{"term":{"address":"location"}}]}}}

ありがとう。

あざっした

$ jq --version
jq-1.5

$ jq --help
jq - commandline JSON processor [version 1.5]
Usage: jq [options] <jq filter> [file...]

        jq is a tool for processing JSON inputs, applying the
        given filter to its JSON text inputs and producing the
        filter's results as JSON on standard output.
        The simplest filter is ., which is the identity filter,
        copying jq's input to its output unmodified (except for
        formatting).
        For more advanced filters see the jq(1) manpage ("man jq")
        and/or https://stedolan.github.io/jq

        Some of the options include:
         -c             compact instead of pretty-printed output;
         -n             use `null` as the single input value;
         -e             set the exit status code based on the output;
         -s             read (slurp) all inputs into an array; apply filter to it;
         -r             output raw strings, not JSON texts;
         -R             read raw strings, not JSON texts;
         -C             colorize JSON;
         -M             monochrome (don't colorize JSON);
         -S             sort keys of objects on output;
         --tab  use tabs for indentation;
         --arg a v      set variable $a to value <v>;
         --argjson a v  set variable $a to JSON value <v>;
         --slurpfile a f        set variable $a to an array of JSON texts read from <f>;
        See the manpage for more options.

2017 年 04 月 10 日(月)

終日

頭痛。後頭部あたりが痛い。背中の張りに引っ張られているような痛み。

色々とガタが…

奥さん

体調不良でお休み。

夕方には少し元気になったようで夕飯を作ったりしていたので一安心。