#はじめに
以前、以下の記事でコンテナでGreengrass環境を構築しました。
参考:Greengrass(V1)を実行するDockerコンテナを構築してみる
今回はGreengrass(V1)で推論を実行する環境を作ります。
推論環境の作成は、公式ドキュメントの以下を参照し、コンテナ自体はJetson nano上で動かします。
参考:最適化された機械学習推論を AWS Management Console を使用して設定する方法
なお、上記ドキュメントではpicmaeraを使うパターンが紹介されていますが、Raspberry Piでないとpicameraは使えないようです。
そのため、今回はopencvを使うNVIDIA Jetson TX2用の機械学習サンプルを利用します。
参考:機械学習のサンプル
また、以下のEC2インスタンスでも今回の記事通りに実行して推論ができたことを補足しておきます。
お手軽に試すことができます。
- AMI:Ubuntu Server 18.04 LTS (HVM), SSD Volume Type 64ビット(Arm)
- インスタンスタイプ:t4g.micro
- ストレージ:EBS(汎用SSD) 30GB
- セキュリティグループ
- 22番(SSH)へのインバウンドを自IPのみ許可
- 8883番へのインバウンドをすべて許可
- アウトバウンドはすべて許可(デフォルト)
#1 ホスト側の設定
Dockerコンテナを利用するため、ホスト側の設定・準備を行います。
なお、Dockerはすでにインストール済です。
#OS情報
$ cat /etc/lsb-release |grep DISTRIB_DESCRIPTION
DISTRIB_DESCRIPTION="Ubuntu 18.04.5 LTS"
$ uname -a
Linux xxxx-desktop 4.9.201-tegra #1 SMP PREEMPT Wed May 5 09:31:36 PDT 2021 aarch64 aarch64 aarch64 GNU/Linux
#Dockerバージョン
$ docker --version
Docker version 20.10.2, build 20.10.2-0ubuntu1~18.04.2
##1-1 Dockerホストの設定
IPv4のフォワーディング有効化とシンボリックリンクとハードリンクの保護を有効化します。
参考:Docker コンテナでの AWS IoT Greengrass の実行
1.シンボリックリンクとハードリンクの保護を有効にする
#rootユーザに切り替え(sudoで実行してもPermission Deniedになるため)
$ sudo su -
#/etc/sysctl.confに設定を追加
$ echo '# AWS Greengrass' >> /etc/sysctl.conf
$ echo 'fs.protected_hardlinks = 1' >> /etc/sysctl.conf
$ echo 'fs.protected_symlinks = 1' >> /etc/sysctl.conf
2.IPv4 ネットワーク転送を有効にする
$ echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
3.設定を反映する
$ sysctl -p
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
net.ipv4.ip_forward = 1
##1-2 docker-compose準備
コンテナ起動時に複数のディレクトリのマウントが必要になるんで、docker-composeを利用します。
1.以下のコマンドでdocker-composeをインストールする
※バージョンはリリースノートより最新の1.29.2を選択
#バージョン指定
$ export DOCKER_COMPOSE_VERSION=1.29.2
#インストール
$ sudo pip install docker-compose=="${DOCKER_COMPOSE_VERSION}"
2.インストールできたことを確認する
$ docker-compose -v
docker-compose version 1.29.2, build unknown
3.docker-compose用のディレクトリを作成する
#docker-compose.ymlを格納するディレクトリの作成
$ mkdir ml_greengrass
$ cd ml_greengrass
#Greengrassの証明書やconfigファイルを格納するディレクトリの作成
$ mkdir certs config
$ touch docker-compose.yml
4.docker-compose.yml
を作成する
version: "3"
services:
greengrass-core:
image: ml-greengrass
volumes:
- /home/xxxx/ml_greengrass/config:/greengrass/config
- /home/xxxx/ml_greengrass/certs:/greengrass/certs
- /ml_model:/ml_model
ports:
- 8883:8883
- 8000:8000
restart: always
#2 Greengrassコンテナの作成
推論を実行するGreengrassコンテナを作成します。
##2-1 Greengrassグループの作成
Greengrassグループの証明書や秘密鍵が必要になるので、先にクラウド側でGreengrassグループを作成します。
1.IoT Coreのマネジメントコンソールで Greengrass > クラシック(V1) > 「グループの作成」の順にクリック
2.「デフォルト作成を使用」をクリック
3.任意のグループ名を入力して「次へ」をクリック
4.任意のCoreの名前を入力して「次へ」をクリック
5.「グループとCoreの作成」をクリック
6.「これらのリソースはtar.gzとしてダウンロードしてください」をクリックしてセキュリティリソースを取得したら、「完了」をクリック
※ルートCAの取得はのちの手順で行う
※Greengrass Coreのソフトウェアはコンテナ内で取得するのでここでは不要
##2-2 コンテナイメージの作成
Dockerfileからコンテナイメージを作成します。
1.以下の通りDockerfile
を作成する
FROM arm64v8/ubuntu
ENV DEBIAN_FRONTEND=noninteractive
ARG GREENGRASS_RELEASE_URL=https://d1onfpft10uf5o.cloudfront.net/greengrass-core/downloads/1.11.1/greengrass-linux-aarch64-1.11.1.tar.gz
ARG CORRETTO_KEY_URL=https://apt.corretto.aws/corretto.key
ARG DLR_URL=https://github.com/neo-ai/neo-ai-dlr
RUN apt upgrade -y && \
apt update -y && \
apt-get install wget gnupg software-properties-common git build-essential cmake curl ca-certificates -y && \
#install greengrass core software
wget $GREENGRASS_RELEASE_URL && \
GREENGRASS_RELEASE=$(basename $GREENGRASS_RELEASE_URL) && \
tar zxvf $GREENGRASS_RELEASE -C / && \
#delete tar.gz file
rm $GREENGRASS_RELEASE && \
#install amazon corretto8 and Python library
wget -O- $CORRETTO_KEY_URL | apt-key add - && \
add-apt-repository 'deb https://apt.corretto.aws stable main' && \
add-apt-repository ppa:deadsnakes/ppa && \
apt-get update -y && \
apt-get install -y java-1.8.0-amazon-corretto-jdk python3.7 python3-distutils python3-setuptools python3-pip libgl1-mesa-dev && \
#add system-user and system-group for greengrass
useradd -r ggc_user && \
groupadd -r ggc_group && \
#set timezon
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
#build DLR
curl https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py && \
python3.7 /tmp/get-pip.py && \
rm /tmp/get-pip.py && \
python3.7 -m pip install -U pip setuptools wheel scikit-learn && \
git clone --recursive $DLR_URL -b v1.6.0 && \
cd /neo-ai-dlr && \
mkdir build && \
cd /neo-ai-dlr/build && \
cmake .. && \
make && \
cd /neo-ai-dlr/python && \
python3.7 setup.py install && \
#install openCV
python3.7 -m pip install opencv-python && \
#clean cache for apt
apt clean && \
rm -rf /var/lib/apt/lists/*
COPY "greengrass-entrypoint.sh" /
EXPOSE 8883
CMD /greengrass-entrypoint.sh
2.同じ階層にプロセスとして実行するgreengrass-entrypoint.sh
を作成する
#!/bin/bash
set -e
# Initial check before starting greengrassd
# Check if default IsolationMode is set to GreengrassContainer or any user lambdas are configured in GreengrassContainer mode
if grep -q "GreengrassContainer" /greengrass/ggc/deployment/group/group.json; then
echo "Default IsolationMode as GreengrassContainer or User Lambdas with GreengrassContainer mode aren't supported to run inside the GGC Docker Container. For troubleshooting, start a fresh deployment by following this guide: https://docs.aws.amazon.com/greengrass/latest/developerguide/run-gg-in-docker-container.html#docker-no-container. Finally, restart the GGC docker container after bind-mounting an empty deployment folder."
exit 1;
fi
/greengrass/ggc/core/greengrassd start
daemon_pid=`cat /var/run/greengrassd.pid`
# block docker exit until daemon process dies.
while [ -d /proc/$daemon_pid ]
do
# Sleep for 1s before checking that greengrass daemon is still alive
daemon_cmdline=`cat /proc/$daemon_pid/cmdline`
if [ $daemon_cmdline != ^/greengrass/ggc/packages/1.11.0/bin/daemon.* ]; then
sleep 1;
else
break;
fi;
done
3.コンテナイメージを作成する
$ docker build -t ml-greengrass .
4.作成されたことを確認する
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ml-greengrass latest b212b6b0a29d 17 seconds ago 1.93GB
arm64v8/ubuntu latest 8707eaf21636 11 days ago 65.6MB
###Dockerfileでやっていること
Dockerfile内で公式ドキュメントの以下を実施しています。
- ステップ 1: Raspberry Pi を設定する
- ステップ 2: Amazon SageMaker Neo 深層学習ランタイムをインストールする
公式ドキュメントの手順のみでは不備・不足もあるので、補った点含めて、何をしているか記載しておきます。
※1つずつ手順を実施していき、エラー対応などまとめてからDockerfileにした
ENV DEBIAN_FRONTEND=noninteractive
ビルドの際に入力待ちを発生させないためにいれている
参考:Docker Ubuntu18.04でtzdataをinstallするときにtimezoneの選択をしないでinstallする
**apt-get install -y(中略)libgl1-mesa-dev && \ **
opencvのimport時に以下のエラーが発生したため
ImportError: libGL.so.1: cannot open shared object file: No such file or directory
参考:OpenCVをPythonで動かそうとしてlibGL.soが無いって言われたけど解決した。
**python3.7 -m pip install -U pip setuptools wheel scikit-learn && \ **
Lambdaのランタイムでpython3.7を利用するため、すべてpython3.7でインストールした
また、scikit-leaernは推論環境構築後のチェックで以下のエラーが発生したためインストールしている
参考:Installing DLR - Validation After Build (Linux Only)
ModuleNotFoundError: No module named 'sklearn'
**git clone --recursive $DLR_URL -b v1.6.0 && \ **
DLRのインストールに際しては、公式ドキュメントの「DLRのインストール」で開くページ内の「Building on Linux」の手順を用いています。
ただし、git clone
ではv1.6.0を指定しています。
以下の公式フォーラムの通り、DLR v1.7.0以降ではlibdlr.so
というファイルが付随しないため、Validationの手順の時点でエラーが発生します。
参考:[AWS official edge ML tutorial of Greengrass] Getting errors on DLR
**make && \ **
Installing DLRではmake -j4
としていますが、オプションを外しています。
当初、EC2インスタンス(t4g.micro)で検証していた際にオプション付きで実行すると落ちたので(スペック不足?)オプションを外しました。
##2-3 コンテナの起動
docker-compose実行用のディレクトリに取得した証明書を格納して、コンテナを起動します。
1.Greengrassグループ作成時に取得したtar.gzファイルをdocker-compose用のディレクトリにアップロードする
※私はFileZillaを使った
2.tar.gzファイルを解凍する
#アップロード先のディレクトリに移動
$ cd /home/xxxx/ml_greengrass
$ ls
certs config docker-compose.yml xxxxxxxxxx-setup.tar.gz
#解凍する
$ tar zxvf xxxxxxxxxx-setup.tar.gz
certs/xxxxxxxxxx.cert.pem
certs/xxxxxxxxxx.private.key
certs/xxxxxxxxxx.public.key
config/config.json
$ ls certs
xxxxxxxxxx.cert.pem xxxxxxxxxx.private.key xxxxxxxxxx.public.key
$ ls config
config.json
#tar.gzファイルは不要なので削除
$ rm xxxxxxxxxx-setup.tar.gz
3.AWSのCA証明書をcertsディレクトリに取得する
$ cd certs
#config.jsonがデフォルトでroot.ca.pemと記載しているため、その通りの名前で取得する
$ wget "https://www.amazontrust.com/repository/AmazonRootCA1.pem" -O root.ca.pem
4.コンテナを起動する
#docker-compose.ymlファイルと同じ階層に移動
$ cd ../
$ docker-compose up -d
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
271082d63b91 ml-greengrass "/bin/sh -c /greengr…" 5 seconds ago Up 4 seconds 0.0.0.0:8883->8883/tcp greengrass_greengrass-core_1
#3 クラウド側の設定
Greengrassグループで推論をするため、推論用の関数作成とグループの設定をクラウド側で行います。
##3-1 Lambdaの作成
「はじめに」で記載したとおり、今回は「NVIDIA Jetson TX2」用のサンプルを利用します。
1.以下のページから「NVIDIA Jetson TX2 用の DLR サンプル」をダウンロードする
参考:機械学習のサンプル
2.ダウンロードしたtar.gzファイルを解凍する
> tar zxvf dlr-py3-aarch64.tar.gz
3.解答してできた「dlr-py3-aarch64」フォルダのexample配下に任意の画像を格納する
※今回はMQTTを受けて、格納した画像の物体検知を行う
4.exampleフォルダ内のinterface.py
を以下の通り修正する
参考:Lambda 関数コードから機械学習リソースにアクセスする
#29-36行目を以下に置換
# Read synset file
synset_path = model_resource_path + '/synset.txt'
with open(synset_path, 'r') as f:
synset = [l.rstrip() for l in f]
5.exampleファイル配下を全選択して、zipファイルに圧縮する
6.Lambdaのマネジメントコンソールから、以下の通り関数を作成する
- 作成方法:一から作成
- 関数名:optimizedImageClassification
- ランタイム:Python3.7
- ハンドラ:inference.handler
7.手順5で作成したzipファイルをアップロードする
8.バージョンを発行し、エイリアスを紐付ける
※ドキュメント通り、エイリアス名は「mlTestOpt」とした
##3-2 Greengrassグループの設定
Greengrassグループの各種設定を行います。
1.対象のグループ > 設定 で以下の通りグループの設定を行う
- グループにロールをアタッチする
- 今回は検証のためロールのポリシーは「AdministratorAccess」としている
- デフォルトの Lambda 関数ユーザー ID/グループ ID:ggc_user/ggc_group
- デフォルトの Lambda 関数コンテナ化:コンテナなし
- CloudWatchログ設定:いずれも「情報ログ(推奨)」を有効化
2.対象のグループ > Lambda でGreengrassグループに作成した関数optimizedImageClassificationを追加する
3.optimizedImageClassificationの設定の編集で以下の通りLambdaの設定を行う
- として実行:グループのデフォルトを使用 (現在: ggc_user/ggc_group)
- コンテナ化:グループのデフォルトを使用 (現在: コンテナなし)
- タイムアウト:5分
- Lambdaのライフサイクル:存続期間が長く無制限に稼働する関数にする
- その他:デフォルトのまま
4.対象のグループ > サブスクリプション で以下のサブスクリプションを追加する
ソース | ターゲット | トピック |
---|---|---|
IoT Cloud | optimizedImageClassification:mlTestOpt | /resnet-18/test |
optimizedImageClassification:mlTestOpt | IoT Cloud | /resnet-18/predictions |
##3-3 推論モデルをグループに登録
エッジで推論を実行するための推論モデルをグループに紐付けます。
1.「機械学習のサンプル」として取得した「dlr-py3-aarch64」配下のmodels/restnet18内のファイルをすべてzipファイルとして圧縮する
※ドキュメントに寄せて、resnet18.zipとする
2.S3にバケットを作成する
※この際、バケット名に必ずgreengrassの文言を含める
バケット名には文字列 greengrass が含まれている必要があります。一意の名前 (greengrass-dlr-bucket-user-id-epoch-time など) を選択します。バケット名にピリオド (.) を使用しないでください。
参考:ステップ 5: SageMaker Neo 最適化モデルリソースを Greengrass グループに追加する
3.作成したバケットに「resnet.18」をアップロードする
4.対象のGreengrassグループ > リソース > Machine Learning の順に遷移し、「機械学習リソースの追加」をクリック
5.コンテナ環境に入り、ggc_userのグループIDを控える
#docker-compose.ymlファイルが格納されているディレクトリで作業する
$ docker-compose exec greengrass-core /bin/bash
#コンテナ内でggc_userのグループIDを確認
$ cat /etc/group | grep ggc_user
ggc_user:x:999:
#コンテナを抜ける
$ exit
6.以下の通りリソースを追加する
- リソース名:resnet18_model
- モデルソース:S3 のモデルをアップロード
- S3のモデル:resnet18.zip
- ローカルパス:/ml_model
- OSグループとアクセス許可を指定
- OSグループID:999 ※手順5で控えたggc_userのグループID
- OSグループのアクセス許可:読み取りと書き込みアクセス
- Lambda関数の関連:optimizedImageClassification
- リソース所有者のアクセス許可を継承する
#4 推論の実行
グループの設定まで完了したので、デプロイした上で推論を実行します。
##4-1 グループのデプロイ
1.コンテナのホスト環境で機械学習リソースのローカルパスに指定したディレクトリを作成する
※指定したローカルパスが存在しないとデプロイエラーになる
$ sudo mkdir /ml_model
2.マネジメントコンソールで対象のGreengrassグループを表示し、アクション > デプロイ の順にクリック
##4-2 推論の実行
1.IoT Coreのマネジメントコンソール > テスト > MQTTテストクライアント の順にクリック
2.トピックのフィルターを「/resnet-18/predictions」として「サブスクライブ」をクリック
3.トピックに公開するタブに移動し、トピックのフィルターを「/resnet-18/test」として「発行」をクリック
成功すれば、Lambdaの実行ファイルと同階層に格納した写真の物体検知結果がMQTTで通知される
例えば以下の画像で実行した場合…
次の通り結果が返ってきた!
June 29, 2021, 21:07:45 (UTC+0900)
Prediction Result: Inference Result: "n01667114 mud turtle" with probability 0.41088324785232544.
June 29, 2021, 21:07:45 (UTC+0900)
Perform prediction on ./IMG_4082.jpg...
#5 備忘録・エラー集
試行錯誤すること多数だったので、遭遇したエラーと対応を備忘録として記録しておきます。
ルートCAが存在するのに no certificates in /greengrass/cert/root.ca.pem とエラーが出る
docker-composeでコンテナを起動する際に、エラーが出てコンテナの起動に失敗しました。
確認すると、以下の通りエラーが出ていました。
$ docker logs ml_greengrass
grep: /greengrass/ggc/deployment/group/group.json: No such file or directory
Setting up greengrass daemon
Validating hardlink/softlink protection
Waiting for up to 1m10s for Daemon to start
Error occured while generating TLS config: ErrUnsupportedObjectType: no certificates in /greengrass/certs/root.ca.pem
The Greengrass daemon process with [pid = 14] died
原因は、以下の通り取得したroot.ca.pemがテキストファイルになっていたからです。
$ file root.ca.pem
root.ca.pem: UTF-8 Unicode text
そして、大元の原因はwgetコマンドのオプションを"-O"ではなく小文字の"-o"にしていたことでした…。
デプロイエラー
Greengrassグループのデフォルトコンテナ設定を「コンテナなし」にしている場合は、リソース追加に制限がかかるようです。
機械学習リソースの追加で、「OSグループなし」を選んでLambdaに読み書き権限を直接設定してデプロイすると以下のエラーが出ます。
We cannot deploy because the group definition is invalid or corrupted for the following reasons: {ErrorSet(errorSet=[ErrorModel(errorCode=RESOURCE_ACCESS_INVALID_ERROR, errorMsg=NoContainer function cannot configure permission when attaching Machine Learning resources. arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:optimizedImageClassification:mlTestOpt refers to Machine Learning resource xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx with permission rw in resource access policy)])}
"NoContainer function cannot configure permission when attaching Machine Learning resources. arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:optimizedImageClassification:mlTestOpt refers to Machine Learning resource xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx with permission rw in resource access policy"
なお、「OSグループなし」のままLambdaを「リソース所有者のアクセス許可を継承する」にすると、リソースの追加画面の「更新」ボタンクリック時にエラーが出ます。
次の Lambda 関数は、リソース所有者のアクセス許可を継承するように設定されています。この設定では、OS グループとリソース所有者「optimizedImageClassification」のアクセス許可を指定する必要があります。
機械学習用のサンプルの選び方
「はじめに」で機械学習用のサンプルとしてJetson TX2用を選択したことを記載しました。
サンプルをダウンロード、解凍すると、これらがアーキテクチャ毎のサンプルとして用意されているだろうことが推測できます。
機械学習用のサンプル | 解凍後フォルダ名 | 対象とするアーキテクチャ? |
---|---|---|
Raspberry Pi 用 | dlr-py3-armv7l | armv7l |
NVIDIA Jetson 用 TX2 | dlr-py3-aarch64 | aarch64 |
Intel Atom 用 | dlr-py3-x86_64 | x86_64 |
当初、アーキテクチャをあまり意識せずサンプルを利用していましたが、(当然の帰結ですが…)この間推論に際してエラーが出続けていました。
原因もわからず苦戦していた以下のエラーは、おそらくこれに起因するものだと推測しています。
[2021-05-31T10:55:16.573+09:00][FATAL]-lambda_runtime.py:142,Failed to import handler function "inference.handler" due to exception:
#おわりに
もとよりドキュメント通りの環境で進めていなかったこともありますが、いろいろなところで詰まりました。
特に、序盤はアーキテクチャをちゃんと理解していなかったので、機械学習のサンプルがホストのアーキテクチャと合致せずどつぼにはまってしまいました…。
動かした結果うまくいった、という箇所もありますが、コンテナでも環境を用意することができました。
#参考文献(文中には登場していないもの)
- python初心者がimportエラー(ModuleNotFoundError)で詰みかけた話
- 環境構築中にimportエラーに直面することが多く参考にさせていただいた