Amazon Web Services

2019/10/21更新

AWS SAMのCLIであるaws-sam-cliを使うとローカルでLambda環境を作ってテストし、それをAWS上にデプロイできる。

ここではaws-sam-cliのインストールからローカルでのテスト、AWS上にデプロイするまでの手順を示す。

aws-sam-cliをインストールするOSはUbuntu 18.04とする。

aws-sam-cliのインストールと動作確認

動作環境

aws-sam-cliを使うにあたりあらかじめ以下をインストールしておく。

Pytyoh 3.x
pip
Docker
AWS CLI
$ python --version
Python 3.6.8

$ pip --version
pip 19.3.1 from /usr/local/lib/python3.6/dist-packages/pip (python 3.6)

$ docker --version
Docker version 18.09.7, build 2d0083d

$ aws --version
aws-cli/1.16.263 Python/3.6.8 Linux/4.15.0-65-generic botocore/1.12.253

aws-sam-cliインストール

aws-sam-cliはpipで簡単にインストールできる。

$ sudo pip install aws-sam-cli

$ sam --version
SAM CLI, version 0.22.0

サンプルアプリケーション作成(Python3.6)

aws-sam-cliがインストールできたらサンプルアプリケーションを作成してみる。

Lambdaは様々な言語で記述できるが、ここではPython3.6のサンプルアプリケーションを作成する。

$ sam init --runtime python3.6 --name sam-python
[+] Initializing project structure...

Project generated: ./sam-python

Steps you can take next within the project folder
===================================================
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json
[*] Start API Gateway locally: sam local start-api

Read sam-python/README.md for further instructions

[*] Project initialization is now complete

$ tree sam-python
sam-python
├── README.md
├── events
│   └── event.json ................. Lambdaに渡すイベントの定義
├── hello_world
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-36.pyc
│   │   └── app.cpython-36.pyc
│   ├── app.py ..................... Lambda関数本体
│   └── requirements.txt
├── template.yaml .................. SAMテンプレート
└── tests
    └── unit
        ├── __init__.py
        ├── __pycache__
        │   ├── __init__.cpython-36.pyc
        │   └── test_handler.cpython-36.pyc
        └── test_handler.py

6 directories, 12 files
単独実行

アプリケーションができたら実行してみる。

$ cd sam-python

$ sam local invoke HelloWorldFunction --event events/event.json --log-file lambda.log
Invoking app.lambda_handler (python3.6)
2019-10-20 23:13:06 Found credentials in shared credentials file: ~/.aws/credentials

Fetching lambci/lambda:python3.6 Docker container image...(略)...
Mounting ~/sam-python/hello_world as /var/task:ro,delegated inside runtime container

実行結果はログファイルに出力される。

$ cat lambda.log
START RequestId: 6eab94c3-2835-4241-9b97-b94b2bd60f2f Version: $LATEST
END RequestId: 6eab94c3-2835-4241-9b97-b94b2bd60f2f
REPORT RequestId: 6eab94c3-2835-4241-9b97-b94b2bd60f2f Duration: 0 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 19 MB

{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

--eventで指定したイベントの定義は以下のようにして作成できる。

$ sam local generate-event [OPTIONS] COMMAND [ARGS] > event.json

COMMANDには様々なAWSのサービスが指定でき、それぞれARGSが異なるのでhelpを参照する。

$ sam local generate-event --help
:
Commands:
  alexa-skills-kit
  alexa-smart-home
  apigateway
  batch
  cloudformation
  cloudfront
  cloudwatch
  codecommit
  codepipeline
  cognito
  config
  connect
  dynamodb
  kinesis
  lex
  rekognition
  s3
  ses
  sns
  sqs
  stepfunctions

(S3用イベントの場合)

$ sam local generate-event s3 --help
:
Commands:
  delete  Generates an Amazon S3 Delete Event
  put     Generates an Amazon S3 Put Event

$ sam local generate-event s3 put --help
:

$ sam local generate-event s3 put --help
Usage: sam local generate-event s3 put [OPTIONS]

Options:
  --region TEXT     Specify the region name you'd like, otherwise the default
                    = us-east-1
  --partition TEXT  Specify the partition name you'd like, otherwise the
                    default = aws
  --bucket TEXT     Specify the bucket name you'd like, otherwise the default
                    = example-bucket
  --key TEXT        Specify the key name you'd like, otherwise the default =
                    test/key
  --debug           Turn on debug logging to print debug message generated by
                    SAM CLI.
  --help            Show this message and exit.
API Gateway経由の実行

次にAPI Gateway経由で実行する。まずはAPI Gatewayを作成してリクエストを待ち受ける。

$ sam local start-api --log-file lambda.log
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2019-10-20 23:15:33  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

この状態で別ターミナルからcurlでアクセスするとAPI Gateway経由でLambdaが起動する。

$ curl http://127.0.0.1:3000/hello
{"message": "hello world"}

元ターミナルには以下のように出力される。

Invoking app.lambda_handler (python3.6)
2019-10-20 23:15:54 Found credentials in shared credentials file: ~/.aws/credentials

Fetching lambci/lambda:python3.6 Docker container image......
Mounting /home/xxxxx/sam-python/hello_world as /var/task:ro,delegated inside runtime container
No Content-Type given. Defaulting to 'application/json'.
2019-10-20 23:15:59 127.0.0.1 - - [20/Oct/2019 23:15:59] "GET /hello HTTP/1.1" 200 -
-> Ctrl + cで中断

ログファイルには以下のように記録される。

$ cat lambda.log
START RequestId: 7b951478-46f9-427a-a9fc-5c2ac8b9966f Version: $LATEST
END RequestId: 7b951478-46f9-427a-a9fc-5c2ac8b9966f
REPORT RequestId: 7b951478-46f9-427a-a9fc-5c2ac8b9966f Duration: 0 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 19 MB

またLambdaはDockerコンテナ上で実行されるのでDockerイメージがダウンロードされている。

$ docker images
REPOSITORY      TAG         IMAGE ID        CREATED         SIZE
lambci/lambda   python3.6   2659a569a6df    2 weeks ago     866MB
ローカルエンドポイント経由の実行

ローカルエンドポイントを作成してAWS CLIから実行することもできる。

$ sam local start-lambda --log-file lambda.log
Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint.
2019-10-21 00:12:59  * Running on http://127.0.0.1:3001/ (Press CTRL+C to quit)

この状態で別ターミナルからAWS CLIでLambdaを実行させる。

$ aws lambda invoke --function-name HelloWorldFunction --endpoint-url http://127.0.0.1:3001 out.txt
{
    "StatusCode": 200
}

$ cat out.txt
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

元ターミナルには以下のように出力される。

Invoking app.lambda_handler (python3.6)
2019-10-21 00:14:35 Found credentials in shared credentials file: ~/.aws/credentials

Fetching lambci/lambda:python3.6 Docker container image......
Mounting /home/xxxxx/sam-python/hello_world as /var/task:ro,delegated inside runtime container
2019-10-21 00:14:40 127.0.0.1 - - [21/Oct/2019 00:14:40] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
-> Ctrl + cで中断

ログファイルには以下のように記録される。

$ cat lambda.log
START RequestId: c3da9704-d7a4-45bf-b484-a0fd274c8036 Version: $LATEST
END RequestId: c3da9704-d7a4-45bf-b484-a0fd274c8036
REPORT RequestId: c3da9704-d7a4-45bf-b484-a0fd274c8036 Duration: 0 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 19 MB
Node.jsのサンプルアプリケーション

他にも例えばNode.jsのサンプルアプリケーションを作成する場合も--runtimeの指定を変えるだけでよい。

$ sam init --runtime nodejs --name sam-nodejs
[+] Initializing project structure...

Project generated: ./sam-nodejs

Steps you can take next within the project folder
===================================================
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json
[*] Start API Gateway locally: sam local start-api

Read sam-nodejs/README.md for further instructions

[*] Project initialization is now complete

$ tree sam-nodejs
sam-nodejs
├── README.md
├── events
│   └── event.json ................. Lambdaに渡すイベントの定義
├── hello-world
│   ├── app.js ..................... Lambda関数本体
│   ├── package.json
│   └── tests
│       └── unit
│           └── test-handler.js
└── template.yaml .................. SAMテンプレート

4 directories, 6 files

Lambdaの実行方法は先のPythonの時と同様なので割愛する。

AWS上にデプロイ

AWS上にデプロイ

ローカルで動作確認できたのでCloudFormationでAWS上にデプロイする。

まずテンプレートの正当性を確認する。

$ sam validate
2019-10-21 01:14:36 Found credentials in shared credentials file: ~/.aws/credentials
/home/xxxxx/sam-python/template.yaml is a valid SAM Template

問題がなければ上記のように「valid」になるのでS3バケットにアップロードする。ここではバケット名を「l-w-i.lambda」とする。

$ aws s3 mb s3://l-w-i.lambda

$ sam package --template-file template.yaml --s3-bucket l-w-i.lambda --output-template-file packaged.yaml
Uploading to c0e3fc815ba0ece850ad243e5994a832  1897 / 1897.0  (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /home/<user>/sam-python/packaged.yaml --stack-name <YOUR STACK NAME>

アップロードが終わったらデプロイを行う。

$ sam deploy --template-file packaged.yaml --stack-name sam-python --capabilities CAPABILITY_IAM

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

これによりCloudFormationのスタックが作成され、Lambda関数がデプロイされる。

$ aws cloudformation describe-stacks --stack-name sam-python --query 'Stacks[].Outputs[1]'
[
    {
        "OutputKey": "HelloWorldApi",
        "OutputValue": "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/",
        "Description": "API Gateway endpoint URL for Prod stage for Hello World function"
    }
]

API Gatewayのエンドポイントも作成されるのでcurlでアクセスしてみる。

$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello
{"message": "hello world"}

Lambdaが実行され、CloudWatch Logsにも実行ログが記録される。

デプロイしたLambda関数を削除する場合はCloudFormationのスタックを削除する。S3バケットとCloudWatch Logsのロググループは削除されないので別途削除する。

$ aws cloudformation delete-stack --stack-name sam-python

参考サイト

2018/12/30更新

対応バージョン: 1.10.33

S3にアクセスする際の署名バージョンには2と4があるが、AWS CLIの古いバージョンで署名バージョン4のみサポートするリージョンのS3バケットにアクセスすると以下のようなエラーになる。(この例ではフランクフルトリージョンのS3バケットにアクセス)

% aws --version
aws-cli/1.10.33 Python/2.7.14 Linux/4.14.72-68.55.amzn1.x86_64 botocore/1.4.23

% aws s3 cp test.txt s3://sample-eu-central-1/
upload failed: ./test.txt to s3://sample-eu-central-1/test.txt A
client error (PermanentRedirect) occurred when calling the PutObject
operation: The bucket you are attempting to access must be addressed
using the specified endpoint. Please send all future requests to this
endpoint: sample-eu-central-1.s3.eu-central-1.amazonaws.com

You can fix this issue by explicitly providing the correct region
location using the --region argument, the AWS_DEFAULT_REGION
environment variable, or the region variable in the AWS CLI
configuration file.  You can get the bucket's location by running "aws
s3api get-bucket-location --bucket BUCKET".

% aws s3api get-bucket-location --bucket sample-eu-central-1
{
    "LocationConstraint": "eu-central-1"
}

これは古いバージョンのAWS CLIがデフォルトで署名バージョン2でS3にアクセスするために起こる事象なので、CLI実行時にリージョンを明示的に指定するか、AWS CLIをアップデートすれば起こらなくなる。

CLI実行時にリージョンを指定する場合
% aws --region eu-central-1 s3 cp test.txt s3://sample-eu-central-1/
upload: ./test.txt to s3://sample-eu-central-1/test.txt
AWS CLIをアップデートする場合
% pip install --upgrade awscli

% aws --version
aws-cli/1.16.81 Python/2.7.14 Linux/4.14.72-68.55.amzn1.x86_64 botocore/1.12.71

% aws s3 cp test.txt s3://sample-eu-central-1/
upload: ./test.txt to s3://sample-eu-central-1/test.txt

参考サイト

2019/06/08更新

対応バージョン: 1.11.117

AWS CLIでLambda関数のソースコードをダウンロードする手順を示す。

まずget-functionコマンドにてLambdaのソースコードが収められたURL("Location": ...)とともにLambda関数の各種設定情報を取得する。

$ aws lambda get-function --function-name <Lambda関数名>
{
    "Code": {
        "RepositoryType": "S3",
        "Location": "https://awslambda-..."
    },
:

次にこのURLに対してcurl等でコンテンツを取り出す。以下のようにすればzip圧縮されたソースコード一式をダウンロードできる。

$ func="<Lambda関数名>"

$ url=$(aws lambda get-function --function-name ${func} | jq -r '.Code.Location')

$ curl -o lambda.zip $url

尚、Lambda関数がバージョン管理されている場合、最新のバージョンをダウンロードするのであれば上記の手順でよいが、特定のバージョンのソースコードをダウンロードするにはまずlist-versions-by-functionコマンドにて保存されているバージョンを確認した後、取得したいバージョンを--qualifierオプションで指定してget-functionコマンドを実行すればよい。

$ aws lambda list-versions-by-function --function-name <Lambda関数名>
{
    "Versions": [
        {
:
            "Version": "$LATEST",
:
            "LastModified": "2019-04-24T05:24:34.064+0000",
:
        },
        {
:            
            "Version": "1",
:
            "LastModified": "2019-03-14T02:08:41.267+0000",
:
        },
        {    
:            
            "Version": "2",
:
            "LastModified": "2019-03-12T02:18:20.732+0000",
        }
:
    ]
}

$ aws lambda get-function --function-name <Lambda関数名> --qualifier <バージョン番号>

2019/10/11更新

対応バージョン: ruby-mqtt 0.5.0

AWS IoTで証明書を作成し、これを使ってRubyのMQTTクライアントからAWS IoTに接続しようとするとエラーになる。

コード
$ vi mqtt.rb
require 'rubygems'
require 'mqtt'

client = MQTT::Client.connect(
           host: 'xxxxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com',
           port: 8883,
           ssl: true,
           cert_file: '<AWS IoTで作成した「このモノの証明書」>',
           key_file: '<AWS IoTで作成した「プライベートキー」>',
           ca_file: '<AWS IoTの「ルートCA ダウンロード」のページから取得したルートCA証明書>')
:
実行結果
$ ruby mqtt.rb
:
SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate) (OpenSSL::SSL::SSLError)

これは既にレガシー扱いになったVeriSign発行のルートCA証明書を使っているからであり、Amazon Trust Services発行のルートCA証明書を使えば問題なく動作する。

関連資料・記事

参考サイト

2017/06/11更新

以下の記事でAWS IoTを使ったセンシングデータのSNS通知手順を示したが、AWS IoTの設定をGUI(マネジメントコンソール)でなくCLIで行う手順を示す。AWS IoT以外の設定は変わらないので割愛する。

関連資料・記事

Amazon SNS

Topic作成
% aws sns create-topic --name "mqtt_test"
{
    "TopicArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:mqtt_test"
}
通知先(Email)設定
% aws sns subscribe --topic-arn "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:mqtt_test" --protocol email --notification-endpoint "xxx@yyy.com"
{
    "SubscriptionArn": "pending confirmation"
}

確認用のメールが届くので「Confirm subscription」をクリックしてこのメールアドレスを有効にするか、「Confirm subscription」のURLからToken="******"部分を抜き出してCLIで有効化する。

% aws sns confirm-subscription --topic-arn "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:mqtt_test" --token "*******************************************"
{
    "SubscriptionArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:mqtt_test:************************************"
}

AWS IoT

ArduinoをThingとして登録
aws iot create-thing --thing-name "Arduino"
{
    "thingArn": "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:thing/Arduino",
    "thingName": "Arduino"
}
センシングデータを処理するルールを設定

最初にルールにひも付けるIAMロールを作成する。

% vi role.json
{
  "Version":"2012-10-17",
  "Statement":[{
    "Effect": "Allow",
    "Principal": {
      "Service": "iot.amazonaws.com"
    },
    "Action": "sts:AssumeRole"
  }]
}

% aws iam create-role --role-name "mqtt_test_role" --assume-role-policy-document file://role.json
{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "iot.amazonaws.com"
                    }
                }
            ]
        },
        "RoleId": "*********************",
        "CreateDate": "2017-06-10T04:29:40.209Z",
        "RoleName": "mqtt_test_role",
        "Path": "/",
        "Arn": "arn:aws:iam::xxxxxxxxxxxx:role/mqtt_test_role"
    }
}

ロールが作成できたらルールを作成してひも付ける。

% vi rule.json
{
  "sql": "SELECT * FROM 'topic/test'",
  "ruleDisabled": false,
  "actions": [{
    "sns": {
      "targetArn": "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:mqtt_test",
      "roleArn": "arn:aws:iam::xxxxxxxxxxxx:role/mqtt_test_role"
    }
  }]
}

% aws iot create-topic-rule --rule-name "sendEmail" --topic-rule-payload file://rule.json
ポリシーを作成
% vi policy.json
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Action": ["iot:*"],
        "Resource": ["*"]
    }]
}

% aws iot create-policy --policy-name "mqtt_policy" --policy-document file://policy.json
{
    "policyName": "mqtt_policy",
    "policyArn": "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:policy/mqtt_policy",
    "policyDocument": "{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [{\n        \"Effect\": \"Allow\",\n        \"Action\": [\"iot:*\"],\n        \"Resource\": [\"*\"]\n    }]\n}\n",
    "policyVersionId": "1"
}
デバイス証明書の作成と有効化
% aws iot create-keys-and-certificate --set-as-active --certificate-pem-outfile cert.pem --private-key-outfile thing-private-key.pem
{
    "certificateArn": "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:cert/********************************",
    "certificatePem": "-----BEGIN CERTIFICATE-----\n******\n-----END CERTIFICATE-----\n",
    "keyPair": {
        "PublicKey": "-----BEGIN PUBLIC KEY-----\n******\n-----END PUBLIC KEY-----\n",
        "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\n******\n-----END RSA PRIVATE KEY-----\n"
    },
    "certificateId": "********************************************************"
}
ポリシーおよびThingをデバイス証明書にアタッチ
% aws iot attach-principal-policy --policy-name "mqtt_policy" --principal "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:cert/****************************************************************"

% aws iot attach-thing-principal --thing-name "Arduino" --principal "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:cert/****************************************************************"

参考サイト

2017/06/09更新

AWS IoTでMQTTブローカーを作り、ArduinoでセンシングしたデータをPub/Sub通信してAmazon SNSでメール通知する手順を示す。

データの流れは以下の通り。

作業の流れは以下のようになる。

Amazon SNS

Topic作成
通知先(Email)設定

AWS IoT

ArduinoをThingとして登録
センシングデータを処理するルールを設定
ポリシーを作成
デバイス証明書の作成と有効化
ポリシーおよびThingをデバイス証明書にアタッチ

ここではAWS IoTの設定はGUI(マネジメントコンソール)を使う。CLIを使った場合の手順は以下に示す。

関連資料・記事

Arduino

センシング用の回路を作成
Arduinoでセンシングを行いAWS IoT上のMQTTブローカーにPublishするプログラムを用意

テスト

尚ここで使用するArduinoはUNOとするが、直接IP通信ができないため母艦としてLinux(Ubuntu 16.04)を使用し、Pub/Sub通信やセンシングを行うプログラムはRubyで作成する。

Amazon SNS

まずセンサーデータを通知する通知先を作成する。手順としては最初にTopicを作成してそれに実際のメールアドレスをひも付ける。

Topic作成

Amazon SNSのダッシュボードから[Topics] > [Create new topic]をクリックし、Topic nameに任意の名前を付けてTopicを作成する。(ここでは「mqtt_test」とする)

メールアドレスのひも付け

作成したTopicにチェックを付け[Actions] > [Subscribe to topic]をクリックする。

Protocolを「Email」としてEndpointに通知先のメールアドレスを指定する。

確認用のメールが届くので「Confirm subscription」をクリックしてこのメールアドレスを有効にする。

ダッシュボードの[Subscriptions]をクリックすると通知先が作成されているのが確認できる。

AWS IoT

通知先が作成できたらAWS IoTの設定に移る。

流れとしてはまずAWS IoT上にArduinoをThingとして登録し、次にセンシングデータを処理するルールを設定したうえでデバイス証明書とポリシーを作成してArduinoにひも付ける。

Thing作成

AWS IoTのダッシュボードから[Registry] > [Things]をクリックして[Create]をクリックする。

Thingに任意の名前を付けて[Create thing]をクリックする。(ここでは「Arduino」とする)

センシングデータを処理するルールを設定

[Rules]をクリックして[Create]をクリックする。

ルールに任意の名前を付け(ここでは「sendEmail」とする)、Message source欄のAttributeに取得対象として全てのデータを表す「*」を、Topic filterに購読するトピック(ここでは「topic/test」とする)をそれぞれ指定して[Add action]をクリックする。

アクションを選択する画面が表示されるので「Send a message as an SNS push notification」を選択し[Configure action]をクリックする。

「SNS target」で先ほど作成したTopic(ここでは「mqtt_test」)を選択し、「IAM role name」で[Create a new role]をクリックする。

IAMロール名を入力できるようになるので任意の名前を付け(ここでは「mqtt_test_role」とする)、[Create a new role]をクリックする。

「Configure action」画面に戻るので[Add action]をクリックする。

「Create a rule」画面で[Create rule]をクリックするとアクションが作成され、ルールの作成が完了する。

ポリシー作成

[Security] > [policies]をクリックし、[Create]をクリックする。

Nameに任意のポリシー名を入力し(ここでは「mqtt_policy」とする)、Thingからの操作を全て許可することとして[Action]に「iot:*」、[Resource ARN]に「*」を入力して[Effect]の[Allow]をチェックして[Create]をクリックする。これでポリシーが作成される。

デバイス証明書作成

[Security] > [Certificates]をクリックし、[Create]をクリックする。

一番上の[Create certificate]をクリックする。

証明書と鍵ペアが作成されるのでこのうち証明書(A certificate for this thing: xxxxx.cert.pem)と秘密鍵(A private key: xxxxx.private.key)をダウンロードして[Activate]をクリックする。これで証明書が有効になる。

ルート証明書も必要になるので入手しておく。この作業はAWS IoTのコンソールでできないのでコマンドで実行する。

% wget https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem -O rootCA.pem

ポリシーおよびThingをデバイス証明書にアタッチ

[Security] > [Certificates]をクリックし、先ほど作成した証明書の「・・・」をクリックして[Attach policy]を選択する。

作成済ポリシーをチェックして[Attach]をクリックする。

同様に先ほど作成した証明書の「・・・」をクリックして[Attach thing]を選択する。

作成済Thingをチェックして[Attach]をクリックする。

Arduino

AWS IoTの設定が終わったら残りはArduino側の設定となる。

Arduino IDEを動かす母艦はUbuntu 16.04とする。

最初にArduinoにセンサーを接続してセンシングを行う回路を作成する。ここでは温度センサーを使って室温を計測することとする。

またArduinoの母艦となるOS(ここではUbuntu 16.04)上にAWS IoTのMQTTブローカーに対してSubscribeを行うプログラムとセンサーからのデータを取得してPublishするプログラムをそれぞれ用意する。言語はここではRubyを使用する。

センシング用の回路を作成

温度センサーとして精度はあまり高くないものの安価に調達できるLM61BIZを使用する。

センサーのピンは3つで刻印面に向かって左から

+Vs(電源電圧)
Vout(出力電圧)
GND(グランド)

という配列になっているのでこれをArduinoの5V、A0、GNDにそれぞれ接続する。

結線イメージは以下のようになる。

関連資料・記事

Arduinoでセンシングを行いAWS IoT上のMQTTブローカーにPublishするプログラムを用意

まずRubyからArduinoを操作するためのarduino_firmataパッケージと、MQTTのPub/Sub通信を行うためのmqttパッケージをインストールする。

% sudo gem install arduino_firmata
% sudo gem install mqtt

次に温度センサーからの値を1秒おきに取得して値を温度に変換したうえで前回取得値と異なる場合はAWS IoT上のMQTTブローカーにPublishするプログラムを用意する。

% vi pub.rb
require 'rubygems'
require 'arduino_firmata'
require 'mqtt'

arduino = ArduinoFirmata.connect "/dev/ttyACM0"

client = MQTT::Client.connect(
           host: '<Thing ARN>',
           port: 8883,
           ssl: true,
           cert_file: '<証明書ファイル>',
           key_file: '<秘密鍵ファイル>',
           ca_file: 'rootCA.pem')

before_temp = 0

while true
  temp = arduino.analog_read 0
  temp = ((temp.to_f * 5 / 1024) - 0.6) * 100
  temp = temp.round
  if before_temp != temp
    puts temp
    before_temp = temp
    client.publish('topic/test', "temp = #{temp}")
  end
  sleep 1
end

arduino.close

関連資料・記事

テスト

上記で用意したプログラムを実行すると、温度が変わる度にAWS IoT上のMQTTブローカーに温度が送信(Publish)される。

% ruby pub.rb
22
22
22
23 <- 温度センサーに手を触れて温度を変化させる
23
23

AWS IoTはこのデータを受け取るとルール設定に従ってAmazon SNS経由でメール通知を行う。通知先には以下のようなメールが届く。

今回はデバイスから取得したデータをアウトプットするまでの一連の動きを見るためにシンプルな実装を行ったが、AWS IoTでは他にも目的に応じて様々な制御が可能である。

参考サイト

2019/10/22更新

Amazon FreeRTOS(以下FreeRTOS)は様々なデバイスにインストールすることができエッジ側にAWSの環境を拡張する手段を提供しているが、ここでは手に入りやすいEspressif ESP32-DevKitC(以下ESP32)にFreeRTOSをインストールする手順を示す。

動作環境

FreeRTOSを使うにあたりあらかじめ以下をインストールしておく。

Pytyoh 2.7.10以降 (3.xでよいが、2.xを使う場面あり)
pip
AWS SDK for Python (boto3)
AWS CLI
$ python --version
Python 3.6.8

$ python2 --version
Python 2.7.15+

$ pip --version
pip 19.3.1 from /usr/local/lib/python3.6/dist-packages/pip (python 3.6)

$ pip list | grep boto3
boto3               1.9.253  

$ aws --version
aws-cli/1.16.263 Python/3.6.8 Linux/4.15.0-65-generic botocore/1.12.253

ツールチェーンインストール

まずEspressif公式サイトからUbuntu用のツールチェーンをダウンロードしてインストールする。

以下をダウンロード & インストール

xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz

$ mkdir ~/esp
$ cd ~/esp
$ tar zxvf ~/Downloads/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
環境変数設定
export PATH="$PATH:$HOME/esp/xtensa-esp32-elf/bin"
バージョン確認
$ xtensa-esp32-elf-gcc --version
xtensa-esp32-elf-gcc (crosstool-NG crosstool-ng-1.22.0-80-g6c4433a) 5.2.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

CMakeインストール

次にFreeRTOSビルド用のCMakeをインストールする。

Amazon FreeRTOSではCMake 3.13以降がサポートされるが、Ubuntuのパッケージリポジトリには3.10しか用意されていないためCMake公式サイトからダウンロードしてインストールする。

以下をダウンロード & インストール

cmake-3.15.4-Linux-x86_64.tar.gz

$ mkdir ~/tmp
$ cd ~/tmp
$ tar zxvf ~/Downloads/cmake-3.15.4-Linux-x86_64.tar.gz
$ sudo cp -r cmake-3.15.4-Linux-x86_64/{bin,doc,share} /usr/local

manファイルについては格納場所/usr/local/manが/usr/local/share/manへのシンボリックリンクのためmanファイルのみ以下のようにしてコピーする。

$ sudo cp -r cmake-3.15.4-Linux-x86_64/man /usr/local/share
バージョン確認
$ cmake --version
cmake version 3.15.4

Amazon FreeRTOSダウンロード & 設定

ビルドの準備が整ったらAmazon FreeRTOS公式GitHubからソース一式をダウンロードする。

$ cd ~/tmp
$ git clone https://github.com/aws/amazon-freertos.git --recurse-submodules

次にセットアップスクリプト用の設定ファイルを編集する。

$ cd amazon-freertos/tools/aws_config_quick_start
$ vi configure.json
{
    "afr_source_dir":"../..", <----------- 上記ダウンロードディレクトリ(amazon-freertosディレクトリのフルパス)
    "thing_name":"$thing_name", <--------- ESP32に付けるThing Name
    "wifi_ssid":"$wifi_ssid", <----------- Wi-FiネットワークのSSID
    "wifi_password":"$wifi_password", <--- Wi-Fiネットワークの接続パスワード
    "wifi_security":"$wifi_security" <---- Wi-Fiネットワークのセキュリティタイプ
}

設定ファイルが編集できたらセットアップスクリプトを実行する。

$ python SetupAWS.py setup

これによりAWS IoT上のリソースが自動的に作られ、リソース間の関連付けも行われる。

モノ(Thing)作成
証明書作成
ポリシー作成
証明書にポリシーをアタッチ
モノに証明書をアタッチ
証明書関連のファイル作成
$ ls -l ESP32_01_*
-r--r--r-- 1 xxx xxx   64 10月 22 08:46 ESP32_01_cert_id_file <----------- 証明書ID
-r--r--r-- 1 xxx xxx 1220 10月 22 08:46 ESP32_01_cert_pem_file <---------- 公開鍵
-r--r--r-- 1 xxx xxx 1675 10月 22 08:46 ESP32_01_private_key_pem_file <--- 秘密鍵
MQTTエンドポイント、Wi-Fiネットワーク情報をデモ用aws_clientcredential.hファイルに追加
$ cat -n ../../demos/include/aws_clientcredential.h
:
    35	/*
    36	 * MQTT Broker endpoint.
    37	 */
    38	#define clientcredentialMQTT_BROKER_ENDPOINT "xxxxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com"
    39	
    40	
    41	/* Use of a "define" and not a "static const" here to be able to
    42	* use pre-compile concatenation on the string. */
    43	#define clientcredentialIOT_THING_NAME "ESP32_01"
:
    56	/*
    57	 * Wi-Fi network to join.
    58	 */
    59	#define clientcredentialWIFI_SSID       "xxxxxxxxxxxxxxxx"
    60	
    61	/*
    62	 * Password needed to join Wi-Fi network.
    63	 */
    64	#define clientcredentialWIFI_PASSWORD   "xxxxxxxxxxxxx"
    65	
    66	/**
    67	 * @brief Security type
    68	 * WPA2 Security, @see WIFISecurity_t
    69	 * Possible values are - eWiFiSecurityOpen, eWiFiSecurityWEP, eWiFiSecurityWPA,
    70	 * eWiFiSecurityWPA2
    71	 */
    72	#define clientcredentialWIFI_SECURITY   xxxxxxxxxxxxxxxxx
    73	
    74	#endif
証明書とプライベートキーをBase64エンコードしてデモ用aws_clientcredential_keys.hファイルに追加
$ cat -n ../../demos/include/aws_clientcredential_keys.h
:
    29	/*
    30	 * PEM-encoded client certificate
    31	 *
    32	 * Must include the PEM header and footer:
    33	 * "-----BEGIN CERTIFICATE-----\n"\
    34	 * "...base64 data...\n"\
    35	 * "-----END CERTIFICATE-----\n"
    36	 */
    37	#define keyCLIENT_CERTIFICATE_PEM \
    38	"-----BEGIN CERTIFICATE-----\n"\
:
    57	"-----END CERTIFICATE-----\n"
    77	/*
    78	 * PEM-encoded client private key.
    79	 *
    80	 * Must include the PEM header and footer:
    81	 * "-----BEGIN RSA PRIVATE KEY-----\n"\
    82	 * "...base64 data...\n"\
    83	 * "-----END RSA PRIVATE KEY-----\n"
    84	 */
    85	#define keyCLIENT_PRIVATE_KEY_PEM \
    86	"-----BEGIN RSA PRIVATE KEY-----\n"\
:
   112	"-----END RSA PRIVATE KEY-----\n"
:

FreeRTOSデモプロジェクトビルド & ESP32書き込み & 実行

設定が終わったのでCMakeでFreeRTOSをビルドし、ESP32に書き込んで実行する。

ビルド
$ cd ../..
$ cmake -DVENDOR=espressif -DBOARD=esp32_devkitc -DCOMPILER=xtensa-esp32 -S . -B build
$ cd build
$ make all -j4
:
[100%] Built target app
ESP32書き込み

ここでESP32をホストPCに接続し、まず以下を実行してESP32の中身を消去する。

$ cd ..
$ ./vendors/espressif/esp-idf/tools/idf.py erase_flash -B build
ESP-IDF currently only supports Python 2.7, and this is Python 3.6.8. Search for 'Setting the Python Interpreter' in the ESP-IDF docs for some tips to handle this.

ただ当該環境ではPython3がデフォルトなので上記のエラーになる。

idf.pyを書き換えてPython2を明示的に指定して再度実行する。

$ vi ./vendors/espressif/esp-idf/tools/idf.py
(変更前)
#!/usr/bin/env python

(変更後)
#!/usr/bin/env python2

$ ./vendors/espressif/esp-idf/tools/idf.py erase_flash -B build
Setting IDF_PATH environment variable: ...
:
Chip erase completed successfully in 3.8s
Hard resetting via RTS pin...
Done

続いてFreeRTOSをESP32に書き込む。

$ cd build
$ make flash
[  0%] Built target blank_ota_data
[  1%] Built target partition_table
[  1%] Built target idf_component_ulp
:
Wrote 953520 bytes (585067 compressed) at 0x00020000 in 14.5 seconds (effective 526.8 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...
[100%] Built target flash
AWS上のMQTTメッセージのモニタリング

AWS IoTコンソールでMQTTクライアントを使ってESP32がAWSに送信するメッセージをサブスクライブしておく。

[テスト]をクリックするとMQTTクライアントが開くので[トピックのサブスクリプション]フィールドに「iotdemo/#」と入力して[トピックへのサブスクラ...]をクリックする。

いまサブスクライブしたトピックの[iotdemo/#]をクリックしてメッセージを待ち受ける。

デモプログラム実行

この状態でデモプログラムを実行するとMQTTでパブリッシュが行われ、AWS IoTコンソール上でもメッセージが受信できていることが確認できる。

$ cd ..
$ ./vendors/espressif/esp-idf/tools/idf.py monitor -p /dev/ttyUSB0 -B build
:
157 914 [iot_thread] [INFO ][DEMO][9140] Demo completed successfully.
158 918 [iot_thread] [INFO ][INIT][9180] SDK cleanup done.
159 918 [iot_thread] [INFO ][DEMO][9180] -------DEMO FINISHED-------
-> Ctrl + ]で終了

参考サイト

2019/08/20更新

対応バージョン: Amazon Linux 2

ローカルのファイルをリモートのEC2にセキュアに同期(コピー)する場合、脆弱性のあるscpを使わずにsftpやrsyncを使ったほうがよいとされており、今後主流になっていくであろうOpenSSH-8.0でも同様の案内がされている。
OpenSSH-8.0リリースノートより
:
Security
========
:
The scp protocol is outdated, inflexible and not readily fixed. We
recommend the use of more modern protocols like sftp and rsync for
file transfer instead.
:

そこで、rsyncを使った同期の方法を示す。

尚、同期元と同期先の環境はそれぞれ以下とする。

同期元 (Ubuntu 18.04)
$ uname -r
4.15.0-58-generic

$ ssh -V
OpenSSH_7.6p1 Ubuntu-4ubuntu0.3, OpenSSL 1.0.2n  7 Dec 2017

$ rsync --version
rsync  version 3.1.2  protocol version 31
同期先 (Amazon Linux 2)
$ uname -r
4.14.123-111.109.amzn2.x86_64

$ ssh -V
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017

$ rsync --version
rsync  version 3.1.2  protocol version 31

rsync使用方法

以下のようにする。

rsync <オプション> -e 'ssh -i <EC2キーペアの秘密鍵>' <ローカルディレクトリ> <リモートディレクトリ>

使用例)

$ rsync -av --delete -e 'ssh -i ~/.ssh/ec2.pem' ~/files/ ec2-user@1.2.3.4:files/

オプションはここでは以下を指定しているが、他にもいろいろあるので用途に応じて指定する。

-a アーカイブモード(-rlptgoDと同義)

-r(ディレクトリを再帰的に処理)

-l(シンボリックリンクをそのままコピー)

-p(ファイルのパーミッションを保持)

-t(ファイルのタイムスタンプを保持)

-g(ファイルの所有グループを保持) ※

-o(ファイルの所有ユーザを保持) ※

-D(デバイスファイル/スペシャルファイルをそのままコピー) ※

※ root権限が必要

-v 処理内容の表示
--delete 同期元に存在しないファイルを同期先から削除

2019/10/19更新

Amazon DynamoDBはAWSクラウド上だけでなくローカルでも実行できるバージョン(DynamoDBローカル)が提供されている。ローカルで実行することにより課金を気にせず動作確認や開発が可能になる。

ここではUbuntu 18.04にDynamoDBローカルを導入してテーブルの作成やデータの操作を行ってみる。

DynamoDBローカル動作環境

DynamoDBローカルはjarファイルとDockerイメージの2種類が提供されているが、ここでは前者の方法で確認する。

jarファイルを利用する場合はあらかじめ以下をインストールしておく。

Java Runtime Environment (JRE) 6.x 以降

DynamoDBローカルインストール

まず以下からDynamoDBローカルのtar.gzをダウンロードする。

ダウンロードしたら任意の場所に展開する。インストールはこれだけである。

$ tar zxvf dynamodb_local_latest.tar.gz

DynamoDBローカルサーバ起動

サーバの起動も簡単で、以下のようにすればよい。Ctrl + cで停止する。

$ java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
Initializing DynamoDB Local with the following configuration:
Port:	8000
InMemory:	false
DbPath:	null
SharedDb:	true
shouldDelayTransientStatuses:	false
CorsParams:	*

尚、デフォルトの待受ポートは8000なので必要に応じて変更する。(-port <n>で指定)

テーブル作成

サーバの起動ができたら実際にテーブルを作成してみる。

AWS CLIを使って以下のようにする。ここでは「test」というテーブルを作り、キーを「num」とする。

$ aws dynamodb create-table --table-name test \
--attribute-definitions AttributeName=num,AttributeType=N \
--key-schema AttributeName=num,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
--endpoint-url http://localhost:8000
{
    "TableDescription": {
        "TableArn": "arn:aws:dynamodb:ddblocal:000000000000:table/test", 
        "AttributeDefinitions": [
            {
                "AttributeName": "num", 
                "AttributeType": "N"
            }
        ], 
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0, 
            "WriteCapacityUnits": 1, 
            "LastIncreaseDateTime": 0.0, 
            "ReadCapacityUnits": 1, 
            "LastDecreaseDateTime": 0.0
        }, 
        "TableSizeBytes": 0, 
        "TableName": "test", 
        "BillingModeSummary": {
            "LastUpdateToPayPerRequestDateTime": 0.0, 
            "BillingMode": "PROVISIONED"
        }, 
        "TableStatus": "ACTIVE", 
        "KeySchema": [
            {
                "KeyType": "HASH", 
                "AttributeName": "num"
            }
        ], 
        "ItemCount": 0, 
        "CreationDateTime": 1571411233.367
    }
}

テーブルができていることを確認する。

$ aws dynamodb list-tables --endpoint-url http://localhost:8000
{
    "TableNames": [
        "test"
    ]
}

データ操作

テーブルが作成できたらデータを挿入/更新/削除してみる。

データ挿入
$ aws dynamodb put-item --table-name test \
--item '{"num":{"N":"1"},"data":{"S":"first"}}' \
--endpoint-url http://localhost:8000
データ取得
$ aws dynamodb get-item --table-name test \
--key '{"num":{"N":"1"}}' \
--endpoint-url http://localhost:8000
{
    "Item": {
        "num": {
            "N": "1"
        }, 
        "data": {
            "S": "first"
        }
    }
}

AWS CLIだけでなくブラウザからアクセスしてJavaScriptで操作することもできるので、以降はブラウザ上で実行する。

http://localhost:8000/shell/

データ挿入

左ペインに以下のコードを入力し、実行ボタン()をクリックすると右ペインに実行結果が表示される。

AWS.config.endpoint = new AWS.Endpoint('http://localhost:8000');
var dynamodb = new AWS.DynamoDB();
var params = {
    TableName: 'test',
    Item: {
        'num':{N: '2'},
        'data':{S: 'second'}
    }
};
dynamodb.putItem(params, function(err, data) {
    if(err) {
        console.log(err, err.stack);
    } else {
        console.log(data);
    }
});
データ取得
AWS.config.endpoint = new AWS.Endpoint('http://localhost:8000');
var dynamodb = new AWS.DynamoDB();
var params = {
    TableName: 'test',
    Key: {
        'num':{N: '2'}
    }
};
dynamodb.getItem(params, function(err, data) {
    if(err) {
        console.log(err, err.stack);
    } else {
        console.log(data);
    }
});
データ更新
AWS.config.endpoint = new AWS.Endpoint('http://localhost:8000');
var dynamodb = new AWS.DynamoDB();
var params = {
    TableName: 'test',
    Key: {
        'num':{N: '2'}
    },
    ExpressionAttributeNames: {
        '#d': 'data'
    },
    ExpressionAttributeValues: {
        ':newData':{S: '2nd'}
    },
    UpdateExpression: 'SET #d = :newData'
};
dynamodb.updateItem(params, function(err, data) {
    if(err) {
        console.log(err, err.stack);
    } else {
        console.log(data);
    }
});
データ削除
AWS.config.endpoint = new AWS.Endpoint('http://localhost:8000');
var dynamodb = new AWS.DynamoDB();
var params = { 
    TableName: 'test',
    Key: {
        'num':{N: '2'}
    }
};
dynamodb.deleteItem(params, function(err, data) {
    if(err) {
        console.log(err, err.stack);
    } else {
        console.log(data);
    }
});
テーブル削除

ひと通り確認が終わったのでテーブルを削除する。

$ aws dynamodb delete-table --table-name test \
--endpoint-url http://localhost:8000
{
    "TableDescription": {
        "TableArn": "arn:aws:dynamodb:ddblocal:000000000000:table/test", 
        "AttributeDefinitions": [
            {
                "AttributeName": "num", 
                "AttributeType": "N"
            }
        ], 
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0, 
            "WriteCapacityUnits": 1, 
            "LastIncreaseDateTime": 0.0, 
            "ReadCapacityUnits": 1, 
            "LastDecreaseDateTime": 0.0
        }, 
        "TableSizeBytes": 14, 
        "TableName": "test", 
        "BillingModeSummary": {
            "LastUpdateToPayPerRequestDateTime": 0.0, 
            "BillingMode": "PROVISIONED"
        }, 
        "TableStatus": "ACTIVE", 
        "KeySchema": [
            {
                "KeyType": "HASH", 
                "AttributeName": "num"
            }
        ], 
        "ItemCount": 1, 
        "CreationDateTime": 1571411233.367
    }
}

AWS CLIやJavaScriptを使って他にも様々な操作ができるので以下のドキュメントを参照のこと。

DynamoDBローカルのバックエンドはSQLite

DynamoDBローカルのバックエンドはSQLiteを使っているのでSQLiteの各種ツールで中身を参照することができる。

ファイルはDynamoDBローカルのサーバを起動したカレントディレクトリに「shared-local-instance.db」として作成される。

ファイルは一つだけなのでバックアップや別ホストへのコピーも容易に行える。

$ sqlite3 shared-local-instance.db 
SQLite version 3.22.0 2018-01-22 18:45:57
Enter ".help" for usage hints.

sqlite> .tables
cf    dm    sm    ss    test  tr    us  

テーブルはそれぞれ以下の用途に使われている。

cf

DynamoDBローカルのバージョン

sqlite> .schema cf
CREATE TABLE cf (version TEXT);

sqlite> select * from cf;
v2.4.0
dm

DynamoDBローカル内に定義したテーブルの情報

sqlite> .schema dm
CREATE TABLE dm (TableName TEXT, CreationDateTime INTEGER, LastDecreaseDate INTEGER, LastIncreaseDate INTEGER, NumberOfDecreasesToday INTEGER, ReadCapacityUnits INTEGER, WriteCapacityUnits INTEGER, TableInfo BLOB, BillingMode INTEGER DEFAULT 0, PayPerRequestDateTime INTEGER DEFAULT 0, PRIMARY KEY(TableName));

sqlite> select * from dm;
test|1571441645458|0|0|0|1|1|{"Attributes":[{"AttributeName":"num","AttributeType":"N"}],"GSIList":[],"GSIDescList":[],"SQLiteIndex":{"":[{"DynamoDBAttribute":{"AttributeName":"num","AttributeType":"N"},"KeyType":"HASH","SQLiteColumnName":"hashKey","SQLiteDataType":"BLOB"}]},"UniqueIndexes":[{"DynamoDBAttribute":{"AttributeName":"num","AttributeType":"N"},"KeyType":"HASH","SQLiteColumnName":"hashKey","SQLiteDataType":"BLOB"}],"UniqueGSIIndexes":[]}|0|0
sm / ss / us

DynamoDB Stream管理情報

sqlite> .schema sm
CREATE TABLE sm (StreamID TEXT, StreamStatus TEXT, TableName TEXT, StreamInfo BLOB, CreationDateTime INTEGER, DeletionDateTime INTEGER, PRIMARY KEY(StreamID));

sqlite> .schema ss
CREATE TABLE ss (StreamID TEXT, ShardID TEXT, CreationDateTime INTEGER, DeletionDateTime INTEGER, InitialSequenceNumberStart INTEGER, SequenceNumberEnd INTEGER, ParentShardID TEXT, PRIMARY KEY(ShardID));

sqlite> .schema us
CREATE TABLE us (StreamID TEXT, ShardID TEXT, SequenceNumber INTEGER, CreationDateTime INTEGER, StreamRecord BLOB, OperationType TEXT, PRIMARY KEY(SequenceNumber));

sqlite> select * from sm;

sqlite> select * from ss;

sqlite> select * from us;
test

今回の導入手順でテスト用に作成したテーブル

sqlite> .schema test
CREATE TABLE IF NOT EXISTS "test" (hashKey BLOB DEFAULT NULL, hashValue BLOB NOT NULL, itemSize INTEGER DEFAULT 0, ObjectJSON BLOB NOT NULL, PRIMARY KEY(hashKey));
CREATE INDEX "test*HVI" ON "test" (hashValue);

sqlite> select * from test;
>=E10000000000000000000000000000000000000|-��,q����	�T	�����|14|{"data":{"S":"first"},"num":{"N":"1"}}
tr

トランザクション管理情報

sqlite> .schema tr
CREATE TABLE tr (TransactionId TEXT, TransactionSignature BLOB, CreationDateTime INTEGER, PRIMARY KEY(TransactionId));

sqlite> select * from tr;

参考サイト

2019/03/06更新

セキュリティ要件などでCloudWatch Logsの特定のロググループを特定のIPからのみ参照可能にしたい場合は、IAMの以下の2つのポリシーを対象となるユーザやグループにアタッチすればよい。

※ ただしAWSの他のサービスからアクセスが必要な場合はこの方法は使えないので注意が必要

1. CloudWatchLogsFullAccessやCloudWatchLogsReadOnlyAccessのようなCloudWatch Logsにアクセスできるポリシー、あるいはそれに類するポリシー

2. 指定したロググループに対して特定のIPからのみアクセス可能とするポリシー(新規作成)

例) 「ng」というロググループに対してSourceIpで指定したIPからのみアクセス可能とする

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Action": "logs:DescribeLogStreams",
            "Resource": "arn:aws:logs:<リージョン>:<AWSアカウント>:log-group:ng:*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "xxx.xxx.xxx.xxx"
                }
            }
        },
        {
            "Effect": "Deny",
            "Action": "logs:FilterLogEvents",
            "Resource": "arn:aws:logs:<リージョン>:<AWSアカウント>:log-group:ng:*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "xxx.xxx.xxx.xxx"
                }
            }
        }
    ]
}

実行例

例えば以下の2つのロググループ/ログストリームが存在する状態で上記のポリシーを適用する。

ok/ok_test
ng/ng_test

この状態でロググループの一覧表示とログストリームの参照・検索を行うと以下のような挙動となる。

ロググループ一覧表示(両方表示される)
$ aws logs describe-log-groups
{
    "logGroups": [
        {
            "arn": "arn:aws:logs:xxxxxxxxxxxx:xxxxxxxxxxxx:log-group:ng:*", 
            "creationTime": 1551426635634, 
            "metricFilterCount": 0, 
            "logGroupName": "ng", 
            "storedBytes": 0
        }, 
        {
            "arn": "arn:aws:logs:xxxxxxxxxxxx:xxxxxxxxxxxx:log-group:ok:*", 
            "creationTime": 1551426626316, 
            "metricFilterCount": 0, 
            "logGroupName": "ok", 
            "storedBytes": 0
        }
    ]
}
ログストリーム参照(どのIPからでも参照可能)
$ aws logs describe-log-streams --log-group-name ok
{
    "logStreams": [
        {
            "creationTime": 1551426648336, 
            "arn": "arn:aws:logs:xxxxxxxxxxxx:xxxxxxxxxxxx:log-group:ok:log-stream:ok_test", 
            "logStreamName": "ok_test", 
            "storedBytes": 0
        }
    ]
}
ログストリーム参照(特定のIPからのみ参照可能)
$ aws logs describe-log-streams --log-group-name ng
An error occurred (AccessDeniedException) when calling the DescribeLogStreams operation: User: arn:aws:iam::xxxxxxxxxxxx:user/xxxx is not authorized to perform: logs:DescribeLogStreams on resource: arn:aws:logs:xxxxxxxxxxxx:xxxxxxxxxxxx:log-group:ng:log-stream: with an explicit deny
ログストリーム検索(どのIPからでも参照可能)
$ aws logs filter-log-events --log-group-name ok
{
    "searchedLogStreams": [], 
    "events": []
}
ログストリーム検索(特定のIPからのみ参照可能)
$ aws logs filter-log-events --log-group-name ng
An error occurred (AccessDeniedException) when calling the FilterLogEvents operation: User: arn:aws:iam::xxxxxxxxxxxx:user/xxxx is not authorized to perform: logs:FilterLogEvents on resource: arn:aws:logs:xxxxxxxxxxxx:xxxxxxxxxxxx:log-group:ng:log-stream: with an explicit deny

参考サイト

2017/7/10更新

対応バージョン: 10.12.5

macOS SierraにAWS CLI環境を構築する手順を示す。

準備

最初にMac App StoreからXcodeをインストールしておく。

AWS CLIインストール

AWS CLIのインストールにはPythonのパッケージマネージャであるpipコマンドを使用するのでpip -> AWS CLIの順でインストールする。

pipインストール
% sudo easy_install pip

% pip --version
pip 9.0.1 from /Library/Python/2.7/site-packages (python 2.7)
awscliインストール
% pip install awscli --upgrade --user

PATH環境変数に以下のパスを追加
$HOME/Library/Python/2.7/bin

% aws --version
aws-cli/1.11.117 Python/2.7.10 Darwin/16.6.0 botocore/1.5.80

JSON Lintインストール

AWS CLIにおけるJSONデータの表示・加工に便利なJSON Lintをインストールする。

JSON LintのインストールにはNode.js付属のnpmコマンドを使用するのでNode.js -> JSON Lintの順でインストールする。

nodebrewインストール
% curl https://raw.githubusercontent.com/hokaccha/nodebrew/master/nodebrew | perl - setup

PATH環境変数に以下のパスを追加
$HOME/.nodebrew/current/bin

% nodebrew selfupdate
Node.jsインストール
% nodebrew install latest

PATH環境変数に以下のパスを追加
$HOME/.nodebrew/node/v8.1.3/bin

% node -v
v8.1.3

% npm -v
5.0.3
JSON Lintインストール
% npm install jsonlint

PATH環境変数に以下のパスを追加
$HOME/node_modules/.bin

% jsonlint -v
1.6.2

% echo '{"key":"value"}' | jsonlint
{
  "key": "value"
}

初期設定

Access KeyとSecret Access Keyを設定してAWS CLIが使用できるようにする。

% aws configure list
      Name                 Value          Type  Location
      ----                 -----          ----  --------
   profile             <not set>          None  None
access_key             <not set>          None  None
secret_key             <not set>          None  None
    region             <not set>          None  None

% aws configure
AWS Access Key ID [None]: xxxxxxxxxxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxx
Default region name [None]: ap-northeast-1 <- 東京リージョンの場合
Default output format [None]: json <- 他にtable、textが指定可能。好みに応じて指定

% aws configure list
      Name                 Value          Type  Location
      ----                 -----          ----  --------
   profile             <not set>          None  None
access_key  ******************** shared-credentials-file
secret_key  ******************** shared-credentials-file
    region        ap-northeast-1   config-file  ~/.aws/config

設定は以下の2ファイルに格納されるが、それぞれのファイルの[default]の部分に一意の名前を付ければ複数の設定(プロファイル)を共存させられる。その場合、awsコマンドを実行する時に--profileに続けてプロファイル名を指定する。

$HOME/.aws/config

コマンド出力結果フォーマットとリージョン

[default]
output = json
region = ap-northeast-1
$HOME/.aws/credentials

認証情報

[default]
aws_access_key_id = xxxxxxxxxxxxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxx

動作確認

インスタンス一覧取得
% aws ec2 describe-instances | jq '.Reservations [] .Instances [] .InstanceId'
インスタンス起動
% aws ec2 start-instances --instance-ids i-xxxxxxxxxxxxxxxxx
インスタンス停止
% aws ec2 stop-instances --instance-ids i-xxxxxxxxxxxxxxxxx
インスタンス削除
% aws ec2 terminate-instances --instance-ids i-xxxxxxxxxxxxxxxxx
IAMユーザ取得
% aws iam list-users
{
    "Users": [
        {
            "UserName": "dev", 
            "Path": "/", 
            "CreateDate": "2017-07/10T04:34:38Z", 
            "UserId": "xxxxxxxxxxxxxxxxxxxxx", 
            "Arn": "arn:aws:iam::xxxxxxxxxxxx:user/dev"
        }
    ]
}

% aws iam get-user --query 'User.Arn'
"arn:aws:iam::xxxxxxxxxxxx:user/dev"

参考サイト

2017/08/15更新

対応バージョン: Aurora(MySQL-Compatible) 5.6.10a

AuroraのスロークエリログのタイムスタンプはSQL開始時刻ではなくSQL終了時刻となる。

例)

16:44:00にSQL開始
mysql> select now(); select sleep(10);
+---------------------+
| now()               |
+---------------------+
| 2017-08-15 07:44:00 |
+---------------------+
1 row in set (0.00 sec)

+-----------+
| sleep(10) |
+-----------+
|         0 |
+-----------+
1 row in set (10.00 sec)
スロークエリログの内容(タイムスタンプはSQL開始時刻ではなくSQL終了時刻)
# Time: 170815 7:44:10
# User@Host: awsuser[awsuser] @ [172.31.29.5] Id: 941
# Query_time: 10.000223 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0
SET timestamp=1502783050;
select sleep(10);

参考サイト

2017/08/12更新

対応バージョン: Aurora(MySQL-Compatible) 5.6.10a

Amazon Auroraで出力されるログはデフォルトで以下の3種類がある。
error/mysql-error-running.log{,.YYYY-MM-DD.nn}
error/mysql-error.log
external/mysql-external.log

Auroraではこれに加え、クエリログ(general_log)とスロークエリログ(slow_query_log)も出力することができる。

手順は以下の通りで、パラメータグループで設定することができる。尚、これら設定はAuroraインスタンスを再起動することなく動的に反映可能である。(反映にかかる時間は10秒程度)

出力先

まずログの出力先(log_output)がデフォルトで"TABLE"になっているので"FILE"に変更する。

続いて各ログの出力設定を行う。片方だけの設定も両方の設定もどちらも可能である。

クエリログ(general_log)

general_logの値を1(ON)に設定する。デフォルトは0(OFF)。

これにより以下のログが新たに出力されるようになる。

general/mysql-general.log

スロークエリログ(slow_query_log)

slow_query_logを1(ON)に設定する。デフォルトは0(OFF)。

さらにクエリが何秒以上かかった時にログを出力させるかをlong_query_timeで設定する。デフォルトは10(秒)で、0(秒)にすると遅延ありなしに関係なく全てのクエリがログに出力されるようになる。

これにより以下のログが新たに出力されるようになる。

slowquery/mysql-slowquery.log

参考サイト