Amazon Athena の API を使ってみた (2017/05)

http://docs.aws.amazon.com/athena/latest/ug/release-notes.html#may-18-2017

2017年5月18日に Amazon Athena にて API が公開されました。
managed presto としての魅力を感じつつも API が存在しないということで original の presto を使っている人にとっては魅力が薄かったサービスですが、 API が公開されることでできることも増えました。

この記事では、 Amazon Athena の API の使い勝手について概観してみようと思います。

API で出来ること

公式ドキュメントにあるように、 API 経由で出来ることは以下になります。

http://docs.aws.amazon.com/athena/latest/APIReference/API_Operations.html

  • BatchGetNamedQuery
  • BatchGetQueryExecution
  • CreateNamedQuery
  • DeleteNamedQuery
  • GetNamedQuery
  • GetQueryExecution
  • GetQueryResults
  • ListNamedQueries
  • ListQueryExecutions
  • StartQueryExecution
  • StopQueryExecution

いくつか API がありますが、出来ることは大別して以下の3つです。

  • Query の実行に関する操作

    • ***QueryExecution
  • Query の実行結果に関する操作
    • GetQueryResults
  • NamedQuery (SavedQuery) に関する操作
    • ***NamedQuery

AWS CLI のセットアップ

この記事では Amazon Athena の API の呼び出しはすべて cli 経由で例示します。
Amazon Athena の API 呼び出しを行うために、あらかじめ awscli を最新版にアップデートしておきます。

$ pip install -U awscli --ignore-installed six
(snip.)
$ aws --version
aws-cli/1.11.90 Python/2.7.10 Darwin/16.5.0 botocore/1.5.53

また、現時点(2017年5月23日)では Amazon Athena は Tokyo Region に来ていないため、region の設定を Athena が動作する region に設定しておく必要があります。

$ cat ~/.aws/config
[default]
region = us-east-1

サポートしていない region を指定して Amazon Athena の cli を実行すると、処理がフリーズし応答がなくなります。(これは、エラーメッセージが表示された方が親切だと思います。)

QueryExecution

StartQueryExecution

http://docs.aws.amazon.com/athena/latest/APIReference/API_StartQueryExecution.html

任意の Presto Query を実行します。
以下からの例では、再現性を考慮しあらかじめ最初から用意されているサンプルデータベースのテーブルを対象にします。
(sampledb.elb_logs)

$ aws athena start-query-execution 
  --query-string 'select * from sampledb.elb_logs limit 1;' 
  --result-configuration OutputLocation=s3://hogehoge/athena-execution-result/
{
    "QueryExecutionId": ".........."
}

--query-string に実行する Presto Query を指定します。
--result-configuration OutputLocation=..... で指定した S3 Bucket に実行結果を保存します。

API の結果として返却される QueryExecxutionId という値を使用して、当該 Query については以後操作することになります。

GetQueryExecution

http://docs.aws.amazon.com/athena/latest/APIReference/API_GetQueryExecution.html

実行した Query の状態などの情報を取得します。

$ aws athena get-query-execution 
  --query-execution-id ()
$ aws athena get-query-execution 
>   --query-execution-id ..........
{
    "QueryExecution": {
        "Status": {
            "SubmissionDateTime": 1495539953.596, 
            "State": "SUCCEEDED", 
            "CompletionDateTime": 1495539955.596
        }, 
        "Query": "select * from sampledb.elb_logs limit 10", 
        "Statistics": {
            "DataScannedInBytes": 850058, 
            "EngineExecutionTimeInMillis": 1651
        }, 
        "ResultConfiguration": {
            "OutputLocation": "s3://hogehoge/athena-execution-result/...........csv"
        }, 
        "QueryExecutionId": ".........."
    }
}

この API の結果からは、Query の現在の状況(実行中か、完了しているか)、開始 / 終了時間などが取得できます。
状態の種類については以下公式ドキュメントに記載されています。
http://docs.aws.amazon.com/athena/latest/APIReference/API_QueryExecutionStatus.html

QUEUED | RUNNING | SUCCEEDED | FAILED | CANCELLED

個人的に、当該 API を cli から使用するときは、 StartQueryExecutionGetQueryExecutionjq コマンドを用いて pipe で繋いで、正しく実行されているかどうかをひと目で確認できるようにしています。
(毎回手で実行するのは手間なので、 warpper shell を用意しています)

$ aws athena get-query-execution 
  --query-execution-id  
  `aws athena start-query-execution --query-string 'select * from kuso_query;' --result-configuration OutputLocation=s3://hogehoge/athena-execution-result/ | jq -r '.QueryExecutionId'`
{
    "QueryExecution": {
        "Status": {
            "SubmissionDateTime": 1495540557.77, 
            "State": "FAILED", 
            "CompletionDateTime": 1495540557.914, 
            "StateChangeReason": "Database, table or column name not found. Please check your query."
        }, 
        "Query": "select * from kuso_query", 
        "Statistics": {
            "DataScannedInBytes": 0, 
            "EngineExecutionTimeInMillis": 67
        },
(snip.)
}

ListQueryExecutions

http://docs.aws.amazon.com/athena/latest/APIReference/API_ListQueryExecutions.html

過去に実行した Query の履歴が取得できます。
特に request parameter で検索条件が指摘できないため、基本的に登録された日時が新しいものから順に取得されます。

$ aws athena list-query-executions 
  --max-results 3
{
    "NextToken": "......", 
    "QueryExecutionIds": [
        "........", 
        "........", 
        "........"
    ]
}

こちらも結果として QueryExecutionId しか返却されず可読性が悪いので、以下のように jq で pipe してみます。
GetQueryExecution には複数の QueryExecutionId が渡せる BatchGetQueryExecution が存在するのでこちらを用います。

$ aws athena batch-get-query-execution  
  --query-execution-ids 
  `aws athena list-query-executions --max-results 3 | jq -r ".QueryExecutionIds[]" | tr 'n' ' '`
{
    "UnprocessedQueryExecutionIds": [], 
    "QueryExecutions": [
        {
            "Status": {
                "SubmissionDateTime": 1495540557.77, 
                "State": "FAILED", 
                "CompletionDateTime": 1495540557.914, 
                "StateChangeReason": "Database, table or column name not found. Please check your query."
            }, 
            "Query": "select * from kuso_query", 
(snip.)
    ]
}

このような形で、直近の Query の実行状況を 1 liner で確認することができます。

GetQueryResults

http://docs.aws.amazon.com/athena/latest/APIReference/API_GetQueryResults.html

実行した Query のデータを取得することができる API です。

$ aws athena get-query-results  
  --query-execution-id ........
{
  "ResultSet": {
    "Rows": [
      {
        "Data": [
          {
            "VarCharValue": "request_timestamp"
          },
          {
            "VarCharValue": "elb_name"
          },
          {
            "VarCharValue": "request_ip"
          },
(snip.)
        ]
      },
      {
        "Data": [
          {
            "VarCharValue": "2015-01-06T12:00:01.612598Z"
          },
          {
            "VarCharValue": "elb_demo_006"
          },
          {
            "VarCharValue": "243.72.152.87"
          },
(snip.)
    "ResultSetMetadata": {
      "ColumnInfo": [
        {
          "Scale": 0,
          "Name": "request_timestamp",
          "Nullable": "UNKNOWN",
          "TableName": "",
          "Precision": 1073741824,
          "Label": "request_timestamp",
          "CaseSensitive": true,
          "SchemaName": "",
          "Type": "varchar",
          "CatalogName": "hive"
        },
(snip.)
}

ただしこの結果内容は、プログラムで扱う分にはまだ良いですが、awscli から扱うには直感的な内容とはい言えないです。

awscli からは、StartQueryExecution 時に指定した OutputLocation から S3 経由で取得する、という方が楽かもしれません。

$ aws s3 cp  
  `aws athena get-query-execution --query-execution-id ........ | jq -r ".QueryExecution.ResultConfiguration.OutputLocation"` ./result.csv

download: s3://hogehoge/athena-execution-result/...........csv to ./result.csv

***NamedQuery

NamedQuery という用語が聞きなれなかったので何を指しているかよくわからなかったのですが、これは Amazon Athena の画面上では Saved Query と表現されているもののようです。

Athena ではよく使う Query などをあらかじめ登録しておくことができる機能がありますが、当該 API はその SavedQuery を操作する API になります。

CreateNamedQuery

http://docs.aws.amazon.com/athena/latest/APIReference/API_CreateNamedQuery.html

$ aws athena create-named-query 
  --name test --description 'for test'  
  --database sampledb  
  --query-string 'select * from sampledb.elb_logs limit 10;' 
{
    "NamedQueryId": "........"
}

QueryExecution と同じような形で NamedQueryId という ID が返却されます。

ListNamedQueries

http://docs.aws.amazon.com/athena/latest/APIReference/API_ListNamedQueries.html

登録されている SavedQuery (NamedQuery) の一覧を取得できます。
特に request parameter で検索条件が指摘できないため、基本的に登録された日時が新しいものから順に取得されます。namedescription などで絞込はできません。

$ aws athena list-named-queries 
  --max-results 3
{
    "NamedQueryIds": [
        "........", 
        "........", 
        "........"
    ], 
    "NextToken": "....."
}

GetNamedQuery

http://docs.aws.amazon.com/athena/latest/APIReference/API_GetNamedQuery.html

あらかじめ登録されている SavedQuery (NamedQuery) の情報を取得できます。
NamedQueryId のみを検索条件に指定可能で、 namedescription などで絞込はできません。

NamedQueryId があらかじめわかっている場合は、以下のような形で pipe で繋ぐことで Query を 1 liner で発行することは一応できます。

$ aws athena start-query-execution 
  --query-string "`aws athena get-named-query --named-query-id ........ | jq -r ".NamedQuery.QueryString"`"  
  --result-configuration OutputLocation=s3://hogehoge/athena-execution-result/
{
    "QueryExecutionId": "........"
}

個人的な感想

ここまで、だらだらと各 API について awscli の例をもとに書いてきました。

いままで JDBC 経由、もしくは Amazon Athena の web console 経由でしか使用ができなかった状況にくらべると格段と可能性は広がったように思えますが、個人的には以下の点で物足りなさを感じています。

  • 全体的に API のインターフェースが気がきいてない
  • ListQueryExecutions API で所定の条件で絞込、ならびに並び替えができない
    • たとえば、実行中の Query だけ取得する、実行時間が長い Query を取得する、ということが API 単体ではできない。
  • ListQueryExecutions の返却結果が QueryExecutionId だけで、情報量が少ない
  • GetQueryResults の使い勝手が悪い
  • 基本的に API の数が少ない
  • このタイミングで Amazon Athena 自身の機能拡張は特に無かった

などなど。

正直、今の機能では積極的にシステム・サービスに組み込んで行くには不足している点が多いと思いますが、期待されているサービスでもありますので今後の進化を期待したいと思います。

個人的に想像しているユースケース

Amazon Athena の API が出る、という話を聞いて、個人的に以下のようなユースケースで使いたいなと感がていました。蛇足になりますが、以下列挙します。

実行時間が長い Query を検知して stop する

現状の API では使い勝手が良くないですが、以下の API を組み合わせることで実現可能です。

  • ListQueryExecutions
  • BatchGetQueryExecution
  • StopQueryExecution

where 句に partition が指定されていない Query を検知して stop する

2017年5月22日現在、Amazon Athena は Tokyo Region に来ていないため、 Tokyo Region の S3 を Athena で使用する場合はどうしても転送量が発生します。

ものすごい巨大なデータ群が入っている S3 Bucket を data source にしている場合、partition を設定していなかったり、もしくは Query に partition 情報が含まれていない場合、膨大な転送量が発生してクラウド破産をしてしまう恐れがあります。
そのため、partition が指定されていない Query を検知し次第、stop をする、というようなユースケースが考えられます。

本来は、Amazon Athena 単体でこのような機能が備わっていてほしいですが、 API を用いることで実現することは可能です。

レポーティングバッチ

おそらくアプリケーション使用用途で一番うれしいのはこのケースだと思います。

レポーティングバッチから Amazon Athena を呼び出して何かしらのレポート処理を行いたい場合、JDBC 経由で対話的に Amazon Athena に繋ぐしか無い状況下では、 Query 実行結果に引きづられてずっとプロセスが待機する必要があったと思います(作りによりますが)

API が提供されたことで、 Query を submit する処理と結果を polling する処理を別に分けることもできますし、そもそも標準機能で実行結果を自動的に S3 にアップできるようにもなりました。

まとめにかえて

長々と書いてきましたが、Amazon Athena は期待の大きいサービスでもありますので、本体の機能も、API の機能についても、より使いやすいものに進化してもらえると、ユーザーとしては嬉しく感じます。

続きを読む

AWS Lambdaから実行したEC2上のPythonでモジュールがimportできない

S3に動画ファイルが配置されたことを契機にLambdaでEC2上のPythonをキックし、
ffmpy経由でffmpegを起動し動画を処理するようなプログラムを作っていたのですが、
どうもffmpyが正常にimportできないので、現状と解決した際の情報共有に書き残します。

環境

実行用コードは下記のようにec2-userのホームディレクトリ配下に配置されています。

/home/ec2-user/hoge/
├── MAIN1_KICKED_BY_LAMBDA.py # メインメソッド
├── fuga
│   └── hogefuga.py           # 動画編集用モジュール
...

下記の設定をrootユーザに対して行いました。

  • pyenv -> Anaconda3.4.1 (Python 3.6)
  • pip install ffmpy
  • ffmpegをインストール (下記スクリプト)
ffmpegcpl.sh
#!/bin/sh

sudo yum -y install autoconf automake cmake freetype-devel gcc gcc-c++ git libtool make mercurial nasm pkgconfig zlib-devel

mkdir ~/ffmpeg_sources

#Yasm
cd ~/ffmpeg_sources
git clone --depth 1 git://github.com/yasm/yasm.git
cd yasm
autoreconf -fiv
./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin"
make
make install
make distclean

#libx264
cd ~/ffmpeg_sources
git clone --depth 1 git://git.videolan.org/x264
cd x264
PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --enable-static
make
make install
make distclean

#libx265
cd ~/ffmpeg_sources
hg clone https://bitbucket.org/multicoreware/x265
cd ~/ffmpeg_sources/x265/build/linux
cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DENABLE_SHARED:bool=off ../../source
make
make install

#libfdk_aac
cd ~/ffmpeg_sources
git clone --depth 1 git://git.code.sf.net/p/opencore-amr/fdk-aac
cd fdk-aac
autoreconf -fiv
./configure --prefix="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean

#libmp3lame
cd ~/ffmpeg_sources
curl -L -O http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz
tar xzvf lame-3.99.5.tar.gz
cd lame-3.99.5
./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --disable-shared --enable-nasm
make
make install
make distclean

#libopus
cd ~/ffmpeg_sources
git clone http://git.opus-codec.org/opus.git
cd opus
autoreconf -fiv
./configure --prefix="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean

#libogg
cd ~/ffmpeg_sources
curl -O http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.gz
tar xzvf libogg-1.3.2.tar.gz
cd libogg-1.3.2
./configure --prefix="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean

#libvorbis
cd ~/ffmpeg_sources
curl -O http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.4.tar.gz
tar xzvf libvorbis-1.3.4.tar.gz
cd libvorbis-1.3.4
LDFLAGS="-L$HOME/ffmeg_build/lib" CPPFLAGS="-I$HOME/ffmpeg_build/include" ./configure --prefix="$HOME/ffmpeg_build" --with-ogg="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean

#libvpx
cd ~/ffmpeg_sources
git clone --depth 1 https://chromium.googlesource.com/webm/libvpx.git
cd libvpx
./configure --prefix="$HOME/ffmpeg_build" --disable-examples
make
make install
make clean

#FFmpeg
cd ~/ffmpeg_sources
git clone http://source.ffmpeg.org/git/ffmpeg.git
cd ffmpeg
PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$HOME/ffmpeg_build" --extra-cflags="-I$HOME/ffmpeg_build/include" --extra-ldflags="-L$HOME/ffmpeg_build/lib" --bindir="$HOME/bin" --pkg-config-flags="--static" --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265
make
make install
make distclean
hash -r

現状・試したこと

実行するコードについては、公開を控えさせてください。。。
Lambdaから実行したときのログは下記のとおりで、どうやらec2-userのpyenv(存在しない)を見にいって落ちているようです。

Traceback (most recent call last):
  File "/home/ec2-user/hoge/MAIN1_KICKED_BY_LAMBDA.py", line 88, in <module>
    hogefuga.exeExtractWav(TMP_DIRECTORY2 + '/', nameonly)
  File "/home/ec2-user/hoge/fuga/hogefuga.py", line 175, in exeExtractWav
    extractWav(filename)
  File "/home/ec2-user/hoge/fuga/hogefuga.py", line 162, in extractWav
    ff.run()
  File "/home/ec2-user/.pyenv/versions/anaconda3-4.3.1/lib/python3.6/site-packages/ffmpy.py", line 99, in run
    raise FFExecutableNotFoundError("Executable '{0}' not found".format(self.executable))
ffmpy.FFExecutableNotFoundError: Executable 'ffmpeg' not found

SSH接続しrootで直接実行した際はroot側のpyenvを見にいき問題なく実行されるのですが、解決策としては以下の2つでしょうか。

  • ec2-user環境にも同様にpyenv, ffmpeg環境を構築する
  • 実行ファイル類をrootのホーム配下へ移動する

そもそも直接実行とLambda実行でパスが変わる原因を明らかにしないとすっきりしませんが・・・。

(2017.05.23 17:12追記)

  • ec2-user環境にも同様にpyenv, ffmpeg環境を構築する
  • 実行ファイル類をrootのホーム配下へ移動する

両方試したものの、下記のエラーが出て終了。

----------ERROR-------
failed to run commands: exit status 1
Traceback (most recent call last):
  File "MAIN1_KICKED_BY_LAMBDA.py", line 17, in <module>
    import formatchg
  File "/root/fuga/hoge/fugahoge.py", line 10, in <module>
    from ffmpy import FFmpeg
ImportError: No module named ffmpy

続きを読む

put_role_policyするとMalformedPolicyDocumentExceptionが発生した

pythonでRoleのpolicyを設定使用としたところ以下のエラーが発生して小一時間ハマりました。

botocore.errorfactory.MalformedPolicyDocumentException: An error occurred (MalformedPolicyDocument) when calling the PutRolePolicy operation: The policy failed legacy parsing

使用したプログラムは次のようなものです。

# coding:utf-8
# !/usr/bin/python

import boto3
iamClient = boto3.client('iam')
policy = '''
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
'''
iamClient.put_role_policy(
    RoleName='MyRole',
    PolicyName='Policy_Of_MyRole',
    PolicyDocument=policy
)

原因

どうやらヒアドキュメントの1行目が改行になってしまっていることが原因のようでした。
以下のようにpolicyの設定行を変更したところ正常に設定できるようになりました。

policy = '''{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
'''

続きを読む

AmazonAthenaをCLIから使う

よーやくCLIから使えるようになりました。
早速試しましょう

前提条件

  • S3にQuery用のダミーデータが入っているものとします
  • S3にAthenaの実行結果保存用ディレクトリが作成されているものとします
  • Athenaは us-east-1 を使用して実行しています

実行環境

コマンド
aws --version
結果
aws-cli/1.11.89 Python/3.5.0 Darwin/16.5.0 botocore/1.5.52

手元にコマンドがない場合はアップデートしましょう

アップデート
sudo pip install -U awscli --ignore-installed six
  • 筆者の環境ではDatapipelinを使用してDynamoDBのあるテーブルデータをS3にバックアップしたものをサンプルデータとして使用しています。
  • DATABASEに関しては以前Javaからの接続の際検証で作成した dynamodb という名前のDATABASEが存在します。
  • テーブルとして、DynamoDBのJSONデータに対応するように作成した dynamodb.sample1 を使用します。

Queryの実行

コマンド
aws athena start-query-execution 
    --query-string 'select count(*) from dynamodb.sample1;' 
    --result-configuration Database=dynamodb 
    --result-configuration OutputLocation=s3://xxxxxxxxxxxxxxxxxxxxxxxxxxx/cli-test/
{
    "QueryExecutionId": "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

結果の取得

コマンド
aws athena get-query-results 
    --query-execution-id "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
結果
{
    "ResultSet": {
        "ResultSetMetadata": {
            "ColumnInfo": [
                {
                    "CatalogName": "hive",
                    "Name": "_col0",
                    "Scale": 0,
                    "SchemaName": "",
                    "Precision": 19,
                    "Type": "bigint",
                    "CaseSensitive": false,
                    "Nullable": "UNKNOWN",
                    "Label": "_col0",
                    "TableName": ""
                }
            ]
        },
        "Rows": [
            {
                "Data": [
                    {
                        "VarCharValue": "_col0"
                    }
                ]
            },
            {
                "Data": [
                    {
                        "VarCharValue": "500000"
                    }
                ]
            }
        ]
    }
}

実行したQueryの詳細を取得する

コマンド
aws athena get-query-execution 
    --query-execution-id  "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
結果
{
    "QueryExecution": {
        "Status": {
            "SubmissionDateTime": 1495440535.706,
            "State": "SUCCEEDED",
            "CompletionDateTime": 1495440549.995
        },
        "QueryExecutionId": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "Statistics": {
            "DataScannedInBytes": 71512882,
            "EngineExecutionTimeInMillis": 13930
        },
        "Query": "select count(*) from dynamodb.sample1",
        "ResultConfiguration": {
            "OutputLocation": "s3://xxxxxxxxxxxxxxxxxxxxxxxxxxx/cli-test/xxxxxxxxxxxxxxxxxxxxxxxxxxx.csv"
        }
    }
}

格納先のオブジェクトパスが取得できます。

これでAthenaを簡単に使える!東京リージョンまだだけど、S3を介してやり取り出来るのでまあまあ使えるんじゃないかという所感。

続きを読む

デプロイ方法のメモ

はじめてデプロイ作業したのでその時につまづいたところをまとめました。

pipのインストール

pipはPythonのパッケージ管理システムです
標準で入ってるらしいですが、自分のPCには入ってなかったので、
入れ方から使い方までメモしました。

Pythonが入ってることを確認しましょう

$ python -V

次にpipが入ってるか確認しましょう。

$ python -m pip -V

python初めての人入っていないので、
https://bootstrap.pypa.io/get-pip.py
ここからpip.pyをダウンロードしてください。

$ sudo python get-pip.py

でインストール。入ったことを確認したら

$ sudo pip install -U pip

でpipをアップデートします。

aws-cli をインストールする

コマンドラインからAWSを操作できる公式のコマンドラインツールです。
http://docs.aws.amazon.com/ja_jp/streams/latest/dev/kinesis-tutorial-cli-installation.html

$ sudo pip install awscli

でインストールできますが、

DEPRECATION: Uninstalling a distutils installed project (six) has been deprecated and will be removed in a future version. This is due to the fact that uninstalling a distutils project will only partially uninstall the project.

というエラーが出ました。どうやらSixというのが邪魔してるみたいです。
SixはPython 2と3の互換性ライブラリのことです。

$ sudo pip install awscli --upgrade --ignore-installed six

これでインストールできます。

セットアップ

aws configure

と打つと対話形式で
AWSの設定を打ち込んでいきます。

AWS Access Key ID [None]:アクセスキーID
AWS Secret Access Key [None]:シークレットアクセスキー
Default region name [None]: ap-northeast-1は東京のこと
Default output format [None]:無視

~ $ aws s3 ls

で確認

あとは、公開したいサイトのディレクトリーに入り
deploy.shファイルを作成し。

./deploy.sh

でファイル一覧がでてきて
yesで
デプロイ完了です。

続きを読む

ビンパッキング問題を利用してクラウド利用料を安くする

ビンパッキング問題を利用したクラウド利用の最適化

さて、AWSやAzure、GCPのようなクラウドを利用していると、どのアプリケーションをどのサイズの仮想マシンに登載すれば効率的なのか、迷うことがあります。
アプリケーションのCPU、メモリ、ディスク利用量が判明しているとして、アプリケーションをどのサイズの仮想マシンに入れれば良いか、コロケーションした方が良いのか、分散した方が良いのか・・・いろいろと考えることはあります。
クラウド利用歴の長い技術者は経験則でどのサイズを選ぶのか、わかったりするもののようです。
しかし今回はちょっとアプローチを変えて、最適化問題として解決策を見出だせないかな、と考えてみました。

例えば以下のような状況で、どのサイズのアプリケーションをどのサイズの仮想マシンに入れれば、効率的でしょうか?

1.png

まずはおことわり

最適化問題が面白そうだったので、勉強がてら、自分にとって身近な問題で考えてみました。
最適化問題歴1週間なので、間違っている箇所やアドバイスはご指摘ください。

なお、勉強に使ったのは以下の本です。
Python言語によるビジネスアナリティクス 実務家のための最適化・統計解析・機械学習

問題設定

今回は必要な数のアプリケーションをクラウドの仮想マシンに登載した結果、費用が一番安くなる構成を、ビンパッキング問題として求めたいと思います。

ビンパッキング問題とは、ある入れ物(箱やビン、コンテナ)に荷物(重さや個数が定められている)を詰める際、必要な入れ物の最少数を求める組み合わせ最適化問題です。
例えば、引っ越しの際に荷物をダンボールに詰めると思いますが、そのダンボールの数を最少にする詰め方を解くものです。

2.png

荷物をダンボールに詰めるのであれば、ダンボール箱の体積(横×縦×高)と耐荷重量に対し、荷物の体積と重さを考慮して入れます。
これを見た時、荷物をアプリケーション、ダンボールを仮想マシンとして、体積や重さをCPU, RAM, Disk, 費用に置き換えれば、クラウドの仮想マシン利用を最適化することができる気がしたのが、今回の発端です。

環境

Pythonで最適化問題を解いてみたいと思います。
Pythonでは最適化問題を解くのに便利なライブラリが色々提供されていまして、ここに詳しく説明されています。
最適化におけるPython

今回はPython3.5(Anaconda)でopenoptを使いました。
OSはCentOS7.3です。
Openoptは数理最適化のモデルを作るライブラリです。

この環境へのopenoptのインストール方法は以下のとおりです。

conda install --channel https://conda.anaconda.org/cachemeorg funcdesigner openopt
pip install cvxopt
pip install glpk

やること

今回はアプリケーションを3種類に分けます。
小さいアプリケーション、中くらいのアプリケーション、大きいアプリケーションです。
それぞれのリソース利用量は以下とします。

小さいアプリケーション 中くらいのアプリケーション 大きいアプリケーション
CPU: 0.2 CPU: 0.5 CPU: 2.4
RAM: 256MB RAM: 512MB RAM: 2048MB
DISK: 1GB DISK: 10GB DISK: 40GB

これらを以下のEC2インスタンスサイズのうち、どれに詰め込むと一番安くなるか、ビンパッキング問題を使って解きます。

M4.4xlarge R3.2xlarge C4.2xlarge
CPU: 16vCPU CPU: 8vCPU CPU: 8vCPU
RAM: 64GB RAM: 61GB RAM: 15GB
Disk: 100GB Disk: 100GB Disk: 100GB
$1.032 / hour $0.798 / hour $0.504 / hour

なお、単価は本日(2017年5月21日)時点の東京リージョンでLinuxのオンデマンドインスタンスを使った場合の値段としています。参考
また、ディスクの費用(EBS)は含んでおりません。

プログラム

プログラム全文はこちらです。

# import openopt
from openopt import *

# 小さいアプリケーション、中くらいのアプリケーション、大きいアプリケーションの数を設定します。
small_num = 20
med_num = 12
large_num = 9

apps = []

# 各アプリケーションのリソース利用量をdictにし、リストに追加します。
for i in range(small_num):
    small_app = {
        'name': 'small%d' % i,
        'cpu': 0.2,
        'mem': 256,
        'disk': 1
        }
    apps.append(small_app)

for i in range(med_num):
    med_app = {
        'name': 'medium%d' % i,
        'cpu': 0.5,
        'mem': 512,
        'disk': 10
        }
    apps.append(med_app)

for i in range(large_num):
    large_app = {
        'name': 'large%d' % i,
        'cpu': 2.4,
        'mem': 2048,
        'disk': 40
        }
    apps.append(large_app)


# AWS EC2インスタンスのサイズを設定します。
# 各リソースを9掛けにしているのは、OSがリソースの10%を使うと仮定しているためです。
instance_sizes = [
    {
        'name': 'm4.x4large',
        'cost': 1.032 * 24 * 30,
        'size': {
            'cpu': 16 * 0.9,
            'mem': 64 * 1024 * 0.9, 
            'disk': 1000 * 0.9
        }
    },
    {
        'name': 'r3.2xlarge',
        'cost': 0.798 * 24 * 30,
        'size': {
            'cpu': 8 * 0.9,
            'mem': 61 * 1024 * 0.9, 
            'disk': 1000 * 0.9
        }
    },
    {
        'name': 'c4.2xlarge',
        'cost': 0.504 * 24 * 30,
        'size': {
            'cpu': 8 * 0.9,
            'mem': 15 * 1024 * 0.9, 
            'disk': 1000 * 0.9
        }
    }
]

# ビンパッキングです。
# openoptのBPPという関数を使います。
def bin_pack_instance(apps, instance_size):
    cost = instance_size['cost']    
    p = BPP(apps, instance_size['size'], goal = 'min')
    r = p.solve('glpk', iprint = 0)
    instances = len(r.xf)
    total_cost = instances * cost
    return r, instances, total_cost

# 実行します。
# 各インスタンスサイズでビンパッキングを行い、最も安くなるものを探します。
if __name__ == '__main__':
    list_cost = []
    for instance in instance_sizes:
        r, instances, total_cost = bin_pack_instance(apps, instance)
        list_cost.append({'instance': instance['name'], 'total_cost': total_cost})

        print("\r") 
        print("Bin packing for : {0}".format(instance['name']))
        print("Total number of apps is " + str(len(apps)))
        print("Total {0} instance used is {1}".format(instance['name'], instances))
        print("Total cost is {0}".format(total_cost))

        for i,s in enumerate(r.xf):
            print ("Instance {0} contains {1} apps".format(i, len(s)))
            print("\t CPU: {0}vCPU\t RAM: {1}MB\t Disk: {2}GB"
                  .format(r.values['cpu'][i], r.values['mem'][i], r.values['disk'][i]))
            print("\t Contains: {0}".format(r.xf[i]))

        print("\r")  

    print("Result: {0}".format(list_cost))

結果はこちらのとおりになります。

------------------------- OpenOpt 0.5625 -------------------------
problem: unnamed   type: MILP    goal: min
solver: glpk
  iter  objFunVal  log10(maxResidual)  
    0  0.000e+00               0.00 
    1  0.000e+00            -100.00 
istop: 1000 (optimal)
Solver:   Time Elapsed = 0.12   CPU Time Elapsed = 0.12
objFuncValue: 3 (feasible, MaxResidual = 0)

Bin packing for : m4.x4large
Total number of apps is 41
Total m4.x4large instance used is 3
Total cost is 2229.12
Instance 0 contains 18 apps
     CPU: 14.200000000000001vCPU     RAM: 13312.0MB  Disk: 228.0GB
     Contains: ('small0', 'small3', 'small4', 'small5', 'small6', 'small7', 'small8', 'small13', 'medium0', 'medium1', 'medium2', 'medium3', 'medium4', 'medium5', 'large3', 'large4', 'large6', 'large7')
Instance 1 contains 17 apps
     CPU: 14.4vCPU   RAM: 13312.0MB  Disk: 212.0GB
     Contains: ('small1', 'small2', 'small9', 'small10', 'small11', 'small12', 'small14', 'small15', 'small16', 'small17', 'small18', 'small19', 'large0', 'large1', 'large2', 'large5', 'large8')
Instance 2 contains 6 apps
     CPU: 3.0vCPU    RAM: 3072.0MB   Disk: 60.0GB
     Contains: ('medium6', 'medium7', 'medium8', 'medium9', 'medium10', 'medium11')


------------------------- OpenOpt 0.5625 -------------------------
problem: unnamed   type: MILP    goal: min
solver: glpk
  iter  objFunVal  log10(maxResidual)  
    0  0.000e+00               0.00 
    1  0.000e+00            -100.00 
istop: 1000 (optimal)
Solver:   Time Elapsed = 0.24   CPU Time Elapsed = 0.23
objFuncValue: 5 (feasible, MaxResidual = 0)

Bin packing for : r3.2xlarge
Total number of apps is 41
Total r3.2xlarge instance used is 5
Total cost is 2872.8
Instance 0 contains 3 apps
     CPU: 7.199999999999999vCPU  RAM: 6144.0MB   Disk: 120.0GB
     Contains: ('large0', 'large4', 'large7')
Instance 1 contains 11 apps
     CPU: 7.199999999999999vCPU  RAM: 6912.0MB   Disk: 107.0GB
     Contains: ('small5', 'small6', 'small7', 'small8', 'small9', 'small18', 'small19', 'medium0', 'medium1', 'large1', 'large2')
Instance 2 contains 13 apps
     CPU: 7.0vCPU    RAM: 6912.0MB   Disk: 91.0GB
     Contains: ('small0', 'small1', 'small2', 'small10', 'small11', 'small12', 'small13', 'small14', 'small15', 'small16', 'small17', 'large5', 'large6')
Instance 3 contains 8 apps
     CPU: 7.199999999999999vCPU  RAM: 6656.0MB   Disk: 122.0GB
     Contains: ('small3', 'small4', 'medium2', 'medium3', 'medium4', 'medium5', 'large3', 'large8')
Instance 4 contains 6 apps
     CPU: 3.0vCPU    RAM: 3072.0MB   Disk: 60.0GB
     Contains: ('medium6', 'medium7', 'medium8', 'medium9', 'medium10', 'medium11')


------------------------- OpenOpt 0.5625 -------------------------
problem: unnamed   type: MILP    goal: min
solver: glpk
  iter  objFunVal  log10(maxResidual)  
    0  0.000e+00               0.00 
    1  0.000e+00            -100.00 
istop: 1000 (optimal)
Solver:   Time Elapsed = 0.14   CPU Time Elapsed = 0.14
objFuncValue: 5 (feasible, MaxResidual = 0)

Bin packing for : c4.2xlarge
Total number of apps is 41
Total c4.2xlarge instance used is 5
Total cost is 1814.4
Instance 0 contains 7 apps
     CPU: 5.4vCPU    RAM: 5120.0MB   Disk: 100.0GB
     Contains: ('medium0', 'medium1', 'medium2', 'medium3', 'medium4', 'medium5', 'large6')
Instance 1 contains 15 apps
     CPU: 7.0vCPU    RAM: 7168.0MB   Disk: 108.0GB
     Contains: ('small8', 'small9', 'small10', 'small14', 'small16', 'small17', 'small18', 'small19', 'medium6', 'medium7', 'medium8', 'medium9', 'medium10', 'medium11', 'large0')
Instance 2 contains 14 apps
     CPU: 7.199999999999999vCPU  RAM: 7168.0MB   Disk: 92.0GB
     Contains: ('small0', 'small1', 'small2', 'small3', 'small4', 'small5', 'small6', 'small7', 'small11', 'small12', 'small13', 'small15', 'large3', 'large4')
Instance 3 contains 3 apps
     CPU: 7.199999999999999vCPU  RAM: 6144.0MB   Disk: 120.0GB
     Contains: ('large1', 'large2', 'large5')
Instance 4 contains 2 apps
     CPU: 4.8vCPU    RAM: 4096.0MB   Disk: 80.0GB
     Contains: ('large7', 'large8')

Result: [{'instance': 'm4.x4large', 'total_cost': 2229.12}, {'instance': 'r3.2xlarge', 'total_cost': 2872.8}, {'instance': 'c4.2xlarge', 'total_cost': 1814.4}]


長くなりますが、結果としてc4.2xlargeを4台に詰め込むのが最も効率が良く、月額$1814.4で済むようです。

感想

今回はアプリケーション配置を最適化問題として考えてみました。
もちろん同一サイズのインスタンスに全アプリケーションを詰め込むケースは少ないでしょうし、サブネット分離等々、アーキテクチャを考える上で考えなければならない点は多いです。
本当は複数インスタンスサイズを混ぜた構成(c4.2xlarge 3台、t2.micro 4台、みたいな)を算出したかったのですが、サイズの違う複数ビンでのパッキング方法がわからず、このような形になりました。
これは今後の課題にします。
もし詳しい方がおりましたら、教えて下さい。

参考

組合せ最適化を使おう
組合せ最適化 – 標準問題と実行方法
最適化におけるPython
ビンパッキング問題の解き方
Python言語によるビジネスアナリティクス 実務家のための最適化・統計解析・機械学習
https://github.com/PythonCharmers/OOSuite/blob/master/OpenOpt/openopt/examples/bpp_2.py

続きを読む

aws周りのメモ2

postgresqlを使う

RDSへpostgresqlをいれて立ち上げ

認証と接続

import-key-pair — AWS CLI 1.11.87 Command Reference
http://docs.aws.amazon.com/cli/latest/reference/ec2/import-key-pair.html

cd $HOGE
openssl genrsa -out my-key.pem 2048
openssl rsa -in my-key.pem -pubout > my-key.pub
# IAMのコンパネで*.pubを入力
# 多分、権限があれば以下でもいける
# aws iam upload-ssh-public-key

【AWS 再入門】EC2 + RDS によるミニマム構成なサーバー環境を構築してみよう – NET BIZ DIV. TECH BLOG
https://tech.recruit-mp.co.jp/infrastructure/retry-aws-minimum-vpc-server-environment/

便利

無料枠

無料のクラウドサービス | AWS 無料利用枠
https://aws.amazon.com/jp/free/

AMI

AWS Marketplace: Search Results
https://aws.amazon.com/marketplace/search/results?x=14&y=18&searchTerms=&page=1&ref_=nav_search_box

CFテンプレート

サンプルコード & テンプレート – AWS CloudFormation | AWS
https://aws.amazon.com/jp/cloudformation/aws-cloudformation-templates/

ec2 ami tool & ec2 api tool

Mac で Amazon EC2 API Toolsを設定する – サーバーワークスエンジニアブログ
http://blog.serverworks.co.jp/tech/2013/01/31/mac-amazon-ec2-api-tools-setup/

ec2 api toolは若干心配。

VPCを使う

接続の際に、sshを経由したい。sslでもいいけどなんかsshがいいなと。
パスワードよりkeyのほうがセキュアだからかな。

0から始めるAWS入門①:VPC編 – Qiita
http://qiita.com/hiroshik1985/items/9de2dd02c9c2f6911f3b

導入

Amazon VPC とは? – Amazon Virtual Private Cloud
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_Introduction.html

公式のいろいろ

料金 – Amazon VPC | AWS
https://aws.amazon.com/jp/vpc/pricing/

基本は無料だけどNATとVPNは別課金。

【AWS 再入門】VPC 環境に踏み台サーバーを構築して SSH 接続してみよう – NET BIZ DIV. TECH BLOG
https://tech.recruit-mp.co.jp/infrastructure/retry-aws-bastion-host-vpc/#i-3

ec2(Bastion)を配置する必要がありそう。

【AWS 再入門】EC2 + RDS によるミニマム構成なサーバー環境を構築してみよう – NET BIZ DIV. TECH BLOG
https://tech.recruit-mp.co.jp/infrastructure/retry-aws-minimum-vpc-server-environment/

VPC に推奨されるネットワーク ACL ルール – Amazon Virtual Private Cloud
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_Appendix_NACLs.html

vpcでのネットワークのポリシーの例

Default VPC

AWSのDefault VPCを削除して困った話 – MikeTOKYO Developers
http://blog.miketokyo.com/post/49939300091/aws-default-vpc

デフォルトvpcは削除したらダメか。使い分けがわからん。

Amazon EC2 と Amazon Virtual Private Cloud – Amazon Elastic Compute Cloud
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-vpc.html

基本の機能はデフォルトとそうじゃないvpcは同じだけど、
デフォルトvpcがないとちゃんと機能しない。
デフォルトの属性によって、指定がないとipを紐付けたりする。

VPCネットワーク設計

これだけ押さえておけば大丈夫!Webサービス向けVPCネットワークの設計指針 | eureka tech blog
https://developers.eure.jp/tech/vpc_networking/

ネットワークは一度稼働させると移行が大変なので、初期設計が非常に重要になります。

わかりやすい。図が特に。

  • Bastion
  • NAT
  • Security Group

ENI

インフラエンジニアに贈るAmazon VPC入門 | シリーズ | Developers.IO
http://dev.classmethod.jp/series/vpcfor-infra-engineer/

サブネットで指定したIPアドレスのうち、先頭4つと末尾の1つはVPCで予約されるため使用できません。

VPCでは常にDHCP有効とするのがポイントです。

また、DHCPサービスで伝えられる情報(DHCPオプション)は、変更することもできます。

仮想マシンにひもづくENIにより、DHCPサーバーから毎回同じMACアドレス、IPアドレスが付与されます。これは、仮想マシンの状態に依存しないため、仮想マシンを再起動しようと、一旦シャットダウンしてしばらくしてから起動した場合でも必ず同じアドレスが付与されます。

ENI(Elastic Network Interface)か。。なるほど。でも、使うことはなさそうだな。

NAT

IPマスカレードが使えないVPC
NATは、Static(静的・サーバー用途)とElastic(仮想・クライアント用途)がある。
個人的には、このNATインスタンスの実装は、あまり好きではありません。動きがややこしいですし、ユーザーが自分でNATインスタンスの管理をしなければならないのも煩雑な印象を受けます。VPCのネットワークサービスの一つとして提供される機能であれば、ユーザーからはなるべく抽象化され仮想マシンとして意識されないようにするべきと考えます。
ただ、ユーザーから仮想マシンとして見える分、機能・実装が具体的に把握できる点やカスタマイズ性が高い点は良いとも思っています。

NAT インスタンスと NAT ゲートウェイの比較 – Amazon Virtual Private Cloud
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/vpc-nat-comparison.html

なるほど。。

Bastion

AWSで最低限セキュアな構成を組む – Qiita
http://qiita.com/ausuited/items/09b626fa5264f0c650fd

パブリックSubnetにEC2インスタンス(踏み台サーバーとして)
NATインスタンスを作成した要領で、パブリックSubnetにEC2インスタンスを作成する。Security groupは新規に作成してSSHをAnywhereに。Key pairは厳重に管理。尚、踏み台サーバーは、使用する時以外はStoppedにしておく事で、さらにセキュアな状態とする。このデザインパターンをOn Demand Bastionパターンと呼ぶらしい。

詳しい。「On Demand Bastionパターン」か。なるほど。

vpcへの踏み台サーバー
ポートフォワーディング、トンネルなどと同じ意味。

Network ACL

インスタンス単位じゃなくサブネット単位でより制限してセキュアにしたい場合に使うのかな。

安全なVPC設計 — Commerce Hack
http://tech.degica.com/ja/2016/01/07/designing-vpc-and-subnets/


結局どうするのか、、ひとまずNATはつかわずに、Bistionをつくってみる感じかな。

アベイラビリティーゾーン

リージョンごとでの、障害などで全部やられないように物理的にセグメントされた範囲の単位かな。
RDSではセグメントグループに2つ以上のゾーンを含める。でも、一つしか使わなくていい。ということか。s

RDSのVPC間の移動

サブネットグループの関連付けを変えればいいらしい。間違って設定したので移動した。

【小ネタ】知っていましたか?RDSを別のVPCに移動できることを | Developers.IO
http://dev.classmethod.jp/cloud/aws/rds_can_move_to_another_vpc/

Bastion作成作業をしてみる

主に下記を参考。

【AWS 再入門】EC2 + RDS によるミニマム構成なサーバー環境を構築してみよう – NET BIZ DIV. TECH BLOG
https://tech.recruit-mp.co.jp/infrastructure/retry-aws-minimum-vpc-server-environment/

  • サブネットってなんだっけとか復習。
  • ストレージはどうするのか。
    • とりあえずssdにしたけどマグネティックでよかったかなあ。

      • ssd:$0.12 : 1 か月にプロビジョニングされたストレージ 1 GB あたり
      • マグネティック: 0.05 USD/GB-月
  • public IPは設定必要だよね
  • market placeからamiを取得した方がいいの?
    • とりあえず公式のウィザードを使ったけど。
  • 認証にIAMが追加されていたので使ってみた
    • これとは別にキーペアは必要ってことかな。
  • CFnテンプレート(CloudFormationテンプレート)というのがあるらしい。。
    • これでつくりなおそうかな。。
  • サブネットとかいろいろネットワーク系の設定
    • なんだかんだいっていろいろあった
  • セキュリティグループ
    • エイリアスみたいなセキュリティグループにできたらいいのに。タグや名前で明示化かな。
    • bastionは22をあけて、rdsは5432をbastionからのみあける
  • ログイン
  • DNS
    • あれ、パブリックDNSがうまく割り振ってないな。。
      AWSでPublic DNS(パブリックDNS)が割り当てられない時の解決法 – Qiita
      http://qiita.com/sunadoridotnet/items/4ea689ce9f206e78a523
    • RDSのDNS
      • nslookupしたら内部ipがかえってくるのね。接続できないけどなんか気持ち悪いな。これかな。。
        外部からdnsを引けることを気にしている人は見かけなくて便利だからって話なのかね。
        【AWS】VPC内でPrivate DNSによる名前解決 – Qiita
        http://qiita.com/y_takeshita/items/2eb5e6abb5eb5516d1de

やってるうちはいいけど、しばらくやらないと設定の方法とか忘れそう。。こういうのは学習コストだけじゃないな。

PlantUMLで図にしておく

Kobito.hQIwJs.png

VPC内のRDSへLambdaから接続。。

しまった!アンチパターンだそうだ。。

Lambda+RDSはアンチパターン – Qiita
http://qiita.com/teradonburi/items/86400ea82a65699672ad

Lambda + RDS benchmark – Qiita
http://qiita.com/taruhachi/items/3f95ae3e84f56edb3787

新し目の記事でIAM認証でクリアできそうな。。

【全世界待望】Public AccessのRDSへIAM認証(+ SSL)で安全にLambda Pythonから接続する – サーバーワークスエンジニアブログ
https://blog.serverworks.co.jp/tech/2017/04/27/rds-iam-auth-lambda-python/


セキュアに接続するのと速度のトレードオフになっていたのが
IAM認証のおかげで両方可能になったということっぽい。
でも、ネットのスループット、コネクション数(料金・負荷)、など、、ほかにも気にすることが出て来そうで若干不安。
非同期でよければキューイングして一回投げっぱなしすればどうだろう。
もしくは、似てるけど、Lambdaから一回値を返してもらってからRDSへ投げ直す。
これでいっかなあ。。Lambdaの意味がなくなる?うーん。

今後

  • 疑問としてrdsなど内部向けのdnsを外から見れなくできないものか。
  • というか、rdsのエンドポイントって再起動したら変わったりしないかね。ipは固定されるのか。
    • たぶん、サブネット内でdhcpになるのでipは変動するけどエンドポイントは固定。。じゃないかしら。

posgresqlをつかうための情報

Amazon RDS 上の PostgreSQL – Amazon Relational Database Service
http://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts.General.SSL

続きを読む

AWS CLIからAthenaのクエリを実行してみた

AWS CLIがAthena対応したので、試してみました。
(いや〜JDBC接続とかめんどかった・・・)

利用環境はMacです

AWS CLIをバージョンアップ

利用していたAWS CLIのバージョンは1.11.88でした

$ aws --version
aws-cli/1.11.88 Python/2.7.10 Darwin/15.6.0 botocore/1.5.51
$ aws help | grep athena

もちろんAthenaが入っていないのでバージョンアップします
–ignore-installed sixをつけないとバージョンアップできないのはなんとかならないかな・・・

$ sudo pip install --upgrade awscli --ignore-installed six

バージョンアップ完了

$ aws --version
aws-cli/1.11.89 Python/2.7.10 Darwin/15.6.0 botocore/1.5.52
$ aws help | grep athena
       o athena

ドキュメントを眺める

コマンド一覧

  • batch-get-named-query
  • batch-get-query-execution
  • create-named-query
  • delete-named-query
  • get-named-query
  • get-query-execution
  • get-query-results
  • list-named-queries
  • list-query-executions
  • start-query-execution
  • stop-query-execution

コマンド実行してみる

本日時点ではAthenaが東京リージョンに来てないので、コマンドに–region us-east-1(バージニアリージョン)を指定。(諸事情でprofileは使わない)
すでに、CloudFrontとCloudTrailのログをAthena上に配置
Athenaを使ってAWSのログを集計する

start-query-execution

クエリを実行するコマンド。
必須パラメータ
–query-string:Stringでクエリを記述。FROM句にDB名は必須。
–result-configuration:結果を出力するS3バケットを指定

$ aws --region us-east-1 athena start-query-execution 
    --query-string "SELECT * FROM aws_logs.cloudfront_log limit 10;" 
    --result-configuration OutputLocation=s3://athena-output
{
    "QueryExecutionId": "9d5f2f3a-e80f-4807-ab6c-35139924d374"
}

get-query-results

クエリの実行結果を見る

$ aws --region us-east-1 athena get-query-results 
    --query-execution-id 9d5f2f3a-e80f-4807-ab6c-35139924d374
{
    "ResultSet": {
        "Rows": [
            {
                "Data": [
                    {
                        "VarCharValue": "date"
                    }, 
                    {
                        "VarCharValue": "time"
                    }, 
                    {
                        "VarCharValue": "xedgelocation"
                    }, 
                    {
                        "VarCharValue": "scbytes"
                    }, 
                    {
                        "VarCharValue": "cip"
                    }, 
                    {
                        "VarCharValue": "csmethod"
                    }, 
                    {
                        "VarCharValue": "cshost"
                    }, 
                    {
                        "VarCharValue": "csuristem"
                    }, 
                    {
                        "VarCharValue": "scstatus"
                    }, 
                    {
                        "VarCharValue": "csreferer"
                    }, 
                    {
                        "VarCharValue": "csuseragent"
                    }, 
                    {
                        "VarCharValue": "csuriquery"
                    }, 
                    {
                        "VarCharValue": "cscookie"
                    }, 
                    {
                        "VarCharValue": "xedgeresulttype"
                    }, 
                    {
                        "VarCharValue": "xedgerequestid"
                    }, 
                    {
                        "VarCharValue": "xhostheader"
                    }, 
                    {
                        "VarCharValue": "csprotocol"
                    }, 
                    {
                        "VarCharValue": "csbytes"
                    }, 
                    {
                        "VarCharValue": "timetaken"
                    }, 
                    {
                        "VarCharValue": "xforwardedfor"
                    }, 
                    {
                        "VarCharValue": "sslprotocol"
                    }, 
                    {
                        "VarCharValue": "sslcipher"
                    }, 
                    {
                        "VarCharValue": "xedgeresponseresulttype"
                    }, 
                    {
                        "VarCharValue": "csprotocolversion"
                    }
                ]
            }, 
            {
                "Data": [
                    {
                        "VarCharValue": "2017-03-11"
                    }, 
                    {
                        "VarCharValue": "07:09:39"
                    }, 
                    {
                        "VarCharValue": "NRT20"
                    }, 
                    {
                        "VarCharValue": "485"
                    }, 
                    {
                        "VarCharValue": "182.251.62.172"
                    }, 
                    {
                        "VarCharValue": "GET"
                    }, 
                    {
                        "VarCharValue": "d296z2px268if9.cloudfront.net"
                    }, 
                    {
                        "VarCharValue": "/sample"
                    }, 
                    {
                        "VarCharValue": "200"
                    }, 
                    {
                        "VarCharValue": "-"
                    }, 
                    {
                        "VarCharValue": "curl/7.43.0"
                    }, 
                    {
                        "VarCharValue": "-"
                    }, 
                    {
                        "VarCharValue": "-"
                    }, 
                    {
                        "VarCharValue": "Miss"
                    }, 
                    {
                        "VarCharValue": "7_kRmqTCtndlAsdecditmwIL3kPgVKjsqBggBEFSu68_tsTGWAVK-g=="
                    }, 
                    {
                        "VarCharValue": "d296z2px268if9.cloudfront.net"
                    }, 
                    {
                        "VarCharValue": "https"
                    }, 
                    {
                        "VarCharValue": "99"
                    }, 
                    {}, 
                    {
                        "VarCharValue": "-"
                    }, 
                    {
                        "VarCharValue": "TLSv1.2"
                    }, 
                    {
                        "VarCharValue": "ECDHE-RSA-AES128-GCM-SHA256"
                    }, 
                    {
                        "VarCharValue": "Miss"
                    }, 
                    {
                        "VarCharValue": "HTTP/1.1"
                    }
                ]
            }, 
・・・

これ使いにくい・・・

get-query-execution

クエリ実行結果(成功/失敗)等の情報を取得する
start-query-executionで実行した結果を取得
必須パラメータ
–query-execution-id:実行時に表示されるQueryExecutionIdを指定

$ aws --region us-east-1 athena get-query-execution 
    --query-execution-id 9d5f2f3a-e80f-4807-ab6c-35139924d374
{
    "QueryExecution": {
        "Status": {
            "SubmissionDateTime": 1495269759.131, 
            "State": "SUCCEEDED", 
            "CompletionDateTime": 1495269762.711
        }, 
        "Query": "SELECT * FROM aws_logs.cloudfront_log limit 10", 
        "Statistics": {
            "DataScannedInBytes": 1454, 
            "EngineExecutionTimeInMillis": 3475
        }, 
        "ResultConfiguration": {
            "OutputLocation": "s3://athena-output/3fbf61dd-866e-4de6-9ba4-56cfdb671964.csv"
        }, 
        "QueryExecutionId": "3fbf61dd-866e-4de6-9ba4-56cfdb671964"
    }
}

StatusにSUCCEEDEDと表示されるので成功している
結果は実行時に指定したS3バケット内にCSVで保存される→OutputLocation

スクリプトを組むならこんな感じ?

athena-query.sh
#!/bin/bash

OutputBucket=athena-output # 出力バケット
# クエリ実行
queryId=$(aws --region us-east-1 athena start-query-execution 
    --query-string "SELECT * FROM aws_logs.cloudfront_log limit 10;" 
    --result-configuration OutputLocation=s3://$OutputBucket 
    | jq -r '.QueryExecutionId')
# 結果の確認
status=$(aws --region us-east-1 athena get-query-execution 
    --query-execution-id $queryId 
    | jq -r '.QueryExecution.Status.State')

if [ "$status" == "SUCCEEDED" ]; then
    aws s3 cp s3://${OutputBucket}/${queryId}.csv .
else
    echo "Query Error!"
fi

cat ${queryId}.csv

まとめ

  • 待望のAthenaがAWS CLIに対応しました。
  • BigQueryはbqコマンドで実行できたので、足並み揃い始めた感じでしょうか
  • もうすぐ東京リージョンに来そうな感じなので、期待大です(Comming Soonってなってるので、AWS Summit Tokyoで発表!?)
  • 出力結果がもっといい感じに見れるといいですね

続きを読む

‘str’ object has no attribute ‘get’

事象

aws cliでs3コマンドを実行するとタイトルのエラー。


$ aws s3 ls

'str' object has no attribute 'get'

原因

.aws/configのs3署名バージョン部で改行が入っていない。

(誤)


$ cat .aws/config
[default]
output = json
region = ap-northeast-1
s3 = signature_version = s3v4

(正)


$ cat .aws/config
[default]
output = json
region = ap-northeast-1
s3 =
      signature_version = s3v4

※[]内は各々で定義したprofile名です。

IAM権限が足りない場合(s3:ListBucketが無い)でも同様のエラーが発生しました。

切り分けのためにpython、boto、aws cliのバージョンやIAMを見て少し長引いたため共有です。

続きを読む