[JAWS-UG CLI] Lambda:#20 Lambda関数の作成 (vpn-conn-monitor: Python版)

前提条件
========


Lambdaへの権限
--------------

Lambdaに対してフル権限があること。


AWS CLI
-------

以下のバージョンで動作確認済

- AWS CLI 1.11.8

```bash:コマンド:
aws --version
```

```text:結果(例):
      aws-cli/1.11.7 Python/2.7.11 Darwin/15.6.0 botocore/1.4.64
```

バージョンが古い場合は最新版に更新しましょう。

```bash:コマンド:
sudo -H pip install -U awscli
```


IAM Role
--------

'lambdaVpnConnMonitorExecution'ロールが存在すること。

```bash:変数の設定:
IAM_ROLE_NAME='lambdaVpnConnMonitorExecution'
```

```bash:コマンド:
aws iam get-role \
         --role-name ${IAM_ROLE_NAME}
```

```json:結果(例):
      {
          "Role": {
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Action": "sts:AssumeRole",
                        "Principal": {
                            "Service": "lambda.amazonaws.com"
                        },
                        "Effect": "Allow",
                        "Sid": ""
                    }
                ]
            },
            "RoleId": "AROAXXXXXXXXXXXXXXXXX",
            "CreateDate": "2016-10-22T01:23:45Z",
            "RoleName": "lambdaVpnConnMonitorExecution",
            "Path": "/",
            "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaVpnConnMonitorExecution"
          }
      }
```


0. 準備
=======


0.1. リージョンの決定
---------------------

```bash:変数の設定
export AWS_DEFAULT_REGION='ap-northeast-1'
```


0.2. 変数の確認
---------------

プロファイルが想定のものになっていることを確認します。

```bash:変数の確認:
aws configure list
```

```text:結果(例):
            Name                    Value             Type    Location
            ----                    -----             ----    --------
         profile       lambdaFull-prjz-mbp13        env    AWS_DEFAULT_PROFILE
      access_key     ****************XXXX shared-credentials-file
      secret_key     ****************XXXX shared-credentials-file
          region        ap-northeast-1        env    AWS_DEFAULT_REGION
```


0.3. IAM RoleのARN取得
----------------------

```bash:変数の設定:
IAM_ROLE_NAME='lambdaVpnConnMonitorExecution'
```

```bash:コマンド:
IAM_ROLE_ARN=$( \
        aws iam get-role \
          --role-name ${IAM_ROLE_NAME} \
          --query 'Role.Arn' \
          --output text \
) \
        && echo ${IAM_ROLE_ARN}
```

```text:結果(例):
      arn:aws:iam::XXXXXXXXXXXX:role/lambdaVpnConnMonitorExecution
```


1. 事前作業
===========


1.1. Lambda関数名の決定
-----------------------

```bash:変数の設定:
LAMBDA_FUNC_NAME="vpn-conn-monitor-$( date '+%Y%m%d' )" \
   &&     echo ${LAMBDA_FUNC_NAME}
```

同名のLambda関数の不存在確認

```bash:コマンド:
aws lambda get-function \
        --function-name ${LAMBDA_FUNC_NAME}
```

```text:結果(例):
      A client error (ResourceNotFoundException) occurred when calling the GetFunction operation: Function not found: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:vpn-conn-monitor-20161024
```


1.2. Lambda関数
---------------

```bash:変数の設定:
FILE_LAMBDA_FUNC="${LAMBDA_FUNC_NAME}.py"
PY_FUNC_NAME='lambda_handler'
```

```bash:変数の確認:
cat << ETX

          FILE_LAMBDA_FUNC: ${FILE_LAMBDA_FUNC}
          PY_FUNC_NAME:     ${PY_FUNC_NAME}

ETX
```

```bash:コマンド:
cat << EOF > ${FILE_LAMBDA_FUNC}
from __future__ import print_function

import boto3

print('Loading function')
cw = boto3.client('cloudwatch')


def put_cloudwatch_metric(metric_name, value, vgw, cgw, region):
    cw.put_metric_data(
        Namespace='VPNStatus',
        MetricData=[{
            'MetricName': metric_name,
            'Value': value,
            'Unit': 'Count',
            'Dimensions': [
                {
                    'Name': 'VGW',
                    'Value': vgw
                },
                {
                    'Name': 'CGW',
                    'Value': cgw
                },
                {
                    'Name': 'Region',
                    'Value': region
                }
            ]
        }]
    )


def lambda_handler(event, context):
    ec2 = boto3.client('ec2')
    aws_regions = ec2.describe_regions()['Regions']
    num_connections = 0
    for region in aws_regions:
        try:
            ec2 = boto3.client('ec2', region_name=region['RegionName'])
            vpns = ec2.describe_vpn_connections()['VpnConnections']
            for vpn in vpns:
                if vpn['State'] == 'available':
                    num_connections += 1
                    active_tunnels = 0
                    if vpn['VgwTelemetry'][0]['Status'] == 'UP':
                        active_tunnels += 1
                    if vpn['VgwTelemetry'][1]['Status'] == 'UP':
                        active_tunnels += 1
                    put_cloudwatch_metric(vpn['VpnConnectionId'],
                                        active_tunnels,
                                        vpn['VpnGatewayId'],
                                        vpn['CustomerGatewayId'],
                                        region['RegionName'])
        except Exception as e:
            print("Exception: " + str(e))
            continue
    return num_connections
EOF

cat ${FILE_LAMBDA_FUNC}
```

```bash:コマンド:
zip ${LAMBDA_FUNC_NAME}.zip ${FILE_LAMBDA_FUNC}
```

```text:結果(例):
      adding: vpn-conn-monitor-20161024.py (deflated 43%)
```


2. Lambda関数の作成
===================


2.1. Lambda関数の作成
---------------------

```bash:変数の設定:
LAMBDA_FUNC_DESC='Monitors VPN connection status of an account in all regions.'
LAMBDA_RUNTIME='python2.7'
LAMBDA_HANDLER="${LAMBDA_FUNC_NAME}.${PY_FUNC_NAME}"
FILE_LAMBDA_ZIP="${LAMBDA_FUNC_NAME}.zip"
```

```bash:変数の確認:
cat << ETX

        LAMBDA_FUNC_NAME:  ${LAMBDA_FUNC_NAME}
        LAMBDA_FUNC_DESC: "${LAMBDA_FUNC_DESC}"
        LAMBDA_RUNTIME:    ${LAMBDA_RUNTIME}
        FILE_LAMBDA_ZIP    ${FILE_LAMBDA_ZIP}
        IAM_ROLE_ARN:      ${IAM_ROLE_ARN}
        LAMBDA_HANDLER:    ${LAMBDA_HANDLER}

ETX
```

```bash:コマンド:
aws lambda create-function \
        --function-name ${LAMBDA_FUNC_NAME} \
        --description "${LAMBDA_FUNC_DESC}" \
        --zip-file fileb://${FILE_LAMBDA_ZIP} \
        --runtime ${LAMBDA_RUNTIME} \
        --role ${IAM_ROLE_ARN} \
        --handler ${LAMBDA_HANDLER}
```

```json:結果(例):
      {
        "CodeSha256": "c++vSFRfioI+KDLOGt3N97oJ+xroodc2SDy5wXWYlF8=",
        "FunctionName": "vpn-conn-monitor-20161024",
        "CodeSize": 781,
        "MemorySize": 128,
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:vpn-conn-monitor-20161024",
        "Version": "$LATEST",
        "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaVpnConnMonitorExecution",
        "Timeout": 3,
        "LastModified": "2016-10-22T01:23:45.678+0000",
        "Handler": "vpn-conn-monitor-20161024.lambda_handler",
        "Runtime": "python2.7",
        "Description": "Monitors VPN connection status of an account in all regions."
      }
```

```bash:コマンド:
aws lambda get-function \
        --function-name ${LAMBDA_FUNC_NAME}
```

```json:結果(例):
      {
        "Code": {
          "RepositoryType": "S3",
          "Location": "https://awslambda-ap-ne-1-tasks.s3-ap-northeast-1.amazonaws.com/snapshots/XXXXXXXXXXXX/HelloWorld-2979ba79-b08f-495d-9ee6-46397c95ba13?x-amz-security-token=AQoDYXdzEDoa8AMR6t8h66eOXhN3%2Fx7XpuRxvf7pVn7IuWV4cEmwx0CtZT6yxCJ1%2BWmigYXqGoyQHuBYOWnxbhmwEcTg839qMuhSu1fk0fXpXf0oJOLkhKMudNqhdElyFQpzyT6Q8GDfhAsfbX9wvwCDTty4imxz7MczF%2FQl6tgvTYdip08ap5fAyrknZGV1%2B1Ggnp5w6JOjydYxuUsWwhoxoEWzi7SoVTmpRQQA91c4VW9lNotOAHACFxo6klzDPM8mxR9RJl66WxFugL0wQJyLUpmtjS9XoArD86sEWWiIccMpV2BQipTPQlzL%2F1Hoy%2BDF6QUxyPUihlDjPBoJTISTP8W1wxmzW%2BLbilAfFQRPY7CFjzR0k%2FA%2FIX5x9iyz52Pu1Q0ASTw1l%2Fq%2Fo3pRbvzWR79QS%2BpxXrwbYzoQHKiK62DSTsQo5tqKPsiDCYzrPxbq8lm7pNBPG%2FsxjePRWBVJeRl08WxEjSjoRRwBOPX5mz1BCUoUBPGG5tEENp87A%2FCdDgibFWM5DdYhwtaYPY7FTmi8DvqjQHL9jOmP8YuVteBTBcv8nFW6UbErPjwwn79FKG1u5M9HoTWUqUMBByz6D4tTRSEw6iJU7XdCujFnhnHe5V8imZ1KGI7fDWpciJhrhml0wnKPCK%2Fe9lK1P2kO7ldSWc7zn5hcIOD2tbEF&AWSAccessKeyId=ASIAJFVALOKV5SJVYPPA&Expires=1445825978&Signature=bvwu1Ny34LgTmZeOO3q4sn7x3Fg%3D"
        },
        "Configuration": {
          "Version": "$LATEST",
          "CodeSha256": "c++vSFRfioI+KDLOGt3N97oJ+xroodc2SDy5wXWYlF8=",
          "FunctionName": "vpn-conn-monitor-20161024",
          "MemorySize": 128,
          "CodeSize": 350,
          "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:vpn-conn-monitor-20161024",
          "Handler": "vpn-conn-monitor-20161024.lambda_handler",
          "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaVpnConnMonitorExecution",
          "Timeout": 3,
          "LastModified": "2016-10-22T01:23:45.678+0000",
          "Runtime": "python2.7",
          "Description": "Monitors VPN connection status of an account in all regions."
        }
      }
```

```bash:コマンド:
aws lambda get-function-configuration \
        --function-name ${LAMBDA_FUNC_NAME}
```

```json:結果(例):
      {
        "CodeSha256": "c++vSFRfioI+KDLOGt3N97oJ+xroodc2SDy5wXWYlF8=",
        "FunctionName": "vpn-conn-monitor-20161024",
        "CodeSize": 781,
        "MemorySize": 128,
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:vpn-conn-monitor-20161024",
        "Version": "$LATEST",
        "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaVpnConnMonitorExecution",
        "Timeout": 3,
        "LastModified": "2016-10-22T01:23:45.678+0000",
        "Handler": "vpn-conn-monitor-20161024.handler",
        "Runtime": "python2.7",
        "Description": "Monitors VPN connection status of an account in all regions."
      }
```


2.2. Lambda関数の更新
---------------------

デフォルトの3秒ではタイムアウトする可能性が高いので、ここでは30秒に変更します。

```bash:変数の設定:
LAMBDA_TIMEOUT='30'
```

```bash:変数の確認:
cat << ETX

        LAMBDA_FUNC_NAME: ${LAMBDA_FUNC_NAME}
        LAMBDA_TIMEOUT:   ${LAMBDA_TIMEOUT}

ETX
```

```bash:コマンド:
aws lambda update-function-configuration \
        --function-name ${LAMBDA_FUNC_NAME} \
        --timeout "${LAMBDA_TIMEOUT}"
```

```json:結果(例):
      {
        "CodeSha256": "c++vSFRfioI+KDLOGt3N97oJ+xroodc2SDy5wXWYlF8=",
        "FunctionName": "vpn-conn-monitor-20161024",
        "VpcConfig": {
            "SubnetIds": [],
            "SecurityGroupIds": []
        },
        "CodeSize": 781,
        "MemorySize": 128,
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:vpn-conn-monitor-20161024",
        "Version": "$LATEST",
        "Role": "arn:aws:iam::XXXXXXXXXXXX:role/lambdaVpnConnMonitorExecution",
        "Timeout": 30,
        "LastModified": "2016-10-22T01:23:45.678+0000",
        "Handler": "vpn-conn-monitor-20161024.handler",
        "Runtime": "python2.7",
        "Description": "Monitors VPN connection status of an account in all regions."
      }
```


3. Lambda関数の動作確認
=======================


3.1. サンプルデータの作成
-------------------------

```bash:変数の設定:
FILE_INPUT="${LAMBDA_FUNC_NAME}-data.json" \
          && echo ${FILE_INPUT}
```

```bash:サンプルデータ:

cat << EOF > ${FILE_INPUT}
{
        "account": "123456789012",
        "region": "${AWS_DEFAULT_REGION}",
        "detail": {},
        "detail-type": "Scheduled Event",
        "source": "aws.events",
        "time": "1970-01-01T00:00:00Z",
        "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
        "resources": [
          "arn:aws:events:${AWS_DEFAULT_REGION}:123456789012:rule/my-schedule"
        ]
}
EOF

cat ${FILE_INPUT}
```

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

```bash:コマンド:
jsonlint -q ${FILE_INPUT}
```

エラーが出力されなければOKです。


3.2. lambda関数の手動実行
-------------------------

```bash:変数の設定:
FILE_OUTPUT_LAMBDA="${LAMBDA_FUNC_NAME}-out.txt"
FILE_LOG_LAMBDA="${LAMBDA_FUNC_NAME}-$(date +%Y%m%d%H%M%S).log"
```

```bash:変数の確認:
cat << ETX

        LAMBDA_FUNC_NAME:   ${LAMBDA_FUNC_NAME}
        FILE_INPUT:         ${FILE_INPUT}
        FILE_OUTPUT_LAMBDA: ${FILE_OUTPUT_LAMBDA}
        FILE_LOG_LAMBDA:    ${FILE_LOG_LAMBDA}

ETX
```

```bash:コマンド:
aws lambda invoke \
        --function-name ${LAMBDA_FUNC_NAME} \
        --log-type Tail \
        --payload file://${FILE_INPUT} \
        ${FILE_OUTPUT_LAMBDA} \
        > ${FILE_LOG_LAMBDA}
```

```bash:コマンド:
cat ${FILE_LOG_LAMBDA} \
        | jp.py 'StatusCode'
```

```text:結果(例):
      200
```


3.3. lambda関数の実行結果の確認
-------------------------------

```bash:コマンド:
cat ${FILE_OUTPUT_LAMBDA}
```

```text:結果(例):
      1
```


3.4. lambda関数のログの確認
---------------------------

```bash:コマンド:
cat ${FILE_LOG_LAMBDA} \
        | jp.py 'LogResult' \
        | sed 's/"//' \
        | base64 --decode
```

```text:結果(例):
      START RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Version: $LATEST
      END RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
      REPORT RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx      Duration: 12870.41 ms   Billed Duration: 12900 ms       Memory Size: 128 MB     Max Memory Used: 33 MB
```


完了
====