EC2 インスタンス一覧を特定の条件でフィルタリングしてから特定の項目を抽出した上で CSV 形式で出力してみた

マネジメントコンソールから EC2 インスタンス一覧をエクスポートするような機能があると嬉しいなと、 1 年に 3 回くらい思います。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

コンバンハ、千葉(幸)です。

EC2 インスタンスの一覧を CSV 出力したい、というケースは稀によくあるかと思います。

そして、すべてのインスタンスを出力するのではなく、ステータスやインスタンスタイプなどの条件によってフィルタリングしたくなる時がしばしばあるでしょう。

さらには、必要なのはインスタンス ID や Name タグなどの特定の項目だけで、出力するのは必要最低限の項目に絞りたいという思いが去来することもあるのではないでしょうか。

取り立てて目新しいことは何もないですが、いざ AWS CLI で実際にやってみようとなった時に時間を要してしまったので、備忘も兼ねて記しておきます。

まとめ

かなり決め打ちの部分もありますが、基本形としては以下です。必要に応じてカスタマイズしてお使いください。

aws ec2 describe-instances\
 --max-items 1000\
 --filters\
    Name=instance-state-name,Values=running\
    Name=availability-zone,Values=ap-northeast-1a,ap-northeast-1d\
 --query 'Reservations[].Instances[].{
     InstanceId:InstanceId,
     InstanceType:InstanceType,
     Name:Tags[?Key==`Name`]|[0].Value,
     ASG:Tags[?Key==`aws:autoscaling:groupName`]|[0].Value
     }'\
 --output json\
  | jq -r '
    ["Nameタグ","インスタンスID","インスタンスタイプ","ASG名"],
    ( .[] | [.Name, .InstanceId, .InstanceType ,.ASG]) | @csv'

変な改行挟まないでくれ、という場合はこちらを。

aws ec2 describe-instances --max-items 1000 --filters Name=instance-state-name,Values=running Name=availability-zone,Values=ap-northeast-1a,ap-northeast-1d --query 'Reservations[].Instances[].{ InstanceId:InstanceId, InstanceType:InstanceType, Name:Tags[?Key==`Name`]|[0].Value, ASG:Tags[?Key==`aws:autoscaling:groupName`]|[0].Value }' --output json | jq -r '["Nameタグ","インスタンスID","インスタンスタイプ","ASG名"], ( .[] | [.Name, .InstanceId, .InstanceType ,.ASG]) | @csv'

上記のコマンドは、ざっくり以下の条件を満たすものです。

  • サーバーサイドのフィルタリング
    • インスタンスがRunningである
    • 所属 AZ が ap-northeast-1a | 1d である
  • クライアントサイドのフィルタリング
    • 以下項目のみ出力
      • インスタンス ID
      • インスタンスタイプ
      • Name タグ
      • AutoScalingGroup タグ

上記の出力結果を jq により CSV 形式に変換しています。 14 行目でヘッダーも指定しています。出力イメージは以下です。

"Nameタグ","インスタンスID","インスタンスタイプ","ASG名"
"Test","i-047875da17caa9cc2","t2.micro",
"MAKARONI","i-07856803079a58378","t3.small","MAKARONI-ASG"
....

ここでは標準出力にそのまま出力する形になっていますが、> ファイル名でファイルに書き出したり、mac であれば| pbcopyでクリップボードに貼り付けるなど、ご自由に処理してください。わたしは pbcopy パターンが最近のお気に入りです。

今回使用した AWS CLI のバージョンは以下です。

% aws --version
aws-cli/2.2.29 Python/3.8.8 Darwin/19.6.0 exe/x86_64 prompt/off

サーバーサイドのフィルタリング

サーバーサイド、クライアントサイドという表現は以下から拝借しています。

ここでは--filtersオプションによるフィルタリングを指します。--filtersオプションは以下の形式で使用します。

Shorthand Syntax

Name=string,Values=string,string ...

JSON Syntax

[
  {
    "Name": "string",
    "Values": ["string", ...]
  }
  ...
]

今回の例では前者の構文を用いています。instance-state-nameavailability-zoneを指定していますが、他にもさまざまな項目で絞り込みを行えます。

詳細は CLI のコマンドリファレンスを参照してください。

クライアントサイドのフィルタリング

サーバーサイドのフィルタリングにより、対象のインスタンスを絞り込むことができました。続いてクライアントサイドのフィルタリング--queryにより出力される項目を絞り込みます。

CLI リファレンスの例から引用したものですが、--queryなしの場合は以下のように大量の項目が返されます。

折り畳み
{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
                "AmiLaunchIndex": 0,
                "ImageId": "ami-0abcdef1234567890,
                "InstanceId": "i-1234567890abcdef0,
                "InstanceType": "t2.micro",
                "KeyName": "MyKeyPair",
                "LaunchTime": "2018-05-10T08:05:20.000Z",
                "Monitoring": {
                    "State": "disabled"
                },
                "Placement": {
                    "AvailabilityZone": "us-east-2a",
                    "GroupName": "",
                    "Tenancy": "default"
                },
                "PrivateDnsName": "ip-10-0-0-157.us-east-2.compute.internal",
                "PrivateIpAddress": "10.0.0.157",
                "ProductCodes": [],
                "PublicDnsName": "",
                "State": {
                    "Code": 0,
                    "Name": "pending"
                },
                "StateTransitionReason": "",
                "SubnetId": "subnet-04a636d18e83cfacb",
                "VpcId": "vpc-1234567890abcdef0",
                "Architecture": "x86_64",
                "BlockDeviceMappings": [],
                "ClientToken": "",
                "EbsOptimized": false,
                "Hypervisor": "xen",
                "NetworkInterfaces": [
                    {
                        "Attachment": {
                            "AttachTime": "2018-05-10T08:05:20.000Z",
                            "AttachmentId": "eni-attach-0e325c07e928a0405",
                            "DeleteOnTermination": true,
                            "DeviceIndex": 0,
                            "Status": "attaching"
                        },
                        "Description": "",
                        "Groups": [
                            {
                                "GroupName": "MySecurityGroup",
                                "GroupId": "sg-0598c7d356eba48d7"
                            }
                        ],
                        "Ipv6Addresses": [],
                        "MacAddress": "0a:ab:58:e0:67:e2",
                        "NetworkInterfaceId": "eni-0c0a29997760baee7",
                        "OwnerId": "123456789012",
                        "PrivateDnsName": "ip-10-0-0-157.us-east-2.compute.internal",
                        "PrivateIpAddress": "10.0.0.157"
                        "PrivateIpAddresses": [
                            {
                                "Primary": true,
                                "PrivateDnsName": "ip-10-0-0-157.us-east-2.compute.internal",
                                "PrivateIpAddress": "10.0.0.157"
                            }
                        ],
                        "SourceDestCheck": true,
                        "Status": "in-use",
                        "SubnetId": "subnet-04a636d18e83cfacb",
                        "VpcId": "vpc-1234567890abcdef0",
                        "InterfaceType": "interface"
                    }
                ],
                "RootDeviceName": "/dev/xvda",
                "RootDeviceType": "ebs",
                "SecurityGroups": [
                    {
                        "GroupName": "MySecurityGroup",
                        "GroupId": "sg-0598c7d356eba48d7"
                    }
                ],
                "SourceDestCheck": true,
                "StateReason": {
                    "Code": "pending",
                    "Message": "pending"
                },
                "Tags": [],
                "VirtualizationType": "hvm",
                "CpuOptions": {
                    "CoreCount": 1,
                    "ThreadsPerCore": 1
                },
                "CapacityReservationSpecification": {
                    "CapacityReservationPreference": "open"
                },
                "MetadataOptions": {
                    "State": "pending",
                    "HttpTokens": "optional",
                    "HttpPutResponseHopLimit": 1,
                    "HttpEndpoint": "enabled"
                }
            }
        ],
        "OwnerId": "123456789012"
        "ReservationId": "r-02a3f596d91211712",
    }
}

上記すべての情報が必要、というケースはなかなかないかと思いますので、適宜取捨選択しましょう。

今回の例では以下の形式で--queryを指定しています。

 --query 'Reservations[].Instances[].{
     InstanceId:InstanceId,
     InstanceType:InstanceType,
     Name:Tags[?Key==`Name`]|[0].Value,
     ASG:Tags[?Key==`aws:autoscaling:groupName`]|[0].Value
     }'\

:の左側のラベルは任意のものを指定できます。

この状態で出力すると、以下のように結果が得られます。

[
    {
        "InstanceId": "i-047875da17caa9cc2",
        "InstanceType": "t2.micro",
        "Name": "Test",
        "ASG": null
    },
    {
        "InstanceId": "i-07856803079a58378",
        "InstanceType": "t3.small",
        "Name": "MAKARONI",
        "ASG": "MAKARONI-ASG"
    }
]

だいぶ見やすくなりましたがもう一息です。

jq による CSV 形式への変換

jq--queryと同じようにクライアントサイドでのフィルタリングに使用できますが、今回は CSV 形式への変換のみに使用しています。(--queryを使用せずjqのみで完結できないかと考えたのですが、タグの指定の部分がどうにもうまくいきませんでした。)

以下の形式で指定しています。ヘッダーが不要な場合は2行目を削ってください。

  | jq -r '
    ["Nameタグ","インスタンスID","インスタンスタイプ","ASG名"],
    (.[] | [.Name, .InstanceId, .InstanceType ,.ASG]) | @csv'

この辺りは以下を大いに参考にしました。

おまけ

jqによる CSV 形式への変換をかまさずとも、アウトプットをtable形式で指定するとそれなりにいい感じに出力してくれます。

% aws ec2 describe-instances\
 --max-items 1000\
 --filters # 任意のものを指定
 --query 'Reservations[].Instances[].{
     InstanceId:InstanceId,
     InstanceType:InstanceType,
     Name:Tags[?Key==`Name`]|[0].Value,
     ASG:Tags[?Key==`aws:autoscaling:groupName`]|[0].Value
     }'\
 --output table
-----------------------------------------------------------------------
|                          DescribeInstances                          |
+----------------+----------------------+---------------+-------------+
|  ASG           |     InstanceId       | InstanceType  |  Name       |
+----------------+----------------------+---------------+-------------+
|  None          |  i-047875da17caa9cc2 |  t2.micro     |  Test       |
|  MAKARONI-ASG  |  i-07856803079a58378 |  t3.small     |  MAKARONI   |
+----------------+----------------------+---------------+-------------+

列がアルファベット順でソートされてしまうところが残念ですが、「とりあえず表の体裁に整っていればいい」という場合にはこちらの方がお手軽かと思います。

おまけ2

わたしは CSV をゴニョゴニョする時は Google スプレッドシートを使用するので、コマンド | pbcopyでクリップボードにコピーするのが好きです。

貼り付け時に「テキストを列に分割」が選択できるので、それを押下します。

SplitCSV

いい感じになりました。便利!

SplitCSV-9303361

あとは好きなだけスプレッドシートの関数と戯れましょう。

終わりに

EC2 インスタンスの一覧を CSV で出力したい、という話でした。

昔はよく、マネジメントコンソール上で一覧を範囲選択してコピーしたものを Excel にぺたっと貼り付けてからズレを直す、という真心を込めた対応をしていました。

今回一覧を出力したい環境は全部で 700 台くらいあったので、ギリギリ真心が足りなくなるな、と思い AWS CLI で出力してみました。

フィルタリングや jq による CSV への変換は他のケースでも使えるかと思いますので、お役立ていただければ幸いです。

以上、 チバユキ (@batchicchi) がお送りしました。