【AWS】VPC Lambdaを構築したときのメモ

普段、インフラを構築する機会が滅多にないけど、最近VPC Lambdaを構築する機会があったので、色々学んだ事をメモしておく。

**やりたかったこと**

- Lambdaからピアリングで他のVPCにあるAPIとの接続
- Lambdaからインターネット経由で外部APIとの接続

**本記事の内容**

- Lambdaを置くPrivateサブネットのCIDR設計
- Privateサブネットからの疎通確認
- SessionManager越しのSSHアクセス


# 背景
今までLambdaをVPC内に置く必要はなかったが、LambdaからVPCピアリングでAPIにリクエストする必要が出たのでLambdaをVPCのサブネットに含む構成に変えることになった。

ネットワーク構成は以下の図の通り。
Lambdaからインターネット経由で外のAPIにアクセスする必要もあるためPublicサブネット内にNATインスタンスを用意している。
![aws-new2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/9819c95d-d564-eca4-2bcb-6e2b79c5290d.jpeg)

# LambdaのサブネットのCIDR
VPC Lambdaはアンチパターンと言われていたイメージがあったので、Lambdaを置くサブネットのCIDR設計の際に以下のような心配をした。

- 新しいENIを作成してアタッチするときにコールドスタートが起こる恐れ
- リクエストの増加につれて、多くのENIが作成されIPが枯渇する恐れ

しかし、調べたら2019年9月からLambda関数がVPCに接続する方法が改善されていた。（参考：[[発表] Lambda 関数が VPC 環境で改善されます](https://aws.amazon.com/jp/blogs/news/announcing-improved-vpc-networking-for-aws-lambda-functions/)）。
以前はVPCのネットワークインターフェースをLambda実行環境に直接マッピングする方法だったのが、現在はVPCのネットワークインターフェースはHyperplane ENIにマッピングされ、Lambda関数はそれを使用して接続する仕組みに変わっている。

↓ 2019年9月まで
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/f281a8e8-3dbb-504e-63ff-3afaae3e6aa6.png)
↓ 2019年9月以降
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/c7de733e-05b1-2bbc-6bd5-b9cc6afb6456.png)

記事の中で自分が懸念していた事柄が改善されている旨がちゃんと記載されていた。
> ネットワークインターフェースの作成は、Lambda 関数が作成されるか、VPC 設定が更新されるときに発生します。 関数が呼び出されると、実行環境は事前に作成されたネットワークインターフェイスを使用するだけで、そこへのネットワークトンネルをすばやく確立します。 これにより、コールドスタートでのネットワークインターフェイスの作成と接続にこれまで発生していた遅延が劇的に削減されます。

> ネットワークインターフェイスは実行環境全体で共有されるため、通常、関数ごとに必要なネットワークインターフェイスはほんの少数です。 アカウント内の関数にまたがるすべての一意のセキュリティグループ：サブネットの組み合わせには、個別のネットワークインターフェイスが必要です。 組み合わせがアカウント内の複数の関数で共有されている場合、関数間で同じネットワークインターフェイスを再利用します。

> 関数のスケーリングは、ネットワークインターフェースの数に直接関係しなくなり、Hyperplane ENI は、多数の同時関数実行をサポートするようにスケールできます

念のため、Lambdaに並列実行数が500リクエスト以上行う検証をしてみたが、最初に作られたネットワークインターフェース１つが常に再利用されていた。

（またCIDRの設計では、VPCピアリングする場合に接続先のVPCとCIDRが被らないように注意が必要ということも当然なことだけど学びました。）

# Privateサブネット内のLambdaからの疎通がうまくいかなかったときの原因とやったこと
Privateサブネット内のLambdaから、ピアリング先や外部との疎通がうまくいかなくてハマった時のことを書いておく。

結論から言うと、以下がそれぞれ原因だった。

- ピアリング先との原因 → **Lambdaのエイリアスのversion指定が古いのを指定していた**
- 外部との原因 → **NATインスタンスの設定で送信元/送信先を確認する設定を無効にしていなかった**

疎通できない原因ってわかると大体あっけないものですよね:sweat:

## 解決のためにやったこと
原因は先程書いたとおりだったが、解決のためにやったことを書いておく。

### ピアリング先との疎通確認

1．Lambdaのいるサブネットに仮でEC2インスタンスを立てて、そこからピアリング先と疎通確認 → 問題なく疎通できた → サブネットの設定は問題なさそう
2．じゃあ原因はLambdaからのアクセスか？
3．仮で新たにテスト用のLambda(以下のtest.js)を作成してレスポンス返ってくるか確認した。
→ 疎通できたのでLambdaからのアクセスも問題なさそう

```test.js
var http = require('http')
     
exports.handler = (event, context, callback) => {
    const options = {
        hostname: event.Host,
        port: 80
    }
    
    const response = {};
    
   http.get(options, (res) => {
        response.httpStatus = res.statusCode
        callback(null, response)
    }).on('error', (err) =>{
        callback(null, err.message);
    })
   
};
```

4．じゃあ原因はAPIGatewayからLambda????

うーん。:rolling_eyes:

5．改めてLambdaの設定を眺めると、指定しているエイリアスのversionがおかしい。。！:scream:

→ エイリアスを指定してLambdaを見てみるとVPCに関連付けされていなかった。。。つまり古い構成のままになってしまっていた。。。という結末でした。

### 外部との疎通確認
1. EC2 instanceの作成時にNAT用のAMIを使用しているか再確認
2. private, publicのサブネットのNACL、インスタンスのセキュリティグループを全許可にして試す
3. privateサブネット内に仮で作ったインスタンスからwww.google.comにpingもcurlも失敗 → この時点でセキュリティグループやNACLの設定の問題じゃなさそう:thinking:
5. publicサブネット内のNATインスタンスへpingは成功 → VPC内の疎通は問題なさそう
6. tracerouteを使ってrouteの経路を見てみる
7. netstatで、TCP接続のステータスを確認 → establishedになっていない

完全に外との疎通が全くできていなさそうなことから、NATインスタンスがNATの役目を果たしていないよなーと思って、改めて[AWSのNATの公式ドキュメント](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/VPC_NAT_Instance.html)を読み直した。
そしたら、、、

> EC2 インスタンスは、送信元/送信先チェックをデフォルトで実行します。つまり、そのインスタンスは、そのインスタンスが送受信する任意のトラフィックの送信元または送信先である必要があります。しかし、NAT インスタンスは、送信元または送信先がそのインスタンスでないときにも、トラフィックを送受信できなければなりません。したがって、NAT インスタンスでは送信元/送信先チェックを無効にする必要があります。

ここを見落としていました。:disappointed::sweat_drops:
ドキュメントに従って設定したら疎通できました。

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/ec4662f9-5e65-7c74-6917-c17d912b4583.png)
![無題2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/36344fe6-f15c-eb32-77e3-5c3bbc3c24f4.png)

# SessionManager越しにSSHアクセスする
SessionManagerを使えば、もうbastionは不要になると聞いて初めてSessionManagerを使ってみた。

SessionManagerを利用するには以下が必須条件。

- EC2インスタンスに`AmazonSSMManagedInstanceCore`のロールが付与されていること
- EC2インスタンスにSSMエージェントがインストールされていること

## ロールの付与
IAMにいき、以下のように`AmazonSSMManagedInstanceCore`ポリシーをアタッチする。
![ssm-role.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/f2097983-ddc5-e743-67d0-56360df60b17.png)

上記のロールをEC2インスタンスのロールに付与する。

## SSM エージェントのインストール
SSMエージェントは、以下のEC2インスタンスとAmazon Machine Images (AMIs)ではデフォルトでインストールされている。

- Amazon Linux
- Amazon Linux 2
- Amazon Linux 2 ECS-Optimized AMIs
- Ubuntu Server 16.04, 18.04, and 20.04

**ただし上記以外のAMIを利用した場合は手動でインストールする必要があります:point_up:。**

ロールを付与した後に、
インスタンスのアクション > 接続 > セッションマネージャー
を開くと、セッションマネージャーがインスタンスに接続できていないかどうかがわかる。

もし以下の図のように、接続できていないならそのインスタンスにSSMエージェントがインストールされていないと思われる。

![ssm1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/d8cd397f-2f5f-e197-5cc3-6cf34222cb5c.png)

![ssm2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/879f4e55-3518-4927-5663-a6b36fffec17.png)

bastion不要になる、22番ポートをセキュリティグループで許可しなくて済むと思っていたけど、SSMエージェントをインストールするために初めは必要なんだね。:kissing:

とはいえ、SSMエージェントをインストールする手順はすぐ簡単にできる。
※試してるだけなので、ユーザーはデフォルトのec2-userです、お許しください

```
// SSHでインスタンスに入る
$ssh -i "test-key.pem" ec2-user@（インスタンスのIP）

// SSMエージェントをインストール
$sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm

// SSMエージェントのステータスを確認してみる
$sudo status amazon-ssm-agent
// 実行結果
amazon-ssm-agent start/running, process 2774
```
このあと、AWSコンソールからSessionManagerを開いてみると、、、
![ssm-success.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/98a00a96-3a61-f16f-014d-db609053151b.png)

表示されているー！:open_hands:

インスタンスのアクション > 接続 > セッションマネージャー
を開いても、SessionManagerがインスタンスに接続できる状態に変わっている！

![ssm-instance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/e038b160-b149-af34-6f14-71a0f6f7c033.png)

セッションを開始するを押すと、無事にインスタンスに入れた。
![ssm-console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266675/33dc2ec0-ca39-f8f6-81be-50a62f00737b.png)

ここまで確認できたら、SSMエージェントのインストールのために許可した22番ポートやbastionを削除するのを最後に忘れずにやっておわりです。

:pushpin:　注意事項としてはエージェントの起動に多少時間がかかるところ。
設定が問題なくても、EC2内のエージェントが起動直後で立ち上がっていないとセッションマネージャーの画面にいっても何も表示されない。自分の場合は10分以上待ってようやく表示されたので、もしSSMエージェントのインストールや、ロールの設定も済んでいるのに表示されない場合は10分以上様子を見た方がいいです。

# おわりに
どれも基本的な内容かなと思いますが、
普段インフラを触ることがあまりない自分にとっては勉強になったことをまとめました！

間違っている点などあればご指摘お願いします:pray: