Greengrass V2 を Raspberry Pi の Docker コンテナ上で 動かしてみた

arm アーキテクチャ用に Greengrass V2 の Docker イメージをビルドして、Raspberry Pi のコンテナ上で Greengrass V2 を動かす手順を紹介します。
2022.12.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Greengrass V2 は、Docker を使わなくても arm デバイスでも動かすことができますが、Docker コンテナ上で動かす場合、パブリックに公開されているコンテナイメージは実は amd64 向けのものしか提供されていません。
(以前の Greengrass であれば、arm, amd 両方のイメージが提供されていました。)

そこで、今回は arm64 向けにイメージをビルドして、Raspberry Pi の Docker コンテナ上で Greengrass V2 を動かしてみました。

検証環境

今回利用したデバイス及び環境は以下になります。

  • Raspberry Pi 4(4GB)
  • OS 環境は以下
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 11 (bullseye)
Release:	11
Codename:	bullseye
$ uname -m
aarch64

それでは、これより構築作業を紹介していきます。

Docker のインストール

まず最初に、下記のコマンドを実行して Raspberry Pi に Docker をインストールします。

$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh

下記バージョンのDocker がインストールできました。

$ docker -v
    Docker version 20.10.21, build baeda1f

ついでに、作業に使っているユーザーでも docker コマンドが利用できるようにしておきます。

$ sudo usermod -aG docker pi

このまま作業すると docker コマンドが使えないことがあるので、Raspberry Pi にログオンし直しておきます。

AWS 認証情報の設定

Greengrass Core をインストールするにあたり、AWS の認証情報が必要となるので、そのための IAM を設定します。認証情報の準備としては、以下の2通りがありますが、インストール時のみに必要な情報であり、一時的な認証情報の方がセキュアなので後者を使うことにします。

  • IAM User による永続的(長期的)な認証情報。
  • IAM Role による一時的な認証情報

まず最初に IAM Policy を下記の内容で作成します。ポリシー名は greengrass-v2-provision-minimal-iam-policy としました。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:AddThingToThingGroup",
        "iot:AttachPolicy",
        "iot:AttachThingPrincipal",
        "iot:CreateKeysAndCertificate",
        "iot:CreatePolicy",
        "iot:CreateRoleAlias",
        "iot:CreateThing",
        "iot:CreateThingGroup",
        "iot:DescribeEndpoint",
        "iot:DescribeRoleAlias",
        "iot:DescribeThingGroup",
        "iot:GetPolicy",
        "iam:GetRole",
        "iam:CreateRole",
        "iam:PassRole",
        "iam:CreatePolicy",
        "iam:AttachRolePolicy",
        "iam:GetPolicy",
        "sts:GetCallerIdentity"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DeployDevTools",
      "Effect": "Allow",
      "Action": [
        "greengrass:CreateDeployment",
        "iot:CancelJob",
        "iot:CreateJob",
        "iot:DeleteThingShadow",
        "iot:DescribeJob",
        "iot:DescribeThing",
        "iot:DescribeThingGroup",
        "iot:GetThingShadow",
        "iot:UpdateJob",
        "iot:UpdateThingShadow"
      ],
      "Resource": "*"
    }
  ]
}

次に IAM Role を作成します。
適当な名前で IAM Role を作成し、上記ポリシーをアタッチします。今回は下記の通りとしました。

  • 作成した IAM Role:greengrass-v2-provision-role
  • アタッチするIAM Policy:greengrass-v2-provision-minimal-iam-policy
    • 先程作成したポリシーです。

信頼関係は以下のとおりです。Principal は利用環境に応じて変更して下さい。下記はスイッチロールで該当の AWS 環境を使っている想定になります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[AWS ACCOUNT ID]:role/[Switched IAM Role Name]"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

これで一時的な認証情報を取得する準備が整いました。一時的な認証情報は後で作成する credentials ファイルに記載してコンテナ起動時に渡すので、このファイルを先に作成しておきます。

作業ディレクトリに greengrass-v2-credentials というディレクトリを作成します。このディレクトリ以下に credentials ファイルを作成します。

$ mkdir ./greengrass-v2-credentials
$ touch ./greengrass-v2-credentials/credentials

次に一時的な認証情報を作成します。作成するための環境に特に指定はありませんが、 AWS CloudShell を使うのが便利です。

CloudShell を利用する際は、先程作成した IAM Role の信頼関係に記載されている Principal の資格で作業することに注意して下さい。

実際に実行するコマンドは下記になります。本記事の通りに AWS リソースを作成している場合は AWS ACCOUNT ID の箇所だけ修正して、そのまま貼り付けて実行します。

OUTPUT=`aws sts assume-role \
--role-arn arn:aws:iam::[AWS ACCOUNT ID]:role/greengrass-v2-provision-role \
--role-session-name "RoleSession01"
`

echo "############ Copy and Paste the following credential to Greengrass Core Divice! ############" ; \
echo "[default]" ; \
echo "aws_access_key_id =`echo $OUTPUT | jq -r .Credentials.AccessKeyId`" ; \
echo "aws_secret_access_key = `echo $OUTPUT | jq -r .Credentials.SecretAccessKey`" ; \
echo "aws_session_token =`echo $OUTPUT | jq -r .Credentials.SessionToken`" ; \
echo ""

以下は、CloudShell を利用している様子です。

12-cloudshell

実行すると下記のような結果が表示されるので、[default] 以下の 4 行をコピーして、デバイス上の credentials ファイルに貼り付けます。これで credentials ファイルが完成しました。

なお、有効期限は作成から 1時間なので、1時間以内に Greengrass Core のインストールを実施して下さい。1時間経過したら再度同じコマンドを実行して認証情報を作り直します。
(有効期限を伸ばすこともできますが、むやみに伸ばすのはリスクになるので、とりあえずデフォルトで良いかと思います。)

############ Copy and Paste the following credential to Greengrass Core Divice! ############
[default]
aws_access_key_id =ASXXXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXX
aws_session_token = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

環境変数ファイルの作成

次に、コンテナ内の Greengrass Core インストーラに渡す情報を記載した「環境ファイル」を作成します。.env というファイル名で作業ディレクトリ上に作成します。
ファイルの内容は次のとおりです。

GGC_ROOT_PATH=/greengrass/v2
AWS_REGION=ap-northeast-1
PROVISION=true
THING_NAME=OnDockerTestRasPi01
THING_GROUP_NAME=On-Docker-RaspiOS-Group
TES_ROLE_NAME=OnDockerTestGreengrassV2TokenExchangeRole
TES_ROLE_ALIAS_NAME=OnDockerTestGreengrassCoreTokenExchangeRoleAlias
COMPONENT_DEFAULT_USER=ggc_user:ggc_group
DEPLOY_DEV_TOOLS=true

必要に応じて以下の項目を修正して下さい。

設定項目 設定内容
GGC_ROOT_PATH Greengrass Core をインストールするディレクトリ。
AWS_REGION Greengrass を利用する AWS リージョン
THING_NAME Greengrass Core が稼働する IoT thing の名前
THING_GROUP_NAME Greengrass Core が稼働する IoT thing の thing グループ
TES_ROLE_NAME Greengrass デバイスが AWS リソースを利用する際に利用する一時的な認証情報のための IAM Role。
存在しなければ指定した名前で作成される
TES_ROLE_ALIAS_NAME 上記トークンの交換ロールエイリアス。
存在しなければ指定した名前で作成される
DEPLOY_DEV_TOOLS Greengrass CLI コンポーネントのデプロイ有無

arm 対応の Greengrass コンテナイメージのビルド

次に、arm アーキテクチャ向けのイメージをビルドします。
以下の GitHub リリースページより Greengrass コンテナのパッケージファイルを Raspberry Pi 上にダウンロードして解凍します。

$ wget https://github.com/aws-greengrass/aws-greengrass-docker/archive/refs/tags/v2.5.3.tar.gz -O aws-greengrass-docker-2.5.3.tar.gz
$ tar xzf aws-greengrass-docker-2.5.3.tar.gz

解凍すると次のようなファイル群が展開されます。

$ tree
.
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── docker-compose.yml
├── Dockerfile
├── greengrass-entrypoint.sh
├── greengrass.zip.sha256
├── LICENSE
├── modify-sudoers.sh
└── README.md

Dockerfile の中身は次のようになっています。利用する Greengrass Core のバージョンを指定したい場合は、7行目を編集して下さい。
今回は、本記事の執筆時点で最新の 2.5.3 を指定しています。

ちなみに、Greengrass V2 の イメージは arm に対応していませんが、AmazonLinux 2 のコンテナイメージは、arm64 に対応しています。

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

FROM amazonlinux:2

# Replace the args to lock to a specific version
ARG GREENGRASS_RELEASE_VERSION=2.5.3
ARG GREENGRASS_ZIP_FILE=greengrass-${GREENGRASS_RELEASE_VERSION}.zip
ARG GREENGRASS_RELEASE_URI=https://d2s8p88vqu9w66.cloudfront.net/releases/${GREENGRASS_ZIP_FILE}
ARG GREENGRASS_ZIP_SHA256=greengrass.zip.sha256

# Author
LABEL maintainer="AWS IoT Greengrass"
# Greengrass Version
LABEL greengrass-version=${GREENGRASS_RELEASE_VERSION}

# Set up Greengrass v2 execution parameters
# TINI_KILL_PROCESS_GROUP allows forwarding SIGTERM to all PIDs in the PID group so Greengrass can exit gracefully
ENV TINI_KILL_PROCESS_GROUP=1 \
    GGC_ROOT_PATH=/greengrass/v2 \
    PROVISION=false \
    AWS_REGION=us-east-1 \
    THING_NAME=default_thing_name \
    THING_GROUP_NAME=default_thing_group_name \
    TES_ROLE_NAME=default_tes_role_name \
    TES_ROLE_ALIAS_NAME=default_tes_role_alias_name \
    COMPONENT_DEFAULT_USER=default_component_user \
    DEPLOY_DEV_TOOLS=false \
    INIT_CONFIG=default_init_config \
    TRUSTED_PLUGIN=default_trusted_plugin_path \
    THING_POLICY_NAME=default_thing_policy_name
RUN env

# Entrypoint script to install and run Greengrass
COPY "greengrass-entrypoint.sh" /
COPY "${GREENGRASS_ZIP_SHA256}" /

# Install Greengrass v2 dependencies
RUN yum update -y && yum install -y python37 tar unzip wget sudo procps which && \
    amazon-linux-extras enable python3.8 && yum install -y python3.8 java-11-amazon-corretto-headless && \
    wget $GREENGRASS_RELEASE_URI && sha256sum -c ${GREENGRASS_ZIP_SHA256} && \
    rm -rf /var/cache/yum && \
    chmod +x /greengrass-entrypoint.sh && \
    mkdir -p /opt/greengrassv2 $GGC_ROOT_PATH && unzip $GREENGRASS_ZIP_FILE -d /opt/greengrassv2 && rm $GREENGRASS_ZIP_FILE && rm $GREENGRASS_ZIP_SHA256

# modify /etc/sudoers
COPY "modify-sudoers.sh" /
RUN chmod +x /modify-sudoers.sh
RUN ./modify-sudoers.sh

ENTRYPOINT ["/greengrass-entrypoint.sh"]

Dockerfile の内容を確認できたらイメージをビルドします。名前は適当なものでいいかと思います。今回は arm64/aws-iot-greengrass としています。
タグは利用環境に応じて検討の余地があると思いますが、ひとまず Greengrass Core のバージョンを付けています。

docker build -t "arm64/aws-iot-greengrass:2.5.3" ./

ビルドには少し時間がかかります。正常にビルドできたら確認してみましょう。

$ docker images

REPOSITORY                  TAG          IMAGE ID       CREATED              SIZE
arm64/aws-iot-greengrass    2.5.3        cba833b00343   About a minute ago   784MB

ついでにアーキテクチャも確認してみます。arm64 としてビルドされていますね。(当然といえば当然ですが...)

$ docker inspect arm64/aws-iot-greengrass:2.5.3  |jq '{ Architecture: .[].Architecture }'
{
  "Architecture": "arm64"
}

コンテナの起動

ここまでの作業で全ての準備ができたので、コンテナを起動したいと思います。
credentials ファイルと .env ファイルのパスは作業環境に応じたパスを指定して下さい。

$ docker run --rm --init -it --name aws-iot-greengrass \
 -v ~/greengrass-v2-credentials:/root/.aws/:ro \
 --env-file .env \
 -p 8883 \
 arm64/aws-iot-greengrass:2.5.3

次のようなログが最後に出れば起動成功です。

Installing Greengrass for the first time...

(中略)

+ java -Dlog.store=FILE -Dlog.level= -Dlog.store=FILE -Droot=/greengrass/v2 -jar /greengrass/v2/alts/current/distro/lib/Greengrass.jar --setup-system-service false
Launching Nucleus...
Launched Nucleus successfully.

起動に成功していれば、コンソール上でもコアデバイスとして登録されていることが確認できます。

01-registried-core-device-on-docker

今回は同時に Greengrass CLI もデプロイしているので、そのデプロイメントもコンソールで確認できます。

02-deployment-ggc-cli

カスタムコンポーネントをデプロイしてみる

Greengrass Core が正常に起動していれば、コンポーネントのデプロイ方法は特に変わる点はありません。
試しに下記で紹介したサンプルのコンポーネントをデプロイしてみます。

このコンポーネントは既に Greengrass に登録済みのもので、S3 バケットにアーティファクトが格納されています。これらを S3 からダウンロードしてデプロイするので、Greengrass Core デバイス(Raspberry Pi)に対して、S3 バケットへのアクセス権限を追加しておきます。

今回は簡単に確認するため、権限のゆるい AmazonS3ReadOnlyAccess をアタッチしています。

11-add-s3-permission

それではコンソールからコンポーネントをデプロイしてみましょう。
メニューから「コンポーネント」画面を開いて、デプロイするコンポーネントをクリックします。

03-select-component

該当のコンポーネントのページで「Deploy」をクリックします。

04-deploy-component-mytest

デプロイ対象を選択します。既に Greengrass CLI をデプロイしたものがあるので、今回は同じものを選択します。

05-select-deployment

ターゲットの設定は「Next」をクリックして次に進みます。

06-specify-target

コンポーネントの選択画面でも、今回は変更する必要がないのでそのまま「Next」をクリックします。

07-select-component

次の画面もそのまま「Next」で先に進みます。

08-configure-component

次の画面は、デプロイ時のキャンセル設定などを指定する画面ですが、今回は特に変更せず先に進みます。

09-configure-advanced

最後にレビュー画面が出るので、内容に問題なければ「Deploy」をクリックしてデプロイを開始します。

10-review

正常に Raspberry Pi にデプロイできたら、コンテナに接続して動作を確認してみます。

docker exec -it aws-iot-greengrass /bin/bash

デプロイしたコンポーネントのログに動作結果が出力されていることが確認できました。

bash-4.2# tail -F /greengrass/v2/logs/com.example.MyTest.log

2022-11-30T15:19:01.247Z [INFO] (Copier) com.example.MyTest: stdout. Hello, GDK! Current time: 2022-11-30 15:19:01.246271.. {scriptName=services.com.example.MyTest.lifecycle.Run, serviceName=com.example.MyTest, currentState=RUNNING}
2022-11-30T15:19:06.251Z [INFO] (Copier) com.example.MyTest: stdout. Hello, GDK! Current time: 2022-11-30 15:19:06.250896.. {scriptName=services.com.example.MyTest.lifecycle.Run, serviceName=com.example.MyTest, currentState=RUNNING}
2022-11-30T15:19:11.261Z [INFO] (Copier) com.example.MyTest: stdout. Hello, GDK! Current time: 2022-11-30 15:19:11.259950.. {scriptName=services.com.example.MyTest.lifecycle.Run, serviceName=com.example.MyTest, currentState=RUNNING}

このコンポーネントは コンテナ上の /tmp/Greengrass_GDKTest.log にも5秒おきにメッセージを出力するものですが、こちらも正常な動作を確認できました。

bash-4.2# tail -F /tmp/Greengrass_GDKTest.log

Hello, GDK! Current time: 2022-11-30 15:19:06.250896.
Hello, GDK! Current time: 2022-11-30 15:19:11.259950.
Hello, GDK! Current time: 2022-11-30 15:19:16.265841.

以上により、Docker コンテナによる Greengrass V2 の一連の動作を確認することができました。長かった…

全体の感想

全体を通して特にハマるような点はなかったですが、実際のデバイス運用を考えるといくつか課題がありそうに思いました。

例えば、デプロイ済みのコンポーネントに対して「機能を追加したい」といったような場合、コンポーネントのバージョンを更新することになりますが、途中でコンテナが停止するようなことがあると、次の起動時に初期化された状態で起動することになるので、その辺りも考慮した設計・運用を考える必要があります。

これらについては、改めて検証していきたいと思います。

最後に

今回は、とりあえずコンテナで Greengrass V2 を動かすことが確認できました。

準備工数がそれなりにかかったり、異常時に備えたローカルへのデータ保存(マウント)なども考慮する必要があり、利用するにはややハードルが高いと感じました。

今回は以上になります。

参考リンク