📘

AWS IoT Greengrassをコンテナで動かす

2022/02/13に公開

概要

これをやって見ます。

https://docs.aws.amazon.com/ja_jp/greengrass/v2/developerguide/run-greengrass-docker.html

今回の構成

今回は以下のEdge deviceにGreengrass(GG)をDockerコンテナとしてデプロイする。GGのバージョンは2.5.3

   ┌──────────────────┐
   │  GGv2 Components │                    ┌──────────────────┐
   ├──────────────────┤                    │                  │
   │  GGv2 Container  │◄──────────────────►│    Greengrass    │
   ├──────────────────┤                    │                  │
   │     Docker       │                    └──────────────────┘
   ├──────────────────┤                 AWS Cloud
   │     Linux VM     │
   └──────────────────┘
       Edge device                               

この方法は、どうやらGGのコンテナが起動するときに、/root/.awsをマウントし、そこにあるファイルからパラメータを読み取るようだ。以降、そのためのファイルを作っていく。

クレデンシャルファイルを作る

動かすにはIAMユーザーのクレデンシャルが必要で以下の2が推奨されているが(ちょっと使い方を調べないといけなさそうなので)今回はひとまず1でやってみる。つまり、今使っているIAMユーザーのクレデンシャルを設定する。

  1. Long-term credentials for an IAM user
  2. Temporary credentials for an IAM role

以下のようなファイルをgreengrass-v2-credentials/credentialsとして置いておく

greengrass-v2-credentials/credentials
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

2.の方法だとaws_session_tokenも必要とのこと。あとでこのやり方も調べたい

環境ファイルを作る

.envファイルをつくる

.env
GGC_ROOT_PATH=/greengrass/v2
AWS_REGION=ap-northeast-1
PROVISION=true
THING_NAME=MyGreengrassCore
THING_GROUP_NAME=MyGreengrassCoreGroup
TES_ROLE_NAME=GreengrassV2TokenExchangeRole
TES_ROLE_ALIAS_NAME=GreengrassCoreTokenExchangeRoleAlias
DEPLOY_DEV_TOOLS=true

GGのイメージをpull

docker pull amazon/aws-iot-greengrass:latest

コンテナ起動

-itdとしてデーモンとして起動しています。

docker run --init -itd --name aws-iot-greengrass \
-v /home/ssm-user/gg-core/gg-config/greengrass-v2-credentials:/root/.aws/:ro \
--env-file .env \
-p 8883 \
amazon/aws-iot-greengrass:latest

出力

Installing Greengrass for the first time...
Running Greengrass with the following options: -Droot=/greengrass/v2 -Dlog.store=FILE -Dlog.level= -jar /opt/greengrassv2/lib/Greengrass.jar --provision true --deploy-dev-tools true --aws-region ap-northeast-1 --start false --thing-name MyGreengrassCore --thing-group-name MyGreengrassCoreGroup --tes-role-name GreengrassV2TokenExchangeRole --tes-role-alias-name GreengrassCoreTokenExchangeRoleAlias --component-default-user ggc_user:ggc_group
Provisioning AWS IoT resources for the device with IoT Thing Name: [MyGreengrassCore]...
Found IoT policy "GreengrassV2IoTThingPolicy", reusing it
Creating keys and certificate...
Attaching policy to certificate...
Creating IoT Thing "MyGreengrassCore"...
Attaching certificate to IoT thing...
Successfully provisioned AWS IoT resources for the device with IoT Thing Name: [MyGreengrassCore]!
Adding IoT Thing [MyGreengrassCore] into Thing Group: [MyGreengrassCoreGroup]...
Successfully added Thing into Thing Group: [MyGreengrassCoreGroup]
Setting up resources for aws.greengrass.TokenExchangeService ... 
TES role alias "GreengrassCoreTokenExchangeRoleAlias" does not exist, creating new alias...
IoT role policy "GreengrassTESCertificatePolicyGreengrassCoreTokenExchangeRoleAlias" for TES Role alias not exist, creating policy...
Attaching TES role policy to IoT thing...
No managed IAM policy found, looking for user defined policy...
IAM policy named "GreengrassV2TokenExchangeRoleAccess" already exists. Please attach it to the IAM role if not already
Configuring Nucleus with provisioned resource details...
Downloading Root CA from "https://www.amazontrust.com/repository/AmazonRootCA1.pem"
Created device configuration
Successfully configured Nucleus with provisioned resource details!
Creating a deployment for Greengrass first party components to the thing group
Configured Nucleus to deploy aws.greengrass.Cli component
Creating user ggc_user 
ggc_user created 
Creating group ggc_group 
ggc_group created 
Added ggc_user to ggc_group 
Nucleus start set to false, exiting...
Making loader script executable...
Starting Greengrass...
+ set +m
++ dirname /greengrass/v2/alts/current/distro/bin/loader
+ PWD=/greengrass/v2/alts/current/distro/bin
+ sigterm_received=0
++ cd /greengrass/v2/alts/current/distro/bin/../../../..
++ pwd
+ GG_ROOT=/greengrass/v2
+ echo 'Greengrass root: /greengrass/v2'
Greengrass root: /greengrass/v2
+ LAUNCH_DIR=/greengrass/v2/alts/current
+ CONFIG_FILE=
+ is_directory_link /greengrass/v2/alts/new
+ '[' -L /greengrass/v2/alts/new ']'
+ is_directory_link /greengrass/v2/alts/broken
+ '[' -L /greengrass/v2/alts/broken ']'
+ is_directory_link /greengrass/v2/alts/old
+ '[' -L /greengrass/v2/alts/old ']'
+ j=1
+ '[' 1 -le 3 ']'
+ '[' 0 -eq 0 ']'
+ launch_kernel
+ is_directory_link /greengrass/v2/alts/current
+ '[' -L /greengrass/v2/alts/current ']'
+ '[' -d /greengrass/v2/alts/current ']'
+ '[' -f /greengrass/v2/alts/current/launch.params ']'
++ cat /greengrass/v2/alts/current/launch.params
+ JVM_OPTIONS='-Dlog.level= -Dlog.store=FILE'
+ JVM_OPTIONS='-Dlog.level= -Dlog.store=FILE -Droot=/greengrass/v2'
+ OPTIONS='--setup-system-service false'
+ '[' '!' -z ']'
+ echo 'JVM options: -Dlog.level=' -Dlog.store=FILE -Droot=/greengrass/v2
JVM options: -Dlog.level= -Dlog.store=FILE -Droot=/greengrass/v2
+ echo 'Nucleus options: --setup-system-service' false
Nucleus options: --setup-system-service false
+ child_pid=
+ trap 'echo Received SIGTERM; sigterm_received=1; kill -TERM ${child_pid}; wait ${child_pid}; echo Killed child PID' TERM
+ child_pid=104
+ wait 104
+ 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.

お、動いたっぽいですね。コンテナに接続します。

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

起動のログはコンテナの中のここです。

bash-4.2# more /greengrass/v2/logs/greengrass.log
bash-4.2# /greengrass/v2/bin/greengrass-cli component list
Feb 08, 2022 12:23:47 PM software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnection$1 onConnectionSetup
INFO: Socket connection /greengrass/v2/ipc.socket:8033 to server result [AWS_ERROR_SUCCESS]
Feb 08, 2022 12:23:47 PM software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnection$1 onProtocolMessage
INFO: Connection established with event stream RPC server
Components currently running in Greengrass:
Component Name: DeploymentService
    Version: 0.0.0
    State: RUNNING
    Configuration: null
Component Name: UpdateSystemPolicyService
    Version: 0.0.0
    State: RUNNING
    Configuration: null
Component Name: TelemetryAgent
    Version: 0.0.0
    State: RUNNING
    Configuration: null
Component Name: aws.greengrass.Nucleus
    Version: 2.5.3
    State: FINISHED
    Configuration: {"awsRegion":"ap-northeast-1","componentStoreMaxSizeBytes":"10000000000","deploymentPollingFrequencySeconds":"15","envStage":"prod","fleetStatus":{"periodicStatusPublishIntervalSeconds":86400.0},"greengrassDataPlanePort":"8443","httpClient":{},"iotCredEndpoint":"c1jaik620mmr8w.credentials.iot.ap-northeast-1.amazonaws.com","iotDataEndpoint":"a3su8icnk38ik7-ats.iot.ap-northeast-1.amazonaws.com","iotRoleAlias":"GreengrassCoreTokenExchangeRoleAlias","jvmOptions":"-Dlog.level= -Dlog.store=FILE","logging":{},"mqtt":{"spooler":{}},"networkProxy":{"proxy":{}},"platformOverride":{},"runWithDefault":{"posixUser":"ggc_user:ggc_group"},"telemetry":{}}
Component Name: FleetStatusService
    Version: null
    State: RUNNING
    Configuration: null
Component Name: aws.greengrass.Cli
    Version: 2.5.3
    State: RUNNING
    Configuration: {"AuthorizedPosixGroups":null,"AuthorizedWindowsGroups":null}

awsコマンドが入っていればこれも使えます

bash-4.2# aws greengrassv2 list-components
{
    "components": []
}

おそらくこのコマンドではWebコンソールからデプロイしたコンポーネントのみが表示される(なので今は何も表示されない)

Core deviceの状態確認。Core device落ちてるはずなのになぜかHealthyとでる。ようわからん。

aws greengrassv2 get-core-device \
  --core-device-thing-name MyGreengrassCore
  {
    "coreDeviceThingName": "MyGreengrassCore",
    "coreVersion": "2.5.3",
    "platform": "linux",
    "architecture": "amd64",
    "status": "HEALTHY",
    "lastStatusUpdateTimestamp": "2022-01-30T20:38:27.706000+09:00",
    "tags": {}
}

Greengrassコンポーネントを作って実行する

https://docs.aws.amazon.com/greengrass/v2/developerguide/getting-started.html#create-first-component

CLIの場合

CLIは実は以下の蓋通りの方法がある。

  1. Create a component (GDK CLI)
  2. Create a component (shell commands)

2の方はここに自動でコンポーネントを作るシェルスクリプトを置いてく

https://gist.github.com/narutaro/b843f5bc54790dc31819673897c6f1db#file-create-hello-world-greengrass-component-sh

このスクリプトでやっている内容は以下のとおり

コンポーネントはartifactsと呼ばれるコード(ここではPythonコード)とrecipesというメタデータでできている。以下のようにファイルを配置する。

/root
`-- greengrassv2
    |-- artifacts
    |   `-- com.example.HelloWorld
    |       `-- 1.0.0
    |           `-- hello_world.py
    `-- recipes
        `-- com.example.HelloWorld-1.0.0.json
com.example.HelloWorld-1.0.0.json
{
  "RecipeFormatVersion": "2020-01-25",
  "ComponentName": "com.example.HelloWorld",
  "ComponentVersion": "1.0.0",
  "ComponentDescription": "My first AWS IoT Greengrass component.",
  "ComponentPublisher": "Amazon",
  "ComponentConfiguration": {
    "DefaultConfiguration": {
      "Message": "world"
    }
  },
  "Manifests": [
    {
      "Platform": {
        "os": "linux"
      },
      "Lifecycle": {
        "Run": "python3 -u {artifacts:path}/hello_world.py \"{configuration:/Message}\""
      }
    },
    {
      "Platform": {
        "os": "windows"
      },
      "Lifecycle": {
        "Run": "py -3 -u {artifacts:path}/hello_world.py \"{configuration:/Message}\""
      }
    }
  ]
}
hello_world.py
import sys

message = "Hello, %s!" % sys.argv[1]

# Print the message to stdout, which Greengrass saves in a log file.
print(message)

コンポーネントをデプロイする

sudo /greengrass/v2/bin/greengrass-cli deployment create \
  --recipeDir ~/greengrassv2/recipes \
  --artifactDir ~/greengrassv2/artifacts \
  --merge "com.example.HelloWorld=1.0.0"

/greengrass/v2/logs/com.example.HelloWorld.logにメッセージが見えるはず。

全てローカル(コンテナ内)での作業なのでGGコアノードの台数が増えたら繰り返しがつらい。テストでしか使えないと思う。

Webコンソールの場合

AWS IoT > Greengrass > Components > Create componentで直接レシピを書く方法。やってることはほぼCLIの場合と同じ。artifactsをS3においておくので複数のGreengrassコアノードにデプロイしやすい。

Lambda Functionをデプロイ

多分これが一番楽な方法。

Lambdaがサポートする言語をGGが全てサポートしている訳ではないので注意が必要。GGではPythonは3.8まで。

Lambda

Lambda側でコンポーネントにするLambda Functionを作る。明示的にバージョンをつけておく。$LATESTは使えないので、Lambda > Functions > Function_NameにてActions > Publish new versionするなど。

Greengrass

  1. AWS IoT > Greengrass > Componentsにて Create component > Import Lambda functionから、作っておいたLambda Functionを選んでデプロイ
  2. Create componentのオプションのLinux process configuration - optionalのところでIsolation modeNo containerにしないとデプロイに失敗する。今回はGG自体がコンテナだからその上にコンテナはデプロイできないのかもしれない。

ちなみにIsolation modeGreengrass containerを選んでしまった場合のエラーはこんなん。

2022-02-12T11:18:41.701Z [ERROR] (pool-2-thread-63) just-print-message: FATAL: Failed to reset thread's mount namespace due to an unexpected error. To maintain consistency, GGC will crash and need to be manually restarted.  {"errorString": "operation not permitted"}. {serviceInstance=0, serviceName=just-print-message, currentState=STARTING}
  1. Lambda function configuration - optionalEvent sources
  • Topic: test/test
  • Type: AWS IoT Core MQTT
    にしておくと、IoT CoreのTestツールからのMQTTメッセージ送信をトリガーにFunctionを実行することができるのでおすすめ。

とりあえず動かすところまでやりました。

シリーズ

Discussion