概要
本記事ではStep Functionsを利用して、Nat Gatewayを自動で作成/削除する方法について記載します。
NAT Gatewayは作成しているだけでコストがかかるリソースであり、開発環境の利用していない時間帯などは停止(削除)することでコスト削減につながります。
同じことを以下の記事でCloudFormationを利用して説明していますが、今回はStep Functionsを利用して実施したいと思います。
https://fu3ak1.hatenablog.com/entry/2020/07/28/135924
以下のことをStep Functionsを利用して実現します。
- 作成時
- 既に存在するElastic IPとSubnetを指定してNAT Gatewayを作成する。
- ルートテーブルを更新し、デフォルトゲートウェイ(0.0.0.0/0)のルーティング先をNAT Gatewayにする
- 削除時
- NAT Gatewayを削除する
- ルートテーブルからデフォルトゲートウェイ(0.0.0.0/0)のルーティングを削除する
Nat Gatewayを作成する
Step FunctionsでNat Gatewayを作成するために必要な情報をJSONの入力で受け取ります。
Step Functionsの入力は以下になります。
{
"subnetId": "subnet-xxxx",
"allocationId": "eipalloc-xxxx",
"routeTableId": "rtb-xxxx"
}
- subnetId: NatGWを作成したいSubnetを指定する
- allocationId: ElasticIPの割り当てIP(allocationId)を指定する。NatGWで利用するElastic IPは固定IPとして利用することを想定して、既に存在するIPを指定します。
- routeTableId: NatGWをデフォルトのルートとするルートテーブルを指定します。
Step Functionsの定義は以下になります。
- CreateNatGateway SubnetIdとAllocationIdを指定してNatGWを作成します。
- Wait NatGWが利用可能になるまで、ルートテーブルのルートに指定できないため、30秒待機します。
- DescribeNatGateways 30秒待機した後、NatGWの情報を取得します。
- CheckStatus NatGWがavailableならば、ルートテーブルにルートを追加するステップに遷移します。まだ、利用可能でない場合はWaitに遷移して、30秒待機します。
- CreateRoute ルートテーブルにルートを追加します。
ワークフロー定義
{
"StartAt": "CreateNatGateway",
"States": {
"CreateNatGateway": {
"Type": "Task",
"Parameters": {
"SubnetId.$": "$.subnetId",
"AllocationId.$": "$.allocationId"
},
"Resource": "arn:aws:states:::aws-sdk:ec2:createNatGateway",
"Next": "DescribeNatGateways",
"ResultSelector": {
"NatGatewayId.$": "$.NatGateway.NatGatewayId"
},
"ResultPath": "$.NatGateway"
},
"Wait": {
"Type": "Wait",
"Seconds" : 30,
"Next": "DescribeNatGateways"
},
"DescribeNatGateways": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:ec2:describeNatGateways",
"Parameters": {
"Filter": [
{
"Name": "nat-gateway-id",
"Values.$": "States.Array($.NatGateway.NatGatewayId)"
}
]
},
"Next": "CheckStatus",
"ResultSelector": {
"NatGatewayId.$": "$.NatGateways[0].NatGatewayId",
"State.$": "$.NatGateways[0].State"
},
"ResultPath": "$.NatGateway"
},
"CheckStatus": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.NatGateway.State",
"StringEquals": "available",
"Next": "CreateRoute"
}
],
"Default": "Wait"
},
"CreateRoute": {
"Type": "Task",
"Next": "Succeed",
"Parameters": {
"DestinationCidrBlock": "0.0.0.0/0",
"NatGatewayId.$": "$.NatGateway.NatGatewayId",
"RouteTableId.$": "$.routeTableId"
},
"Resource": "arn:aws:states:::aws-sdk:ec2:createRoute"
},
"Succeed": {
"Type": "Succeed"
}
}
}
Nat Gatewayを削除する
Step FunctionsでNat Gatewayを削除するために必要な情報をjsonの「入力」で受け取ります。
Step Functionsの入力は以下になります。
- subnetId: 削除したいNat Gatewayが存在するSubnetを指定します。
- routeTableId: デフォルトルートにNat Gatewayのルートテーブルを指定します。
{
"subnetId": "subnet-xxxx",
"routeTableId": "rtb-xxxx"
}
Step Functionsの定義は以下になります。
- DescribeNatGateways: 指定したSubnetに存在するNat Gatewayを取得します。後続でそのIdを指定します。作成削除を行う上で、Nat GatewayのIdは固定されていません。そのため、固定のリソースである、SubnetからDescribeしてIdを取得します。
- DeleteNatGateway: DescribeNatGatewaysで取得したNat Gatewayをidを指定して削除します。
- DeleteRoute: ルートテーブルに存在するデフォルトルートを削除します。
ワークフロー定義
{
"StartAt": "DescribeNatGateways",
"States": {
"DescribeNatGateways": {
"Type": "Task",
"Next": "DeleteNatGateway",
"Parameters": {
"Filter": [
{
"Name": "subnet-id",
"Values.$": "States.Array($.subnetId)"
},
{
"Name": "state",
"Values": ["available"]
}
]
},
"Resource": "arn:aws:states:::aws-sdk:ec2:describeNatGateways",
"ResultPath": "$.DescribeNatGateways",
"Next": "DeleteNatGateway"
},
"DeleteNatGateway": {
"Type": "Task",
"Parameters": {
"NatGatewayId.$": "$.DescribeNatGateways.NatGateways[0].NatGatewayId"
},
"Resource": "arn:aws:states:::aws-sdk:ec2:deleteNatGateway",
"ResultPath": "$.DeleteNatGateway",
"Next": "DeleteRoute"
},
"DeleteRoute": {
"Type": "Task",
"Next": "Succeed",
"Parameters": {
"DestinationCidrBlock": "0.0.0.0/0",
"RouteTableId.$": "$.routeTableId"
},
"Resource": "arn:aws:states:::aws-sdk:ec2:deleteRoute"
},
"Succeed": {
"Type": "Succeed"
}
}
}
複数のNAT Gatewayを作成する
上記の例では一つのNAT Gatewayを指定するパターンを紹介しましたが、複数指定できる方が望ましいです。
また、Step Functionsの入力はいちいちStep Functions実行時に指定するのではなく、どこかで固定しておける方が良いと思います。
そこで、今回は入力をStep FunctionsのItemReaderで取得し、Mapで複数処理することにします。
ItemReaderによってS3に保存されているJSONから複数の入力を受け取ることができます。
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/input-output-itemreader.html
NAT Gatewayを複数作成する例を紹介します。削除する方法も同様の方法で実現できます。
Step Functionsの定義は以下のようになります。createNatGatewayから始まる一連の処理は同じですが、入力をS3から取得して、複数のワークフローを実行できるようにしています。
ワークフロー定義
{
"StartAt": "MapNATGWFromS3",
"States": {
"MapNATGWFromS3": {
"Type": "Map",
"ItemReader": {
"Resource": "arn:aws:states:::s3:getObject",
"ReaderConfig": {
"InputType": "JSON"
},
"Parameters": {
"Bucket": "{S3バケットを指定}",
"Key": "create-natgw.json"
}
},
"MaxConcurrency": 1000,
"Label": "MapNATGWFromS3",
"End": true,
"ItemProcessor": {
"ProcessorConfig": {
"Mode": "DISTRIBUTED",
"ExecutionType": "STANDARD"
},
"StartAt": "createNatGateway",
"States": {
"createNatGateway": {
"Type": "Task",
"Parameters": {
"SubnetId.$": "$.subnetId",
"AllocationId.$": "$.allocationId"
},
"Resource": "arn:aws:states:::aws-sdk:ec2:createNatGateway",
"Next": "CheckStatus",
"ResultSelector": {
"NatGatewayId.$": "$.NatGateway.NatGatewayId"
},
"ResultPath": "$.NatGateway"
},
"Wait": {
"Type": "Wait",
"Seconds" : 30,
"Next": "CheckStatus"
},
"CheckStatus": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:ec2:describeNatGateways",
"Parameters": {
"Filter": [
{
"Name": "nat-gateway-id",
"Values.$": "States.Array($.NatGateway.NatGatewayId)"
}
]
},
"Next": "IsRunning",
"ResultSelector": {
"NatGatewayId.$": "$.NatGateways[0].NatGatewayId",
"State.$": "$.NatGateways[0].State"
},
"ResultPath": "$.NatGateway"
},
"IsRunning": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.NatGateway.State",
"StringEquals": "available",
"Next": "CreateRoute"
}
],
"Default": "Wait"
},
"CreateRoute": {
"Type": "Task",
"Next": "Succeed",
"Parameters": {
"DestinationCidrBlock": "0.0.0.0/0",
"NatGatewayId.$": "$.NatGateway.NatGatewayId",
"RouteTableId.$": "$.routeTableId"
},
"Resource": "arn:aws:states:::aws-sdk:ec2:createRoute"
},
"Succeed": {
"Type": "Succeed"
}
}
}
}
}
}
ワークフローの最初で指定しているcreate-natgw.jsonは以下のようなJSONになります。
NAT Gatewayを作成したい[subnetId, allocationId, routeTableId]の組を指定します。
これを指定したS3に保存しておくことで、Step Functionsの入力として処理することができます。
[
{
"subnetId": "subnet-xxxx",
"allocationId": "eipalloc-xxxx",
"routeTableId": "rtb-xxxx"
},
{
"subnetId": "subnet-yyyy",
"allocationId": "eipalloc-yyyy",
"routeTableId": "rtb-yyyy"
}
]
EventBridgeを利用して定期的に実行する
上記で作成したStep Functionを定期的に実行することで、作成/削除を行うことができます。
terraformのコードで例えば9時に作成して、20時に削除する場合は以下のようになります。
resource "aws_cloudwatch_event_rule" "create" {
name = "create-rule"
description = "create rule"
is_enabled = true
schedule_expression = "cron(0 0 * * ? *)"
}
resource "aws_cloudwatch_event_target" "create" {
target_id = "create-rule"
arn = "{Nat Gatewayを作成するstepfunctionsのarn}"
rule = aws_cloudwatch_event_rule.stop.name
role_arn = aws_iam_role.eventbridge.arn
}
resource "aws_cloudwatch_event_rule" "delete" {
name = "delete-rule"
description = "delete rule"
is_enabled = true
schedule_expression = "cron(0 11 * * ? *)"
}
resource "aws_cloudwatch_event_target" "delete" {
target_id = "delete-rule"
arn = "{Nat Gatewayを削除するstepfunctionsのarn}"
rule = aws_cloudwatch_event_rule.stop.name
role_arn = aws_iam_role.eventbridge.arn
}
まとめ
Step Functionsを利用してNAT Gatewayを定期的に作成/削除する方法を紹介しました。
NAT Gatewayは利用しない時間もコストがかかるサービスなので、少なからずコスト削減につながると思います。