Greengrass(V1)の自動起動設定&コアデバイスの接続情報をLambdaで取得

#はじめに
Jetson nanoにGreengrassをインストールしていますが、毎回電源を入れる度にGreengrassを起動するのが手間でした。
そこで、公式ドキュメントに自動起動の設定方法が記載されている通りに設定をしました。

また、今回はあわせてJetson nanoにssh接続するためのIPアドレスを返すLambdaをJetson nanoにデプロイしました。
結論から言うと、こちらは結果的に不要な作業だったのですが、なぜ不要だったか含めて書き残しておきます。
#1　Greengrassの自動起動設定
公式ドキュメントにある通り、systemdを利用したGreengrasの自動起動設定を行います。
参考：[Init システムを設定して Greengrass デーモンを開始する](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/gg-core.html#start-on-boot)
##1-1　ユニットの設定ファイル作成
最初にsystemdでGreengrassを扱えるようにユニットファイルを作成します。

1.Jetson nanoに接続する
2.以下の通りユニットの設定ファイルを作成する

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

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

```
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
```
##1-2　サービスの有効化
サービスを有効化して、Jetson nano起動時にGreengrassも起動するようにします。

1.サービスを有効化する

```
sudo systemctl enable greengrass.service
```
2.起動と停止を試す

```
#Greengrassの起動
sudo systemctl start greengrass.service

#起動の確認（表示される）
ps aux | grep -E "greengrass.*daemon"

#Greengrassの終了
sudo systemctl stop greengrass.service

#終了の確認（表示されない）
ps aux | grep -E "greengrass.*daemon"
```
3.Jetson nanoを終了→起動して、Greengrassが自動起動していることを確認する

```
#起動後、表示されることを確認
ps aux | grep -E "greengrass.*daemon"
```
##1-3　備考：ユニットの設定ファイルの内容
下記の整理にあたっては以下のサイトを参考にさせていただいています。
参考：[Systemd入門(4) - serviceタイプUnitの設定ファイル](https://enakai00.hatenablog.com/entry/20130917/1379374797)
###[Unit]セクション
* Description：ユニットについての説明文

###[Service]セクション
* Type：サービスプロセスの起動完了の判定方法を指定する。forkingは、子プロセスをバックグラウンドに回して最初のコマンド自体は終了する場合に指定する
* PIDFile：サービスのメインプロセスのPIDファイルを指定する。systemdは実行コマンドのPIDを見るため、forkする場合はメインプロセスのPIDファイルを指定する。Greengrassは/var/run/greengrassd.pidとしてPIDファイルを生成する
* Restart：サービスのメインプロセスが終了した際の動作を指定する。on-failureは終了コード0以外で停止した際に再起動する
* Exec***：ExecStart、ExceReload、ExecStopはそれぞれ起動/再起動/停止時に実行するコマンドを指定する

###[Install]セクション
* WantedBy：systemctl enableコマンド実行時にここで指定した.wantsディレクトリ配下にリンクを作成する。今回であれば、multi-user.target.wantsディレクトリにgreengrass.serviceのリンクが作成される

```
cd /etc/systemd/system/multi-user.target.wants

ls -la greengrass.service
lrwxrwxrwx 1 root root 38  3月 16 16:23 greengrass.service -> /etc/systemd/system/greengrass.service
```
#2　Greengrass Coreデバイスの接続情報取得
Greengrassの自動起動設定ができたので、Jetson nanoの電源をいれた時点からローカルのLambdaが受け付けてくれるようになりました。
そこで、MQTTメッセージを受け取ったらhostaname、IPアドレスを返すLambdaをデプロイし、接続情報を取得できるようにします。

なお、今回はGreengrassコアデバイスへのLambdaデプロイの詳細な方法は割愛します。
詳細は以下の記事の手順をご確認ください。
参考：[Greengrass(V1)のコアデバイスにLambdaをデプロイ・実行してみた](https://qiita.com/kt-yt/items/d8d44c7649699303a727)
##2-1　Lambda関数の作成（ローカル）
今回は以下の通りLambda関数を作成しました。
デバイス名、hostname、IPアドレス、タイムスタンプをトピック「get/connection」にMQTTメッセージとしてJSON形式でパブリッシュします。

```python:get_connection_info.py
import greengrasssdk
import logging
import socket
import sys
import datetime
import json

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

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

hostname = socket.getfqdn()
ip = socket.gethostbyname(hostname)
date = datetime.datetime.now()
timestamp = date.strftime("%Y-%m-%d %H:%M:%S")

def get_connection_info(event, context):
    client.publish(
        topic="get/connection",
        payload=json.dumps(
            {
                "device": "jetson nano", 
                "hostname": hostname,
                "ip":  ip,
                "timestamp": timestamp
            }
        )
    )
```
上記の「get_connection_info.py」と「greengrasssdk」を圧縮してzipファイルにします。
※greengrasssdkは以下のリンクからダウンロード
　GitHub：[AWS IoT Greengrass Core SDK for Python](https://github.com/aws/aws-greengrass-core-sdk-python)
##2-2　Lambda関数の作成（クラウド）
Lambdaのマネジメントコンソールに作成したLambda関数を登録します。

1.Lambdaで新規に関数を作成する
※ランタイムは「Python3.7」とした
2.zipファイルをアップロードする
3.ハンドラを「get_connection_info.get_connection_info」とする
※「[ファイル名].[関数名]」の形式
4.バージョンを発行し、エイリアスに紐づける
##2-3　Greengrassグループへの紐づけとデプロイ
IoT CoreマネジメントコンソールのGreengrass > クラシック(V1) > グループ > 対象グループに移動し、デプロイに向けた設定を行います。

1.対象グループのLambdaに作成したLambda関数を登録する
2.Lambdaの設定を「オンデマンド関数」にする
3.対象グループに以下のサブスクリプションを追加する

* IoT Cloud -> 作成したLambda関数　※topicは任意の階層
* 作成したLambda関数 -> IoT Cloud　topicはget/connection　※関数内で指定した送信先トピック

3.対象グループをデプロイする
##2-4　動作検証
1.IoT Coreマネジメントコンソールのテストに移動する
2.「get/connection」（＝Lambda関数のメッセージ送信先）のサブスクリプションを開始する
3.サブスクリプションで設定したIoT Cloud -> Lambda関数のトピックにメッセージを発行する
4.成功すれば以下の通りメッセージが返ってくる

```json
{
  "device": "jetson nano",
  "hostname": "xxxx-desktop",
  "ip": "172.16.xxx.xxx",
  "timestamp": "2021-03-17 16:24:36"
}
```
##2-5　今回引っかかったところ
今回はLambda関数の中身でいくつか引っかかったので記録を残しておきます。
###hostnameが「sandbox」となってしまう
今回は以下の記事を参考にhostnameを取得するコードを書いた
参考：[pythonでホスト名を取得する。](https://blog.suganoo.net/entry/2017/10/26/184612)

上記で紹介されている方法をすべて試しましたが、hostnameは以下の通りとなりました

|コード|取得されたhostname|
|:-|:-|
| socket.getfqdn() | ※Jetson nanoに設定しているhostnameが取得された |
| socket.gethostname() | sandbox |
| os.uname()[1] | sandbox  |
| platform.uname()[1] | sandbox  |

「sandbox」と取得されたものは、Lambdaを実行しているコンテナから情報を取得した…ということでしょうか？
原因は分からなかったのですが、socket.getfqdn() で欲しい情報が取得できたためこの通り実装しています。

###IPアドレスが「127.0.1.1」となってしまう
socket.getfqdn() で取得したhostnameを引数にしてsocket.gethostbyname() でIPアドレスを取得すると「127.0.1.1」と返ってきました。
原因はJetson nanoで以下の設定になっていたためです。

* /etc/hosts に [Jetson nanoに設定しているhostname] 127.0.1.1 が記載されていた
* /etc/nsswitch.conf で名前解決の優先順位でfiles（=/etc/hosts）が最優先になっていた

hostsファイルの設定行をコメントアウトしたところ、LAN内でのIPアドレスを取得できました。
参考：[Linuxマシンの名前解決の仕組み](https://qiita.com/kk31108424/items/33660a96c7f2c77cb49a)

なお、hostnameを「sandbox」と直接指定しても、IPアドレスは返ってきました。

##2-6　そもそもこんなLambda関数を用意しなくても・・・
Greengrassグループのデプロイ時に「自動検出」を選択していれば、今回のようなLambdaを用意しなくてもIPアドレスを確認できます。

1.IoT Coreのマネジメントコンソールで左のメニューから Greengrass > クラシック(V1) > グループ > 対象グループ の順にクリック
2.コア > 対象のコア をクリックする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/ef5868ab-0955-f53a-0724-dcfa228b59d3.png)
3.接続 をクリックするとIPアドレスが確認できる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/48ce0ac6-1578-d49a-01ed-a6afcbad8975.png)

「自動検出」設定にしている場合、グループへのデプロイ時に「IPDetector」というLambda関数があわせてデプロイされ、IPアドレスの変更を監視し、情報を取得しているようです。
ただし、このLambda関数はマネジメントコンソール上には表示されません。
> このシステム Lambda 関数は、Lambda コンソールでは表示されません。この関数が最新のグループバージョンに追加された後は、コンソールから行うデプロイに含まれます (API を使用して、その置き換えや削除を行う場合を除く)。
出典：[自動 IP 検出をアクティブ化する](https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/gg-core.html#ip-auto-detect)

#3　おわりに
Jetson nanoにインストールしたGreengrassを触りやすくしたい、という出発点でしたが、Lambda関数はやや無駄骨でした。
最終的にはLinux周辺の勉強になったような…。
#4　参考文献（本文中に登場しなかったもの）

* [【小ネタ】[AWS IoT Greengrass] クイックスタートを使用して、コマンド３行でセットアップする（RaspberryPi） - 5 自動起動](https://dev.classmethod.jp/articles/amazon-iot-greengrass-setup/#toc-9)
* [第598回　systemdユニットの設定を変える](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0598)
 * 独自に作成するユニットファイルの配置場所を /etc/systemd/system とする理由について
* [Linux豆知識193　「/var/run」ディレクトリ](https://linuc.org/study/knowledge/523/)
 * PIDファイルが作成される場所について＠
* [OS起動時にsystemdで行われていること - multi-user.target は何をするのか？](https://qiita.com/ledmonster/items/5f2e1633d4124cb978fe#multi-usertarget-%E3%81%AF%E4%BD%95%E3%82%92%E3%81%99%E3%82%8B%E3%81%AE%E3%81%8B)
 * systemctl enable *** したあとにmulti-user.targetにシンボリックリンクが作成されるとどうなるか
* [Python で自身の IP アドレスを取得したい - Windows 編](https://edosha.hatenablog.jp/entry/2017/08/09/150636)