エッジAIアプリをGreengrass x Terraformで管理する

に公開

どんな記事か

Greengrass ComponentをTerraformを使って管理したいと考えている方向けに、利用方法と管理時のディレクトリ構成についてまとめた記事です。

はじめに

ELEMENTS開発部AiQグループの森本です。AiQ PERMISSIONというプロダクトの開発を担当しております。AiQ PERMISSIONは、セルフガソリンスタンドで義務化されている給油者の行動監視をAIが代替し、人手不足の解消や業務効率化、安全性の向上を目的としているプロダクトです。

AiQ PERMISSIONではセルフガソリンスタンドにエッジデバイスを置いています。これらのデバイスはAWS IoT Coreの中で管理しており、カメラ映像から危険行動を検知するモジュール、ガソリンスタンドに常設しているSSCという機器と通信するモジュール等をGreengrassを使ってデプロイしています。

Greengrassを扱うにあたって公式ドキュメントが充実しているかといえばそうでもなく、CLIやIaCなどでデプロイしようとなると先行記事が少ないのが現状で、ChatGPTに聞いても手探りでやっていく部分が多いため、良い事例になればと思い書きました。

色々な手法がありますが、

  • チーム内にWeb開発でTerraformを扱った人材が多い
  • プロジェクト内で既にTerraformリソース管理していたものがある
    などの理由から統一した管理できるようにTerraformでGreengrassを扱ってみました。

※: IaCでGreengrassを触る記事自体はいくつかございますので、合わせてみてみると良いかもしれないです。
https://dev.classmethod.jp/articles/reinvent-2023-rob301/
https://zenn.dev/fuminori/articles/cdk-greengrass-sample

利用方法

AWSのプロバイダには、GreengrassのComponentに該当するものがないので、AWSCCのプロバイダを利用しました。具体的には、greengrassv2_component_versionを利用すればComponentが生成できます。

Component自体はこのリソースを基本的に利用することで基本的には事足ります。

ディレクトリ構成

一部抜粋してディレクトリ構成です。

repo
├── docs/                           # 技術ドキュメント
├── engine/                         # 画像処理エンジンのライブラリ
├── permission/                     # 給油許可システムのライブラリ
├── terraform/                      # Terraformリソース
│   ├── environments/               # 環境別設定
│   │   ├── dev/                    # 開発環境
│   │   ├── stg/                    # ステージング環境
│   │   └── prd/                    # 本番環境
│   ├── modules/                    # 再利用可能なモジュール
│   │   └── greengrass-component/   # AWS IoT Greengrass関連
│   ├── backend-config/             # Terraformバックエンド設定
│   └── README.md           
├── webui/                          # フロントエンド画面
└── .github/                        # CI/CD設定
    ├── ISSUE_TEMPLATE/             # イシューテンプレート
    └── workflows/                  # GitHub Actions

気をつけたこと

エッジデバイス上のアプリケーションはモノレポ管理をする

AiQ PERMISSIONはエッジデバイスに載せるアプリケーションをモノレポで管理しています。Greengrass Componentに関係するファイルだけでなく、給油許可周辺のライブラリ/画像処理のライブラリ/ドキュメントも一つのレポジトリに入れています。

エッジ上のシステムの挙動は、すべてドキュメントに記載しているので、Claude Codeがドキュメントを軸にして影響範囲を見て各レポジトリに変更を加えやすい構造になっています。

エッジデバイスをdev/stg/prdのいずれかに紐付けて、環境を分割する

エッジデバイス環境では動作確認の一つ一つに労力がかかるため、なるべく早く検証できるような環境が必要なので、通常のWeb開発に近い形で開発できるように整備しました。

3つの環境に、それぞれ複数のデバイスが紐づいており、各トリガーでGitHub Actions経由で、Greengrass Componentがデプロイされるような仕組みにしています。

それぞれの環境の定義は以下の通りです。

  • dev: 社内のシミュレータ環境等に接続しているエッジデバイスにデプロイして動作確認する用. PRのlabelの有無でデプロイを管理。
  • stg: mainブランチのアプリケーションを動作確認するための環境. mainにPRがmergeされた段階でデプロイが走る。
  • prd: 実店舗用環境. TagをトリガーにComponentがデプロイされる。

greengrass componentをmoduleでまとめる

以下の構成で一つのコンポーネントを一つのモジュールでまとめました。エッジ上で稼働するGreengrassは複数になるので、リソースの関係性を明確にするためにmodule化は効果がありました。

.
├── greengrass.tf
├── outputs.tf
├── artifacts/
├── providers.tf
├── recipe.yml.tmpl
└── variables.tf

AWSCCのgreengrassv2_component_versionリソースでは、コンポーネントの定義方法としてrecipeファイル(inline_recipeやrecipe_url)とLambda関数の直接指定(lambda_function)が利用できますが、recipeファイルでデプロイする設定を選びました。recipeが増えるとjsonでは分量が大きくなり、tfファイルが複雑化するので、recipeに相当するものはtmplで書くようにしました。

artifactsには、立ち上げ用のシェルスクリプトやdocker-composeファイルを主に入れています。

recipe.yml.tmplは以下のように記述します。ここからartifactsを読み込んでdocker-compose.ymlを立ち上げています。

RecipeFormatVersion: "2020-01-25"
ComponentName: ${component_name}
ComponentVersion: ${greengrass_version}
ComponentDescription: "Description"
ComponentDependencies:
  aws.greengrass.DockerApplicationManager:
    VersionRequirement: ~2.0.0
  aws.greengrass.TokenExchangeService:
    VersionRequirement: ~2.0.0
Manifests:
  - Platform:
      os: "linux"
    Dependencies:
      aws.greengrass.TokenExchangeService:
        VersionRequirement: ~2.0.0
        DependencyType: "HARD"
    Artifacts:
      - URI: "docker:${image}"
      - URI: "${s3_artifacts_uri_base}/artifacts.zip"
        Unarchive: "ZIP"
    Lifecycle:
      Run:
        Script: |
          export IMAGE=${image}
          docker-compose -f {artifacts:decompressedPath}/artifacts/docker-compose.yml up

greengrass.tfは以下のようなファイルになっています。

resource "awscc_greengrassv2_component_version" "component_sample" {
  inline_recipe = jsonencode(yamldecode(templatefile("${path.module}/recipe.yml.tmpl", {
    component_name                = var.component_name,
    greengrass_version            = var.greengrass_version,
    s3_artifacts_uri_base         = "s3://${var.bucket_name}/${var.s3_artifacts_dir}",
    image                         = var.image,
  })))

  depends_on = [aws_s3_object.artifacts]
}

まとめ

本記事では、GreengrassとTerraformを使ってエッジデバイスで動作するAIのアプリを管理する方法について書きました。エッジデバイスを使ったプロジェクトで開発生産性を上げたい場合の参考になれば幸いです。

ELEMENTS Engineering

Discussion