VPCアクセス可能なlambda関数をamplifyで作成する方法


欲しい情報にたどり着くまでに苦労したので残しておきます。

# やること

amplifyで作成したバックエンド用lambda関数に対して、コンソール画面からVPCの設定を変更すると、`amplify push` や `amplify publish`コマンドを実行するタイミングでVPCの設定が消えてしまいます。
そこで本記事では、lambdaのコンソール画面をいじらずにamplifyのみでVPC設定を行い、上記のような場合でも設定が消えないようにするための手順を記載します。

# 前提

- amplifyのバージョン: 10.6.2
- amplifyプロジェクト名： vpctestapp ※作成済み
- 接続先VPCにプライベートサブネット3つとセキュリティグループを1つ作成済み
 （パブリックサブネットへの接続は試していません）
- amplifyを実行できるよう設定したcloud9を利用

以上の環境でlambda関数`vpclambda`を新規追加する場合を想定して、手順を記載します。


# 手順

1. 【事前準備】lambdaの新規作成
1. lambdaのテンプレートファイル編集
    1. VPC接続設定に必要なIAMポリシー追加
    2. VPC接続設定追加（セキュリティグループIDとサブネットグループID）
    3. リソースの構築順指定（lambda作成**前**にIAMポリシー作成する） **←ココ重要:exclamation:**
1. ビルド＆クラウドへの反映
1. 確認

以下に詳細な手順を記載します。

## 1.  【事前準備】lambdaの新規作成

以下のコマンドでlambdaを作成します。

```bash:実行コマンド
amplify add function
```

以下に抜粋した2つの質問以外はすべてデフォルトとしました。

```bash:実行結果（抜粋）
? Provide an AWS Lambda function name: vpclambda
? Do you want to edit the local lambda function now? No
```

```bash:実行結果（全体）
$ amplify add function
? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: vpclambda
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World

Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
- Environment variables configuration
- Secret values configuration

? Do you want to configure advanced settings? No
? Do you want to edit the local lambda function now? No
Successfully added resource vpclambda locally.

Next steps:
Check out sample function code generated in <project-dir>/amplify/backend/function/vpclambda/src
"amplify function build" builds all of your functions currently in the project
"amplify mock function <functionName>" runs your function locally
To access AWS resources outside of this Amplify app, edit the /home/ec2-user/environment/vpctestapp/amplify/backend/function/vpclambda/custom-policies.json
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud
```

```bash:実行結果（作成されるツリー構造）
$ tree amplify/backend/function/vpclambda/
amplify/backend/function/vpclambda/
├── amplify.state
├── custom-policies.json
├── function-parameters.json
├── src
│   ├── event.json
│   ├── index.js
│   └── package.json
└── vpclambda-cloudformation-template.json
```

## 2. lambdaのテンプレートファイル編集

1.で作成されたテンプレートファイル`vpclambda-cloudformation-template.json` に対して3か所追記します。

```diff_json:テンプレートファイル追記箇所
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Lambda Function resource stack creation using Amplify CLI",
  "Parameters": {...},
  "Conditions": {...},
  "Resources": {
    "LambdaFunction": {
+      // 2-3. リソースの構築順指定
+      //   (2-1のIAMポリシー作成後にLambdaFunctionを作成)
+      "DependsOn": [
+        "LambdaExecutionPolicyCustom"
+      ],
+      // 2-3 ここまで
      "Type": "AWS::Lambda::Function",
      "Metadata": {...},
      "Properties": {
        ...
        "Timeout": 25,
+        // 2-2. VPC接続設定追加（上の行のカンマも追加必要）
+        "VpcConfig": {
+          "SecurityGroupIds": [
+            "sg-XXXXXXXXXXXXXXXXX"
+          ],
+          "SubnetIds": [
+            "subnet-XXXXXXXXXXXXXXXXX",
+            "subnet-XXXXXXXXXXXXXXXXX",
+            "subnet-XXXXXXXXXXXXXXXXX"
+          ]
+        }
+        // 2-2 ここまで
      }
    },
    "LambdaExecutionRole": {...},
    "lambdaexecutionpolicy": {...},
+    // 2-1. VPC接続設定に必要なIAMポリシー追加（上の行のカンマも追加必要）
+    "LambdaExecutionPolicyCustom": {
+      "DependsOn": [
+        "LambdaExecutionRole"
+      ],
+      "Type": "AWS::IAM::Policy",
+      "Properties": {
+        "PolicyName": "lambda-execution-policy-custom",
+        "Roles": [
+          {
+            "Ref": "LambdaExecutionRole"
+          }
+        ],
+        "PolicyDocument": {
+          "Version": "2012-10-17",
+          "Statement": [
+            {
+              "Effect": "Allow",
+              "Action": [
+                "ec2:CreateNetworkInterface",
+                "ec2:DescribeNetworkInterfaces",
+                "ec2:DeleteNetworkInterface"
+              ],
+              "Resource": "*"
+            }
+          ]
+        }
+      }
+    }
+   // 2-1 ここまで
  },
  "Outputs": {...}
}
```

- `...`は省略の意
- 行頭`⁺`は追加行を強調するために使用
  (コピペする場合は`⁺`を削除すること)
- テンプレート内にコメントを記入できますが、次の手順を実施するとコメントは消えます

## 3. ビルド＆クラウドへの反映

テンプレートへの追記が完了したら以下のコマンドを実行してクラウドに反映させます。

```bash:実行コマンド
amplify push -y
```

## 4. 確認

AWSコンソールのlambdaのVPC設定に、テンプレートで指定したサブネットID・セキュリティグループが表示されていれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617842/cc080d25-bbb6-2a5e-7de9-ad2610a6e52f.png)

lambdaの実行ロールのポリシーを確認すると、テンプレートで指定した`lambda-execution-policy-custom`ポリシーが追加定されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617842/5893ab89-6e57-074e-ec0c-4fa8d4ad373b.png)



# 【補足1】リソースの構築順を変更しない場合

手順2で「2-3.リソースの構築順変更」を実施しなかった場合、`amplify push`時に以下のようなエラーが出ます。
「2-1. VPC接続設定に必要なIAMポリシー追加」する前の状態（つまり必要な権限がない状態）で、「2-2. VPC接続設定追加」を行うためです。

```bash:I
🛑 The following resources failed to deploy:
Resource Name: LambdaFunction (AWS::Lambda::Function)
Event Type: update
Reason: Resource handler returned message: "The provided execution role does not have permissions to call CreateNetworkInterface on EC2 (Service: Lambda, Status Code: 400, Request ID: ..., HandlerErrorCode: InvalidRequest)
```


# 【補足2】どうしてもリソースの構築順を変更したくない場合

そのような場合があるかはおいておきます:sweat_smile:
以下のように2段階に分けてテンプレートの変更をクラウドに反映すると「2-3.リソースの構築順変更」を実施しなくともエラーにはなりません。


1. 「2-1. VPC接続設定に必要なIAMポリシー追加」実施
1. 「3. ビルド＆クラウドへの反映」実施
1. 「2-2. VPC接続設定追加」実施
1. 「3. ビルド＆クラウドへの反映」再実施
   → **必要な権限が 2.でクラウド上で設定されているため、VPC接続設定が成功します**

ですが、手順を複雑にすると思わぬところではまる可能性がありますので、「2-3.リソースの構築順変更」は実施することをお勧めします。


# 最後に

補足で書いた内容にたどり着くまでに1日費やしました。参考になれば幸いです。

# 参考

本記事の、特にテンプレートファイルの編集内容については以下の記事を参考にさせて頂きました。ありがとうございます。

https://github.com/aws-amplify/amplify-cli/issues/32

https://qiita.com/knakazono10111/items/ec8d1760e5836d3baf71