Serverless FrameworkでVPC作るところからHello Worldまで全部自動化してみた！

# はじめに

この記事は [「ハンズラボ Advent Calendar 2018」](https://qiita.com/advent-calendar/2018/handslab) 22日目の記事です。

業務でAWSを利用することになったため、いろいろ自動化したいと思ってServerless Frameworkについて調べた内容をまとめました。
具体的には、Serverless Frameworkを使って以下の作業を行なっていきます。

0. 事前準備（stage, profile, regionなどの設定）
0. VPCの作成
0. InternetGatewayの作成
0. EIPの作成
0. NatGatewayの作成
0. Private Subnetの作成
0. Public Subnetの作成
0. APIGateway（Public Subnet）の作成
0. Lambda(Private Subnet)の作成
0. Serverlessでdeployしてみる
0. Labmdaを実際に実行してみる
0. 最後に

なお、本記事はAWSを初めて2ヶ月弱。
Serverless Frameworkを使い出して1週間の状態でいろいろ調べた結果をまとめた内容になっています。
このため、不備などあればぜひコメントで教えていただけるとうれしいです！

# 1. 事前準備（stage, profile, regionなどの設定）
今回は東京リージョンで、python3.7を指定して環境構築を行なっていきます。
profileは適宜ご自身の環境のものを利用してもらえればと思います。
また、`serverless-python-requirements`pluginも利用しますので、インストールをお願いします。

```serverless.yml
provider:
  name: aws
  stage: ${opt:stage, 'stg'}
  profile: serverless-sample-${self:provider.stage}
  region: ap-northeast-1
  runtime: python3.7
  timeout: 30
  memorySize: 128
  versionFunctions: false
  environment:
    TZ: Asia/Tokyo

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true
  prefix: ${self:service}-${self:provider.stage}
```

# 1. VPCの作成
Resources配下に以下を追記します。
今回はサンプル環境のため、DeletionPolicyに`Delete`を指定しています。

```serverless.yml
# リソースの構築
resources:
  Resources:

    SampleVPC:
      Type: AWS::EC2::VPC
      DeletionPolicy: Delete
      Properties:
        CidrBlock: 10.1.0.0/16
        Tags:
          - Key: Name
            Value: SampleVPC
```
# 2. InternetGatewayの作成
Resources配下に以下を追記します。

```serverless.yml
    SampleInternetGateway:
      Type: AWS::EC2::InternetGateway
      Properties:
        Tags:
          - Key: Name
            Value: SampleInternetGateway

    VPCGatewayAttachment:
      Type: AWS::EC2::VPCGatewayAttachment
      Properties:
        VpcId: !Ref SampleVPC
        InternetGatewayId: !Ref SampleInternetGateway
```
# 3. EIPの作成
Resources配下に以下を追記します。
なお、他のResourceと同じようにPropertiesでTagsを試したところ、未サポートでしたのでコメントアウトしてあります。
EIPはVPC内に作成できる上限値があったりするので注意が必要です。

```serverless.yml
    SampleElasticIp:
      Type: AWS::EC2::EIP
      Properties:
        Domain: vpc
        # Tags:
        #   - Key: Name
        #     Value: SampleElasticIp
```

# 4. NatGatewayの作成
Resources配下に以下を追記します。
なお、`AllocationId`の指定でしばらくハマりました。
当初は、`!Ref SampleElasticIp`としていたのですが、この指定だとEIPのIPアドレスが取得されてしまい、NatGatewayの作成で失敗してしまうため、注意が必要です。

```serverless.yml
    SampleNatGateway:
      DependsOn: SampleElasticIp
      Type: AWS::EC2::NatGateway
      Properties:
        AllocationId: !GetAtt SampleElasticIp.AllocationId
        SubnetId: !Ref SamplePublicSubnet
        Tags:
          - Key: Name
            Value: SampleNatGateway
```

# 5 PrivateSubnetの作成
Resources配下に以下を追記します。

```serverless.yml
    SamplePrivateSubnet:
      DependsOn: SampleVPC
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref SampleVPC
        # AvailabilityZone: ${self:provider.region}a
        CidrBlock: 10.1.1.0/24
        Tags:
          - Key: Name
            Value: SamplePrivateSubnet

    SamplePrivateRouteTable:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId: !Ref SampleVPC
        Tags:
          - Key: Name
            Value: SamplePrivateRouteTable

    SamplePrivateRoute:
      Type: AWS::EC2::Route
      Properties:
        RouteTableId: !Ref SamplePrivateRouteTable
        DestinationCidrBlock: 0.0.0.0/0
        NatGatewayId: !Ref SampleNatGateway

    SubnetRouteTableAssociationPrivateSubnet:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        SubnetId: !Ref SamplePrivateSubnet
        RouteTableId: !Ref SamplePrivateRouteTable
```
# 6. PublicSubnetの作成
Resources配下に以下を追記します。

```serverless.yml
    SamplePublicSubnet:
      DependsOn: SampleVPC
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref SampleVPC
        # AvailabilityZone: ${self:provider.region}a
        CidrBlock: 10.1.11.0/24
        Tags:
          - Key: Name
            Value: SamplePublicSubnet

    SamplePublicRouteTable:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId: !Ref SampleVPC
        Tags:
          - Key: Name
            Value: SamplePublicRouteTable

    SamplePublicRoute:
      Type: AWS::EC2::Route
      Properties:
        RouteTableId: !Ref SamplePublicRouteTable
        DestinationCidrBlock: 0.0.0.0/0
        GatewayId: !Ref SampleInternetGateway

    SubnetRouteTableAssociationPublicSubnet:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        SubnetId: !Ref SamplePublicSubnet
        RouteTableId: !Ref SamplePublicRouteTable
```
# 7. APIGatewayの作成
Resources配下に以下を追記します。
今回はサンプル環境のため、DeletionPolicyに`Delete`を指定しています。

```serverless.yml
    SampleApiGatewayRestApi:
      Type: AWS::ApiGateway::RestApi
      DeletionPolicy: Delete
      Properties:
        EndpointConfiguration:
          Types:
            - REGIONAL
        Name: ${self:service}-${self:provider.stage}
```
# 8. SecurityGroupの作成
Resources配下に以下を追記します。

```serverless.yml
    SampleSecurityGroup:
      DependsOn: SampleVPC
      Type: AWS::EC2::SecurityGroup
      Properties:
        GroupDescription: SecurityGroup for Sample Functions
        VpcId: !Ref SampleVPC
```
# 9. Lambdaの作成
Resources配下に以下を追記します。

```serverless.yml
functions:
  api:
    # warmup: true
    name: ${self:custom.prefix}-api
    handler: lambda_function.lambda_handler
    module: functions/api
    vpc:
      securityGroupIds:
        - !Ref SampleSecurityGroup
      subnetIds:
        - !Ref SamplePrivateSubnet
    events:
      - http:
          integration: lambda-proxy
          path: helloworld
          method: POST
          cors: true
    environment:
      test: api-test
  batch:
    name: ${self:custom.prefix}-batch
    handler: lambda_function.lambda_handler
    module: functions/batch
    vpc:
      securityGroupIds:
        - !Ref SampleSecurityGroup
      subnetIds:
        - !Ref SamplePrivateSubnet
    environment:
      test: batch-test
```

`api`ディレクトリ配下にapi用のPythonスクリプト`lambda_function.py`および、空のファイル`requirements.txt`を用意してください。
※　`requirements.txt`は`serverless-python-requirements`を利用している関係で必要となります。

```api/lambda_function.py
import os
import logging

logger = logging.getLogger() 
logLevel = logging.WARNING

def lambda_handler(event, context):
    
    # Immediate response for WarmUP plugin
    if event.get('source', None) == 'serverless-plugin-warmup':
        logger.info('WarmUP - Lambda is warm!')
        return 'Lambda is warm!'
    
    return {
        "statusCode": 200,
        "body": "{\"message\": \"hello lambda api!\", \"input\": {\"test\": \"" + os.environ["test"] + "\"}}"
    }
```

`batch`ディレクトリ配下にbatch用のPythonスクリプト`lambda_function.py`および、空のファイル`requirements.txt`を用意してください。
※　`requirements.txt`は`serverless-python-requirements`を利用している関係で必要となります。

```batch/lambda_function.py
import os
import logging

logger = logging.getLogger() 
logLevel = logging.WARNING

def lambda_handler(event, context):
    return {
        "message": "hello lambda batch!", "input": {"test": os.environ["test"]}
    }
```

# 10. Serverlessでdeployしてみる
`profile`の設定などを確認して、以下のコマンドを実行します。

```bash
$ serverless deploy
```

1から環境を構築するため、数分かかります。
のんびり気長に待ってください。
以下のような結果が表示されれば環境構築成功です。

```bash
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (1.64 KB)...
Serverless: Uploading service .zip file to S3 (1.64 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................................................................................
Serverless: Stack update finished...
Service Information
service: serverless-sample
stage: stg
region: ap-northeast-1
stack: serverless-sample-stg
api keys:
  None
endpoints:
  POST - https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/stg/helloworld
functions:
  api: serverless-sample-stg-api
  batch: serverless-sample-stg-batch
layers:
  None
```

# 11. Labmdaを実際に実行してみる
## 11.1. functionの`api`を実行する
functionの`api`はapigatewayと関連付けしているため、作成されたendpointをcurlで実行してみます。

```bash
$ curl -X POST -H "Content-Type: application/json" https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/stg/helloworld
```

以下のようなレスポンスが返却されてくれば成功です。

```bash
{"message": "hello lambda api!", "input": {"test": "api-test"}}
```

## 11.2. functionの`batch`を実行する
functionの`batch`はapigatewayと関連付けしていないため、serverlessコマンドで実行します。

```bash
$ serverless invoke -f batch
```

以下のようなレスポンスが返却されてくれば成功です。

```bash
{
    "message": "hello lambda batch!",
    "input": {
        "test": "batch-test"
    }
}
```

# 12. 最後に
UnixやLinuxをオンプレ環境で構築したことなどはあったのですが、AWSでServerless Frameworkなどを使って環境構築などをすれば、再現性もあり非常に便利だなと感じました。
今後もAWSについてもっと勉強していきたいと思います。

[「ハンズラボ Advent Calendar 2018」](https://qiita.com/advent-calendar/2018/handslab) の23日目は、@daisudaisuke さんの[「Amazon Managed Blockchainでフリマアプリ作りたい Part1」](https://qiita.com/daisudaisuke/items/2579e061178d88af9c8c)です！