nat-instance(terraform-aws-nat-instance)を利用して発生したトラブルについてまとめ

## はじめに

私が参画しているプロジェクトでは、Terraformを使ってAWS環境を管理しています。

AWSのコスト削減を目的に、[terraform-aws-nat-instance](https://registry.terraform.io/modules/int128/nat-instance/aws/latest)モジュールを導入し、NAT Gatewayの代わりにNAT Instanceを利用している箇所があります。

これを組み込んでいる具体的なシステムには触れませんが、チーム内専用で利用するようなシステム(仕組み)の中で使っています。
いつ動かすか分からない上、呼んだ時はすぐに動作して欲しいため、常時稼働させておく必要はあるのですがコストは掛けたくない、AWSのフルマネージドの恩恵までは不要、くらいの扱いなのでちょうど良かった感じです。

このモジュールはTerraformでサクッとnat-instanceを構築するのに便利な一方で、利用している中で予期せぬトラブルが発生しました。

その時のメモを残しておこうと思い書きました。

## 留意点

ここで触れている[terraform-aws-nat-instance](https://registry.terraform.io/modules/int128/nat-instance/aws/2.1.0)モジュールのバージョンは、2023年1月7日に公開されたものです。

terraform-aws-nat-instanceモジュールは、[GitHubリポジトリ上](https://github.com/int128/terraform-aws-nat-instance)でアーカイブ状態になっています。

作者でメンテされないことを考慮すると、今後AWSやTerraformの更新等で突如機能しなくなる可能性もあるため、そのまま利用するにあたっては十分な注意が必要です。

基本的には、社内に閉じたシステム(ちょっとしたツール、自動化の仕組み等)でNATのコストを節約したい場合、すでに動いているシステムや引き継いだシステムで同モジュールが使われていて同様の問題が起きている場合、個人学習・個人開発等で使う場合といった方に向けて参考になるのかなと思います。

## このモジュールを使って発生した問題

ここから本題です。


### いつの間にか再起動されていた

このモジュールでは、コストを抑えるためにデフォルトでスポットインスタンスを利用するようになっています。このスポットインスタンスは[突然インスタンスが終了する可能性](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/how-spot-instances-work.html)があります。

スポットインスタンスを利用していたためいつの間にか再起動が発生していた、ということなのですが、特に設定値を気にせず利用していた関係で、利用初期当時は意図せず落ちた理由は調査するまで分かりませんでした。

[terraform-aws-nat-instance](https://registry.terraform.io/modules/int128/nat-instance/aws/latest)モジュールではオートスケーリングが設定されているため、万一落ちても自動復旧されるようになっています。

そのため、再起動されること自体は問題はなかったのですが、EC2の急な停止に伴う次の事象が問題につながっていました。

### ENIがデタッチされたままになる

インスタンスが終了しても、オートスケーリングによって新しいNAT Instanceが自動で立ち上がるため、一見問題はなさそうに思えます。

しかし、インスタンスが再起動された際に、インスタンスにENIがアタッチされていなかったのです。

本来であれば新しいインスタンスに同じENIが再アタッチされるべきですがうまくアタッチされていませんでした。これにより、NAT Instance経由での通信ができなくなるという問題が発生しました。

細かい事情で言えば、社内で利用するシステムとして、システム間連携のためにIP指定で穴あけをしていたのですが、ENIのアタッチができなかった関係で外向きのIPが変わってしまい、疎通が取れなくなっていたのです。

## 復旧

###  ENIを手動でアタッチする

調査をしてこの問題が発覚し、即時対応としては手動でENIを再アタッチするを試み、無事に復旧しました。
ENIの一覧で「Available」になっているものが1つだけ出てくる(※)ので、外れたもの・アタッチする必要があるものはすぐに特定できるかと思います。

(※) 使用しないENIリソースはいつまでもAvailable状態で残しておかない前提です...

### 予防策を取る

NAT InstanceがENIをデタッチしたままになる問題は、手動で復旧することはできますが、可能であれば事前に手を打っておきたいです。

#### スポットインスタンスを利用しないようにする

これは根本解決ではないですが、前述の即興対応の延長線上としての1案です。

今回、スポットインスタンスが突然終了してしまうことでこの事象が発生しました。
また、スポットインスタンスがいつ利用できなくなるかはわからないため、再起動の頻度が未知数です。少なくともオンデマンドよりは増えてしまいます。

そこで、若干コストを犠牲にして安定性を優先する場合は、オンデマンドインスタンスを利用するように修正します。(それでもNAT Gatewayよりは十分安価です。)

##### 手動で対応する場合

ENIをアタッチしたついでに、オートスケーリングの画面で変更します。

オートスケーリングの設定で、インスタンスの購入オプションを「スポットインスタンス : 0%」「オンデマンドインスタンス : 100%」とすればOKです。

##### コードを修正する場合

[こちら](https://github.com/int128/terraform-aws-nat-instance)を参考に、モジュールを使用している箇所の記述を修正します。

```hcl
module "nat_instance" {
  source  = "int128/nat-instance/aws"
  version = "2.1.0"

  ...

  use_spot_instance = false # これを追記
}

```

### 根本解決に向けて

この問題は認知されており、GitHub上で解決に向けたプルリクエストが出されています。

- 参考プルリクエスト: [Pull Request #72](https://github.com/int128/terraform-aws-nat-instance/pull/72)
- 修正されたコード: <https://github.com/roman8422/terraform-aws-nat-instance/tree/fix-interface-in-use>

こちらはまだ試せていないですが、この修正されたバージョンを利用することで解決する可能性があります。

<https://github.com/int128/terraform-aws-nat-instance/pull/72/files>

修正コードを見ると、アタッチするタイミングの調整と、アタッチできるまで繰り返し試みるようになってますね。

ただし、このプルリクエストはマージされていないため、動作確認をした上で利用を検討するのが良さそうです。(Public archiveなのでマージされないかと思いますが) 

結構Forkされているので、上記修正の考慮や独自カスタムを加えて利用すると幸せになれるかもしれません。

## おまけ

### NAT Gateway と NAT Instance

AWS環境にて、プライベートサブネットのリソースからインターネットにアクセスするためにはNAT機能を使用します。
NATには、AWSのマネージドサービスである「NAT Gateway」と、自分でEC2インスタンスを使って構築する「NAT Instance」という2つの選択肢があります。

基本的にはNAT Gatewayを用いるのが無難ですが、そのシステムの用途や利用頻度等によってはコストが気になってくるケースがあると思います。そんなに使ってないシステムなのでコスト節約を検討しろ！みたいなケースになってしまうかもしれません。そこでNAT Instanceが選択肢に入ったりします。

### コスト比較

- NAT Gatewayのコスト
    - (例)東京リージョンでは1つのNAT Gatewayを利用した場合
        - 44.64USD（0.062USD/時）＋データ転送料（0.062USD/GB）

- NAT Instanceを利用した場合のコスト
    - (例)t3.microインスタンス（オンデマンド）を利用した場合
        - 9.93USD（0.0136USD/時間）

結構違ってきますね。
このコスト差分とNAT Gatewayを使っているシステムの利用用途やSLA等を踏まえて、マネージドである必要性、コストを支払った分の恩恵を受けられるかどうか、あたりを考慮すると良さそうですね。


### このモジュールを使う利点

モジュールの謳い文句はこちらですね。

<https://github.com/int128/terraform-aws-nat-instance>

> Features:
> 
> - Providing NAT for private subnet(s)
> - Auto healing using an auto scaling group
> - Saving cost using a spot instance (from $1/month)
> - Fixed source IP address by reattaching ENI
> - Supporting Systems Manager Session Manager
> - Compatible with workspaces

#### オートヒーリング

オートスケーリングを使って、NAT Instanceが意図せず停止した場合でも自動的に復旧するように設計されています。

参考 : [オートリカバリーとオートヒーリングの違いを教えてください](https://dev.classmethod.jp/articles/tsnote-ec2-auto-recovery-healing/)

#### スポットインスタンスでさらにコスト削減

EC2オンデマンドインスタンスでNAT Gatewayよりも安価になりますが、スポットインスタンスを利用することでさらにコスト削減が実現できます。
スポットインスタンスはAWSの未使用リソースを活用しているため、オンデマンドより安価で利用できる仕組みです。

terraform-aws-nat-instanceモジュールでは、デフォルトではスポットインスタンスを利用するようになっています。

参考 : [スポットインスタンス](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-spot-instances.html)

## おわりに

備忘録兼ねて、terraform-aws-nat-instanceモジュールを使った時に発生したトラブルについてまとめてみました。

急にシステムが使えなくなってびっくりして、どこが原因なのかを突き止めるのに調査にはそれなりに時間を要してしまいました。

同様の問題に直面している方やNAT Instanceの導入を検討している方の参考になれば幸いです。