20190415のAWSに関する記事は20件です。

DeepRacerの報酬関数トレースログをLogsInsightsでパース

DeepRacerのSageMakerサンプルノートブックを動かすと、CWLogsに報酬関数のトレースログが吐かれます。これをLogsInsightsでパースしてみました。

サンプルノートブック 自体は下記の記事を参考に動かしています。

吐かれるログ

ログはデフォルトで以下のCloudWatchlogsに吐かれる。

ロググループ名

/aws/robomaker/SimulationJobs

対象ログイベント

  • SIM_TRACE_LOGで始まるログ
  • deepracer_env.py の def infer_reward_state の中で「入力パラメータ」と「結果の報酬」を出力している

ソースコードを見ると報酬関数に渡しているものとは若干違うパラメータもログ出力していますが、今回はそのままパースしてみます。

デフォルトの出力

どれがどのパラメータかパッと見ではわからない...

logs.png

Logs Insights でパース

実装コードから、どの出力がどのパラメータなのか確認し、insightsのparseコマンドに反映。

トレースログをパースして100行表示

クエリ

fields @message
| filter @message like /^SIM_TRACE_LOG/
| parse @message "SIM_TRACE_LOG:*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*" as episodes,steps,x,y,car_orientation,steering,throttle,action_taken,reward,progress,waypoint_action,done,on_track,current_progress,initidxWayPoint,closest_waypoint_index,track_length,time
| sort by time
| limit 100

出力結果

これでまともに見れそうな気がしてきました。

query1.png

条件に合うログのみ表示してみる

50steps以降で、スロットル0.5未満で1以上の報酬を与えたログを100行表示してみます。

クエリ

fields @message
| filter @message like /^SIM_TRACE_LOG/
| parse @message "SIM_TRACE_LOG:*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*" as episodes,steps,x,y,car_orientation,steering,throttle,action_taken,reward,progress,waypoint_action,done,on_track,current_progress,initidxWayPoint,closest_waypoint_index,track_length,time
| filter (steps>50 and throttle<0.5 and reward>=1)
| sort by time
| limit 100

出力結果

query2.png

補足

progress(コード上はtotal_progressという変数)はドキュメント上、0.0〜1.0が入るように読めたが

progress float [0,1] Percentage of track completed.

実際は0〜100のレンジっぽい値が出力された。コードを確認すると積算用の進捗をcurrent_progress *= 100.0 していたり気になりましたがパラメータの詳細についてはこの記事で触れません。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSでデータ分析環境を構築する

自分用の備忘メモになります。
AWSのサービスの中には、機械学習向けにSagemakerがありますが、EC2インスタンスを用いて地道に分析環境構築してみようとチャレンジしてみました。
Sagemakerを利用するより、価格がお手軽になるのではないかと期待しています。

1.EC2インスタンスのGPUの制限解除申請
GPUを利用する際には、利用申請が必要になります。利用申請は、日本語で大丈夫です。日本のAWSで申請受付されてから、US本国のAWSでチェックされます。
私が実際に2019年3月末に行った際は、申請から承認まで3日間(営業日)でした。日本側は即USへ申請した旨の返事あり。そこからUSの承認まで数日かかりました。
なお、GCPでは使用申請はないものの、無料枠を消化しないとGPUの使用制限解除にならないことがわかりました。

2.ubuntu 1604 のリモートデスクトップ接続環境の構築
GPUを用いて分析を行うには、CUDA、Anaconda、Chainer等々のPythonライブラリ等のインストールと設定が必要になります。AWSの場合、AMIといってあらかじめ必要なツールを用意してくれるセットがあります。
(利用目的によって、OSをwindowsにしたり、SQLが用意されていたり等々)
2019年4月時点、Ubuntuで、データ分析、深層学習を行うAMIは、Ubuntuバージョンは、16.04になります。Ubuntu 16.04のインスタンスに対して、自分のPCからWINDOWS10を用いて、リモートデスクトップ接続実行したい場合は、下記対応が必要になります。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/connect-to-ubuntu-1604-windows/

3.ブラウザツールのインストール
anaconda navigatorを用い、JupyterNotebookを起動する場合は、ブラウザツールのインストールが必要になります。今回は、Firefoxをインストールしました。
Firefoxのインストール方法は下記URLになります。
https://qiita.com/rokusyou/items/8089b7495119b7c8e29b

4.日本語化対応
英語でUbuntu環境が作成されていますので、下記URLで日本語対応いたしました。
https://qiita.com/hachisukansw/items/154b5349f99a7152fd46

5.困ったこと
(1)リモートデスクトップ接続した際に、“真っ黒画面”が表示される。
リモートデスクトップの設定を試したのですが何も改善されませんでした。AMIのツール類、GUI等でインスタンスのボリュームの使用率が100%になっていたのが原因と推測されます。ストレージのサイズはデフォルト75GiBでは足りませんでした。
(結果的に、ストレージを300GiBで再作成して解消しました。)
ストレージを追加して、下記URLでパーテーションの容量を大きくしようと試みたのですがうまくいかず(これは、私のLinuxの知識不足が大きな原因です。)、インスタンスを再作成しました。
 https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html

(2)キーボード配列の設定
Anaconda Navigatorでは、日本語用のキーボードの配列で入力できませんでした。先にインストールしたFirefoxでは、日本語キーボード配列で正しく入力できたのですが・・・。
以下URLに対応方法がありますが、未実施です。
https://geek-memo.com/ubukey/
https://tech-mmmm.blogspot.com/2017/08/xubuntu-1604xrdprdp.html

※ご参考1
ここまで環境を作成するのに、p2xlargeのインスタンスサイズで100円程度と思います。
(再作成したり、他に色々動かしたので推測です。今のところ7.4$です。)

※ご参考2
Sagemakerでml.p2.xlargeの3つのGPUインスタンスを30時間使ったら260$でした。

GCPでも同様にインスタンス構築からチャレンジしてみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Web Services 基礎からのネットワーク&サーバ構築のまとめ Chapter1

Amazon Web Services 基礎からのネットワーク&サーバ構築の個人的な学習記録として、この記事を書いています。問題があるようでしたら消します。

1-1 ネットワークとサーバーの知識を付けておくメリット

  • 障害が起きた時に、どこで起きてるのか分かる
  • 一人で色々できるようになる

1-2, 1-3 ネットワークとサーバーについて

サーバー

サーバーには用途別で色々種類がある。「Webサーバー」、「メールサーバー」、「データベースサーバー」などなど。その用途に合わせて、OSからソフトウェアをインストールする必要がある。

  • WEBサーバー
    • Apache
    • nginx
  • データベースサーバー
    • MySQL
    • PostgreSQL
  • メールサーバー
    • Sendmail
    • Postfix

ネットワーク

用語
  • TCP/IP:インターネットと接続する為のプロトコル。
  • IPアドレス:サーバーに振り当てるピリオドで区切られた4つの数字で住所と同じ役割。重複することはない。
  • DNSサーバー:ドメイン名とIPアドレスを関連付けてくれるサーバー。
公開されたネットワークと隠されたネットワーク

「パプリックサブネット」と呼ばれる公開されたネットワークと「プライベートネットワーク」と呼ばれるインターネットに直接接続しないネットワークがある。前者には見てもらう為のWebサーバー、後者には見られたくないDBサーバーを置く。

NAT

隠しておきたいけど、DBサーバーとやり取りする為にはインターネットと接続する必要がある。そんな時にNATを使うと、片方だけの接続ができるようになり、外から見られないで済む。

1-4 AWS

こいつのおかげで、物理的にサーバーとかルーターを用意しなくてもネットワーク、サーバーが構築できる。

ネットワークとVPC

VPC = ネットワークの領域
EC2 = サーバー
サーバー1つをインスタンスと呼ぶ。

感想

1章を読んでみた感じ、ネットワークとサーバー、AWSを全く知らない人向けって感じでしたね。個人的には良い復習になった章でした。

Chapter2

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CLI の全てのコマンドをMFA認証必須にする

AWS CLIでMFA認証を必須にする

AWS CLIはアクセスキーを使用して認証するのでアクセスキーが流出してしまうと
そのユーザの権限でなんでもできてしまう。権限を絞ることで被害を抑えることもできるが
AWS CLIでもMFAの認証を行うこともできる。

明示的な拒否のポリシーの方が強いのでMFA認証されるまでは全てのアクションが拒否されるが
認証後は全てのアクションが許可される。
AWS CLIでMFA認証するためのaws sts get-session-tokenを実行するのに必要な権限はない

policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        },
        {
            "Effect": "Deny",
            "Action": "*",
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": false
                }
            }
        }
    ]
}

認証方法

MFA認証するにはget-session-tokenでセッショントークンを取得して環境変数に入れる必要がある。

console
aws sts get-session-token --serial-number arn:aws:iam::アカウントID:mfa/ユーザ名 --token-code ワンタイムパスワード

{
    "Credentials": {
        "AccessKeyId": "xxxxxxxxxxxxxxxxxx",
        "SecretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "SessionToken": "xxxxxxxxxxxxxxxxxxxxxxxx", 
        "Expiration": "yyyy-mm-ddThh:MM:ssZ"
    }
}



  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CLI ローカルからS3へディレクトリ構造を保ちながらコピーする

bucketバケットに folder1/file1と folder2/file2をコピーする

$ tree
.
├── folder1
│   └── file1
└── folder2
    └── file2
$ find folder* -type f | xargs -I{} aws s3 cp {} s3://bucket/{}/ --recursive
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(メモ) CloudFormation StackSetsでIAM Policyを展開するときに、Policy記述内に展開先のアカウントIDを記載する方法

背景

CloudFormation StackSetsでIAM Policyを展開するとき、Policy記述内に展開先のアカウントIDを記載しないといけない場合にどうしようと一瞬考えたのでメモ。例えばResourceエレメントを使う場合はARNにリージョンやアカウントIDが入るため、展開先のアカウントごとに値が異なる。

解決策

単純にCloudFormationの擬似パラメータ(AWS::AccountIdとかAWS::Regionとか)を使えば良いだけだった。
これで展開先アカウント/リージョンの値に変換される。

サンプル
AWSTemplateFormatVersion: "2010-09-09"
Resources: 
  TestPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: TestPolicy
      Description: test policy
      Path: /
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          -
            Sid: DenyAdminRoleChange
            Effect: Deny
            Action: iam:*
            Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/AdminRole

蛇足

IAM Boundaryの設定展開がきっかけでこれを考えていたが、最初に以下のようにWildcardを試してみたがうまくいかず。
特定のPermissions Boundaryを付与していないとRoleのCreateやらPolicyの付与ができないようにするステートメントのCondition句でアカウントIDをワイルドカードにした場合、適切なBoundaryを付与してもRole作成が拒否されてしまった。
これがなぜかは今の所不明。。。この書き方自体は問題ないはずだが。。。

ステートメント例
<...snip...>
       {
            "Sid": "DenyCreateOrChangeRoleWithoutBoundary",
            "Effect": "Deny",
            "Action": [
                "iam:CreateRole",
                "iam:PutRolePolicy",
                "iam:DeleteRolePolicy",
                "iam:AttachRolePolicy",
                "iam:DetachRolePolicy",
                "iam:PutRolePermissionsBoundary"
            ],
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "iam:PermissionsBoundary": "arn:aws:iam::*:policy/ExampleBoundaryPolicy"
                }
            }
        },
<...snip...>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoでAmazon S3をKVSとして使う

これはなに

永続化が必要なバイナリKVSとしてAmazon S3を使いたい場合があります。
セッションの取り回しや、aws.WriteAtBufferやエラーがトリッキーなのでスニペットです。

package s3kvs

import (
        "bytes"

        "github.com/aws/aws-sdk-go/aws"
        "github.com/aws/aws-sdk-go/aws/awserr"
        "github.com/aws/aws-sdk-go/aws/session"
        "github.com/aws/aws-sdk-go/service/s3"
        "github.com/aws/aws-sdk-go/service/s3/s3manager"
)

var PersistentS3Store = &S3Store{}

type S3Store struct {
        Bucket     string
        Uploader   *s3manager.Uploader
        Downloader *s3manager.Downloader
}

func (s *S3Store) Init(bucket, region string) (err error) {
        s.Bucket = bucket

        sess, err := session.NewSession(&aws.Config{
                Region: aws.String(region),
        })
        if err != nil {
                return
        }

        s.Uploader = s3manager.NewUploader(sess)
        s.Downloader = s3manager.NewDownloader(sess)

        return
}

func (s *S3Store) Set(key string, body []byte) (err error) {
        params := &s3manager.UploadInput{
                Bucket: aws.String(s.Bucket),
                Key:    aws.String(key),
                Body:   bytes.NewReader(body),
        }

        _, err = s.Uploader.Upload(params)
        return
}

func (s *S3Store) Get(key string) ([]byte, error) {
        buffer := aws.NewWriteAtBuffer([]byte{})

        _, err := s.Downloader.Download(buffer, &s3.GetObjectInput{
                Bucket: aws.String(s.Bucket),
                Key:    aws.String(key),
        })
        if err != nil {
                if aerr, ok := err.(awserr.Error); ok && aerr.Code() == "NoSuchKey" {
                        return nil, nil
                }
                return nil, err
        }

        return buffer.Bytes(), nil
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cloudwatch Logs Insights の parse 関数の使い方

最近プロダクション環境でFARGATE on ECSを導入しました。この場合ログはCloudwatch Logsに送られることになります。
ログから簡単なデータ集計をしたい場合に昨年発表されたCloudwatch Logs Insightsが使えるのではないかと思い試してみたところ、非常に便利だった、かつちょっとparse関数に手間取ったので備忘録がてら残しておきます。

Cloudwatch Logs Insightsとは

(詳細は公式ドキュメントに任せて、便利なところを)
Cloudwatch LogsのデータをWebコンソール上から確認する場合、これまではLog Group内のLog Streamを指定しなければフィルタがかけられませんでした。
FARGATEで複数コンテナが動く場合、コンテナ1つ1つに対してLog Streamが作られるため、APIコンテナが4つ稼働していた場合、4つのログに対して同時にログのフィルタをかけることが(Webコンソール単体では)できませんでした。

Insightsを使うことで、Log Streamをまたいでクエリを発行できるようになり、前述の問題点が解消されます。
さらに、構造化(=JSON)ログを出力している場合はSQLライクな集計も簡単にできます。また、構造化されていないログを出している場合でも、今回説明するparse関数を使うことである程度集計を行うことができます。

やりたいこと

Go製のメッセージ配信APIサーバがあり、あるIDに対して何個のメッセージを配信したかをログに出しています。(通知に相当するものであり、量が多くなるためにデータベースには記録していません)

[GIN] 2019/04/14 - 18:35:55 | 200 | 387.381544ms | 10.0.0.167 | POST /api/v1/message/
2019/04/14 18:35:55.044306: [I] message(s) sent to 2 address(es) for id 01234567
2019/04/14 18:59:56.772482: [I] message(s) sent to 1 address(es) for id 98765432
[GIN] 2019/04/14 - 18:59:56 | 200 | 49.884µs | 10.0.1.163 | GET /

(フレームワークのログとアプリケーションのログの形式が違うのはいつか直したいところです)

2行目と3行目のログを抽出し、特定期間のidと送信したメッセージの数を集計したいです。

できたこと

まず始めに完成したクエリと、実行した画面サンプルを表示します。

fields @message
| filter @message =~ "message(s) sent to"
| parse @message /sent to (?<@nummsgs>\d+) address\(es\) for id (?<@cid>[a-zA-Z0-9]+)/
| stats sum(@nummsgs) by @cid

画面サンプルはちょっとだけ正規表現が違ったりするのでぼかしています。上のものがそのまま入ってるとお考え下さい。

image.png

ご覧のように、SQLライクな感じで集計ができています。メインでないので省きますが期間はクエリを書くボックスの右上から指定できています(もちろんクエリ内で指定する方法もある、はず)

やってること

1行目 fileds @message

fields関数を使い、Logsからもってくるフィールドを指定します。 サポートされるログと検出されるフィールド - Amazon CloudWatch Logs にある表では「その他のログタイプ」に相当します。今回欲しいのはログのテキストだけなので @message を指定します

2行目 filter @message =~ "message(s) sent to"

filter関数を使い、 欲しいデータのみにそぎ落としています。 =~like でも大丈夫ですし、正規表現で指定することも可能です

3行目 parse @message /sent to (?<@nummsgs>\d+) address\(es\) for id (?<@cid>[a-zA-Z0-9]+)/

使い方や注意点は後述しますが、parse関数を用いてログからデータを作っています。

4行目 stats sum(@nummsgs) by @cid

SQL(どちらかというとElasticsearchに近いですかね?)のようにグルーピングと集計を行っています。
少し補足ですが、実行イメージ画像の棒グラフの下に「34 records matched」となっており、結果は24recordsなので、ちゃんとグルーピングされていることもわかります。

parse関数の使い方、注意点

本題です。
CloudWatch Logs Insights クエリ構文 - Amazon CloudWatch Logs にparse関数の使い方は書いてあるのですが、非常に簡単なサンプルがかいてあるだけで、*が使えることしかわかりません。

ですが、英語版のドキュメントやStack Overflowを覗くと以下のようにもかけることがわかりました。

  • 正規表現の名前付きグループ(?<名前>正規表現)を使って次のクエリに渡せる

クエリを読めばわかるのでこれ以上の説明はないのですが、他にもはまったことがあるので記載します

  • 新たに作成するfieldに@idは使えない

何かとバッティングしているのでしょう。 @cidの部分を初めは@idにしていたのですが、クエリが失敗するエラーが出ました。

終わりに

月1の報告のためにこの集計がしたかったのですが、「ログ全部S3にもってって手元にDLしてから集計するか~」とか思っていたのですが、これで十分でした。

料金については以下のような感じなので、データ量が多い場合は注意が必要ではありますが、コンソール右上で期間が予め指定されており、普通にクエリを打つだけでは全走査は走らないので安心です。

料金はクエリごとに走査したログデータの量に対してかかります。US East (N. Virginia)の場合、$0.005/GB で、他のリージョンも同様の料金です。

新機能 – Amazon CloudWatch Logs Insights – 高速でインタラクティブなログ分析 | Amazon Web Services ブログ

Cloudwatch LogsからElasticsearchに流し込む方法も用意されていますが、簡単なものだとこれで十分ですね!スバラシイ!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS-CLI & CloudWatch Insightでログを集計する

概要

とある案件でCloudWatch Logsの集計をAWS-CLI & CloudWatch Insightを用いて行うことになったので、その方法を共有します。
定期的に集計をレポーティングすることを想定しているため、最終的な出力はcsvを想定しています。

CloudWatch Ingihtとは?

新しいサービス CloudWatch Logs Insights がお役に立ちます。これはクラウドスケールで動作するよう設計され、セットアップやメンテナンスが不要なフルマネージドのサービスです。これは大量のログを数秒で走査し、インタラクティブなクエリの実行と可視化を提供します。あらゆるログフォーマットや、JSON ログから自動検出したフィールドを扱えます。使ってみると分かるように、これは大変フレキシブルであり、ログを調査するためのお気に入りのツールの一つになるでしょう。

公式ドキュメントより抜粋。

ログの検索自体はすでに実装されているfilter_log_eventsで近しいことはできます。
しかしこれにはクエリ言語が設定されておらず、ちょっと工夫した検索をしたい場合はとても使いづらいものになります。

CloudWatch Insightは独自の検索クエリ言語を持っており、多様なログフォーマットの検索を柔軟に実行できます。
非常に強力なツールです。しかし、後述の問題点があります。

背景

この強力なツールは当然各種AWS-SDKに含まれていると思うでしょう?
実は本記事執筆時点でCloudWatch InsightはSDKに含まれていないのです。
だから定期的に集計バッチ処理を行おうとすると、シェルスクリプトとAWS-CLIで頑張るしかないのです。
ということで、この解説記事を書きました。

必要なパッケージ

  • AWS-CLI ver.1.16以上(このバージョンじゃないとInsightが使えない)
  • jq(レスポンスを処理するために使う)

インストール方法は割愛します。
なお、実行環境はMacかec2上とします。

ソース

実際に動作するソースを見ながら解説していきます。
サンプルプログラムでは、あるロググループに対して昨日中の集計を行うことを目的としています。

#!/bin/sh
alias date='gdate'

YESTERDAY=`date -d '1 days ago' '+%Y/%m/%d'`
START_TIME=`date -d $YESTERDAY' 00:00:00 9 hours ago' '+%s'`
END_TIME=`date -d $YESTERDAY' 23:59:59 9 hours ago' '+%s'`

#必要な設定
CLOUDWATCH_LOG_GROUP_NAME="ここにロググループ名"
REGION="ap-northeast-1"
QUERY="fields @timestamp, @message | limit 10"

echo 'Start aggregation.'

QUERYID=`
aws logs start-query \
--log-group-name $CLOUDWATCH_LOG_GROUP_NAME \
--start-time $START_TIME \
--end-time $END_TIME \
--query-string "$QUERY" \
--region $REGION \
--output text
`

#終了を待つ
printf 'Wait for complete'
while : 
do
    STATUS=`aws logs get-query-results --query-id $QUERYID --output text --region $REGION | sed -n '1p'`
    if [ "$STATUS" = "Complete" ]; then
        break
    fi
    printf "."
    sleep 1s;
done

#終了したら結果をjsonで出力
aws logs get-query-results --query-id $QUERYID --output table --region $REGION
RESULT_JSON=`aws logs get-query-results --query-id $QUERYID --output json --region $REGION`

#マッチ件数が1件以上ならjsonをcsvに変換し、results以下に保存する
MATCHED=`echo $RESULT_JSON | jq '.statistics.recordsMatched'`
if [ $MATCHED -gt 0 ] ; then
    #
    #header書き込み
    echo $RESULT_JSON \
    | jq '.results[0]' \
    | jq -r -c '([ .[].field | values ]) | @csv' \
    > ./result.csv

    #内容書き込み
    echo $RESULT_JSON \
    | jq '.results[]' \
    | jq -r -c '([ .[].value | values ]) | @csv' \
    >> ./result.csv
fi

echo 'Aggregation has completed.'

解説

前準備

#!/bin/sh
alias date='gdate'

YESTERDAY=`date -d '1 days ago' '+%Y/%m/%d'`
START_TIME=`date -d $YESTERDAY' 00:00:00 9 hours ago' '+%s'`
END_TIME=`date -d $YESTERDAY' 23:59:59 9 hours ago' '+%s'`

#必要な設定
CLOUDWATCH_LOG_GROUP_NAME="ここにロググループ名"
REGION="ap-northeast-1"
QUERY="fields @timestamp, @message | limit 10"

ここではクエリ実行に必要な変数を定義しています。
注意したいのは期間指定であり、ここはUTC標準時のタイムスタンプを設定する必要があります。
なぜならAWS-CLIにおけるCloudWatch Insightの期間指定は、タイムスタンプで指定する必要があるからです。
これは他のlogs関連クエリでも同様です。

今回の例では前日の丸一日を走査の対象としています。
Insightは走査したログのデータ量によって料金が変わってきますので、不要に長い期間を設定するのは時間と金の無駄遣いと心得ましょう。

なお、冒頭のgdateのエイリアスはMacでは必要ですが、ec2で動かすなら不要です。

クエリの実行

QUERYID=`
aws logs start-query \
--log-group-name $CLOUDWATCH_LOG_GROUP_NAME \
--start-time $START_TIME \
--end-time $END_TIME \
--query-string "$QUERY" \
--region $REGION \
--output text
`

ここからが本題です。AWS-CLIでCloudWatch Insightを実行するためのコマンドは aws logs start-queryとなっています。
詳しくは公式ドキュメントを見る必要がありますが、ここでは使用しているパラメータについて解説します。

  • log-group-nameはそのままロググループ名です。必須です。
  • start-timeは集計開始のタイムスタンプです。必須です。
  • end-timeは集計終了のタイムスタンプです。必須です。
  • query-stringはクエリ本文です。必須です。
  • regionはリージョンです。configでデフォルト値を設定している場合は不要です。
  • outputは出力形式です。このコマンドの出力結果は実行結果を得るためのクエリIDなので、単にテキストを取得できればいいので指定しています。

このパラメータで注意したいのは、期間指定のタイムスタンプはということです。
というのも他のCloudWatch logsの取得コマンドではマイクロ秒を指定する場合があるためです。
例えばfilter-log-eventsでは期間指定がマイクロ秒となっているので注意してください。ややこしいな!

クエリ実行完了の待機

while : 
do
    STATUS=`aws logs get-query-results --query-id $QUERYID --output text --region $REGION | sed -n '1p'`
    if [ "$STATUS" = "Complete" ]; then
        break
    fi
    printf "."
    sleep 1s;
done

#終了したら結果をjsonで出力
aws logs get-query-results --query-id $QUERYID --output table --region $REGION
RESULT_JSON=`aws logs get-query-results --query-id $QUERYID --output json --region $REGION`

start-queryを実行するとAWSのほうで集計タスクが走りますが、これはすぐに完了するものではありません。
完了するまでクエリIDを用い、レスポンスを確認する処理が必要です。

スクリプト側はget-query-resultコマンドをクエリIDを指定して実行することで、集計ステータスおよび集計結果を得ることができます。
出力形式をテキストにしている場合、レスポンスの1行目は単なるテキストRunningとなり、完了するとcompleteとなります。よってレスポンスがRunningの間はループを実行しています。

ステータスがcompleteになった後、出力形式をテーブルにした上で結果を表示しています。
最終的には出力形式をjsonにした上で変数に格納します。

集計結果がjsonで得られれば十分であればここで完了となります。
次のパートから実用的と思われるcsvに変換します。

結果の処理

#マッチ件数が1件以上ならjsonをcsvに変換し、results以下に保存する
MATCHED=`echo $RESULT_JSON | jq '.statistics.recordsMatched'`
if [ $MATCHED -gt 0 ] ; then
    #
    #header書き込み
    echo $RESULT_JSON \
    | jq '.results[0]' \
    | jq -r -c '([ .[].field | values ]) | @csv' \
    > ./result.csv

    #内容書き込み
    echo $RESULT_JSON \
    | jq '.results[]' \
    | jq -r -c '([ .[].value | values ]) | @csv' \
    >> ./result.csv
fi

echo 'Aggregation has completed.'

ここは本題から外れるので詳しくは解説はしません。

awscliの出力はtext, json, tableと選択できます。
簡単なメッセージのみ取得したいのであればtextで十分ですし、データを視覚的に認識したい場合はtableで出力します。
今回はcsvとして最終的な結果を得るために、処理をしやすいjson形式で出力しています。

この部分はstatistics.recordsMatchedが0以上の場合、jsonからcsvに変換しています。
statistics.recordsMatchedは条件に一致した件数をさします。

最後に

そのうちSDKでもCloudWatch Insightが利用できるようになると思います。
おそらく、このような定期実行によるレポーティングをするベストプラクティスは下記のような構成でしょう。

  • 実行環境はLambda、Pythonならboto3ライブラリでクエリを実行する
  • CloudWatch EventsでLambdaを定期実行する
  • 検索クエリはs3に配置して、集計結果もs3にアップロード

私が作った集計バッチプログラムも、実行環境がec2上という以外は上にならっています。
直近で必要な方はここのソースを参考にしてみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSを使ってLAMP環境を構築する手順

自己学習のメモとして。
AWS上にLAMP環境を作成する手順を書いておきます。

Linux仮想マシン(EC2)を起動

1、AWSのマネジメントコンソールからEC2を選択。
※この段階でリージョンを日本にしておく

2、インスタンスの作成を選択
・AMAZON Lunux AMIを選択
・無料利用枠のt2.microを選択
→インスタンスを作成

キーペアが生成できるのでダウンロードしておく。

パブリックIPアドレスを設定

パブリックIPアドレスが毎回変化してしまうので固定する。

・左のメニューからElastic IPを選択
・新しいアドレスの割り当てをクリック
・出来上がったアドレスを選択し、アクション→アドレスの関連付け
・インスタンスをクリックし、先ほど設定したLinuxのIDが出るので選択して紐づけ
・インスタンスからセキュリティグループをクリッ
アクション→インバウンドルールの編集を選択
・ルールの追加→http→保存

リモートアクセス

・ターミナル(コマンドプロンプト)を起動
・.sshフォルダがあるか確認する。(無ければmkdirでユーザーディレクトリの中に作っておく)
・.sshの中に先ほどのKey.pemファイルを移動しておく
・以下のsshコマンドでAWSにアクセス

ssh -i ~/.ssh/xxx.pem ec2-user@xx.xxx.xxx.xxx

※デフォルトで名前はec2-userになっている
初回はyesを選択して接続

今後接続するときは毎回このコマンドを打つので覚えておきたい

exitで接続を終了できる

Apacheを導入

・Linuxをアプデしておく

sudo yum -y update

※-yをつけると確認画面なしにできる

・Apacheをインストールする

sudo yum -y install httpd

・Apacheを起動する

sudo service httpd start

・今後自動で立ち上がるように設定する

sudo chkconfig httpd on

※ブラウザに切り替えてIPアドレスからapacheが見られるか確認するとよい

おまけ1

htmlファイルを作って試しに見てみる方法

htmlを作成

sudo vi /var/www/html/index.html

1、編集画面になったら「i」を打って適当にhtmlファイルを作成する
2、「esc」を押してから「:wq」で上書き保存しenter

※:q!で保存せず終了できる

ファイルの確認方法

ls /var/www/html

ブラウザにIPアドレスを打ち込んで確認できる

おまけ2

scpコマンドで、ローカルで作成したhtmlファイルを転送する
※scpはPC間で安全に転送するコマンド

おまけ1でAWSに作ったhtmlファイルをローカルにダウンロードする(デスクトップの場合)

scp -i ~/.ssh/xxx.pem ec2-user@xx.xxx.xxx.xxx:/var/www/html/index.html ~/Desktop

適当にhtmlファイルを編集し、AWSに転送する

scp -i ~/.ssh/xxx.pem ~/Desktop/index.html ec2-user@xx.xxx.xxx.xxx:

成功したかどうかsshコマンドで接続して確認
(pwd、lsで見られる。)

現状htmlファイルはec2-userの中にあるので正しい保存先に移動させる

sudo mv ~/index.html /var/www/html

これでブラウザで見ることができるようになる

この流れは面倒なのでもう少し簡略化できるよう設定する

EC2にリモートアクセスしている状態でwwwというグループを作成する

sudo groupadd www

このグループに自分のユーザー名を追加する

sudo usermod -a -G www ec2-user

いったんexitでログアウトし、もう一度ログイン→グループの権限が有効になっているはず

groups

で確認すると

ec2-user wheel www

となっているはず

htmlを保存するディレクトリのグループ所有権を変更する

sudo chown -R root:www /var/www

/var/www以下のディレクトリに書き込み許可を設定する

sudo chmod 2775 /var/www

将来のサブディレクトリにグループIDを設定

find /var/www -type d -exec sudo chmod 2775 {} \;

/var/www及びサブディレクトリのファイル許可を繰り返し、グループの書き込み許可を与える

find /var/www -type f -exec sudo chmod 0664 {} \;

今後は転送先に直接/var/www/htmlを指定できる

scp -i ~/.ssh/xxx.pem ~/Desktop/index.html ec2-user@xx.xxx.xxx.xxx:/var/www/html

PHPをインストール

※お決まりのコマンドでリモートログインしておく

管理者権限に切り替えておく

sudo su

ユーザー名の部分に#がつけばOK。
切り替えておけばsudoコマンドを打つ必要が無くなる。

時間の設定

date

ロンドンの時間になっていたりすると、掲示板などのサービスを作った際におかしくなるので日本時間に修正

ln -sf /usr/share/zoneinfo/Japan /etc/localtime

dateコマンドで確認して問題なければOK

・PHPをインストールする

yum install -y php

動作確認

・PHPの設定をしておく
※バックアップを取っておくと良い

cp /etc/php.ini /etc/php.bak

もしバックアップの必要が出た場合は

cp /etc/php.bak /etc/php.ini

viでphp.iniの設定をいじる

vi /etc/php.ini

行番号を表示させると見やすくなるのでescキーを押して

:set number

520行目あたりを編集したいので

:520

iモードにして

error_reporting = E_ALL &E_DEPRECATED 

に追記をすれば、phpの余分なエラーメッセージを表示させずに済む

error_reporting = E_ALL &E_DEPRECATED & ~E_NOTICE

次に537行目を編集。
ディスプレイエラーをOnにしておく
これでphpのエラーがブラウザに表示されるようになる

display_errors = On

ターミナルに戻り、apacheを再起動

service httpd restart

viでindex.phpを作って動作確認をすると良い
ブラウザでxx.xxx.xxx.xxx/index.php、(または番号のみで閲覧できる?)で確認できる。

MySQLのインストール

※sshでリモートアクセスし、sudo suで管理者にしておく。

yumでmysqlをインストールする

yum install -y mysql-server

phpからmysqlをコントロールするため追加プログラムを入れる

yum install -y php-mysqlnd

mysqlを起動してみる

service mysqld start

・mysqlの設定

mysql_secure_installation

パスワードを聞かれるが無いのでそのままenter
すると新しくパスワードを設定するかを聞かれるのでyを押し、パスワードを設定する
※パスワードは忘れないように
確認のためにもう一度打ち込む
そしてRemove anonyemous users?(ゲストユーザーを消しますか?)と尋ねられるのでyを押す。
そのあとはすべてyでOK。

・mysqlの文字コードをviで設定

vi /etc/my.cnf

11行目あたりにある[mysqld_safe]の前に

character-set-server = utf8

を追加。:wqで保存
再起動して確認

service mysqld restart

OKと表示されているればOK。

phpMyAdminを導入

phpMyAdminをインストールするためにyumコマンドの設定を変更する

yum-config-manager --enable epel

これで準備ができたので、phpmyadminをインストールする

yum install -y phpmyadmin

・自分のローカルPCのみでphpmyadminにログインできるよう設定する
自分のPCのグローバルIPアドレスを確認する
確認くん

確認できたらviで設定をいじる

vi /etc/httpd/conf.d/phpMyAdmin.conf

※大文字に注意

127.0.0.1の部分をグローバルIPアドレスに書き換えればOK

せっかくなので置換機能を使ってみる

:%s/127.0.0.1/xxx.xx.xx.xx/g

全4か所が修正されていればOK
設定を反映させるために再起動する

service httpd restart

確認のためxx.xx.xx.xx/phpmyadminをブラウザで確認する
ユーザー名はroot、パスワードはmysqlで設定したものを入力。

これにてLAMP環境が完成!

今後はLaravel環境を作る方法を書けるようになれればと思っています!

ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3のファイルの中身をダウンロードせずに簡単に確認する方法

AWS S3のバケットにaaaというファイルがあったとしましょう。

$ aws s3 ls s3://(バケット名)/
2019-04-15 01:42:01         65 aaa
2019-04-15 01:42:08         65 aaa.gz
$

このaaaの中身ってみるのっていちいちダウンロードしたし、コンソール開いたり、
結構めんどくさかったりしませんか?
今回はこのファイルの中をちょろちょろって確認する方法を紹介します。

Linuxのコマンドだけでできるんで簡単ですよ。

はいいきなりですが、コマンドは下記です。
ポイントはcp の結果を - にすると標準出力になるんです。
それをcat連結するとみれますよ。

$ aws s3 cp s3://(バケット名)/aaa - | cat
testhogehoge1
testhogehoge1
吾輩は猫である
testhogehoge1
testhogehoge1
$

応用その1.

$ aws s3 cp s3://(バケット名)/aaa.gz - | zcat
testhogehoge1
testhogehoge1
吾輩は猫である
testhogehoge1
testhogehoge1
$

応用その2.

$ aws s3 cp s3://(バケット名)/aaa.gz - | zcat  | grep 猫
吾輩は猫である
$

そしてこれ、ローカルにファイルのこりません。(当然か..)
ちょっとろしたログの調査のときとかにすごく便利ですよ!

よい Linux AWS ライフを!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS S3上ファイルの中身をLinuxコマンドだけで超絶簡単に確認する方法

AWS s3のファイルの中を見るときにいちいちダウンロード→解凍→中身を確認 とか
コンソールとかで頑張ってファイルを参照とかしていませんか?

あれ、結構めんどくさかったりしませんか?
今回はこのファイルの中をちょろちょろって確認する方法を紹介します。

AWS S3のバケットにaaaというファイルがあったとしましょう。

$ aws s3 ls s3://(バケット名)/
2019-04-15 01:42:01         65 aaa
2019-04-15 01:42:08         65 aaa.gz
$

このaaaの中身ってみるの超絶簡単に確認する方法の紹介します。
Linuxのコマンドだけでできるんですよ。

はいいきなりですが、コマンドは下記です。
ポイントはcp の結果を - にすると標準出力になるんです。
それをcat連結するとみれますよ。

$ aws s3 cp s3://(バケット名)/aaa - | cat
testhogehoge1
testhogehoge1
吾輩は猫である
testhogehoge1
testhogehoge1
$

応用その1.

$ aws s3 cp s3://(バケット名)/aaa.gz - | zcat
testhogehoge1
testhogehoge1
吾輩は猫である
testhogehoge1
testhogehoge1
$

応用その2.

$ aws s3 cp s3://(バケット名)/aaa.gz - | zcat  | grep 猫
吾輩は猫である
$

そしてこれ、ローカルにファイルのこりません。(当然か..)
ちょっとろしたログの調査のときとかにすごく便利ですよ!

よい Linux AWS ライフを!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】ポイントをおさえてAWS IoT Jobsを概説する #1/2【IoT】

はじめに

AWS IoT Jobsについての記事が少ないこと、あってもAWS IoTのREST APIを利用した検証でMQTTを利用した内容でなかったりと、情報が少ないので書きました。

本稿の方針

  • AWS IoT Jobsがカバーする領域を理解し、その特徴・制約を理解することを第一の目的とする。
  • そのため、シーケンス図と、授受されるデータを中心に説明する。
  • デバイス側はMQTTによる操作を行い、クラウド側はAWS Management Consoleで操作を行うことを想定する。
  • MQTT通信するコード・個別具体的なAPI仕様の記載は極力避け、最低限必要なものだけを記載する。
  • MQTTの基本的な概念(以下の項目)は理解していることを前提とする。
    • MQTT Broker
    • Connect
    • Subscribe
    • Publish
    • QoS0/1/2

TL;DR

  • AWS IoT Jobsの本質は、デバイス側とクラウド側で一定のルールに従ってJSONドキュメントを共有・更新すること、といえる。
  • MQTTでのJobs操作は少し面倒くさいが、通信の途絶・デバイスの長時間の停止を考慮した、よく考えられた仕組みであると言える。
  • QoS0(0回以上メッセージが送信される=メッセージがロストする可能性がある)だけで通信しても問題がない仕組みであると言える。

用語の定義

用語 説明 備考
AWS IoT Jobs AWS IoTの機能の一つとして提供されるもの。遠隔地で稼働しているIoTデバイスに対してなんらか実行させたい処理を配信し、その実行状況を管理することができる。本稿では、「IoT Jobs」と表現することもある。 Jobs - AWS IoT
デバイス MQTTでAWS IoT MQTT Brokerと通信し、AWS IoT Jobsに関するデータをやり取りする。いわゆるIoTデバイスを想定。
クラウド AWS側全般を指す。
JobDocument デバイスに実行させたい内容を記述したJSONドキュメント。記載内容をもとにどのような処理を行うかについては、完全にユーザ側に任されている。

環境・事前準備

AWSアカウントは既に取得済みであるものとします。
本稿では以下のAWSリソースを使います。S3 Bucket/IAM Roleは作成済みであるものとします。

  • S3 Bucket
    • investigate-iot-jobs
  • IAM Role
    • Name: role-investigate-iot-jobs
    • Trust Relationship: 「iot.amazonaws.com」からAssumeRole出来るよう設定
    • Permissions: S3 BucketのファイルをGetObject出来る権限を付与

検証作業の中で、以下のThing/Jobを作成します。

  • IoTデバイス名(Thing Name)
    • device01
  • ジョブ名:
    • iot-job01
    • iot-job02

S3 Bucketを作成する

手順は省略します。AWS Management ConsoleやAWS CLIなどでS3 Bucketを作成しておきます。
Bucket Policyなどは特に設定しません。

IAM Roleを作成する

詳細は後述しますが、AWS IoTサービスがAssumeRoleする必要がありますので、この用途で用いるIAM Roleをあらかじめ作成しておきます。
デバイスで何らかファイルを取得する必要がある場合、配信対象となるファイルは通常S3 Bucketに格納しておきます。このIAM Roleの権限を利用して、各デバイスはS3上のファイルの取得します。

Permissionとしては、最低限、当該S3 Bucket上のファイルに対するGetObjectが必要となります。
Trust Relationshipsは「iot.amazonaws.com」を指定します。
具体的な設定は以下のようになります。

Permissions

最低限、下記のPolicyを設定します。今回はinline policyとして付与しました。

inlinepolicy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::investigate-iot-jobs/*"
        }
    ]
}

Trust Relationship

「iot.amazonaws.com」からAssumeRole出来るように、Trust Relationshipを設定します。

Trust Relationship
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "iot.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Thingの作成

Thingをdevice01という名前で作成する

AWS CLIでThingを作成するには以下のように実行します。

### Thingを作成する
$ aws iot create-thing --thing-name device01
{
    "thingArn": "arn:aws:iot:ap-northeast-1:4**********8:thing/device01",
    "thingName": "device01",
    "thingId": "f2******-****-****-****-**********43"
}

### Thingの存在を確認する
$ aws iot list-things
{
    "things": [
        {
            "thingArn": "arn:aws:iot:ap-northeast-1:4**********8:thing/device01",
            "version": 1,
            "thingName": "device01",
            "attributes": {}
        }
    ]
}

解説

概要

AWS IoT Jobsとは、遠隔地の無数のデバイスに対して何らかのオペレーションを送信し、実行させることを目的とした機能です。
ただし、AWS IoT Jobsはリモートデバイスで実行させる処理の内容そのものは定義しません。この機能は、既定のルールに従ってクラウド側とデバイス側でJSONドキュメントをやり取りすること、MQTTメッセージがロストしても最終的には必要な情報を確実に相互に更新・参照できる仕組みであること、が本質といえます。

デバイス上で具体的に何を実行するかについては、予め利用者が設計する必要があります。
授受されるJSONドキュメント(≒Job Document)の内容に従い、デバイス上での処理内容を制御する、という形になります。

重要な用語の定義は下記公式ドキュメントにまとめられています。

Jobs - AWS IoT
https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html

配信するファイルの準備

AWS IoT Jobsで必要となるファイルをあらかじめ作成し、S3 Bucketに格納しておきます。
詳細は後述します。

iot-job01/iot-job02用のJobDocumentを作成し、S3 Bucketに格納する

JobDocumentを「JobDocument-iot-job01.txt」及び「JobDocument-iot-job02.txt」というファイル名で作成します。中身は単純なJSONドキュメントです。このJSONドキュメントの構造・内容は、AWS利用者が自由に設定できます。
ここでは、「jobType」と「fileUrl」というオブジェクトを持つJSONドキュメントとします。「fileUrl」項目で配信ファイルのURLを指定することとなります。
それぞれ下記のようになります。

JobDocument-iot-job01.txt
{
    "jobType": "fetchFile",
    "fileUrl": "${aws:iot:s3-presigned-url:https://s3.amazonaws.com/investigate-iot-jobs/iot-job01-files/distfile.bin}"
}
JobDocument-iot-job02.txt
{
    "jobType": "fetchFile",
    "fileUrl": "${aws:iot:s3-presigned-url:https://s3.amazonaws.com/investigate-iot-jobs/iot-job02-files/distfile.bin}"
}

「${}」でくくられた文字列は特殊な値です。この値は、デバイスに渡される際にS3 Presigned URLが生成されて置き換えられます。Presigned URLとは一定時間のみ有効なURLのことで、生成からxx分間を超えてアクセスするとエラーとなります。
具体的な説明は下記ドキュメントを参照ください。

Managing Jobs - AWS IoT
https://docs.aws.amazon.com/iot/latest/developerguide/create-manage-jobs.html

Your job document can contain a presigned Amazon S3 URL that points to your code file (or other file). Presigned Amazon S3 URLs are valid for a limited amount of time and so are not generated until a device requests a job document. Because the presigned URL has not been created when you are creating the job document, you put a placeholder URL in your job document instead. A placeholder URL looks like the following: ${aws:iot:s3-presigned-url:https://s3.region.amazonaws.com/\/<code file>} where bucket is the Amazon S3 bucket that contains the code file and code file is the Amazon S3 key of the code file.

また、S3 Presinged URLについての仕様・詳細な仕組みについて下記記事にまとめましたので併せて参考にしてください。

【AWS IoT Jobs】有効期限付きのURLでファイルを配信する【S3 Presigned URL】
【AWS S3】S3 Presigned URLの仕組みを調べてみた

作成した「JobDocument-iot-job01.txt」及び「JobDocument-iot-job02.txt」ファイルをS3 Bucketにアップロードします。

配布用ファイルをアップロードする

配信用ファイルとして適当なバイナリファイルを作り、これをS3 Bucketに格納しておきます。JobDocumentの「fileUrl」に一致するファイル名でアップロードする必要があります。
本稿では10MBのファイルを作成してS3 Bucketに格納しています。

S3 Bucketに格納されたファイルの確認

最終的には、JobDocument・配信ファイルは下記のように格納されます。

iot-job02用のファイル
$ aws s3 ls --recursive s3://investigate-iot-jobs/
2019-04-14 17:13:30        150 JobDocument-iot-job01.txt
2019-04-14 17:13:30        150 JobDocument-iot-job02.txt
2019-04-14 16:20:21   10485760 iot-job01-files/distfile.bin
2019-04-14 16:20:44   10485760 iot-job02-files/distfile.bin

基本的なフロー

Jobの配信から完了までの時間軸を考えた場合、一連の処理はまず大きく「Jobの取得」と「Jobの実行」という2つに分けられます。
「Jobの取得」処理はさらに、「能動的な取得」と「受動的な取得」に分けることができます。
整理すると、以下の3つのフローになります。

  • 1. Jobの取得:クラウド側で作成されたJobを、デバイスが認知する
    • 1-1. 受動的なJob取得:クラウド側でJobを作成したことをトリガーとし、その内容がデバイス側に通知される。
    • 1-2. 能動的なJob取得:デバイス側から、自分宛に割り当てられたJobを取得しにいく。
  • 2. Jobの実行:デバイス側で、認知したJobを実行し、完了させる。

1. Jobの取得

1-1. 受動的なJob取得

まずは、受動的にJobを取得するフローを見ていきます。
1-1.RetrieveJobsReactively.png

通常、デバイスの電源投入後、必要なプログラムが初期化・実行されていきますが、一般的には常駐してなんらか処理を行うプログラムが実行されます。
このプログラムの初期化処理の中で、MQTT Connectionの確立・TopicのSubscribeが行われることで、いつでもMQTTメッセージを受け取れる状態になります。
また必要に応じて、MQTTメッセージを任意のTopicにPublishする、ということも行います。

まず最初に、以下の2つのTopicをSubscribeします。「{{Thing Name}}」は、各デバイスのThing Nameが入りますので、実際に動作検証する際には適宜読み替えてください。

  • $aws/things/{{Thing Name}}/jobs/notify
  • $aws/things/{{Thing Name}}/jobs/notify-next

Management ConsoleなりAWS CLIなりでJobを追加すると、その情報が上記TopicにPublishされます。Topicの詳細については下記も併せてごらんください。

Devices and Jobs - AWS IoT -> Jobs Notifications
https://docs.aws.amazon.com/iot/latest/developerguide/jobs-devices.html

Jobを作成する際に必要な情報は以下の項目があります。

  • JobId: 当該Jobを識別するためのUniqueな名前。
  • DocumentSource: JobDocumentが格納されているS3パス。
  • JobDocument: デバイスに実行させたい処理内容を記載したJSONドキュメント。

詳細は下記ドキュメントを合わせて参照ください。

Creating and Managing Jobs (CLI) - AWS IoT -> Create Jobs
https://docs.aws.amazon.com/iot/latest/developerguide/manage-job-cli.html#create-job

MQTT Topic「\$aws/things/{{Thing Name}}/jobs/notify」および「\$aws/things/{{Thing Name}}/jobs/notify-next」をSubscribeした状態で、AWS Management Console等からJobを作成(「iot-job01」という名称で作成)すると、下記のようなメッセージが当該TopicにPublishされます。

「\$aws/things/{{Thing Name}}/jobs/notify」にはjobIdなどの情報しかPublishされませんが、「\$aws/things/{{Thing Name}}/jobs/notify-next」には次に実行するべきJobの詳細情報がPublishされます。
デバイスは基本的に「\$aws/things/{{Thing Name}}/jobs/notify-next」にPublishされた内容をもとに、必要な処理を実行していきます。

\$aws/things/device01/jobs/notify(※データ定義1-1-2)
{
  "timestamp": 1555262670,
  "jobs": {
    "QUEUED": [
      {
        "jobId": "iot-job01",
        "queuedAt": 1555262670,
        "lastUpdatedAt": 1555262670,
        "executionNumber": 1,
        "versionNumber": 1
      }
    ]
  }
}

下記JSONドキュメントで重要な情報は「execution.jobDocument」です。これは先ほど作成してS3に格納したJobDocumentoファイルと同等の内容になります。ただし、S3 Presinged URLを利用する場合は、下記のように実際のURLに置き換わります。

\$aws/things/device01/jobs/notify-next(※データ定義1-1-3)
{
  "timestamp": 1555262670,
  "execution": {
    "jobId": "iot-job01",
    "status": "QUEUED",
    "queuedAt": 1555262670,
    "lastUpdatedAt": 1555262670,
    "versionNumber": 1,
    "executionNumber": 1,
    "jobDocument": {
      "jobType": "fetchFile",
      "fileUrl": "https://investigate-iot-jobs.s3.ap-northeast-1.amazonaws.com/iot-job01-files/distfile.bin?X-Amz-Security-Token=AgoGb3JpZ2luEGUaDmFwLW5vcnRoZWFzdC0xIoACFZBtrnE5T6QTP7%2FElU9iW06MYGK%2FmRVNuOZWGSV8WjY63nnm37IYbREWtVNkqSZ7gMf%2BSIf7o9fFW4r6IIWA7YDdfpYUCSBJWSt8zcr2hQTlXwtMc4FJlZztw2D%2F%2F5XBK32%2FJH22LuH0Tleac6a1f6hdGqxPPh8S5pysWz8XOzG4Qsa9qtaet4rM8iPMVH3fXfttMvYPYSHn7hNUsJyWnAyIBXiF920wspUDA%2BxFdV%2B%2FoQNkzusn%2F36mOgsToee9pgko3YgrDVm%2B3pica2E5vArQN1aW5SLFSAyE%2BPjNOF6PKBVjfPVa8UehzUrQ7CiVmEwPYe5M2YAVFRgpp6MWoiqPAgjb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDQ5NzMzOTA2MzAwOCIM28PN%2F0N3kY5OS2XdKuMBKnMV7KWUxHpw5CuLGWCgi9rce6A6%2Fyq3Y%2Fmiz6AfqWECjivGPzYiaN77u7u7z3kOKdQJJ7Jd8OQU%2B%2Fm9vF09JuVJe6II5IDvSorw%2FXUDafZj78bayCIApioHbjVDt8jrMgEeBiW%2Bxj8OcwDA1rYVhElA8qIdT6snzQRnKXAjhBt3nqNTQKU2N6PqLLiLRuZT51ENF1kfiY0FOLb6Hqyimmt1za2Pk9vrNo9poQeVCIAIZnKuhqzQzdp%2B1KRrYhTzhGEA3r%2FSvMiTDwcY%2FexRYWxIVOZGo418jLakBuUi6z%2B6PkMwztnN5QU%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20190414T172430Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAXHS5X3LQDCAJRFN4%2F20190414%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=636cc1afc7156948b49d6c8eff86af4f4d055117d9a0a7dda9fc4e394a4b8218"
    }
  }
}

今はJobが一つだけ存在する状態ですが、さらに新たなJobを作成するとどうなるでしょうか。「iot-job02」を作成してみます。

「\$aws/things/device01/jobs/notify」には下記のようなJSONドキュメントがPublishされます。

\$aws/things/device01/jobs/notify(※データ定義1-1-2)
{
  "timestamp": 1555263139,
  "jobs": {
    "QUEUED": [
      {
        "jobId": "iot-job01",
        "queuedAt": 1555262670,
        "lastUpdatedAt": 1555262670,
        "executionNumber": 1,
        "versionNumber": 1
      },
      {
        "jobId": "iot-job02",
        "queuedAt": 1555263139,
        "lastUpdatedAt": 1555263139,
        "executionNumber": 1,
        "versionNumber": 1
      }
    ]
  }
}

上記Topicには、最大10個までのpending jobの情報がPublishされます。先ほどの公式ドキュメントの「Jobs Notifications」に記載の内容を引用します。

  • A ListNotification contains a list of no more than 10 pending job executions. The job executions in this list have status values of either IN_PROGRESS or QUEUED. They are sorted by status (IN_PROGRESS job executions before QUEUED job executions) and then by the times when they were queued.

「\$aws/things/device01/jobs/notify-next」には何もPublishされません。このTopicにメッセージがPublishされるタイミングは下記のとおりです。先ほどの公式ドキュメントの「Jobs Notifications」に記載の内容を引用します。

  • A NextNotification contains summary information about the one job execution that is next in the queue.

A NextNotification is published whenever the first job execution in the list changes.

  • A new job execution is added to the list as QUEUED, and it is the first one in the list.
  • The status of an existing job execution that was not the first one in the list changes from QUEUED to IN_PROGRESS and becomes the first one in the list. (This happens when there are no other IN_PROGRESS job executions in the list or when the job execution whose status changes from QUEUED to IN_PROGRESS was queued earlier than any other IN_PROGRESS job execution in the list.)
  • The status of the job execution that is first in the list changes to a terminal status and is removed from the list.

1-2. 能動的なJob取得

次に、デバイス側から能動的にJobを取得するフローを見ていきます。
1-2.RetrieveJobsProactively.png

通常、長時間のネットワーク切断・電源停止などの最中にJobが作成された場合、デバイスはこのタイミングではJobの情報を取得できません。そこでデバイスの起動時に、自分宛に登録されたJobがあるかどうか確認し、あればこれを実行するという処理が必要になります。
AWS IoT Jobsにはこの目的のために「DescribeJobExecution」というAPIが用意されています。

Using the AWS IoT Jobs APIs - AWS IoT -> DescribeJobExecution
https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-describejobexecution

まず、下記2つのMQTT TopicをSubscribeします。「+」はワイルドカードを意味し、任意の文字列にマッチします。

  • $aws/things/{{Thing Name}}/jobs/+/get/accepted
  • $aws/things/{{Thing Name}}/jobs/+/get/rejected

次に、下記MQTT Topicに空のメッセージをPublishします。実際には任意の文字列を指定することができますが、内容は無視されます。
「jobId」の箇所には「$next」という特殊な文字列を指定することが可能です。これは、当該デバイスが次に実行するべきJob(statusがIN_PROGRESSかQUEUEDのもの)を意味します。

  • $aws/things/{{Thing Name}}/jobs/{{jobId}}/get

上記MQTT TopicにメッセージをPublishすると、「$aws/things/{{Thing Name}}/jobs/+/get/accepted」にJobの内容がPublishされます。

$aws/things/device01/jobs/+/get/accepted(※データ定義1-2-1)
{
  "timestamp": 1555263695,
  "execution": {
    "jobId": "iot-job01",
    "status": "QUEUED",
    "queuedAt": 1555262670,
    "lastUpdatedAt": 1555262670,
    "versionNumber": 1,
    "executionNumber": 1,
    "jobDocument": {
      "jobType": "fetchFile",
      "fileUrl": "https://investigate-iot-jobs.s3.ap-northeast-1.amazonaws.com/iot-job01-files/distfile.bin?X-Amz-Security-Token=AgoGb3JpZ2luEGUaDmFwLW5vcnRoZWFzdC0xIoACQqsC5krx0S6azrc4h%2BGxErc2Bn56FRw3Wv1HWRY3REaruyiGNHA7iW3zyc9eBmY1%2FyGNNzPJSxaSsnOrWtgS0hfRChjB9Kp7rPgi9pRAxd7ZeSspaXH1s5mTJnBSzzbChCUbpsX1x5WdMp97Gi5LcbYff8rM8JirZr6kXxhOuVLtXSSvPgtGnzjdpvP1ERcRtFCj2e%2FchTmOtUCSakYlT04Qeek%2BKuRdkstdQh9w25p6NtOgFWsUqbMlGHVKuW7xalXecx0XZ1bOZ1NRChv1vj6qHt%2FNPX4XEvhprK47I0t1PyjsAA%2Fl8UR31uhBffAjVFsmvrhVwtE24QD2zfZgViqPAgjb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDQ5NzMzOTA2MzAwOCIMDgPv2Nu%2FenWUH1EOKuMBbpGvp77yjc7XjEwgybl9SSgDVzKhJIp9vuGsRp95ly9yMUJbpYSNI6DTpseq1W1LIeDs%2FACxlvLl%2B5COdHGurn0IHP42w4b6RL0UxtB1goo36225Jk9jLSdMfYGXVPvM%2F5DtT4BRlC81PNuzlwvBQmfdZavjZD1%2Fp6fkQnIbjD8PlB9MC17UPi%2FlZGUB2LhYl0o3XDSp8NmQ2qJyHwJXsV3BaCLWztmqj8JALGQlqqVVtvc%2FbCWlNZ1vM5czj%2Fb7Llbsr9N3lqTuuThTEnfgCKF67DoumQ7sQ4dmK2efktoVvVswz%2BHN5QU%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20190414T174135Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAXHS5X3LQCRWMEZ7L%2F20190414%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=71f4b9575a89fba54315441b306f915c6231cdb5bf20f7f8c43752f5f9900edb"
    }
  }
}

2. Jobの実行

上記までのフローにより、デバイス側でJobの内容を取得することができました。デバイスはJobDocumentに記載の内容をもとに必要な処理を実行していきます。
分量が多くなったので、Job実行時のフローは別記事にまとめます。

※続きます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】ポイントをおさえてAWS IoT Jobsを理解する #1/2【IoT】

はじめに

AWS IoT Jobsについての記事が少ないこと、あってもAWS IoTのREST APIを利用した検証でMQTTを利用した内容でなかったりと、情報が少ないので書きました。

本稿の方針

  • AWS IoT Jobsがカバーする領域を理解し、その特徴・制約を理解することを第一の目的とする。
  • そのため、シーケンス図と、授受されるデータを中心に説明する。
  • デバイス側はMQTTによる操作を行い、クラウド側はAWS Management Consoleで操作を行うことを想定する。
  • MQTT通信するコード・個別具体的なAPI仕様の記載は極力避け、最低限必要なものだけを記載する。
  • MQTTの基本的な概念(以下の項目)は理解していることを前提とする。
    • MQTT Broker
    • Connect
    • Subscribe
    • Publish
    • QoS0/1/2

TL;DR

  • AWS IoT Jobsの本質は、デバイス側とクラウド側で一定のルールに従ってJSONドキュメントを共有・更新すること、といえる。
  • MQTTでのJobs操作は少し面倒くさいが、通信の途絶・デバイスの長時間の停止を考慮した、よく考えられた仕組みであると言える。
  • QoS0(0回以上メッセージが送信される=メッセージがロストする可能性がある)だけで通信しても問題がない仕組みであると言える。

用語の定義

用語 説明 備考
AWS IoT Jobs AWS IoTの機能の一つとして提供されるもの。遠隔地で稼働しているIoTデバイスに対してなんらか実行させたい処理を配信し、その実行状況を管理することができる。本稿では、「IoT Jobs」と表現することもある。 Jobs - AWS IoT
デバイス MQTTでAWS IoT MQTT Brokerと通信し、AWS IoT Jobsに関するデータをやり取りする。いわゆるIoTデバイスを想定。
クラウド AWS側全般を指す。
JobDocument デバイスに実行させたい内容を記述したJSONドキュメント。記載内容をもとにどのような処理を行うかについては、完全にユーザ側に任されている。

環境・事前準備

AWSアカウントは既に取得済みであるものとします。
本稿では以下のAWSリソースを使います。S3 Bucket/IAM Roleは作成済みであるものとします。

  • S3 Bucket
    • investigate-iot-jobs
  • IAM Role
    • Name: role-investigate-iot-jobs
    • Trust Relationship: 「iot.amazonaws.com」からAssumeRole出来るよう設定
    • Permissions: S3 BucketのファイルをGetObject出来る権限を付与

検証作業の中で、以下のThing/Jobを作成します。

  • IoTデバイス名(Thing Name)
    • device01
  • ジョブ名:
    • iot-job01
    • iot-job02

S3 Bucketを作成する

手順は省略します。AWS Management ConsoleやAWS CLIなどでS3 Bucketを作成しておきます。
Bucket Policyなどは特に設定しません。

IAM Roleを作成する

詳細は後述しますが、AWS IoTサービスがAssumeRoleする必要がありますので、この用途で用いるIAM Roleをあらかじめ作成しておきます。
デバイスで何らかファイルを取得する必要がある場合、配信対象となるファイルは通常S3 Bucketに格納しておきます。このIAM Roleの権限を利用して、各デバイスはS3上のファイルの取得します。

Permissionとしては、最低限、当該S3 Bucket上のファイルに対するGetObjectが必要となります。
Trust Relationshipsは「iot.amazonaws.com」を指定します。
具体的な設定は以下のようになります。

Permissions

最低限、下記のPolicyを設定します。今回はinline policyとして付与しました。

inlinepolicy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::investigate-iot-jobs/*"
        }
    ]
}

Trust Relationship

「iot.amazonaws.com」からAssumeRole出来るように、Trust Relationshipを設定します。

Trust Relationship
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "iot.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Thingの作成

Thingをdevice01という名前で作成する

AWS CLIでThingを作成するには以下のように実行します。

### Thingを作成する
$ aws iot create-thing --thing-name device01
{
    "thingArn": "arn:aws:iot:ap-northeast-1:4**********8:thing/device01",
    "thingName": "device01",
    "thingId": "f2******-****-****-****-**********43"
}

### Thingの存在を確認する
$ aws iot list-things
{
    "things": [
        {
            "thingArn": "arn:aws:iot:ap-northeast-1:4**********8:thing/device01",
            "version": 1,
            "thingName": "device01",
            "attributes": {}
        }
    ]
}

AWS IoT Jobsとは

AWS IoT Jobsとは、遠隔地の無数のデバイスに対して何らかのオペレーションを送信し、実行させることを目的とした機能です。
ただし、AWS IoT Jobsはリモートデバイスで実行させる処理の内容そのものは定義しません。この機能は、既定のルールに従ってクラウド側とデバイス側でJSONドキュメントをやり取りすること、MQTTメッセージがロストしても最終的には必要な情報を確実に相互に更新・参照できる仕組みであること、が本質といえます。

デバイス上で具体的に何を実行するかについては、予め利用者が設計する必要があります。
授受されるJSONドキュメント(≒Job Document)の内容に従い、デバイス上での処理内容を制御する、という形になります。

重要な用語の定義は下記公式ドキュメントにまとめられています。

Jobs - AWS IoT
https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html

配信するファイルの準備

AWS IoT Jobsで必要となるファイルをあらかじめ作成し、S3 Bucketに格納しておきます。
詳細は後述します。

iot-job01/iot-job02用のJobDocumentを作成し、S3 Bucketに格納する

JobDocumentを「JobDocument-iot-job01.txt」及び「JobDocument-iot-job02.txt」というファイル名で作成します。中身は単純なJSONドキュメントです。このJSONドキュメントの構造・内容は、AWS利用者が自由に設定できます。
ここでは、「jobType」と「fileUrl」というオブジェクトを持つJSONドキュメントとします。「fileUrl」項目で配信ファイルのURLを指定することとなります。
それぞれ下記のようになります。

JobDocument-iot-job01.txt
{
    "jobType": "fetchFile",
    "fileUrl": "${aws:iot:s3-presigned-url:https://s3.amazonaws.com/investigate-iot-jobs/iot-job01-files/distfile.bin}"
}
JobDocument-iot-job02.txt
{
    "jobType": "fetchFile",
    "fileUrl": "${aws:iot:s3-presigned-url:https://s3.amazonaws.com/investigate-iot-jobs/iot-job02-files/distfile.bin}"
}

「${}」でくくられた文字列は特殊な値です。この値は、デバイスに渡される際にS3 Presigned URLが生成されて置き換えられます。Presigned URLとは一定時間のみ有効なURLのことで、生成からxx分間を超えてアクセスするとエラーとなります。
具体的な説明は下記ドキュメントを参照ください。

Managing Jobs - AWS IoT
https://docs.aws.amazon.com/iot/latest/developerguide/create-manage-jobs.html

Your job document can contain a presigned Amazon S3 URL that points to your code file (or other file). Presigned Amazon S3 URLs are valid for a limited amount of time and so are not generated until a device requests a job document. Because the presigned URL has not been created when you are creating the job document, you put a placeholder URL in your job document instead. A placeholder URL looks like the following: ${aws:iot:s3-presigned-url:https://s3.region.amazonaws.com/\/<code file>} where bucket is the Amazon S3 bucket that contains the code file and code file is the Amazon S3 key of the code file.

また、S3 Presinged URLについての仕様・詳細な仕組みについて下記記事にまとめましたので併せて参考にしてください。

【AWS IoT Jobs】有効期限付きのURLでファイルを配信する【S3 Presigned URL】
【AWS S3】S3 Presigned URLの仕組みを調べてみた

作成した「JobDocument-iot-job01.txt」及び「JobDocument-iot-job02.txt」ファイルをS3 Bucketにアップロードします。

配布用ファイルをアップロードする

配信用ファイルとして適当なバイナリファイルを作り、これをS3 Bucketに格納しておきます。JobDocumentの「fileUrl」に一致するファイル名でアップロードする必要があります。
本稿では10MBのファイルを作成してS3 Bucketに格納しています。

S3 Bucketに格納されたファイルの確認

最終的には、JobDocument・配信ファイルは下記のように格納されます。

iot-job02用のファイル
$ aws s3 ls --recursive s3://investigate-iot-jobs/
2019-04-14 17:13:30        150 JobDocument-iot-job01.txt
2019-04-14 17:13:30        150 JobDocument-iot-job02.txt
2019-04-14 16:20:21   10485760 iot-job01-files/distfile.bin
2019-04-14 16:20:44   10485760 iot-job02-files/distfile.bin

AWS IoT Jobsのフロー

Jobの配信から完了までの時間軸を考えた場合、一連の処理はまず大きく「Jobの取得」と「Jobの実行」という2つに分けられます。
「Jobの取得」処理はさらに、「能動的な取得」と「受動的な取得」に分けることができます。
整理すると、以下の3つのフローになります。

  • 1. Jobの取得:クラウド側で作成されたJobを、デバイスが認知する
    • 1-1. 受動的なJob取得:クラウド側でJobを作成したことをトリガーとし、その内容がデバイス側に通知される。
    • 1-2. 能動的なJob取得:デバイス側から、自分宛に割り当てられたJobを取得しにいく。
  • 2. Jobの実行:デバイス側で、認知したJobを実行し、完了させる。

1. Jobの取得

1-1. 受動的なJob取得

まずは、受動的にJobを取得するフローを見ていきます。
1-1.RetrieveJobsReactively.png

通常、デバイスの電源投入後、必要なプログラムが初期化・実行されていきますが、一般的には常駐してなんらか処理を行うプログラムが実行されます。
このプログラムの初期化処理の中で、MQTT Connectionの確立・TopicのSubscribeが行われることで、いつでもMQTTメッセージを受け取れる状態になります。
また必要に応じて、MQTTメッセージを任意のTopicにPublishする、ということも行います。

まず最初に、以下の2つのTopicをSubscribeします。「{{Thing Name}}」は、各デバイスのThing Nameが入りますので、実際に動作検証する際には適宜読み替えてください。

  • $aws/things/{{Thing Name}}/jobs/notify
  • $aws/things/{{Thing Name}}/jobs/notify-next

Management ConsoleなりAWS CLIなりでJobを追加すると、その情報が上記TopicにPublishされます。Topicの詳細については下記も併せてごらんください。

Devices and Jobs - AWS IoT -> Jobs Notifications
https://docs.aws.amazon.com/iot/latest/developerguide/jobs-devices.html

Jobを作成する際に必要な情報は以下の項目があります。(※データ定義1-1-1)

  • JobId: 当該Jobを識別するためのUniqueな名前。
  • Targets: Job実行対象のThingを指定する。Thing名/Thing Group名で指定が可能。
  • DocumentSource: JobDocumentが格納されているS3パス。
  • JobDocument: デバイスに実行させたい処理内容を記載したJSONドキュメント。JSONドキュメントでさえあれば、データ構造・内容は自由に設定できます。

詳細は下記ドキュメントを合わせて参照ください。

Creating and Managing Jobs (CLI) - AWS IoT -> Create Jobs
https://docs.aws.amazon.com/iot/latest/developerguide/manage-job-cli.html#create-job

MQTT Topic「\$aws/things/{{Thing Name}}/jobs/notify」および「\$aws/things/{{Thing Name}}/jobs/notify-next」をSubscribeした状態で、AWS Management Console等からJobを作成(「iot-job01」という名称で作成)すると、下記のようなメッセージが当該TopicにPublishされます。

「\$aws/things/{{Thing Name}}/jobs/notify」にはjobIdなどの情報しかPublishされませんが、「\$aws/things/{{Thing Name}}/jobs/notify-next」には次に実行するべきJobの詳細情報がPublishされます。
デバイスは基本的に「\$aws/things/{{Thing Name}}/jobs/notify-next」にPublishされた内容をもとに、必要な処理を実行していきます。

「\$aws/things/{{Thing Name}}/jobs/notify」には、対象デバイスで実行するべきJob一覧が、最大10個まで表示されます。StatusがIN_PROGRESS/QUEUEDのものが対象となり、Status・timestampの順にソートされています。

\$aws/things/device01/jobs/notify(※データ定義1-1-2)
{
  "timestamp": 1555262670,
  "jobs": {
    "QUEUED": [
      {
        "jobId": "iot-job01",
        "queuedAt": 1555262670,
        "lastUpdatedAt": 1555262670,
        "executionNumber": 1,
        "versionNumber": 1
      }
    ]
  }
}

下記JSONドキュメントで重要な情報は「execution.jobDocument」です。これは先ほど作成してS3に格納したJobDocumentoファイルと同等の内容になります。ただし、S3 Presinged URLを利用する場合は、下記のように実際のURLに置き換わります。

\$aws/things/device01/jobs/notify-next(※データ定義1-1-3)
{
  "timestamp": 1555262670,
  "execution": {
    "jobId": "iot-job01",
    "status": "QUEUED",
    "queuedAt": 1555262670,
    "lastUpdatedAt": 1555262670,
    "versionNumber": 1,
    "executionNumber": 1,
    "jobDocument": {
      "jobType": "fetchFile",
      "fileUrl": "https://investigate-iot-jobs.s3.ap-northeast-1.amazonaws.com/iot-job01-files/distfile.bin?X-Amz-Security-Token=AgoGb3JpZ2luEGUaDmFwLW5vcnRoZWFzdC0xIoACFZBtrnE5T6QTP7%2FElU9iW06MYGK%2FmRVNuOZWGSV8WjY63nnm37IYbREWtVNkqSZ7gMf%2BSIf7o9fFW4r6IIWA7YDdfpYUCSBJWSt8zcr2hQTlXwtMc4FJlZztw2D%2F%2F5XBK32%2FJH22LuH0Tleac6a1f6hdGqxPPh8S5pysWz8XOzG4Qsa9qtaet4rM8iPMVH3fXfttMvYPYSHn7hNUsJyWnAyIBXiF920wspUDA%2BxFdV%2B%2FoQNkzusn%2F36mOgsToee9pgko3YgrDVm%2B3pica2E5vArQN1aW5SLFSAyE%2BPjNOF6PKBVjfPVa8UehzUrQ7CiVmEwPYe5M2YAVFRgpp6MWoiqPAgjb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDQ5NzMzOTA2MzAwOCIM28PN%2F0N3kY5OS2XdKuMBKnMV7KWUxHpw5CuLGWCgi9rce6A6%2Fyq3Y%2Fmiz6AfqWECjivGPzYiaN77u7u7z3kOKdQJJ7Jd8OQU%2B%2Fm9vF09JuVJe6II5IDvSorw%2FXUDafZj78bayCIApioHbjVDt8jrMgEeBiW%2Bxj8OcwDA1rYVhElA8qIdT6snzQRnKXAjhBt3nqNTQKU2N6PqLLiLRuZT51ENF1kfiY0FOLb6Hqyimmt1za2Pk9vrNo9poQeVCIAIZnKuhqzQzdp%2B1KRrYhTzhGEA3r%2FSvMiTDwcY%2FexRYWxIVOZGo418jLakBuUi6z%2B6PkMwztnN5QU%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20190414T172430Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAXHS5X3LQDCAJRFN4%2F20190414%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=636cc1afc7156948b49d6c8eff86af4f4d055117d9a0a7dda9fc4e394a4b8218"
    }
  }
}

今はJobが一つだけ存在する状態ですが、さらに新たなJobを作成するとどうなるでしょうか。「iot-job02」を作成してみます。

「\$aws/things/device01/jobs/notify」には下記のようなJSONドキュメントがPublishされます。

\$aws/things/device01/jobs/notify(※データ定義1-1-2)
{
  "timestamp": 1555263139,
  "jobs": {
    "QUEUED": [
      {
        "jobId": "iot-job01",
        "queuedAt": 1555262670,
        "lastUpdatedAt": 1555262670,
        "executionNumber": 1,
        "versionNumber": 1
      },
      {
        "jobId": "iot-job02",
        "queuedAt": 1555263139,
        "lastUpdatedAt": 1555263139,
        "executionNumber": 1,
        "versionNumber": 1
      }
    ]
  }
}

上記Topicには、最大10個までのpending jobの情報がPublishされます。先ほどの公式ドキュメントの「Jobs Notifications」に記載の内容を引用します。

  • A ListNotification contains a list of no more than 10 pending job executions. The job executions in this list have status values of either IN_PROGRESS or QUEUED. They are sorted by status (IN_PROGRESS job executions before QUEUED job executions) and then by the times when they were queued.

「\$aws/things/device01/jobs/notify-next」には何もPublishされません。このTopicにメッセージがPublishされるタイミングは下記のとおりです。先ほどの公式ドキュメントの「Jobs Notifications」に記載の内容を引用します。

  • A NextNotification contains summary information about the one job execution that is next in the queue.

A NextNotification is published whenever the first job execution in the list changes.

  • A new job execution is added to the list as QUEUED, and it is the first one in the list.
  • The status of an existing job execution that was not the first one in the list changes from QUEUED to IN_PROGRESS and becomes the first one in the list. (This happens when there are no other IN_PROGRESS job executions in the list or when the job execution whose status changes from QUEUED to IN_PROGRESS was queued earlier than any other IN_PROGRESS job execution in the list.)
  • The status of the job execution that is first in the list changes to a terminal status and is removed from the list.

1-2. 能動的なJob取得

次に、デバイス側から能動的にJobを取得するフローを見ていきます。
1-2.RetrieveJobsProactively.png

通常、長時間のネットワーク切断・電源停止などの最中にJobが作成された場合、デバイスはこのタイミングではJobの情報を取得できません。そこでデバイスの起動時に、自分宛に登録されたJobがあるかどうか確認し、あればこれを実行するという処理が必要になります。
AWS IoT Jobsにはこの目的のために「DescribeJobExecution」というAPIが用意されています。

Using the AWS IoT Jobs APIs - AWS IoT -> DescribeJobExecution
https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-describejobexecution

まず、下記2つのMQTT TopicをSubscribeします。「+」はワイルドカードを意味し、任意の文字列にマッチします。

  • $aws/things/{{Thing Name}}/jobs/+/get/accepted
  • $aws/things/{{Thing Name}}/jobs/+/get/rejected

次に、下記MQTT Topicに空のメッセージをPublishします。実際には任意の文字列を指定することができますが、内容は無視されます。
「jobId」の箇所には「$next」という特殊な文字列を指定することが可能です。これは、当該デバイスが次に実行するべきJob(statusがIN_PROGRESSかQUEUEDのもの)を意味します。

  • $aws/things/{{Thing Name}}/jobs/{{jobId}}/get

上記MQTT TopicにメッセージをPublishすると、「$aws/things/{{Thing Name}}/jobs/+/get/accepted」にJobの内容がPublishされます。

$aws/things/device01/jobs/+/get/accepted(※データ定義1-2-1)
{
  "timestamp": 1555263695,
  "execution": {
    "jobId": "iot-job01",
    "status": "QUEUED",
    "queuedAt": 1555262670,
    "lastUpdatedAt": 1555262670,
    "versionNumber": 1,
    "executionNumber": 1,
    "jobDocument": {
      "jobType": "fetchFile",
      "fileUrl": "https://investigate-iot-jobs.s3.ap-northeast-1.amazonaws.com/iot-job01-files/distfile.bin?X-Amz-Security-Token=AgoGb3JpZ2luEGUaDmFwLW5vcnRoZWFzdC0xIoACQqsC5krx0S6azrc4h%2BGxErc2Bn56FRw3Wv1HWRY3REaruyiGNHA7iW3zyc9eBmY1%2FyGNNzPJSxaSsnOrWtgS0hfRChjB9Kp7rPgi9pRAxd7ZeSspaXH1s5mTJnBSzzbChCUbpsX1x5WdMp97Gi5LcbYff8rM8JirZr6kXxhOuVLtXSSvPgtGnzjdpvP1ERcRtFCj2e%2FchTmOtUCSakYlT04Qeek%2BKuRdkstdQh9w25p6NtOgFWsUqbMlGHVKuW7xalXecx0XZ1bOZ1NRChv1vj6qHt%2FNPX4XEvhprK47I0t1PyjsAA%2Fl8UR31uhBffAjVFsmvrhVwtE24QD2zfZgViqPAgjb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDQ5NzMzOTA2MzAwOCIMDgPv2Nu%2FenWUH1EOKuMBbpGvp77yjc7XjEwgybl9SSgDVzKhJIp9vuGsRp95ly9yMUJbpYSNI6DTpseq1W1LIeDs%2FACxlvLl%2B5COdHGurn0IHP42w4b6RL0UxtB1goo36225Jk9jLSdMfYGXVPvM%2F5DtT4BRlC81PNuzlwvBQmfdZavjZD1%2Fp6fkQnIbjD8PlB9MC17UPi%2FlZGUB2LhYl0o3XDSp8NmQ2qJyHwJXsV3BaCLWztmqj8JALGQlqqVVtvc%2FbCWlNZ1vM5czj%2Fb7Llbsr9N3lqTuuThTEnfgCKF67DoumQ7sQ4dmK2efktoVvVswz%2BHN5QU%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20190414T174135Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAXHS5X3LQCRWMEZ7L%2F20190414%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=71f4b9575a89fba54315441b306f915c6231cdb5bf20f7f8c43752f5f9900edb"
    }
  }
}

2. Jobの実行

上記までのフローにより、デバイス側でJobの内容を取得することができました。デバイスはJobDocumentに記載の内容をもとに必要な処理を実行していきます。
分量が多くなったので、Job実行時のフローは下記記事にまとめます。

【AWS】ポイントをおさえてAWS IoT Jobsを理解する #2/2【IoT】 - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

気付いたこと備忘録

気付いたこと備忘録

ひたすら気付いたことをいったん書きなぐる記事

AWSCloud9関連

gitの変更内容はなぜか、runを押さないと反映されない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TerraformでEC2インスタンスを構築(実践編)

はじめに

AWSを使ってて、毎回コンソールから環境を構築するのは面倒です。
Terraformというツールを使えば、環境をドカッと作れて、ゴソッと消せるらしいので触ってみました。

まずは、お試し感覚でAWSのEC2を構築します。

この実践編ではインストールが終わったあとの構築方法を説明しています。
インストールや初期設定についてはインストール編で説明しています。

目標

Terraformを用いたインフラの構築は以下のような流れで行うことを目標にします。

① .tfファイルにインフラのコードを書く
② terraform planコマンドで現状との差分を比較する
③ terraform applyコマンドでインフラを構成を適用する
④ terraform showコマンドで状態を確認する

今回は環境を構築したあとに、削除します。
削除は以下の流れで行います。

⑤ terraform destroyコマンドで削除
⑥ terraform showコマンドで削除されたことを確認

実践

環境構築

① .tfファイル作成

Terraformはインフラの構成情報をHCL(HaashCorp Configuration Language)というDSLで.tfファイルに記述します。

今回は、VPCとサブネットとインスタンスをresourceとして定義します。

パラメータは公式ドキュメントを見ながらやればOK。

Provider: AWS - Terraform by HashiCorp

aws_ec2_config.tf
resource "aws_instance" "linux" {
    ami = "ami-00a5245b4816c38e6"
    instance_type = "t2.micro"
    subnet_id = "${aws_subnet.public_subnet.id}"
}

resource "aws_vpc" "vpc" {
    cidr_block = "10.0.0.0/16"
    instance_tenancy = "default"
}

resource "aws_subnet" "public_subnet" {
vpc_id = "${aws_vpc.vpc.id}"
cidr_block = "10.0.10.0/24"
availability_zone = "ap-northeast-1a"
}

② terraform plan

terraform planコマンドで現在の環境と.tfファイルの差分を確認します。

今回は、VPCとサブネットとインスタンスが新規作成されるので「+」で表示されます。

$ ./terraform plan
Terraform will perform the following actions:

  + aws_instance.linux
      id:                               <computed>
      ami:                              "ami-00a5245b4816c38e6"
      arn:                              <computed>
      ・
      ・
      ・
  + aws_subnet.public_subnet
      id:                               <computed>
      arn:                              <computed>
      assign_ipv6_address_on_creation:  "false"
      ・
      ・
      ・

  + aws_vpc.vpc
      id:                               <computed>
      arn:                              <computed>
      ・
      ・
      ・

Plan: 3 to add, 0 to change, 0 to destroy.

③ terraform apply

構成情報の確認ができたら、terraform applyコマンドで構成情報を適用します。
途中でアクションを続行するか聞かれるので「yes」を入力します。

$ ./terraform apply
      ・
      ・
      ・
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

以下のように表示されれば、処理が完了です。

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

④ terraform show

terraform showコマンドで構成情報を確認できます。
また、AWSコンソールなどからも確認することができます。

$ ./terraform show
aws_instance.linux:
  id = i-*****************
  ami = ami-00a5245b4816c38e6
  arn = arn:aws:ec2:ap-northeast-1:************:instance/i-*****************
      ・
      ・
      ・

aws_subnet.public_subnet:
  id = subnet-*****************
  arn = arn:aws:ec2:ap-northeast-1:************:subnet/subnet-*****************
      ・
      ・
      ・

aws_vpc.vpc:
  id = vpc-*****************
  arn = arn:aws:ec2:ap-northeast-1:************:vpc/vpc-*****************
      ・
      ・
      ・

環境の削除

⑤ terraform destroy

terraform destroyコマンドで定義したリソースを削除することができます。

削除のときも、途中でアクションを続行するか聞かれるので「yes」を入力します。

$ ./terraform destroy
      ・
      ・
      ・
Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

以下のように表示されれば、処理が完了です。

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

⑥ terraform show

最後にもう一度、terraform showコマンドで現在の構成を確認します。
正常に削除できている場合は、以下のように何も表示されません。

$ ./terraform show

おわり

今回までで、TerraformのインストールとEC2インスタンスの構築までが一旦終わりました。

応用すれば、もっと複雑なインフラも構築できるはず!

また、時間があればTerraformのstateやBackendという概念についても深掘っていきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS IoT Jobs】有効期限付きのURLでファイルを配信する【S3 Presigned URL】

はじめに

AWS IoT Jobsを使ってデバイスにファイルを配信する際には、デバイスからHTTPSでダウンロード可能なURLを指定することが多いかと思います。
公式ドキュメント上はS3 Presigned URLを使うことが想定されていますが、これはいったいどういうものか、なぜ使うのか、といった観点での説明が足りないように思えるので、これらを補足する意味で記事にまとめます。

なぜS3 Presigned URLを使うのか

通信方向・プロトコル

まずはS3 Presigned URLのことを考える前に、なぜHTTPSを利用するのか、何故デバイスからGetする形でファイルを取得するのか、という点を考えます。

原理上は、デバイス側で何らかMQTT TopicをSubscribeしておいて、MQTT Brokerから当該TopicにファイルをPublishすることで、ファイルの送達は可能です。
しかしながらこれは、全てがうまく動いているとき/うまく動くと保証されているときのみ有効な手段です。まず、当該ファイルがPublishされる前に、デバイス側は確実にMQTT TopicをSubscribeした状態になっている必要があります。また、ファイルの送信中に通信が切断された場合は、リトライする必要があります。

またMQTT Brokerからデバイスに対してQoS1でメッセージを送信することで、メッセージのロストはなくなりますが、
AWSのMQTT Brokerでは、QoS1のメッセージを保持できる件数には限りがあります。

いずれにせよ、IoTシステムにおいて、デバイス側とクラウド側の通信処理を考える際には、「すべてがうまく動かない」ということを前提に考える必要があります。

この観点から出発すると、デバイス側からの操作を起点に考えた場合、以下の条件を満たす構成が一番都合がよいことに気づきます。

  • ファイル配信に関する情報(JobDocument)はいつでも・何度でも取得可能
  • 配信ファイル自体を取得する操作は、デバイスから何度でもリトライできる

AWS IoT Jobsの仕様においては、すでに前者の要件は考慮されています。
後者の要件を満たすためには、HTTPSでファイルをダウンロードできるようにしておいて、デバイス側からGetする、という方式がベストであるように見えます。

セキュリティ

上記までの構成そのままでは、だれでも情報を取得できるという状態になります。
つぎに、予め許可されたデバイスに対してのみ、情報・ファイルの取得が可能なようにする方法を考えます。

ファイル配信に関する情報(JobDocument)の取得

  • ファイル配信に関する情報(JobDocument)はいつでも・何度でも取得可能

まずこの操作は、デバイス側からMQTT over TLSで行われるを前提とします(AWS APIで取得することも可能ですが、ここでは考慮しません)。AWS IoTのMQTT BrokerとMQTT通信する際には、有効なクライアント証明書・秘密鍵を持っていないと行えませんので、ここでセキュリティを担保できることになります。
どのデバイスがどのMQTT Topicを操作できるかは、AWS IoTのPolicyで設定することが可能です

配信ファイルの取得

  • 配信ファイル自体を取得する操作は、デバイスから何度でもリトライできる

次に、上記の操作に関して、許可したデバイスのみファイルをダウンロードできるようにする方法を考えます。

まず、S3 Bucket上のファイルをダウンロードさせる場合、2つの方法があります。

  • AWS S3 API経由で取得する(≒AWS CLIやAWS SDKを利用する)
  • HTTPSを利用して取得する

前者の方法を利用する場合、デバイス側はAWS Credentials(AccessKey/SecretAccessKey、Temporary Credentialsの場合はさらにSessionToken)を持っている必要があります。

デバイスからのリクエストに応じてTemporary Credentialsを返却する、デバイスはこれを使ってAWS S3 API経由でファイルを取得する、という方法で原理上は実現可能です。
ただしこの方法は、デバイスからのリクエストに応じてTemporary Credentialsを返却する仕組みを作りこむ必要があり、少し大仰です。

つぎに、HTTPSでファイルを取得する方式を考えます。

クライアントに権限があるかどうかをチェックするために、MQTT over TLS用のクライアント証明書を利用できればベストですが、現状ではS3はそこまで対応していません(おそらく今後も対応されないと思います)。

では、クライアントからのリクエストに応じ、一時的に有効なダウンロードURL(HTTPSベース)を発行することはどうでしょうか。S3 Presigned URLを利用することで、これを実現できます。

ダウンロードURLはJobDocument内に記載されてデバイスに渡されます。JobDocumentを渡す操作において権限チェックが行われるため、セキュリティ上も問題ありません。

なお、S3 Presigned URLについての説明が長くなったので、下記記事に個別にまとめました。
【AWS S3】S3 Presigned URLの仕組みを調べてみた - Qiita

試してみる

環境を準備する

試した環境は以下のようになります。

  • AWSアカウント
    • アカウントID: 4**********8
  • S3
    • Bucket名: investigate-iot-jobs
    • 配信ファイル: s3://investigate-iot-jobs/example-distfile.bin
    • JobDocumentファイル: s3://investigate-iot-jobs/example-job-document.json
  • IAM Role(配信ファイルのPresigned URLを生成するためのRole)
    • role-investigate-iot-jobs
    • Permissions: 後述するinline policyを付与
  • AWS IoT Thing(配信対象のデバイス)
    • Thing Name: example-device01
role-investigate-iot-jobsのinline policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::investigate-iot-jobs/*"
        }
    ]
}

S3 Bucketには、配信ファイルとJobDocumentファイルが必要です。

まず、JobDocumentファイル「example-job-document.json」として下記JSONファイルを作成します。

example-job-document.json
{
    "distFileUrl": "${aws:iot:s3-presigned-url:https://s3.amazonaws.com/investigate-iot-jobs/example-distfile.bin}"
}

example-distfile.binは、1MBのバイナリファイルを生成したものとなります。このファイルは何でもよいです。

$ dd if=/dev/zero of=example-distfile.bin bs=1024 count=1024
1024+0 records in
1024+0 records out
1048576 bytes (1.0 MB) copied, 0.00191433 s, 548 MB/s

$ ls -la example-distfile.bin
-rw-rw-r-- 1 vagrant vagrant 1048576 Apr 14 09:51 example-distfile.bin

上記2つのファイルとS3 Bucket格納します。最終的に下記のように2つのファイルが格納されます。

$ aws s3 ls --recursive  s3://investigate-iot-jobs/
2019-04-14 09:52:20    1048576 example-distfile.bin
2019-04-14 09:54:59        120 example-job-document.json

AWS IoT Jobsでのファイル配信対象となるデバイスが必要ですので併せて作成します。
example-device01という名称で作成しました。

aws iot create-thing
$ aws iot create-thing --thing-name example-device01
{
    "thingArn": "arn:aws:iot:ap-northeast-1:4**********8:thing/example-device01",
    "thingName": "example-device01",
    "thingId": "81******-****-****-****-**********f6"
}

IoT Jobを作成する

この作業はAWS Management Consoleから行ってみます。
以下の内容でJobを作ります。

  • JobID: example-job01
  • Select devices to update: example-device01
  • Add a job file: s3://investigate-iot-jobs/example-job-document.json
  • Pre-sign resource URLs
    • I want to pre-sign my URLs and have configured my job file.
    • Role: role-investigate-iot-jobs

まず、下記のURL等からAWS IoT CoreのDashboardを開きます。
https://ap-northeast-1.console.aws.amazon.com/iot/home?region=ap-northeast-1#/dashboard

次に、「Manage」->「Jobs」を選択します。遷移した画面で「Create a job」ボタンを押します。
2019-04-14_19h13_05.png

どのようなジョブを作成するかを選択する画面になりますので「Create custom job」ボタンを押します。
2019-04-14_19h13_09.png

具体的な値を設定する画面に遷移しますので、上述した内容でJobを作っていきます。入力したら「Next」ボタンを押します。
2019-04-14_19h16_12.png

次のページは、特に変更する点はありません。そのまま「Create」ボタンを押します。
2019-04-14_19h16_43.png

作成が完了すると、以下のように作成されたJobが表示されます。
2019-04-14_19h16_49.png

Jobをクリックすると、詳細を参照することが可能です。
2019-04-14_19h16_58.png

作成したIoT JobをAWS CLIから参照する

AWS CLIからは、iot list-jobsおよびiot describe-jobで参照することができます。

aws iot list-jobs
$ aws iot list-jobs
{
    "jobs": [
        {
            "status": "IN_PROGRESS",
            "jobArn": "arn:aws:iot:ap-northeast-1:4**********8:job/example-job01",
            "jobId": "example-job01",
            "lastUpdatedAt": 1555237008.749,
            "targetSelection": "SNAPSHOT",
            "createdAt": 1555237002.922
        }
    ]
}
aws iot describe-job
$ aws iot describe-job --job-id example-job01
{
    "documentSource": "https://investigate-iot-jobs.s3.ap-northeast-1.amazonaws.com/example-job-document.json",
    "job": {
        "status": "IN_PROGRESS",
        "jobArn": "arn:aws:iot:ap-northeast-1:4**********8:job/example-job01",
        "jobProcessDetails": {
            "numberOfQueuedThings": 1,
            "numberOfInProgressThings": 0,
            "numberOfSucceededThings": 0,
            "numberOfTimedOutThings": 0,
            "numberOfCanceledThings": 0,
            "numberOfFailedThings": 0,
            "numberOfRemovedThings": 0,
            "numberOfRejectedThings": 0
        },
        "presignedUrlConfig": {
            "expiresInSec": 300,
            "roleArn": "arn:aws:iam::4**********8:role/role-investigate-iot-jobs"
        },
        "jobId": "example-job01",
        "lastUpdatedAt": 1555237008.749,
        "targetSelection": "SNAPSHOT",
        "timeoutConfig": {},
        "jobExecutionsRolloutConfig": {
            "maximumPerMinute": 1000
        },
        "targets": [
            "arn:aws:iot:ap-northeast-1:4**********8:thing/example-device01"
        ],
        "createdAt": 1555237002.922
    }
}

デバイスからJobの情報を取得する

ではいよいよ、デバイスからMQTT経由で作成したJobの情報を取得します。
ただし、実際に組み込み系デバイスでMQTT経由でJobを取得するためには、それ相応のプログラムを実装する必要があり、冗長です。
本稿では、AWS Management ConsoleのMQTTクライアント機能を利用し、デバイスから行う操作を疑似的に再現することとします。

デバイスからJobを取得するフローは、下記ドキュメントの「DescribeJobExecution」に従います。

Using the AWS IoT Jobs APIs - AWS IoT -> DescribeJobExecution
https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-describejobexecution

これはjobIdで指定したJobの内容を取得することができます。
MQTTのTopic名にjobIdを指定しますが、「$next」という特殊な文字列を設定することで、当該Thingに割り当てられたJobのうち実行待ちのもの(statusがIN_PROGRESSかQUEUEDのもの)のを対象とすることができます。

まず、AWS IoT CoreのDashboardから「Test」をクリックし、MQTTクライアントを実行します。
以下のMQTT TopicをSubscribeします。「Subscription topic」にSubscribeしたいTopicを入力し「Subscribe to topic」ボタンを押します。

  • \$aws/things/example-device01/jobs/$next/get/accepted
  • \$aws/things/example-device01/jobs/$next/get/rejected

2019-04-15_00h29_40.png
2019-04-15_00h30_05.png

次に、「Publish to a topic」リンクをクリックします。
「Specify a topic and a message to publish with a QoS of 0.」の欄に下記のTopic名を入力します。
メッセージ本文は何でもよいです。ここでは空オブジェクト「{}」を指定します。
入力後、「Publish to topic」ボタンをクリックします。

  • \$aws/things/example-device01/jobs/$next/get

すると、「\$aws/things/example-device01/jobs/$next/get/accepted」TopicにメッセージがPublishされます。
2019-04-15_00h34_09.png

「\$aws/things/example-device01/jobs/$next/get/accepted」Topicを見てみると、下記のようなJSONドキュメントがPublishされていることが分かります。
2019-04-15_00h42_33.png

\$aws/things/example-device01/jobs/$next/get/accepted
{
  "timestamp": 1555256042,
  "execution": {
    "jobId": "example-job01",
    "status": "QUEUED",
    "queuedAt": 1555237004,
    "lastUpdatedAt": 1555237004,
    "versionNumber": 1,
    "executionNumber": 1,
    "jobDocument": {
      "distFileUrl": "https://investigate-iot-jobs.s3.ap-northeast-1.amazonaws.com/example-distfile.bin?X-Amz-Security-Token=AgoGb3JpZ2luEGMaDmFwLW5vcnRoZWFzdC0xIoACYYh9OutqTnIbbvusQBKUckbKpj6MHQj3UQ7CzQUAIHc%2F5XkrAWB5lF5ZqG%2Ff16K25ZzSIHcbx9o62M1e0f8k2gY1ere2rGjt4REKavic6I%2BjpvQzUyTXaoZTTbbEwRc8QgrwwPgy5Qt6EdxC5z3zDchn3t%2FzKj5l1RgQ7fxwisics%2Fmiy80xI%2FYJ2CS2gIV3mzPbNaUAq1%2FoPxEGxPhMVKXztsTp70%2Fny0FSoaDkI4xW1VomKmFvnG%2Fz5F5KVNHOSZdEwyvzvgqXE9iuAubHdBKsTQoNCYHfLEoQ3Cn%2Beg92r6ysnrZgRWevLJlF9v2ae7IlPmV7%2B9uGjbA3FdHmGyqPAgjZ%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDQ5NzMzOTA2MzAwOCIMYC6D7wfC2Yvm23yxKuMBfBo17VFQlPA%2F64fqRyYGdofOpIQskk1V3dMsMA%2Bu2PMusYItU5%2F5bTUu%2Fh7s1%2FFN4gPal6Y1cN4S4UzqVBWRq7siOzm%2Bc0yte%2BSHTE6YxcfphgOBJ6PwCfuZa9t3%2FlUCTiTYposI8yxaVsJk8Sb%2BG8jVLXLgJDMrAchhGGR2wlRGGd92bIKMheuK5en1Jxucucvfoi9BFmJAE2zWhmQNzw1j3LYa%2BbtyTqezBZQg1UW7D1wdHwqMu1zm37MI6jLpVi%2BJOkIEAXr123MX0SkpCwJKGwHFwQsduTovZ9UsxfwoSSww6qXN5QU%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20190414T153402Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAXHS5X3LQE6XTCHWH%2F20190414%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=5e93dbde98e7c15bb603eec07f09ae796e4423942e26e413fb36cc13f670f068"
    }
  }
}

ここで重要な項目は「execution.jobDocument」になります。これは先ほど作ってS3上に格納したもJobDocumentファイルの内容が入ります。
ただし、1点大きく違う点があります。「distFileUrl」の値が書き換わり、実際のPresigned URLになっていることです。
ちなみに、Job作成時に有効期限を「5 minutes」に設定しているので、上記のPresigned URLの有効期限は5分になります。

もともとのファイルは下記の内容でした。「\${aws:iot:s3-presigned-url:.......}」の項目が、実際のPresigned URLに置き換わっています。

example-job-document.json
{
    "distFileUrl": "${aws:iot:s3-presigned-url:https://s3.amazonaws.com/investigate-iot-jobs/example-distfile.bin}"
}

JobDocumentに記載のPresigned URLの仕様

まずPresigned URLは、MQTT経由で取得した場合のみ置き換えられます。

Using the AWS IoT Jobs APIs - AWS IoT -> GetJobDocument
https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html

GetJobDocumentというAPIがありますが、ここのNoteにその旨、記載があります。

GetJobDocument
Gets the job document for a job.

Note

Placeholder URLs are not replaced with presigned Amazon S3 URLs in the document returned. Presigned URLs are generated only when the AWS IoT Jobs service receives a request over MQTT.

Presigned URLは、デバイスから取得のリクエストが行われた場合に生成されます。
したがって、Presigned URLの有効期限が切れた場合は、デバイス側から再度JobDocumentを取得することで、新たなPresigned URLを得ることができます。

Managing Jobs - AWS IoT
https://docs.aws.amazon.com/iot/latest/developerguide/create-manage-jobs.html

Managing Jobs

When a device requests the job document, AWS IoT generates the presigned URL and replaces the placeholder URL with the presigned URL. Your job document is then sent to the device.

おわりに

現実的にAWS IoTを利用してリモートデバイスをコントロールする場合、MQTTが主な通信手段となるでしょう。この構成でファイル配信する場合、JobDocumentにPresigned URLを記載し、デバイスからダウンロードさせる方式が現時点ではベストな方式になると思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エンジニア初心者がRailsでランキング機能実装の際につまづいたところ

CDの発売日を管理できるサービスを作っていて、発売日が過ぎたときにランキングから除外しなきゃ!と思ったものの微妙に詰まったのでここに備忘録として書き残しておきます。?

環境:cloud9(IDE)EC2(仮想サーバー)rails(rubyFW)mysql(DB)

まず大前提の知識としてModelはDBの情報を引っ張ってくるということです!(今更)
MVCのそれぞれの役割をしっかり理解しておくことは本当に重要で、理解してプログラミングをしていると今自分が何をやっているのか頭の中で整理できます。

今回は実際に色々試した過程を書きます。

大まかな流れとして、
1.DB設計を考える 2.mysql(DB)でコマンドを色々試す→3.処理が成功したらmodelに実装
という形で行いました。

1.DB設計を考える

ランキングは中間テーブル(relationships)で集計しているので、中間テーブルに発売日カラムを登録することで発売日でフィルターをかけることができるのでは?

2.mysqlでコマンドを色々試す

▼カラム追加(mysql)
alter table relationships add sales_date date;
楽天apiから発売日情報を文字列型で引っ張って来ているのでエラーになってしまった。

alter table relationships add sales_date varchar(255);
文字列型で登録〜うん良さそう

▼発売日を過ぎたものは非表示にするコマンドをチェック
私が実際にやったときはmodelの記述を先に行ってしまったのですごい時間を食ってしまった…先にこっちで確認すべきだった…良い学び

select * from relationships where sales_date > date_format(current_date(),'%Y年%m月%d日');
処理内容としては、現在日時をdate_formatを用いて年月日に変更し、sales_dateと比較演算子を用いて比較しています。これをそのままmodelに実装します。

3.処理が成功したらmodelに実装

▼記述方法

relationship.rb
def Relationship.ranking
  Relationship.where().group().order().limit().count()
end

前述の通り、modelはdbから情報を引っ張ってくるので書き方としてはかなりdbに近いです。
まずwhereで絞り込み、groupで重複した値をまとめ、orderで並べ替え、limitで表示制限をして最後のcountでCDの登録された数を数えるといった流れです。実際のコードを書いていきます。

relationship.rb
def self.ranking
  self.where("sales_date > date_format(current_date(),'%Y年%m月%d日')").group(:item_id).order('count_item_id DESC').limit(12).count(:item_id)
end

where以外はそこまで特別なことはしていないので省きます。
結論から書くとwhereにdbで成功した文をダブルクォーテーションで囲むだけでOKです!笑
これでできたときは拍子抜けしました…それと同時にMVCの理解度が低かったとかなり痛感しました。
modelはdbの情報を引っ張ってくるわけだからdbで実際に成功した文をそのまま書けば確かに間違いはないですねw

まとめ

MVCの役割を理解することの重要性を書きたかったのですがシッチャカメッチャカになっちゃった感。。。
まあそれなりに自分の考えは整理できた気がするので良しとします笑
反省点としては何を書くのか考えていなかったこと、考えをまとめていなかったことの二点ですかね?精進します!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TerraformでEC2インスタンスを構築(インストール編)

はじめに

AWSを使ってて、毎回コンソールから環境を構築するのは面倒です。
Terraformというツールを使えば、環境をドカッと作れて、ゴソッと消せるらしいので触ってみました。

まずは、お試し感覚でAWSのEC2を構築します。

このインストール編ではインストールから初期設定までを説明しています。
実際の構築については実践編で説明しています。

Terraformとは

  • Terraformとは、HashiCorp社製のAWSやGCPなどのクラウド上のリソース管理をする構成管理ツールです。

  • HashiCorp社のツールはTerraformの他にもVargrantなどがあり、これらはOSSとして公開されています。

Terraformの使い方

  • インフラの構成情報を.tfという拡張子のファイルに記述します。
  • terraformの各コマンドを実行することで、記述したインフラの構成を管理することができます。
  • .tfファイルは複数に分かれていても、同じディレクトリに存在すればまとめて適用することが可能。

Terraformの開発フロー

Terraformを用いたインフラの開発は以下のような流れで行います。

  1. .tfファイルにインフラのコードを書く
  2. terraform planコマンドで現状との差分を比較する
  3. terraform applyコマンドでインフラを構成を適用する
  4. terraform showコマンドで状態を確認する

Terraformのインストール

ダウンロード先

Terraform は、下記のリンクからダウンロードできる。

MacOS 版(AMD64)、Linux 版 (i386, AMD64)、Windows 版(i386) に対応

https://www.terraform.io/downloads.html

インストール

①インストール先のディレクトリを作成する。

$ mkdir terraform

$ cd terraform

②URLからダウンロード & 解凍

$ wget -O terraform_0.11.13_linux_amd64.zip https://releases.hashicorp.com/terraform/0.11.13/terraform_0.11.13_linux_amd64.zip

$ unzip terraform_0.11.13_linux_amd64.zip

③ディレクトリを見てみる

$ tree .
.
├── 0.1.0_linux_amd64.zip
├── terraform
└── terraform_0.11.13_linux_amd64.zip

④バージョン確認のコマンドを実行してみる

$ ./terraform --version
Terraform v0.11.13

インストールは以上で完了。必要であればパスを通してある/usr/bin/などにコピーしてもいいかも。

初期設定

事前準備 for AWS

Trraformを使ってAWS環境を構築する前に、以下2つをやっておく。

① IAMユーザの作成

TerraforでAWS環境を管理するにはアクセスキーIDとシークレットキーが必要なのでIAMユーザを作成しておく

② AWS CLIのインストール

事前にAWSCLIのインストールをインストールしておく。

pip install awscliでインストールできる。

Terraform初期設定

① IAMなどを登録

TerraformでAWSを管理する場合、アクセスキーの登録が必要になります。

Terraformをインストールしたディレクトリに.tfファイルを作成し、アクセスキーを記述します。
ファイル名は適当でOK。

リージョンも指定しておきます。

aws_setting.tf
    provider "aws" {
        access_key = "YOUR_ACCESS_KEY"
        secret_key = "YOUR_SECRET_KEY"
        region = "ap-northeast-1"
    }

② Terraform初期化

IAMのアクセスキーをterraform init コマンドを実行する。

Terraformの作業ディレクトリを初期化し、AWSを操作するためのプラグインをインストールします。

このコマンドを実行すると、terraform planやterraform applyコマンドが実行可能になります。

$ ./terraform  init 
Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "aws" (2.6.0)...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 2.6"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

③ terraform planで現在の状態を見てみる

terraform planコマンドを実行して、現在の状態を確認してみます。

変更なし、最新の状態であるというメッセージが表示される。

まだ、Terraformの設定ファイルにはAWSの構成情報は記述していないのでOK。

$ ./terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

おわり

今回はインストール編なのでここまでです。

次回は実際にTerraformを使って、EC2インスタンスを作成してみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS認定ソリューションアーキテクトアソシエイトへの道 その7 VPC関連のハンズオン

tl;dr

現在Webエンジニアをやっているが下記の理由のためにAWSソリューションアーキテクトアソシエイト取得を目指す。

  • スキルアップ
  • 業務の幅を広げる
  • 知的好奇心

現在のAWSスキル

  • テスト用にEC2を作成したことはある(LAMP環境を構築)
  • ネットワーク用語はある程度わかる(マスタリングTCP/IP 入門編は名著だと思う)

学習方法

  • 対策本を読むことを考えたが、手を動かしながら学んだほうが身につくと考え、Udemyの動画教材を購入
  • AWS INNOVATE(Amazonが主催するAWSを学ぶためのONLINE CONFERENCE) → 開催中だったために登録
  • 参考書も購入

購入した教材はこちら

AWS認定ソリューションアーキテクト – アソシエイト試験突破講座

購入した本はこちら

合格対策 AWS認定ソリューションアーキテクト - アソシエイト

本日の課題

  1. VPC/サブネットにサーバーを設定する(ハンズオン)
  2. ネットワークACL(ハンズオン)
  3. VPCエンドポイント(ハンズオン)
  4. VPC Flow logs
  5. VPCのまとめ
  6. 小テスト VPCテスト

課題メモ

  • ネットワークACLのinbound rule,outbound ruleは番号が小さい方が優先される

参考URL

「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典:CIDR

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む