AWS CLI の –filters オプションと jq を使用して特定のルートテーブルの特定の値を出力してみた

ルートテーブルの必要な情報を AWS CLI を使用して出力してみました。 --query オプションは宗教上の理由で使用していません。

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

ルートテーブル、きちんと管理できていますか?

そこまで量が多くない環境であれば目で確認していくこともできますが、一定量を超えると人力では太刀打ちできなくなります。

ある一定規模の環境で、以下のような確認をしたい機会がありました。

  • この NAT Gateway 向けのルートを持つサブネットってどれだ?
  • S3 向けの VPC エンドポイント(ゲートウェイ)へのルートってどのルートテーブルに入っている?
  • パブリックサブネットだけ一覧化したい!

早々にマネジメントコンソールとのにらめっこを諦めて、AWS CLI を駆使して対応しました。ポイントとなるのはjq--filtersオプションです。

それぞれの使い方のメモを残しておきます。

jq を使って必要な情報のみ出力する

jqを使うと何が嬉しいかを見ていきましょう。

ルートテーブルを一覧出力するコマンド aws ec2 describe-route-tables を(なにも指定せず)そのまま実行した時の出力イメージは以下の通りです。

{
    "RouteTables": [
        {
            "Associations": [
                {
                    "Main": true,
                    "RouteTableAssociationId": "rtbassoc-0df3f54e06EXAMPLE",
                    "RouteTableId": "rtb-09ba434c1bEXAMPLE"
                }
            ],
            "PropagatingVgws": [],
            "RouteTableId": "rtb-09ba434c1bEXAMPLE",
            "Routes": [
                {
                    "DestinationCidrBlock": "10.0.0.0/16",
                    "GatewayId": "local",
                    "Origin": "CreateRouteTable",
                    "State": "active"
                },
                {
                    "DestinationCidrBlock": "0.0.0.0/0",
                    "NatGatewayId": "nat-06c018cbd8EXAMPLE",
                    "Origin": "CreateRoute",
                    "State": "blackhole"
                }
            ],
            "Tags": [],
            "VpcId": "vpc-0065acced4EXAMPLE",
            "OwnerId": "111122223333"
        },
        {
            "Associations": [
                {
                    "Main": true,
                    "RouteTableAssociationId": "rtbassoc-9EXAMPLE",
                    "RouteTableId": "rtb-a1eec7de"
                }
            ],
            "PropagatingVgws": [],
            "RouteTableId": "rtb-a1eec7de",
            "Routes": [
                {
                    "DestinationCidrBlock": "172.31.0.0/16",
                    "GatewayId": "local",
                    "Origin": "CreateRouteTable",
                    "State": "active"
                },
                {
                    "DestinationCidrBlock": "0.0.0.0/0",
                    "GatewayId": "igw-fEXAMPLE",
                    "Origin": "CreateRoute",
                    "State": "active"
                }
            ],
            "Tags": [],
            "VpcId": "vpc-3EXAMPLE",
            "OwnerId": "111122223333"
        },
------以下略------

ハイライト部がひとつのルートテーブルに関する情報の始まりを表します。

ひとつのルートテーブルだけでも情報量が多いですね。これがルートテーブルの数だけ出力されていきます。大抵の場合において必要となる情報はこの一部のみですので、jqを使用してひとつのルートテーブルあたりの情報を減らします。

jqのインストール方法や、そもそも何ぞやという部分については本エントリでは割愛します。

特定の値のみ出力する

ルートテーブル ID だけを出力する場合。

$ aws ec2 describe-route-tables\
 | jq '.RouteTables[].RouteTableId'
 
"rtb-xxxb19fd681afbc82"
"rtb-xxxe65ace9b3d862e"
"rtb-xxx4253d4d1a43486"
"rtb-xxxeed7382f10fa48"
"rtb-xxxc22e8a1cf8d06b"
"rtb-xxxc8ccc385f199d6"
"rtb-xxxa20f086b660e42"
"rtb-xxx3b90c62efdef3f"
"rtb-xxxc9a553b256976c"
------以下略------

ルートだけを出力する場合。

$ aws ec2 describe-route-tables\
 | jq '.RouteTables[].Routes[]'

{
  "DestinationCidrBlock": "192.168.0.0/16",
  "GatewayId": "local",
  "Origin": "CreateRouteTable",
  "State": "active"
}
{
  "DestinationCidrBlock": "10.0.0.0/16",
  "Origin": "CreateRoute",
  "State": "active",
  "VpcPeeringConnectionId": "pcx-xxx34603"
}
{
  "DestinationCidrBlock": "0.0.0.0/0",
  "NatGatewayId": "nat-xxx094c175acfbfbd",
  "Origin": "CreateRoute",
  "State": "active"
}
------以下略------

ルートの中の宛先 CIDR ブロック(IPv4)のみを出力する場合。(IPv6 CIDR DestinationIpv6CidrBlockを持つルートがあった場合、該当部はnullとなります。)

$ aws ec2 describe-route-tables\
 | jq '.RouteTables[].Routes[].DestinationCidrBlock'
 
"192.168.0.0/16"
"10.0.0.0/16"
"0.0.0.0/0"
null
"172.31.0.0/16"
------以下略------

条件付きで特定の値のみ出力する

タグのうち、キーが Name であるもののみ Value を出力する場合。

$ aws ec2 describe-route-tables\
 | jq '.RouteTables[].Tags[] | select(.Key == "Name").Value'

"DEV-ABC-PUB"
"PRO-MIRROR-PRV"
"STG-EFG-PUB"
"PRO-EFG-PUB"
------以下略------

ソートして出力する

ルートテーブルIDの昇順にソートしてルートテーブルIDを出力する場合。

$ aws ec2 describe-route-tables\
 | jq '.RouteTables | sort_by('.RouteTableId')| .[].RouteTableId'

"rtb-0164253d4d1a43xxx"
"rtb-016e33abd3513fxxx"
"rtb-023975477d1adfxxx"
"rtb-02978f64b1ba54xxx"
"rtb-02e107895525a7xxx"
------以下略------

複数の値を出力する

ルートテーブルIDと VPC ID を並列で出力する場合。

$ aws ec2 describe-route-tables\
 | jq -c '.RouteTables[] | [.RouteTableId,.VpcId]'

["rtb-xxxb19fd681afbc82","vpc-xxx645cc0765bfcb5"]
["rtb-xxxe65ace9b3d862e","vpc-xxxeae90"]
["rtb-xxx4253d4d1a43486","vpc-xxxd34c5"]
["rtb-xxxeed7382f10fa48","vpc-xxxee4adfe7047309"]
["rtb-xxxc22e8a1cf8d06b","vpc-xxxc4055"]
------以下略------

ルートテーブルとタグを並列で出力する場合。

$ aws ec2 describe-route-tables\
 | jq -c '.RouteTables[] | [.RouteTableId,.Tags[]]'

["rtb-xxxb19fd681afbc82",{"Key":"Name","Value":"DEV-ABC-PUB"},{"Key":"Env","Value":"Dev"}]
["rtb-xxxe65ace9b3d862e",{"Key":"Name","Value":"PRO-MIRROR-PRV"},{"Key":"Env","Value":"Pro"}]
["rtb-xxx4253d4d1a43486",{"Key":"Env","Value":"Test"}]
["rtb-xxxeed7382f10fa48",{"Key":"Name","Value":"STG-EFG-PUB"},{"Key":"Env","Value":"Stg"}]

ルートテーブル ID と Name タグを並列で出力する場合。(Nameタグが無い場合はルートテーブルIDのみ。)

$ aws ec2 describe-route-tables\
 | jq -c '.RouteTables[] | [.RouteTableId,(.Tags[] | select(.Key == "Name").Value)]'

["rtb-xxxb19fd681afbc82","DEV-ABC-PUB"]
["rtb-xxxe65ace9b3d862e","PRO-MIRROR-PRV"]
["rtb-xxx4253d4d1a43486"]
["rtb-xxxeed7382f10fa48","STG-EFG-PUB"]
------以下略------

いろいろ組み合わせて出力する

Nameタグの値でソートして Name タグとルートテーブル ID を表示する場合。

$ aws ec2 describe-route-tables\
 | jq -c  '.RouteTables | sort_by(.Tags[] | select(.Key == "Name").Value) | .[] | [(.Tags[] | select(.Key == "Name").Value),.RouteTableId]'
 
["rtb-xxx3388d"]
["rtb-xxx409ac"]
["rtb-xxx0484c"]
["rtb-xxx88aaa"]
["DEV-ABC-PRV","rtb-xxx3b90c62efdef3f"]
["DEV-ABC-PUB","rtb-xxxc22e8a1cf8d06b"]
["DEV-EFG-PRV","rtb-xxx87907a844afb48"]
------以下略------

Nameタグの値でソートして Name タグとルートテーブルIDと VPC ID と関連づけサブネットを表示する場合。

$ aws ec2 describe-route-tables\
| jq -c  '.RouteTables | sort_by(.Tags[] | select(.Key == "Name").Value) | .[] | [(.Tags[] | select(.Key == "Name").Value),.RouteTableId,.VpcId,.Associations[].SubnetId]'

["rtb-xxx3388d","vpc-xxxeae90",null]
["rtb-xxx409ac","vpc-xxxfcde0",null,"subnet-xxx5c1d5","subnet-xxx16567"]
["rtb-xxx0484c","vpc-xxxd34c5",null]
["rtb-xxx88aaa","vpc-xxx456d7",null]
["DEV-ABC-PRV","rtb-xxx3b90c62efdef3f","vpc-xxxc4055","subnet-xxxcfbfccc9552f14"]
["DEV-ABC-PUB","rtb-xxxc22e8a1cf8d06b","vpc-xxxc4055","subnet-xxx4c5f1f492899d8","subnet-xxx8661855ac07146"]
["DEV-EFG-PRV","rtb-xxx87907a844afb48","vpc-xxx7871f72aa43134","subnet-xxxade7d3be17d66a"]
------以下略------

jq の使い方については以下もあわせてご参照ください。

--filters オプションを使って特定のリソースのみ出力対象にする

jqを使用することで必要な値のみ出力させることができることがわかりました。しかし、あくまで個々のルートテーブルに関する情報を絞っているだけで、母数となるルートテーブルの数は変わっていません。

特定の条件に合致するルートテーブルをフィルタリングし、その上でjqで特定の値を出力したいです。フィルタリングに使用できるものとして、 AWS CLI の--filtersオプションがあります。

--filtersオプションはすべての AWS CLI コマンドで使用できるわけではなく、describe系オペレーションの一部で使用可能となっています。実行したいコマンドで対応しているか、都度リファレンスで確認すると良いでしょう。

--filters オプションの書式

以下のような書式で指定することになります。

--filters "Name=xxx,Values=yyy"

Values には複数の値を指定できます。

--filters "Name=xxx,Values=yyy,zzz"

Name と Values の組み合わせを複数指定することもできます。

--filters "Name=xxx,Values=yyy,zzz" "Name=aaa,Values=bbb"

Values を複数指定した場合は OR 条件--filtersを複数指定した場合には AND 条件となります。

--filters オプションの Name と Value(s)

どういった条件でフィルタリングするかはNameValuesに指定した値によって決まります。そしてどのような値を指定できるかはコマンドごとに異なります。これも都度リファレンスを参照するとよいでしょう。

今回は aws ec2 describe-route-tablesで指定できるものを取り上げます。

Name 説明 Value例
association.route-table-association-id ルートテーブルのアソシエーション ID rtbassoc--xxx
association.route-table-id アソシエーション内のルートテーブル ID rtb-xxx
association.subnet-id アソシエーション内のサブネット ID subnet-xxx
association.main メインルートテーブルであるか否か true | false
owner-id ルートテーブルの owner のアカウント番号 111111111111
route-table-id ルートテーブル ID rtb-xxx
route.destination-cidr-block ルートで宛先として指定された IPv4 CIDR 範囲 0.0.0.0/0
route.destination-ipv6-cidr-block ルートで宛先として指定された IPv6 CIDR 範囲 2001:db8:1234:1a00::/56
route.destination-prefix-list-id ルートで宛先として指定されたプレフィックスリストID pl-xxx
route.egress-only-internet-gateway-id ルートでターゲットとして指定された EIGW ID eigw-xxx
route.gateway-id ルートでターゲットとして指定された IGW ID igw-xxx
route.instance-id ルートでターゲットとして指定されたインスタンス ID(NATインスタンスなど) i-xxx
route.nat-gateway-id ルートでターゲットとして指定された NAT Gateway ID nat-xxx
route.transit-gateway-id ルートでターゲットとして指定された Transit Gateway ID tgw-xxx
route.origin ルートがどのように作成されたか ※後述
route.state ルートのステータス active | blackhole
route.vpc-peering-connection-id ルートでターゲットとして指定されたピアリング ID pcx-xxx
tag :<key> タグのキーと値の組み合わせでフィルタリングする場合に使用 <key>タグの値
tag-key タグのキーだけでフィルタリングする場合に使用 Name
vpc-id ルートテーブルが所属する VPC ID vpc-xxx

「※後述」としたroute.originの Value は以下のいずれかをとります。

  • CreateRouteTable: ルートテーブルの作成時に作成されたルート(local
  • CreateRoute: 手動で作成されたルート
  • EnableVgwRoutePropagation: ルート伝播により作成されたルート

冒頭の Output 例を鑑みると、概ねすべての情報を使用してフィルタリングが可能であることがわかります。

これを踏まえて幾つかのシナリオでフィルタリングしていきましょう。

特定の NAT Gateway をターゲットに持つルートテーブルをフィルタリング

特定の NAT Gateway でデータ転送料金が大きく掛かっており、そこを経由するインスタンスがどのサブネットに属しているか?というのを確認したいケースがありました。

NAT Gatewayとコストのお話については以下をどうぞ。

NAT Gateway は他の多くの VPC のゲートウェイと異なり、AZ を跨ぐ構成は取れません。マルチ AZ 構成をとる環境では、大抵の場合、それぞれの AZ に NAT Gatway を作成することになります。そして、各 AZ のワークロードが同じであれば、各 NAT Gateway で似たようなデータ転送量をとることになるでしょう。

今回のケースでも、「特定の NAT Gateway」は2つありました。

  • nat-xxx
  • nat-yyy

上記のいずれかの NAT Gateway をターゲットに持つルートを含むルートテーブルをフィルタリングしたいです。--filtersオプションでは Name にroute.nat-gateway-idを、Values には nat-xxxnat-yyyを指定することで実現できます。

aws ec2 describe-route-tables \
  --filters "Name=route.nat-gateway-id,Values=nat-xxx,nat-yyy" \
  | jq '.RouteTables[].RouteTableId'

"rtb-xxxc22e8a1cf8d06b"
"rtb-xxx338e9d9d5280c5"
"rtb-xxxe33abd3513ff20"
------以下略------

jqは先述の通り様々な出力の仕方ができますので、必要に応じて変更してください。

S3 向けのゲートウェイ VPC エンドポイント宛のルートを持つルートテーブルをフィルタリング

NAT Gateway を経由する通信が意図通りのものであればよいですが、内訳に S3 宛の通信が含まれていると困ります。 S3 向けの VPC エンドポイントを経由した場合は追加の通信料はかからないため、 NAT Gateway を経由している場合には余計なコストを払っているということになるからです。

S3 向けの VPC エンドポイント(ゲートウェイ型)をターゲットに持つルートをフィルタリングしたくなります。

--filtersの Name には VPC エンドポイントを指定できるものが用意されていません。代わりに、route.destination-prefix-list-idを使用します。

ゲートウェイエンドポイントをターゲットに指定する際、宛先にはプレフィックスリスト ID を指定することになります。東京リージョンの S3 であればpl-61a54008が該当します。--filtersの Value としてそちらを指定すれば問題なくフィルタリングできます。

プレフィックスリスト ID はマネジメントコンソールで確認することもできますし、以下のコマンドで確認することもできます。

% aws ec2 describe-prefix-lists --region ap-northeast-1
{
    "PrefixLists": [
        {
            "Cidrs": [
                "52.94.8.0/24"
            ],
            "PrefixListId": "pl-78a54011",
            "PrefixListName": "com.amazonaws.ap-northeast-1.dynamodb"
        },
        {
            "Cidrs": [
                "3.5.152.0/21",
                "52.219.0.0/20",
                "52.219.136.0/22",
                "52.219.16.0/22",
                "52.219.68.0/22"
            ],
            "PrefixListId": "pl-61a54008",
            "PrefixListName": "com.amazonaws.ap-northeast-1.s3"
        }
    ]
}

以下のようなコマンドで S3 VPC エンドポイント向けルートを持つルートテーブルをフィルタリングできます。

$ aws ec2 describe-route-tables\
 --filters "Name=route.destination-prefix-list-id,Values=pl-61a54008"

先ほどとの結果とあわせて、特定の NAT Gateway 向けのルートを持つルートテーブルが VPC エンドポイント向けルートも持っているか、を AND 条件でフィルタリングすることもできます。

$ aws ec2 describe-route-tables\
 --filters "Name=route.destination-prefix-list-id,Values=pl-61a54008"\
           "Name=route.nat-gateway-id,Values=nat-xxx,nat-yyy"

パブリックサブネットをフィルタリング

--filtersオプションではワイルドカードを使用することもできます。

フィルタの値には、ワイルドカードを使用することもできます。アスタリスク (*) は 0 個以上の文字、クエスチョンマーク (?) は 0 文字または 1 文字にマッチングします。

以下のように指定すれば、(特定のリソースに限定せず)いずれかのインターネットゲートウェイをターゲットとするルートを持つルートテーブル、というフィルタリングの仕方ができます。

aws ec2 describe-route-tables \
  --filters "Name=route.gateway-id,Values=igw-*"

Values はigw-*でなく*で問題ないのでは、と考えていたのですが、*を指定すると IGW をターゲットに持たないものも含めてすべてが該当してしまい、フィルタリングを行う前と同様の結果となりました。(出力結果の個数をwc -lで確認)

Values で * を指定した場合

$ aws ec2 describe-route-tables\
 --filters "Name=route.gateway-id,Values=*"\
 | jq '.RouteTables[].RouteTableId' | wc -l
      82

フィルタリングをしない場合

aws ec2 describe-route-tables\
 | jq '.RouteTables[].RouteTableId' | wc -l
      82

もちろんi*でもig*でもいいのですが、何かしら指定する必要があります。

--filtersオプションについては以下もあわせてご参照ください。

終わりに

AWS CLI の--flitersオプションとjqを使用してお目当てのルートテーブルを探し当てる、というエントリでした。ルートテーブルが適用されているサブネットを引っ張ってくることもできるのは便利ですね。

jqでなく--queryオプションを使用しても同じようなことはできるはずなのですが、なんとなくjqの書き方の方が好みなのでついつい使ってしまいます。

ささっと作業するにはマネジメントコンソールが便利ですが、ある程度の規模になったら AWS CLI でいい感じにこねくり回していきたいですね。

以上、千葉(幸)がお送りしました。