簡単に仮想のS3を作成してAWSLambdaとS3サービスの連携をローカル環境でテストする

事前準備

実行する前にEclise用のAWSツールキットを導入しておいてください。導入手順は以下のリンクにご参考をお願いします。

ー>AWS Toolkit導入手順

導入完成したらAWSプロジェクトがプロジェクト新規画面で出てくるはずです。

mavenは導入済みの前提です。

Screenshot from 2017-04-20 17-13-55.png

Lambdaファンクションを書く

  1. まずはAWS Lambda Java ファンクションのプロジェクト作成する.

Screenshot from 2017-04-20 17-19-41.png
- プロジェクト名 :S3EventTutorial
- パッケージ名:com.amazonaws.lambda.s3tutorial
以上のように必須な情報をいれておきまましょう。「完了」を押したらプロジェクトは作成されて一般的なプロジェクトフォルダは以下のようにみれます。
Screenshot from 2017-04-20 17-25-12.png

  1. S3をモックするように「s3mock_2.11」というライブラリをMavenで導入する。pomファイルに依存ライブラリを定義するだけでいいですので下のpomファイルを参考にして自分が作成したプロジェクトのpomを作成してみてください。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.amazonaws.lambda</groupId>
    <artifactId>s3tutorial</artifactId>
    <version>4.0.0</version>
    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.1.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-events</artifactId>
            <version>1.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk</artifactId>
            <version>1.11.119</version>
            <scope>compile</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.typesafe.akka/akka-http-experimental_2.11 -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-http-experimental_2.11</artifactId>
            <version>2.4.11.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.typesafe.scala-logging/scala-logging_2.11 -->
        <dependency>
            <groupId>com.typesafe.scala-logging</groupId>
            <artifactId>scala-logging_2.11</artifactId>
            <version>3.5.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.findify/s3mock_2.11 -->
        <dependency>
            <groupId>io.findify</groupId>
            <artifactId>s3mock_2.11</artifactId>
            <version>0.1.10</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.7.22</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.github.tomakehurst/wiremock -->
        <dependency>
            <groupId>com.github.tomakehurst</groupId>
            <artifactId>wiremock</artifactId>
            <version>2.6.0</version>
        </dependency>


    </dependencies>
</project>

ロカールにおいてあるmavenのリポジトリーにない依存ライブラリがあるかもしれないので一応プロジェクトのrootフォルダで「mvn package」をコマンドラインとして実行してみましう。そしてmavenはpomに定義されていたdependencyをダウンロードしてくれます。

  1. Lambdaファンクションのロジック
    作成してもらったLambdaFunctionHandler.javaを開いてロジックをかいてみましょう。アイデアは凄っく簡単です。

S3からファイルがアップロードされたというイベントがこられたら、イベントの内容を見てアップロードされたファイルをゲットしてコンソールでそのファイルを書き出すという作業です。コードみてみたらすぐ分かると思いますので説明しないですむ。


public class LambdaFunctionHandler implements RequestHandler<S3Event, Object> {

    private AmazonS3 s3Client;

    public LambdaFunctionHandler(AmazonS3 s3Client){
        this.s3Client = s3Client;
    }
    public LambdaFunctionHandler(){
        this.s3Client =  new AmazonS3Client(new ProfileCredentialsProvider());
    }

    private static void storeObject(InputStream input) throws IOException {
        // Read one text line at a time and display.
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        while (true) {
            String line = reader.readLine();
            if (line == null)
                break;
            System.out.println("    " + line);
        }
        System.out.println();
    }

    @Override
    public Object handleRequest(S3Event input, Context context) {
        context.getLogger().log("Input: " + input);

        // Simply return the name of the bucket in request
        LambdaLogger lambdaLogger = context.getLogger();
        S3EventNotificationRecord record = input.getRecords().get(0);
        lambdaLogger.log(record.getEventName()); // イベント名

        String bucketName = record.getS3().getBucket().getName();
        String key = record.getS3().getObject().getKey();
        /*
         * Get file to do further operation
         */
        try {
            lambdaLogger.log("Downloading an object");

            S3Object s3object = s3Client.getObject(new GetObjectRequest(bucketName, key));

            lambdaLogger.log("Content-Type: " + s3object.getObjectMetadata().getContentType());

            storeObject(s3object.getObjectContent());

            // Get a range of bytes from an object.

            GetObjectRequest rangeObjectRequest = new GetObjectRequest(bucketName, key);
            rangeObjectRequest.setRange(0, 10);
            S3Object objectPortion = s3Client.getObject(rangeObjectRequest);

            System.out.println("Printing bytes retrieved.");
            storeObject(objectPortion.getObjectContent());

        } catch (AmazonServiceException ase) {
            System.out.println("Caught an AmazonServiceException, which" + " means your request made it "
                    + "to Amazon S3, but was rejected with an error response" + " for some reason.");
            System.out.println("Error Message:    " + ase.getMessage());
            System.out.println("HTTP Status Code: " + ase.getStatusCode());
            System.out.println("AWS Error Code:   " + ase.getErrorCode());
            System.out.println("Error Type:       " + ase.getErrorType());
            System.out.println("Request ID:       " + ase.getRequestId());
        } catch (AmazonClientException ace) {
            System.out.println("Caught an AmazonClientException, which means" + " the client encountered "
                    + "an internal error while trying to " + "communicate with S3, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message: " + ace.getMessage());
        }catch (IOException ioe){
            System.out.println("Caught an IOException, which means" + " the client encountered "
                    + "an internal error while trying to " + "save S3 object, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message: " + ioe.getMessage());
        }
        return record.getS3().getObject().getKey();
    }

}


書いたコードに対してのテストケースを作成しましょう

今回は実装したLambdaコードを注目しますのでLambdaFunctionHandlerTestを開いてテストケース作成します。まずはテストケースのコードに目を通してみましょう。


    private static S3Event input;
    private static AmazonS3Client client;

    @BeforeClass
    public static void createInput() throws IOException {
        input = TestUtils.parse("s3-event.put.json", S3Event.class);

        S3Mock api = S3Mock.create(8999, "/tmp/s3");
        api.start();

        client = new AmazonS3Client(new AnonymousAWSCredentials());
        client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));

        // use IP endpoint to override DNS-based bucket addressing
        client.setEndpoint("http://127.0.0.1:8999");

    }

    private Context createContext() {
        TestContext ctx = new TestContext();

        // TODO: customize your context here if needed.
        ctx.setFunctionName("Your Function Name");

        return ctx;
    }

    @Test
    public void testLambdaFunctionHandlerShouldReturnObjectKey() {

        client.createBucket(new CreateBucketRequest("newbucket", "ap-northeast-1"));
        ClassLoader classLoader = this.getClass().getClassLoader();
        File file = new File(classLoader.getResource("file/test.xml").getFile());
        client.putObject(new PutObjectRequest(
                                 "newbucket", "file/name", file));

        LambdaFunctionHandler handler = new LambdaFunctionHandler(client);
        Context ctx = createContext();

        Object output = handler.handleRequest(input, ctx);

        if (output != null) {
            assertEquals("file/name", output.toString());
            System.out.println(output.toString());
        }
    }

テストのため、createInput関数でS3Mockのインスタンスを作成して起動します。このインスタンスはローカル環境の8999番ポートにバイドしてリクエストを待ちます。それに「/temp/s3」というフォルダを作成しておいてS3サービスのストレージを真似する。

一番大事なのはtestLambdaFunctionHandlerShouldReturnObjectKeyという関数の内容です。見るの通り、以下の作業を実装します。
– 「testbucket」を作成する。注意:Regionを指定するのは必須です(Regionの内容は別になでもいいですがなかったらjava.lang.NoSuchMethodError: com.amazonaws.regions.RegionUtils.getRegionByEndpoint(Ljava/lang/String;)Lcom/amazonaws/regions/Region;というErrorが出てきます。これはAWSのバグです)
– プロジェクトのしたにあるresourceフォルダに作成したfile/test.xmlを仮ストレージにアップロードする
– アップロードしたファイルを仮S3からダウンロードして内容をチェックする。

トリガーは「s3-event.put.json」で定義されているイベントの内容なので「s3-event.put.json」の内容にアップロードされたファイルの情報を反映しなければなりません


{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "us-east-1",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "C3D13FE58DE4C810",
        "x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "testbucket",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::mybucket"
        },
        "object": {
          "key": "file/name",
          "size": 1024,
          "eTag": "d41d8cd98f00b204e9800998ecf8427e"
        }
      }
    }
  ]
}

注意:bucket名とobjectのキーは一番大事です。見た内容の通りファイルはtestbuckにfile/nameというキーでアップロードされましたので応じてjsonの内容はそ言うことを表現される。

#終わり

ドラフトに説明しましたが不明なところがありましたらご相談をお願いします

続きを読む

terraform importの使い方メモ

terraform importの使い方メモ

背景

terraformではv0.7.0以降で実装されたimportコマンドを使用して既存インフラをterraform管理下に置くことができます。といってもtfstateへのインポートのみです。しかもv0.9.2時点では一括インポートができないため、リソースIDを指定してインポートする必要があります。さらにその後terraform planで差分がなくなるまでtfファイルを作り込む面倒な作業が待っています。そのような事情もあって現時点ではdtan4/terraformingを使用する人が多いと思われますが、やはり対応リソースの広さに惹かれて本家の機能を使ってみました。

本題

事前にモジュール構造を決める

とりあえず手で環境を作ってHashicorpのbest practiceに従ったモジュール構造でコード化しようと試みました。作った環境は上記のコードとだいたい同じで、パブリックのサブネットとプライベートなサブネットがある単純なVPCです。(詳細は割愛)

インポート

何はともあれ公式のリファレンスに従ってインポートしてみます。vpc-idはマネジメントコンソールから拾ってきます。リソースの多い場合はSDKなどでIDを引っこ抜いてくるスクリプトを作ったほうが良いと思います。

$ terraform import aws_vpc.vpc vpc-********

次にvpcのtfファイルを作成し、planを実行しました。しかし既存リソースは定義なしと見なされ再作成の対象となってしまいます。

$terraform plan

- aws_vpc.vpc

+ module.network.vpc.aws_vpc.vpc

原因は一見してわかるようにモジュール構造が異なるためです。そこでモジュール指定でインポートしてみると、今度はリソース名を解決できないと怒られます。

$ terraform import module.network.vpc.aws_vpc.vpc vpc-********
Error importing: failed to parse resource address 'module.network.vpc.aws_vpc.vpc': Unexpected value for InstanceType field: "vpc"

モジュールの指定方法

インポートの際はplanの表示と違ってvpcモジュール側もmodule.vpcと指定する必要があったのでした。apply等と同じ仕様ですね。

$ terraform import module.network.module.vpc.aws_vpc.vpc vpc-********

この状態でterraform planを実行するとめでたく差分なしとなりvpcは破壊されなくて済むのでした。terraformの管理下におく場合はとりあえずインポートしてからtfファイルを作り始めるというよりモジュール構造を決めてからインポートした方が効率的と思います。

インポートできないリソース

ルートテーブルなどに紐づく関連付けやセキュリティグループのルールは個別のインポートができない仕様です。ルートテーブルに複数の関連付けなどがある場合自動的にリソース名の末尾に数字が付いた名前で別々のリソース定義としてインポートされてきます。

ここで気になるのが、例えばルートテーブルに対する関連付けを動的に生成するような管理をしたい場合です。例えば下記のようなコードでprivateルートテーブルに対する関連付けを一括して管理したいとします。

resource "aws_route_table_association" "private" {
  count          = "${length(var.private_subnet_ids)}"
  subnet_id      = "${element(var.private_subnet_ids, count.index)}"
  route_table_id = "${element(aws_route_table.private.*.id, count.index)}"
}

しかしimportは上位のルートテーブル単位でしかできません。

terraform import module.network.module.route_table.aws_route_table.private rtb-*******

このコマンドの結果はaws_route_table_association.privateaws_route_table_association.private-1などどと別々の定義としてtfstateに書き出されます。こうなると事後でstateに合わせるためにはtfファイルに関連付けの数だけリソース定義を書かないといけなくなりそうです。stateと差分がなければコードなんて何でもいいだろという思想も一理ありますが、やはりここは保守性や拡張性なども考えたいところです。

逆にtfstateをコードに合わせる

推奨はしませんがtfstateを手で編集するか、terraformをカスタマイズしてpull requestを出してしまうかですが、余程腕に自信のあるエンジニアでない限り前者の方が手っ取り早いでしょう。aws_route_table_association.private-1aws_route_table_association.private.1
などどコードに合わせて書き換えてしまいましょう。(自己責任)

tfstateの書き換えは下記の記事を参考にさせて頂きました
Terraforming未対応の既存リソースも自力でtfstateを書いてTerraform管理下に入れる

結論

まだまだ未完成の機能なので単にインポートするだけで綺麗にコード化して管理できるとはいかないようです。v1.0が待たれます。

続きを読む

AWS Transit VPCソリューションを使って 手軽に マルチリージョン間 VPN環境を構築してみる

Transit VPC とは

AWS の VPC 間 VPN 接続を自動化するソリューションです。
高度なネットワークの知識がなくても、超カンタンに、世界中の リージョンにまたがる VPC を接続するネットワークを構築することができます。

概要


ある VPC を転送専用の VPN HUB(Transit VPC)として作成することで、他のVPCからのVPN接続を終端し、スター型のトポロジを自動作成します。
利用している AWSのアカウントに紐づいた VPC で VGW を作成すれば、HUBルーターに自動的に設定が投入され、VPNの接続が完了します。

予めテンプレートが用意されているので、ルーターの細かい設定を行う必要がありません。

また、高度な設定が必要な場合も、Cisco CSR1000V を利用しているので、Cisco IOS の機能は自由に利用することも可能です。

Adobe社では、大規模なネットワークを、AWS上の CSR1000V で構築しています。
https://blogs.cisco.com/enterprise/adobe-uses-cisco-and-aws-to-help-deliver-rich-digital-experiences

仕組み

  • AWS CloudFormation を使って、Transit VPC の CSR1000v のプロビジョニングが非常に簡単に行えるようになっています。

  1. VGW Poller という Lambda ファンクションが用意されており、Amazon CloudWatch を使って、Transit VPC に接続すべき VGW が存在するかを常に確認しています。
  2. VGWが存在する場合、VGW Poller が必要な設定を行い、設定情報を Amazon S3 bucket に格納します
  3. Cisco Configurator という名前の Lambda ファンクションが用意されており、格納した設定情報から、CSR1000v 用の設定を作成します
  4. Cisco Configurator が Transit VPC の CSR1000v へ SSH でログインし設定を流し込みます。
  5. VPN接続が完了します。

詳細については、下記、ご参照ください。
– AWS ソリューション – Transit VPC
https://aws.amazon.com/jp/blogs/news/aws-solution-transit-vpc/
– Transit Network VPC (Cisco CSR)
https://docs.aws.amazon.com/solutions/latest/cisco-based-transit-vpc/welcome.html

Transit VPC 構築方法

基本は、Wizard に従い、3(+1)ステップで設定が可能です。

事前準備 – CSR1000V用 Key Pair の作成

CSR1000V へログインする際に使う、Key Pair を EC2 で一つ作っておきましょう。
EC2 > Network & Security > Key Pairs から作成できます。名前はなんでもいいです。

.pem ファイルが手に入るので、後ほど、CSR1000V へログインする際にはこれを使います。

Step 1. Cisco CSR1000v ソフトウェア利用条件に同意

AWS マーケットプレイスの下記のページから、リージョンごとの値段が確認できます。

https://aws.amazon.com/marketplace/pp/B01IAFXXVO

  • For Region で 好きなリージョンを選択
  • Delivery Method で Transit Network VPC with the CSR 1000v を選択

その後 Continue

  • Manual Launch タブが選択されていること
  • Region / Deployment Options が先に設定したものとなっていること

上記を確認し、Accept Software Terms をクリックします。

Step 2. Transit VPC スタックの起動

下記のページより、Transit VPC 用の CloudFormation を起動することで、Transit VPC に必要な CSR1000v をセットアップすることができます。

http://docs.aws.amazon.com/ja_jp/solutions/latest/cisco-based-transit-vpc/step2.html

上記ページから Launch Solution をクリックします。
CloudFormation のページが立ち上がるので、まず、VPN HUB を 設置するリージョンになっているか確認します。
このソリューションは、Lambda を使うので、Transit VPC を設置できるのは、Lambda が使えるリージョンに限ります。東京は対応してます。
Lambda 対応リージョンは、こちらで確認できます。
https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/

正しいリージョンになっていたら、設定を進めます。

2-1. Select Template

テンプレートがすでに選択された状態になっていると思うので、特に設定の必要なし

Next をクリック

2-2. Specify Details

幾つかのパラメーターがありますが、最低限下記を変更すれば、動作します。

  • Specify Details

    • Stack name を設定します。
  • Parameters
    - Cisco CSR Configuration

    • SSH Key to access CSR で、事前準備で作成した key を選択します。
    • License Modelで、BYOL (Bring Your Own License) もしくは License Included を選択します。

CSR1000v のライセンスについて、Cisco から購入したものを使う場合は、BYOL を選択します。
ライセンス費用も利用料に含まれた利用形式が良ければ、License included version を選択します。
検証ライセンスがあるので、テストの場合は、BYOL で良いかと思います

入力が完了したら、 Next

2-3. Options

特に設定の必要なし Next をクリック

2-4. Review

  • 設定を確認して、一番下の acknowledge のチェックボックスをクリック

その後、Create で CSR1000V のインスタンス作成・セットアップが始まります

5分ほどすると、CSR1000V が 2インスタンス 立ち上がります。

事後設定(オプション)

起動した CSR1000V は デフォルトで、Lambda ファンクションからのログインのみを許可する設定になっているので、ssh で CLI にアクセスしたい場合は、Security Group の設定を変更します。

EC2 のダッシュボードから CSR1000V を選択し、Security Group の設定で SSH を許可します。
すべてのPCからSSHを受ける場合は、ソースアドレスとして 0.0.0.0/0 を指定します。

設定後、ssh でログインするには、事前準備で作成した キーペアで取得した .pem ファイルのパーミッションを変更します。

chmod 400 <キーペア名>.pem

これを使って、下記のコマンドで CSR1000V にログインできます

ssh -i <キーペア名>.pem <CSR1000V のパブリックIP>

ログインすれば、CSR1000v であることがわかると思います。

ip-XXX-XXX-XXX-XXX #sh ver
Cisco IOS XE Software, Version 16.03.01a
Cisco IOS Software [Denali], CSR1000V Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.3.1a, RELEASE SOFTWARE (fc4)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2016 by Cisco Systems, Inc.
Compiled Fri 30-Sep-16 02:53 by mcpre

<省略>

Step 3. スポーク VPC のタグ付け

上記、CSR1000V が立ち上がれば、あとは、Spoke VPC の VGW でタグを付けるだけで、CSR1000V への VPN 接続が完了します。

3-1. Spoke VPC の作成

新たに Spoke VPC を作成する場合は、下記の手順で作ります。

  • 好きなリージョンに、VPC を作成します。

    • VPC Dashboard > Virtual Private Cloud > Your VPC > Create VPC で作成します。
  • Virtual Private Gateway(VPG) を作成します。
    • VPC Dashboard > VPN Connection > Virtual Private Gateways > Create Virtual Private Gateway で作成します。
  • 作成した VPG を、先程作成した VPCにアタッチします。
    • VPC Dashboard > VPN Connection > Virtual Private Gateways > Attach to VPC  から、VPCへVGWをアタッチします。

3-2. VGW のタグ付け

  • 先程作成した VGW を選択し、下部の Tags タブを選択します。

    • Edit をクリックし Tag を追加します。

デフォルトの設定では、下記のタグを利用する設定になっていますので、下記タグを追加します
Key = transitvpc:spoke
Value = true

上記の設定が完了すると、VGWと Step2. で作成した、2台の CSR1000Vの間で自動的に VPN 接続が確立します。

http://docs.aws.amazon.com/ja_jp/solutions/latest/cisco-based-transit-vpc/step3.html

Step 4. 他の AWS アカウントからの接続設定 (オプション)

http://docs.aws.amazon.com/ja_jp/solutions/latest/cisco-based-transit-vpc/step4.html

まとめ

AWS の Transit VPC を使えば、世界中 にまたがる AWS の 各リージョンのVPCを簡単に VPN 接続できます。

また、VPN HUB ルーターは Cisco CSR1000v なので、企業内のシスコルーターと VPN 接続すれば、DC を AWS にして、世界中にまたがる VPN ネットワークを構築することができます。
ダイレクトコネクトとも連携可能です。

参考

On board AWS TransitVPC with Cisco CSR1000v (英語ですが、手順がわかりやすいです)

Cisco CSR 1000v: Securely Extend your Apps to the Cloud
https://www.slideshare.net/AmazonWebServices/cisco-csr-1000v-securely-extend-your-apps-to-the-cloud

続きを読む

CloudWatch resolution

EC2

name detail normal to typical note
Network-In/Out Byte/Minute Byte/5 Minutes bps = X * 8 /(60 or 300)
Network-Count In/Out Counts/5 Minutes Counts/5 Minutes PPS = X / 300 Detail 5min

ElastiCache Redis

name to typical note
GetTypeCmds X RAW = QPS (get,mget,hget)
SetTypeCmds X RAW = QPS (set, hset)

RDS Aurora

http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.Monitoring.html

name to typical note
Queries X RAW = QPS
SelectThroughput X RAW = QPS
AuroraReplicaLag X gauge
DatabaseConnections

DynamoDB

See. http://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/dynamo-metricscollected.html

name to typical note
Consumed(Read/Write)CapacityUnits units/sec = X(sum) /(60 or 300) Dimensions (Table or GSI)
UserErrors Errors/sec = X(counts) / (60 or 300) NO dimentions ↓↓
(Write/Read)ThrottleEvents No data No metrics(No 0)

続きを読む

Raspberry Pi 3 と DSUN-PIR を使って人感センサーを作る + 人感センサー検知状況を表示する簡易Webアプリケーションを作る

はじめに

最近、Raspberry Piを使い出したのですが、色々な用途に使えるので面白いですね!

今回はRaspberry Piとセンサーの勉強を兼ねて、Raspberry Pi 3 と DSUN-PIR(赤外線センサー)を使って人感センサーを作り、人感センサーの検知状況を表示する簡易Webアプリケーションを作ってみました。

今回使用した機材 及び 実行環境

今回は以下の機材を使用しました。
Raspberry Pi3 Model Bを使用しました。

Raspberry Pi スターターパック (Pi3用Standard)

Raspberry Pi3 Model B ボード&ケースセット 3ple Decker対応 (Element14版, Blue)

Raspberry Piには以下の赤外線感知センサーを取り付けました。

PIRセンサーモジュール [DSUN-PIR]

人感センサーの検知状況を表示する簡易WebアプリケーションはAmazon EC2(Amazon Linux)のRuby + Sinatraで作りました。RubyやSinatraサーバのセットアップ手順は以下をご参照下さい。

Amazon LinuxにRuby Sinatra環境構築(rbenv + ruby-build + Ruby 2.3.1 + Sinatra 1.4.7インストール)

参考資料

以下の記事を参考にさせて頂きました。
とても参考になりました。ありがとうございました。

Raspberry PiのGPIO制御方法を確認する(GPIO制御編その1)

人感センサ A500BP (DSUN-PIR, SB00412A-1も) が安いだけでなく Raspberry Pi との相性もバッチリだったので、人感カメラが10分で出来てしまった話。

RaspberryPiで人感センサーを作る

Raspberry Pi 3 (Raspbian Jessie)の無線LANに固定IPアドレスを設定する

Raspberry Pi の初期設定

Raspberry Pi側のセットアップ手順

(1) Raspberry Piをセットアップします。

参考資料の記事を見ながら、Raspberry Piをセットアップします。

人感センサ A500BP (DSUN-PIR, SB00412A-1も) が安いだけでなく Raspberry Pi との相性もバッチリだったので、人感カメラが10分で出来てしまった話。

RaspberryPiで人感センサーを作る

Raspberry Pi 3 (Raspbian Jessie)の無線LANに固定IPアドレスを設定する

以下は任意ですが、Raspberry Piに無線LANでIPアドレスを自動付与するようにしておくと、Raspberry PiにLANケーブルを接続しなくても良くなるので便利です。

root@raspberrypi:~# wpa_passphrase 無線LANのSSID SSIDのパスフレーズ >> /etc/wpa_supplicant/wpa_supplicant.conf
root@raspberrypi:~# cat /etc/wpa_supplicant/wpa_supplicant.conf
country=GB
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
    ssid="SSID"
    psk=パスフレーズが暗号化されて設定される
}
root@raspberrypi:~# 

(2) Raspberry PiのGPIO18に人感センサー(DSUN-PIR)を取り付けます。

Raspberry Piに人感センサー(DSUN-PIR)を取り付けます。

スクリーンショット 2017-04-02 23.17.25.png

スクリーンショット 2017-04-02 23.17.33.png

スクリーンショット 2017-04-03 1.25.56.png

スクリーンショット 2017-04-03 1.25.48.png

(3) Raspberry Piへsshログインします。

本記事では、例としてRaspberry Piに設定したIPアドレスを 198.51.100.10 と表記させて頂きます。

Raspberry Piへsshでログインします。piユーザでログインします。

MacBookProPC001:~ user$ ssh pi@198.51.100.10
pi@198.51.100.10's password: -> piユーザの初期パスワード raspberry を入力します。

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Mar 28 18:05:14 2017
pi@raspberrypi:~ $ 

(4) Raspberry PiのGPIO18を有効化します。

rootユーザにスイッチします。

pi@raspberrypi:~ $ sudo su - 
root@raspberrypi:~# 

Raspberry PiのGPIO18を有効化するスクリプトを作成します。

root@raspberrypi:~# mkdir /root/scripts
/root/scripts/start_gpio.sh
root@raspberrypi:~# vi /root/scripts/start_gpio.sh 
#!/bin/sh

GPIO="18"

echo $GPIO > /sys/class/gpio/export

ls -lrta /sys/class/gpio/
root@raspberrypi:~# chmod 755 /root/scripts/start_gpio.sh 
root@raspberrypi:~# 

スクリプトを実行し、Raspberry PiのGPIO18を有効化します。

root@raspberrypi:~# /root/scripts/start_gpio.sh 
total 0
drwxr-xr-x 50 root root    0 Jan  1  1970 ..
-rwxrwx---  1 root gpio 4096 Mar 28 18:05 unexport
lrwxrwxrwx  1 root gpio    0 Mar 28 18:05 gpiochip100 -> ../../devices/platform/soc/soc:virtgpio/gpio/gpiochip100
lrwxrwxrwx  1 root gpio    0 Mar 28 18:05 gpiochip0 -> ../../devices/platform/soc/3f200000.gpio/gpio/gpiochip0
-rwxrwx---  1 root gpio 4096 Apr  2 13:56 export
drwxrwx---  2 root gpio    0 Apr  2 13:56 .
lrwxrwxrwx  1 root root    0 Apr  2 13:56 gpio18 -> ../../devices/platform/soc/3f200000.gpio/gpio/gpio18
root@raspberrypi:~# 

(5) Raspberry PiのGPIO18に取り付けた人感センサー(DSUN-PIR)のテストを行います。

Raspberry PiのGPIO18に人感センサーを取り付けた場合、人感センサーに人が近くづくと /sys/class/gpio/gpio18/value の値が 0 から 1 へ変わります。

人感センサー周辺に人がいない場合、以下ファイルの値は 0 になります。

root@raspberrypi:~# cat /sys/class/gpio/gpio18/value 
0
root@raspberrypi:~# 

人感センサーに近づいてみます。

スクリーンショット 2017-04-02 23.14.50.png

すると、以下ファイルの値が 0 から 1 に変わります。

root@raspberrypi:~# cat /sys/class/gpio/gpio18/value 
1
root@raspberrypi:~# 

人感センサーから離れたら、以下ファイルの値が 1 から 0 に戻る事を確認します。

root@raspberrypi:~# cat /sys/class/gpio/gpio18/value 
0
root@raspberrypi:~# 

人感センサー検知状況表示用Webアプリケーションサーバのセットアップ手順

(6) 人感センサー検知情報用の簡易Webアプリケーションを作ります。

人感センサー検知情報表示用の簡易Webアプリケーションサーバ(Webサーバ)を作成します。

人感センサーは人が近づくと、Raspberry Piの/sys/class/gpio/gpio18/valueの値が変化しますので、この値をAPIサーバへ送信するようにします。

今回の記事では、WebサーバはRubyとSinatraで作ります。
RubyやSinatraのセットアップ手順は以下を参考にしてみて下さい。

Amazon LinuxにRuby Sinatra環境構築(rbenv + ruby-build + Ruby 2.3.1 + Sinatra 1.4.7インストール)

本記事では、例としてWebサーバを 198.51.100.20 と表記させて頂きます。
Webサーバへsshログインします。

MacBookProPC001:~ user$ ssh -i EC2インスタンスログイン用のSSH秘密鍵 ec2-user@198.51.100.20
Last login: Sun Apr  2 23:29:03 2017 from i121-115-95-232.s42.a013.ap.plala.or.jp

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2016.09-release-notes/
[ec2-user@example-ruby-sinatra-server ~]$ 

sinatraユーザを追加します。

[ec2-user@example-ruby-sinatra-server ~]$ sudo su - 
最終ログイン: 2017/04/02 (日) 23:30:13 JST日時 pts/0
[root@example-ruby-sinatra-server ~]# groupadd sinatra
[root@example-ruby-sinatra-server ~]# useradd sinatra -g sinatra -d /home/sinatra -s /bin/bash
[root@example-ruby-sinatra-server ~]# 
[root@example-ruby-sinatra-server ~]# su - sinatra
[sinatra@example-ruby-sinatra-server ~]$ 
[sinatra@example-ruby-sinatra-server ~]$ id
uid=501(sinatra) gid=501(sinatra) groups=501(sinatra)
[sinatra@example-ruby-sinatra-server ~]$ 
[sinatra@example-ruby-sinatra-server ~]$ /sbin/ifconfig eth0 | grep inet
          inet addr:198.51.100.20  Bcast:198.51.100.255  Mask:255.255.255.0
          inet6 addr: XXXX::XXX:XXXX:XXXX:XXXX/64 Scope:Link
[sinatra@example-ruby-sinatra-server ~]$ 

sinatraユーザに権限を付与します。

[root@example-ruby-sinatra-server ~]# echo "sinatra ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/sinatra
[root@example-ruby-sinatra-server ~]# chown root:root /etc/sudoers.d/sinatra 
[root@example-ruby-sinatra-server ~]# chmod 644 /etc/sudoers.d/sinatra 
[root@example-ruby-sinatra-server ~]# 
[sinatra@example-ruby-sinatra-server ~]$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]
[sinatra@example-ruby-sinatra-server ~]$ 

(7) 人感センサー検知情報用の簡易Webアプリケーションを作ります。

まずWebアプリケーション作成用のディレクトリを作成します。

[sinatra@example-ruby-sinatra-server ~]$ ls -lrta /home/sinatra/
total 24
-rw-r--r-- 1 sinatra sinatra  124 Aug 16  2016 .bashrc
-rw-r--r-- 1 sinatra sinatra  193 Aug 16  2016 .bash_profile
-rw-r--r-- 1 sinatra sinatra   18 Aug 16  2016 .bash_logout
drwxr-xr-x 4 root    root    4096 Apr  2 23:31 ..
-rw------- 1 sinatra sinatra    8 Apr  2 23:31 .bash_history
drwx------ 2 sinatra sinatra 4096 Apr  2 23:31 .
[sinatra@example-ruby-sinatra-server ~]$ 

[sinatra@example-ruby-sinatra-server ~]$ mkdir /home/sinatra/example-pir-api
[sinatra@example-ruby-sinatra-server ~]$ 
[sinatra@example-ruby-sinatra-server ~]$ cd /home/sinatra/example-pir-api/
[sinatra@example-ruby-sinatra-server example-pir-api]$ pwd
/home/sinatra/example-pir-api
[sinatra@example-ruby-sinatra-server example-pir-api]$ 

Gemfileを作成します。

[sinatra@example-ruby-sinatra-server example-pir-api]$ ls -lrta /home/sinatra/example-pir-api/
total 8
drwx------ 3 sinatra sinatra 4096 Apr  2 23:37 ..
drwxrwxr-x 2 sinatra sinatra 4096 Apr  2 23:37 .
[sinatra@example-ruby-sinatra-server example-pir-api]$ 
[sinatra@example-ruby-sinatra-server example-pir-api]$ bundle init
Writing new Gemfile to /home/sinatra/example-pir-api/Gemfile
[sinatra@example-ruby-sinatra-server example-pir-api]$ 
[sinatra@example-ruby-sinatra-server example-pir-api]$ ls -lrta /home/sinatra/example-pir-api/Gemfile 
-rw-r--r-- 1 sinatra sinatra 75 Apr  2 23:37 /home/sinatra/example-pir-api/Gemfile
[sinatra@example-ruby-sinatra-server example-pir-api]$ 
[sinatra@example-ruby-sinatra-server example-pir-api]$ echo "gem 'sinatra'" >> /home/sinatra/example-pir-api/Gemfile
[sinatra@example-ruby-sinatra-server example-pir-api]$ echo "gem 'sinatra-contrib'" >> /home/sinatra/example-pir-api/Gemfile
[sinatra@example-ruby-sinatra-server example-pir-api]$ echo "gem 'json'"    >> /home/sinatra/example-pir-api/Gemfile
[sinatra@example-ruby-sinatra-server example-pir-api]$ 
[sinatra@example-ruby-sinatra-server example-pir-api]$ cat /home/sinatra/example-pir-api/Gemfile
# frozen_string_literal: true
source "https://rubygems.org"

# gem "rails"
gem 'sinatra'
gem 'sinatra-contrib'
gem 'json'
[sinatra@example-ruby-sinatra-server example-pir-api]$ 

Sinatraをインストールします。

[sinatra@example-ruby-sinatra-server example-pir-api]$ pwd
/home/sinatra/example-pir-api
[sinatra@example-ruby-sinatra-server example-pir-api]$ ls -lrta /home/sinatra/example-pir-api/
total 12
drwx------ 3 sinatra sinatra 4096 Apr  2 23:37 ..
drwxrwxr-x 2 sinatra sinatra 4096 Apr  2 23:37 .
-rw-r--r-- 1 sinatra sinatra  122 Apr  2 23:39 Gemfile
[sinatra@example-ruby-sinatra-server example-pir-api]$ 
[sinatra@example-ruby-sinatra-server example-pir-api]$ bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/..........
Fetching version metadata from https://rubygems.org/.
Resolving dependencies...
Installing backports 3.7.0
Installing json 2.0.3 with native extensions
Installing multi_json 1.12.1
Installing rack 1.6.5
Installing tilt 2.0.7
Using bundler 1.13.2
Installing rack-protection 1.5.3
Installing rack-test 0.6.3
Installing sinatra 1.4.8
Installing sinatra-contrib 1.4.7
Bundle complete! 3 Gemfile dependencies, 10 gems now installed.
Bundled gems are installed into ./vendor/bundle.
[sinatra@example-ruby-sinatra-server example-pir-api]$ 
[sinatra@example-ruby-sinatra-server example-pir-api]$ ls -lrta /home/sinatra/example-pir-api/
total 24
-rw-r--r-- 1 sinatra sinatra  122 Apr  2 23:39 Gemfile
drwxrwxr-x 2 sinatra sinatra 4096 Apr  2 23:39 .bundle
drwxrwxr-x 3 sinatra sinatra 4096 Apr  2 23:39 vendor
drwx------ 4 sinatra sinatra 4096 Apr  2 23:39 ..
-rw-rw-r-- 1 sinatra sinatra  568 Apr  2 23:39 Gemfile.lock
drwxrwxr-x 4 sinatra sinatra 4096 Apr  2 23:39 .
[sinatra@example-ruby-sinatra-server example-pir-api]$ 
[sinatra@example-ruby-sinatra-server example-pir-api]$ 

人感センサー検知状況表示用の簡易Webアプリケーションを作成します。

[sinatra@example-ruby-sinatra-server ~]$ vi /home/sinatra/example-pir-api/example-pir-api.rb 
/home/sinatra/example-pir-api/example-pir-api.rb
require 'sinatra'
require 'sinatra/reloader'
require 'json'

PIR_STATUS_FILE = "/tmp/example-pir-status.log"
PIR_STATUS_FILE.freeze

get '/' do

  if File.exist?(PIR_STATUS_FILE)

    status = File.read(PIR_STATUS_FILE, :encoding => Encoding::UTF_8)
    status = status.chomp

    if status == "1" then
      room_status = "[リビングに人がいます] " + status + ""
    else
      room_status = "[リビングに人はいません] " + status + ""
    end

  else

    room_status = "[リビングの人感センサー検知状況を表示できません]"

  end

  room_status

end

post '/put' do
  body = request.body.read

  File.open(PIR_STATUS_FILE, "w") do |f| 
    f.puts(body);
  end

  if body == ''
    status 400
  else
    body.to_json
  end
end

(8) Webサーバを起動します。

[sinatra@example-ruby-sinatra-server example-pir-api]$ sudo /usr/local/rbenv/shims/bundle exec ruby /home/sinatra/example-pir-api/example-pir-api.rb -o 0.0.0.0 -p 80 &
[1] 4965
[sinatra@example-ruby-sinatra-server example-pir-api]$ [2017-04-03 00:28:03] INFO  WEBrick 1.3.1
[2017-04-03 00:28:03] INFO  ruby 2.3.1 (2016-04-26) [x86_64-linux]
== Sinatra (v1.4.8) has taken the stage on 80 for development with backup from WEBrick
[2017-04-03 00:28:03] INFO  WEBrick::HTTPServer#start: pid=4966 port=80

 -> Enterキーを押します。
[sinatra@example-ruby-sinatra-server example-pir-api]$ ps awux | grep -v grep | grep ruby
root      4965  0.0  0.4 186148  4452 pts/2    S    00:28   0:00 sudo /usr/local/rbenv/shims/bundle exec ruby /home/sinatra/example-pir-api/example-pir-api.rb -o 0.0.0.0 -p 80
root      4966  1.7  2.7 360380 28236 pts/2    Sl   00:28   0:00 ruby /home/sinatra/example-pir-api/example-pir-api.rb -o 0.0.0.0 -p 80
[sinatra@example-ruby-sinatra-server example-pir-api]$ 

Raspberry Pi側の作業

(9) Raspberry Piで人感センサーが検知した情報をWebサーバへ定期的に送信するようにします。

人感センサー検知情報をWebサーバへ送信するスクリプトを作成します。
本記事では例としてWebサーバを 198.51.100.20 と表記しておりますので、198.51.100.20 を指定します。

root@raspberrypi:~# vi /root/scripts/put_gpio_status.sh 
/root/scripts/put_gpio_status.sh
#!/bin/sh

GPIO="18"
GPIO_VALUE="/sys/class/gpio/gpio$GPIO/value"
API="https://198.51.100.20/put"

SLEEP="2"

if [ ! -f $GPIO_VALUE ] ; then
  echo "$GPIO_VALUE not found."
  exit
fi

while :
do
  curl -H "Accept: text/plain" -X POST -d `cat $GPIO_VALUE` $API

  sleep $SLEEP
done

スクリプトを実行し、人感センサー検知情報をWebサーバへ定期的に送信するようにします。

root@raspberrypi:~# chmod 755 /root/scripts/put_gpio_status.sh
root@raspberrypi:~# 
root@raspberrypi:~# /root/scripts/put_gpio_status.sh &
[1] 1833
root@raspberrypi:~# 

動作確認

(10) Webブラウザを起動して、人感センサー検知情報表示用の簡易Webアプリケーションを表示してみます。

WebブラウザからWebサーバへアクセスしてみます。

 http://198.51.100.20/

人感センサーの周辺に人がいない場合は [リビングに人はいません] と表示されます。

スクリーンショット 2017-04-03 0.51.01.png

人感センサーに近づいてみます。

スクリーンショット 2017-04-03 1.01.45.png

すると、[リビングに人がいます] と表示が変わる事を確認します。

スクリーンショット 2017-04-03 0.54.05.png


Raspberry Piとセンサー類を組み合わせると、部屋の入室検知やそれに応じたアクションの自動化等、色々な事に活用出来そうですね!

以上になります。

続きを読む

EMR の各Hadoopプロセスの起動停止方法

Upstart使ってます

EMRでの各Hadoopプロセスの起動スクリプトはUpstart形式のようで /etc/init/ 配下に配置されてます。

[root@ip-172-31-30-132 init]# ll /etc/init/
total 156
-rw-r--r-- 1 root root  412 Apr  9  2015 control-alt-delete.conf
-rw-r--r-- 1 root root  330 Dec 19 23:22 elastic-network-interfaces.conf
-rw-r--r-- 1 root root 1677 Apr  1 04:35 ganglia-rrdcached.conf
-rw-r--r-- 1 root root 1049 Apr  1 04:35 gmetad.conf
-rw-r--r-- 1 root root  979 Apr  1 04:35 gmond.conf
-rwxr-xr-x 1 root root 2928 Feb 17 18:10 hadoop-hdfs-namenode.conf
-rwxr-xr-x 1 root root 3331 Feb 17 18:10 hadoop-httpfs.conf
-rwxr-xr-x 1 root root 2856 Feb 17 18:10 hadoop-kms.conf
-rwxr-xr-x 1 root root 2989 Feb 17 18:10 hadoop-mapreduce-historyserver.conf
-rwxr-xr-x 1 root root 2944 Feb 17 18:10 hadoop-yarn-proxyserver.conf
-rwxr-xr-x 1 root root 2964 Feb 17 18:10 hadoop-yarn-resourcemanager.conf
-rwxr-xr-x 1 root root 2959 Feb 17 18:10 hadoop-yarn-timelineserver.conf
-rwxr-xr-x 1 root root 3314 Feb 17 18:17 hbase-master.conf
-rwxr-xr-x 1 root root 3302 Feb 17 18:17 hbase-rest.conf
-rwxr-xr-x 1 root root 3314 Feb 17 18:17 hbase-thrift.conf
-rwxr-xr-x 1 root root 2920 Feb 17 18:30 hive-hcatalog-server.conf
-rwxr-xr-x 1 root root 3114 Feb 17 18:30 hive-server2.conf
-rwxr-xr-x 1 root root 2922 Feb 17 18:30 hive-webhcat-server.conf
-rwxr-xr-x 1 root root 4054 Feb 17 18:59 hue.conf
[root@ip-172-31-30-132 init]# initctl list
rc stop/waiting
tty (/dev/tty3) start/running, process 2855
tty (/dev/tty2) start/running, process 2853
tty (/dev/tty1) start/running, process 2851
tty (/dev/tty6) start/running, process 2862
tty (/dev/tty5) start/running, process 2860
tty (/dev/tty4) start/running, process 2858
hbase-thrift start/running, process 12362
gmetad start/running, process 6955
update-motd stop/waiting
hadoop-mapreduce-historyserver start/running, process 5611
hadoop-yarn-timelineserver start/running, process 4929
hive-server2 start/running, process 15435
plymouth-shutdown stop/waiting
presto-server start/running, process 15790
control-alt-delete stop/waiting
hive-hcatalog-server start/running, process 16669
hive-webhcat-server start/running, process 8223
rcS-emergency stop/waiting
zookeeper-server start/running, process 8375

なので停止起動などは Upstart のお作法に沿ってあげましょう

[root@ip-172-31-30-132 init]# initctl status hue
hue start/running, process 17451
[root@ip-172-31-30-132 init]# initctl stop hue
hue stop/waiting
[root@ip-172-31-30-132 init]# ps -ef | grep hue
root     32388 13209  0 01:24 pts/0    00:00:00 grep --color=auto hue
[root@ip-172-31-30-132 init]# initctl status hue
hue stop/waiting
[root@ip-172-31-30-132 init]# initctl start hue
hue start/running, process 32488
[root@ip-172-31-30-132 init]# initctl status hue
hue start/running, process 32488
[root@ip-172-31-30-132 init]# ps -ef | grep hue
root     32489     1  2 01:24 ?        00:00:00 python2.7 /usr/lib/hue/build/env/bin/supervisor -p /var/run/hue/supervisor.pid -l /var/log/hue -d
hue      32494 32489 56 01:24 ?        00:00:03 python2.7 /usr/lib/hue/build/env/bin/hue runcherrypyserver
root     32582 13209  0 01:24 pts/0    00:00:00 grep --color=auto hue

今回は Hue.ini とか修正

Hue 3.11 でS3ブラウジングができなくなってたので、ひとまずアクセスキー・シークレットアクセスキーをコンフィグに書いて動作確認しました。
/etc/hue/conf/hue.ini を修正し、プロセス再起動。

無事閲覧できましたー

スクリーンショット 0029-04-02 10.58.03.png

続きを読む