Greengrass Development Kit CLI(GDK CLI)で AWS IoT Greegrass V2 のコンポーネント開発を効率化する

AWS IoT Greengrass V2 コンポーネント開発は「GDK」が超便利!!
2022.01.21

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

AWS IoT Greengrass (V2)には、カスタムコンポーネントの開発を支援するために「Greengrass Development Kit CLI(GDK CLI)」というツールがあります。
今回は、その利用方法や動作などを確認してみましたので、ご紹介したいと思います。

これまでの課題

これまでの作業では、カスタムコンポーネントを作って AWS 側にコンポーネントを登録する場合、下記のような作業が必要で少々煩わしいと感じる部分がありました。

  • コンポーネントバージョンに合わせたフォルダ階層でS3にアーティファクトを保存
    • 例:s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/HelloWorld.zip
  • コンポーネントを更新したいときも上記パスの新しいバージョンでフォルダを作成してアーティファクトを再度保存
  • (必要に応じて)アーティファクトになる複数ファイルを ZIP 化
  • AWS 側でコンポーネントの新バージョンを作成

Greengrass Development Kit CLI(GDK CLI)のメリット

この GDK CLI を使うことで先程記載した作業内容を自動で行ってくれるので、作業効率が大きく改善します。
具体的には下記を実現してくれます。

  • コンポーネントのビルド
  • S3 へ所定のパス構成でアーティファクトを保存
    • 例:s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/HelloWorld.zip
    • 対象の S3 バケットがなければ自動的に作成
  • AWS 側へ自動的にコンポーネントを作成
  • コンポーネント更新時は新しいバージョンのフォルダ構成で新しいアーティファクトを保存
    • 更新時はAWS側にも自動的にコンポーネントの新バージョンが作成される

以上の通り GDK CLI を使うことで、コンポーネント作成に関する周辺作業がコマンド1つで実行できるようになるので、コンポーネント開発に注力できるようになります。

GDK CLI でカスタムコンポーネントを作成してみる

公式ドキュメントでは、カスタムコンポーネントの作成方法が分からなかったので、ドキュメントを参考にしつつ試していきたいと思います。
公式ドキュメントは下記ページ以下が対象になります。

GDK CLI インストール

最初に作業 PC に GDK CLI をインストールしましょう。MacOS、Windows、Unixライクなコンピュータにインストール可能です。
前提条件はドキュメントに詳細な記載がありますが、少なくとも下記の環境が必要になります。

  • python3.8 以上がインストールされていること
  • AWS CLI がインストールされていること(AWS CLI V1 でも可)
  • GDK CLI を実行する PC に下記の IAM 権限
    • s3:CreateBucket
    • s3:PutObject
    • greengrass:CreateComponentVersion
    • greengrass:ListComponentVersions

私は Mac を使っていますが pyenvの環境で「Python 3.8.7」を用意しました。IAM 権限については Assume Role を設定するなど環境に応じてご用意ください。

GDK CLI のインストールは下記コマンドを実行します。

$ python3 -m pip install git+https://github.com/aws-greengrass/aws-greengrass-gdk-cli.git

下記のように help の内容が正常に出力されていれば OK です。

$ gdk --help

usage: gdk [-h] [-d] [-v] {component} ...

Greengrass development kit - CLI for developing AWS IoT GreengrassV2 components.

positional arguments:
  {component}
    component    Initialize, build and publish GreengrassV2 components using this command.

optional arguments:
  -h, --help     show this help message and exit
  -d, --debug    Increase command output to debug level
  -v, --version  show program's version number and exit

GDK CLI で初期化する

最初に適当なところで空の作業用ディレクトリを作成します。

このディレクトリ名は S3 に保存する「アーティファクトの ZIP ファイルのファイル名」と同一にする必要があるので注意してください。

$ mkdir GDKTest
$ cd GDKTest

次に、コミュニティコンポーネントなどからコンポーネントテンプレートをダウンロードします。

$ gdk component init -l python -t HelloWorld

-t, --template オプションでコンポーネントテンプレートを指定します。単純な内容の「HelloWorld」を使うのが分かりやすそうです。
また、今回は python のコンポーネントを作りたいので、-l, --languageオプションで言語を指定しています。

GDK CLI 構成ファイルを編集する

初期化が終わると下記のようなファイルが展開されます。

.
├── README.md
├── gdk-config.json
├── main.py
├── recipe.yaml
├── src
│   └── greeter.py
└── tests
    └── test_greeter.py

編集する必要があるファイルを一つずつ見ていきましょう。
まずはgdk-config.jsonです。これは「GDK CLI 構成ファイル」というもので、ビルドシステムの種類や アーティファクトを保存する S3 バケットの指定などを行います。

展開した直後のデフォルトの内容は下記のとおりです。

{
  "component": {
    "com.example.PythonHelloWorld": {
      "author": "<PLACEHOLDER_AUTHOR>",
      "version": "NEXT_PATCH",
      "build": {
        "build_system": "zip"
      },
      "publish": {
        "bucket": "<PLACEHOLDER_BUCKET>",
        "region": "<PLACEHOLDER_REGION>"
      }
    }
  },
  "gdk_version": "1.0.0"

これを次のように更新します。

{
  "component": {
    "com.example.MyGDKTest": {
      "author": "CM-ICHIDA",
      "version": "NEXT_PATCH",
      "build": {
        "build_system": "zip"
      },
      "publish": {
        "bucket": "gdk-build",
        "region": "ap-northeast-1"
      }
    }
  },
  "gdk_version": "1.0.0"
}

更新した箇所は下記です。

  • author: コンポーネントの作成者または発行者を指定します
  • bucket: アーティファクトを保存する S3 バケット名
    • [BucketName]-[region]-[AWS ACCOUNT ID]という形式のバケットにアーティファクトを保存します
      • 構成ファイルで指定した名前が上記の[BucketName]に入ります。
      • (上記の場合、gdk-build-ap-northeast-1-XXXXXXXXXXXXというバケットになる)
    • 存在しなければ自動的に作成されます
  • region: コンポーネントを公開するリージョン

なお、build_systemは更新していません。python などインタプリタ型の言語の場合は zipを指定します。言語に応じて「Maven」や「Gradle」などを指定することができたり、カスタム指定も可能です。
それぞれの値に関する詳細については下記ドキュメントを参考にしてください。

レシピを編集する

次はコンポーネントのレシピであるrecipe.yamlを修正します。 デフォルトでは下記のとおりです。GDK CLI 特有の記載はありませんが、変数になっている項目については先程の構成ファイルやGDK CLI により利用されるものと推測されます。

---
RecipeFormatVersion: "2020-01-25"
ComponentName: "{COMPONENT_NAME}"
ComponentVersion: "{COMPONENT_VERSION}"
ComponentDescription: "This is simple Hello World component written in Python."
ComponentPublisher: "{COMPONENT_AUTHOR}"
ComponentConfiguration:
  DefaultConfiguration:
    Message: "World"
Manifests:
  - Platform:
      os: all
    Artifacts:
      - URI: "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/HelloWorld.zip"
        Unarchive: ZIP
    Lifecycle:
      Run: "python3 -u {artifacts:decompressedPath}/HelloWorld/main.py {configuration:/Message}"

このレシピを下記のように編集します。

---
RecipeFormatVersion: "2020-01-25"
ComponentName: "{COMPONENT_NAME}"
ComponentVersion: "{COMPONENT_VERSION}"
ComponentDescription: "This is GDK Test component written in Python."
ComponentPublisher: "{COMPONENT_AUTHOR}"
ComponentConfiguration:
  DefaultConfiguration:
    Message: "GDK"
Manifests:
  - Platform:
      os: all
    Artifacts:
      - URI: "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/GDKTest.zip"
        Unarchive: ZIP
    Lifecycle:
      Run: "python3 -u {artifacts:decompressedPath}/GDKTest/main.py {configuration:/Message}"

ArtifactsURIにあるアーティファクトのファイル名を作業用ディレクトリと同じものに変更しておきます。このファイル名と作業用ディレクトリが異なっていると次の作業であるビルド時にエラーになります。

また、ComponentDescriptionDefaultConfigurationMessageも適当に変えてみました。

コンポーネントのプログラムを編集する

最後にデバイス上で動作するコンポーネントの実体である Python スクリプト(main.py)を編集します。 サンプルの HelloWorld スクリプトは下記のような内容です。

import sys
import src.greeter as greeter

def main():
    args = sys.argv[1:]
    if len(args) == 1:
        print(greeter.get_greeting(args[0]))

if __name__ == "__main__":
    main()

これを適当な内容で変更します。適宜やりたいことに合わせてスクリプトを書いてください。今回は下記の様な内容にしました。

import sys
import datetime
import time

message = "Hello, %s! Current time: %s." % (sys.argv[1], datetime.datetime.now())

while(True):
    # Print the message to stdout.
    print(message)

    # Append the message to the log file.
    with open('/tmp/Greengrass_GDKTest.log', 'a') as f:
        print(message, file=f)
    time.sleep(5)

ビルドする

準備が終わったのでビルドします。

$ gdk component build

正常にビルドできれば下記のようなメッセージが表示されます。

[2022-01-21 11:52:01] INFO - Getting project configuration from gdk-config.json
[2022-01-21 11:52:01] INFO - Found component recipe file 'recipe.yaml' in the  project directory.
[2022-01-21 11:52:01] INFO - Building the component 'com.example.MyGDKTest' with the given project configuration.
[2022-01-21 11:52:01] INFO - Using 'zip' build system to build the component.
[2022-01-21 11:52:01] WARNING - This component is identified as using 'zip' build system. If this is incorrect, please exit and specify custom build command in the 'gdk-config.json'.
[2022-01-21 11:52:01] INFO - Zipping source code files of the component.
[2022-01-21 11:52:01] INFO - Copying over the build artifacts to the greengrass component artifacts build folder.
[2022-01-21 11:52:01] INFO - Updating artifact URIs in the recipe.
[2022-01-21 11:52:01] INFO - Creating component recipe in '/your-folder-path/GDKTest/greengrass-build/recipes'.

ビルド後のディレクトリの内容は下記のようになります。greengrass-buildディレクトリやzip-buildディレクトリが作成されているのが分かります。

.
├── README.md
├── gdk-config.json
├── greengrass-build
│   ├── artifacts
│   │   └── com.example.MyGDKTest
│   │       └── NEXT_PATCH
│   │           └── GDKTest.zip
│   └── recipes
│       └── recipe.yaml
├── main.py
├── recipe.yaml
├── src
│   └── greeter.py
├── tests
│   └── test_greeter.py
└── zip-build
    ├── GDKTest
    │   ├── README.md
    │   ├── main.py
    │   ├── src
    │   │   └── greeter.py
    │   └── tests
    │       └── test_greeter.py
    └── GDKTest.zip

パブリッシュする

ビルドが正常に完了すれば、次はいよいよ AWS へコンポーネントを作成することになります。この際に先に掲載した IAM 権限が必要になるので、必要に応じて用意してください。

パブリッシュ自体は下記で実行します。

$ gdk component publish

正常にパブリッシュできれば下記のようなメッセージが出力されます。

メッセージを見ると構成ファイルに基づき gdk-build-ap-northeast-1-[AWS_ACCOUNT_ID]という S3 バケットが作成されたことが分かります。また、com.example.MyGDKTestというプライベートコンポーネントがバージョン1.0.0にて AWS 側に作成されたことも分かります。

[2022-01-21 11:53:52] INFO - Getting project configuration from gdk-config.json
[2022-01-21 11:53:52] INFO - Found component recipe file 'recipe.yaml' in the  project directory.
[2022-01-21 11:53:52] INFO - Found credentials in environment variables.
[2022-01-21 11:53:53] INFO - No private version of the component 'com.example.MyGDKTest' exist in the account. Using '1.0.0' as the next version to create.
[2022-01-21 11:53:53] INFO - Publishing the component 'com.example.MyGDKTest' with the given project configuration.
[2022-01-21 11:53:53] INFO - Uploading the component built artifacts to s3 bucket.
[2022-01-21 11:53:53] INFO - Uploading component artifacts to S3 bucket: gdk-build-ap-northeast-1-[AWS-ACCOUNT-ID]. If this is your first time using this bucket, add the 's3:GetObject' permission to each core device's token exchange role to allow it to download the component artifacts. For more information, see https://docs.aws.amazon.com/greengrass/v2/developerguide/device-service-role.html.
[2022-01-21 11:53:54] INFO - Not creating an artifacts bucket as it already exists.
[2022-01-21 11:53:54] INFO - Updating the component recipe com.example.MyGDKTest-1.0.0.
[2022-01-21 11:53:54] INFO - Creating a new greengrass component com.example.MyGDKTest-1.0.0
[2022-01-21 11:53:55] INFO - Created private version '1.0.0' of the component in the account.'com.example.MyGDKTest'.

S3 バケットを確認するとパブリッシュしたコンポーネントのアーティファクト用のフォルダが作成されていることが確認できます。

01-gdk-test-s3

このフォルダにアーティファクトの ZIP ファイルが保存されていますが、バージョンが1.0.0として保存されていることが分かります。

02-artifact-path-on-s3

AWS IoT Greengrass のコンソール画面を見ると、パブリッシュしたコンポーネントが登録されていることを確認できました!

03-component-on-console

コンポーネントをデプロイしてみる

GDK CLI がやってくれることは AWS へコンポーネントを作成することまでですが、せっかくなのでデバイスにデプロイしてみましょう。
デプロイ作業はいつもどおりの作業になります。さきほど GDK CLI で作成したコンポーネントを選択してデプロイします。

04-deploy-component

デプロイが正常に完了すると、コンポーネントログ(/greengraass/v2/logs/com.example.MyGDKTest.log)にメッセージが5秒ごとに出力されていることが分かります。

05-component-log

コンポーネントのスクリプトで出力先を指定した /tmp/Greengrass_GDKTest.logにもメッセージを確認できました。

06-component-tmp-log

コンポーネントを GDK CLI で更新する

実は、先程デプロイしたスクリプトは一部ミスがあり、出力メッセージにあるタイムスタンプが更新されていませんでした。これを修正したコンポーネントを GDK CLI で更新してデプロイし直したいと思います。

作業 PC で先程のmain.pyを次のように修正します。

import sys
import datetime
import time

while(True):
    # Print the message to stdout.
    message = "Hello, %s! Current time: %s." % (sys.argv[1], datetime.datetime.now())
    print(message)

    # Append the message to the log file.
    with open('/tmp/Greengrass_GDKTest.log', 'a') as f:
        print(message, file=f)
    time.sleep(5)

GDK CLI 構成ファイルやレシピなど、他のファイルは修正する必要がないので、このままビルドします。

$ gdk component build

ビルド後のファイル構成は特に変わりませんが、タイムスタンプを付与した状態で見ると、greengrass-buildディレクトリやzip-buildのタイムスタンプが更新されていました。
(最初にビルドしたのは午前11時台でしたが、再ビルドは14時14分頃に実施しました)

$ tree -AD
.
├── [Jan 21 11:16]  README.md
├── [Jan 21 11:31]  gdk-config.json
├── [Jan 21 14:14]  greengrass-build
│   ├── [Jan 21 14:14]  artifacts
│   │   └── [Jan 21 14:14]  com.example.MyGDKTest
│   │       └── [Jan 21 14:14]  NEXT_PATCH
│   │           └── [Jan 21 14:14]  GDKTest.zip
│   └── [Jan 21 14:14]  recipes
│       └── [Jan 21 14:14]  recipe.yaml
├── [Jan 21 14:12]  main.py
├── [Jan 21 11:45]  recipe.yaml
├── [Jan 21 11:16]  src
│   └── [Jan 21 11:16]  greeter.py
├── [Jan 21 11:16]  tests
│   └── [Jan 21 11:16]  test_greeter.py
└── [Jan 21 14:14]  zip-build
    ├── [Jan 21 14:14]  GDKTest
    │   ├── [Jan 21 11:16]  README.md
    │   ├── [Jan 21 14:12]  main.py
    │   ├── [Jan 21 11:16]  src
    │   │   └── [Jan 21 11:16]  greeter.py
    │   └── [Jan 21 11:16]  tests
    │       └── [Jan 21 11:16]  test_greeter.py
    └── [Jan 21 14:14]  GDKTest.zip

次にパブリッシュします。

$ gdk component publish

2回目のパブリッシュが完了すると、S3 上のアーティファクトの保存先が新しいバージョン(1.0.1)で作成されていました。

07-s3-update-component-version

08-s3-zip-update-version

AWS 側のコンソールでもコンポーネントの新バージョン(1.0.1)が作成されていました。

09-new-version-component

この新しいバージョンのコンポーネントを再度デプロイして正常に5秒ごとのタイムスタンプが出力されるようになりました。

10-update-check-component-logs

11-component-tmp-logs

以上のように、「コンポーネントの更新〜新バージョンの作成」の作業も非常に簡単になりました。

最後に

GDK CLI により面倒な作業を自動化することでコンポーネント開発に専念できるようになり、開発スピードも改善できることが分かりました。

とても便利なツールなので、ぜひ使ってみていただければと思います!!

参考ドキュメント等