ようへいの日々精進XP

よかろうもん

moto ✕ Rspec で Ruby で書いたライブラリをテストするばい(moto チュートリアル 弐)

モト

inokara.hateblo.jp

moto という AWS SDK のレスポンスを模倣する Python ライブラリについて引き続きです。

Stand-alone Server Mode

moto には Stand-alone Server Mode という Flask で実装された Mock サーバーが提供されていて、HTTP サーバーなので Python に限らず、他の言語ライブラリからも利用出来るのがスーパーメリットだと思いました。

github.com

実際に GitHub リポジトリにも https://github.com/spulec/moto/tree/master/other_langs というディレクトリに RubyJava から利用するサンプルが提供されています。

example

Install moto server

$ cat requirements.txt
moto[server]

$ pip install -r requirements.txt

Start moto server

まずは Help

$ moto_server --help
usage: moto_server [-h] [-H HOST] [-p PORT] [service]

positional arguments:
  service

optional arguments:
  -h, --help            show this help message and exit
  -H HOST, --host HOST  Which host to bind
  -p PORT, --port PORT  Port number to use for connection

EC2 用サーバーを起動

$ moto_server ec2
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Ruby Script

以下のようにインスタンス一覧を取得するだけのライブラリをサンプルとして利用します。

require 'aws-sdk'

class MyEc2

  def ec2
     @ec2 ||= Aws::EC2::Client.new(profile: 'mock_profile',
                                   region: 'us-west-2',
                                   endpoint: 'http://127.0.0.1:5000')
  end

  def list_ec2_instances
    instance_ids = []
    ec2.describe_instances.reservations.each do |res|
      res.instances.each do |instance|
        instance_ids << instance.instance_id
      end
    end
    instance_ids
  end

  def main
    list_ec2_instances.each do |instance|
      instance
    end
  end
end

# myec2 = MyEc2.new
# p myec2.main

moto_server の URL とポートを Aws::EC2::Clientインスタンスを生成する際の引数として endpoint を指定するだけで moto_server が利用出来ます。

最後の 2 行のコメントアウトを外して実行すると以下のようなレスポンスが得られます。

$ bundle exec ruby my_ec2.rb
[]

moto_server を起動したコンソールでは以下のようにログが出力されていることを確認出来るかと思います。

$ moto_server ec2
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [21/Apr/2017 00:37:27] "POST / HTTP/1.1" 200 -

Rspec で moto を利用してテストを行う

上記の Ruby スクリプト(ライブラリ)を moto と Rspec でテストしてみたいと思いますので、以下のようなテストを書きます。

$LOAD_PATH.push('./')
require "spec_helper"
require "my_ec2"
require "pty"

describe 'MyEc2' do
  let(:myec2) { MyEc2.new }
  before(:all) do
    @output, @input = PTY.spawn("moto_server ec2")
    client = Aws::EC2::Client.new(region: 'us-west-2', endpoint: 'http://127.0.0.1:5000', profile: 'mock_profile')
    res = client.run_instances({ image_id: 'ami-20d1c544', min_count: 3, max_count: 3 })
    @instance_ids = res.instances.map { |instance| instance.instance_id }
  end

  it "#ec2" do
    res = myec2.ec2
    expect(res).to be_an_instance_of(Aws::EC2::Client)
  end

  it '#list_ec2_instances' do
    res = myec2.list_ec2_instances
    expect(res).to eq @instance_ids
  end

  it '#main' do
    res = myec2.main
    expect(res).to eq @instance_ids
  end

end

ポイントは before 句で moto_server にアクセスして EC2 インスタンス 3 台を Run Instance している部分です。

実際にテストを走らせてみると…

moto_server が実行出来る環境にて、以下のように rspec を実行してみます。

$ bundle exec rspec spec

MyEc2
  #ec2
  #list_ec2_instances
  #main

Finished in 1.47 seconds (files took 0.16527 seconds to load)
3 examples, 0 failures

うまくテストが通ったようです。

以上

Rspec の書き方がだいぶん怪しいですが、moto_server を使うことでリアルな AWS リソースにアクセスすることなく、スクリプトやライブラリのテストやデバッグが言語に関係無く出来るといのは嬉しい限りです。

有難うございました。

おまけ

moto については 4/22(土)に厳かに執り行われる「JAWS-UG福岡:Reboot#4、荒木さんとAWSの話をしてみたり JAWS DAYS 参加者から話をきいてみたりしよう」にて語らせて頂く予定でござります。

speakerdeck.com

2017 年 04 月 19 日(水)

日課

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

奥さんが作るナムル

がとても美味しい。ご飯のお供にも良し、お酒のアテにも最高。

自分で作ったツール

duster(リソースを削除するやつ)、eryastic(Amazon ESS を操作するやつ)自分で使いながらバグ潰したりしている。もっとエレガントなコードが書けるようになりたい。

2017 年 04 月 18 日(火)

ジョギング

日課

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

バタバタした

一日だった。

そして、一日、一日、気温が上がってきて初夏な陽気。

終日

BGM は TM Network の楽曲。

moto で boto3 を使ったツールやライブラリのテストをするぞ(moto チュートリアル 壱)

どうも、モト春樹です

尊敬するモト冬樹さんのブログ、「ツルの一声」というタイトルでグッと身近に感じたかっぱです。

ameblo.jp

モト春樹と名乗らせて頂きたいと思います。

moto

昨年後半から今年にかけて boto3 や aws-sdk for ruby を使って、ちょっとしたツールやライブラリを書かせてもらう機会が増えてきました。有難いことなのですが、動作確認を行うあたって、AWS リソースを作ったり、消したりしながら書いていたりすると、いつのまにかソースコードデグレしていたりして、その非効率さに頭を悩ませていました。

そんな悩みもあり、ツールやライブラリを書いたらユニットテストくらいは書けるようになりたいということで、テストを書くにあたって必要そうなモジュールを調べていたところ、moto という boto3(boto や boto-core) の結果をシュミレートしてくれるモジュールに出会いましたので試してみました。

github.com

moto なのか boto なのかごっちゃになりそうです。

example

リポジトリ

github.com

チュートリアル環境

$ python --version
Python 2.7.13

$ pip list --format=columns | egrep 'moto|boto|coverage'
boto            2.44.0
boto3           1.4.2
botocore        1.4.85
coverage        4.2
moto            0.4.30

ec2.py

インスタンス ID 一覧を取得する簡単なライブラリです。

import boto3

def get_client():
    """
    Returns the ec2 boto3 client
    """
    return boto3.client('ec2')


def list_ec2_instances():
    """
    List EC2 InstanceId
    """
    ec2 = get_client()

    response = ec2.describe_instances()
    if response:
        for res in response.get('Reservations', []):
            for instance in res.get('Instances', []):
                yield instance['InstanceId']

def main():
    """
    Main entry
    """

    for instance in list_ec2_instances():
        print instance

if __name__ == '__main__':
    main()

コマンドラインで実行すると以下のようにインスタンス ID がヅラヅラ〜と表示されるだけです。

$ python ec2.py
i-12345678901234567
i-12345678
i-xxxxxxxxxxxxxxxxx

tests/ec2_test.py

以下のようにテストを書きました。

ポイントは各テストケースにデコレータとして mock_ec2 を付与している部分。また、__moto_setup でダミーの EC2 インスタンスrun_instances している部分だと思います。

import sys
import os
import StringIO
import unittest
from moto import mock_ec2
from ec2 import get_client, list_ec2_instances, main

class Ec2TestCase(unittest.TestCase):

    def setUp(self):
        """
        setUp will run before execution of each test case
        """
        pass

    @mock_ec2
    def __moto_setup(self):
        """
        Run Instance
        """
        ec2 = get_client()
        reservation = ec2.run_instances(ImageId='ami-f00ba4', MinCount=1, MaxCount=1)
        self.instance_id = reservation['Instances'][0]['InstanceId']

    def tearDown(self):
        """
        tearDown will run after execution of each test case
        """
        pass

    @mock_ec2
    def test_get_client(self):
        """
        check that out get_client function has a valid endpoint
        """
        ec2 = get_client()
        self.assertEqual(ec2._endpoint.host, 'https://ec2.ap-northeast-1.amazonaws.com')

    @mock_ec2
    def test_list_ec2_instances(self):
        """
        check that our bucket shows as expected
        """
        instances = [e for e in list_ec2_instances()]
        self.assertEqual([], instances)

    @mock_ec2
    def test_main(self):
        """
        verifies the execution of the main function
        """
        # setup ec2 environment
        self.__moto_setup()

        # capture stdout for processing
        sys.stdout = mystdout = StringIO.StringIO()

        # run main function
        main()

        content = mystdout.getvalue()
        self.assertEqual(self.instance_id, content.strip())

setup.py

setup.py 自体は初めて書く機会を得ましたが、Ruby で言うところの rake の Rakefile みたいなものなのかなという理解です。今後、もう少し深掘りしていきたいと思います。

この setup.py を利用してテストを実行することになります。

from setuptools import setup, find_packages

setup(
    name='oreno-pj2',
    version='0.0.1',

    description="Oreno Sample Project",
    license='GPLv2',

    author='inokappa',
    author_email='xxxxxxxxxxxxxxxxxx',

    packages=find_packages(
        exclude=['tests']
    ),

    test_suite='tests',

    install_requires=[
        'boto3'
    ],

    tests_require=[
        'moto'
    ],

    entry_points={
        'console_scripts': [
            'ec2 = ec2:main'
        ]
    },
)

テスト

早速、テストを実行してみたいと思います。

$ python setup.py test

以下のように結果が出力されます。

$ python setup.py test
running test
running egg_info
writing requirements to oreno_pj2.egg-info/requires.txt
writing oreno_pj2.egg-info/PKG-INFO
writing top-level names to oreno_pj2.egg-info/top_level.txt
writing dependency_links to oreno_pj2.egg-info/dependency_links.txt
writing entry points to oreno_pj2.egg-info/entry_points.txt
writing manifest file 'oreno_pj2.egg-info/SOURCES.txt'
running build_ext
test_get_client (tests.ec2_test.Ec2TestCase) ... ok
test_list_ec2_instances (tests.ec2_test.Ec2TestCase) ... ok
test_main (tests.ec2_test.Ec2TestCase) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.357s

OK

いい感じです。

coverage

その名の通り、Python コードのカバレッジを計測するモジュールです。

coverage を利用することで、テストを実行してカバレッジの計測や計測した結果を HTML 等でも表示することが出来ます。

coverage run

coverage runPython スクリプトを実行させることが出来ます。

$ coverage run setup.py test

以下のように出力されます。

running test
running egg_info
writing requirements to oreno_pj2.egg-info/requires.txt
writing oreno_pj2.egg-info/PKG-INFO
writing top-level names to oreno_pj2.egg-info/top_level.txt
writing dependency_links to oreno_pj2.egg-info/dependency_links.txt
writing entry points to oreno_pj2.egg-info/entry_points.txt
writing manifest file 'oreno_pj2.egg-info/SOURCES.txt'
running build_ext
test_get_client (tests.ec2_test.Ec2TestCase) ... ok
test_list_ec2_instances (tests.ec2_test.Ec2TestCase) ... ok
test_main (tests.ec2_test.Ec2TestCase) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.718s

OK

coverage report

coverage reportカバレッジのレポートを確認することが出来ます。

$ coverage report -m -i --omit=${除外したいパスを記載}

以下のように出力されます。

Name                Stmts   Miss  Cover   Missing
-------------------------------------------------
ec2.py                 15      1    93%   31
setup.py                2      0   100%
tests/__init__.py       0      0   100%
tests/ec2_test.py      27      0   100%
-------------------------------------------------
TOTAL                  44      1    98%

coverage html

coverage html では coverage report の結果を HTML で出力させることが出来ます。

$ coverage html --omit=${除外したいパスを記載}

実行しても何も出力されませんが、以下のように htmlconv ディレクトリ以下の index.html をブラウザで開くとカバレッジの結果が HTML で確認することが出来ます。

$ open htmlcov/index.html

以下のようにブラウザで確認することが出来ます。

f:id:inokara:20170418100900p:plain

Module を項目をクリックすると、各プログラム毎のカバレッジを確認することが出来ます。

f:id:inokara:20170417101635p:plain

Stand-alone Server Mode

Python 以外の言語ライブラリからも利用可能

moto には Stand-alone Server Mode というモードが存在していて、Flask で実装された API サーバーを起動することが出来ます。この Stand-alone Server Mode を利用することで、Python 以外の言語ライブラリでも moto が利用出来るようになるとのことです。

Stand-alone Server Mode インストールと起動

インストールは以下のように。

$ cat requirements.txt
moto[server]

$ pip install -r requirements.txt

起動は以下のように。

$ moto_server ec2
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

boto3 からアクセスしてみる

以下のような EC2 インスタンスのリストを取得するようなコードを利用します。

import boto3

ec2 = boto3.resource('ec2', region_name='us-west-1', endpoint_url='http://localhost:5000')
for instance in ec2.instances.all():
   print instance

実行すると以下のような結果が出力されます。

$ python test.py
ec2.Instance(id='i-xxxxxxx1')
ec2.Instance(id='i-xxxxxxx2')
ec2.Instance(id='i-xxxxxxx3')
ec2.Instance(id='i-xxxxxxx4')
ec2.Instance(id='i-xxxxxxx5')

先に起動した moto_server には以下のようなログが記録されています。

$ moto_server ec2
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [18/Apr/2017 08:12:14] "POST / HTTP/1.1" 200 -
127.0.0.1 - - [18/Apr/2017 08:12:28] "POST / HTTP/1.1" 200 -
...
127.0.0.1 - - [18/Apr/2017 08:20:42] "POST / HTTP/1.1" 200 -

以上

モト春樹が moto をチュートリアルしてみました。

Python でテストを書いた事がそもそも無いのでテストの書き方から学ぶ必要がありましたが、思ったよりも簡単にモックを使ったテストを書くことが出来ました。これから少しずつですが、自分が書くツールやライブラリでは moto を使ったテストも合わせて実装するようにしたいと思います。

参考

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

引き続き

Eryastic の話し。

github.com

コマンドラインツールとは言え

所詮は手作業なので、意図した構成であるかの確認については他のツールに委ねたいという思いがあり、ドメイン作成、削除、更新時には awspec でテスト出来るように spec ファイルを吐くようにしてみました。

github.com

例えば

Amazon Elasticsearch Service ドメインを作成

$ AWS_PROFILE=xxxxxx AWS_REGION=ap-northeast-1 bundle exec eryastic domain --create --config-file=demo-es1.toml
I, [2017-04-16T13:24:26.961332 #23293]  INFO -- : 以下の構成で Elasticsearch ドメインを作成します.
+-------------------------------+------------------------------------------------------------------------------+
| key                           | value                                                                        |
+-------------------------------+------------------------------------------------------------------------------+
| domain_name                   | demo-es1                                                                     |
| elasticsearch_version         | 2.3                                                                          |
...
| volume_size                   | 10                                                                           |
| automated_snapshot_start_hour | 1                                                                            |
+-------------------------------+------------------------------------------------------------------------------+
処理を続行しますか ? [y|n]:
y
I, [2017-04-16T13:24:38.136936 #23293]  INFO -- : 処理を続行します.
I, [2017-04-16T13:24:39.429898 #23293]  INFO -- : 処理が成功しました.

作成が開始されると、spec ディレクトリには deploy_spec.rb というファイルが生成されています。(事前に spec ディレクトリを作っておいてください。)

bash-3.2$ tree spec/
spec/
├── deploy_spec.rb
└── spec_helper.rb

0 directories, 2 files

実際に awspec を実行してみると…

bash-3.2$ AWS_PROFILE=xxxxx AWS_REGION=ap-northeast-1 bundle exec rake spec:deploy
...

elasticsearch 'demo-es1'
  should exist
  should have access policies "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"\",\n      \"Effect\"...pAddress\": {\n          \"aws:SourceIp\": \"xxx.xxx.xxx.xxx\"\n        }\n      }\n    }\n  ]\n}\n"
  elasticsearch_cluster_config.instance_type
    should eq "t2.micro.elasticsearch"
  elasticsearch_cluster_config.instance_count
    should eq 3
  elasticsearch_cluster_config.dedicated_master_enabled
    should eq false
  elasticsearch_cluster_config.zone_awareness_enabled
    should eq false
  ebs_options.ebs_enabled
    should eq true
  ebs_options.volume_type
    should eq "gp2"
  ebs_options.volume_size
    should eq 10
  snapshot_options.automated_snapshot_start_hour
    should eq 1
  domain_name
    should eq "demo-es1"
  elasticsearch_version
    should eq "2.3"

Finished in 0.56487 seconds (files took 1.96 seconds to load)
12 examples, 0 failures

いい感じです。

作成したドメインを更新

次にインスタンス数を減らしてみたいと思います。

AWS_PROFILE=xxxxx AWS_REGION=ap-northeast-1 bundle exec eryastic domain --update --domain-name=demo-es1 --config-file=demo-es1.toml

以下のように出力されます。

f:id:inokara:20170416133141p:plain

更新処理が開始されると、spec ディレクトリには deploy_spec.rb というファイルが生成されています。(既存の deploy_spec.rb は上書きされます。)

bash-3.2$ tree spec/
spec/
├── deploy_spec.rb
└── spec_helper.rb

0 directories, 2 files

テストを流してみます。

bash-3.2$ AWS_PROFILE=xxxxxx AWS_REGION=ap-northeast-1 bundle exec rake spec:deploy
...

elasticsearch 'demo-es1'
  should exist
  should have access policies "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"\",\n      \"Effect\"...pAddress\": {\n          \"aws:SourceIp\": \"xxx.xxx.xxx.xxx\"\n        }\n      }\n    }\n  ]\n}\n"
  elasticsearch_cluster_config.instance_type
    should eq "t2.micro.elasticsearch"
  elasticsearch_cluster_config.instance_count
    should eq 1
  elasticsearch_cluster_config.dedicated_master_enabled
    should eq false
  elasticsearch_cluster_config.zone_awareness_enabled
    should eq false
  ebs_options.ebs_enabled
    should eq true
  ebs_options.volume_type
    should eq "gp2"
  ebs_options.volume_size
    should eq 10
  snapshot_options.automated_snapshot_start_hour
    should eq 1
  domain_name
    should eq "demo-es1"

Finished in 0.47943 seconds (files took 1.93 seconds to load)
11 examples, 0 failures

実際の構成変更については少々時間がかかる為、マネジメントコンソール上で確認するとインスタンス数は変わっていないかもしれませんが、API 上では意図したインスタンス数になっていることは確認することが出来ます。

ドメインを削除

ドメインを削除してみます。

bash-3.2$ AWS_PROFILE=oreno-profile AWS_REGION=ap-northeast-1 bundle exec eryastic domain --delete --domain-name=demo-es1
I, [2017-04-16T13:36:00.514878 #26010]  INFO -- : 以下の Amazon Elasticsearch Service ドメインを削除します.
+----------------------------------------+------------------------------------------------------------------------------+
| key                                    | value                                                                        |
+----------------------------------------+------------------------------------------------------------------------------+
| domain_id                              | 044703681656/demo-es1                                                        |
| domain_name                            | demo-es1                                                                     |
...
| automated_snapshot_start_hour          | 1                                                                            |
| rest.action.multi.allow_explicit_index | true                                                                         |
+----------------------------------------+------------------------------------------------------------------------------+
処理を続行しますか ? [y|n]:
y
I, [2017-04-16T13:36:04.319120 #26010]  INFO -- : 処理を続行します.
I, [2017-04-16T13:36:05.091255 #26010]  INFO -- : 処理が成功しました.

削除処理が開始されると、spec ディレクトリには delete_spec.rb というファイルが生成されています。

bash-3.2$ tree spec/
spec/
├── delete_spec.rb
├── deploy_spec.rb
└── spec_helper.rb

0 directories, 3 files

テストを流してみます。

bash-3.2$ AWS_PROFILE=xxxxx AWS_REGION=ap-northeast-1 bundle exec rake spec
...

elasticsearch 'demo-es1'
  should not exist (FAILED - 1)

elasticsearch 'demo-es1'
  should exist
  should have access policies "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"\",\n      \"Effect\"...pAddress\": {\n          \"aws:SourceIp\": \"xxx.xxx.xxx.xxx\"\n        }\n      }\n    }\n  ]\n}\n"
  elasticsearch_cluster_config.instance_type
    should eq "t2.micro.elasticsearch"
  elasticsearch_cluster_config.instance_count
    should eq 1
  elasticsearch_cluster_config.dedicated_master_enabled
    should eq false
  elasticsearch_cluster_config.zone_awareness_enabled
    should eq false
  ebs_options.ebs_enabled
    should eq true
  ebs_options.volume_type
    should eq "gp2"
  ebs_options.volume_size
    should eq 10
  snapshot_options.automated_snapshot_start_hour
    should eq 1
  domain_name
    should eq "demo-es1"

Failures:

  1) elasticsearch 'demo-es1' should not exist
     Failure/Error: it { should_not exist }
       expected elasticsearch 'demo-es1' not to exist
     # ./spec/delete_spec.rb:3:in `block (2 levels) in <top (required)>'

Finished in 1.13 seconds (files took 1.68 seconds to load)
12 examples, 1 failure

Failed examples:

rspec ./spec/delete_spec.rb:3 # elasticsearch 'demo-es1' should not exist

まだ、完全に削除が完了していない為でしょうか、delete_spec.rb のテストは Failure となってしまいますし、削除したにも関わらず deploy_spec.rb のテストは Success となってしまいますので注意が必要です。暫く時間を置いてから delete_spec.rb だけを実行してみたいと思います。

以上

eryastic 自体は toml で Amazon Elasticsearch Service の構成を管理する事は出来るので、awspec の spec ファイルを書くことを考えるとコードの二重管理問題が発生しそうですが、自動で吐くようにすることで二重管理問題から解放されて、且つ、環境が意図した内容で構築出来ていることを第三者(第三のツール)の視点で確認出来るというメリットはあると考えています。

余談になりますが、先日 awspec のコミット権を頂きましたので、今後も awspec を頑張っていきつつ Ruby 力を高めていければと考えています。

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 月 15 日(土)

ジョギング

Self Control

Amazon Prime Music で聴いている。

Self Control

Self Control

良い。