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個しか動かすつもりがないのでこんな感じで

おつかれやまでした

続きを読む

AWS Certificate Manager に コマンドで 証明書をインポートする

AWS Certificate Manager(ACM) に、COMODOなどで発行した証明書をコマンドラインでインポートする手順を紹介します。

ELBや AWS API Gatewayなどに独自ドメインを設定してhttpsでアクセスするようにしている場合、SSL証明書の設定が必要になります。
ELBは証明書の更新が楽でダウンタイムもないのですが、API Gatewayの場合手順によってはダウンタイムが発生します。
API Gatewayでは、直接証明書を設定することもできるのですが、この場合は証明書の更新のために 独自ドメイン設定を一度削除して、設定し直す 必要があるようです。
当然、削除してから再設定までの間は、ドメインが利用できずダウンタイムが発生してしまいますが、ACMを使えば無停止での証明書の更新ができるので、この手順をまとめます。

前提条件

  • 最新版のAWS CLI がインストール済み
  • jqがインストール済み
  • 実行環境が *nix(含むMAC OSX)
  • 証明書ファイルはnopass設定で用意済み
    • 秘密鍵
    • 公開鍵
    • 中間証明書

手順

1. ACM に証明書をインポートする

aws acm import-certificate --certificate file://{PATH_TO_CERTIFICATE} --private-key file://file://{PATH_TO_PRIVATEKEY} --certificate-chain file://file://{PATH_TO_CERTIFICATE_CHAIN}

※ もしリージョン指定が必要なら --region {AWS_REGION} を追加する

結果

一応マネジメントコンソールの画面です
範囲を選択_020.png

2. インポートした証明書にNameタグを追加する

インポートしただけだとNameタグが空なので、設定しておく

登録済みの証明書一覧

証明書の一覧を表示して、タグを追加したい証明書のarnをコピーします

aws acm list-certificates 

タグを追加

xargs aws acm add-tags-to-certificate --tags Key=Name,Value={NAME} --certificate-arn #{CERTIFICATE_ARN}

結果

一応マネジメントコンソールの画面です
範囲を選択_021.png

参考

参考にしたWebサイトや技術書などを列挙する

次にやること

あとは、ELBなりApi Gatewayなりで、インポートした証明書を指定して設定するだけです。
注意点としては、 Api Gatewayを利用する場合、証明書をインポートするリージョンは us-east-1 でなくてはいけません。
私は、最初 ap-northeast-1 に上げて、作業をやり直しました。(画像のリージョンがTokyoになっているのはそのため)
必要に応じてコマンドに --region us-east-1 を追加して実行してください。

続きを読む

jqでちょっぴり複雑な検索をする

 jqコマンドで配列の要素かつ複雑な構造をもつ要素(ここではaws ec2 describe-instancesの結果)に対して検索を行ってみました。

はじめに

 株式会社アイリッジのCommon Lisp大好きサーバサイドエンジニア、tanaka.lispです。

 AWS EC2を利用していると、特定のインスタンスの情報(インスタンスIDやインスタンスタイプ、などなど…)を知りたくなることがあります。ただ、そのためだけに管理コンソールをぽちぽちして取得していると、なんだか負けた気がするし(これ大事)、シェルスクリプトなどで結果を利用するときに不便です。

 awscliaws ec2 describe-instancesで取得した結果をjqに食わせ、インスタンスにつけている名前からイイかんじに検索・整形したいところですが、awscliはけっこう複雑な返り値を返してきますよね…:

{
  "Reservations": [
    "Instances": [
      {
        "InstanceId": "i-commonlisp",
        "Tags": [
          {
            "Key": "Name",
            "Value": "app.commonlisp"
          },
          {
            "Key": "aws.cloudformation:stack-name",
            "Value": "stack.commonlisp"
          }
        ],
        ...
      },
      ...
    ],
    ...
  ]
}

 そこで、名前(TagsKeyNameの値)によるインスタンスの検索方法を試行錯誤しました。

よういするもの

  • jq: jq-1.5-1-a5b5cbe
  • awscli: aws-cli/1.11.58 Python/3.6.0 Linux/4.8.0-41-generic botocore/1.5.21

jqでの検索

 jqでは入力のJSONに対して、以下のようなことができます:

  • 入力JSONの整形 ({"id": .InstanceId, "tags": .Tags})
  • 入力JSONからのデータ抽出 (select)

 今回はこれらを組み合わせて、aws ec2 describe-instancesの結果から探しているインスタンスのインスタンスID(InstanceId)を取得してみます。

入力の整形

 たとえばこんなJSONオブジェクトがあって、

[
  {
    "name": "Lisp",
    "developer": "John McCarthy"
  },
  {
    "name": "Common Lisp",
    "developer": "ANSI X3J13 committee",
    "spec": "ANSI INCITS 226-1994 (R2004)"
  },
  {
    "name": "Arc",
    "developer": "Paul Graham",
    "books": [
      "On Lisp",
      "Hackers and Painters"
    ]
  },
  {
    "name": "Clojure",
    "developer": "Rich Hicky"
  }
]

言語名(lang)と開発者名(developer)だけがほしいとき、入力をprintf的に整形できます。

$ echo '...上のJSON...' \
   | jq '.[] | {"lang": .name, "developer": .developer}'
{
  "lang": "Lisp",
  "developer": "John McCarthy"
}
{
  "lang": "Common Lisp",
  "developer": "ANSI X3J13 committee"
}
{
  "lang": "Arc",
  "developer": "Paul Graham"
}
{
  "lang": "Clojure",
  "developer": "Rich Hicky"
}

要素の抽出のようにも思えますが、別にもっと抽出っぽいオペレータがあるので、こちらは整形と呼びました。

入力からのデータ抽出

 条件を満たすもののみをpass throughするというオペレータもあって、それがselectです。こちらを抽出とここでは呼んでいます。

 manにあることがすべてなのですが、

   select(boolean_expression)
       The function select(foo) produces its input unchanged if foo returns true for that input, and produces no output otherwise.

       It´s useful for filtering lists: [1,2,3] | map(select(. >= 2)) will give you [2,3].

正規表現を組み合わせたりできるので強力です;

$ echo '...上のJSON...' \
  | jq '.[] | .name |select( .| test("Lisp"))'
"Lisp"
"Common Lisp"

実際に抽出

Exactlyな名前でインスタンスの検索

 では実際に検索クエリをつくってみます。

 まずはインスタンスリストを得て、

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]'
{
  "ImageId": "ami-commonlisp",
  "State": {
    "Code": 16,
    "Name": "running"
  }
}
{
...  # ずらずら出るので省略

そこにTagsKeyNameValueがExactlyapp.commonlispの要素だけ真になる条件を加えます(ルシのファルシがパージでコクーン感ある)。

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and .Tags[].Value == "app.commonlisp")
{
  "InstanceId": "i-commonlisp",
  ...  # ずらずら出るので省略
}

 ついでに、不要な要素は出ないようにしましょう。

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select(.Tags[].Key == "Name" and .Tags[].Value == "app.commonlisp")
  | {"instance-id": .InstanceId, "tags": .Tags}'
{
  "instance-id": "i-commonlisp",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.commonlisp"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "commonlisp-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}

名前うろおぼえインスタンスの検索

 なんかappって名前をつけたような気がするけどなー、まったく思い出せない。俺たちは雰囲気でサーバを立てている。そんなアナタに捧ぐ。主にぼく自身に捧ぐ :angel:

$ aws ec2 describe-instances | jq '.Reservations[].Instances[]
  | select( .Tags[].Key == "Name" and (.Tags[].Value | test("^app")))
  | {"instance-id": .InstanceId, "tags": .Tags}'
{
  "instance-id": "i-commonlisp",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.commonlisp"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "commonlisp-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}
{
  "instance-id": "i-clojure",
  "tags": [
    {
      "Key": "Name",
      "Value": "app.clojure"
    },
    {
      "Key": "aws:autoscaling:groupName",
      "Value": "clojure-ApplicationFleet"
    },
    ...  # タグが出るので省略
  ]
}

おわりに

 jqでの検索について、以下のことを述べました:

  • 要素を検索する基本的な方法
  • 対象要素のさらに中の辞書も検索条件にする方法

 jqってよくできたDSLですね。jqはチューリング完全でもあるらしく、まさしくJSON時代のawk感があります。


 ところでjqで検索するだけなのになぜ記事が長くなるのか :sob:

続きを読む

小ネタ道場一本勝負 〜 AWS CLI と jq を使って CloudWatch Logs にログが転送されていることを確認 …

小ネタ道場一本勝負 〜 AWS CLI と jq を使って CloudWatch Logs にログが転送されていることを確認する手順の一つ 〜 たのもう 一本! ありがとうご … 続きを読む