Serverless FrameworkでEC2をスケジュール起動/停止するテンプレート(Lambda[java])

Serverless Framework

はじめに

  • コンテナ付いてる昨今は、久しくServerless Framework触って無かったのですが、EC2を8時に起動して、20時半に停止する要件が浮上したので、サクッとslsで作りました。
  • ソースはGithubで公開してます。
  • 至極簡単な内容なので、速攻実現出来ると思ってます。

環境のセットアップ

Serverless FrameworkでDeploy

ソースの取得

  • 以下のGithubからソースを取得します。
$ git clone https://github.com/ukitiyan/operation-ec2-instance.git

STS(Eclipse)にインポート

  • STSを起動して、Project Explorer -> 右クリック -> Maven -> Existing Maven Projectsで先程Githubから取得した「operation-ec2-instance」フォルダを選択します。

serverless.yml の修正 + Build

  • serverless.ymlのL37 周辺の設定を適宜修正します。

    • rate: AWS Lambda – Scheduled EventのCron書式
      を参考に UTC で記載
    • instanceId: 対象インスタンスのinstanceIdを記載
    • scheduleは、縦に増やせるので複数インスタンスに対応できます。(それを踏まえて環境変数でinstanceIdを指定してません)
serverless.yml
- schedule:
    rate: cron(30 11 * * ? *)
    input:
      goal: stop
      instanceId: i-XXXXXXXXXXXXXXXXX
- schedule:
    rate: cron(0 23 * * ? *)
    input:
      goal: start
      instanceId: i-XXXXXXXXXXXXXXXXX
  • プロジェクトを右クリック -> Run As -> Maven Install でビルドします。
  • target配下にoperation-ec2-instance.1.0.0.jarが出来上がります。

Deploy

  • 例のごとく、以下のコマンド一発です。
$ cd operation-ec2-instance
$ serverless deploy -v
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
・
・
Serverless: Stack update finished...
Service Information
service: operation-ec2-instance
stage: prod
region: ap-northeast-1
api keys:
  None
endpoints:
  None
functions:
  aws-java-maven-prod-hello: arn:XXXXXXXX
  • 以下のJsonでコンソールから test するか、設定時間になるまで待って、問題ないか気にしておきましょう。
{
  "goal": "stop",
  "instanceId": "i-XXXXXXXXXXXXXXXXX"
}

まとめ

  • まぁ、やっつけですが。速攻実現出来ました。
  • STSで完結するのが、アプリ屋にとっては本当うれしいです。

続きを読む

AWS初心者がインフラ設計/構築を理解するために、まず最初におさえるべき専門用語 15選

【 はじめに 】 ※現在絶賛執筆中!

~ 現段階 (2017/09/25) では、AWSサービスのことをほとんど知らない ~

今回、AWS初心者の僕がインフラ設計/構築の担当者になった。何事も経験だ!」と、自分から手を挙げたものの、
覚えることが多すぎやしないか?と思いつつ、これも自分の伸びしろだと、、まだまだ伸びる証拠だと
ポジティブに捉えて(本田風)
、絶賛AWSサービスと格闘する毎日を送っている自称ITエンジニアの奮闘記である。

【 現状の課題とその解決策 】

作業を開始してから2週間程度が経過した時点で、このまま場当たり的に知識を身に着けて行くのは、
かなり効率が悪いということに気が付いた。( 自分で言うのも何だが遅すぎないか? :man_tone5: )

もちろん現場で起こっている課題を解決しながら、
場当たり的に問題解決能力を知識を身に着けていくことも重要だと思う。

しかしながら、この局面 (AWSのサービスを全く知らない。インフラに関しての知識はゼロ。) においては、
クラウドの基礎知識を体系的に一からきちんと知識を身に着けていく戦法が有効
だと考える。

理由は、下記の通り。


  • 各単語の関係性についても合わせて学習することにより、単語の忘却曲線をより緩やかにすることができる。
    現状の課題 (1) : 単発だと結構すぐに忘れてしまう。
  • クラウドの全体像を把握するスピードが早くなるので、効率よく知識を吸収することができる。
    現状の課題 (2) : 基礎知識を短期間で習得できるので、後々応用が効きやすい。
  • いきなりAWSコンソール画面を触って間違えてしまうといったような無駄が無くなる。
    現状の課題 (3) : 基礎がないので、想定していないミスをするケースも。。(痛い目に合うのも大事だけどw)

上記のような理由より、自分の知識を蓄積し、常にアップデートしていくため手段 (備忘録) として、
Qiitaに思考プロセス等を書き残していこうと思う。

(他の人がこれを読むことで勉強になるかどうかは分からないですが、、。)

【 対象読者のみなさん 】

  • 業務でインフラを担当することになったけど、どうしたら良いのかイマイチ分からない人
  • インフラ構成図を見て一通り内容を理解しておきたい人
  • AWSインフラに興味があるけど、触ったことがない人

etc ..

【 お願い 】

もし、本記事の内容に「誤り・誤字」があれば、遠慮なくお気軽にご指摘いただければ幸いです。

それでは、AWSでのインフラ構築担当になってまず最初に覚えたワード15選について、
筆者が参考にしたサイト、各概念の分かりやすい表現をPickUp:point_up:!しながら、
ご説明させていただこうと思います。

読んでいただいた方のお役に立てるような記事にしたいと思っていますので、
どうぞ最後までお付き合いください
:grinning: !!!

【 本編 】AWS初心者がインフラ設計/構築を理解するために、まず最初におさえるべき専門用語 15選

まずは、「AWSって何なの?」ってところから。
  
amazon-web-services-logo-large1-e1334297880876.png

00. AWSの基本概念

AWSは、「Amazon Web Service」の略なんだけど、AWSとは?
と何も見ずに語れるほどの知識はないので、この部分に関しては後日アップデートする予定です。

[ 2017/10/13(Fri)時点での、筆者のAWSへ対する認識 ]

クラウドコンピューティングのリソースを提供していて、その上に様々なサービスを乗っけて、
誰でも簡単に?使った分だけの費用(従量課金)でインフラ環境を構築できるサービスってイメージ。

クラウドコンピューティングのリソースというのは、
世界各地にあるAWSが保有しているデータセンターのことなんだけど、、

まず最初に、

どの地域 [リージョン(Region)] のコンピューティングリソースを使用するのか?

ということを決めないといけない。

1. リージョン (Region)

リージョン(Region) 自体は「範囲、領域」を持つ英単語 :point_up:
Amazon クラウドコンピューティングリソース世界各地の多くの場所でホストされており、
これらの世界各地の拠点、物理的に離れている領域のことリージョン(Region)と言います。

2017/10/12(木)時点で、世界中に16個ものリージョン(Region)が存在します。

(以下、「AWS公式サイト」より引用。)

image.png

※今後、さらに5つのリージョンが追加される予定みたいです。(2017/10/12時点)

・中国
・フランス
・香港
・スウェーデン
・米国 (2番目のAWS GovCloudリージョン)

初めてAWSサービスに触れられる方は、とりあえず何も考えずに、
アジアパシフィック(東京リージョン)を選ばれることをお勧めいたします。

※別途リージョン選択についての細かな部分について紹介する記事を書こうと思います。

[参考サイト]


各リージョン内にはかならず2つ以上のアベイラビリティーゾーン (≒データセンター)と呼ばれる拠点があります。

続いては、そのアベイラビリティーゾーンについてご紹介したいと思います。

2. アベイラビリティーゾーン (AZ : Availability Zone)

アベイラビリティーゾーンを物凄く分かりやすく、簡単に言うと、、。

東京リージョン(物理的に離れた地域)= 東京という都市(場所)に、3つの独立したデータセンター(拠点)が存在し、
この各々のデータセンターのことを「アベイラビリティーゾーン(AZ)」と言います。

AWS.jpg

* 1つのリージョン内に2つ以上のアベイラビリティーゾーン(データセンター)が存在するので、
インフラ設計をする際は、複数のアベイラビリティーゾーン(Multi-AZ)を活用することにより、
構築するインフラ/アプリケーションの耐障害性を向上させることができます。

「リージョンとアベイラビリティゾーンに関する概念について」

各リージョンは完全に独立しています。各アベイラビリティーゾーンも独立していますが、
同じリージョン内のアベイラビリティーゾーン同士は低レイテンシーのリンクで接続されています。

リージョンとアベイラビリティーゾーン」より引用

3. VPC (Virtual Private Cloud)

AWSクラウドの論理的に分離した領域 (セクション) を、誰でも簡単に用意することができます。

下図のようなイメージです。

VPC.jpg

この VPC (Virtual Private Cloud) を活用すると自分で定義した仮想ネットワーク内
AWSリソース (ex. EC2,RDS) を起動させることができます。

VPC (Virtual Private Cloud) のIPアドレスは、以下規則で指定することができます。

  • VPC全体で1つのIPアドレスを持つ
  • サブネットでIPアドレス空間を分割する

[ ※注意 ]
ネットワークアドレスは作成後変更不可なので、あらかじめ20ビット以下など、
ある程度のレンジを持つアドレスを設定しておくのが無難です。

[参考サイト]

4. サブネット(Subnet [Public, Private])

サブネットとは、大きなネットワーク (≒VPC) を
複数の小さなネットワークに分割して管理する際の管理単位となる小さなネットワーク (≒サブネット) のこと。

※下図のようなイメージ。

Subnet.jpg

自分で定義したネットワーク(VPC)を複数のネットワークに分割するために使用します。

具体的には、各インスタンスの役割ごとにグループ化 (サブネットに分割) し、
ルートテーブルをアタッチする際などに使われることが多い (きめ細やかなアクセスコントロールが可能) です。

[ パブリックサブネット ][ プライベートサブネット ] の違いについて


まずは、下図をご覧ください!

Subnet2.jpg

上図の通り、インターネットからVPCインスタンスに接続する ためには、
インターネットゲートウェイ (次項で説明) を用意する必要があります。
そして、VPCネットワーク内にあるルーターを介して、各サブネット内のインスタンスへ通信が行われます。

この時、各サブネットにアタッチされているルートテーブル (経路制御表) の内容に沿って、
インターネットからのアクセスを許可するのか、許可しないのかを判断します。

上記の違いで、パブリックサブネット, プライベートサブネットの区別をしています。

  • インターネット(外部ネットワーク)からのアクセスを許可したサブネットを「パブリックサブネット
  • VPC内部の通信のみ許可しているサブネットを「プライベートサブネット

[参考サイト]

5. インターネットゲートウェイ (Internet Gateway)

インターネットゲートウェイは、VPCのインスタンスとインターネットとの間の通信を可能にする
VPCコンポーネント。

こちらのインターネットゲートウェイの役割大きく2つあります。


1. みなさんが作成したVPCネットワークのルートテーブルに、
インターネットへルーティングが可能な宛先を追加すること。

2. パブリックIPv4アドレスが割り当てられているインスタンスに対して、
ネットワークアドレス変換(NAT)を行うこと。


[参考サイト]

6. デフォルトゲートウェイ (Default Gateway)

所属するLANなどの内部ネットワーク (AWS上のサブネット) から、
外部にあるネットワーク(他のサブネットもしくは、インターネット)に通信を行う場合の
出入り口の役割を果たすよう設定されているルーターやコンピューターのことです。

デフォルトゲートウェイは、
ネットワーク上でプロトコル(規約)が異なる複数のデータを相互に変換し通信を可能
にします。

[参考サイト]

7. ルーター(Router)

IP(Internet Protocol)で通信する端末は、まず最初に通信相手が自分と同じネットワーク(同一サブネット内)に
属する端末かどうかを調べ、自身の属するネットワーク外への通信であれば、ルーター(Router)を経由して
通信を行います。

[参考サイト]

8. ルートテーブル (経路制御表:ルーティングテーブル)

ルーターや端末が保持するパケットの配送先に関する経路情報。

VPCの各サブネットをルートテーブル

ルートテーブルの生成・管理方式には、下記2種類が存在します。


・Static Routing (スタティックルーティング)

経路情報を各ルーター内に手動で設定する手法で、
この経路情報は基本的にルート・テーブルより消えることがありません。

・Dynamic Routing (ダイナミックルーティング)

RIP(Routing Information Protocolo)」「OSPF(Open Shortest Path First)
BGP(Border Gateway Protocol)」などのルーティング・プロトコルを用いて、
ルーターが経路情報を自動的に学習する手法で、この経路情報は動的に更新されます。


[参考サイト]

9. NAT (NAT Gateway)

NATとは、Network Address Translationの略称であり、IPアドレスを変換する技術です。
一般的には、プライベートIPアドレスをグローバルIPアドレスに変換する技術とされています。

ex.
企業LAN内のクライアントPCがインターネットに接続する場合に、プライベートIPアドレスを
グローバルIPアドレスに変換する必要があります。

この時に必要になる仕組みが、NAT(NAT:Network Address Translation)です。

10. 踏み台(Bastion)サーバー

メンテナンスの為の接続経路用途で用意されるサーバーのことを指します。

具体的には、アプリサーバー自体が外部(インターネット)から直接SSH接続を受け付けること自体、セキュリティの観点からもよろしくないので、外部からのSSH接続は、アプリサーバーとは別の専用サーバーが受け付けるべきです。そして、そのサーバーからアプリサーバーにSSH接続するといった二段階接続の構えを取ることでセキュアな環境を実現することができます。この時に用意するサーバーが上記の踏み台(Bastion)サーバーとなります。

また、踏み台サーバーは管理者が使用する時間帯以外は停止状態にしておくことにより、
部外者が勝手に侵入するといった心配もなくなるので、セキュアな構成を実現できる仕組みとなります。

(以下、近日中アップデート予定)

11. セキュリティグループ

異なるセキュリティグループに属するインスタンスと通信を行う際に、
トラフィックの制御を行う仮想ファイアウォールとして機能します。

※セキュリティグループはサブネット単位ではなく、インスタンス単位で設定する必要があります。

また、インスタンスを起動 ( 新規作成 ) する際には、対象のインスタンスと1つ以上のセキュリティグループとの
関連付けが必須となります。

[参考サイト]

12. ElasticIP

13. VPCエンドポイント

[参考サイト]

01. アクセスポリシー

AWSでのアクセスポリシーオプション (アクセス制御 ) は、大きく2つに分類されます。

  • リソースベース(S3のバケットやオブジェクトに対して)のポリシー
  • ユーザーポリシー

どのようなアクセス制御をしたいかによって、
リソース自体に閲覧等の権限を付けるもの (リソースベースのポリシー)
ユーザー単位で操作可能範囲を決めるもの (ユーザーポリシー)など、
柔軟性の高いアクセスポリシー設定が可能です。

本記事では、それぞれ代表的なものを一つずつ紹介したいと思います。

14. IAM (Identity and Access Management) 【 ユーザーポリシー 】

ユーザーに対して、AWSへのアクセスを安全に制御するための仕組み。 AWSコンソール画面から設定が可能で、
IAM (Identity and Access Management) の利用自体が課金対象になることはありません。 (基本無料)

IAMを使用することにより、下記のようなケースでアクセス制御を行うことができます。

  • どのユーザーがAWSリソースを使用できるか?
  • (リソースを使用できる場合)どのような方法で使用することが可能か?

[参考サイト]

15. ACL (Access Control List) 【 リソースベースのポリシー 】

リソースごとに少しずつ設定方法が異なるみたいですが、今回はS3()のバケットやオブジェクト
に対してのアクセス管理について紹介したいと思います。

各リソースには、サブリソースとして、ACLをアタッチする必要があります。
このACLによって、アクセスが許可されるAWSアカウントまたはグループと、アクセスの種類が定義されます。

当該リソースに対するリクエストを受信すると、Amazon S3はアタッチされたACLの内容を調べて、
リクエスト送信者がACLの内容を満たしているか判断します。

[参考サイト]

00. ランク外だけど重要なキーワード

16. シークレットキー

[ 筆者失敗談 ]

[※注意!] するほどでもない話。

もし、AWSのアカウントを取得されている方でこれからEC2インスタンスを生成するという方、
S3に新しいバケットを作られるという方はですね、今一度AWSコンソール画面右上のリージョン設定を
ご確認いただくことをお勧めいたします。

image.png

僕は気が付いたら、米国西部 (オレゴン) のリージョンでEC2インスタンスを立ち上げていて、
他のEC2インスタンス(東京リージョンで作成済み)がコンソール画面で確認できないので凄く焦りましたw

みなさんはこんな凡ミスはしないとは思いますが、一応ここに間違えた人間がいるので、記載しておきます。

最後に

今後記事内容をアップデートしていきながら、自分自身もAWSへの知見を深められたらなと思います。
最後までお読みいただき、ありがとうございました (^^)/

続きを読む

Serverless FrameworkでAWS Lamda関数を作成する

概要

Serverless Frameworkとは、Lambda、API Gateway、DynamoDBなどを作成、管理、デプロイできるツールです。
Frameworkと付いていますが、ツールです。
この記事では、python3でLambda関数を作成します。

環境

  • CentOS7.2
  • serverless 1.23.0
  • node v6.11.3
  • npm 3.10.10
  • OpenSSL 1.0.2k-fips 26 Jan 2017

npmのインストール

以下の記事を参照
npmのインストール手順

Serverless Frameworkのインストール

slsというディレクトリを作成し、そこで作業を行います。

$ mkdir sls
$ cd sls
$ npm init
$ npm install --save serverless

serverlessコマンドのパスを通します

$ npm bin serverless
$ echo 'export PATH="$HOME/sls/node_modules/.bin/:$PATH"' >> ~/.bash_profile
$ source ~/.bash_profile

インストール確認

$ serverless -v
1.23.0

aws credential登録

以下のコマンドで、AWSのキーを登録します。

$ serverless config credentials --provider aws --key XXXXXXXXXXXXEXAMPLE --secret XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEXAMPLEKEY
Serverless: Setting up AWS...
Serverless: Saving your AWS profile in "~/.aws/credentials"...
Serverless: Success! Your AWS access keys were stored under the "default" profile.

Lambda関数の作成

以下のコマンドで、Lambda関数を作成します。

$ serverless create -t aws-python3 -p sample-app

Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/home/vagrant/sls/sample-app"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  ___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.23.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-python3"

オプションの説明ですが、
-pは、Lambda関数名のprefixとなります。

また、-tで実装する言語を選びます。
以下のいずれかを選択します。

  • -t

    • aws-nodejs
    • aws-python
    • aws-python3
    • aws-java-maven
    • aws-java-gradle
    • aws-scala-sbt
    • aws-csharp
    • openwhisk-nodejs

すると、以下のファイルが生成されます。
handler.pyは、Lambda関数のテンプレート、serverless.ymlは設定ファイルになります。

$ ll sample-app/
total 8
-rw-rw-r--. 1 vagrant vagrant  497 Oct 10 04:42 handler.py
-rw-rw-r--. 1 vagrant vagrant 2758 Oct 10 04:42 serverless.yml

関数の情報を設定

serverless.ymlに関数の設定情報が書かれているので、環境合わせて編集します。

serverless.yml
provider:
  name: aws
  runtime: python3.6

# you can overwrite defaults here
- #  stage: dev
-#  region: us-east-1
+  stage: production
+  region: ap-northeast-1

# *snip*

# 関数名などの定義
functions:
-  hello:
-    handler: handler.hello
+  sample-func:
+    handler: handler.main

serverless.ymlで、関数のメソッド名を変更したので、handler.pyも以下のように編集します。

handler.py
-def hello(event, context):
+def main(event, context):

deploy

以下のコマンドでデプロイをします。

$ cd sample-app
$ serverless deploy

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (389 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............
Serverless: Stack update finished...
Service Information
service: sample-app
stage: production
region: ap-northeast-1
stack: sample-app-production
api keys:
  None
endpoints:
  None
functions:
  sample-func: sample-app-production-sample-func

すると、sample-app-production-sample-funcという名前のLambda関数が作成されます。

Lambda関数の実行

deployが出来たら、以下のコマンドで、Lambda関数を実行することができます。

$ serverless invoke -f sample-func

{
    "statusCode": 200,
    "body": "{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {}}"
}

パラメータを付ける場合は、-dオプションで指定します。
戻り値にinputの項目が増えているのが確認できます。

$ serverless invoke -f sample-func -d '{"key":"value"}'

{
    "statusCode": 200,
    "body": "{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {"key": "value"}}"
}

以下のようなjsonファイルを作成して、パラメータを渡すこともできます。

event.json
{
  "key" : "value"
}

-pオプションでjsonファイルを指定して実行

$ serverless invoke -f sample-func -p event.json
{
    "statusCode": 200,
    "body": "{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {"key": "value"}}"
}

Lambda関数の削除

関数の削除は以下のコマンドで行います。
関連するS3のファイルもすべて消してくれます。
AWS Console上で手動でLambda関数を削除すると、S3のファイルなどが残ってしまいます。

関数のあるディレクトリに移動でして実行します。

$ cd sample-function
$ serverless remove -v

-sオプションで、特定のstageのみを削除することもできます。

$ serverless remove -v -s dev

その他

以下のモジュールで、擬似的にローカルでApi Gateway、DynamoDBを使うことができます。

$ npm install aws-sdk
# 擬似的Api Gateway
$ npm install --save-dev serverless-offline
# 擬似的DynamoDB
$ npm install --save-dev serverless-dynamodb-local

参考

続きを読む

AWS GreengrassでLチカ:クラウドとエッジについて考える(2)

前回記事(AWS GreengrassでLチカ:クラウドとエッジについて考える(1))の続きになります.
取り組む課題や環境,クラウドタイプのやり方についてはそちらを参照ください.

2. AWS Greengrassにてネットワーク型信号機を実現する

本記事で取り組む課題は”ネットワーク型信号機の設計”ですが,クラウドの考え方を素直に適用した場合,いろいろと問題が発生しそうなことについては前回記事にて述べました.今回はそれらの問題を解消すべく,エッジの考え方を導入したネットワーク型信号機をAWS Greengrassを使って実現します.

今回もできるだけ単純なモデルとするため,構成は以下としました.

p2.png

大きな違いはメッセージの発行元です.

  • クラウドタイプ : インターネットをはさんだサーバー上で稼働するLambda関数がメッセージ発行元
  • エッジタイプ : RasPi3自身(正確にはRasPi3上で動作するLambda関数)がメッセージ発行元

構成を見る限り自立して稼働しそうな気がします…なんだかエッジぽいです.
早速作成してみます.

信号機側にデプロイされるLambda関数

まずはこれを作成することにします.作成はAWS Lambdaのコンソール上で実施しました.AWS Lambdaのコンソールから [関数の作成]選択,”Greengrass”で検索すると現状では2件ヒットします.うちPython版の”greengrass-hello-world”をテンプレートにして作業を進めます.
今回は,テンプレートを少しだけ変更して以下としました.

import greengrasssdk
import platform
from threading import Timer
import time

client = greengrasssdk.client('iot-data')

my_platform = platform.platform()

def greengrass_hello_world_run():
    # (追加部分)緑点灯のためのメッセージ発行,後5秒待ち
    client.publish(topic='io/led', payload='{"red":0,"green":1}')
    time.sleep(5)
    # (追加部分)赤点灯のためのメッセージ発行,後5秒待ち
    client.publish(topic='io/led', payload='{"red":1,"green":0}')
    # 関数自身を呼ぶことで無限ループ
    Timer(5, greengrass_hello_world_run).start()

# 実行開始
greengrass_hello_world_run()

def function_handler(event, context):
    return

※本来はデバイスシャドウを利用すべきかもしれませんが,簡素化のために直にメッセージを発行する形にしています

AWS Greengrass を通じてこのLambda関数がエッジ側(RasPi3)にデプロイされると,(無限ループのおかげで)動き続けることになります.エッジ側で動くLambda関数は通常のLambda関数と異なり,動作時間上限がありません.デプロイの過程やセキュリティの担保はGreengrassが面倒をみてくれるので,いろいろ考えなくてすみます.

信号機側S/W

前回記事にて作成したS/Wを改良する形で進めます.AWS Greengrassのサンプルを参考にしました.

主な変更点はMQTTクライアントの接続先を,サーバー側ではなく,RasPi3上で動作するGreengrassコア相手にする事です.接続先情報はサーバー側に問い合わせる機構となっているようです.

# -*- coding: utf-8 -*-

# AWS Greengrass を取り扱うためのモジュール
from AWSIoTPythonSDK.core.greengrass.discovery.providers import DiscoveryInfoProvider
from AWSIoTPythonSDK.core.protocol.connection.cores import ProgressiveBackOffCore
from AWSIoTPythonSDK.exception.AWSIoTExceptions import DiscoveryInvalidRequestException

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import RPi.GPIO as GPIO
import time
import json
import sys
import uuid
import os

# gpio pins
LED_R = 14
LED_G = 15
LEVEL = [GPIO.LOW, GPIO.HIGH]

# host
host = "a364yta89jsfw8.iot.ap-northeast-1.amazonaws.com"
port = 8883

# certs
rootca_path = "./certs/root-ca.pem"
certificate_path = "./certs/thing-certificate.pem.crt"
privatekey_path = "./certs/thing-private.pem.key"
thing_name = "RasPi3"

def onMessage(message):    # メッセージを受け取った際に呼ばれる
    print(message.payload)
    payload = json.loads(message.payload.decode('utf-8'))
    print(" > Message : {}".format(payload))
    print("--------------n")   

    try:
        red_lv = int(payload['red'])    
        green_lv = int(payload['green'])
        GPIO.output(LED_R, LEVEL[red_lv])
        GPIO.output(LED_G, LEVEL[green_lv])
    except:
        print(" ! Invalid message payload")     

def getGgCoreInfo():    # エッジ上Greengrass Coreの情報取得
    GROUP_CA_PATH = "./groupCA/"

    # Greengrass coreの検索
    discovery_info_provider = DiscoveryInfoProvider()
    discovery_info_provider.configureEndpoint(host)
    discovery_info_provider.configureCredentials(rootca_path, certificate_path, privatekey_path)
    discovery_info_provider.configureTimeout(10)  # 10 sec  

    discovery_info = discovery_info_provider.discover(thing_name)
    ca_list = discovery_info.getAllCas()
    core_list = discovery_info.getAllCores()

    # 見つかった1つめのCoreの情報を取得
    group_id, ca = ca_list[0]
    core_info = core_list[0]
    print("Discovered GGC: %s from Group: %s" % (core_info.coreThingArn, group_id))

    print("Now we persist the connectivity/identity information...")
    group_ca = GROUP_CA_PATH + group_id + "_CA_" + str(uuid.uuid4()) + ".crt"
        # GroupCAの取得(...よく理解していない)
    if not os.path.exists(GROUP_CA_PATH):
        os.makedirs(GROUP_CA_PATH)
    group_ca_file = open(group_ca, "w")
    group_ca_file.write(ca)
    group_ca_file.close()

    return (group_ca,core_info)

if __name__ == "__main__":

    # gpio setup
    GPIO.setwarnings(False) 
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(LED_R, GPIO.OUT)
    GPIO.setup(LED_G, GPIO.OUT)

    # mqtt setup
    mqtt_client = AWSIoTMQTTClient(thing_name)
    #mqtt_client.configureEndpoint(host,port)
    #mqtt_client.configureCredentials(rootca_path, privatekey_path, certificate_path)

    mqtt_client.configureOfflinePublishQueueing(0)
    mqtt_client.configureDrainingFrequency(2)
    mqtt_client.configureConnectDisconnectTimeout(120)
    mqtt_client.configureMQTTOperationTimeout(5)

    # Register an onMessage callback
    mqtt_client.onMessage = onMessage

    # 取得した接続先に対して順に接続を試みる
    connected = False
    (rootca_path, core_info) = getGgCoreInfo()
    for connectivity_info in core_info.connectivityInfoList:
        current_host = connectivity_info.host
        current_port = connectivity_info.port
        print(" * Trying to connect to core at %s:%d" % (current_host, current_port))
        mqtt_client.configureEndpoint(current_host, current_port)
        mqtt_client.configureCredentials(rootca_path, privatekey_path, certificate_path)
        try:
            mqtt_client.connect()
            connected = True
            break
        except BaseException as e:
            print(" * Error in connect!")
            print(" * Type: %s" % str(type(e)))
            print(" * Error message: %s" % e.message)

    if not connected:
        print(" * Cannot connect to core %s. Exiting..." % core_info.coreThingArn)
        sys.exit(-2)    

    mqtt_client.connect()
    time.sleep(2)
    print(" * connection success")  
    mqtt_client.subscribe("io/led", 1, None)
    print(" * start subscribing...")

    while True:
        time.sleep(3)

公式のサンプルではGreengrass Coreの検索自体をリトライする構成になっています.これは “RasPi3側にLambda関数をデプロイした後でしか接続先を発見できない”ためです.今回はコード簡略化のため”Lambda関数はすでにデプロイされている前提”で進めます.

信号機側H/W

前回記事と同じなので省略します.

動かしてみる

(本来の動きとは逆になりますが)まずはRasPi3側にLambda関数をデプロイします.注意点としてデプロイ前に

  • RasPi3上でGreengrass Coreを起動しておく
  • Lambda関数を作成後,[アクション]→[新しいバージョンを発行]を実行しておく
  • サブスクリプション(メッセージが配送される経路)を適切に設定しておく

が必要です.Greengrass Coreは以下で起動できます.起動に失敗する場合はGreengrass Coreの設定が間違っている可能性が高いです.

pi@raspi3-nobu_e753:/greengrass/ggc/core $ pwd
/greengrass/ggc/core
pi@raspi3-nobu_e753:/greengrass/ggc/core $ sudo ./greengrassd start
Setting up greengrass daemon
Validating execution environment
Found cgroup subsystem: cpu
...
Starting greengrass daemon
Greengrass successfully started with PID: 2856

サブスクリプションの経路は以下としました(自信なし).ターゲットに”IoT Cloud”を設けておくことで,AWS IoTのコンソール側([テスト]→[トピックへサブスクライブする])でも発行メッセージを確認することができます.

aws03s.png

設定が完了したらデプロイを実行します.あらかじめ[Lambda]タブで指定された関数がエッジ側へロードされます.ランプが緑に変わったら完了です.

aws04s.png

次にRasPi3側のプログラムを動かします.

pi@raspi3-nobu_e753:~/Workspace/aws $ ls
certs/  led_blink_awsgg.py  led_blink_awsiot.py
pi@raspi3-nobu_e753:~/Workspace/aws $ python led_blink_awsgg.py 
Discovered GGC: arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:thing/GgcGroup0_Core from Group: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Now we persist the connectivity/identity information...
 * Trying to connect to core at xxxx::xxx:xxxx:xxxx:xxxx:8883 # 接続先1トライ
 * Error in connect!
 * Type: <class 'socket.error'>
 * Error message: 
 * Trying to connect to core at 127.0.0.1:8883 # 接続先2トライ
 * connection success
 * start subscribing...    # エッジ側のLambda関数が発行するメッセージを受け,以下が出力される
{"red":1,"green":0}
 > Message : {u'green': 0, u'red': 1}
--------------

{"red":0,"green":1}
 > Message : {u'green': 1, u'red': 0}
--------------

{"red":1,"green":0}
 > Message : {u'green': 0, u'red': 1}
--------------

この状態で,RasPi3に接続されたLEDの色が5秒ごとに赤→緑→赤…と変わるのが確認できると思います.

最後に

エッジ側だけの情報で制御がなされていることを確認するためRasPi3のネットワーク接続を切断してみます(RasPi3のWifiを切断,ルーターの電源をおとす..などどのような方法でもよいです).その後もLEDの色が切り替わり続けるはずです.

このエッジタイプ信号機であれば,クラウドタイプ信号機の問題点を解決できそうですね.

わからなかった箇所

エッジ側へデプロイするLambda関数内で利用可能なコード
例えば,時刻の取得 datetime.now() やプラットフォーム情報の取得
platform.platform() は実行可能でしたが,ディレクトリ構成の取得 os.listdir() は実行できませんでした.何が実行できて何が実行できるのでしょう…

まとめ

AWS Greengrassを使い,信号機に見立てたLEDの制御を行うシステムを2通り作成しました.これを通じて,クラウドとエッジの違いを感覚的につかむことができました.

続きを読む

CloudWatch Logsのインストール・設定

CloudWatch logsでやれること

  • EC2の任意のログをリアルタイムで取得することができる
  • サーバにログインする必要がなく、AWSのコンソール画面でログを確認することができる
  • 取得したログの保持期間は、最短1日から失効なしまで選ぶことができる
  • ログの出力された期間を指定し、エクスポートすることができる

インストール前提

  • EC2へログインしていること
  • rootに切り替えていること
  • IAMで以下の権限付与されていること
      ・CloudWatch logsFulllAccess
  • EC2にアクセスキーとシークレットキーが登録されていること
  • 今回は、 /var/log/httpd/access_log を取得する

インストール手順

1. インストールコマンドの実行

インストールスクリプトのダウンロード
# curl https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py -O

パーミッションの変更
# chmod +x ./awslogs-agent-setup.py

インストールスクリプトの実行 ★S3を使用しないときは、下記(■設定内容のセットアップ)を参照
# ./awslogs-agent-setup.py -n -r ap-northeast-1 -c s3://使用するS3のBucket名/ 
■設定内容のセットアップ
# python ./awslogs-agent-setup.py --region ap-northeast-1 --only-generate-config

Launching interactive setup of CloudWatch Logs agent ...
Skipping downloading and installation of agent bits.Step 3 of 5: Configuring AWS CLI ...
AWS Access Key ID [****************MAJQ]: <シークレットキー> ★エンター
AWS Secret Access Key [****************fPn1]: <シークレットアクセスキー> ★エンター
Default region name [ap-northeast-1]: <リージョン> ★エンター
Default output format [json]: <出力形式(基本jsonでOK)> ★エンター

Step 4 of 5: Configuring the CloudWatch Logs Agent ...
Path of log file to upload [/var/log/messages]: <取得したいログのパス> ★エンター
Destination Log Group name [/var/log/messages]: <AWSコンソールで表示させる名前> ★エンター

Choose Log Stream name:
  1. Use EC2 instance id.
  2. Use hostname.
  3. Custom.
Enter choice [1]: <取得したサーバーの情報を何で表示させるか> ★エンター

Choose Log Event timestamp format:
  1. %b %d %H:%M:%S    (Dec 31 23:59:59)
  2. %d/%b/%Y:%H:%M:%S (10/Oct/2000:13:55:36)
  3. %Y-%m-%d %H:%M:%S (2008-09-08 11:52:54)
  4. Custom
Enter choice [1]: <ログのタイムスタンプを選択> ★エンター

Choose initial position of upload:
  1. From start of file.
  2. From end of file.
Enter choice [1]: ★エンター
More log files to configure? [Y]:  <複数のログを取得するなら[Y]、終了するなら[N]>

Step 5 of 5: Setting up agent as a daemon ...DONE


------------------------------------------------------
- Configuration file successfully saved at: /var/awslogs/etc/awslogs.conf
- You can begin accessing new log events after a few moments at https://console.aws.amazon.com/cloudwatch/home?region=ap-northeast-1#logs:
- You can use 'sudo service awslogs start|stop|status|restart' to control the daemon.
- To see diagnostic information for the CloudWatch Logs Agent, see /var/log/awslogs.log
- You can rerun interactive setup using 'sudo python ./awslogs-agent-setup.py --region ap-northeast-1 --only-generate-config'
------------------------------------------------------


2.起動確認コマンドの実行

# systemctl status awslogs
● awslogs.service - LSB: Daemon for AWSLogs agent.
   Loaded: loaded (/etc/rc.d/init.d/awslogs; bad; vendor preset: disabled)
   Active: active (running) since 月 2017-08-21 16:58:05 JST; 1 weeks 4 days ago
     Docs: man:systemd-sysv-generator(8)
  Process: 820 ExecStop=/etc/rc.d/init.d/awslogs stop (code=exited, status=0/SUCCESS)
  Process: 840 ExecStart=/etc/rc.d/init.d/awslogs start (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/awslogs.service
           tq846 /bin/sh /var/awslogs/bin/awslogs-agent-launcher.sh
           mq849 /var/awslogs/bin/python /var/awslogs/bin/aws logs push --con...

取得ログの追加

設定ファイルの変更
# vi /var/awslogs/etc/awslogs.conf ← CloudWatch logsの設定ファイル

設定ファイルに以下の内容を追記
[/var/log/httpd/access_log]
datetime_format = %Y-%m-%d %H:%M:%S
file = /var/log/httpd/access_log ← 取得したいログファイル
buffer_duration = 5000
log_stream_name = {ip_address} ← ログストリーム名を入力
initial_position = start_of_file
log_group_name = /var/log/http/access_log ← ロググループ名を入力

3.AWSコンソールで確認する
AWSコンソールにログインする⇒[サービス]⇒[CloudWatch]⇒[ログ]⇒手順1で設定した(Destination Log Group name)押下⇒CloudWatch Logsをインストールしたサーバーが出てればOK

参考URL

http://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/EC2NewInstanceCWL.html

続きを読む

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の魅力の記事を書きました。
今回は、serverless frameworkで 「 lambda + APIGateway + DynamoDB 」 の構成で簡単なサンプルアプリを作成します。

この記事でできるようになること

  • REST FULLなAPIを構築する
  • DynamoDBと連携できる
  • スケージューリングで実行する
  • DynamoStreamが使えるようになる

前準備

  • serverlessをinstallしておく
$ npm install -g serverless
  • プロジェクトを生成する
$ serverless create --template aws-nodejs --path my-service

すると、以下のファイルが出来ているはずです。

$ ls
serverless.yml
handler.js

また、サービス用のテンプレートですが、以下の言語用のテンプレートが用意されています。

使える言語

aws-nodejs
aws-python
aws-java-maven
aws-java-gradle
aws-scala-sbt

まずは、単純にJSONを返してみる

  • 最初はHelloWorld!をJSONで返すAPIで表示してみます。
  • serverless create をした時点でhandler.jsはこのようになっているので、messageを hello world など適当に変えれば良いです。
handler.js
'use strict';

module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: event,
    }),
  };

  callback(null, response);

  // Use this code if you don't use the http event with the LAMBDA-PROXY integration
  // callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event });
};

  • 最初のserverless.ymlを少し編集します

    • regionがdefaultでは us-east-1 なので ap-northeast-1 にする
    • getでアクセスできるように eventsを追加する
serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs6.10

  stage: dev
  region: ap-northeast-1

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
  • これでOKです!! さっそく、deployしてみましょう!!!!!!
$ sls deploy -v -s dev
  • -v は進捗を表示
  • -s はステージの名前を指示します(defaultはdev)
  • APIGatewayでは、プロジェクトからステージごとにデプロイしていましたが、serverlessではステージごとにプロジェクトが作られます。
Serverless: Stack update finished...
Service Information
service: XXXXXXXX
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  GET - https://XXXXXXXX.ap-northeast-1.amazonaws.com/dev/hello
functions:
  hello: XXXXXXXX
Stack Outputs

Serverless: Removing old service versions...

デプロイが成功すると、 APIGateWayには設定済みのプロジェクトと、endpoints を教えてくれるのでそこにアクセスしてみてhelloとJSONが表示されればOKです!!!

スクリーンショット 2017-09-23 15.28.55.png

スクリーンショット 2017-09-23 15.28.38.png

このように、GETでアクセスをするとさっき作成したJSONを返していることがわかります。
完璧だー 🍙🍙🍙

REST APIを作ってみる

  • 次は、DynamoDBと連携 して、get post put deleteを実装してみます。

  • serverless.ymlを編集します

  • REST APIのサンプルは、aws-node-rest-api-with-dynamodbにあります。

serverless.yml

serverless.yml
service: serverless-rest-api-with-dynamodb

frameworkVersion: ">=1.1.0 <2.0.0"

provider:
  name: aws
  runtime: nodejs4.3
  environment:
    DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"

functions:
  create:
    handler: todos/create.create
    events:
      - http:
          path: todos
          method: post
          cors: true

  list:
    handler: todos/list.list
    events:
      - http:
          path: todos
          method: get
          cors: true

  get:
    handler: todos/get.get
    events:
      - http:
          path: todos/{id}
          method: get
          cors: true

  update:
    handler: todos/update.update
    events:
      - http:
          path: todos/{id}
          method: put
          cors: true

  delete:
    handler: todos/delete.delete
    events:
      - http:
          path: todos/{id}
          method: delete
          cors: true

resources:
  Resources:
    TodosDynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          -
            AttributeName: id
            AttributeType: S
        KeySchema:
          -
            AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: ${self:provider.environment.DYNAMODB_TABLE}
  • serverless.ymlのポイント

    • environmentで、どのLambdaからでも DYNAMODB_TABLE を参照することができます。
    • todosというフォルダの中に create.js get.js list.js delete.js update.jsを置いておきます。
    • resourcesでDynamoDBを生成しています。

次に、それぞれのLambda関数を作成します

create.js

todos/create.js
'use strict';

const uuid = require('uuid');
const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies

const dynamoDb = new AWS.DynamoDB.DocumentClient();

module.exports.create = (event, context, callback) => {
  const timestamp = new Date().getTime();
  const data = JSON.parse(event.body);
  if (typeof data.text !== 'string') {
    console.error('Validation Failed');
    callback(null, {
      statusCode: 400,
      headers: { 'Content-Type': 'text/plain' },
      body: 'Couldn't create the todo item.',
    });
    return;
  }

  const params = {
    TableName: process.env.DYNAMODB_TABLE,
    Item: {
      id: uuid.v1(),
      text: data.text,
      checked: false,
      createdAt: timestamp,
      updatedAt: timestamp,
    },
  };

  // write the todo to the database
  dynamoDb.put(params, (error) => {
    // handle potential errors
    if (error) {
      console.error(error);
      callback(null, {
        statusCode: error.statusCode || 501,
        headers: { 'Content-Type': 'text/plain' },
        body: 'Couldn't create the todo item.',
      });
      return;
    }

    // create a response
    const response = {
      statusCode: 200,
      body: JSON.stringify(params.Item),
    };
    callback(null, response);
  });
};

delete.js

todos/delete.js
'use strict';

const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies

const dynamoDb = new AWS.DynamoDB.DocumentClient();

module.exports.delete = (event, context, callback) => {
  const params = {
    TableName: process.env.DYNAMODB_TABLE,
    Key: {
      id: event.pathParameters.id,
    },
  };

  // delete the todo from the database
  dynamoDb.delete(params, (error) => {
    // handle potential errors
    if (error) {
      console.error(error);
      callback(null, {
        statusCode: error.statusCode || 501,
        headers: { 'Content-Type': 'text/plain' },
        body: 'Couldn't remove the todo item.',
      });
      return;
    }

    // create a response
    const response = {
      statusCode: 200,
      body: JSON.stringify({}),
    };
    callback(null, response);
  });
};

get.js

todos/get.js
'use strict';

const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies

const dynamoDb = new AWS.DynamoDB.DocumentClient();

module.exports.get = (event, context, callback) => {
  const params = {
    TableName: process.env.DYNAMODB_TABLE,
    Key: {
      id: event.pathParameters.id,
    },
  };

  // fetch todo from the database
  dynamoDb.get(params, (error, result) => {
    // handle potential errors
    if (error) {
      console.error(error);
      callback(null, {
        statusCode: error.statusCode || 501,
        headers: { 'Content-Type': 'text/plain' },
        body: 'Couldn't fetch the todo item.',
      });
      return;
    }

    // create a response
    const response = {
      statusCode: 200,
      body: JSON.stringify(result.Item),
    };
    callback(null, response);
  });
};

list.js

todos/list.js
'use strict';

const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies

const dynamoDb = new AWS.DynamoDB.DocumentClient();
const params = {
  TableName: process.env.DYNAMODB_TABLE,
};

module.exports.list = (event, context, callback) => {
  // fetch all todos from the database
  dynamoDb.scan(params, (error, result) => {
    // handle potential errors
    if (error) {
      console.error(error);
      callback(null, {
        statusCode: error.statusCode || 501,
        headers: { 'Content-Type': 'text/plain' },
        body: 'Couldn't fetch the todos.',
      });
      return;
    }

    // create a response
    const response = {
      statusCode: 200,
      body: JSON.stringify(result.Items),
    };
    callback(null, response);
  });
};

update.js

todos/update.js
'use strict';

const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies

const dynamoDb = new AWS.DynamoDB.DocumentClient();

module.exports.update = (event, context, callback) => {
  const timestamp = new Date().getTime();
  const data = JSON.parse(event.body);

  // validation
  if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') {
    console.error('Validation Failed');
    callback(null, {
      statusCode: 400,
      headers: { 'Content-Type': 'text/plain' },
      body: 'Couldn't update the todo item.',
    });
    return;
  }

  const params = {
    TableName: process.env.DYNAMODB_TABLE,
    Key: {
      id: event.pathParameters.id,
    },
    ExpressionAttributeNames: {
      '#todo_text': 'text',
    },
    ExpressionAttributeValues: {
      ':text': data.text,
      ':checked': data.checked,
      ':updatedAt': timestamp,
    },
    UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt',
    ReturnValues: 'ALL_NEW',
  };

  // update the todo in the database
  dynamoDb.update(params, (error, result) => {
    // handle potential errors
    if (error) {
      console.error(error);
      callback(null, {
        statusCode: error.statusCode || 501,
        headers: { 'Content-Type': 'text/plain' },
        body: 'Couldn't fetch the todo item.',
      });
      return;
    }

    // create a response
    const response = {
      statusCode: 200,
      body: JSON.stringify(result.Attributes),
    };
    callback(null, response);
  });
};

これもデプロイをしてみてください!!

postmanなどで GET POST PUT DELETE すると確認ができるはずです。

スケジュールを設定して定期実行してみる

serverless.yml
  hoge:
    handler: functions/aggregate/index.handler
    events:
      - schedule:
          rate: cron(0 1 * * ? *)

serverless.yml
  hoge:
    handler: functions/aggregate/index.handler
    events:
      - schedule:
          rate: rate(5 minutes)

DynamoStreamを使ってputされたときに何か実行してみる

  • DynamoStreamとは、DynamoDBにputやupdateがあった場合に、そのイベントをトリガーにLambdaでまた処理させることができます。

  • DynamoのResourcesにStreamViewType を追加

serverless.yml
    HogeDynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          -
            AttributeName: column
            AttributeType: S
        KeySchema:
          -
            AttributeName: column
            KeyType: HASH

        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: "hoge-${self:provider.stage}"
        StreamSpecification:
          StreamViewType: KEYS_ONLY

そして、Streamでどの関数が呼ばれるかを指定します
以下のようにすると、二つのDynamoDBに変更がある場合に1つのLambda関数が呼ばれます。

serverless.yml
  hoge:
    handler: functions/hoge/index.handler
    events:
      - stream:
          type: dynamodb
          arn:
            Fn::GetAtt:
              - HogeDynamoDbTable
              - StreamArn
          batchSize: 1
      - stream:
          type: dynamodb
          arn:
            Fn::GetAtt:
              - HogeDynamoDbTable2
              - StreamArn
          batchSize: 1

batchSizeは1度にどれだけ項目がほしいか設定できます。

ちなみに、StreamViewTypeには

KEYS_ONLY => HASHキーのみ関数で取得できる
NEW_IMAGE =>  新しいものだけ取得できます
OLD_IMAGE => 古いものだけ取得できます
NEW_AND_OLD_IMAGES => 新旧のデータが取得できます

があります。

その他できること

まとめ

  • serverlessを使うとかんたんにAPIが作成できました!!
  • イベントの作成やオプションもまったく困らないと思います。
  • サンプルやプラグインが豊富で、ベストプラクティスには迷いません!

余談

  • 前回serverlessの魅了の紹介で、serverlessを使わないほうがいいときはないのかという質問を受けました。

serverlessを使わないほうがいいとき

  • 複雑なクエリを要するアプリを作るとき

    • 複雑なクエリを要するアプリを作るときは、DynamoDBでも設計を入念に行えばいいかと思いますが、whereなどのクエリが使えないので注意が必要です
    • MySQLクライアントもLambdaで使用することができますが、若干使いにくいようです。
  • Dynamoに書き込む容量が大きいとき

次回

  • serverless frameworkを使って本格的なAPIサーバーを構築(Express編)です!!

続きを読む

ローカルでS3的なもの(s3rver)を動かしてaws-cliで操作する

S3を使うプログラムをローカルでテストしたくなったので、s3rverというものを使ってみました。

インストール

$ yarn add --dev s3rver 
yarn add v0.27.5
 :
Done in 70.65s.

npm install --dev s3rverでも大丈夫です。

確認

global installではないので、node_modules/.bin/s3rverで起動します。

$ node_modules/.bin/s3rver --help

  Usage: s3rver [options]


  Options:

    --version                   output the version number
    -h, --hostname [value]      Set the host name or ip for the server
    -p, --port <n>              Set the port of the http server
    -s, --silent                Suppress log messages
    -i, --indexDocument [path]  Index Document for Static Web Hosting
    -e, --errorDocument [path]  Custom Error Document for Static Web Hosting
    -d, --directory [path]      Data directory
    -c, --cors                  Enable CORS
    -h, --help                  output usage information

格納先ディレクトリを準備して起動

$ mkdir -p ./temp/s3rver
$ node_modules/.bin/s3rver -d ./temp/s3rver
now listening on host localhost and port 4568

デフォルトでは4568ポートでListenしています。

aws-cliで操作する

aws-cliのs3コマンドまたはs3apiコマンドに--endpoint http://localhost:4568を指定すると利用できます。

Bucketの作成と表示(s3api使用)

$ aws --endpoint http://localhost:4568 s3api create-bucket --bucket test-bucket
{
    "Location": "/test-bucket"
}

$ aws --endpoint http://localhost:4568 s3api list-buckets
{
    "Buckets": [
        {
            "Name": "test-bucket",
            "CreationDate": "2017-09-23T06:13:07.000Z"
        }
    ],
    "Owner": {
        "DisplayName": "S3rver",
        "ID": "123"
    }
}

ファイルの操作(s3使用)

$ aws --endpoint http://localhost:4568 s3 cp ./README.md s3://test-bucket/
upload: ./README.md to s3://test-bucket/README.md

$ aws --endpoint http://localhost:4568 s3 ls s3://test-bucket/
2017-09-23 15:14:49       7052 README.md

javascript sdk (nodejs)から利用

AWS.config.update()でendpointを指定することで利用できます。

const AWS = require('aws-sdk');
AWS.config.update({ endpoint: 'http://localhost:4568' });
const s3 = new AWS.S3();

s3.listBuckets().promise()
  .then((data) => console.log(data.Buckets));

$ node index.js 
[ { Name: 'test-bucket', CreationDate: 2017-09-23T06:14:49.000Z } ]

これとaws-sam-localを組み合わせれば、Lambdaの開発もかなり捗りそうです。

(参考)実体は?

s3rver -d <directory>のディレクトリにファイルの実体があるのですが、そのまま置かれているわけではないようです。

$ tree -a ./temp/s3rver/test-bucket/
./temp/s3rver/test-bucket/
└── README.md
    ├── .dummys3_content
    └── .dummys3_metadata

ファイル名(S3上のKey)がディレクトリ構成で表現され、.dummys3_contentが実体、.dummys3_metadataにcreationDateなどが格納されていました。

この構造を理解していると、テストデータとしてS3にObjectを置くことができそうですが、s3 syncs3 cpが使えるので、そちらのほうが楽だと思います。

続きを読む

Amazon EC2 (Amazon Linux)に PHP 5.6 + Laravel 5.3 + Apache 2.4をインストールする手順 (201709 メモ書き)

参考記事:
Amazon EC2 (Amazon Linux)に PHP 5.6 + Laravel 5.3 + Apache 2.4をインストールする手順 (sedでphp.iniのdate.timezoneをAsia/Tokyoに変更するワンライナー)
https://qiita.com/na0AaooQ/items/e9b782be01ce6946d7e8

apache 2.4 install

# Apache 2.2 あれば削除
rpm -qa | grep http
yum remove httpd-*

# Apache 2.4 Install
sudo yum -y install httpd24

# Version
httpd -v


sudo /etc/init.d/httpd configtest
sudo /etc/init.d/httpd start
ps awux | grep -v grep | grep http

PHP 5.6

sudo yum -y install php56 php56-devel php56-mbstring php56-mcrypt php56-mysqlnd php56-pdo

Composer

sudo curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
sudo chown root:root /usr/local/bin/composer
ll /usr/local/bin/composer

gitなどのツール

sudo yum -y install git
sudo yum -y install php-pear
sudo yum -y install git
sudo yum -y install zlib-devel
sudo yum -y install gcc

httpdの設定

sudo chown ec2-user:ec2-user /var/www/laravel

mysql

自力でインストール:

* https://qiita.com/toshihirock/items/c5933d3628c7d4e81ffa
 EC2にMySQLインストールと設定確認
sudo yum install  mysql56 mysql56-server

RDS利用:

Source Deploy

git clone... の後、composer updateを実行したが、エラー。

[ec2-user@ip /var/www/roren-migration]$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 62 installs, 0 updates, 0 removals
  - Installing zendframework/zend-stdlib (3.1.0): Downloading (100%)
    proc_open(): fork failed - Cannot allocate memory
    The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)
    Unzip with unzip command failed, falling back to ZipArchive class
The following exception is caused by a lack of memory or swap, or not having swap configured
Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details

PHP Warning:  proc_open(): fork failed - Cannot allocate memory in phar:///usr/local/bin/composer/vendor/symfony/console/Application.php on line 979

Warning: proc_open(): fork failed - Cannot allocate memory in phar:///usr/local/bin/composer/vendor/symfony/console/Application.php on line 979

  [ErrorException]
  proc_open(): fork failed - Cannot allocate memory


update [--prefer-source] [--prefer-dist] [--dry-run] [--dev] [--no-dev] [--lock] [--no-custom-installers] [--no-autoloader] [--no-scripts] [--no-progress] [--no-suggest] [--with-dependencies] [-v|vv|vvv|--verbose] [-o|--optimize-autoloader] [-a|--classmap-authoritative] [--apcu-autoloader] [--ignore-platform-reqs] [--prefer-stable] [--prefer-lowest] [-i|--interactive] [--root-reqs] [--] [<packages>]...

Swapメモリが足りないので追加してみた

AWS Amazon Linux スワップファイル作成によりSwap領域のサイズを増やす
https://qiita.com/na0AaooQ/items/278a11ed905995bd16af

grep Mem /proc/meminfo
grep Swap /proc/meminfo
free
uname -a
# /swapfile1 に保存
sudo dd if=/dev/zero of=/swapfile1 bs=1M count=512
grep Swap /proc/meminfo

ll /swapfile1
sudo chmod 600 /swapfile1
mkswap /swapfile1
ll /swapfile1
sudo mkswap /swapfile1
swapon -s
free
sudo swapon /swapfile1
free
grep Swap /proc/meminfo

続きを読む