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 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で発表!?)
  • 出力結果がもっといい感じに見れるといいですね

続きを読む

CodeBuildでSpecify either ‘DefinitionUri’ or ‘DefinitionBody’ property and not both

DefinitionBodyは書いているのに怒られた

AWS SAMを使用したデプロイを行っていて、ドハマリしたのでメモを共有します

前提環境

  • クロスリージョン&クロスアカウントなサーバレスアプリケーションのデプロイをSAMで行う

    • CodeCommit
    • CodePipeline
    • CodeBuild
      • AmazonLinux node4.4.6 (eb-nodejs-4.4.6-amazonlinux-64:2.1.3)

現象

CloudFormationテンプレート

下記のようなCloudFormationテンプレートを作成

template.yaml
  HelloFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: hello.handler
      Runtime: nodejs6.10
      CodeUri: ./src/
  Api:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage
      DefinitionBody:
        swagger: "2.0"
        info:
          version: "1.0"
          title: "HelloApi"
        schemes:
        - https
        basePath: !Sub /${Stage}
        paths:
          /hello:
            get:
              x-amazon-apigateway-integration:
                uri: !Sub  "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloFunction.Arn}/invocations"
                passthroughBehavior: "when_no_match"
                httpMethod: "POST"
                type: "aws_proxy"

CodeBuild

CodeBuildでテスト&デプロイを行う

buildspec.yml
version: 0.1

phases:
  install:
    commands:
      - curl -o /usr/local/bin/jq -L https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 && chmod +x /usr/local/bin/jq
      - mkdir ~/.aws
      - aws sts assume-role --role-arn ${DEV_CFN_ROLE} --role-session-name codebuild | jq -r '"[default]naws_access_key_id = (.Credentials.AccessKeyId)naws_secret_access_key = (.Credentials.SecretAccessKey)naws_session_token = (.Credentials.SessionToken)n"' >> ~/.aws/credentials
      - cd test && npm install && npm install -g mocha
  pre_build:
    commands:
      - echo Test Started on `date`
      - cd test && mocha test.js | tee test.log
  build:
    commands:
      - echo Build started on `date`
      - aws cloudformation --region ap-northeast-1 package --template-file template.yaml --s3-bucket ${DEV_SAM_BUCKET} --output-template-file outputTemplate.yaml
      - aws cloudformation --region ap-northeast-1 deploy --template-file outputTemplate.yaml --stack-name ${PROJECT_ID} --capabilities CAPABILITY_IAM --parameter-overrides Stage=${STAGE}
  post_build:
    commands:
      - echo Build completed on `date`
artifacts:
  files:
    - '**/*'

実行結果

[Container] 2017/04/29 02:07:51 Waiting for changeset to be created..
[Container] 2017/04/29 02:08:02 Failed to create the changeset: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state Status: FAILED. Reason: Transform AWS::Serverless-2016-10-31 failed with: Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Api] is invalid. Specify either 'DefinitionUri' or 'DefinitionBody' property and not both

template.yamlにはDefinitionBodyが正しく入っているのに怒られた!

解決

https://github.com/awslabs/serverless-application-model/issues/93

aws-cliのバグだったΣ(゚Д゚)

buildspec.ymlの修正

使っていたCodeBuildコンテナのaws-cliが古いことが原因だったようなので、上記issueにも記載の通り、aws-cliをアップデートしてあげる

buildspec.yml
version: 0.1

phases:
  install:
    commands:
      - pip install --upgrade awscli
      - curl -o /usr/local/bin/jq -L https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 && chmod +x /usr/local/bin/jq
      - mkdir ~/.aws
      - aws sts assume-role --role-arn ${DEV_CFN_ROLE} --role-session-name codebuild | jq -r '"[default]naws_access_key_id = (.Credentials.AccessKeyId)naws_secret_access_key = (.Credentials.SecretAccessKey)naws_session_token = (.Credentials.SessionToken)n"' >> ~/.aws/credentials
      - cd test && npm install && npm install -g mocha
  pre_build:
    commands:
      - echo Test Started on `date`
      - cd test && mocha test.js | tee test.log
  build:
    commands:
      - echo Build started on `date`
      - aws cloudformation --region ap-northeast-1 package --template-file template.yaml --s3-bucket ${DEV_SAM_BUCKET} --output-template-file outputTemplate.yaml
      - aws cloudformation --region ap-northeast-1 deploy --template-file outputTemplate.yaml --stack-name ${PROJECT_ID} --capabilities CAPABILITY_IAM --parameter-overrides Stage=${STAGE}
  post_build:
    commands:
      - echo Build completed on `date`
artifacts:
  files:
    - '**/*'

実行結果

[Container] 2017/04/29 04:03:34 Waiting for changeset to be created..
[Container] 2017/04/29 04:03:45 Waiting for stack create/update to complete
[Container] 2017/04/29 04:04:16 Successfully created/updated stack - helloproject

無事デプロイ出来ました

教訓

パッケージはアップデートしてから使いましょう。。。

続きを読む

ec2 インスタンスをまとめてaws cli から停止する

忘れやすいのでメモメモ

1. まず確認

指定の名前タグが入っているインスタンスIDと名を取り出す。
タグに入れてあるそれらしい文字列XXXXXXを指定する。

bash
$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and (.Tags[].Value | test("XXXXXX")))
  |  {"InstanceName":(.Tags[] | select(.Key=="Name").Value) ,"instance-id": .InstanceId}'

{
  "InstanceName": "XXXXXX-28",
  "instance-id": "i-0791d3f5e36351883"
}
{
  "InstanceName": "XXXXXX-22",
  "instance-id": "i-03c6f00bd51f7dbcd"
}
{
  "InstanceName": "XXXXXX-23",
  "instance-id": "i-0c5cf083435cb6070"
}
{
  "InstanceName": "XXXXXX-30",
  "instance-id": "i-0151fcf7f29fb0876"
}
{
  "InstanceName": "XXXXXX-25",
  "instance-id": "i-0d0360a9c2b6c8bae"
}

違うインスタンスは含まれていないようです。

2. 停止 インスタンスIDリスト作成

--cli-input-json

を使っても良いのですが、 JSONファイルを作らないと行けないので、 今回は、 --instance-ids で気楽に指定します。なので、1を修正して、
“instance-id1” “instance-id2”
の1行に変換します。

まず、インスタンスIDのみ取り出してみます。

bash
$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and (.Tags[].Value | test("XXXXXX")))
  |  {"InstanceName":(.Tags[] | select(.Key=="Name").Value) ,"instance-id": .InstanceId}'

{
  "instance-id": "i-0791d3f5e36351883"
}
{
  "instance-id": "i-03c6f00bd51f7dbcd"
}
{
  "instance-id": "i-0c5cf083435cb6070"
}
{
  "instance-id": "i-0151fcf7f29fb0876"
}
{
  "instance-id": "i-0d0360a9c2b6c8bae"

悪くないですね。
これを、sed で IDのみ抽出します。

bash
$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and (.Tags[].Value | test("XXXXXX")))
  |  {"instance-id": .InstanceId}' | sed -n -e 's/.*"instance-id": "(.*)"/1/p'

i-0791d3f5e36351883
i-03c6f00bd51f7dbcd
i-0c5cf083435cb6070
i-0151fcf7f29fb0876
i-0d0360a9c2b6c8bae

改行が入っているので削除してスペースに変換します。

bash
$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and (.Tags[].Value | test("XXXXXX")))
  |  {"instance-id": .InstanceId}' | sed -n -e 's/.*"instance-id": "(.*)"/1/p' | tr 'n' ' '
i-0791d3f5e36351883 i-03c6f00bd51f7dbcd i-0c5cf083435cb6070 i-0151fcf7f29fb0876 i-0d0360a9c2b6c8bae

“instance-id1” “instance-id2” の形式になりました。

3. 停止コマンド発行

では、$ aws ec2 stop-instances を使いましょう。
最初は、Dry-runとか付けておくと良いでしょう。

$ aws ec2 stop-instances --dry-run --instance-ids $(2のコマンドを入れる)

次のようになります。

bash
aws ec2 stop-instances --dry-run --instance-ids $(
aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and (.Tags[].Value | test("XXXXXX")))
  |  {"instance-id": .InstanceId}' | sed -n -e 's/.*"instance-id": "(.*)"/1/p' | tr 'n' ' ')

こんな感じです。 2の部分を --instance-ids の後ろに $() でBashの展開を利用して指定しています。
上記のコマンドでは、 $() の部分が実行されて実際は2のインスタンスIDのリストになっています。
では、–dry-runを外します。

bash
aws ec2 stop-instances --instance-ids $(aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and (.Tags[].Value | test("XXXXXX")))
  |  {"instance-id": .InstanceId}' | sed -n -e 's/.*"instance-id": "(.*)"/1/p' | tr 'n' ' ')

{
    "StoppingInstances": [
        {
            "InstanceId": "i-0a011f82414e366bd",
            "CurrentState": {
                "Code": 64,
                "Name": "stopping"
            },
            "PreviousState": {
                "Code": 16,
                "Name": "running"
            }
        },
        {
            "InstanceId": "i-0528abc7952480f4c",
            "CurrentState": {
                "Code": 64,
                "Name": "stopping"
            },
            "PreviousState": {
                "Code": 16,
                "Name": "running"
            }
        },
        {
            "InstanceId": "i-0f049840b8939d681",
            "CurrentState": {
                "Code": 64,
                "Name": "stopping"
            },
            "PreviousState": {
                "Code": 16,
                "Name": "running"
            }
        },
    <<<<<中略>>>>>
    ]
}

良いようですね。コンソールでも確認します。

image.png

OKでした。

続きを読む

RDSのスロークエリログをmysqldumpslowに突っ込む

JSON形式で取得されるRDS(MySQL)のスロークエリログをcatして絶望したときに、心を落ち着けてmysqldumpslowに突っ込めるようにします。

用意するもの

  • awscli
  • jq
  • mysqldumpslow 1

スロークエリログのダウンロード

スロークエリログのリストを取得

$ aws rds describe-db-log-files \
  --db-instance-identifier hoge-instance \
  | jq '.DescribeDBLogFiles[].LogFileName' -r  \
  | grep slow
slowquery/mysql-slowquery.log
slowquery/mysql-slowquery.log.0
...

スロークエリログのダウンロード

describe-db-log-files取得されたファイル名には/が含まれていて邪魔なので、ファイルに保存するときに取っています。

for log in $(cat slowlog-list.txt); do \
  aws rds download-db-log-file-portion \
  --db-instance-identifier hoge-instance \
  --log-file-name $log \
  > $(echo $log | cut -d / -f 2); done

スロークエリログの読み込み

取得されたスロークエリログの中身はJSONデータの奥に格納されたテキスト文字列なので、jqで抽出してからmysqldumpslowに食わせます。

$ cat mysql-slowquery.log \
  | jq -rs .[].LogFileData \
  | mysqldumpslow -
# 遅い順にソートされたクエリたち...

  1. Ubuntuではmysql-clientをインストールするとついてきます。 

続きを読む

aws-cli + jq でロードバランサー(ELB, ALB)のロギング設定を確認する

遭遇した課題

AWSではロードバランサー(ELB, ALB)のアクセスログをS3上に保存することができるわけですが、将来のことをあまり良く考えずに場当たり的に設定した結果、以下のような課題にぶち当たりました。
起動しているロードバランサーが数台であれば、WEB管理画面からポチポチ確認すれば良いのですが、10台超えたあたりからしんどくなってきます。。。

  • そもそもアクセスログ有効になってるのか?
  • どのS3バケットのどんなパスに保存しているのか?

そこで、以下のように aws-cli + jq を使うといくらか苦痛が緩和されました。

手順

ELB の場合

実行したコマンド

$ aws elb describe-load-balancers | jq -r '.LoadBalancerDescriptions[].LoadBalancerName' > /tmp/elb-names.txt

$ cat /tmp/elb-names.txt | while read NAME \
do \
echo $NAME \
aws elb describe-load-balancer-attributes --load-balancer-name=$NAME \
| jq '.LoadBalancerAttributes.AccessLog | "s3://\(.S3BucketName)/\(.S3BucketPrefix)"' \
echo \
done

出力例

# ロギング設定なし
elb-hogehoge1
"s3://null/null"

elb-hogehoge2
"s3://bucket2/prefix"

elb-hogehoge3
"s3://bucket2/prefix"

ALB の場合

実行したコマンド

$ aws elbv2 describe-load-balancers | jq -r '.LoadBalancers[].LoadBalancerArn' > /tmp/alb-arns.txt

$ cat /tmp/alb-arns.txt | while read ARN \
do \
echo $ARN | cut -d'/' -f3 \
aws elbv2 describe-load-balancer-attributes --load-balancer-arn $ARN \
| jq '"s3://\(.Attributes[] | select(.Key=="access_logs.s3.bucket").Value)/\(.Attributes[] | select(.Key=="access_logs.s3.prefix").Value)"' \
echo \
done

出力例

# ロギング設定なし
alb-hogehoge1
"s3:///"

alb-hogehoge2
"s3://bucket2/prefix"

alb-hogehoge3
"s3://bucket3/prefix"

続きを読む

AWS EBSのリサイズをシェルスクリプトにまとめてみた

はじめに

AWSのストレージサービスならばとりあえずS3を選んでおけば間違いはないのでしょうけど,既存サービスを短期間でAWSに移行する場合にはEBSを使うことがあるかと思います.以前はEBSの容量を変更できませんでしたので,事前に余裕を持って確保するか,都度容量を割り当ててデータを移行するか悩ましいところでした.

2017年2月にAWSよりEBSの新機能について発表があり,EBSの容量やタイプ,IOPSなどがオンザフライで変更できるようになりました.そこで,EBSの空き容量が閾値を下回ったときに拡張するシェルスクリプトを用意し,定期的に実行することでお財布への負担と管理運用作業を軽くしてみました.

https://aws.amazon.com/jp/blogs/news/amazon-ebs-update-new-elastic-volumes-change-everything/

書いてみた

  • スペシャルデバイス名とEBSボリューム名(Nameタグに割り当てた文字列)を指定して実行します.
  • 使用容量が95%に達したら,80%弱になるくらいに拡張します.
  • Amazon Linuxでroot権限で実行することを前提としています.
  • 2017年4月の時点でのAmazon Linuxにプレインストールされているaws-cliではaws ec2 modify-volumeに未対応なため,アップデートが必要でした.
  • JSONのパースにjqが必要なので,事前にインストールしてください.
#!/bin/sh
#
# $ sudo yum install jq
# $ sudo pip install -U awscli
# # sh ./ebs-resize.sh -d /dev/xvdf -v ebs-volume-name
#

usage() {
  echo "Usage: $0 -d device-name -v ebs-volume-name" 1>&2
  exit 1
}

while getopts d:v:h OPT
do
  case $OPT in
    'd')
      DEVICE_NAME=$OPTARG
      ;;
    'v')
      VOLUME_NAME=$OPTARG
      ;;
    'h')
      usage
      ;;
    ?)
      usage
      ;;
  esac
done

shift $((OPTIND - 1))

if test ${#DEVICE_NAME} -eq 0 -o ${#VOLUME_NAME} -eq 0; then
  usage
fi

df -k $DEVICE_NAME 1> /dev/null
if test "$?" -ne 0; then
  echo "$DEVICE_NAME is not found."
  exit 1
fi

export THRESHOLD_PERCENT=95
export EXPAND_RATE=1.25
export USED_PERCENT=`df -k $DEVICE_NAME | grep -v Filesystem | awk '{print $5}' | awk -F% '{print $1}'`
export CURRENT_SIZE=`df -k $DEVICE_NAME | grep -v Filesystem | awk '{print $2}'`
export NEW_SIZE=`echo "scale=0;$CURRENT_SIZE * $EXPAND_RATE / 1024 / 1024 + 1" | bc -l`
export AWS_DEFAULT_REGION=`curl --silent http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//g'`
export INSTANCE_ID=`curl --silent http://169.254.169.254/latest/meta-data/instance-id`
export VOLUME_ID=`aws ec2 describe-volumes --filter "Name=attachment.instance-id,Values=$INSTANCE_ID" "Name=tag:Name,Values=$VOLUME_NAME" |  jq -r '.Volumes[].VolumeId'`

if test ${#VOLUME_ID} -eq 0; then
  echo "$VOLUME_NAME is not found."
  exit 1
fi

if test $USED_PERCENT -ge $THRESHOLD_PERCENT; then
  aws ec2 modify-volume --volume-id $VOLUME_ID --size $NEW_SIZE
  resize2fs $DEVICE_NAME
fi

exit 0

おわりに

crontabに登録して1日1回程度動かすようにしましょう.なおaws ec2 modify-volumeは1度実行すると6時間は変更できないようです.ご利用は計画的に.

リンク

https://gist.github.com/stoshiya/4cafa50b34223aaa2a70c588abbede89

続きを読む

AWS CloudFormation(CFn) ことはじめ。(とりあえずシンプルなEC2インスタンスを立ててみる)

AWS CloudFormation(CFn) ことはじめ。(とりあえずシンプルなEC2インスタンスを立ててみる)

まえおき

  • 世のはやりは Infrastructure as Code なのです (博士)
  • AWS でもやって当然なのです (助手)

前提

目的

  • 趣味で AWS いじる範囲だと GUI で EC2 インスタンス建てるのに慣れてしまっていつしか EC2 インスタンスに必要な要素を忘れがちではないですか?

    • セキュリティグループ
    • デフォルトサブネット有無
    • パブリック自動IP付与
    • etc …
  • コード化の際にいろいろ気付きもあり、あとでコードを見返せば必要な要素を一覧することもできるという一挙両得
  • ひとり適当運用で セキュリティグループ や キーペア ぐっちゃぐちゃに増えていってませんか? (自分だけ?)
  • 運用をコード化しつつ見直して行きましょう

今回は CFn の練習がてら EC2 に以下の要素を盛り込んでいきます

  1. AMI

    • Amazon Linux
  2. タイプ
    • t2.micro
  3. 手元の作業PCのから SSH 接続出来るようにする
    • サブネット

      • パブリックIP自動付与
    • キーペア
  4. セキュリティグループ
    • 22 番ポートが開いている
  5. IAM プロファイル(適宜)

実行環境

  • AWS CLI を利用します
  • 適宜 pip 等でインストールして下さい

ref. https://github.com/aws/aws-cli

テンプレートを準備する

  • JSON, YAML などで記載します
  • 自分で使いやすい方を選びましょう
  • JSON は(一般に)コメントが利用できないため、コメントを書き込みたい場合は YAML を選択しましょう

ref. http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html

1. CFn の構文とプロパティ(EC2インスタンス)

ref. http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-formats.html

  • 基本は以下の入れ子構造になります

  • Resources

    • {リソース名}

      • Type
      • Properties
        • {各プロパティ}
  • YAML の場合は以下 (注意: デフォルトサブネットを削除している場合などで以下のサンプルをそのまま使うと失敗します)

Resources:
        myec2instance:
                Type: "AWS::EC2::Instance"
                Properties:
                        ImageId: "ami-859bbfe2" #Amazon Linux AMI
                        InstanceType: "t2.micro"
  • JSON の場合は以下のようになります (注意: デフォルトサブネットを削除している場合などで以下のサンプルをそのまま使うと失敗します)
{
"Resources" : {
    "myec2instance" : {
        "Type" : "AWS::EC2::Instance",
        "Properties" : {
            "ImageId" : "ami-859bbfe2",
            "InstanceType" : "t2.micro"
            }
        }
    }
}

2. 必要なプロパティを収集しましょう

ref. http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#aws-properties-ec2-instance-prop

必要な機能 プロパティ名 タイプ 種別
AMIイメージID ImageId String ami-859bbfe2 既定値
インスタンスタイプ InstanceType String t2.micro 既定値
サブネットID SubnetId String subnet-f7ea1081 ユーザ個別
キーペア KeyName String aws-tokyo-default001 ユーザ個別
セキュリティグループ SecurityGroupIds List sg-f9b58f9e ユーザ個別
IAMロール名 IamInstanceProfile String ec2-001 ユーザ個別

2-1. AWS CLI (と jq コマンド)を利用すると以下のように AMIイメージID を検索できます

ref. http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html
ref. http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/finding-an-ami.html#finding-an-ami-aws-cli

% aws ec2 describe-images --owners amazon \
--filters "Name=name,Values=amzn-ami-hvm-2017*x86_64-gp2" \
| jq '.Images[] | { Name: .Name, ImageId: .ImageId }'
{
  "Name": "amzn-ami-hvm-2017.03.rc-1.20170327-x86_64-gp2",
  "ImageId": "ami-10207a77"
}
{
  "Name": "amzn-ami-hvm-2017.03.0.20170401-x86_64-gp2",
  "ImageId": "ami-859bbfe2"
}
{
  "Name": "amzn-ami-hvm-2017.03.rc-0.20170320-x86_64-gp2",
  "ImageId": "ami-be154bd9"
}

2-2. サブネットも同様に

ref. http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-subnets.html

% aws ec2 describe-subnets \
| jq '.Subnets[] | { CIDR: .CidrBlock, PublicIPMapping: .MapPublicIpOnLaunch, SubnetId: .SubnetId }'
{
  "CIDR": "172.31.0.192/26",
  "PublicIPMapping": true,
  "SubnetId": "subnet-7bf8a60d"
}
{
  "CIDR": "172.31.0.128/26",
  "PublicIPMapping": true,
  "SubnetId": "subnet-f7ea1081"
}

2-3. キーペア名

ref. http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-key-pairs.html

% aws ec2 describe-key-pairs \
> | jq '.KeyPairs[].KeyName'
"aws-tokyo-default001"

2-4. IAM (以下手抜き)

ref. http://docs.aws.amazon.com/cli/latest/reference/iam/list-roles.html

% aws iam list-roles | jq '.Roles[]'

2-5. セキュリティグループ

ref. http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html

% aws ec2 describe-security-groups | jq '.SecurityGroups[]'

3. テンプレートを書きます

JSONの場合
% cat cloudformation-study.json
{
    "Description" : "test template",
    "Resources" : {
        "myec2instance" : {
            "Type" : "AWS::EC2::Instance",
            "Properties" : {
                "ImageId" : "ami-859bbfe2",
                "InstanceType" : "t2.micro",
                "SubnetId" : "subnet-f7ea1081",
                "KeyName" : "aws-tokyo-default001",
                "SecurityGroupIds" : [ "sg-f9b58f9e" ],
                "IamInstanceProfile" : "ec2-001"
            }
        }
    }
}
YAMLの場合
% cat cloudformation-study.yaml
Resources:
        myec2instance:
                Type: "AWS::EC2::Instance"
                Properties:
                        ImageId: "ami-859bbfe2" #Amazon Linux AMI
                        InstanceType: "t2.micro"
                        SubnetId: "subnet-f7ea1081"
                        KeyName: "aws-tokyo-default001"
                        SecurityGroupIds: [ "sg-f9b58f9e" ]
                        IamInstanceProfile: "ec2-001"

4. では実行します

4-1. ユニークなスタック名を確認します

スタックが1個もない場合の出力例
% aws cloudformation describe-stacks
{
    "Stacks": []
}

4-2. 実行

コマンド
% aws cloudformation create-stack --stack-name create-ec2-001 --template-body file://cloudformation-study.json
実行結果
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:942162428772:stack/create-ec2-001/1b059fa0-1dbc-11e7-9600-50a68a175ad2"
}

4-3. 実行ステータスはマネコン(WEB GUI)か、さきほど実行したコマンドで確認できます

  • “StackStatus”: “CREATE_COMPLETE” を確認
% aws cloudformation describe-stacks
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:942162428772:stack/create-ec2-001/1b059fa0-1dbc-11e7-9600-50a68a175ad2",
            "Description": "test template",
            "Tags": [],
            "CreationTime": "2017-04-10T07:05:40.261Z",
            "StackName": "create-ec2-001",
            "NotificationARNs": [],
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false
        }
    ]
}

4-4. EC2 インスタンスが作成されていて、SSH で接続可能なことを確認します

% aws ec2 describe-instances --filter "Name=instance-state-name,Values=running"
以下の例はキーファイルがあるディレクトリで実行したもの
% ssh -i "aws-tokyo-default001.pem" ec2-user@ec2-hogehoge.ap-northeast-1.compute.amazonaws.com

5. テンプレートの更新

5-1. AMI イメージID を ami-0099bd67 に変更してみましょう

% aws cloudformation update-stack --stack-name create-ec2-001 --template-body file://cloudformation-study.json
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:942162428772:stack/create-ec2-001/1b059fa0-1dbc-11e7-9600-50a68a175ad2"
}
% aws cloudformation describe-stacks
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:942162428772:stack/create-ec2-001/1b059fa0-1dbc-11e7-9600-50a68a175ad2",
            "Description": "test template",
            "Tags": [],
            "CreationTime": "2017-04-10T07:05:40.261Z",
            "StackName": "create-ec2-001",
            "NotificationARNs": [],
            "StackStatus": "UPDATE_IN_PROGRESS",
            "DisableRollback": false,
            "LastUpdatedTime": "2017-04-10T07:20:37.436Z"
        }
    ]
}

5-2. 何が起こったか確認できましたか?

Screenshot 2017-04-10 16.22.28.png

からの

Screenshot 2017-04-10 16.22.56.png

最初に作成した EC2インスタンス は自動シャットダウンからターミネートされ、あたらしい EC2インスタンス が起動しました

5-3. はい、そういうことですね

% aws cloudformation describe-stack-events --stack-name create-ec2-001 \
| jq '.StackEvents[] | { ResourceStatus: .ResourceStatus,Timestamp: .Timestamp }'

(snip)

{
  "ResourceStatus": "DELETE_COMPLETE",
  "Timestamp": "2017-04-10T07:22:47.470Z"
}
{
  "ResourceStatus": "DELETE_IN_PROGRESS",
  "Timestamp": "2017-04-10T07:21:40.963Z"
}

(snip)

6. おわり

7. おまけ

  • コードですので github などに置いて差分管理するとなお良いかと
  • 認証情報などをコード内に書かないように注意

https://github.com/hirofumihida/my-cfn

続きを読む

CloudTrailから特定の作業を検索

CloudTrailのログから特定の作業を検索

たまに使うので個人用メモとして残しておく

準備

  • aws cliが導入済み
  • jqが導入済み
  • CloudTrailでログを出力

作業

ログの確認

  1. ログを展開
# gunzip *.json.gz 等
gzになっているので閲覧出来るようにする

検索する

2-1. ログから指定のイベントを検索

SecurityGroupからの削除を検索
# cat CLOUDTRAIL_LOG_NAME.json | jq '.Records[]|select(contains({eventName:"RevokeSecurityGroupIngress"}))'
API名で検索すればOK

2-2. ユーザの名前で検索

admin-user01を検索
# cat CLOUDTRAIL_LOG_NAME.json | jq '.Records[]|select(.userIdentity.userName == "admin-user01")'

.userIdentity.sessionContext.sessionIssuer.userName の場合もあるからAPIによる?

続きを読む

peco, jq, aws-cliでEC2にSSHするワンライナー

色々あって こちらこちらこちらが使えなかったので、
あったやつだけで頑張ったあれのメモ

前提

aws configure --profile hoge
などを使って適宜Profileを設定しておくこと
(output formatはjsonになるように)

jq, pecoがいい感じに実行できること

やること

こんな感じで頑張る

aws ec2 describe-instances --profile hogehoge | jq ".Reservations[].Instances[] | select(has(\"PublicIpAddress\")) | {InstanceId, PublicIpAddress, InstanceName: (.Tags[] | select(.Key==\"Name\").Value)}" | jq -r 'to_entries | [.[].value] | @csv' | peco | cut -f 3 -d , | xargs -o -n 1 ssh -i ~/keys/hogehoge.pem -l ec2-user

やってること

1つめ

aws ec2 describe-instances --profile hogehoge

インスタンス一覧をとってくる

2つめ

jq ".Reservations[].Instances[] | select(has(\"PublicIpAddress\")) | {InstanceId, PublicIpAddress, InstanceName: (.Tags[] | select(.Key==\"Name\").Value)}"

インスタンス情報が返ってくる所で、 PublicIpAddress を持つInstanceだけを拾ってきて、以下のようなJSONに整形する

ここのselect(has(\"PublicIpAddress\"))を調整したり、すると、他のパラメータを使うよう変更したりできる

{
   "InstanceId": <Instance1のid>,
   "PublicIpAddress": <Instance1のPublicIP>,
   "InstanceName": <Instance1のNameタグの値>
},
{
   "InstanceId": <Instance2のid>,
   "PublicIpAddress": <Instance2のPublicIP>,
   "InstanceName": <Instance2のNameタグの値>
},
...

3つめ

jq -r 'to_entries | [.[].value] | @csv'

整形されたJSONを下記のようなCSVに変換する

<Instance1のid>,<Instance1のNameタグの値>,<Instance1のPublicIP>
<Instance2のid>,<Instance2のNameタグの値>,<Instance2のPublicIP>
...

2つめで指定した順番どおりになってないけど、
どうやら to_entries をする際にパラメータ名でソートされてしまう模様

4つめ

peco

pecoで検索できるように渡してあげる

5つめ

cut -f 3 -d ,

3番目のフィールドにPublicIPがあるので、
デリミタを,で指定して3番目のフィールドの値を拾ってくる

6つめ

xargs -o -n 1 ssh -i ~/keys/hogehoge.pem -l ec2-user

xargsで値を拾ってsshしてあげる

xargs経由でsshする時は、-oオプションを指定して標準入力を再度開いてあげないと上手く動かないっぽかった。
-n 1オプションは、1個しか動かすつもりがないのでこんな感じで

おつかれやまでした

続きを読む