NAT Instanceの移行作業をやってみた

2023.03.21

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

しばたです。

私はAWS利用費を節約するために昔から検証環境でNAT Instanceを利用しています。
これまではずっとAmazon Linuxの公式AMIを使っていたのですが、先日Amazon Linux 2023も正式リリースされ「流石に2世代前のディストリビューションを使い続けるのはマズいな。メンテナンスサポート切れも近いし...」という気持ちになったので環境を刷新することにしました。

前の記事では移行先となる新しいNAT Instanceの作り方を解説しましたが、本記事では環境の移行手順を説明していきます。

最初に考えた方法

検証環境は下図の様にVPC内にPublicサブネットとPrivateサブネットがそれぞれあり、Publicサブネットに現行のNAT Instance(Amazon Linux環境)が配置されています。
そしてPrivateサブネットのルートテーブルにNAT InstanceのENIを宛先とするルートが記述されています。

最初はルートテーブルを変更しないで済む様に、新しいNAT Instance(Amazon Linux 2023環境)を作った後でENIを差し替えようと計画したのですが残念ながらこの方法は使えませんでした。

理由はシンプルでプライマリネットワークインターフェイスと紐づくENIはデタッチできないAWSの仕様があるためです。

一応EC2インスタンスを削除する場合であればENIだけ残しておく事は可能なんですが、新しいNAT Instanceで動作確認が終るまでは現環境を残しておきたかったのでこの方法は諦めることにしました。

実際に試した方法

というわけで実際に行った方法は愚直に

  1. 新しいNAT Instance(Amazon Linux 2023環境)を新規作成する
  2. 現NAT Instanceを参照するルートテーブルを一つずつ更新していく

という形になりました。

結果だけ見ればごく当たり前の方法に落ち着いた感じです。
一応ルートテーブルの更新漏れがない様に後半の作業はCLIでやることにします。

1. 新しいNAT Instance(Amazon Linux 2023環境)の作成

新しいNAT Instanceの構築方法は前回の記事を参照してください。

インスタンスのスペックは現環境をベースに決めると良いでしょう。
セキュリティグループは現環境のものを共用できるはずです。

私の検証環境では下図の様に現NAT Instance(shiba-dev-nat-al1)と新NAT Instance(shiba-dev-nat-al2023)が共存する形になりました。

2. ルートテーブルの更新

この時点で各ルートテーブルは現NAT Instanceを参照しています。

AWS Cloud Shellから以下のPowerShellコマンドを実行すると現NAT Instaceを参照してるルートテーブルとルート情報の一覧を取得できます。
(自分がPowerShellに慣れ過ぎていてAWS CLIより圧倒的に早くコマンドを書けるため今回はAWS Tools for PowerShellを採用しています。AWS CLIに慣れている方はAWS CLIを使うと良いでしょう。)

# 自分が書き慣れているAWS Tools for PowerShellで記述

# 当該NAT Instanceを含むルート一覧を取得
$currentNATInstanceID = 'i-05bc56xxxxxxxxxxx' # EC2インスタンスIDを指定
Get-EC2RouteTable -Filter @{Name='route.instance-id'; Values=$currentNATInstanceID} | 
    ForEach-Object {
        $routeTableID = $_.RouteTableId
        $_.Routes | Where-Object {$_.InstanceId} | ForEach-Object {
            [PSCustomObject]@{
                RouteTableId = $routeTableID
                DestinationCidrBlock = $_.DestinationCidrBlock
                NetworkInterfaceId = $_.NetworkInterfaceId
                InstanceId = $_.InstanceId
            }
        }
    }

マネジメントコンソールから確認するとこんな感じです。
現NAT InstanceのENI(eni-076db8xxxxxxxxxxx)を参照しているのが分かりますね。

続けて以下のコマンドで各ルートテーブルのインスタンスIDを新NAT Instanceのものに更新します。
前のコマンドでルートの一覧を取得しているので、そのままパイプで繋げてやりSet-EC2Routeコマンドを使い一つ一つ内容を更新しています。

# 自分が書き慣れているAWS Tools for PowerShellで記述

# 各ルートの内容を更新
$currentNATInstanceID = 'i-05bc56xxxxxxxxxxx' # 現NAT Instance IDを指定
$newNATInstaceID      = 'i-099afdxxxxxxxxxxx' # 新NAT Instance IDを指定
Get-EC2RouteTable -Filter @{Name='route.instance-id'; Values=$currentNATInstanceID} | 
    ForEach-Object {
        $routeTableID = $_.RouteTableId
        $_.Routes | Where-Object {$_.InstanceId} | ForEach-Object {
            [PSCustomObject]@{
                RouteTableId = $routeTableID
                DestinationCidrBlock = $_.DestinationCidrBlock
                NetworkInterfaceId = $_.NetworkInterfaceId
                InstanceId = $_.InstanceId
            }
        }
    } | ForEach-Object {
        # ここから更新処理
        Write-Output ('Update RouteTable {0}, route {1} instance to {2} ...' -f $_.RouteTableId, $_.DestinationCidrBlock, $newNATInstaceID)
        Set-EC2Route -RouteTableId $_.RouteTableId -DestinationCidrBlock $_.DestinationCidrBlock -InstanceId $newNATInstaceID
    }

結果はこんな感じです。
今回は対象ルートテーブルが1つだけだったのですが複数あっても期待した動作をするはずです。

マネジメントコンソールからもちゃんとルート情報が更新されているのがわかります。

3. 動作確認と後始末

あとは実際に新NAT Instanceが使わているか確認し、問題がなければ現NAT Instanceを停止・削除すると良いでしょう。

最後に

以上となります。

結果として愚直な方法を選ぶことになりましたがAWSの仕様を誤解してたことに早めに気が付くことができて良かったと感じています。
本記事の内容が皆さんの役に立てば幸いです。