serverless frameworkを使って本格的なAPIサーバーを構築(魅力編)

serverless frameworkを使って本格的なAPIサーバーを構築(魅力編)

目次

  • serverless frameworkを使って本格的なAPIサーバーを構築(魅力編)← 今ここ
  • serverless frameworkを使って本格的なAPIサーバーを構築(ハンズオン編)
  • serverless frameworkを使って本格的なAPIサーバーを構築(Express編)
  • serverless frameworkを使って本格的なAPIサーバーを構築(MVC編)

framework_repo.png

こんにちは!某会社のインフラエンジニアをしておりますが、最近は サーバーレス という言葉が流行ってますね。
今回は、serverless frameworkで 「 lambda + APIGateway + DynamoDB 」 の構成でいくつかサービスを作ったので、利点などをまとめていけたらと思います。

サーバーレスとserverless framework

まずサーバーレスとは

サーバーの管理が不要になる

  • サーバーレスアーキテクチャでは、管理するサーバーがないため、サーバー管理から開放され、その分管理コストがかからなくなります。物理的なサーバーの管理はクラウド事業者に一任できるので、故障やパッチ適用の心配をする必要はありません。

また、一般にコード実行サービスは高い可用性を持っているため、オンプレミスサーバーよりも障害耐性が高くなるでしょう。

アプリ開発において便利

  • APIサーバーとして、ハッカソン用に作ったアプリの運用など、ほとんどアクセスがないようなAPIサーバーをずっと放置しておくのは大変お金がかかります。

従来の冗長化構成に比べ安価

  • EC2 + RDS環境を構築したときに、ある程度の規模であれば安価に済ますことができます。

サーバーの脆弱性を突いた攻撃が効かない

  • セキュリティ被害の多くはサーバーOSやミドルウェアの脆弱性を突いた攻撃ですが、公開ネットワーク上にサーバーが無いので、一般的なサーバーの脆弱性を突く攻撃が効きません。

  • Amazonのプラットフォームに脆弱性が無いとは言えませんが、プラットフォーム仕様が公開されていないので脆弱性を探すことが難しく、一般的なサーバーOSより脆弱性被害の可能性は少ないと考えられます。

サーバーが落ちることがない

  • サーバーが無いのでサーバーダウンの概念がありません。

  • 厳密にはAmazonがダウンすれば共倒れしますが、どのプラットフォームもサービス本体がダウンすれば同じことが言えるので、世界的インフラのAWSと、自社サーバーや一般企業のレンタルサーバーのどちらを信用するか、という話になります。

放置できる

  • セキュリティパッチや、サーバー自体の管理が発生しないためある程度放置できます。

インフラエンジニアがいなくてもスケールする

  • lambdaは最大で3000も同時に実行できます。
  • dynamodbは、キャパシティユニットとオートスケールの設定がかんたんにできます。
  • S3で静的コンテンツを配信したとしても障害確率はかなり低いです。

とにかく安い!

  • サーバーは初期導入コストが高く、必要になるサーバースペックも事前に見積もっておかなければいけません。物理障害時のパーツ交換費用なども必要です。
    クラウドサーバーにしても、起動している時間は料金がかかるため、閑散時などは100%コストを活用できているとはいえません。

  • 一方、コード実行サービスであれば、実際にコードを実行した時間に応じて課金されるため、ムダなコストが発生しません。初期導入コストもかからないため、システムの規模に合わせて柔軟にスケールできます。

保守メンテが楽

  • セキュリテイパッチやサーバー管理などのメンテが不要になります。

APIGateway+DynamoDB+Lambda手動構築の苦悩

image.png

  • serverless framework を使わずに「lambda + APIGateway + DynamoDB」を構築しようとすると、ほぼボタン操作で設定しないといけないです。
  • どのステージをデプロイするのか、設定した項目・手順を忘れた。もとに戻したい、引き継ぎを行いたいなどいろいろなケースが考えられます

再現性がない

  • これに尽きると思います。
  • 基本的にAPIGatewayの設定などはボタン操作です。もちろんgit管理などできない。
    ステージの作成、レスポンスコード、methodの作成など操作を間違えても元に戻すことが難しくなり、実装コスト、人件費、考えるだけで大変なことが多すぎます。(大変だった。)

よって保守性・メンテ・引き継ぎが難しくなっていました。

そこでserverless frameworkの出番です!!

image.png

  • 基本的にAWSコンソールでのボタン操作は不要!!
  • serverless.ymlですべて管理・設定ができる => 設定をgit管理できる

    • aws cloudformationの形式と似ているため、最悪それを真似できます(cloudformationよりかんたん!)
  • コマンド1発でデプロイとロールバックが可能
  • 公式リポジトリにサンプルが沢山!!(こまりません)
  • プラグインもたくさん(ぶっちゃけ使わなくても高機能!!)
  • Expressと組み合わせると、超便利!!!!!(後日記事を書きます)

注意点など

  • iamのadmin権限が必要です。
  • Cloudfrontは設定できません。

次回

  • serverless frameworkを使って本格的なAPIサーバーを構築(ハンズオン編)(仮)

参考記事

続きを読む

AWS CloudWatch で、Amazon EC2 Windows インスタンスのパフォーマンスとログの監視をしてみる

0.はじめに

Amazon EC2 Windows インスタンスを利用していますが、
パフォーマンス監視は Zabbix を使って、
ログ監視は特に何も、
という感じでした。

CloudWatch のメトリクスの保存期間も長くなったみたいですし、
運用の手間やリスク、コスト削減も考慮して、
パフォーマンス監視を CloudWatch、
ログ監視を CloudWatch Logs、
にしようかと思います。

1.EC2Config の最新バージョンへのアップデート

  1. 以下のサイトの手順に従って、アップデートします。

2.Amazon EC2 Systems Manager (SSM) エージェントのインストール

  1. EC2 インスタンスに割り当てられている IAM Role に「AmazonEC2RoleforSSM」を付与します。

  2. 以下のサイトの手順に従って、アップデートします。

3.Amazon EC2 Systems Manager (SSM) エージェントを使っての CloudWatch の設定

  1. 以下のサイトを参考にしました。

  2. まず、以下の json ファイルを準備します。

    • 78 行目 : “NameSpace”: “System/Detail/Windows”

      • これが、カスタム名前空間になります。適当な名称に変更して下さい。
    • 44-69 行目 :
      • C ドラのメトリクスの設定になります。他のドライブを追加するのであれば、C を D に変えて、行を追加して下さい。
      • 85 行目にも追加するのを忘れずに。
    Sample-ConfigureCloudWatch-Windows.json
    {
        "EngineConfiguration": {
            "PollInterval": "00:00:15",
            "Components": [
                {
                    "Id": "CPULoadAverage",
                    "FullName": "AWS.EC2.Windows.CloudWatch.PerformanceCounterComponent.PerformanceCounterInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "CategoryName": "System",
                        "CounterName": "Processor Queue Length",
                        "InstanceName": "",
                        "MetricName": "CPULoadAverage",
                        "Unit": "Percent",
                        "DimensionName": "InstanceId",
                        "DimensionValue": "{instance_id}"
                    }
                },
                {
                    "Id": "MemoryAvailable",
                    "FullName": "AWS.EC2.Windows.CloudWatch.PerformanceCounterComponent.PerformanceCounterInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "CategoryName": "Memory",
                        "CounterName": "Available Bytes",
                        "InstanceName": "",
                        "MetricName": "MemoryAvailable",
                        "Unit": "Bytes",
                        "DimensionName": "InstanceId",
                        "DimensionValue": "{instance_id}"
                    }
                },
                {
                    "Id": "MemoryUtilization",
                    "FullName": "AWS.EC2.Windows.CloudWatch.PerformanceCounterComponent.PerformanceCounterInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "CategoryName": "Memory",
                        "CounterName": "% Committed Bytes in Use",
                        "InstanceName": "",
                        "MetricName": "MemoryUtilization",
                        "Unit": "Percent",
                        "DimensionName": "InstanceId",
                        "DimensionValue": "{instance_id}"
                    }
                },
                {
                    "Id": "DiskAvailableMegabytesC",
                    "FullName": "AWS.EC2.Windows.CloudWatch.PerformanceCounterComponent.PerformanceCounterInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "CategoryName": "LogicalDisk",
                        "CounterName": "Free Megabytes",
                        "InstanceName": "C:",
                        "MetricName": "DiskAvailableMegabytesC",
                        "Unit": "Megabytes",
                        "DimensionName": "InstanceId",
                        "DimensionValue": "{instance_id}"
                    }
                },
                {
                    "Id": "DiskFreeSpaceC",
                    "FullName": "AWS.EC2.Windows.CloudWatch.PerformanceCounterComponent.PerformanceCounterInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "CategoryName": "LogicalDisk",
                        "CounterName": "% Free Space",
                        "InstanceName": "C:",
                        "MetricName": "DiskFreeSpaceC",
                        "Unit": "Percent",
                        "DimensionName": "InstanceId",
                        "DimensionValue": "{instance_id}"
                    }
                },
                {
                    "Id": "CloudWatch",
                    "FullName": "AWS.EC2.Windows.CloudWatch.CloudWatch.CloudWatchOutputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters":
                    {
                        "AccessKey": "",
                        "SecretKey": "",
                        "Region": "ap-northeast-1",
                        "NameSpace": "System/Detail/Windows"
                    }
                }
            ],
            "Flows": {
                "Flows":
                [
                    "(CPULoadAverage,MemoryAvailable,MemoryUtilization,DiskAvailableMegabytesC,DiskFreeSpaceC),CloudWatch"
                ]
            }
        }
    }
    

  3. EC2 のマネジメントコンソールの左側ペインから、「SYSTEMS MANAGER 共有リソース」→「マネージドインスタンス」を選択します。対象となる EC2 インスタンスが表示されるはずなので選択し(表示されなければ、これまでの手順に何か問題があるのかも?)、「関連付けの作成」ボタンを押下します。

    • FireShot Capture 152 - EC2 Management C_ - https___ap-northeast-1.console.aws.amazon.com_ec2_v2_home.png

  4. 「関連付けの作成」画面が表示されるので、以下の項目を入力し、「関連付けの作成」ボタンを押下します。

    • 関連付けの名前 : Sample-ConfigureCloudWatch-Windows ※ 任意
    • ドキュメントを選択
      • ドキュメント : AWS-ConfigureCloudWatch
      • バージョンの説明 : –
    • ターゲット ※ 対象となる EC2 インスタンスが設定されれば OK。
      • ターゲットの選択 : インスタンスの手動選択 ※ 任意
      • インスタンスの選択 : 対象となる EC2 インスタンス
    • スケジュール
      • 指定 : Cron スケジュールビルダー ※ デフォルト
      • 関連付けの実行 : 30 分ごと ※ デフォルト
    • パラメーター
      • Status : Enabled ※ デフォルト
      • Properties : ※ json ファイルの内容をコピペ
    • 詳細設定
      • S3 への書き込み : □ ※ デフォルト

    • FireShot Capture 153 - EC2 Management C_ - https___ap-northeast-1.console.aws.amazon.com_ec2_v2_home.png

  5. 「関連付けの作成」の結果画面が表示されるので、「閉じる」ボタンを押下します。

    • FireShot Capture 158 - EC2 Management C_ - https___ap-northeast-1.console.aws.amazon.com_ec2_v2_home.png

  6. 元の一覧画面が表示されるので、関連付けがちゃんと作成されているか確認します。

    • ※しばらくすると、ステータスが「保留中」から「成功」に変わるはずです。

    • FireShot Capture 159 - EC2 Management C_ - https___ap-northeast-1.console.aws.amazon.com_ec2_v2_home.png

    • FireShot Capture 160 - EC2 Management C_ - https___ap-northeast-1.console.aws.amazon.com_ec2_v2_home_.png

  7. CloudWatch のマネジメントコンソールの左側ペインから、「メトリクス」を選択します。設定したカスタム名前空間と EC2 インスタンスの ID でフィルタをかけて、設定したメトリクスのデータがあるか確認します。

    • FireShot Capture 161 - CloudWatch Management Console_ - https___ap-northeast-1.console.aws_.png

4.Amazon EC2 Systems Manager (SSM) エージェントを使っての CloudWatch Logs の設定

  1. CloudWatch Logs の設定も、CloudWatch の手順と同じです。違うのは、json ファイルの設定内容だけです。

  2. 以下、EventLog の json ファイルです。

    • 36 行目 : “LogGroup”: “Windows/EventLog”

      • ロググループです。適当な名称に変更して下さい。
    • 10,18,26 行目 :
      • 出力レベルです。適当な値に変更して下さい。
    Sample-ConfigureCloudWatchLogs-Windows_EventLog.json
    {
        "EngineConfiguration": {
            "PollInterval": "00:00:15",
            "Components": [
                {
                    "Id": "ApplicationEventLog",
                    "FullName": "AWS.EC2.Windows.CloudWatch.EventLog.EventLogInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "LogName": "Application",
                        "Levels": "7"
                    }
                },
                {
                    "Id": "SystemEventLog",
                    "FullName": "AWS.EC2.Windows.CloudWatch.EventLog.EventLogInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "LogName": "System",
                        "Levels": "7"
                    }
                },
                {
                    "Id": "SecurityEventLog",
                    "FullName": "AWS.EC2.Windows.CloudWatch.EventLog.EventLogInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                    "LogName": "Security",
                    "Levels": "7"
                    }
                },
                {
                    "Id": "CloudWatchLogs",
                    "FullName": "AWS.EC2.Windows.CloudWatch.CloudWatchLogsOutput,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "AccessKey": "",
                        "SecretKey": "",
                        "Region": "ap-northeast-1",
                        "LogGroup": "Windows/EventLog",
                        "LogStream": "{instance_id}"
                    }
                }
            ],
            "Flows": {
                "Flows":
                [
                    "(ApplicationEventLog,SystemEventLog,SecurityEventLog),CloudWatchLogs"
                ]
            }
        }
    }
    

  3. 以下、IIS ログの json ファイルです。

    • 22 行目 : “LogGroup”: “Windows/IisLog”

      • ロググループです。適当な名称に変更して下さい。
    • 5-14 行目 :
      • サイト毎の設定となります。W3SVC1 以外にも取得したのであれば、変更し行を追加して下さい。
      • 30 行目にも追加するのを忘れずに。
    Sample-ConfigureCloudWatchLogs-Windows_IisLog.json
    {
        "EngineConfiguration": {
            "PollInterval": "00:00:15",
            "Components": [
                {
                    "Id": "IISLogW3SVC1",
                    "FullName": "AWS.EC2.Windows.CloudWatch.IisLog.IisLogInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "LogName": "IISLogW3SVC1",
                        "LogDirectoryPath": "C:\inetpub\logs\LogFiles\W3SVC1",
                        "TimestampFormat": "yyyy-MM-dd HH:mm:ss",
                        "Encoding": "UTF-8"
                    }
                },
                {
                    "Id": "CloudWatchLogs",
                    "FullName": "AWS.EC2.Windows.CloudWatch.CloudWatchLogsOutput,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "AccessKey": "",
                        "SecretKey": "",
                        "Region": "ap-northeast-1",
                        "LogGroup": "Windows/IisLog",
                        "LogStream": "{instance_id}"
                    }
                }
            ],
            "Flows": {
                "Flows":
                [
                    "(IISLogW3SVC1),CloudWatchLogs"
                ]
            }
         }
    }
    

  4. 以下、カスタムログの json ファイルです。

    • 24 行目 : “LogGroup”: “Windows/CustomLog”

      • ロググループです。適当な名称に変更して下さい。
    • 5-16 行目 :
      • 取得したいログファイル毎の設定となります。適宜変更して下さい。
    • ※すいません。こちら未確認です…。
    Sample-ConfigureCloudWatchLogs-Windows_CustomLog.json
    {
        "EngineConfiguration": {
            "PollInterval": "00:00:15",
            "Components": [
                {
                    "Id": "CustomLogs",
                    "FullName": "AWS.EC2.Windows.CloudWatch.CustomLog.CustomLogInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "LogDirectoryPath": "C:\CustomLogs\",
                        "TimestampFormat": "MM/dd/yyyy HH:mm:ss",
                        "Encoding": "UTF-8",
                        "Filter": "",
                        "CultureName": "en-US",
                        "TimeZoneKind": "Local"
                    }
                },
                {
                    "Id": "CloudWatchLogs",
                    "FullName": "AWS.EC2.Windows.CloudWatch.CloudWatchLogsOutput,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "AccessKey": "",
                        "SecretKey": "",
                        "Region": "ap-northeast-1",
                        "LogGroup": "Windows/CustomLog",
                        "LogStream": "{instance_id}"
                    }
                }
            ],
            "Flows": {
                "Flows":
                [
                    "(CustomLogs),CloudWatchLogs"
                ]
            }
        }
    }
    

  5. 以下、ETW の json ファイルです。

    • 24 行目 : “LogGroup”: “Windows/ETW”

      • ロググループです。適当な名称に変更して下さい。
    • 5-16 行目 :
      • 取得したいログファイル毎の設定となります。適宜変更して下さい。
    • ※すいません。こちら未確認です…。
    Sample-ConfigureCloudWatchLogs-Windows_ETW.json
    {
        "EngineConfiguration": {
            "PollInterval": "00:00:15",
            "Components": [
                {
                    "Id": "ETW",
                    "FullName": "AWS.EC2.Windows.CloudWatch.EventLog.EventLogInputComponent,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "LogName": "Microsoft-Windows-WinINet/Analytic",
                        "Levels": "4"
                    }
                },
                {
                    "Id": "CloudWatchLogs",
                    "FullName": "AWS.EC2.Windows.CloudWatch.CloudWatchLogsOutput,AWS.EC2.Windows.CloudWatch",
                    "Parameters": {
                        "AccessKey": "",
                        "SecretKey": "",
                        "Region": "ap-northeast-1",
                        "LogGroup": "Windows/ETW",
                        "LogStream": "{instance_id}"
                    }
                }
            ],
            "Flows": {
                "Flows":
                [
                    "(ETW),CloudWatchLogs"
                ]
            }
        }
    }
    

  6. CloudWatch のマネジメントコンソールの左側ペインから、「ログ」を選択します。ロググループの一覧が表示されるので、設定したロググループと EC2 インスタンスの ID でフィルタをかけて、設定したログのデータがあるか確認します。

    • FireShot Capture 162 - CloudWatch Management Console_ - https___ap-northeast-1.console.aws.png

    • FireShot Capture 163 - CloudWatch Management Console_ - https___ap-northeast-1.console.aws.png

    • FireShot Capture 164 - CloudWatch Management Console_ - https___ap-northeast-1.console.aws_.png

99.ハマりポイント

  • 今回も結構ハマりました…。毎回、何やってんだ…。

  • 最初、今回の手順ではなく「SYSTEMS MANAGER 共有リソース」にある「ドキュメント」を作成して対応しようと思ったんですが、ドキュメントの作成がうまくいかず…、断念…。サポートセンターに問い合わせ中です。
  • その後、今回の手順で設定を進めたんですが、json ファイルの設定内容でハマりました。特にディスクの空容量のメトリクスの取得とかがよくわからず、難儀しました。こちらのサイトで何とか解決しました。ありがとうございます。

XX.まとめ

これで、
何とか運用監視も AWS だけで事足りる様になるのではと。

CloudWatch の 15ヶ月のデータ保存じゃ短すぎる!!
という場合は、
こちらのサイトのやり方などもあるみたいです。

続きを読む

業務でawsを使っていたらCloudTrailを設定しよう

awsを使っていたらCloudTrailを設定しよう

CloudTrailとは

  • AWS アカウントのガバナンス、コンプライアンス、運用監査、リスク監査を可能にするサービスです。(公式)

CloudTrail-01-HeaderPic-v2-small.png

できること

  • アカウントアクティビティをログに記録し、継続的に監視し、保持できます。
  • SDK やコマンドラインツール、その他の AWS のサービスを使用して実行されるアクションなど、AWS アカウントアクティビティのイベント履歴を把握できます。
  • このイベント履歴により、セキュリティ分析、リソース変更の追跡、トラブルシューティングをより簡単に実行できるようになります。

メリット

コンプライアンスの簡素化

  • アカウント内で行われた操作のイベントログが自動的に記録および保存されるため、コンプライアンス監査を簡素化できます。
  • Amazon CloudWatch Logs との統合により、ログデータを検索したり、コンプライアンス違反のイベントを特定したり、インシデントの調査と監査者の要求に対する応答を迅速化したりできます。

ユーザーとリソースのアクティビティの可視化

  • AWS マネジメントコンソールでの操作と AWS API コールを記録することにより、ユーザーおよびリソースのアクティビティを把握しやすくなります。
  • AWS を呼び出したユーザーとアカウント、呼び出し元 IP アドレス、および呼び出し日時を特定できます。

セキュリティのオートメーション

  • セキュリティの脆弱性を引き起こす可能性のあるイベントが検出されたときに実行されるワークフローを定義できます。
  • 例えば、 Amazon S3 バケットを公開する API コールが CloudTrail によってログに記録されたときに特定のポリシーをそのバケットに追加する、というワークフローを作成できます。

利用してみる

  • CloudTrailにアクセスするとすでにログが記録されているのがわかります
  • 誰が何時に何の操作をしたのかがわかります

Screen Shot 2017-09-16 at 21.28.41.png

  • すべてのイベントを表示してみると

Screen Shot 2017-09-16 at 21.28.56.png

  • 7日以上のログを保存する場合や、アラートを飛ばしたいときは「証跡情報を作成」します

Screen Shot 2017-09-16 at 21.29.17.png

  • s3バケットなどを細かく設定できます!!!!!!

料金

  • WS CloudTrail を使用すると、サポートされたサービスの作成、変更、削除のオペレーションに関する過去 7 日間分のアカウントアクティビティの表示とダウンロードを無料で実行できます。

  • 証跡情報を作成する際に AWS CloudTrail からの課金は発生しません。CloudTrail の証跡を作成すると、Amazon S3 バケットに 2 種類のイベントを送信できるようになります。

  • 詳しくは公式の料金表

サポートされているサービス

ほとんどサポートされていますね

  • AWS Marketplace
  • Amazon Athena
  • Amazon CloudSearch
  • Amazon EMR
  • AWS Data Pipeline
  • Amazon Kinesis Firehose
  • Amazon Kinesis Streams
  • Amazon QuickSight
  • Amazon API Gateway
  • Amazon Elastic Transcoder
  • Amazon Elasticsearch Service
  • Amazon Simple Workflow Service
  • AWS Step Functions
  • Amazon Machine Learning
  • Amazon Polly
  • Amazon WorkDocs
  • アプリケーションの Auto Scaling
  • Auto Scaling
  • Amazon EC2 Container Registry
  • Amazon EC2 Container Service
  • AWS Elastic Beanstalk
  • Amazon Elastic Compute Cloud
  • Elastic Load Balancing
  • AWS Lambda
  • Amazon Lightsail
  • Amazon DynamoDB
  • Amazon ElastiCache
  • Amazon Redshift
  • Amazon Relational Database Service
  • Amazon WorkSpaces
  • AWS CodeBuild
  • AWS CodeCommit
  • AWS CodeDeploy
  • AWS CodePipeline
  • AWS CodeStar
  • Amazon GameLift
  • AWS IoT
  • AWS Application Discovery Service
  • AWS CloudFormation
  • AWS CloudTrail
  • Amazon CloudWatch
  • Amazon CloudWatch Events
  • Amazon CloudWatch Logs
  • AWS Config
  • AWS マネージドサービス
  • AWS OpsWorks
  • AWS OpsWorks for Chef Automate
  • AWS Organizations
  • AWS Service Catalog
  • Amazon Simple Email Service
  • Amazon Simple Notification Service
  • Amazon Simple Queue Service
  • AWS Database Migration Service
  • AWS Server Migration Service
  • Amazon Cognito
  • AWS Device Farm
  • Amazon CloudFront
  • AWS Direct Connect
  • Amazon Route 53
  • Amazon Virtual Private Cloud
  • AWS Certificate Manager
  • Amazon Cloud Directory
  • AWS CloudHSM
  • AWS Directory Service
  • AWS Identity and Access Management
  • Amazon Inspector
  • AWS Key Management Service
  • AWS Security Token Service
  • AWS WAF
  • Amazon Elastic Block Store
  • Amazon Elastic File System
  • Amazon Glacier
  • Amazon Simple Storage Service
  • AWS Storage Gateway
  • AWS Personal Health Dashboard
  • AWS サポート

サポートされていないもの

サポートされていないものもありますので注意が必要です。

Amazon AppStream
Amazon AppStream 2.0
Amazon Connect
Amazon Lex
Amazon Mobile Analytics
Amazon Pinpoint
Amazon Rekognition
Amazon WorkMail
AWS Batch
AWS Greengrass
AWS Shield
AWS Snowball
AWS X-Ray
Amazon Chime
Amazon WorkSpaces Application Manager
AWS Artifact
AWS Mobile Hub
AWS Snowball Edge
AWS Snowmobile
AWS Trusted Advisor

便利なリンク先

こちらいろいろやっていらっしゃる方がいます

まとめ

  • これでアカウントのガバナンス、コンプライアンス、運用監査、リスク監査を可能になりました。
  • 案外かんたんなので、設定触ってみることをおすすめします!

続きを読む

Three.jsのかっこいいサンプルとAWSを連携させてみた

かっこいい画面が作りたかったので、やってみた備忘録。

やりたいこと

Three.jsがとてもかっこいいんですが、値の設定が静的なので、何とか動的に値を変えつつ画面に反映できないかな、、、と。このサンプルを使ってみます。

スクリーンショット 2017-05-14 19.08.58.png

イメージとしては、この画像で異常が検出されたところを赤くアラート表示したりできないかな、と。

実際の流れの整理

Githubからサンプルを持って来て、必要なものだけ抜き出します。

異常があったら表示する、という感じにしたいので、以前投稿したロジックを使います。

AWS LambdaでDynamoDBから取得した値に任意の集計をかける(グルーピング処理追加)

最新値を取得する中で、異常があったら対象の項目に変化をつけたいと思います。

とりあえずサンプルをAWS上で動かす

AWS上で動作させたいので、とりあえずこのサンプルをAWS上に乗せる必要があります。

  1. 必要なファイルの抽出(抽出結果などはこちらに。https://github.com/kojiisd/aws-threejs
  2. 適当な名前のバケットをS3に作成(今回は「aws-three」という名前にしました)
  3. ファイルのアップロード
  4. パーミッションの適用

パーミッションの適用は、とりあえず以下のコマンドで一括でつけました。実際の運用を考えると、もっと慎重にならないといけないところだとは思います。こちらのサイトを参考にさせてもらいました。

$ aws s3 ls --recursive s3://aws-three/ | awk '{print $4}' | xargs -I{} aws s3api put-object-acl --acl public-read --bucket aws-three --key "{}"

これで一旦サンプルがS3上で動くようになりました。

データの中身を理解する

実際にソースを読んで見ると、一つ一つのオブジェクトをどの様に格納しているかがわかります。

var table = [
    "H", "Hydrogen", "1.00794", 1, 1,
    "He", "Helium", "4.002602", 18, 1,
    "Li", "Lithium", "6.941", 1, 2,
    "Be", "Beryllium", "9.012182", 2, 2,
    :
    :

5つの要素で1セットとしてオブジェクトを表示している様です。元の実装がいいかどうかは置いておいて(^^;とりあえずこれを動的に変更できるようにします。

DBからの値をtableに反映する

データの構造自体はとても単純なので、1レコード5カラムのデータを持つテーブルを作成すれば、後々データの内容全てをDBから持ってくることも可能になりそうです。

DynamoDBで下記の様な単純なテーブルを作成します。もちろんAWS Consoleから作成可能ですが、Localで同じスキーマを作成したい場合はこんな感じで定義を流し込めばOK。

var params = {
    TableName: "test-three",
    KeySchema: [
        {
            AttributeName: "id",
            KeyType: "HASH"
        },
        {
            AttributeName: "timestamp",
            KeyType: "RANGE"
        }
    ],
    AttributeDefinitions: [
        {
            AttributeName: "id",
            AttributeType: "S"
        },
        {
            AttributeName: "timestamp",
            AttributeType: "S"
        }
    ],
    ProvisionedThroughput: {
        ReadCapacityUnits: 1,
        WriteCapacityUnits: 1
    }
};

dynamodb.createTable(params, function(err, data) {
    if (err) ppJson(err);
    else ppJson(data);
});

はい、無事出来ました。

スクリーンショット 2017-07-09 16.12.16.png

データの準備と投入

次にデータを流し込みます。こんな感じでpythonスクリプトでサクッと。実行する前に、クライアントのAWS認証設定が正しいことを確認してください。

data-insert.py
args = sys.argv

if len(args) < 4:
    print "Usage: python data-insert.py <FileName> <Region> <Table>"
    sys.exit()

dynamodb = boto3.resource('dynamodb', region_name=args[2])
table = dynamodb.Table(args[3])

if __name__ == "__main__":
    print "Data insert start."
    target = pandas.read_csv(args[1])
    for rowIndex, row in target.iterrows():
        itemDict = {}
        for col in target:
            if row[col] != None and type(row[col]) == float and math.isnan(float(row[col])) and row[col] != float('nan'):
                continue
            elif row[col] == float('inf') or row[col] == float('-inf'):
                continue
            elif type(row[col]) == float:
                itemDict[col] = Decimal(str(row[col]))
            else:
                itemDict[col] = row[col]
        print itemDict

        table.put_item(Item=itemDict)

    print "Data insert finish."

とりあえずこんなデータを用意しました。先ほどのスクリプトで投入します。

id,score,timestamp
"H",0,"2017-07-23T16:00:00"
"H",0,"2017-07-23T16:01:00"
"H",0,"2017-07-23T16:02:00"
"H",0,"2017-07-23T16:03:00"
"H",0,"2017-07-23T16:04:00"
"H",1,"2017-07-23T16:05:00"
"H",0,"2017-07-23T16:06:00"

無事投入できました。

$ python data-insert.py sample-data.csv us-east-1 test-three
Data insert start.
{'timestamp': '2017-07-23T16:00:00', 'score': 0, 'id': 'H'}
{'timestamp': '2017-07-23T16:01:00', 'score': 0, 'id': 'H'}
{'timestamp': '2017-07-23T16:02:00', 'score': 0, 'id': 'H'}
{'timestamp': '2017-07-23T16:03:00', 'score': 0, 'id': 'H'}
{'timestamp': '2017-07-23T16:04:00', 'score': 0, 'id': 'H'}
{'timestamp': '2017-07-23T16:05:00', 'score': 1, 'id': 'H'}
{'timestamp': '2017-07-23T16:06:00', 'score': 0, 'id': 'H'}
Data insert finish.

スクリーンショット 2017-07-23 17.33.30.png

API Gatewayの設定

絞り込み結果を得るためのLambdaを実行するRESTの口を設けます。API Gatewayを設定してPOST通信でデータが来た場合に該当のLambdaを実行するようにします。

スクリーンショット 2017-07-23 18.13.07_dec.png

画面からDynamoDBのデータを取得する

定期的に画面からDynamoDBのデータを取得します。今回はCognitoによるセキュアな通信は実装しません。他のページで色々と実装されている方がいるので、そちらを参考にして見てください。

処理の流れとしては、以下のような感じ。(S3からの取得処理が定期的に実行される)

S3 → API Gateway → Lambda → DynamoDB

API GatewayでSDK生成

API Gatewayの設定を以下にし、SDKを生成します。

  1. HTTPメソッドはPOSTを指定
  2. とりあえず認証などは今回はかけない

で、作成されたSDKを、本家のページを参考に埋め込みます。

API Gateway で生成した JavaScript SDK を使用する

以下のコードをScriptタグ部分に貼り付けました。

        <script type="text/javascript" src="../extralib/axios/dist/axios.standalone.js"></script>
        <script type="text/javascript" src="../extralib/CryptoJS/rollups/hmac-sha256.js"></script>
        <script type="text/javascript" src="../extralib/CryptoJS/rollups/sha256.js"></script>
        <script type="text/javascript" src="../extralib/CryptoJS/components/hmac.js"></script>
        <script type="text/javascript" src="../extralib/CryptoJS/components/enc-base64.js"></script>
        <script type="text/javascript" src="../extralib/url-template/url-template.js"></script>
        <script type="text/javascript" src="../extralib/apiGatewayCore/sigV4Client.js"></script>
        <script type="text/javascript" src="../extralib/apiGatewayCore/apiGatewayClient.js"></script>
        <script type="text/javascript" src="../extralib/apiGatewayCore/simpleHttpClient.js"></script>
        <script type="text/javascript" src="../extralib/apiGatewayCore/utils.js"></script>
        <script type="text/javascript" src="../extralib/apigClient.js"></script>

実際にAPI Gatewayにアクセスするスクリプトはこんな感じになります。

SDKを用いてAPI Gatewayにブラウザからアクセスしてみる

var apigClient = apigClientFactory.newClient();

var params = {
    // This is where any modeled request parameters should be added.
    // The key is the parameter name, as it is defined in the API in API Gateway.
    "Content-Type": "application/x-www-form-urlencoded"
};

var body = {

    "label_id": "id",
    "label_range": "timestamp",
    "id": [
    "H"
    ],
    "aggregator": "latest",
    "time_from": "2017-07-23T16:00:00.000",
    "time_to": "2017-07-23T16:06:00.000",
    "params": {
    "range": "timestamp"
    }
};


  var additionalParams = {
    // If there are any unmodeled query parameters or headers that must be
    //   sent with the request, add them here.
    headers: {
    },
    queryParams: {
    }
  };

  apigClient.rootPost(params, body, additionalParams)
      .then(function(result){
        // Add success callback code here.
        alert("Success");
        alert(JSON.stringify(result));
      }).catch( function(result){
        // Add error callback code here.
      });

パラメータ(body部)には「AWS LambdaでDynamoDBから取得した値に任意の集計をかける(グルーピング処理追加)」で必要になるJSONを渡します。

API GatewayにアクセスするにはCORSの設定が必要なので、AWS Consoleから設定します。この際Resourcesで変更したものはStaging環境に再デプロイをしないと追加で設定した項目は反映されないので注意(ハマった。。。)

「Enable CORS」から設定できます。

スクリーンショット 2017-09-10 10.56.00.png

実際に画面にアクセスすると、サーバから値が取れたことがわかります。(今回はAPI KEYなどはOFFにしていますが、実際に運用するとなったら、もちろん考慮が必要です。)

スクリーンショット 2017-09-10 11.03.04.png

スクリーンショット 2017-09-10 11.03.11_deco.png

無事値が取れました。ここまでくればもう一息です。

取得した値を元に画面に変更を加える

さて、先ほど取得できた値には「score」というものが入っています。この「score」値を元に画面のコンポーネントに変化を加えます。

持ってきたサンプルコードの中で色に影響を与えているのは以下のコードになります。

for ( var i = 0; i < table.length; i += 5 ) {

    var element = document.createElement( 'div' );
    element.className = 'element';
    element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';

全てのコンポーネントに対してrgbaで値を与えているので、ここを変更します。とはいえ、そのまま変更しようとしてもinit()メソッドを実行すると追加でコンポーネントが描画されてしまうので、描画後に変更が可能なように以下のようなidの埋め込みを行います。

var element = document.createElement( 'div' );
element.className = 'element';
element.id = table[ i ];                        // ここが追加したところ
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')';

これらの準備を前提として、処理の流れは以下とします。

  1. DynamoDBから取ってきたjson値をオブジェクトに変換。
  2. 一旦オブジェクト配列としてidとscore値を持たせるようにする。
  3. オブジェクト配列でループ処理を実装し、その中でscore値が「0」ではないidに対して、色の変更を実施する。

今回の準備を踏まえると、上記で対象となるのは「id: H」となります。DynamoDBからデータを取得するために、rootPost呼び出し後のコールバック処理を以下のように変更します。今回色変更の処理をシュッと実装するために、jQueryを読み込ませています。

apigClient.rootPost(params, body, additionalParams)
    .then(function(result){
        var resultObjArray= new Array();
        // Add success callback code here.
        var resultJson = JSON.parse(result.data);
        for (var index = 0; index < resultJson.length; index++) {
            var resultObj = new Object();
            resultObj.id = resultJson[index].id;
            resultObj.score = resultJson[index].score
            resultObjArray[index] = resultObj;
        }

        for (var index = 0; index < resultObjArray.length; index++) {
            var resultObj = resultObjArray[index];
            if (resultObj.score != 0) {
                $('#' + resultObj.id).css('background-color', 'rgba(255,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')')
            }
        }

    }).catch( function(result){
        // Add error callback code here.
        alert("Failed");
        alert(JSON.stringify(result));
    });

DynamoDBに格納されている値(検索期間の最新)を「1」に変更して画面を更新すると、無事赤くなりました。

スクリーンショット 2017-09-10 18.58.41.png

実行タイミングを任意にするためにボタンからのクリックイベントとして実装する

このままでもいいのですが、今のままだと最初のアニメーションが終わる前に色が変わるので、色が変わった感がありません。ですので一つボタンを追加してクリックしたら画面に値を反映する、という手法を取ろうと思います。

先ほどのrootPostを呼び出すプログラムを、画面に配置したボタンのイベントとして処理させます。

<button id="getData">Get Data from DynamoDB</button>

画面にはこんな感じでボタンを配置し、jQueryでイベントを登録します。

$(document).ready(function(){
    $("#getData").click(function() {
        apigClient.rootPost(params, body, additionalParams)
        .then(function(result){
            var resultObjArray= new Array();
            // Add success callback code here.
            var resultJson = JSON.parse(result.data);
            for (var index = 0; index < resultJson.length; index++) {
                var resultObj = new Object();
                resultObj.id = resultJson[index].id;
                resultObj.score = resultJson[index].score
                resultObjArray[index] = resultObj;
            }

            for (var index = 0; index < resultObjArray.length; index++) {
                var resultObj = resultObjArray[index];
                if (resultObj.score != 0) {
                    $('#' + resultObj.id).css('background-color', 'rgba(255,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')')
                }
            }

        }).catch( function(result){
            // Add error callback code here.
            alert("Failed");
            alert(JSON.stringify(result));
        });
    });
});

これで無事画面から操作することが可能になりました。ボタンを押すといい感じに「H」の要素が赤く光っています。

スクリーンショット 2017-09-10 23.08.28.png

まとめ

もともとかっこいいと思っていたThree.jsの画面をカスタマイズして監視画面(IoTとかのデバイス監視画面)のような機能にできないものか、と実装してみましたが、なんとかここまでたどり着けました。

DynamoDBからの取得間隔やIDの指定方法など、まだまだハードコーディングな部分はありますが、土台は出来上がったのであとはちょっとのカスタマイズで実際に使えそうなところまではイメージが持てました。元々の実装の仕組みも理解できたので、条件が合えば文言を変えるなども実現できそうです。

やっぱり業務で使うものもかっこいい画面でないとね、と思うこの頃です。
(GithubのコードはAPI GatewayのSDKさえ埋め込めば動くような作りにしています)

https://github.com/kojiisd/aws-threejs

続きを読む

RaspberryPiに接続したセンサの情報をCloud Watchでモニタリングする

構想

RaspberryPiから送信したセンサの情報をAWSIoTを使って、DynamoDBに格納する。
格納した情報をLambdaを使ってCloudWatchに流し、モニタリングする。

AWS IoTにモノを登録する

まずはAWSIoTに接続するモノを登録する。

接続をクリック

1.png

デバイスの設定の今すぐ始めるをクリック

2.png

手順を確認して今すぐ始めるをクリック

3.png

接続するモノの環境を選択

今回はRaspberryPiを使用するのでOSはLinux、言語はPythonを選択する。

4.png

モノの名前を設定する

自分の好きな名前で良い。

5.png

接続キットをダウンロードする

今回登録したモノ専用の証明書などが同梱されているので、取扱に気をつける。

6.png

画面のコマンドを実行する

以前の記事を参考にRaspberryPiのPython環境を整えてから、画面に表示されたコマンドを実行してみる。

7.png

$ ./start.sh

2017-09-10 14:30:53,709 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Try to put a publish request 2 in the TCP stack.
2017-09-10 14:30:53,710 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Publish request 2 succeeded.
Received a new message: 
b'New Message 0'
from topic: 
sdk/test/Python
--------------


2017-09-10 14:30:54,713 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Try to put a publish request 3 in the TCP stack.
2017-09-10 14:30:54,714 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Publish request 3 succeeded.
Received a new message: 
b'New Message 1'
from topic: 
sdk/test/Python
--------------


2017-09-10 14:30:55,717 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Try to put a publish request 4 in the TCP stack.
2017-09-10 14:30:55,718 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Publish request 4 succeeded.
Received a new message: 
b'New Message 2'
from topic: 
sdk/test/Python
--------------

上記のようなメッセージが流れ出したら成功。

送信内容を変更する

上記で実行したサンプルプログラムでは、永遠とNew Message Xというメッセージを送り続けているだけなので、これを温度/湿度センサ(DHT11)の出力に変える。

まずは先程ダウンロードしたconnect_device_packageの直下にhttps://github.com/szazo/DHT11_Python.gitをクローンする。

そして、./start.shで呼び出されているbasicPubSub.pyの中身を改変する。

connect_device_package/aws-iot-device-sdk-python/samples/basicPubSub/basicPubSub.py

'''
/*
 * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
 '''

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
from DHT11_Python import dht11
import RPi.GPIO as GPIO
import sys
import logging
import time
import argparse
import datetime
import json


# Custom MQTT message callback
def customCallback(client, userdata, message):
    print("Received a new message: ")
    print(message.payload)
    print("from topic: ")
    print(message.topic)
    print("--------------\n\n")

# Read in command-line parameters
parser = argparse.ArgumentParser()
parser.add_argument("-e", "--endpoint", action="store", required=True, dest="host", help="Your AWS IoT custom endpoint")
parser.add_argument("-r", "--rootCA", action="store", required=True, dest="rootCAPath", help="Root CA file path")
parser.add_argument("-c", "--cert", action="store", dest="certificatePath", help="Certificate file path")
parser.add_argument("-k", "--key", action="store", dest="privateKeyPath", help="Private key file path")
parser.add_argument("-w", "--websocket", action="store_true", dest="useWebsocket", default=False,
                    help="Use MQTT over WebSocket")
parser.add_argument("-id", "--clientId", action="store", dest="clientId", default="basicPubSub", help="Targeted client id")
parser.add_argument("-t", "--topic", action="store", dest="topic", default="sdk/test/Python", help="Targeted topic")

args = parser.parse_args()
host = args.host
rootCAPath = args.rootCAPath
certificatePath = args.certificatePath
privateKeyPath = args.privateKeyPath
useWebsocket = args.useWebsocket
clientId = args.clientId
topic = args.topic

if args.useWebsocket and args.certificatePath and args.privateKeyPath:
    parser.error("X.509 cert authentication and WebSocket are mutual exclusive. Please pick one.")
    exit(2)

if not args.useWebsocket and (not args.certificatePath or not args.privateKeyPath):
    parser.error("Missing credentials for authentication.")
    exit(2)

# Configure logging
logger = logging.getLogger("AWSIoTPythonSDK.core")
logger.setLevel(logging.DEBUG)
streamHandler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)

# Init AWSIoTMQTTClient
myAWSIoTMQTTClient = None
if useWebsocket:
    myAWSIoTMQTTClient = AWSIoTMQTTClient(clientId, useWebsocket=True)
    myAWSIoTMQTTClient.configureEndpoint(host, 443)
    myAWSIoTMQTTClient.configureCredentials(rootCAPath)
else:
    myAWSIoTMQTTClient = AWSIoTMQTTClient(clientId)
    myAWSIoTMQTTClient.configureEndpoint(host, 8883)
    myAWSIoTMQTTClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath)

# AWSIoTMQTTClient connection configuration
myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTClient.configureOfflinePublishQueueing(-1)  # Infinite offline Publish queueing
myAWSIoTMQTTClient.configureDrainingFrequency(2)  # Draining: 2 Hz
myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10)  # 10 sec
myAWSIoTMQTTClient.configureMQTTOperationTimeout(5)  # 5 sec

# Connect and subscribe to AWS IoT
myAWSIoTMQTTClient.connect()
myAWSIoTMQTTClient.subscribe(topic, 1, customCallback)
time.sleep(2)

# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

# read data using pin 2
instance = dht11.DHT11(pin=2)

while True:
    result = instance.read()
    if result.is_valid():
        data = {'timestamp': str(datetime.datetime.now()),
                'clientId': clientId,
                'temperature': result.temperature,
                'humidity': result.humidity
                }
        myAWSIoTMQTTClient.publish(topic, json.dumps(data), 1)
        time.sleep(1)

DHT11の結果をディクショナリ形式で格納し、myAWSIoTMQTTClient.publish(topic, json.dumps(data), 1)json.dumpsしてパブリッシュする。

また、start.shも次の設定で扱いやすいように、
basicPubSub.pyの呼び出しオプションを追加する。

start.sh
# stop script on error
set -e

# Check to see if root CA file exists, download if not
if [ ! -f ./root-CA.crt ]; then
  printf "\nDownloading AWS IoT Root CA certificate from Symantec...\n"
  curl https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem > root-CA.crt
fi

# install AWS Device SDK for Python if not already installed
if [ ! -d ./aws-iot-device-sdk-python ]; then
  printf "\nInstalling AWS SDK...\n"
  git clone https://github.com/aws/aws-iot-device-sdk-python.git
  pushd aws-iot-device-sdk-python
  python setup.py install
  popd
fi

# run pub/sub sample app using certificates downloaded in package
printf "\nRunning pub/sub sample application...\n"
python aws-iot-device-sdk-python/samples/basicPubSub/basicPubSub.py -e a33hl2ob9z80a1.iot.us-west-2.amazonaws.com -r root-CA.crt -c RaspberryPi01.cert.pem -k RaspberryPi01.private.key -t dht11

追加したのは-t dht11という部分で、
これはトピックの名前を設定する部分である。

この名前を設定しておくことで、受信したメッセージがどのトピックのものか判別がつき、
AWSIoT側で受信後の処理を振り分けることができる。

AWS IAMで必要な権限を持ったロールを作成しておく

本来は機能毎にロールを分けて必要以上に権限を持たせないのが良いが、
面倒くさいので今回は一つのロールに全て持たせる。
(どの権限が必要になるかわからなかったので、多めにアタッチした。
あとで勉強しておく必要あり。)

  • CloudWatchFullAccess
  • AmazonDynamoDBFullAccess
  • AmazonDynamoDBFullAccesswithDataPipeline
  • AWSLambdaDynamoDBExecutionRole
  • AWSLambdaInvocation-DynamoDB

AWSIoTでルールを設定する

ルールをクリック

8.png

右上の作成ボタンをクリック

9.png

名前と説明を設定する

10.png

どのメッセージにルールを適用するかを設定する

属性は*、トピックフィルターは先程、basicPubSub.pyの呼び出しに追加した-tオプションの値を入力する。

こうすることで-tオプションで指定した名前と一致したもののみにこのルールを適用することができる。

11.png

メッセージに対してどのような処理を行うか決める。

今回はまず、AWS IoTで受信したメッセージをDynamoDBに送り込みたいので、そのように設定する。

12.png

13.png

新しいリソースを作成するをクリック。

14.png

DHT11のデータを格納するテーブルを作成する

15.png

16.png

こんな感じに設定して、テーブルを作成する。

テーブルを選択する

もとの画面を戻りテーブルを選択する。

17.png

ハッシュキーの値とレンジキーの値が空欄になっているので、
そこには上記のように${timestamp}などを指定する。

なお、メッセージとして送信されるjsonの各メンバーには${member}という形でアクセスできる。

ロールを指定する

18.png

ここには前もって用意しておいたロールを指定する。

19.png

これでアクションが追加できたので、ルールを作成するをクリックしてルール作成は完了。

確認

実行してみて、DynamoDBにデータが入るか確認する。

$ ./start.sh
 .
 .
 .

2017-09-09 23:32:23,411 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Try to put a publish request 1394 in the TCP stack.
2017-09-09 23:32:23,412 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Publish request 1394 succeeded.
Received a new message: 
b'{"timestamp": "2017-09-09 23:32:23.409885", "clientId": "basicPubSub", "temperature": 27, "humidity": 70}'
from topic: 
dht11
--------------


2017-09-09 23:32:25,715 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Try to put a publish request 1395 in the TCP stack.
2017-09-09 23:32:25,716 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Publish request 1395 succeeded.
Received a new message: 
b'{"timestamp": "2017-09-09 23:32:25.714083", "clientId": "basicPubSub", "temperature": 27, "humidity": 70}'
from topic: 
dht11
--------------

20.png

うまくいってるようだ。
NとかSとかは、NumberStringの略だそう。
最初NorthSouthかと思ってなんのこっちゃってなったけど。

DynamoDBからCloudWatchにデータを流す

トリガーの作成

長いけどあと一息。
DynamoDBのテーブルの画面から、トリガー => トリガーの作成 => 新規関数と選択。

21.png

22.png

トリガーの設定

ステップ1でdynamodb-process-streamが選択された状態で、ステップ2から始まるが、ステップ1に一旦戻る。

23.png

今回はdynamodb-process-streamのPython3版を選択した。

24.png

28.png

テーブルを指定し、開始位置は水平トリムにする。

25.png

トリガーの有効化は後でするので、チェックを入れずに次へ。

関数を定義する

26.png

適当に基本情報を埋める。

27.png

上図のように関数を定義できるので、以下のコードに変更する。

from __future__ import print_function

import json
import boto3
from decimal import Decimal

print('Loading function')


def lambda_handler(event, context):
    # print("Received event: " + json.dumps(event, indent=2))
    client = boto3.client('cloudwatch') 
    for record in event['Records']:
        # print(record['eventID'])
        # print(record['eventName'])
        # print("DynamoDB Record: " + json.dumps(record['dynamodb'], indent=2))
        print(record['dynamodb']['NewImage'])
        print(record['dynamodb']['NewImage']['timestamp']['S'])
        response = client.put_metric_data(
            Namespace='dht11',
            MetricData=[
                {
                    'MetricName': 'temperature',
                    'Dimensions': [
                        {
                            'Name': 'clientId',
                            'Value': record['dynamodb']['NewImage']['payload']['M']['clientId']['S'],
                        },
                    ],
                    'Value': Decimal(record['dynamodb']['NewImage']['payload']['M']['temperature']['N']),
                    'Unit': 'None'
                },
            ]
        ) 
        response = client.put_metric_data(
            Namespace='dht11',
            MetricData=[
                {
                    'MetricName': 'humidity',
                    'Dimensions': [
                        {
                            'Name': 'clientId',
                            'Value': record['dynamodb']['NewImage']['payload']['M']['clientId']['S'],
                        },
                    ],
                    'Value': Decimal(record['dynamodb']['NewImage']['payload']['M']['humidity']['N']),
                    'Unit': 'Percent'
                },
            ]
        )

編集したら、確認をして関数を保存する。

トリガーの有効化

DynamoDBのトリガーの画面に戻ると、作成した関数が出現している。
作成した関数を選択肢してトリガーの編集ボタンをクリック。

29.png

トリガーを選択してトリガーを有効化する。

30.png

CloudWatchでモニタリングする

あとはCloudWatch側で表示の設定をすれば、温度や湿度の推移が見えるようになる。
ダッシュボード => ダッシュボードの作成でダッシュボードを作成した後、
ウィジェットの追加からtemperaturehumidityを検索する。

Lambdaで作成されたメトリクスが出てくるので、それを選択すると・・・

31.png

無事、確認できる。

まとめ

  • AWSIoT => DynamoDB => Lambda => CloudWatchの連携で、DHT11センサの値をモニタリングすることができた。

続きを読む

DynamoDB LocalとCognitoを併用する場合の注意点

先に結論

DynamoDB LocalとCognitoを併用する場合は、必ず別々のendpointを定義する。
特にCognito側のendpointを書く事例はほとんど見かけないので忘れがち。

utils/auth.js
import AWS from 'aws-sdk'

const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({
  apiVersion: '2016-04-18',
  region: 'ap-northeast-1',
  endpoint: `https://cognito-idp.ap-northeast-1.amazonaws.com/`,
})

export default function auth(AccessToken) {
  return new Promise((resolve, reject)=>{
    cognitoIdentityServiceProvider.getUser({AccessToken}, (err, data)=>{
      if (err) return reject(err)
      return resolve(data)
    })
  })
}
utils/dynamoose.js
import dynamoose from 'dynamoose'

dynamoose.AWS.config.update({region:'ap-northeast-1'})
if ( process.env.NODE_ENV === 'dev' ) {
  dynamoose.AWS.config.endpoint = new dynamoose.AWS.Endpoint('http://localhost:8000')
}
export default dynamoose

前提

  • 今回のケースではNode.jsのdynamooseというORMを使っているが、その他のケースでも発生する可能性があるので念のためメモ。
  • ServerlessFrameworkを使用しているが、それは今回の件とは関係ないはず?

経緯

  • ServerlessFrameworkのプラグイン serverless-dynamodb-local を使って、DynamoDB Localを起動し、Cognitoでユーザー管理をしようとした
  • アプリケーションを実行すると、1度目は成功するが2度目で必ず下記エラーが出ることに気づいた
MissingAuthenticationToken: Request must contain either a valid (registered) AWS access key ID or X.509 certificate.
  • AccessTokenが間違えてるのかなと思ったが合ってた
  • リージョン指定が間違えてるのかなと思ったが合ってた
  • DynamoDBの向き先をローカルではなく本番に向けたらエラーが出なかった
  • 向き先をローカルに戻して、CognitoのSDK側にendpointを指定したらエラーが出なかった

推察

  • 恐らくCognito User Poolは内部でDynamoDBを使っていて、DynamoDBのendpointを変えるとCognitoのSDK側にも影響を及ぼしてしまうのではないか?
  • このあたり詳しい人、もしくは中の人、今回の件について何かあれば教えてもらえたら嬉しいです

続きを読む

LambdaでDynamoDBのデータを操作する(Node&Python)

はじめに

最近はもっぱらIoT関連の案件を担当しています。
AWSを使っているので、DynamoDBにデバイスからのデータを溜め込んだり、そのデータを取得して可視化したりなどすることが多く、その際にはLambdaが大活躍しています。
主にNode.jsを使っており、案件に応じてPythonでも実装をしていて、自分の中でテンプレート化してきたなーと思ったのでまとめておこうっとな。

DynamoDBにデータをPutする

主にデバイスから受けとったデータをDynamoDBに書き込みます。
本来であればデータを加工する必要がありますが、そこはデバイスによって多種多様ですので割愛します。
ここで重要なのは、PartitionKeyとSortKeyの指定です。
ここを間違えてしまうとエラーを吐いてきます。(Key情報が間違ってるぜ!的な感じ)

Node.js

const AWS = require("aws-sdk");
const dynamoDB = new AWS.DynamoDB.DocumentClient({
  region: "ap-northeast-1" // DynamoDBのリージョン
});

exports.handler = (event, context, callback) => {
  const params = {
    TableName: "table-name" // DynamoDBのテーブル名
    Item: {
      "PartitionKey": "your-partition-key-data", // Partition Keyのデータ
      "SortKey": "your-sort-key-data", // Sort Keyのデータ
      "OtherKey": "your-other-data"  // その他のデータ
    }
  }

  // DynamoDBへのPut処理実行
  dynamoDB.put(params).promise().then((data) => {
    console.log("Put Success");
    callback(null);
  }).catch((err) => {
    console.log(err);
    callback(err);
  });
}

Python

import boto3

def lambda_handler(event, context):
  try:
    dynamoDB = boto3.resource("dynamodb")
    table = dynamoDB.Table("table-name") # DynamoDBのテーブル名

    # DynamoDBへのPut処理実行
    table.put_item(
      Item = {
        "PartitionKey": "your-partition-key-data", # Partition Keyのデータ
        "SortKey": "your-sort-key-data", # Sort Keyのデータ
        "OtherKey": "your-other-data"  # その他のデータ
      }
    )
  except Exception as e:
        print e

DynamoDBのデータを取得する(query)

主にDynamoDB内のデータを取得する際にはqueryを使います。
PartitionKeyだけでなくSortKeyもDynamoDBにあれば、ScanIndexForwardをfalseにLimitで取得した件数を指定して、降順(最新)のデータから固定の件数を取得することができます。
よく多いパターンとしては、あるDeviceID(PartitionKey)が送信してきたデータを最新から○○件取得したいなどのパターンがあります。

Node.js

const AWS = require("aws-sdk");
const dynamoDB = new AWS.DynamoDB.DocumentClient({
  region: "ap-northeast-1" // DynamoDBのリージョン
});

exports.handler = (event, context, callback) => {
  const params = {
    TableName: "table-name" // DynamoDBのテーブル名
    KeyConditionExpression: "#PartitionKey = :partition-key-data and #SortKey = :sort-key-data", // 取得するKey情報
    ExpressionAttributeNames: {
      "#PartitionKey": "your-partition-key", // PartitionKeyのアトリビュート名
      "#SortKey": "your-sort-key" // SortKeyのアトリビュート名
    },
    ExpressionAttributeValues: {
      ":partition-key-data": "your-partition-key-data", // 取得したいPartitionKey名
      ":sort-key-data": "your-sort-key-data" // 取得したいSortKey名
    }
    ScanIndexForward: false // 昇順か降順か(デフォルトはtrue=昇順)
    Limit: 1 // 取得するデータ件数
  }

  // DynamoDBへのquery処理実行
  dynamoDB.query(params).promise().then((data) => {
    console.log(data);
    callback(data);
  }).catch((err) => {
    console.log(err);
    callback(err);
  });
}

Python

import boto3
from boto3.dynamodb.conditions import Key

def lambda_handler(event, context):
  try:
    dynamoDB = boto3.resource("dynamodb")
    table = dynamoDB.Table("table-name") # DynamoDBのテーブル名

    # DynamoDBへのquery処理実行
    queryData = table.query(
      KeyConditionExpression = Key("your-partition-key").eq("your-partition-key-data") & Key("your-sort-key").eq("your-sort-key-data"), # 取得するKey情報
      ScanIndexForward = false, # 昇順か降順か(デフォルトはtrue=昇順)
      Limit: 1 # 取得するデータ件数
    )
    return queryData
  except Exception as e:
        print e

さいごに

DynamoDBにはよくPutとqueryでデータを扱うことが多いので二つの方法についてまとめました。
場合によってはPutじゃなくてUpdateでなければならなかったり、とりあえず全件取得したいということきはscanを使ったりします。
ちゃんとLambdaにDynamoDBを操作できるポリシーを持たせておくのを忘れずに!
またquery実行時にときたま発生するのですが、「エラーが吐かれずに処理が完了したように見えるのに、DynamoDBにデータが入ってない!」見たいなことがあります。そんなときはタイムアウトを疑ってください。デフォルトでは3秒なので、queryで取得するデータの件数が多い場合はタイムアウトしてしまいます。
これはLambdaの中で使うように書きましたが、普通にSDKを使っているだけなので、WebアプリケーションやSDKを積めるデバイスからデータを扱う場合でも、上記のソースコードを改造すれば使えると思います。

余談ですが、Node書いて、Python書いてとしていると文法等がこんがらがってしまうのが最近の悩みのひとつです・・・

(ソースコードに汚い部分がありましたらご容赦ください・・・)

ではまた!

続きを読む