AWS上にエッジ環境を構築してGreengrass(V1)の動きを確認する

#はじめに
GreengrassはIoT Coreとの疎通が切れても、エッジ環境内でメッセージのやり取りやLambdaの実行ができるようになっています。
また、GreengrassからIoT Coreへのメッセージであれば、疎通回復まである程度保持することもできます。

今回はAWS上に疑似的なエッジ環境を構築し、エッジ環境とIoT Core間の通信が切れた場合の挙動を確認します。
具体期には以下を検証しました。

* IoT Coreと通信できない状況でもGreengrassデバイス間でMQTTのやり取りができること
* IoT Coreと通信できない状況でもGreengrassのローカルLambdaが実行できること
* IoT Coreと通信できない間のメッセージを疎通回復後に送信できること

#1　AWS上にエッジ環境を構築
まず、AWS上に疑似的なエッジ環境を構築します。
Greengrassグループ内のネットワークを維持しつつIoT Coreとの疎通を切断するのが容易と考えての手段です。
作成後の構成は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/522833c0-fd5b-05ee-fee1-209902cf405e.png)
##1-1　ネットワーク周りの作成
VPCをはじめ、ネットワーク周辺の設定を行います。
具体的には以下を作成します。

| 作成するもの | 備考 |
|:-|:-|
| VPC | 10.0.0.0/16 |
| サブネットA | 10.0.0.0/24 |
| サブネットB | 10.0.1.0/24 |
| インターネットゲートウェイ | VPCにアタッチする |
| ルートテーブルA | to local と to Internet のルーティングを設定 |
| ルートテーブルB | to local のみルーティングを設定 |

###VPCの作成
AWS上のプライベートなネットワーク環境を作成します。

1.VPCのマネジメントコンソールで VPC > 「VPCを作成」の順にクリック
2.以下の通り設定し、「VPCを作成」をクリック

* 名前タグ：※任意のVPC名
* IPv4 CIDRブロック：10.0.0.0/16
* ※その他はデフォルト設定のまま

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/70a48a74-fe2e-b1da-b0f5-3915682984e1.png)
###サブネットの作成
踏み台サーバを置くサブネットと仮想デバイスを置くサブネットをそれぞれ作成します。

1.VPCのマネジメントコンソールで サブネット > 「サブネットを作成」の順にクリック
2.以下の通り設定して「サブネットを作成」をクリック
※1つ目のサブネットの情報を入力したあと、「新しいサブネットを追加」をクリックで2つ目のサブネットの設定も入力できる

* VPC（共通）：※先ほどの手順で作成したVPC
* サブネット名：※任意のサブネット名
* アベイラビリティゾーン：ap-northeast-1a
* IPv4 CIDR ブロック（1つ目）：10.0.0.0/24
* IPv4 CIDR ブロック（2つ目）：10.0.1.0/24

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/da482377-bd69-e31d-d83e-e1dcdedc02d5.png)
###インターネットゲートウェイの作成とアタッチ
VPCと外部が接続できるようにインターネットゲートウェイを作成し、アタッチします。

1.VPCのマネジメントコンソールで インターネットゲートウェイ > 「インターネットゲートウェイの作成」の順にクリック
2.任意の名前を入力して、「インターネットゲートウェイの作成」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/af317bbf-ad6d-5e8c-e1fe-130e87023ceb.png)
3.アクション > VPCにアタッチ の順にクリック
4.先ほど作成したVPCを選択して、「インターネットゲートウェイのアタッチ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/62dae07c-0c24-ec75-bafe-299232961548.png)
###ルートテーブルの作成
今回は外部向けのルーティングを持つルートテーブルと持たないルートテーブル、それぞれを作成します。
以降の検証でエッジ環境とIoT Coreの接続を切る際に、後者のルートテーブルを利用します。

1.VPCのマネジメントコンソールで ルートテーブル をクリック
2.VPC作成時にあわせて作成されたルートテーブルを選択し、タグタブをクリック
3.Nameタグを追加する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/e67859fd-f8e7-1607-3a27-c03537d77716.png)
4.ルートタブ > ルートの編集 の順にクリック
5.「ルートの追加」をクリックし、以下の通り設定して「ルートの保存」をクリック

* 送信先：0.0.0.0/0
* ターゲット：※作成したインターネットゲートウェイ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/e72365a3-0c63-402b-5bb5-92b6838a906d.png)
6.「ルートテーブルの作成」をクリック
7.以下の通り設定して「作成」をクリック

* 名前タグ：※任意の名前
* VPC：※作成したVPC

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/9d9ada34-dd7e-7fc6-b48e-f0cdee775479.png)
8.インターネットゲートウェイへのルートを追加ルートテーブルを選択して アクション > サブネットの関連付けを編集 の順にクリック
9.作成したサブネットをいずれも選択して、「保存」をクリック
※仮想デバイス用のサブネットもデプロイや疎通確認を行うため、検証時以外はパブリックサブネットとする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/a0c5a982-7448-0f68-8bd5-54e7ffee9a66.png)

以上の手順で以下の構成まで完成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/fcba036a-7749-7297-89c2-995cb43d31fd.png)
##1-2　EC2インスタンスの作成
仮想デバイスと踏み台サーバ用にEC2インスタンスを作成します。
また、セキュリティグループも合わせて作成します。
###セキュリティグループの作成
以下の要件を満たすようにセキュリティグループを2つ作成します

* 踏み台サーバはSSHアクセスのみ許可　※今回は接続元を問わない
* 仮想デバイスとするサーバは以下を許可
 * 仮想デバイスサーバ間はすべての通信を許可
 * 踏み台サーバからのSSH接続を許可

1.EC2のマネジメントコンソールで セキュリティグループ > 「セキュリティグループを作成」の順にクリック
2.以下の通り設定して「セキュリティグループを作成」をクリック

* セキュリティグループ名：※任意の名前
* 説明：※任意の説明
* VPC：※作成したVPC
* インバウンドルール：タイプ SSH / ソース 任意の場所
* アウトバウンドルール：タイプ すべてのトラフィック / 送信先 カスタム（0.0.0.0/0）※デフォルト
* タグ：キー Name / 値 ※任意の名前

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/ac05bd73-5088-6012-24fe-550b0262509a.png)
3.「セキュリティグループを作成」をクリックし、以下の通り設定して再度「セキュリティグループを作成」をクリック

* セキュリティグループ名：※任意の名前
* 説明：※任意の説明
* VPC：※作成したVPC
* インバウンドルール：タイプ SSH / ソース カスタム ※先ほど作成したセキュリティグループを指定
* アウトバウンドルール：タイプ すべてのトラフィック / 送信先 カスタム（0.0.0.0/0）※デフォルト
* タグ：キー Name / 値 ※任意の名前

4.「インバウンドルールを編集」をクリック
5.以下を追加して「ルールを保存」をクリック

* タイプ：すべてのトラフィック
* ソース：カスタム ※編集中のセキュリティグループ自身を指定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/56ed1bc6-1a22-2b05-eab2-fefa88b933bc.png)
###EC2インスタンス（仮想デバイス用サーバ）の作成
1.EC2のマネジメントコンソールで インスタンス > インスタンスを起動 の順にクリック
2.「Amazon Linux 2 AMI」（64ビット(x86)）の「選択」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/f5e569f1-4f06-7cf8-de42-7978667749c6.png)
3.「t2.micro」を選択し、「次のステップ：インスタンスの詳細の設定」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/34fa0b70-4229-920a-6d9f-93299f0c801b.png)
4.以下のみ設定を変更し、「次のステップ：ストレージの追加」をクリック

* インスタンス数：3
* ネットワーク：※作成したVPC
* サブネット：※踏み台サーバ用に作成したVPC
* 自動割り当てパブリックIP：有効

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/bf40e900-d6fb-2c89-3e71-70f797b5d3c3.png)
5.ストレージ設定は変更せず「次のステップ：タグの追加」をクリック
6.Nameタグを追加して「次のステップ：セキュリティグループの設定」をクリック
7.以下の通り設定して「確認と作成」をクリック

* セキュリティグループの割り当て：既存のセキュリティグループを選択する
* セキュリティグループ：※仮想デバイスサーバ用に作成したセキュリティグループ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/155493c9-fde6-31db-0fea-68e844ee2ffa.png)
8.「起動」をクリック
9.「新しいキーペアの作成」を選択し、キーペア名を入力して「キーペアのダウンロード」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/05de8ab0-f413-4aef-0c3e-8d21a315b9fb.png)
10.「インスタンスの作成」をクリック
11.作成されたインスタンスのName列をクリックし、各サーバの役割が分かるように名前をつけて「保存」をクリック
※Nameタグとして追加される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/be4e99d6-95f1-cec1-223d-c73f4f5e585b.png)
###EC2インスタンス（踏み台サーバ）の作成
1.EC2のマネジメントコンソールで インスタンス > インスタンスを起動 の順にクリック
2.「Amazon Linux 2 AMI」（64ビット(x86)）の「選択」をクリック
3.「t2.micro」を選択し、「次のステップ：インスタンスの詳細の設定」をクリック
4.以下のみ設定を変更し、「次のステップ：ストレージの追加」をクリック

* ネットワーク：※作成したVPC
* サブネット：※踏み台サーバ用に作成したVPC
* 自動割り当てパブリックIP：有効

5.ストレージ設定は変更せず「次のステップ：タグの追加」をクリック
6.Nameタグはここでは追加せず「次のステップ：セキュリティグループの設定」をクリック
7.以下の通り設定して「確認と作成」をクリック

* セキュリティグループの割り当て：既存のセキュリティグループを選択する
* セキュリティグループ：※踏み台サーバ用に作成したセキュリティグループ

8.「起動」をクリック
9.「既存のキーペアの選択」を選択し、先ほど作成したキーペアを選択する
10.「選択した～」のチェックボックスを選択し、「インスタンスの作成」をクリック
##1-3　接続確認
インスタンスの起動を確認できたら接続確認を行います。

1.EC2のマネジメントコンソールで インスタンス をクリック
2.踏み台サーバを選択し、ネットワーキングタブでパブリックIPv4アドレスをコピーする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/cdd1d810-16d0-cf0d-104a-7fe38bb2bf8c.png)
3.以下の情報でSSH接続を行う
※今回はTereTermで接続

* IPアドレス：※先ほどコピーしたIPアドレス
* ポート：22
* ユーザ名：ec2-user
* 秘密鍵：インスタンス作成時に取得したキーペア

4.`yum upgrade` と `yum update` を実施

```bash
sudo yum upgrade -y
sudo yum update -y
```
5.タイムゾーンを変更する
参考：[【Linux】タイムゾーン(Timezone)の変更](https://qiita.com/azusanakano/items/b39bd22504313884a7c3)

```bash
#タイムゾーンファイルの変更
sudo ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

#タイムゾーンが変更されていることを確認
date
Wed Apr 14 18:29:31 JST 2021

#再起動後にUTCに戻ることを防ぐため /etc/sysconfig/clock を編集
sudo vim /etc/sysconfig/clock
```
```:/etc/sysconfig/clock
ZONE="Asia/Tokyo"
UTC=false
```
6.踏み台サーバから仮想デバイス用のサーバにSSH接続するため、キーペアを踏み出サーバにアップロードする
※今回はFileZillaを利用
7.アップロードしたキーペアの権限を変更する

```bash
chmod 400 [アップロードしたキーペア]
```
8.EC2のマネジメントコンソールで仮想デバイス用サーバのプライベートIPアドレスをコピーする
9.踏み台サーバからSSH接続する

```
ssh ec2-user@[接続先のプライベートIPアドレス] -i [アップロードするしたキーペア]
```
10.仮想デバイス用のサーバでも `yum upgrade` と `yum update` を実施
11.仮想デバイス用のサーバでもタイムゾーンを変更する
12.検証で仮想デバイス用のサーバに何度も接続するため、接続用に以下のシェルスクリプトを作成した

```bash:connection.sh
#!/bin/bash

#set variables
USER="ec2-user"
KEY="[キーペアのパス]"
TIMESTAMP=`date "+%Y-%m-%d %H:%M:%S"`

#select server to connect
echo "+----------------------------+"
echo "接続先サーバを選択してください"
echo ""
echo "1 -> test-ggd-001"
echo "2 -> test-ggd-002"
echo "3 -> test-ggc-server"
echo "+----------------------------+"

read -p ">" INPUT

#set connection information
case "$INPUT" in
        "1") IP_ADDR="[対象サーバのプライベートIPアドレス]"; SERVER="[対象サーバ名]";;
        "2") IP_ADDR="[対象サーバのプライベートIPアドレス]"; SERVER="[対象サーバ名]";;
        "3") IP_ADDR="[対象サーバのプライベートIPアドレス]"; SERVER="[対象サーバ名]";;
        *) echo "[Error]1,2,3 のいずれかを入力してください"; exit 1
esac

#connect to target server
echo ">$TIMESTAMP $SERVER[$IP_ADDR]にssh接続します..."
ssh $USER@$IP_ADDR -i $KEY
```

以上の手順で以下の構成まで完成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/1076c69b-d049-32bc-c3ab-00a2a62558c4.png)
#2　Greengrassグループの設定
EC2インスタンスにGreengrassやMQTTクライアントをインストールし、仮想デバイスとして扱えるようにします。
##2-1　エッジ環境にGreengrassをインストール
まず、Greengrass Coreをインストールします。
インストールにあたっては、AWSのクレデンシャル情報が必要になります。
参考：[AWS IoT Greengrass > 開発者ガイド、バージョン 1 > クイックスタート: Greengrass デバイスのセットアップ](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/quick-start.html)

1.踏み台サーバ経由で仮想デバイス用サーバにssh接続する
2.クレデンシャル情報を環境変数に設定する

```bash
#環境変数の設定
export AWS_ACCESS_KEY_ID=[アクセスキー]
export AWS_SECRET_ACCESS_KEY=[シークレットアクセスキー]

#設定されていることを確認
echo $AWS_ACCESS_KEY_ID
echo $AWS_SECRET_ACCESS_KEY
```
3.インストール用のスクリプトを実行する

```
wget -q -O ./gg-device-setup-latest.sh https://d1onfpft10uf5o.cloudfront.net/greengrass-device-setup/downloads/gg-device-setup-latest.sh && chmod +x ./gg-device-setup-latest.sh && sudo -E ./gg-device-setup-latest.sh bootstrap-greengrass-interactive
```
4.コマンドプロンプト上でグループ名などをインタラクティブに設定する

```bash
[GreengrassDeviceSetup] Forwarding command-line parameters: bootstrap-greengrass-interactive

#先ほどの手順で環境変数に設定しているため、何も入力せずEnter
Enter your AWS access key ID, or press 'Enter' to read it from your environment variables.

#先ほどの手順で環境変数に設定しているため、何も入力せずEnter
Enter your AWS secret access key, or press 'Enter' to read it from your environment variables.

#一時認証情報を利用する場合は必要。そうでない場合は何も入力せずEnter
Enter your AWS session token, which is required only when you are using temporary security credentials. Press 'Enter' to read it from your environment variables or if the session token is not required.

#グループを作成するリージョンを指定。今回は東京リージョン（ap-northeast-1）
Enter the AWS Region where you want to create a Greengrass group, or press 'Enter' to use 'us-west-2'.
ap-northeast-1
#作成するグループ名を指定
Enter a name for the Greengrass group, or press 'Enter' to use 'GreengrassDeviceSetup_Group_aeb684fa-7dff-46ea-b68d-c57c0eca94ff'.
GGTEST_GROUP
#作成するGreengrass Coreの名前を指定
Enter a name for the Greengrass core, or press 'Enter' to use 'GreengrassDeviceSetup_Core_af687bff-efb2-4987-ad77-f37cdc5307b7'.
GGTEST_CORE
#Greengrass Coreのインストール先を指定。デフォルトでは '/' 配下にgreengrassディレクトリが作成される
Enter the installation path for the Greengrass core software, or press 'Enter' to use '/'.

#サンプルのLambdaをGreengrassグループに含めるか指定。今回は含めない
Do you want to include a Hello World Lambda function and deploy the Greengrass group? Enter 'yes' or 'no'.
no
#デプロイのタイムアウト時間を指定
Enter a deployment timeout (in seconds), or press 'Enter' to use '180'.

#インストール時のログファイルをどこに作成するか指定。デフォルトではカレントディレクトリに GreengrassDeviceSetup-YYYYMMDD-HHMMSS.log が作成される
Enter the path for the Greengrass environment setup log file, or press 'Enter' to use './'.
```
5.以下の通り返ってきたらセットアップ完了
※エラーが発生した場合は `Enter the path for the Greengrass environment setup log file, or press 'Enter' to use './'.` で設定したパスに作成されるログファイルを確認する

```bash
You can now use AWS IoT Console to manage your Greengrass group.
```
##2-2　Greengrassの自動起動設定
インスタンスを停止→起動した際にGreengrassが自動で起動するように設定します。
参考：[Greengrass(V1)の自動起動設定&コアデバイスの接続情報をLambdaで取得](https://qiita.com/kt-yt/items/877d735081adcee3da01)

1.以下の通りユニットの設定ファイルを作成する

```
#ユニットの設定ファイルを作成するディレクトリに移動
cd /etc/systemd/system

#ユニットの設定ファイルの作成
sudo touch greengrass.service
```
2.作成したユニットの設定ファイルに以下の通り設定を書き込む

```
sudo vim greengrass.service
```
```greengrass.service
[Unit]
Description=Greengrass Daemon

[Service]
Type=forking
PIDFile=/var/run/greengrassd.pid
Restart=on-failure
ExecStart=/greengrass/ggc/core/greengrassd start
ExecReload=/greengrass/ggc/core/greengrassd restart
ExecStop=/greengrass/ggc/core/greengrassd stop

[Install]
WantedBy=multi-user.target
```
3.サービスを有効化する

```
sudo systemctl enable greengrass.service
```
##2-3　グループにデバイスを追加
Greengrassグループにデバイスを追加します。
参考：[Greengrass(V1)のグループにデバイスを追加する](https://qiita.com/kt-yt/items/9b4c844ca03d737d59c5)

1.IoT Coreのマネジメントコンソールで Greengrass > クラシック(V1) > グループ の順にクリック
2.先ほど作成したグループが表示されているので名前をクリック
3.デバイス > デバイスの追加 の順にクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/ec0c55b2-4028-a3cf-56fe-caeeae33a8fe.png)
4.「新しいデバイスの作成」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/cd75d469-4d67-8fe8-f5fa-6fa8772a01f4.png)
5.任意の名前を入力して、「次へ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/97631529-7fc7-fab4-db78-89ef08d619df.png)
6.今回は1-clickデプロイを利用することとし、「デフォルトを使用」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/f9db7e14-28aa-4192-bd8d-172cc2753d74.png)
7.「これらのリソースはtar.gzとしてダウンロードしてください」をクリックし、「完了」をクリック
※AmazonのルートCAはのちの手順で取得する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/ad709fec-1db0-bd2b-bc65-3c1be94501b4.png)
8.手順3~7を繰り返して、デバイスをもう1つ追加する
9.アクション > デプロイ の順にクリック
※Greengrass Coreにグループ情報をデプロイする
※Greengrass Coreが起動していないとデプロイできないため注意
10.初回は検出方法を訊かれるので「自動検出」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/f1522101-880e-01bb-1ec1-fc95f360cd25.png)
11.「正常に完了しました」と表示されることを確認する
##2-4　仮想デバイス用サーバにモノの情報を渡す
デバイス情報をEC2インスタンスに転送し、仮想デバイスとして扱えるようにします。
また、Greengrass CoreのMQTTブローカーと通信するために必要なグループ証明書を取得します。

1.取得したtar.gzファイルを踏み台サーバにアップロードする
2.scpコマンドでそれぞれのサーバにtara.gzファイルを送信する

```bash
#今回は/home/ec2-user/ 配下に送る
scp -i [キーペア] xxxxxxxxx-setup.tar.gz ec2-user@[送信先サーバのプライベートIPアドレス]:~/
```
3.送信先の仮想デバイス用サーバでtar.gzファイルを解凍する

```bash
#tar.gzファイルを解凍
tar xvf xxxxxxxxx-setup.tar.gz

#.certsディレクトリを作成し、配下に格納
mkdir .certs
mv xxxxxxxxx* .certs
```
4.AmazonのルートCAを取得する

```bash
#証明書を格納したディレクトリに移動
cd .certs

#wgetでAmazonのルートCAを取得
wget "https://www.amazontrust.com/repository/AmazonRootCA1.pem"
```
5.グループ証明書取得用に以下のPythonファイルを作成する
※/home/ec2-user/.certs 配下に `groupCA.crt` という名前でグループ証明書を上書きで作成
※実行しているAPIについては以下を参考
　参考：[検出レスポンスドキュメントの例](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/gg-discover-api.html#gg-discover-response-doc)

```python:get_groupCA.py
import requests
import json
import argparse

#get args
psr = argparse.ArgumentParser(
        description = "get groupCA"
        )

psr.add_argument("-t", "--things", required=True, help="thing name")
psr.add_argument("-c", "--cert", required=True, help="cert file")
psr.add_argument("-k", "--key", required=True, help="private key")

args = psr.parse_args()

things = args.things
cert = args.cert
key = args.key

#set variables for requests
url = "https://greengrass-ats.iot.ap-northeast-1.amazonaws.com:8443/greengrass/discover/thing/" + things

#set path for CA file
CAfile = "/home/ec2-user/.certs/groupCA.crt"

#get present group CA
response = requests.get(url, cert=(cert, key))

#make groupCA file
jsonData = response.json()
groupCA = jsonData["GGGroups"][0]['CAs'][0]

f = open(CAfile, "w")
f.write(groupCA)
f.close()
```
6.作成したPythonファイルを実行して、グループ証明書を取得

```
python get_groupCA.py --things "モノの名前" --cert "デバイス証明書" --key "秘密鍵"
```
##2-5　Mosquittoのインストール
今回はGreengrassデバイス間のMQTT通信にMosquittoを使います。

1.踏み台サーバ経由で仮想デバイス用サーバに接続する
2.以下のコマンドでMosquittoをインストールする
参考：[EC2 の Amazon Linux 2 インスタンスに Mosquitto をインストールして SSL/TLS 認証を設定する](https://mseeeen.msen.jp/install-mosquitto-and-enable-ssl-tls-in-amazon-linux2/)

```bash
sudo amazon-linux-extras install epel -y

sudo yum install mosquitto -y
```
3.自動起動設定を行う

```bash
sudo systemctl start mosquitto

sudo systemctl enable mosquitto
```

以上の手順で以下の構成まで完成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/6b17317d-47aa-7316-69b3-3e8b26cc958d.png)
#3　検証① Greengrassデバイス間でMQTT通信
Greengrass(V1)はMQTTブローカーとして、Greengrassデバイス間のMQTT通信を可能にします。
エッジ環境内で完結するはずなので、IoT Coreと接続していない状態でも動くことを確認してみます。
##3-1　MQTT通信用シェルスクリプトの作成
Mosquittoを使ってPublish、Subscribeするためのシェルスクリプトを作成します。
今回は、IoT CoreへのPub/Sub、Greengrass CoreへのPub/Sub双方を試すので、シェルスクリプトで分岐処理できるようにしています。

なお、Greengrass Group内はQoS=0、Greengrass CoreからIoT Coreへの通信はQoS=1になる点に注意します。
※Greengrass Group内のPub/Subを試した際、QoS=1を指定していたところ、エラーが発生した

また、今回は検証用ということでGreengrass Coreのエンドポイント（ローカルのIPアドレス）をシェルスクリプト内に直接記載していますが、Greengrass Discovery APIで都度取得することが推奨されています。
参考：[Greengrass コアを使用したデバイス認証の管理](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/security-best-practices.html#manage-device-authentication-with-core)

1.IoT Coreのマネジメントコンソールで 設定 をクリックし、IoT Coreのエンドポイントを控える
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/a5379b68-b6f7-9212-a8af-6283b1026d79.png)
2.仮想デバイス用（Publish側）のサーバで以下のシェルスクリプトを作成

```bash:mqtt_pub.sh
#!/bin/bash

#set common variables
CERT_DIR=/home/ec2-user/.certs
CERT=$CERT_DIR/[モノの証明書]
KEY=$CERT_DIR/[秘密鍵]
THING_NAME=[モノの名前]

TLS_VERSION=tlsv1.2
PORT=8883
MESSAGE="{\"message\":\"Helloworld\"}"

#select target to connect, AWS IoT Core or Greengrass Core
echo ">接続先を選択してください"
echo ">1 -> Greengrass Core"
echo ">2 -> IoT Core"

read -p ">" INPUT

#input topic
echo ">publishするtopicを入力してください"

read -p ">" TOPIC

#set variables about connection 
case "$INPUT" in
	"1") 
        QOS=0;
        CA=$CERT_DIR/groupCA.crt;
        ENDPOINT=[Greengrass CoreのローカルIPアドレス];;
	"2") 
        QOS=1;
        CA=$CERT_DIR/AmazonRootCA1.pem;
        ENDPOINT=[IoT Coreのエンドポイント];;
	*) 
        echo "[Error]1,2 のいずれかを入力してください"; 
        exit 1;;
esac

#send payload per 5 seconds
while :
    do
        mosquitto_pub --cafile $CA --cert $CERT --key $KEY \
            -i $THING_NAME --tls-version $TLS_VERSION \
            -h $ENDPOINT -p $PORT -q $QOS -t $TOPIC -m $MESSAGE -d

        sleep 5
    done
```
2.実行権限を付与する

```bash
chmod 744 mqtt_pub.sh
```
3.仮想デバイス用（Subscribe側）のサーバで以下のシェルスクリプトを作成

```bash:mqtt_sub.sh
#!/bin/bash

#set common variables
CERT_DIR=/home/ec2-user/.certs
CERT=$CERT_DIR/[モノの証明書]
KEY=$CERT_DIR/[秘密鍵]
THING_NAME=[モノの名前]

TLS_VERSION=tlsv1.2
PORT=8883

#select target to connect, AWS IoT Core or Greengrass Core
echo ">接続先を選択してください"
echo ">1 -> Greengrass Core"
echo ">2 -> IoT Core"

read -p ">" INPUT

#input topic
echo ">subscribeするtopicを入力してください"

read -p ">" TOPIC

#set variables about connection 
case "$INPUT" in
	"1") 
        QOS=0;
        CA=$CERT_DIR/groupCA.crt;
        ENDPOINT=[Greengrass CoreのローカルIPアドレス];;
	"2") 
        QOS=1;
        CA=$CERT_DIR/AmazonRootCA1.pem;
        ENDPOINT=[IoT Coreのエンドポイント];;
	*) 
        echo "[Error]1,2 のいずれかを入力してください"; 
        exit 1;;
esac

#send payload
mosquitto_sub --cafile $CA --cert $CERT --key $KEY \
        -i $THING_NAME --tls-version $TLS_VERSION \
        -h $ENDPOINT -p $PORT -q $QOS -t $TOPIC -d
```
4.実行権限を付与する

```bash
chmod 744 mqtt_sub.sh
```
##3-2　サブスクリプションの設定
Greengrass Coreを介してメッセージをやりとりする場合は、グループのサブスクリプションを設定する必要があります。

1.IoT Coreのマネジメントコンソールで Greengrass > クラシック(V1) > グループ の順にクリック
2.対象のグループ名 > サブスクリプション > サブスクリプションの追加 の順にクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/3635efc3-ecd6-706d-bb4f-a8f466e31b47.png)
3.ソースとターゲットを以下の通り選択して、「次へ」をクリック

* ソース：`mqtt_pub.sh` を作成した仮想デバイス
* ターゲット：`mqtt_sub.sh` を作成した仮想デバイス

4.トピックのフィルターに任意のtopicを入力して「次へ」をクリック
5.内容を確認して「完了」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/b92a3e6d-012d-5dc2-14e7-3e21d23a87f2.png)
6.アクション > デプロイ の順にクリック
##3-3　疎通確認（IoT Core経由）
Greengrassのグループに登録したモノは、同様にIoT Coreのモノとしても登録されます。
そのため、モノの証明書と秘密鍵でIoT Coreと直接通信できます。

1.IoT Coreのマネジメントコンソールで テスト をクリック
2.トピックのフィルターに任意のtopicを入力して、「サブスクライブ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/f7f401d6-edc1-ab55-7714-0402bb187190.png)
3.`mqtt_pub.sh` を作成した仮想デバイス用サーバに踏み台サーバ経由で接続する
4.`mqtt_pub.sh` を実行し、IoT Coreでサブスクライブ中のtopicにメッセージをpublishする

```bash
#mqtt_pub.shが格納されているディレクトリで実行した場合
./mqtt_pub.sh
>接続先を選択してください
>1 -> Greengrass Core
>2 -> IoT Core
#今回はIoT Core宛にpublishするので2を選ぶ
>2
>publishするtopicを入力してください
#先程の手順で設定したtopicを入力
>test/pub
#5秒おきに以下の通りメッセージを送付する
Client test-ggd-001 sending CONNECT
Client test-ggd-001 received CONNACK (0)
Client test-ggd-001 sending PUBLISH (d0, q1, r0, m1, 'test/pub', ... (24 bytes))
Client test-ggd-001 received PUBACK (Mid: 1, RC:0)
Client test-ggd-001 sending DISCONNECT
```
5.IoT Coreのマネジメントコンソールでサブスクライブされたことを確認する
6.今度は`mqtt_sub.sh` を作成した仮想デバイス用サーバに踏み台サーバ経由で接続する
7.`mqtt_sub.sh` を実行し、IoT Coreでサブスクライブ中のtopicにメッセージをsubscribeする

```bash
#mqtt_pub.shが格納されているディレクトリで実行した場合
./mqtt_sub.sh
>接続先を選択してください
>1 -> Greengrass Core
>2 -> IoT Core
#今回はIoT Core宛にsubscribeするので2を選ぶ
>2
>subscribeするtopicを入力してください
#任意のtopicを指定
>test/sub
#以下の通りサブスクライブが開始される
Client test-ggd-002 sending CONNECT
Client test-ggd-002 received CONNACK (0)
Client test-ggd-002 sending SUBSCRIBE (Mid: 1, Topic: test/sub, QoS: 1, Options: 0x00)
Client test-ggd-002 received SUBACK
Subscribed (mid: 1): 1
```
8.IoT Coreのマネジメントコンソールで テスト > トピックに公開する の順にクリック
9.トピック名に先程指定したtopicを入力し、「発行」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/8b07331a-93ca-ab09-bd58-33eaec9d5c61.png)
10.メッセージをサブスクライブできることを確認する

```bash
Client test-ggd-002 received PUBLISH (d0, q0, r0, m0, 'test/sub', ... (57 bytes))
{
  "message": "AWS IoT コンソールからの挨拶"
}
```

ここでは以下の構成で確認を行いました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/95821f85-7156-8bb9-c14a-57e340c34466.png)
##3-4　疎通確認（Greengrass Core経由）
GreengrassのMQTTブローカー経由でメッセージをやりとりできることを確認します。
この際、サブネットのルートテーブルをローカルのみ通信するものに変更し、エッジ環境が内に閉じている状況でもやりとりできることをあわせて確認します。

1.VPCのマネジメントコンソールで サブネット をクリック
2.仮想デバイス用サーバが起動しているサブネットを選択し ルートテーブルタブ > ルートテーブルの関連付を編集 の順にクリック
3.送信先がlocalのみのルートテーブルを選択して、「保存」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/d9ac946c-35a6-945e-359b-8cdb841bc184.png)
4.`mqtt_pub.sh` を作成した仮想サーバと `mqtt_sub.sh` を作成した仮想サーバそれぞれに踏み台サーバ経由で接続する
5.`mqtt_pub.sh` 、`mqtt_sub.sh` がいずれも実行できないことを確認する
※IoT Coreと接続できないため
　シェルスクリプトはいずれもプロンプトが返ってこないまま止まるので、`Ctrl + C` で終了する
6.グループのサブスクリプションで設定したtopic宛にpub/subを実施し、Greengrass CoreのMQTTブローカー経由でやりとりできることを確認する

```bash:subscribe側
#mqtt_sub.shが格納されているディレクトリで実行した場合
./mqtt_sub.sh
>接続先を選択してください
>1 -> Greengrass Core
>2 -> IoT Core
#今回はGreengrass Core宛にsubscribeするので1を選ぶ
>1
>subscribeするtopicを入力してください
#グループのサブスクリプションで設定したtopicを指定
>test/pubsub
#以下の通りサブスクライブが開始される
Client test-ggd-002 sending CONNECT
Client test-ggd-002 received CONNACK (0)
Client test-ggd-002 sending SUBSCRIBE (Mid: 1, Topic: test/pubsub, QoS: 0, Options: 0x00)
Client test-ggd-002 received SUBACK
Subscribed (mid: 1): 0
```
```bash:publish側
#mqtt_pub.shが格納されているディレクトリで実行した場合
./mqtt_pub.sh
>接続先を選択してください
>1 -> Greengrass Core
>2 -> IoT Core
#今回はGreengrass Core宛にpublishするので1を選ぶ
>1
>publishするtopicを入力してください
#グループのサブスクリプションで設定したtopicを指定
>test/pubsub
#5秒おきに以下の通りメッセージを送付する
Client test-ggd-001 sending CONNECT
Client test-ggd-001 received CONNACK (0)
Client test-ggd-001 sending PUBLISH (d0, q0, r0, m1, 'test/pubsub', ... (24 bytes))
Client test-ggd-001 received PUBACK (Mid: 1, RC:0)
Client test-ggd-001 sending DISCONNECT
```
```bash:subsribe側
Client test-ggd-002 received PUBLISH (d0, q0, r1, m0, 'test/pubsub', ... (24 bytes))
{"message":"Helloworld"}
```
ここでは以下の構成で確認を行いました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/38925413-e3a8-d983-e0e4-9e78e71ff38c.png)
#4　検証② ローカルLambdaの実行
Greengrassはエッジ環境でLambdaを実行することができます。
こちらも、IoT Coreと接続していない状態でも動くことを確認してみます。
##4-1　Greengrass用のLambdaの作成
今回は特定topicへのメッセージをトリガーして起動するLambdaを作成します。
起動後、ローカルにログファイルの出力とIoT CoreへMQTTのメッセージを通知させます。
Lambda作成にあたっては「AWS IoT Greengrass Core SDK for Python」を利用しています。
###プログラムの作成
Greengrass SDKの利用にあたっては、デプロイするLambdaの中にSDKのパッケージを含める必要があります。
> Greengrass Lambda 関数で SDK を使用するには、AWS Lambda にアップロードする Lambda 関数デプロイパッケージに SDK を含めます。
出典：[SDKs 関数のGreengrass Lambda](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/lambda-functions.html#lambda-sdks)

1.「AWS IoT Greengrass Core SDK for Python」のGitHubレポジトリにアクセスし、Code > Download ZIP の順にクリック
Github：[AWS IoT Greengrass Core SDK for Python](https://github.com/aws/aws-greengrass-core-sdk-python)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/0bfb98b9-10f8-9dcd-f021-0cd39e1a2785.png)
2.ダウンロードした「aws-greengrass-core-sdk-python-master.zip」を解凍する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/06126a0c-3972-dfd6-ed57-c5f8c1c5be93.png)
3.今回は以下のプログラムを作成する

```python:gg_nwtest.py
import greengrasssdk
import logging
import platform
import sys
from threading import Timer
import json
import subprocess
import datetime

logger = logging.getLogger(__name__)
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

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

logfile = "/tmp/gg-nw-test.log"

def gg_nwtest(event, context):
    date = datetime.datetime.now()
    timestamp = date.strftime("%Y-%m-%d %H:%M:%S")
    
    client.publish(
        topic="test/gg-nwtest",
        payload=json.dumps(
            {
                "function": "gg-nwtest", 
                "message": "This is Test",
                "version":  "1.0",
                "timestamp": timestamp
            }
        )
    )
    
    f = open(logfile, mode="a")
    f.write(timestamp + " success!\n")
    f.close()
```
4.作成したプログラムと「greengrasssdk」フォルダをまとめて圧縮
###Lambdaの作成
作成したプログラムをLambdaとして登録します。

1.Lambdaのマネジメントコンソールで「関数の作成」をクリック
2.「一から作成」を選択し、以下の通り設定して「関数の作成」をクリック

* 関数名：※任意の関数名
* ランタイム：Python 3.7

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/aba714c3-6d2b-318b-ea67-0723737b3ef6.png)
3.関数が作成されたら、コードタブで アップロード元 > zipファイル の順にクリック
4.ファイル選択ウィンドウで先ほど作成したzipファイル（プログラムと「greengrasssdk」をまとめて圧縮したもの）を選択して「保存」をクリック
※これにより、以下の通り作成した関数とSDKをLambda関数に反映できる
5.コードタブでランタイム設定の「編集」をクリック
6.ハンドラを「[プログラムファイル名].[関数名]」に修正して「保存」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/14302c1e-d4cb-cc9d-8af1-0a1c469aaef4.png)
7.関数画面で アクション > 新しいバージョンを発行 をクリックし、「発行」をクリックする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/35c8f50b-97da-f1ca-7825-6b15da366939.png)
7.アクション > エイリアスを作成 をクリック
8.以下の通りエイリアス設定を入力し、「保存」をクリックする

* 名前：※任意のエイリアス名
* バージョン：1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/1e2a590f-fd1d-d15e-ed8e-cd8c8a0d8325.png)
###LmabdaをGreengrassグループに登録
作成したLambda関数をGreengrassグループに登録します。

1.IoT Coreのマネジメントコンソールで Greengrass > クラシック(V1) > グループ の順にクリック
2.対象のグループ名 > Lambda > 「Lambdaの追加」の順にクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/9306190d-967d-7310-95af-1e53d552f89f.png)
3.「既存のLambdaの使用」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/7de32ac4-8240-c38f-eb20-93e21cbf41b1.png)
4.Lambdaの選択で先ほど作成したLambdaを選択して、「次へ」をクリック
5.バージョンの選択で先ほど作成したエイリアスを選択して、「完了」をクリック
6.追加したLambdaの「…」 > 設定の編集 の順にクリック
7.Lambdaのライフサイクルで「オンデマンド関数」を選択して、「更新」をクリック
※イベント契機で実行するLambdaとして設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/7808af2a-e977-8cd4-37d9-8e6070a9ca7c.png)
###サブスクリプションの追加
Lambdaからのメッセージ送信、Lambdaへのメッセージ送信をそれぞれ設定します。
サブスクリプションの設定手順の詳細は「3-2　サブスクリプションの設定」をご参照ください。

1.Lambda -> IoT CoreのMQTT通信を許可するため、以下のサブスクリプションをグループに追加する

* ソース：※グループに追加したLambda
* ターゲット：IoT Cloud
* トピック：test/gg-nwtest ※Lambdaからメッセージを送る宛先のtopic

2.Greengrassデバイス -> LambdaのMQTT通信を許可するため、以下のサブスクリプションをグループに追加する

* ソース：※`mqtt_pub.sh` を作成した仮想デバイス
* ターゲット：※グループに追加したLambda
* トピック：※任意のtopic

###ローカルリソースアクセスの追加
今回デプロイするLambdaは /tmp ファイルを作成し実行時刻を書き込みます。
Greengrass Coreを実行しているデバイス上のローカルリソースにアクセスするためには、ローカルリソースアクセスを追加します。
参考：[Lambda 関数とコネクタを使ってローカルリソースにアクセスする](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/access-local-resources.html)

1.IoT Coreのマネジメントコンソールで Greengrass > クラシック(V1) > グループ の順にクリック
2.対象のグループ名 > リソース > 「ローカルリソースの追加」の順にクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/c731ad7c-89d5-ee85-53d0-18116f6cad08.png)
3.以下の通り設定して、「保存」をクリック

* リソース名：※任意のリソース名
* リソースタイプ：ボリューム
* ソースパス：/tmp ※ログを書き出す先のディレクトリ（ここにログが書き出される）
* 送信先パス：/tmp ※Lambdaのコード上で指定したログを書き出す先のディレクトリ
* グループ所有者のファイルアクセス許可：OSグループなし ※デフォルト
* Lambda関数の関連：※作成したLambda関数 / 読み取りと書き込みアクセス

4.以上でグループの設定が完了したので、アクション > デプロイ の順にクリック
※「3-4　疎通確認（Greengrass Core経由）」で仮想デバイス用のサブネットのルートテーブルをローカル宛のみのものにしたままの場合は、0.0.0.0/0 -> IGW を含むルートテーブルに切り替える

以上の手順で以下の構成まで完成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/2993c063-552a-72f2-0478-ae5d5d4bcfaf.png)
##4-2　動作確認（Greengrass Device -> Lambda）
仮想デバイス用のサーバがIoT Coreと接続していない状態でも動くことを確認します。

1.IoT Coreのマネジメントコンソールで テスト をクリック
2.LambdaでMQTTでpublishする宛先としたtopicをサブスクライブする
3.Greengrass Coreをインストールした仮想デバイス用サーバ、`mqtt_pub.sh` を作成した仮想デバイス用サーバにそれぞれ踏み台サーバ経由で接続する
4.`mqtt_pub.sh` を実行する

```bash
#mqtt_pub.shが格納されているディレクトリで実行した場合
./mqtt_pub.sh
>接続先を選択してください
>1 -> Greengrass Core
>2 -> IoT Core
#今回はGreengrass Core宛にpublishするので1を選ぶ
>1
>publishするtopicを入力してください
#グループのサブスクリプションで設定したtopicを指定
>cmd/gg-nwtest
```
5.以下をそれぞれ確認する

* IoT Coreでメッセージをサブスクライブできている
* Greengrass Coreをインストールした仮想デバイス用サーバに `/tmp/nw-test.log` が作成されている
* `/tmp/nw-test.log` に実行時刻が書き込まれている

```
cat /tmp/nw-test.log
2021-04-16 20:06:18 success!
2021-04-16 20:06:23 success!
```
6.仮想デバイス用のサブネットのルートテーブルをローカル宛のみものに切り替える
※詳しい手順は「3-4　疎通確認（Greengrass Core経由）」を参照
7.以下を確認する

* IoT Coreにメッセージが届かなくなる
* `/tmp/nw-test.log` が継続して更新される

```bash
#5秒おきにファイル末尾に実行時刻が追記されていくことを確認する
tail -F /tmp/nw-test.log
```
ここでは以下の構成で確認を行いました。
#5　検証③ Greengrass Core -> IoT Coreのメッセージ保持
GreengrassからIoT Coreへ送付するMQTTメッセージは、未処理の場合キューに保存されます。
そのため、クラウドとの接続が切れてしまっても疎通再開後に送信することができます。
>AWS クラウドターゲットを送信先とする MQTT メッセージは、処理待ちとしてキューされます。キュー状態のメッセージは先入れ先出し (FIFO) の順序で処理されます。メッセージが処理され、AWS IoT Core に発行された後、このメッセージはキューから削除されます。
デフォルトでは、Greengrass コアは AWS クラウドターゲット宛ての未処理のメッセージをメモリに保存します。代わりにコアを設定して、未処理のメッセージをメモリあるいはローカルストレージキャッシュに保存できます。インメモリストレージとは異なり、ローカルストレージキャッシュにはコアの再起動の後でも維持される機能があるため (たとえば、グループデプロイ後あるいはデバイスの再起動後など)、AWS IoT Greengrass はメッセージの処理を続けられます。また、ストレージサイズを設定することもできます。
参考：[MQTT設定の設定 - クラウドターゲットのMQTTメッセージキュー](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/gg-core.html#mqtt-message-queue)

ここでは上記の挙動を確認します。
##5-1　Lambdaの設定変更
先ほど作成したLambdaの設定を変更して、Greengrass Coreをインストールした仮想デバイス用サーバから5秒おきにメッセージを送り続けるようにします。
###コードの修正
1.Lambdaのマネジメントコンソールで 関数 > 作成した関数名 の順にクリック
2.コードを以下の通り変更し、「Deploy」をクリック

```python:gg_nwtest.py
import greengrasssdk
import logging
import platform
import sys
from threading import Timer
import json
import subprocess
import datetime

logger = logging.getLogger(__name__)
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

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

logfile = "/tmp/gg-nw-test.log"

def gg_nwtest():
    date = datetime.datetime.now()
    timestamp = date.strftime("%Y-%m-%d %H:%M:%S")
    
    client.publish(
        topic="test/gg-nwtest",
        payload=json.dumps(
            {
                "function": "gg-nwtest", 
                "message": "This is Test",
                "version":  "1.0",
                "timestamp": timestamp
            }
        )
    )
    
    f = open(logfile, mode="a")
    f.write(timestamp + " success!\n")
    f.close()

    Timer(5, gg_nwtest).start()

gg_nwtest()
```
3.アクション > 新しいバージョンを発行 > 「発行」の順にクリック
4.画面上部の関数名 > エイリアスタブ の順にクリック
5.作成済みのエイリアスを選択して、「編集」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/62a771d7-6ed0-3d12-7520-05787c2e5402.png)
6.先ほど発行したバージョンを選択して、「保存」をクリック
※これにより、GreengrassグループがデプロイするLambdaのバージョンがエイリアスに紐づけたバージョンとなる（GreengrassでLambdaのバージョン指定をエイリアスにしていることが前提）
###設定の変更
1.IoT Coreのマネジメントコンソールで Greengrass > クラシック(V1) > グループ の順にクリック
2.対象のグループ名 > Lambda > 対象のLambda関数の「…」 > 設定の編集 の順にクリック
3.Lambdaのライフサイクルで「存続期間が長く無制限に稼働する関数にする」を選択して、「更新」をクリックする
4.アクション > デプロイ の順にクリック

以上の手順で以下の構成まで完成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/e07db50c-d70f-5d31-bcb9-e21a78e325a4.png)
##5-2　動作確認①
仮想デバイス用サーバとIoT Core間の接続を切断→回復し、その間のMQTTメッセージが届くことを確認します。

なお、Greengrassはデフォルトではメッセージをキャッシュに保存しているため再起動によってデータは失われてしまいます。
疎通再開前にGreengrassを再起動した場合の挙動もあわせて確認します。
###確認準備
1.IoT Coreのマネジメントコンソールで テスト をクリック
2.トピックをサブスクライブするタブで追加設定をクリックし、以下の設定で「サブスクライブ」をクリック

* トピックのフィルター：test/gg-nwtest ※LambdaがMQTTメッセージをpublishする宛先topic
* 保持するメッセージ数：100 ※切断前後、溜めていた分も確認するため多めに設定する
* その他：※デフォルト設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/24cc5319-821b-4b08-d57f-d5d38f5d6b9f.png)
3.Greengrass Coreをインストールした仮想デバイス用サーバに踏み台サーバ経由で接続する
※2つのウィンドウで接続しておくと後の確認作業が楽
###検証
今回は以下の流れで確認します。

* STEP1：IoT Coreでサブスクライブ（スタート地点）
* STEP2：接続断
* STEP3：Greemgrass Core再起動
* STEP4：接続再開

STEP2~3の間に生成されたMQTTメッセージ（キャッシュに保持）は再起動によって失われます。
一方で、STEP3~4の間に生成されたMQTTメッセージは、再起動がないためキャッシュ上のメッセージは消えず、IoT Coreに送信されることを確認します。

1.IoT Coreのテスト画面でサブスクライブし続けていることを確認する
2.仮想デバイス用サーバで `tail -F /tmp/gg-nwtest.log` を実行し、ローカルでLambdaが実行されていることを確認する
3.仮想デバイス用のサブネットのルートテーブルをローカル宛のみものに切り替える
※詳しい手順は「3-4　疎通確認（Greengrass Core経由）」を参照
4.以下を確認する

* IoT Coreにメッセージが届かなくなる
* `gg-nwtest.log` に継続して書き込みが行われている（＝Lambdaは継続して動いている）

5.仮想デバイス用サーバでGreengrass Coreを再起動する

```bash
sudo systemctl restart greengrass
```
6.`tail -F /tmp/gg-nwtest.log` でログが再度書き出されることを確認する
※Greengrassが起動し、Lmabdaも実行されたことの確認

```bash
tail -F /tmp/gg-nwtest.log
#(中略)
2021-04-17 17:31:36 success!
2021-04-17 17:31:41 success!
2021-04-17 17:31:46 success!
2021-04-17 17:31:51 success!
2021-04-17 17:31:56 success!
2021-04-17 17:32:01 success!
#接続断から17:32:01までのメッセージは再起動で消失する想定
#17:32:02〜17:32:55の間はGreengrassは再起動中のためメッセージなし
#以降のメッセージは接続再開で送信される想定
2021-04-17 17:32:56 success!
2021-04-17 17:33:01 success!
2021-04-17 17:33:06 success!
```
7.仮想デバイス用のサブネットのルートテーブルをIGW宛を含むものに切り替える
8.IoT Coreに届くメッセージで以下を確認する

* ネットワーク切断後〜再起動までのメッセージが届かない（=再起動で消失）
* 再起動後から接続再開後のメッセージは届く

以下は再開後のメッセージですが、2021-04-17 17:32:55までのデータが抜けています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/28dfd0ea-c43a-4492-f274-9797ee34d19b.png)
一方で再起動以降のデータは接続再開後にまとめて送付されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/a2ff140b-5623-233a-0ac2-4de2a0b69715.png)
##5-3　MQTTの設定変更
Greengrassを再起動してもメッセージが消えないように、メッセージキューの保存場所をローカルストレージに変更します。
設定はマネジメントコンソールではできないので、APIを使って行います。
参考：[MQTT設定の設定 - ローカルストレージでメッセージをキャッシュするには](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/gg-core.html#configure-local-storage-cache)

1.踏み台サーバに接続する
※今回は踏み台サーバ上でMQTT設定の変更を行う
2.レスポンスのjsonデータを整形するためにjqをインストールする
参考：[jqコマンド(jsonデータの加工, 整形)の使い方](https://www.wakuwakubank.com/posts/676-linux-jq/)

```bash
sudo yum install jq -y
```
3.以下のシェルスクリプトを作成する
※公式ドキュメントで実施している手順をシェルスクリプトとして整理したもの

```bash:mqtt_setting.sh
#!/bin/bash

#set common variables
LOG="/home/ec2-user/mqtt_setting.log"
START_TIME=`date "+%Y-%m-%d %H:%M:%S"`

#set mqtt settings to define
echo ""
echo "MQTT設定情報を入力してください"

read -p ">設定対象のグループ名：" TARGET_GROUP
read -p ">キューの場所（FileSystem or Memory ）：" GG_CONFIG_STORAGE_TYPE
read -p ">ストレージ容量（262144 バイト以上）：" GG_CONFIG_MAX_SIZE_BYTES

#check mqtt setting variables
if [ $GG_CONFIG_STORAGE_TYPE != "FileSystem" ] &&  [ $GG_CONFIG_STORAGE_TYPE != "Memory" ] ; then
    echo "[ERROR]キューの場所はFileSystemかMemoryを選んでください"
    exit 1 ;
fi

if [ $GG_CONFIG_MAX_SIZE_BYTES -lt 262144 ] ; then
    echo "[ERROR]ストレージ容量は262144以上（単位はバイト）にしてください"
    exit 1 ;
fi

#get group information
GROUP_INFO=`aws greengrass list-groups --query "Groups[?Name=='$TARGET_GROUP']" | jq -c .`

LATEST_GROUP_VERSION=`echo $GROUP_INFO | jq -r .[].LatestVersion`
GROUP_ID=`echo $GROUP_INFO | jq -r .[].Id`

#get present group definition
GROUP_DEFINITION=`aws greengrass get-group-version \
        --group-id $GROUP_ID \
        --group-version-id $LATEST_GROUP_VERSION | jq -c .`

#set each definition arn
CORE_DEFINITION_ARN=`echo $GROUP_DEFINITION | jq -r .Definition.CoreDefinitionVersionArn`
RESOURCE_DEFINITION_ARN=`echo $GROUP_DEFINITION | jq -r .Definition.ResourceDefinitionVersionArn`
FUNCTION_DEFINITION_ARN=`echo $GROUP_DEFINITION | jq -r .Definition.FunctionDefinitionVersionArn`
DEVICE_DEFINITION_ARN=`echo $GROUP_DEFINITION | jq -r .Definition.DeviceDefinitionVersionArn`
LOGGER_DEFINITION_ARN=`echo $GROUP_DEFINITION | jq -r .Definition.LoggerDefinitionVersionArn`
SUBSCRIPTION_DEFINITION_ARN=`echo $GROUP_DEFINITION | jq -r .Definition.SubscriptionDefinitionVersionArn`

FUNCTION_DEFINITION_ID=`echo $FUNCTION_DEFINITION_ARN | awk -F "/" '{print $5}'`
FUNCTION_DEFINITION_VERSION_ID=`echo $FUNCTION_DEFINITION_ARN | awk -F "/" '{print $7}'`

#get present lambda functions definition
LAMBDA_DEFENITION=`aws greengrass get-function-definition-version \
    --function-definition-id $FUNCTION_DEFINITION_ID \
    --function-definition-version-id $FUNCTION_DEFINITION_VERSION_ID | jq -c .`

LAMBDA_ARN=`echo $LAMBDA_DEFENITION | jq -r .Arn`
LAMBDA_FUNCTIONS=`echo $LAMBDA_DEFENITION | jq -c .Definition.Functions`

#add function definition
NEW_FUNCTIONS=${LAMBDA_FUNCTIONS%]}',{"FunctionArn": "arn:aws:lambda:::function:GGCloudSpooler:1","FunctionConfiguration":{"Environment": {"Variables":{"GG_CONFIG_MAX_SIZE_BYTES":"'$GG_CONFIG_MAX_SIZE_BYTES'","GG_CONFIG_STORAGE_TYPE":"'$GG_CONFIG_STORAGE_TYPE'"}},"Executable": "spooler","MemorySize": 32768,"Pinned": true,"Timeout": 3},"Id": "spooler-function"}]'

NEW_FUNCTION_DEFINITION=`aws greengrass create-function-definition-version \
    --function-definition-id $FUNCTION_DEFINITION_ID \
    --functions "$NEW_FUNCTIONS" | jq -c . `

NEW_FUNCTION_DEFINITION_ARN=`echo $NEW_FUNCTION_DEFINITION | jq -r .Arn`
NEW_FUNCTION_DEFINITION_VERSION=`echo $NEW_FUNCTION_DEFINITION | jq -r .Version`

#create new group version
NEW_GROUP_VERSION=`aws greengrass create-group-version \
    --group-id $GROUP_ID \
    --core-definition-version-arn $CORE_DEFINITION_ARN \
    --function-definition-version-arn $NEW_FUNCTION_DEFINITION_ARN \
    --device-definition-version-arn $DEVICE_DEFINITION_ARN \
    --logger-definition-version-arn $LOGGER_DEFINITION_ARN \
    --resource-definition-version-arn $RESOURCE_DEFINITION_ARN \
    --subscription-definition-version-arn $SUBSCRIPTION_DEFINITION_ARN | jq -c .`

NEW_GROUP_VERSION_ARN=`echo $NEW_GROUP_VERSION | jq -r .Arn`
NEW_GROUP_VERSION_ID=`echo $NEW_GROUP_VERSION | jq -r .Version`

#deploy new group
DEPROIMENT_INFO=`aws greengrass create-deployment \
    --group-id $GROUP_ID \
    --group-version-id $NEW_GROUP_VERSION_ID \
    --deployment-type NewDeployment | jq -c .`

echo ">設定が完了しました！"

#logging
if [ ! -f $LOG ] ; then
    touch $LOG
fi

FINISH_TIME=`date "+%Y-%m-%d %H:%M:%S"`

{
    echo "------------------------------------------------------------------------" 
    echo "####基本情報####"
    echo "リージョン：$AWS_DEFAULT_REGION"
    echo "開始時刻："$START_TIME
    echo "終了時刻："$FINISH_TIME
    echo ""
    echo "####各変数情報####"
    echo "設定情報："
    echo "　キューの場所："$GG_CONFIG_STORAGE_TYPE
    echo "　ストレージ容量："$GG_CONFIG_MAX_SIZE_BYTES
    echo ""
    echo "変更前グループ情報："
    echo "　Latest Group Version："$LATEST_GROUP_VERSION
    echo "　Group ID："$GROUP_ID
    echo ""
    echo "変更前グループ定義ARN："
    echo "　Core Definition Version Arn:"$CORE_DEFINITION_ARN
    echo "　Resource Definition Version Arn:"$RESOURCE_DEFINITION_ARN
    echo "　Function Definition Version Arn:"$FUNCTION_DEFINITION_ARN
    echo "　Device Definition Version Arn:"$DeviceDefinitionVersionArn
    echo "　Logger Definition Version Arn:"$LoggerDefinitionVersionArn
    echo "　Subscription Definition Version Arn:"$SubscriptionDefinitionVersionArn
    echo ""
    echo "変更前Lambda関数定義："
    echo "　Function Definition ID："$FUNCTION_DEFINITION_ID
    echo "　Function Definition Version ID："$FUNCTION_DEFINITION_VERSION_ID
    echo ""
    echo "変更後Lambda関数定義："
    echo "  Function Definition ARN："$NEW_FUNCTION_DEFINITION_ARN
    echo "  Function Definition Version ID："$NEW_FUNCTION_DEFINITION_VERSION
    echo ""
    echo "変更後グループ情報："
    echo "  ARN："$NEW_GROUP_VERSION_ARN
    echo "  Group Version:"$NEW_GROUP_VERSION_ID
    echo ""
    echo "####実行コマンドとレスポンス####"
    echo "aws greengrass list-groups --query \"Groups[?Name==\'$TARGET_GROUP\']\""
    echo $GROUP_INFO | jq .
    echo ""
    echo "aws greengrass get-group-version \\"
    echo "  --group-id $GROUP_ID \\"
    echo "  --group-version-id $LATEST_GROUP_VERSION"
    echo $GROUP_DEFINITION | jq .
    echo ""
    echo "aws greengrass get-function-definition-versio \\"
    echo "  --function-definition-id $FUNCTION_DEFINITION_ID \\"
    echo "  --function-definition-version-id $FUNCTION_DEFINITION_VERSION_ID"
    echo $LAMBDA_DEFENITION | jq .
    echo ""
    echo "aws greengrass create-function-definition-version \\"
    echo "  --function-definition-id $FUNCTION_DEFINITION_ID \\"
    echo "  --functions $NEW_FUNCTIONS"
    echo $NEW_FUNCTION_DEFINITION | jq .
    echo ""
    echo "aws greengrass create-group-version \\"
    echo "  --group-id $GROUP_ID \\"
    echo "  --core-definition-version-arn $CORE_DEFINITION_ARN \\"
    echo "  --function-definition-version-arn $NEW_FUNCTION_DEFINITION_ARN \\"
    echo "  --device-definition-version-arn $DEVICE_DEFINITION_ARN \\"
    echo "  --logger-definition-version-arn $LOGGER_DEFINITION_ARN \\"
    echo "  --resource-definition-version-arn $RESOURCE_DEFINITION_ARN \\"
    echo "  --subscription-definition-version-arn $SUBSCRIPTION_DEFINITION_ARN"
    echo $NEW_GROUP_VERSION | jq .
    echo ""
    echo "aws greengrass create-deployment \\"
    echo "  --group-id $GROUP_ID \\"
    echo "  --group-version-id $NEW_GROUP_VERSION \\"
    echo "  --deployment-type NewDeployment"
    echo $DEPROIMENT_INFO | jq .
}>>$LOG
```
4.作成したシェルスクリプトに実行権限を与える

```bash
chmod 744 mqtt_setting.sh
```
5.AWS CLIコマンド実行用に環境変数を設定
参考：[AWS CLI を設定する環境変数](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-envvars.html)

```bash
export AWS_ACCESS_KEY_ID=[AWSのアクセスキーID]
export AWS_SECRET_ACCESS_KEY=[AWSのシークレットアクセスキー]
export AWS_DEFAULT_REGION=[今回MQTT設定を変更するグループが所属するリージョン]
```
6.シェルスクリプトを実行する
※実行後 `/home/ec2-user/mqtt_setting.log` が作成される

```bash
./mqtt_setting.sh

MQTT設定情報を入力してください
#設定対象のグループ名を入力
>設定対象のグループ名：
#今回はFileSystemを入力
>キューの場所（FileSystem or Memory ）：FileSystem
#今回は2621440を入力
>ストレージ容量（262144 バイト以上）：2621440
>設定が完了しました！
```
##5-4　動作確認②
「5-2　動作確認①」と同様にして動作確認を行います。

1.IoT Coreのマネジメントコンソールで テスト をクリック
2.トピックをサブスクライブするタブで追加設定をクリックし、以下の設定で「サブスクライブ」をクリック

* トピックのフィルター：test/gg-nwtest ※LambdaがMQTTメッセージをpublishする宛先topic
* 保持するメッセージ数：100 ※切断前後、溜めていた分も確認するため多めに設定する
* その他：※デフォルト設定

3.Greengrass Coreをインストールした仮想デバイス用サーバに踏み台サーバ経由で接続する
4.仮想デバイス用サーバで `tail -F /tmp/gg-nwtest.log` を実行し、ローカルでLambdaが実行されていることを確認する
5.仮想デバイス用のサブネットのルートテーブルをローカル宛のみものに切り替える
※詳しい手順は「3-4　疎通確認（Greengrass Core経由）」を参照
6.以下を確認する

* IoT Coreにメッセージが届かなくなる
* `gg-nwtest.log` に継続して書き込みが行われている（＝Lambdaは継続して動いている）

7.仮想デバイス用サーバでGreengrass Coreを再起動する

```bash
sudo systemctl restart greengrass
```
8.`tail -F /tmp/gg-nwtest.log` でログが再度書き出されることを確認する
※Greengrassが起動し、Lmabdaも実行されたことの確認

```bash
tail -F /tmp/gg-nwtest.log
#(中略)
2021-04-19 10:10:52 success!
2021-04-19 10:10:57 success!
2021-04-19 10:11:02 success!
2021-04-19 10:11:07 success!
2021-04-19 10:11:12 success!
2021-04-19 10:11:17 success!
#接続断から10:11:17までのメッセージも今回は残る想定
#10:11:18〜10:12:08の間はGreengrassは再起動中のためメッセージなし
2021-04-19 10:12:09 success!
2021-04-19 10:12:14 success!
2021-04-19 10:12:19 success!
```
7.仮想デバイス用のサブネットのルートテーブルをIGW宛を含むものに切り替える
8.IoT Coreにネットワーク切断後〜再起動までのメッセージも含めてメッセージがすべて届くことを確認する

以下の通り、接続再開後に切断前に届いた直後（5秒後）のメッセージが届いています。
再起動してもデータが失われていないことが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/c8d2597d-cd91-3ae5-6c5d-39b51da6603a.png)
一方で、ログに吐かれていたうち「10:11:12」と「10:11:12」のメッセージは失われています。
`sudo systemctl restart greengrass` の実行後に書き出されていたので、キャッシュ自体は実行Lambdaより先に止まっていたなど考えられるかもしれません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/258c25ba-4343-11a5-57ae-e44f31a80b6d.png)
#6　おわりに
Greengrass(V1)とIoT Coreの接続が切れた場合の挙動を確認しました。
仮想エッジ環境の構築も含めて、最近触っていたことを総動員したようなかっこうです。

グループ証明書の取得、MQTT設定の変更など、一部検証にあたってPythonファイル、Lambda、シェルスクリプトの作成を行っています。
上記検証の中で動かしてはいますが、実行して何か問題等あればご連絡ください。
※MQTT設定用のシェルスクリプトは2回目以降の設定変更用の処理がないので、いずれ修正したい…
#7　参考文献（文中で登場していないもの）
AWS周りについてではなく、Python、シェルスクリプトを書いた際に参照したものを備忘録として載せておきます。

* Python
 * [Pythonでファイルの読み込み、書き込み（作成・追記）](https://note.nkmk.me/python-file-io-open-with/)
 * [TypeError: list indices must be integers, not strがわからんかったけどどうにか出来た](http://miyakenjpn.hatenablog.com/entry/2016/06/29/231710)
 * [Advanced Usage - Session Objects](https://docs.python-requests.org/en/master/user/advanced/)
 * [PYTHONでコマンドライン引数が受け取れるコマンドを作成してみる](https://blog.denet.co.jp/python-command/)
* シェルスクリプト
 * [シェルスクリプトで任意の文字入力待ち→入力→確認→完了 を作成](https://web-creators-hub.com/linux/shell-s/)
 * [bashで変数から部分文字列を取得する](https://qiita.com/koara-local/items/04d3efd1031ea62d8db5#--%E5%B7%A6%E7%AB%AF%E3%81%8B%E3%82%89%E3%81%AE%E6%9C%80%E7%9F%AD%E3%81%AE%E6%9C%80%E9%95%B7%E3%81%AE%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E4%B8%80%E8%87%B4%E3%81%BE%E3%81%A7%E3%82%92%E9%99%A4%E5%A4%96)
 * [【sed / awk / grep】文字列の置換・抽出・検索と正規表現 | Linux Cheat Sheet](https://qiita.com/shuntaro_tamura/items/e4e942e7186934fae5e7)
 * [コマンドプロンプトでAWS CLIを使うときのJSONの取り扱い](https://qiita.com/danishi/items/ea01b9491088364e0494)