AWS IoTとRuby製MQTTクライアントでPub/Subしてみた


## この文書について

先日ベータ版が公開されたAWS IoT（AWS上でサーバーインスタンスを自分で用意する事なくMQTTブローカー（+α）のサービスを利用できる）をRubyクライアント([ruby-mqtt](https://github.com/njh/ruby-mqtt))から試してみた個人的なメモです。

[本家のクイックスタート](https://docs.aws.amazon.com/iot/latest/developerguide/iot-quickstart.html)を見つつ、[AWS IoT Message BrokerのMQTTでpub/subをやってみた](http://dev.classmethod.jp/cloud/aws/pub-sub-with-aws-iot-over-mqtt/)も参考にさせて頂きつつやってた覚書ですが、現時点（2015/10/14）でAWS IoTはベータ版であり、今後サービスの仕様が変わる可能性があるため、できるだけ本家のドキュメントを参照される事をおすすめします。

AWS IoTではクライアント側のSDKも提供されているようですが、今回はオープンソースで公開されているMQTTクライアントを使ってPub/Subを試してみます。

AWS IoTの概念については[Learn how AWS IoT works with this interactive tutorial](https://ap-northeast-1.console.aws.amazon.com/iot/home?region=ap-northeast-1#/tutorial/help?step=1)で図解入りでわかりやすく説明されているので、こちらを参照。以下は同ページのスクリーンショット。RGBのスイッチの状態がMQTTブローカーを通して別のデバイスやストレージ、AWS上の別のサービスと連携する様子が示されています。

![スクリーンショット 2015-10-14 11.09.11.png](https://qiita-image-store.s3.amazonaws.com/0/13835/ba79a1a6-0328-caeb-283a-af05d96637c7.png "スクリーンショット 2015-10-14 11.09.11.png")



## AWS CLIの準備

すでに最新のAWS CLIをインストール済みの場合は、この項目は飛ばしてかまいません。

```
$ sudo pip install awscli --upgrade
```

なお、AWS CLIの詳細なインストール方法に関しては[Getting Set Up with the AWS Command Line Interface](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html)を参照のこと。

## Thingを登録する

MQTTクライアントからMQTTブローカーに接続するには `Thing` を登録する必要があるとのこと。以下のようにして ```sample_things``` という名前で登録しました。

```
$ aws iot create-thing --thing-name "sample_things"
```

現在登録されているThingのリストは以下のコマンドで確認できます。

```
$ $ aws iot list-things

{
    "things": [
        {
            "attributes": {}, 
            "thingName": "sample_things"
        }, 
        {
            "attributes": {}, 
            "thingName": "my_things"
        }
    ]
}
```

先ほど登録した `sample_things` の他に以前私が登録した `my_things` も表示されています。

## AWS IoTとThing間でのセキュアな通信のための準備

### 証明書の用意

先ほど登録した `Thing` と `AWS IoT` 間のセキュアな通信のために、X.509証明書を作成します。

以下のコマンドで、証明書とキーペアを `cert.json` というファイルに書き出します。

```
$ aws iot create-keys-and-certificate --set-as-active > cert.json
```

`cert.json` の中身はJSONフォーマットですので、ファイルから秘密鍵と公開鍵をそれぞれ `thing-public-key.pem` と `private-key.pem` というファイルに書き出します。
(なお、ローカルに `jq` コマンドがインストールされていない場合は `$ brew install jq` すること)

```
$ cat cert.json | jq .keyPair.PublicKey -r > thing-public-key.pem
$ cat cert.json | jq .keyPair.PrivateKey -r > private-key.pem
```

証明書を `cert.pem` という名前のファイルとして書き出します。

```
$ aws iot​ describe-certificate --certificate-id "<here-is-your-certificate-id>" --output text --query certificateDescription.certificatePem > cert.pem
```

'&lt;here-is-your-certificate-id>' の部分に先ほどの `cert.json` の最終行付近にある `"certificateId"` として与えられた文字列を入れて実行します。

### AWS IoTポリシーを作成して証明書に紐付ける

以下のような内容で `policy.json` というファイルを新規に作成します。

```policy.json
{
    "Version": "2012-10-17", 
    "Statement": [{
        "Effect": "Allow",
        "Action":["iot:*"],
        "Resource": ["*"]
    }]
}
```

作成したファイルへのパスをコマンド引数に渡して `PubSubToAnyTopic` という名前のポリシーを作成します。

```
$ aws iot create-policy --policy-name "PubSubToAnyTopic" --policy-document file://policy.json
```

作成した証明書とポリシーを紐付けます。

```
$ aws iot attach-principal-policy --principal "<certificate-arn>" --policy-name "PubSubToAnyTopic"
```

'&lt;certificate-arn>' のところには、先ほどの `cert.json` 中で `"certificateArn"` として渡された文字列( `"arn:aws:iot:ap-northeast-1::..."` ) を入れます。

### 証明書をThingに紐付ける

さて、最後に証明書とThingを紐付けます。

```
$ aws iot attach-thing-principal --thing-name "<thing-name>" --principal "<certificate-arn>"
```

`<thing-name>` のところには最初に登録したThingの名前が入ります。今回で言えば `sample_things` になりますね。

`<certificate-arn>` のところには、先ほどと同じく `cert.json` の中で、`"certificateArn"` として与えられた文字列（ `"arn:aws:iot:ap-northeast-1::..."` ）が入ります。

お疲れ様でした、これでAWS IoTを使ってPub/Subする準備が整いました。

## Ruby ClientでPub/Subしてみる

### MQTT クライアントライブラリのインストール

AWSのクイックスタートではMosquittoのPub/Subクライアントを使ってテストしていますが、私はRubyプログラマなのでRubyを使ってやってみます。

MQTTのRubyクライアントとしては[github.com/njh/ruby-mqtt](https://github.com/njh/ruby-mqtt)があります。

インストールはgemで

```
$ gem install mqtt
```

### ルート証明書のダウンロード

また、ルート証明書を `rootCA.pem` というファイル名でカレントディレクトリに落としておきます。

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

### MQTTブローカーエンドポイントの確認

それと、MQTTブローカーのエンドポイントはAWSのアカウントごとに用意されています。自分のアカウントのエンドポイントを確認するには、以下のようにします。

```
$ aws iot describe-endpoint
{
    "endpointAddress": "AAAAAAAAAAA.iot.ap-northeast-1.amazonaws.com"
}
```

この場合は `AAAAAAAAAAA.iot.ap-northeast-1.amazonaws.com` が自分のアカウントのエンドポイントということになります。

ポートは共通で `8883` です。

### Ruby製Subscriberの用意

データを待機するSubscrberは以下のように書きました。

```ruby:sub.rb
require "mqtt"

MQTT::Client.connect(host: "AAAAAAAAAAA.iot.ap-northeast-1.amazonaws.com",
                     port: 8883,
                     ssl: true,
                     cert_file: "cert.pem",
                     key_file: "thing-private-key.pem",
                     ca_file: "rootCA.pem") do |client|

  client.subscribe("topic/test")
  topic,message = client.get #ここでブロックする
  p [topic, message]
end
```

### Ruby製Publisherの用意

データを送信するPublisherは以下のように

```ruby:pub.rb
require "mqtt"

MQTT::Client.connect(host: "AAAAAAAAAAA.iot.ap-northeast-1.amazonaws.com",
                     port: 8883,
                     ssl: true,
                     cert_file: "cert.pem",
                     key_file: "thing-private-key.pem",
                     ca_file: "rootCA.pem") do |client|

  client.publish("topic/test", "From Ruby!")
end

```

### 実際にPub/Subしてみる

まずは `sub.rb` を実行。

```
$ ruby sub.rb

```

この時点でSubscriberがデータを待機してブロックします。

----

続いて `pub.rb` を実行。

```
$ ruby pub.rb 
```

`pub.rb` はデータをパブリッシュしたらすぐに終了します。直後にブロックしていた `sub.rb` 側で以下のように受信したデータを表示します。

```
$ ruby sub.rb
["topic/test", "From Ruby!"]
```

## 料金

AWS IoTの課金体系については、この文書を書いている段階で以下のように[公表](https://aws.amazon.com/jp/iot/pricing/)されています。

* **US East (N. Virginia):**	$5 per million messages
* **US West (Oregon):** $5 per million messages
* **EU (Ireland):**	$5 per million messages
* **Asia Pacific (Tokyo):**	$8 per million messages

東京リージョンだけ、ちょっとお高いですね。ただ実装するアプリケーションがリアルタイム性を重視するなら、やはり日本国内では東京リージョンを使うことになるでしょう。

## 感想など

最初の手順が若干必要ですが、順を追って一つづつやっていけば簡単にMQTTブローカーを利用できるようになります。EC2で自分でサーバーインスタンスを立ち上げる必要もなく、またサーバーインスタンスの保守・運用についても考慮することなくセキュリティを考慮されたMQTTブローカーが使用できるというのは、これからMQTTを使ったアプリケーション開発を始める人にとってはとっても楽チンで、とりあえずMQTTを使って端末間の通信を実現したい場合には第一候補になるサービスと感じました。

ただ、本当にシビアな、多数のクライアントから多数のメッセージが送られてくるような場合は、どこかで自前のサーバーを用意したほうが安くつく分岐点があるのだろうと予想しますが、その分岐点がどこになるかは今の私にはわかりません。

AWS IoTは純粋なMQTTブローカーとしても使用できますが、やはりAWSでMQTTする場合の最大の魅力は、AWSの他のサービスとの連携だと思います。次回以降でそのあたりも色々やってみたいと思います。それと `Shadow` という機能も気になりますね。こっちも色々やってみます。