AWS CodeArtifact に保存した Python の独自モジュールを AWS IoT Greengrass V2 のコンポーネントデプロイ時にインストールして利用してみた

AWS CodeArtifact の活用例をご紹介します!
2022.03.07

背景と課題

背景

AWS IoT Greengrass(V2)で複数のコンポーネントを動かしているとき、各コンポーネントで共通する処理を行うことがあります。しかし、各コンポーネントは独立して稼働するためコンポーネント毎にパッケージングする必要があり、メンテナンスなど運用に負荷がかかってしまいます。

00-diagram-before

課題

上記の背景を踏まえて、各コンポーネントの共通機能をモジュールとしてインポートして利用できるようにしたいですが、環境によってはモジュールは PyPI のようにパブリックに公開したくない場合があります。

またこのとき、IoT デバイスは工場構内、屋外、車上、海上など様々な場所で利用されるため、インポートする際にデバイス側には永続的なクレデンシャル情報を保持したくありません。
(最低限、AWS IoT Core と接続するためのルート証明書、デバイス証明書、秘密鍵は持つものとします。)

このような課題背景に対して、AWS CodeArtifact と AWS IoT Greengrass をうまく利用することで課題を解決できそうだったので、試した内容をご紹介したいと思います。

(AWS Secrets Manager を使う方法でもよさそうと思うので、機会があればそちらも試してみたいと思います。)

構成

全体の構成は下記のようになります。

00-diagram-after2

Greengrass Core デバイスのセットアップ時にロールエイリアスを設定しますが、コンポーネントのデプロイ時にそのロールエイリアスが持つ権限の一時クレデンシャルを取得します。

今回の場合、このクレデンシャルに必要な権限は下記のとおりです。

  • アーティファクトを保存している S3 バケットからオブジェクトを取得できる権限
    • 今回はAWS 側からコンポーネントのデプロイを実行する為です。
  • CodeArtifact のリポジトリからPython のカスタムモジュールをダウンロードできる権限

構成と環境構築に必要な情報が分かったので、早速作っていきたいと思います。

AWS CodeArtifact の構築

作業内容

ここで行うことは下記です。

  • AWS CodeArtifact のドメインとレポジトリの作成
  • カスタムモジュールの用意
  • 手元の PC から AWS CodeArtifact に接続して、カスタムモジュールをアップロード

AWS CodeArtifact 側の手順

最初に、適当な名前でドメインとレポジトリを作成します。今回はそれぞれ下記のようにしました。

  • ドメイン:mydomain
  • リポジトリ:myrepo

10-codeartifact-domain

11-codeartifact-repo

次にカスタムモジュールを用意します。今回はモジュールの作成が目的ではないので、カスタムモジュールの作成は行わずにサンプルパッケージである sampleproject をAWS CodeArtifact に登録したいと思います。

適当な作業ディレクトリを作成してsampleprojectをダウンロードします。

% mkdir -p ~/sampleproject-temp/dist
% cd ~/sampleproject-temp
% pip download sampleproject

AWS CodeArtifactへのアップロードは twine を使って行うので、twine を PC にインストールします。

% pip install -U twine

準備ができたので、手元の PC(Mac)から AWS CodeArtifact にログインします。事前に Assume Role するなどして AWS CodeArtifact に接続できる権限を PC に持たせておいてください。

% aws codeartifact login \
  --tool pip \
  --repository myrepo \
  --domain mydomain \
  --domain-owner [YOUR_AWS_ACCOUNT_ID]

ログインできたら twine を使ってアップロードを行います。

% cd ..
% aws codeartifact login --tool twine --repository myrepo --domain mydomain
% twine upload -r codeartifact dist/*

アップロードができたらコンソールで確認してみましょう。下記のように作成したリポジトリにsampleprojectがあれば成功です。

02-mysampleproject

AWS IoT Greengrass 側の準備

作業内容

ここで行うことは下記です。

  • AWS CLI のインストール
  • コンポーネントレシピの作成
  • アーティファクトコードの作成
  • AWS IoT Greengrass へコンポーネント登録
  • Greengrass コアデバイスのロールエイリアスに AWS CodeArtifact を利用する権限を付与

AWS IoT Greengrass 側の手順

AWS CodeArtifact にログインする為、事前に AWS CLI をインストールしておきます。

Raspberry Pi 32bit 版など、デバイスによっては、「AWS CLI v1」が必要な場合は下記を参照して下さい。
(最近、正式に Raspberry Pi OS 64 bit がリリースされたので、今なら Raspberry Pi でも「AWS CLI v2」が利用できます)

次にコンポーネントを作成します。コンポーネントの作成は「GDK CLI」(Greengrass Development Kit CLI)を利用しますが GDK の利用方法は割愛します。詳細は下記のブログをご参照ください。

今回使うコンポーネント用のレシピを下記の内容で作成します。Lifecycle の箇所で AWS CodeArtifact にログインしてsampleproject をインストールするコマンドを記載しています。

[YOUR_AWS_ACCOUNT_ID]の部分はご利用のものに変更してください。

---
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-0202-03"
Manifests:
  - Platform:
      os: all
    Artifacts:
      - URI: "s3://BUCKET_NAME/COMPONENT_NAME/COMPONENT_VERSION/GDKTest.zip"
        Unarchive: ZIP
    Lifecycle:
      Install:
        Script: |
          aws codeartifact login --tool pip --repository myrepo --domain mydomain --domain-owner [YOUR_AWS_ACCOUNT_ID]
          pip3 install -U sampleproject
          pip3 config unset global.index-url
      Run: "python3 -u {artifacts:decompressedPath}/GDKTest/main.py {configuration:/Message}"

レシピのように aws codeartifact login --tool pip コマンドを実行すると、~/.config/pip/pip.confに トークンが記載された URL が記載されてしまいます。このトークンの有効時間はデフォルトで 12 時間ですが、トークンが永続的に残るような形は好ましくありません。
そのため、上記レシピの21行目 pip3 config unset コマンドで URL 情報を pip.conf から削除しています。

(参考までに、Greengrass 環境の場合 pip.confはデフォルトで home/ggc_user/.config/pip/pip.conf になります。)

ちなみに aws codeartifact login --tool pip コマンドを使わずに、同じ動作を下記のように書くこともできます。少し行数が増えますがこちらの方が処理内容が分かりやすいです。

Install:
  Script: |
    ODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain mydomain --domain-owner [YOUR_AWS_ACCOUNT_ID] --query authorizationToken --output text`
    pip3 config set global.index-url https://aws:$CODEARTIFACT_AUTH_TOKEN@mydomain-[YOUR_AWS_ACCOUNT_ID].d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/simple/
    pip3 install -U sampleproject
    pip3 config unset global.index-url

   

次にアーティファクトとしてコードを用意します。今回は下記のような単純なものにしました。

先程 AWS CodeArtifact にアップロードしたsampleproject は インポートするだけにしています(4行目)。コンポーネントのデプロイ時に AWS CodeArtifact から正常にダウンロード・インポートできなければデプロイが失敗します。

import sys
import datetime
import time
import sample # Test the import of modules installed from CodeArtifact

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
$ gdk component publish

次に、Greengrass コアデバイスが AWS Artifact にアクセスできるようロールエイリアスに権限を付与します。ロールエイリアスは Greengrass コアソフトウェアのセットアップ時に指定したものになります。

今回は、AWS 管理ポリシーである AWSCodeArtifactAdminAccess をロールエイリアスにアタッチしました。このポリシー内容は下記の通りです。

{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Action": [
            "codeartifact:*"
         ],
         "Effect": "Allow",
         "Resource": "*"
      },
      {
         "Effect": "Allow",
         "Action": "sts:GetServiceBearerToken",
         "Resource": "*",
            "Condition": {
               "StringEquals": {
                  "sts:AWSServiceName": "codeartifact.amazonaws.com"
               }
            }
      }
    ]
}

参考ドキュメントは下記になります。

なお、AWS CodeArtifact はリソースベースポリシーもサポートしているので、必要に応じてご利用下さい。

デプロイ

準備が全て完了したのでデバイスにデプロイを行います。デプロイ作業は従来のコンポーネントデプロイと変わらないので割愛します。

デプロイ時のコンポーネントログには、次のようなメッセージが出ており、AWS CodeArtifact のリポジトリからインストールできていることが分かります。
(見やすさのため一部編集しています)

2022-03-03T10:30:38.633Z Successfully configured pip to use AWS CodeArtifact repository https://mydomain-[YOUR_AWS_ACCOUNT_ID].d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/. 
2022-03-03T10:30:38.634Z Login expires in 12 hours at 2022-03-04 07:30:36+09:00. 
2022-03-03T10:30:42.145Z Looking in indexes: https://mydomain-[YOUR_AWS_ACCOUNT_ID].d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/simple/, https://www.piwheels.org/simple. 
2022-03-03T10:30:42.150Z Collecting sampleproject. 
2022-03-03T10:30:43.973Z Downloading https://mydomain-[YOUR_AWS_ACCOUNT_ID].d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/simple/sampleproject/2.0.0/sampleproject-2.0.0-py3-none-any.whl. 
2022-03-03T10:30:44.003Z Collecting peppercorn (from sampleproject). 
2022-03-03T10:30:44.817Z Downloading https://mydomain-[YOUR_AWS_ACCOUNT_ID].d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/simple/peppercorn/0.6/peppercorn-0.6-py3-none-any.whl. 
2022-03-03T10:30:45.676Z Installing collected packages: peppercorn, sampleproject. 
2022-03-03T10:30:45.720Z Successfully installed peppercorn-0.6 sampleproject-2.0.0.

最後に

今回の構成であれば、下記のような当初の課題を解決できることが分かりました。

  • モジュールはパブリックに公開したくない
    • AWS CodeArtifact のリポジトリに保存できる
  • デバイスには永続的なクレデンシャル情報を保持したくない
    • 一時クレデンシャルを利用して AWS CodeArtifact を利用できる

カスタムモジュールの必要性が出てくるケースは多くないかもしれませんが、もし必要になった場合は今回の構成を参考にしていただければ幸いです。

以上です。