2018年2月17日土曜日

AWS SAM Local と DynamoDB Local で API Gateway + Lambda を試す


下記のAWSのブログで紹介されていたサーバレスアプリケーションを試してみました。
AWS SAM Local(ベータ版) – サーバーレスアプリケーションをローカルに構築してテストする
AWS SAM Local と DynamoDB Local は、VirtualBoxの同じサーバ(CentOS7)で実行します。

1. ローカルにLambda の用意


AWS SAM Local のインストールは、こちらを参照。
template.yaml は以下のとおり。
AWSブログのものを、少し変えています。
Events に GetVotes を追加しました。
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  VotesTable:
    Type: "AWS::Serverless::SimpleTable"
  VoteSpacesTabs:
    Type: "AWS::Serverless::Function"
    Properties:
      Runtime: python2.7
      Handler: vote_function.vote_handler
      Policies: AmazonDynamoDBFullAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref VotesTable
      Events:
        GetVotes:
          Properties:
            Method: get
            Path: /
          Type: Api
        Vote:
          Type: Api
          Properties:
            Path: /
            Method: post

lambdaのソースコードは、以下のとおり。
AWS SAM Local で lambda を実行した場合は、DynamoDB の接続先(endpoint) をローカルのDynamoDB しています。
192.168.56.102 は、DynamoDBが起動するローカルのIPアドレスです。
ここを、localhost にすると、AWS SAM Local で lambda が実行される Dockerコンテナ を意味し、ローカルのDynamoDB に接続できないので注意。
import os
import json
import boto3

def vote_handler(event, context):

    if os.getenv("AWS_SAM_LOCAL"):
        votes_table = boto3.resource(
            'dynamodb',
            endpoint_url="http://192.168.56.102:8000/"
        ).Table("spaces-tabs-votes")
    else:
        votes_table = boto3.resource('dynamodb').Table(os.getenv('TABLE_NAME'))

    if event['httpMethod'] == 'GET':
        resp = votes_table.scan()
        return {'body': json.dumps({item['id']: int(item['votes']) for item in resp['Items']})}
    elif event['httpMethod'] == 'POST':
        try:
            body = json.loads(event['body'])
        except:
            return {'statusCode': 400, 'body': 'malformed json input'}
        if 'vote' not in body:
            return {'statusCode': 400, 'body': 'missing vote in request body'}
        if body['vote'] not in ['spaces', 'tabs']:
            return {'statusCode': 400, 'body': 'vote value must be "spaces" or "tabs"'}

        resp = votes_table.update_item(
            Key={'id': body['vote']},
            UpdateExpression='ADD votes :incr',
            ExpressionAttributeValues={':incr': 1},
            ReturnValues='ALL_NEW'
        )
        return {'body': "{} now has {} votes".format(body['vote'], resp['Attributes']['votes'])}

用意したファイルは以下のとおり。
[root@centos702 lambda5]# ls
template.yaml  vote_function.py

2. ローカルに DynamoDB の用意


dynamodb ディレクトリを作成して、そこにDynamoDBをインストールします。
[root@centos702 ~]# mkdir dynamodb
[root@centos702 ~]# cd dynamodb/
[root@centos702 dynamodb]# curl -OL https://s3-ap-northeast-1.amazonaws.com/dynamodb-local-tokyo/dynamodb_local_latest.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 16.2M  100 16.2M    0     0  3551k      0  0:00:04  0:00:04 --:--:-- 4084k
[root@centos702 dynamodb]# tar xfz dynamodb_local_latest.tar.gz
[root@centos702 dynamodb]# ls
DynamoDBLocal.jar  DynamoDBLocal_lib  LICENSE.txt  README.txt  dynamodb_local_latest.tar.gz  third_party_licenses

以下のようにコマンドを実行して、DynamoDBを起動します。
[root@centos702 dynamodb]# java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
Initializing DynamoDB Local with the following configuration:
Port:   8000
InMemory:       false
DbPath: null
SharedDb:       true
shouldDelayTransientStatuses:   false
CorsParams:     *



別のターミナルを開きます。
以下のようにコマンドを実行して、DynamoDBにテーブルを作成します。
[root@centos702 lambda5]# aws dynamodb create-table --table-name spaces-tabs-votes --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 --output table --endpoint-url http://localhost:8000
------------------------------------------------------------------------------------------
|                                       CreateTable                                      |
+----------------------------------------------------------------------------------------+
||                                   TableDescription                                   ||
|+------------------+-------------------------------------------------------------------+|
||  CreationDateTime|  1518857833.28                                                    ||
||  ItemCount       |  0                                                                ||
||  TableArn        |  arn:aws:dynamodb:ddblocal:000000000000:table/spaces-tabs-votes   ||
||  TableName       |  spaces-tabs-votes                                                ||
||  TableSizeBytes  |  0                                                                ||
||  TableStatus     |  ACTIVE                                                           ||
|+------------------+-------------------------------------------------------------------+|
|||                                AttributeDefinitions                                |||
||+--------------------------------------------------------------+---------------------+||
|||  AttributeName                                               |  id                 |||
|||  AttributeType                                               |  S                  |||
||+--------------------------------------------------------------+---------------------+||
|||                                      KeySchema                                     |||
||+--------------------------------------------------------+---------------------------+||
|||  AttributeName                                         |  id                       |||
|||  KeyType                                               |  HASH                     |||
||+--------------------------------------------------------+---------------------------+||
|||                                ProvisionedThroughput                               |||
||+------------------------------------------------------------------+-----------------+||
|||  LastDecreaseDateTime                                            |  0.0            |||
|||  LastIncreaseDateTime                                            |  0.0            |||
|||  NumberOfDecreasesToday                                          |  0              |||
|||  ReadCapacityUnits                                               |  1              |||
|||  WriteCapacityUnits                                              |  1              |||
||+------------------------------------------------------------------+-----------------+||
[root@centos702 lambda5]#

3. ローカルに API Gateway の用意


以下のようにコマンドを実行して AMS SAM Local の API Gateway を起動します。
API Gateway にアクセスすると、ここに、ログが表示されます。
[root@centos702 lambda5]# sam local start-api
A newer version of the AWS SAM CLI is available!
Your version:   0.2.4
Latest version: 0.2.6
See https://github.com/awslabs/aws-sam-local for upgrade instructions

2018/02/17 18:15:27 Connected to Docker 1.35
2018/02/17 18:15:27 Fetching lambci/lambda:python2.7 image for python2.7 runtime...
python2.7: Pulling from lambci/lambda
Digest: sha256:87a3b9bb1ba6ae666b0087107852b67415cae0660669ae5633a0ab828aea2c69
Status: Image is up to date for lambci/lambda:python2.7

Mounting vote_function.vote_handler (python2.7) at http://127.0.0.1:3000/ [GET]
Mounting vote_function.vote_handler (python2.7) at http://127.0.0.1:3000/ [POST]

You can now browse to the above endpoints to invoke your functions.
You do not need to restart/reload SAM CLI while working on your functions,
changes will be reflected instantly/automatically. You only need to restart
SAM CLI if you update your AWS SAM template.



4. 動作確認


API Gateway に POST メソッドでアクセスし、"tabs" に投票します。
lambda が実行されて、DynamoDBが更新されます。
[root@centos702 lambda5]# curl -XPOST -d '{"vote": "tabs"}' http://127.0.0.1:3000/
tabs now has 3 votes

API Gateway には、以下のようにログが表示されます。
2018/02/17 18:16:22 Invoking vote_function.vote_handler (python2.7)
2018/02/17 18:16:22 Mounting /root/lambda5 as /var/task:ro inside runtime container
START RequestId: 6e24834b-e125-413a-ac8b-bbd219b7b77d Version: $LATEST
END RequestId: 6e24834b-e125-413a-ac8b-bbd219b7b77d
REPORT RequestId: 6e24834b-e125-413a-ac8b-bbd219b7b77d Duration: 1068 ms Billed Duration: 0 ms Memory Size: 0 MB Max Memory Used: 23 MB

API Gateway に GET メソッドでアクセスし、"tabs" の投票数を参照します。
[root@centos702 lambda5]# curl -XGET -d '{"vote": "tabs"}' http://127.0.0.1:3000/
{"tabs": 3}




2018年2月12日月曜日

AutoScaling のライフサイクルフックで Lambda関数 を実行する


AutoScalingグループにライフサイクルフックを設定し、スケールアウト時に、Lambda関数を実行してみます。
このLambda関数では、ライフサイクルフックで保留状態のインスタンスにSSH接続して、コマンドを実行するようにします。
コマンド実行後は、保留を解除してライフサイクルを続行します。

ライフサイクルフックの詳細は下記URLを参照。
https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/lifecycle-hooks.html

1.lambda関数 の用意


AutoScalingでインスタンスが起動したときに実行する lambda関数を作成します。
ソースコードは以下のとおり。
paramiko をLambdaで使用する方法は、こちらの記事を参照。
import boto3
import paramiko

def scaleout_handler(event, context):

    # instance-id -> private ip address
    print "get ip"
    ec2 = boto3.resource('ec2')
    instance = ec2.Instance(event['detail']['EC2InstanceId'])
    ip = instance.private_ip_address
    print "ip=" + ip

    # Download private key file from S3 bucket
    print "get key from s3"
    s3 = boto3.client('s3')
    s3.download_file('blue21.dev.local','ssh_key.pem', '/tmp/ssh_key.pem')

    # ssh connect
    print "connecting ssh"
    k = paramiko.RSAKey.from_private_key_file("/tmp/ssh_key.pem")
    c = paramiko.SSHClient()
    c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    c.connect( hostname = ip, username = "centos", pkey = k )
    print "connected"

    # execute command
    commands = [
        "ps -ef"
    ]
    for command in commands:
        print "Executing {}".format(command)
        stdin , stdout, stderr = c.exec_command(command)
        print stdout.read()

    # lifecycle continue
    print "lifecycle continue"
    as_client = boto3.client('autoscaling')
    res = as_client.complete_lifecycle_action(
        LifecycleHookName=event['detail']['LifecycleHookName'],
        AutoScalingGroupName=event['detail']['AutoScalingGroupName'],
        LifecycleActionToken=event['detail']['LifecycleActionToken'],
        LifecycleActionResult='CONTINUE'
    )

    return {
        'message' : "scaleout function finished"
    }
この Lambda関数では以下の処理を実行します。
(1) イベント情報のインスタンスIDからプライベートIPを取得
(2) S3バケットからSSH秘密鍵(ssh_key.pem)をダウンロード
(3) プライベートIPのサーバにsshログイン
(4) ps -ef コマンドを実行
(5) lifecycle-hook で待機状態になったインスタンス状態を続行する。

このLambda関数が受信する LifecycleHook のイベント情報は以下のとおり。
{
  "account": "xxxxx",
  "region": "us-east-1",
  "detail": {
    "EC2InstanceId": "i-03e42db2057f658b0",
    "AutoScalingGroupName": "centos7_scale_test",
    "LifecycleActionToken": "d9436277-fcbc-4a41-b197-fe4c044aeb7f",
    "LifecycleHookName": "scaleout_fook",
    "NotificationMetadata": "a,b,c",
    "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING"
  },
  "detail-type": "EC2 Instance-launch Lifecycle Action",
  "source": "aws.autoscaling",
  "version": "0",
  "time": "2018-02-11T03:47:27Z",
  "id": "5b94f10c-5b57-d22f-96d1-99e914f3ba0b",
  "resources": [
    "arn:aws:autoscaling:us-east-1:xxxxx:autoScalingGroup:1179ba6e-dce9-465e-ab4f-45c02ccab751:autoScalingGroupName/centos7_scale_test"
  ]
}

AWSへのデプロイに使用する template.yaml は以下のとおり。
関数名は、ScaleoutFunction にしました。
AWSにデプロイする方法は、こちらの記事を参照。
AWSTemplateFormatVersion: '2010-09-09'
Description: scaleout lifecycle hook
Transform: AWS::Serverless-2016-10-31
Resources:
  ScaleoutFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      FunctionName: ScaleoutFunction
      Handler: scaleout_function.scaleout_handler
      MemorySize: 128
      Role: arn:aws:iam::xxxxx:role/Lambda01_Role
      Runtime: python2.7
      Timeout: 10
      VpcConfig:
        SubnetIds:
          - subnet-2ea4d166
        SecurityGroupIds:
          - sg-bbf176df

このLambda関数は、プライベートサブネットで実行しますが、インスタンスIDからプライベートIPを取得するのにインターネットとの通信が必要なので、NATゲートウェイを使用します。
また、S3からファイルをダウンロードするので VPCエンドポイントも使用します。
Lambda関数が使用するサブネットのルーティングテーブルは下図のようになります。


2.起動設定の用意


起動設定を作成します。
名称は、"launch-centos7-02" にしました。
AMIは CentOS7 を使用します。
プライベートIPのみ使用して、パブリックIPは設定しません。


3.AutoScalingグループの用意


AutoScalingグループを作成します。
名称は、"as-group-01" にしました。
[ターゲットグループ]の[希望],[最小]は 0 にし、[最大]は 1 にしてます。
あとで、動作確認するときに、[希望],[最小]を 1 にします。
[サブネット]は、Lambdaと同じプライベートサブネットを使用します。


3.ライフサイクルフックの設定


上記で用意した AutoScalingグループにライフサイクルフックを定義します。
名称は、"hook-scaleout" にしました。
[ライフサイクルフックタイプ]は、”インスタンスの作成”を選択して、インスタンス作成時にフックされるようにします。



4.CloudWatchEvent の設定


ライフサイクルフック発動時に、Lambda関数を実行するイベントのルールを定義します。
[イベントソース]
 サービス名 ⇒ AutoScaling
 イベントタイプ ⇒ Instance Launch and Terminate
 特定のインスタンスイベント ⇒ EC2 Instance-launch Lifecycle Action
 特定のグループ名 ⇒ as-group-01
[ターゲット]
 Lambda関数
 ScaleoutFunction


[ルールの定義]
 名前 ⇒ rule-scaleout
 状態 ⇒ 有効


5.動作確認


AutoScaling でスケールアウトして、ライフサイクルフックの動きを見てみます。

S3バケットにSSH秘密鍵を置いておきます。
[root@centos702 lambda4]# aws s3 cp /root/ssh_key.pem s3://blue21.dev.local/
upload: ../ssh_key.pem to s3://blue21.dev.local/ssh_key.pem

ターゲットグループの[希望][最小]に1を設定して、スケールアウトします。


しばらくするとEC2インスタンスが起動します。


ライフサイクルフックでEC2インスタンスが待機状態になっています。
このとき Lambda関数が実行されます。


Lambda関数が実行され、ライフサイクルの続行を指示すると、"保留:待機" から"実行中"になります。


[アクティビティ履歴]を見ると、"成功" になっています。


Lambda関数のログは以下のとおり。
"ps -ef" コマンドの実行結果が記録されています。

[root@centos702 lambda4]# aws logs get-log-events --log-group-name /aws/lambda/ScaleoutFunction --log-stream-name '2018/02/12/[$LATEST]45c4bec38f264a46bad65ccfebe8f2ce' --output=text --query "events[*].message"
START RequestId: 637efafc-0f95-11e8-a1ec-fd427653aa45 Version: $LATEST
        get ip
        ip=10.0.20.20
        get key from s3
        connecting ssh
        connected
        Executing ps -ef
        UID        PID  PPID  C STIME TTY          TIME CMD
        root         1     0  9 01:38 ?        00:00:02 /usr/lib/systemd/systemd --switched-root --system --deserialize 20
        root         2     0  0 01:38 ?        00:00:00 [kthreadd]
        root         3     2  0 01:38 ?        00:00:00 [ksoftirqd/0]
        root         4     2  0 01:38 ?        00:00:00 [kworker/0:0]
        root         5     2  0 01:38 ?        00:00:00 [kworker/0:0H]
        root         6     2  0 01:38 ?        00:00:00 [kworker/u30:0]
        root         7     2  0 01:38 ?        00:00:00 [migration/0]
        root         8     2  0 01:38 ?        00:00:00 [rcu_bh]
        root         9     2  0 01:38 ?        00:00:00 [rcu_sched]
        root        10     2  0 01:38 ?        00:00:00 [watchdog/0]
        root        12     2  0 01:38 ?        00:00:00 [kdevtmpfs]
        root        13     2  0 01:38 ?        00:00:00 [netns]
        root        14     2  0 01:38 ?        00:00:00 [xenwatch]
        root        15     2  0 01:38 ?        00:00:00 [xenbus]
        root        16     2  0 01:38 ?        00:00:00 [kworker/0:1]
        root        17     2  0 01:38 ?        00:00:00 [khungtaskd]
        root        18     2  0 01:38 ?        00:00:00 [writeback]
        root        19     2  0 01:38 ?        00:00:00 [kintegrityd]
        root        20     2  0 01:38 ?        00:00:00 [bioset]
        root        21     2  0 01:38 ?        00:00:00 [kblockd]
        root        22     2  0 01:38 ?        00:00:00 [md]
        root        27     2  0 01:38 ?        00:00:00 [kswapd0]
        root        28     2  0 01:38 ?        00:00:00 [ksmd]
        root        29     2  0 01:38 ?        00:00:00 [crypto]
        root        37     2  0 01:38 ?        00:00:00 [kthrotld]
        root        38     2  0 01:38 ?        00:00:00 [kworker/u30:1]
        root        39     2  0 01:38 ?        00:00:00 [kmpath_rdacd]
        root        40     2  0 01:38 ?        00:00:00 [kpsmoused]
        root        41     2  0 01:38 ?        00:00:00 [kworker/0:2]
        root        42     2  0 01:38 ?        00:00:00 [ipv6_addrconf]
        root        61     2  0 01:38 ?        00:00:00 [deferwq]
        root       116     2  0 01:38 ?        00:00:00 [kauditd]
        root       124     2  0 01:38 ?        00:00:00 [kworker/0:3]
        root       179     2  0 01:38 ?        00:00:00 [rpciod]
        root       180     2  0 01:38 ?        00:00:00 [xprtiod]
        root       246     2  0 01:38 ?        00:00:00 [ata_sff]
        root       248     2  0 01:38 ?        00:00:00 [scsi_eh_0]
        root       250     2  0 01:38 ?        00:00:00 [scsi_tmf_0]
        root       251     2  0 01:38 ?        00:00:00 [scsi_eh_1]
        root       253     2  0 01:38 ?        00:00:00 [scsi_tmf_1]
        root       255     2  0 01:38 ?        00:00:00 [kworker/u30:2]
        root       256     2  0 01:38 ?        00:00:00 [kworker/u30:3]
        root       268     2  0 01:38 ?        00:00:00 [bioset]
        root       269     2  0 01:38 ?        00:00:00 [xfsalloc]
        root       270     2  0 01:38 ?        00:00:00 [xfs_mru_cache]
        root       271     2  0 01:38 ?        00:00:00 [xfs-buf/xvda1]
        root       272     2  0 01:38 ?        00:00:00 [xfs-data/xvda1]
        root       273     2  0 01:38 ?        00:00:00 [xfs-conv/xvda1]
        root       274     2  0 01:38 ?        00:00:00 [xfs-cil/xvda1]
        root       275     2  0 01:38 ?        00:00:00 [xfs-reclaim/xvd]
        root       276     2  0 01:38 ?        00:00:00 [xfs-log/xvda1]
        root       277     2  0 01:38 ?        00:00:00 [xfs-eofblocks/x]
        root       278     2  0 01:38 ?        00:00:00 [xfsaild/xvda1]
        root       355     1  0 01:38 ?        00:00:00 /usr/lib/systemd/systemd-journald
        root       389     1  0 01:38 ?        00:00:00 /usr/lib/systemd/systemd-udevd
        root       436     1  0 01:38 ?        00:00:00 /sbin/auditd
        root       494     2  0 01:38 ?        00:00:00 [ttm_swap]
        root       516     2  0 01:38 ?        00:00:00 [edac-poller]
        root       551     1  0 01:38 ?        00:00:00 /usr/sbin/rsyslogd -n
        polkitd    553     1  0 01:38 ?        00:00:00 /usr/lib/polkit-1/polkitd --no-debug
        root       555     1  0 01:38 ?        00:00:00 /usr/lib/systemd/systemd-logind
        dbus       559     1  0 01:38 ?        00:00:00 /bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
        chrony     563     1  0 01:38 ?        00:00:00 /usr/sbin/chronyd
        root       585     1  0 01:38 ?        00:00:00 /usr/sbin/gssproxy -D
        root       775     1  0 01:38 ?        00:00:00 /sbin/dhclient -1 -q -lf /var/lib/dhclient/dhclient--eth0.lease -pf /var/run/dhclient-eth0.pid eth0
        root       840     1  0 01:38 ?        00:00:00 /usr/bin/python -Es /usr/sbin/tuned -l -P
        root       854     2  0 01:38 ?        00:00:00 [kworker/0:1H]
        root       963     1  0 01:38 ?        00:00:00 /usr/libexec/postfix/master -w
        postfix    964   963  0 01:38 ?        00:00:00 pickup -l -t unix -u
        postfix    965   963  0 01:38 ?        00:00:00 qmgr -l -t unix -u
        root       996     1  0 01:38 ?        00:00:00 /usr/lib/systemd/systemd-hostnamed
        root      1017     1  2 01:38 ?        00:00:00 /usr/sbin/crond -n
        root      1018     1  0 01:38 tty1     00:00:00 /sbin/agetty --noclear tty1 linux
        root      1021     1  0 01:38 ttyS0    00:00:00 /sbin/agetty --keep-baud 115200 38400 9600 ttyS0 vt220
        root      1063     1  0 01:38 ?        00:00:00 /usr/sbin/sshd -D
        root      1084  1063 17 01:38 ?        00:00:00 sshd: centos [priv]
        centos    1087  1084  0 01:38 ?        00:00:00 sshd: centos@notty
        centos    1088  1087  0 01:38 ?        00:00:00 ps -ef

lifecycle continue
        END RequestId: 637efafc-0f95-11e8-a1ec-fd427653aa45
        REPORT RequestId: 637efafc-0f95-11e8-a1ec-fd427653aa45  Duration: 6824.96 ms    Billed Duration: 6900 ms        Memory Size: 128 MB       Max Memory Used: 90 MB

[root@centos702 lambda4]#

テスト中、ライフサイクルフックで、"保留:待機" 状態になったEC2インスタンスを Lambda関数で続行できなかった場合は、 aws-cli で続行できます。
以下のように、AutoScalingグループ名、ライフサイクルフック名、インスタンスIDを指定してコマンドを実行します。
[root@centos702 ~]# aws autoscaling complete-lifecycle-action --lifecycle-action-result CONTINUE --instance-id i-0d11d4ff1f236cc75 --lifecycle-hook-name hook-scaleout --auto-scaling-group-name as-group-01




2018年2月11日日曜日

AWS SAM Local でデプロイしたLambda(python) を実行する - 2/2


下記の記事の続きです。
  1. AWS SAM Local を CentOS7 にインストール
  2. SSHでシェルを実行するLambda(python) 関数をAWS SAM Local でテストする 
  3. AWS SAM Local で Lambda(python) をデプロイする 
  4. AWS SAM Local でデプロイしたLambda(python) を実行する
  5. lambda(python)でEC2を検索して、別のlambda(python)関数を実行する - 1/2

前回、TriggerFunction を AWS Sam Local で動作確認しました。
今回は、TriggerFunction を AWSにデプロイし、スケジュールイベント(5分ごと)で実行してみます。

1.TriggerFunction用のIAMロール用意


TriggetFunction 用のロールを用意します。


インラインポリシーで、lambda関数を実行する権限を付けてます。
lambda_invoke の定義は以下のとおり。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "lambda:InvokeFunction",
            "Resource": "arn:aws:lambda:*:*:function:*"
        }
    ]
}

2. TriggerFunction のデプロイ


S3上にパッケージを作成します。
[root@centos702 lambda2]# sam package --template-file template.yaml --s3-bucket blue21.dev.local --output-template-file /tmp/output-template.yaml
A newer version of the AWS SAM CLI is available!
Your version:   0.2.4
Latest version: 0.2.6
See https://github.com/awslabs/aws-sam-local for upgrade instructions

Uploading to 878fce0d1586a726423c4c8f2b7efd8c  1535 / 1535.0  (100.00%)
Successfully packaged artifacts and wrote output template to file /tmp/output-template.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /tmp/output-template.yaml --stack-name <YOUR STACK NAME>

CloudFormation を使用してデプロイします。
[root@centos702 lambda2]# aws cloudformation deploy --template-file /tmp/output-template.yaml --stack-name LambdaTriggerFunction

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - LambdaTriggerFunction

デプロイが成功すると、AWSコンソールのLambda画面で下図のように TriggerFunction が表示されます。





登録したスケジュールを見てみます。
[CloudWatchEvent]をクリックして、下にスクロールします。

下図のように、登録したスケジュールが表示されます。
この例では、5分ごとに TriggerFunction が実行されます。


3. 実行結果の確認


デプロイ後、しばらく待って、ログを見てみます。
ロググループを確認します。
[root@centos702 lambda1]# aws logs describe-log-groups --output text --query "logGroups[*].logGroupName"
/aws/lambda/SSHFunction /aws/lambda/TriggerFunction

TriggerFunction のログを見ます。
[root@centos702 lambda1]#  aws logs describe-log-streams --log-group-name /aws/lambda/TriggerFunction --output=text --query "logStreams[*].logStreamName"
2018/02/11/[$LATEST]75d34dba53084a94993fa09e00905cee
[root@centos702 lambda1]# aws logs get-log-events --log-group-name /aws/lambda/TriggerFunction --log-stream-name '2018/02/11/[$LATEST]75d34dba53084a94993fa09e00905cee' --output=text --query "events[*].message"
START RequestId: 9ff3b640-0ec7-11e8-8609-198ad9decfe5 Version: $LATEST
        Invoking worker_function on 10.0.10.65
        {u'Payload': <botocore.response.StreamingBody object at 0x7f67497f8bd0>, 'ResponseMetadata': {'RetryAttempts': 0, 'HTTPStatusCode': 202, 'RequestId': 'a0996bfe-0ec7-11e8-9ac3-31707f309fc8', 'HTTPHeaders': {'x-amzn-requestid': 'a0996bfe-0ec7-11e8-9ac3-31707f309fc8', 'content-length': '0', 'x-amzn-trace-id': 'root=1-5a7f96cf-5c0d200803432cde6ca9c811;sampled=0', 'x-amzn-remapped-content-length': '0', 'connection': 'keep-alive', 'date': 'Sun, 11 Feb 2018 01:05:19 GMT'}}, u'StatusCode': 202}
        END RequestId: 9ff3b640-0ec7-11e8-8609-198ad9decfe5
        REPORT RequestId: 9ff3b640-0ec7-11e8-8609-198ad9decfe5  Duration: 1072.07 ms    Billed Duration: 1100 ms        Memory Size: 128 MB   Max Memory Used: 59 MB

SSHFunctionのログを見てみます。
[root@centos702 lambda1]#  aws logs describe-log-streams --log-group-name /aws/lambda/SSHFunction --output=text --query "logStreams[*].logStreamName"
2018/02/11/[$LATEST]78278dfd8e7e4a9fb55f5134cd9f4029
[root@centos702 lambda1]# aws logs get-log-events --log-group-name /aws/lambda/SSHFunction --log-stream-name '2018/02/11/[$LATEST]78278dfd8e7e4a9fb55f5134cd9f4029' --output=text --query "events[*].message"
START RequestId: a0996bfe-0ec7-11e8-9ac3-31707f309fc8 Version: $LATEST
        [ERROR] 2018-02-11T01:05:19.699Z        a0996bfe-0ec7-11e8-9ac3-31707f309fc8    Socket exception: Operation not permitted (1)
        Connecting to 10.0.10.65
        Connected to 10.0.10.65
        Executing /usr/bin/aws s3 cp s3://blue21.dev.local/HelloWorld.sh /tmp/HelloWorld.sh --region us-east-1
download: s3://blue21.dev.local/HelloWorld.sh to ../../tmp/HelloWorld.shng


Executing /usr/bin/chmod 700 /tmp/HelloWorld.sh


Executing /tmp/HelloWorld.sh
        Sun Feb 11 01:05:20 UTC 2018
        ip-10-0-10-65.ec2.internal
        Hello


Executing /bin/rm /tmp/HelloWorld.sh
        END RequestId: a0996bfe-0ec7-11e8-9ac3-31707f309fc8
        REPORT RequestId: a0996bfe-0ec7-11e8-9ac3-31707f309fc8  Duration: 1570.62 ms    Billed Duration: 1600 ms        Memory Size: 128 MB   Max Memory Used: 67 MB



[root@centos702 lambda1]#

2018年2月10日土曜日

lambdaのログを aws-cli で見る


aws-cliでlambdaのログを見ます。

ロググループを一覧します。
[root@centos702 lambda2]# aws logs describe-log-groups --output text --query "logGroups[*].logGroupName"
/aws/lambda/myhello

目的のロググループのストリームを一覧します。
[root@centos702 lambda2]# aws logs describe-log-streams --log-group-name /aws/lambda/myhello --output=text --query "logStreams[*].logStreamName"
2018/02/10/[$LATEST]a6f797c784304016bc3481abce2fccf4

目的のストリームを指定して、lambda の実行結果ログを見ます。
[root@centos702 lambda2]# aws logs get-log-events --log-group-name /aws/lambda/myhello --log-stream-name '2018/02/10/[$LATEST]a6f797c784304016bc3481abce2fccf4' --output=text --query "events[*].message"
Loading function
        START RequestId: 5fb46fb4-0e3b-11e8-9eae-07b594f1ffb1 Version: $LATEST
        value1 = value1
        value2 = value2
        value3 = value3
        END RequestId: 5fb46fb4-0e3b-11e8-9eae-07b594f1ffb1
        REPORT RequestId: 5fb46fb4-0e3b-11e8-9eae-07b594f1ffb1  Duration: 0.51 ms       Billed Duration: 100 ms         Memory Size: 128 MB      Max Memory Used: 19 MB

[root@centos702 lambda2]#

いらないロググループを削除します。
[root@centos702 lambda2]# aws logs delete-log-group --log-group-name /aws/lambda/myhello


lambda(python)でEC2を検索して、別のlambda(python)関数を実行する - 1/2


lambdaを使用し、EC2に設定したタグ(Environment)の値が"dev"のサーバでシェルを実行してみます。

下記の記事で使用した lambda関数のSSHFunctionを使用します。


1.EC2を検索する lambda の作成


ソースコードは以下のとおり。
タグ(Environment)に "dev" が設定されているEC2を検索して、lambda関数の SSHFunctionを実行します。
trigger_function.py ファイルに保存します。
import boto3

def trigger_handler(event, context):

    # Get IP addresses of EC2 instances
    ec2 = boto3.client('ec2')
    instances = ec2.describe_instances(
        Filters=[{'Name':'tag:Environment','Values':['dev']}]
    )

    hosts = []
    for r in instances['Reservations']:
        for inst in r['Instances']:
            hosts.append(inst['PrivateIpAddress'])

    if len(hosts) == 0 :
        return {
          'message' : "Trigger function finished: no host"
        }

    # Invoke worker function for each IP address
    la = boto3.client('lambda')
    for host in hosts:
        print "Invoking worker_function on " + host
        response = la.invoke(
            FunctionName='SSHFunction',
            InvocationType='Event',
            LogType='Tail',
            Payload='{"IP":"'+ host +'"}'
        )
        print response

    return {
        'message' : "Trigger function finished"
    }

2. AWS SAM Local で動作確認


上記1のプログラムを AWS SAM Local で動作確認します。
まず、template.yaml を作成し、関数名を TriggerFunction とします。
TriggerFunctionはVPC外で実行します。
AWSTemplateFormatVersion: '2010-09-09'
Description: trigger application.
Transform: AWS::Serverless-2016-10-31
Resources:
  TriggerFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      FunctionName: TriggerFunction
      Handler: trigger_function.trigger_handler
      MemorySize: 128
      Role: arn:aws:iam::xxxxxxx:role/Lambda02_Role
      Runtime: python2.7
      Timeout: 10
      Events:
        ScheduleRate5min:
          Type: Schedule
          Properties:
            Schedule: cron(0/5 * * * ? *)

この TriggerFunction は、スケジュールイベントで実行したいので、以下のようにしてテスト用のイベントデータを作成します。
[root@centos702 lambda2]# sam local generate-event schedule > event.json
A newer version of the AWS SAM CLI is available!
Your version:   0.2.4
Latest version: 0.2.6
See https://github.com/awslabs/aws-sam-local for upgrade instructions

[root@centos702 lambda2]# cat event.json
{
  "account": "123456789012",
  "region": "us-east-1",
  "detail": {},
  "detail-type": "Scheduled Event",
  "source": "aws.events",
  "time": "1970-01-01T00:00:00Z",
  "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
  "resources": [
    "arn:aws:events:us-east-1:123456789012:rule/my-schedule"
  ]
}[root@centos702 lambda2]#

用意したファイルは以下のとおり。
[root@centos702 lambda2]# ls
event.json  template.yaml  trigger_function.py

TriggerFunction を実行すると、以下のとおり。
まだ、EC2を用意していないので、SSHFunction を実行する前で処理が終了しています。
[root@centos702 lambda2]# sam local invoke TriggerFunction -e event.json
A newer version of the AWS SAM CLI is available!
Your version:   0.2.4
Latest version: 0.2.6
See https://github.com/awslabs/aws-sam-local for upgrade instructions

2018/02/10 12:55:12 Successfully parsed template.yaml
2018/02/10 12:55:12 Connected to Docker 1.35
2018/02/10 12:55:12 Fetching lambci/lambda:python2.7 image for python2.7 runtime...
python2.7: Pulling from lambci/lambda
Digest: sha256:87a3b9bb1ba6ae666b0087107852b67415cae0660669ae5633a0ab828aea2c69
Status: Image is up to date for lambci/lambda:python2.7
2018/02/10 12:55:14 Invoking trigger_function.trigger_handler (python2.7)
2018/02/10 12:55:14 Mounting /root/lambda2 as /var/task:ro inside runtime container
START RequestId: 8d9565ee-e50e-4ef0-8b40-7945e5cda16c Version: $LATEST
END RequestId: 8d9565ee-e50e-4ef0-8b40-7945e5cda16c
REPORT RequestId: 8d9565ee-e50e-4ef0-8b40-7945e5cda16c Duration: 1338 ms Billed Duration: 1400 ms Memory Size: 128 MB Max Memory Used: 37 MB

{"message": "Trigger function finished: no host"}

[root@centos702 lambda2]#

EC2を用意して、もう一度、実行します。
確認しやすいように、SSHFunction のログは消しておきます。
[root@centos702 lambda2]# aws logs describe-log-groups --output text
LOGGROUPS       arn:aws:logs:us-east-1:544509011205:log-group:/aws/lambda/SSHFunction:* 1518244672189   /aws/lambda/SSHFunction 00
[root@centos702 lambda2]# aws logs delete-log-group --log-group-name /aws/lambda/SSHFunction
[root@centos702 lambda2]#

TiggerFunction を実行すると以下のとおり。
AWSに登録した SSHFunction が実行されました。
[root@centos702 lambda2]# sam local invoke "TriggerFunction" -e event.json
A newer version of the AWS SAM CLI is available!
Your version:   0.2.4
Latest version: 0.2.6
See https://github.com/awslabs/aws-sam-local for upgrade instructions

2018/02/10 15:42:30 Successfully parsed template.yaml
2018/02/10 15:42:30 Connected to Docker 1.35
2018/02/10 15:42:30 Fetching lambci/lambda:python2.7 image for python2.7 runtime...
python2.7: Pulling from lambci/lambda
Digest: sha256:87a3b9bb1ba6ae666b0087107852b67415cae0660669ae5633a0ab828aea2c69
Status: Image is up to date for lambci/lambda:python2.7
2018/02/10 15:42:33 Invoking trigger_function.trigger_handler (python2.7)
2018/02/10 15:42:33 Mounting /root/lambda2 as /var/task:ro inside runtime container
START RequestId: 5f9dc595-2346-42d1-baf2-b400ddfe53c0 Version: $LATEST
Invoking worker_function on 10.0.11.189
{u'Payload': <botocore.response.StreamingBody object at 0x7f06e174e7d0>, 'ResponseMetadata': {'RetryAttempts': 0, 'HTTPStatusCode': 202, 'RequestId': '95152c29-0e2d-11e8-bd14-b9436f2119d9', 'HTTPHeaders': {'x-amzn-requestid': '95152c29-0e2d-11e8-bd14-b9436f2119d9', 'content-length': '0', 'x-amzn-trace-id': 'root=1-5a7e945d-0a492b15067bfbb92fd8e167;sampled=0', 'x-amzn-remapped-content-length': '0', 'connection': 'keep-alive', 'date': 'Sat, 10 Feb 2018 06:42:37 GMT'}}, u'StatusCode': 202}
END RequestId: 5f9dc595-2346-42d1-baf2-b400ddfe53c0
REPORT RequestId: 5f9dc595-2346-42d1-baf2-b400ddfe53c0 Duration: 2856 ms Billed Duration: 2900 ms Memory Size: 128 MB Max Memory Used: 39 MB

{"message": "Trigger function finished"}


[root@centos702 lambda2]#

SSHFunction のログを見てみます。
[root@centos702 lambda2]# aws logs describe-log-groups --output text
LOGGROUPS       arn:aws:logs:us-east-1:544509011205:log-group:/aws/lambda/SSHFunction:* 1518244957766   /aws/lambda/SSHFunction 00
[root@centos702 lambda2]# aws logs describe-log-streams --log-group-name /aws/lambda/SSHFunction --output=text                   LOGSTREAMS      arn:aws:logs:us-east-1:544509011205:log-group:/aws/lambda/SSHFunction:log-stream:2018/02/10/[$LATEST]c157d903671c49f1aef959d7e46ee9fa    1518244957800   1518244957394   1518244957394   1518244972984   2018/02/10/[$LATEST]c157d903671c49f1aef959d7e46ee9fa     0       49578651561709524302585247451449080513328803549848761458
[root@centos702 lambda2]# aws logs get-log-events --log-group-name /aws/lambda/SSHFunction --log-stream-name '2018/02/10/[$LATEST]c157d903671c49f1aef959d7e46ee9fa' --output=text
b/33857993943797576004760612550598371585409610073464832000      f/33857993994397966860226596477071430965818472836432855052
EVENTS  1518244957823   START RequestId: 95152c29-0e2d-11e8-bd14-b9436f2119d9 Version: $LATEST
        1518244957394
EVENTS  1518244972984   Connecting to 10.0.11.189
        1518244957454
EVENTS  1518244972984   Connected to 10.0.11.189
        1518244957778
EVENTS  1518244972984   Executing /usr/bin/aws s3 cp s3://blue21.dev.local/HelloWorld.sh /tmp/HelloWorld.sh --region us-east-1
        1518244957778
download: s3://blue21.dev.local/HelloWorld.sh to ../../tmp/HelloWorld.shile(s) remaining
        1518244958444
EVENTS  1518244972984

Executing /usr/bin/chmod 700 /tmp/HelloWorld.sh
        1518244958444
EVENTS  1518244972984

Executing /tmp/HelloWorld.sh
        1518244958529
EVENTS  1518244972984   Sat Feb 10 06:42:38 UTC 2018
        1518244958608
EVENTS  1518244972984   ip-10-0-11-189.ec2.internal
        1518244958608
EVENTS  1518244972984   Hello
        1518244958608
EVENTS  1518244972984

Executing /bin/rm /tmp/HelloWorld.sh
        1518244958608
EVENTS  1518244972984   END RequestId: 95152c29-0e2d-11e8-bd14-b9436f2119d9
        1518244958662
EVENTS  1518244972984   REPORT RequestId: 95152c29-0e2d-11e8-bd14-b9436f2119d9  Duration: 1265.48 ms    Billed Duration: 1300 ms Memory Size: 128 MB     Max Memory Used: 59 MB
        1518244958662
EVENTS  1518244972984

        1518244959663
[root@centos702 lambda2]#

TriggerFunction から SSHFunction が実行され、EC2で HelowWorld.sh が実行されたのがわかります。




2018年2月4日日曜日

CloudWatchダッシュボードを python で作る

python の boto3 を使用して、CloudWatch ダッシュボードを作ります。

EC2のタグ(Environment) に 'dev' が設定されてるサーバを対象にして、CPU使用率を表示するグラフを作ってみます。

ソースコードは以下のとおり。

[root@centos702 ~]# cat ./dashbord_cpu.py
#!/usr/bin/python
# coding: utf-8

# Dashboard Body Structure and Syntax
#
# https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/APIReference/CloudWatch-Dashboard-Body-Structure.html#CloudWatch-Dashboard-Properties-Metrics-Array-Format
#

import boto3
import json

cw   = boto3.client("cloudwatch")
ec2  = boto3.client("ec2")
ec2r = boto3.resource('ec2')

metrics_name  = "CPUUtilization"
x, y          = [0, 0]
width, height = [10, 5]
widgets       = []
metrics       = []

# TAG(Environment) が "dev" の EC2 が対象
instances = ec2.describe_instances(
              Filters=[{'Name':'tag:Environment','Values':['dev']}]
            )

# CPU使用率のメトリクス 作成
# 1グラフに複数のEC2を表示する
for r in instances['Reservations']:
    for i in r['Instances']:
       # TAG(Name) の値を取得する
       ec2instance = ec2r.Instance(i['InstanceId'])
       name = [t['Value'] for t in ec2instance.tags if t['Key'] == 'Name']
       if len(name) != 0:
           # メトリクスをセット
           metric = ['AWS/EC2', metrics_name, 'InstanceId', i['InstanceId'], {'label':name[0]}]
           metrics.append(metric)

# ウィジェット作成
widget = {'type'      : 'metric',
          'x'         : x,
          'y'         : y,
          'height'    : height,
          'width'     : width,
          'properties': { 'view'    : 'timeSeries',
                          'stacked' : False,
                          'metrics' : metrics,
                          'period'  : 1,
                          'stat'    : 'Average',
                          'region'  : 'us-east-1',
                          'title'   : metrics_name
                        }
         }

widgets.append(widget)

# ClodWatch dashbord 用 のJSONデータ
body   = {'widgets' : widgets}
body_j = json.dumps(body)
#print body_j

# Dashbord 登録
cw.put_dashboard(DashboardName = "EC2_DEV_CPU",
                 DashboardBody = body_j)

以下のようにして実行します。
[root@centos702 ~]# chmod +x ./dashbord_cpu.py
[root@centos702 ~]# ./dashbord_cpu.py
[root@centos702 ~]#

AWSコンソールで、CloudWatchを見ると、下図のようにダッシュボードが作成されます。



グラフは、下図のように表示されます。
1つのグラフに、複数サーバの CPU使用率が表示されます。
グラフ線のラベルには、Nameタグの値(サーバ名)を使用しています。





2018年2月3日土曜日

AWS SAM Local でデプロイしたLambda(python) を実行する

前回の記事でデプロイした Lmbda 関数を実行します。

事前に、EC2インスタンスを起動しておきます。

1. テスト用のイベントを作成する


テスト用のイベントを作成します。



用意したEC2インスタンスのプライベートIPアドレスを指定します。



2.Lambda関数を実行する


上記で作成したイベントを指定して、[テスト]ボタンをクリックします。



関数の実行が成功すると下図のようにログが表示されます。



ログを見ると、下記のようなエラーメッセージがありました。

[ERROR] 2018-02-03T08:46:55.405Z c9897fd5-08be-11e8-8efc-358b494b1cbf Socket exception: Operation not permitted (1)

そこで、プログラムを修正しました。
下記のように記述していたところを
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())

下記のように修正しました。
c.set_missing_host_key_policy(paramiko.WarningPolicy())

AWS SAM Localで再度パッケージ化してデプロイしました。
[root@centos702 lambda1]# sam package --template-file template.yaml --s3-bucket blue21.dev.local --output-template-file /tmp/output-template.yaml
A newer version of the AWS SAM CLI is available!
Your version:   0.2.4
Latest version: 0.2.6
See https://github.com/awslabs/aws-sam-local for upgrade instructions

Uploading to 1a0a221d60d64481d730a3c59d8beb34  5730978 / 5730978.0  (100.00%)
Successfully packaged artifacts and wrote output template to file /tmp/output-template.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /tmp/output-template.yaml --stack-name <YOUR STACK NAME>
[root@centos702 lambda1]# aws cloudformation deploy --template-file /tmp/output-template.yaml --stack-name LambdaSSHFunction

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - LambdaSSHFunction

もう一度実行すると、エラーが消えました。