Raspberry Pi 5をセットアップ後、Systems Managerに登録して、AWSからコマンドを実行する

Raspberry Piをセットアップ後、Systems Managerに登録して、AWSからコマンドを実行する

Raspberry Pi 5をセットアップ後、Systems Managerに登録して、AWSからコマンドを実行する

AWS Systems ManagerはEC2インスタンスだけでなく、ハイブリッドおよびマルチクラウド環境下のサーバも管理することができます。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-managedinstances.html

本ページでは、Raspberry Pi 5をオンプレサーバに見立てて、Systems Managerに登録します。
登録後、Systems Managerの機能を使用して、AWS側からRaspberry Pi 5ににおいてコマンドを実行します。

構築する環境

Diagram of After setting up the Raspberry Pi, register it in Systems Manager and run commands from AWS.

Raspberry Pi 5の初期セットアップを行います。
具体的には以下の通りです。

  • 必要な物品の調達
  • OSインストール
  • 組み立てと初回起動

なおRaspberry PiにインストールするOSはRaspberry Pi OSとします。
またOSインストール等の作業はMacbook Air(M1, 2020) Sonoma 14.3.1を使用します。

Raspberry PiをSystems Managerで管理するために、SSMハイブリッドアクティベーションを作成します。
用意したアクティベーションIDとコードを使用しつつ、Raspberry PiにSSM Agentをインストールします。

Systems ManagerでRaspberry Piが管理できるようになった後は、Systems Managerの機能を使用して、Raspberry Piにおいてコマンドを実行します。
今回は検証用ということで、テストファイルを作成するコマンドを実行します。

CloudFormationテンプレートファイル

上記の構成において、AWS側の構成に関しては、CloudFormationを使用して構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-fa/tree/main/152

Raspberry Pi 5初回セットアップ

必要な物品の調達

今回、私は以下の物品をAmazonで購入しました。

Detail of Raspberry Pi 01.

Raspberry Pi 5

1つ目はRaspberry Pi 5本体です。

今回はメモリが8GBタイプのものを購入しました。

SDカード

2つ目はSDカードです。

容量は128GBのものを選びました。

ケース

3つ目はRaspberry Piのケースです。

基盤を剥き出しにした状態で運用することは好ましくなく、またRaspberry Pi 5は発熱が激しいという話を聞いたため、冷却用ファン付きケースを用意しました。

Micro HDMI to HDMI変換アダプタ

4つ目はMicro HDMI to HDMI変換アダプタです。

Raspberry Pi 5が持っている映像用ポートはMicro HDMIです。
手元には同ポート用のケーブルがなかったため、通常のHDMIケーブルに変換するためのアダプタを購入しました。

OSインストール

SDカード準備

Raspberry PiはSDカードをストレージとして使用します。
ですからSDカードにOSをインストールします。

まず梱包されているSDカードを取り出します。

Detail of Raspberry Pi 03.

micro SDカードにアダプターを装着します。

Detail of Raspberry Pi 04.

最近のMacbook AirにはSDカード用のスロットがないため、Macbook用のドックを使用します。

Anker PowerExpand Direct 7-in-2 USB-C PD メディア ハブ/4K対応 HDMIポート/100W Power Delivery対応/USB-Cポート/USB-Aポート/microSD & SDカードスロット/usbハブ/hdmi type-c対応

以下の通りにMacbookとSDカードを接続します。

Detail of Raspberry Pi 05.

Raspberry Pi Imagerを使用してSDカードにOSインストール

Macbookを使用して、SDカードにOSをインストールします。
OSインストールはRaspberry Pi Imagerを使用します。

https://www.raspberrypi.com/software/

Raspberry Pi Imagerを使用するためには、同ソフトウェアをMacbookにインストールする必要があります。
上記ページからインストーラをダウンロードします。

Detail of Raspberry Pi 06.

ダウンロードされたインストーラをダブルクリックし、Raspberry Pi ImagerをApplicationsにドラッグアンドドロップします。
これでRaspberry Pi Imagerがインストールされます。

Detail of Raspberry Pi 07.

インストールされたRaspberry Pi Imagerを起動します。

Detail of Raspberry Pi 08.

3つの項目を指定します。

左の項目はOSを起動するデバイスに関するものです。

Detail of Raspberry Pi 09.

「Rasberry Pi 5」を選択します。

真ん中の項目はOSの種類やOSのビット数に関するものです。

Detail of Raspberry Pi 10.

「Raspberry Pi OS (64-bit)」を選択します。

右の項目はOSをインストールするSDカードに関するものです。

Detail of Raspberry Pi 11.

先ほど接続したSDカードを選択します。

3項目の選択が完了した後に、「次へ」を押下すると、追加の設定変更に関するページ(Use OS customization?)が表示されます。

Detail of Raspberry Pi 12.

「設定を編集する」を押下します。

3つのタブが表示されます。
それぞれ以下の通りに設定しました。

Detail of Raspberry Pi 13.
Detail of Raspberry Pi 14.
Detail of Raspberry Pi 15.

「保存」を押下すると、再びUse OS customization?のページが表示されますので、「はい」を押下して、設定を確定させます。

するとSDカードへOSインストールが開始されます。

Detail of Raspberry Pi 16.

しばらく待つとOSインストールが完了します。

Detail of Raspberry Pi 17.

この表示が出た後に、SDカードを取り外します。

ケース組み立て

Raspberry Piを箱から出します。

Detail of Raspberry Pi 18.

次にケースを箱から取り出します。

Detail of Raspberry Pi 19.

ケースがネジで固定されていますが、取り外すと2つに分解できます。

ケースの中にはネジやヒートシンクがパッケージングされています。

Detail of Raspberry Pi 20.

4つのヒートシンクをRaspberry Piに装着します。
ヒートシンクは両面テープで固定される形となります。

下部ケースにネジを装着します。

Detail of Raspberry Pi 21.

このネジはRaspberry Piの基盤をケースに固定するためのものです。

このネジにRaspberry Piの穴を通します。

Detail of Raspberry Pi 22.

さらに基盤の上からネジを装着し、ケースと基盤を固定します。

Detail of Raspberry Pi 23.

上部ケースのファン用ケーブルを基盤に接続します。

Detail of Raspberry Pi 24.

接続口はカバーがされているため、取り外す必要があるとにご注意ください。

Detail of Raspberry Pi 25.

ケースをネジで固定してケースの組み立ては完成です。

初回起動

先ほどOSをインストールしたSDカードをRaspberry Piに接続します。

Detail of Raspberry Pi 26.

加えてケーブル類を接続します。

Detail of Raspberry Pi 27.

マウス・キーボードをUSBポートに接続します。
Micro HDMI変換アダプタを使用してHDMIケーブルを接続します。
電源ケーブルをType-Cポートに接続します。

電源ケーブルを接続すると自動的にRasberry Piが起動します。

Detail of Raspberry Pi 28.

起動しない場合は側面のボタンを押下してください。

Raspberry PiをSystems Managerに登録する

Raspberry PiをSystems Managerに登録するためには、以下の手順を踏みます。

  1. ハイブリッドアクティベーションを作成する。
  2. 用意したアクティベーションIDとコードを使用しつつ、Raspberry PiにSSM Agentをインストールする。

ハイブリッドアクティベーションを作成する

ハイブリッドアクティベーションを作成する方法は、以下のAWS公式ページで説明されています。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-managedinstances.html

ここではCloudFormationを使用して、ハイブリッドアクティベーションを作成する上でのポイントをご紹介します。

SSM用IAMサービスロールを作成する

以下の通り、ハイブリッドアクティベーションを作成するためには、SSM用のIAMロールを作成する必要があるとされています。

ハイブリッドおよびマルチクラウド環境の非 EC2 (Amazon Elastic Compute Cloud) マシンでは、AWS Systems Manager サービスと通信するために AWS Identity and Access Management (IAM) サービスロールが必要です。ロールは、Systems Manager サービスに AWS Security Token Service (AWS STS) AssumeRole の信頼を付与します。

ステップ 1: ハイブリッドおよびマルチクラウド環境に IAM サービスロールを作成する
Resources:
  SSMServiceRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - ssm.amazonaws.com
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnEquals:
                aws:SourceArn: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:*"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Code language: YAML (yaml)

上記のページを参考に、IAMロールを作成しました。

本ロールにAWS管理ポリシーAmazonSSMManagedInstanceCoreをアタッチしているところがポイントです。

CloudFormationカスタムリソースを使用して、アクティベーションを作成/削除する

ハイブリッドアクティベーションを作成すると、アクティベーションIDとアクティベーションコードが返されます。
これらはRaspberry PiにSSM Agentインストールする際に使用します。

マネージメントコンソールやAWS CLI等を使用することで、ハイブリッドアクティベーションの作成/削除は可能です。
しかしCloudFormationには該当するリソースが用意されていないため、直接アクティベーションを作成することはできません。
ですからCloudFormationカスタムリソースを使用して、スタック作成/削除時にLambda関数を呼び出し、アクティベーションを作成/削除します。

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt Function.Arn

  Function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          PREFIX: !Ref Prefix
          REGION: !Ref AWS::Region
          SSM_SERVICE_ROLE: !Ref SSMServiceRole
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os

          prefix = os.environ['PREFIX']
          region = os.environ['REGION']
          ssm_service_role = os.environ['SSM_SERVICE_ROLE']

          CREATE = 'Create'
          DELETE = 'Delete'
          response_data = {}
          physical_resource_id = ''

          key_name_id = 'ActivationId'
          key_name_code = 'ActivationCode'

          ssm_client = boto3.client('ssm', region_name=region)

          def lambda_handler(event, context):
            print(event)

            try:
              if event['RequestType'] == CREATE:
                ssm_response = ssm_client.create_activation(
                  DefaultInstanceName=prefix,
                  IamRole=ssm_service_role
                )

                physical_resource_id = ssm_response[key_name_id]
                response_data[key_name_id] = ssm_response[key_name_id]
                response_data[key_name_code] = ssm_response[key_name_code]

              elif event['RequestType'] == DELETE:
                physical_resource_id = event['PhysicalResourceId']
                ssm_response = ssm_client.delete_activation(
                  ActivationId=physical_resource_id
                )

              cfnresponse.send(
                event=event,
                context=context,
                responseStatus=cfnresponse.SUCCESS,
                responseData=response_data,
                physicalResourceId=physical_resource_id
                )

            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      Timeout: !Ref Timeout

  FunctionRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: LambdaFunctionPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - iam:PassRole
                  - ssm:CreateActivation
                  - ssm:DeleteActivation
                Resource: "*"
Code language: YAML (yaml)

CloudFormationカスタムリソースに関する基本的な事項については以下のページをご確認ください。

あわせて読みたい
CloudFormationカスタムリソース入門 【CloudFormationカスタムリソースの挙動を確認する構成】 CloudFormationの機能の1つにカスタムリソースがあります。 カスタムリソースを使用すると、テンプレートにカ...

今回はランタイム環境がPython3.12のLambda関数を実行します。

実行するコードの内容を確認します。
boto3のSSM用のクライアントオブジェクトを作成後、スタック作成時はcreate_activationメソッド、スタック削除時はdelete_activationメソッドを実行します。
それぞれハイブリッドアクティベーションを作成/削除します。

スタック作成時、つまりアクティベーション作成時は、アクティベーションIDおよびアクティベーションコードが取得できます。
これらをcfnresponse.send関数を実行する際の引数responseDataの一部として渡します。
こうすることによって、これらをCloudFormationスタックのOutputsに設定し、スタック作成後に値を確認することができます。

加えてアクティベーションIDを同関数を実行する際の引数physicalResourceIdに指定します。
これはスタック削除時、つまりアクティベーション削除時にアクティベーションIDを参照できるようにするためです。

このアイデアは以下のページを大いに参考にしました。

https://dev.classmethod.jp/articles/on-premise-ubuntu-cloudfomation-aws-systems-manager-activation/

この設定の優れている点は、スタック削除時もこの値を参照することができることです。
先述のresponseDataの値はCloudFormationテンプレートファイル側からはアクセス可能ですが、カスタムリソース側からはスタック削除時にアクセスできません。
しかしphysicalResourceIdはスタック作成時に設定した値をスタック削除時にも参照することができます。
よってphysicalResourceIdにアクティベーションIDを格納することによって、スタック削除時にこのIDを参照して、アクティベーションを削除することができます。

CloudFormationスタック作成

AWS CLIを使用して、CloudFormationスタックを作成します。
上記のテンプレートファイルを任意のS3バケットに設置した上で、以下のコマンドを実行します。

aws cloudformation create-stack \
--stack-name fa-152-01 \
--template-url https://[bucket-name].s3.[region].amazonaws.com/fa-152/fa-152-01.yaml \
--capabilities CAPABILITY_IAM
Code language: Bash (bash)

スタック作成の詳細については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

作成されたリソースを確認します。

IAMロールを確認します。

Detail of IAM 01.

確かにIAMロールが作成されています。
このIAMロールはSSM用サービスロールです。

Lambda関数を確認します。

Detail of Lambda 01.

正常に関数が作成されています。

この関数の実行ログを確認します。

Detail of Lambda 02.

確かに関数が実行されています。
つまりCloudFormationカスタムリソースによって、スタック作成時に自動的にこの関数が呼び出されたということです。

ログ内のレスポンスの内容を見ると、DataにハイブリッドアクティベーションのIDとコードが設定されてることがわかります。
加えてPhysicalResourceIdの値がアクティベーションIDであることもわかります。

Dataに両データを設定しているため、CloudFormationのOutputsでも確認することができます。

Detail of CloudFormation 01.

確かにアクティベーションIDとコードが表示されています。

Systems Managerを確認します。

Detail of SSM 01.

確かにハイブリッドアクティベーションが作成されています。

このようにCloudFormationカスタムリソースを使用することによって、自動的にハイブリッドアクティベーションを作成することが可能です。

Raspberry PiにSSM Agentをインストールする

Raspberry PiにSSM Agentをインストールする方法は以下のページで紹介されています。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-install-managed-linux.html

今回はRaspberry Piで以下のコマンドを実行しました。

sudo apt-get -y update
sudo apt-get -y upgrade
sudo apt-get -y install libc6:armhf
mkdir /tmp/ssm
curl https://amazon-ssm-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/debian_arm/ssm-setup-cli -o /tmp/ssm/ssm-setup-cli
sudo chmod +x /tmp/ssm/ssm-setup-cli
sudo /tmp/ssm/ssm-setup-cli -register -activation-code "[activation-code]" -activation-id "[activation-id]" -region "ap-northeast-1"
Code language: Bash (bash)

下4行は上記のページで紹介されていたコマンドで、上4行は私が追加したコマンドです。
これは私の手元の環境ですと、依存関係のエラーが発生し、SSM Agentを正常にインストールできなかったためです。

最後のコマンドの引数として、先ほど確認したハイブリッドアクティベーションのIDとコードを指定します。

なお上記コマンドを実行する方法ですが、私はRaspberry PiにSSHして実行しました。
Raspberry Piにマウス・キーボードを直接接続していれば上記コマンドを実行することも可能ですが、これではコマンドを手打ちしなければならなくなります。
ですからRaspberry Piの無線LAN側に設定されたIPアドレスを確認後(nmcli device showコマンド等)、同アドレス向けにSSHして上記コマンドをコピペして実行することとしました。

SSM Agentのインストールが正常に完了すると、同エージェントが自動的に起動します。

awstut@raspberrypi:~ $ sudo systemctl status amazon-ssm-agent
● amazon-ssm-agent.service - amazon-ssm-agent
      Loaded: loaded (/lib/systemd/system/amazon-ssm-agent.service; enabled; preset: enabled)
      Active: active (running) since Sun 2024-03-10 14:20:20 JST; 28s ago
    Main PID: 3709 (amazon-ssm-agen)
      Tasks: 19 (limit: 9250)
        CPU: 309ms
      CGroup: /system.slice/amazon-ssm-agent.service
              ├─3709 /usr/bin/amazon-ssm-agent
              └─3725 /usr/bin/ssm-agent-worker

Mar 10 14:20:20 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:20 INFO [amazon-ssm-agent] amazon-ssm-agent - v3.2.2303.0
Mar 10 14:20:20 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:20 INFO [amazon-ssm-agent] OS: linux, Arch: arm
Mar 10 14:20:20 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:20 INFO [amazon-ssm-agent] Starting Core Agent
Mar 10 14:20:20 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:20 INFO [CredentialRefresher] credentialRefresher has started
Mar 10 14:20:20 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:20 INFO [CredentialRefresher] Starting credentials refresher loop
Mar 10 14:20:20 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:20 INFO [CredentialRefresher] Credentials ready
Mar 10 14:20:20 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:20 INFO [CredentialRefresher] Next credential rotation will be in 29.9927887592 mi>
Mar 10 14:20:21 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:21 INFO [amazon-ssm-agent] [LongRunningWorkerContainer] [WorkerProvider] Worker ss>
Mar 10 14:20:21 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:21 INFO [amazon-ssm-agent] [LongRunningWorkerContainer] [WorkerProvider] Worker ss>
Mar 10 14:20:22 raspberrypi amazon-ssm-agent[3709]: 2024-03-10 14:20:21 INFO [amazon-ssm-agent] [LongRunningWorkerContainer] Monitor long running worke>
Code language: Bash (bash)

改めてAWS側を確認します。

SSM Feet ManagerのManaged Nodeのページを確認します。

Detail of SSM 02.

確かに1台Managed Nodeが追加されています。
アクティベーションIDを見ると、確かに先ほどCloudFormationカスタムリソースによって作成されたものであることがわかります。

このようにハイブリッドアクティベーションを使用しつつSSM Agentをインストールすることによって、Raspberry PiをSystems Managerに登録することができます。

AWS Systems Managerを使用して、Raspberry Piにおいてコマンドを実行する

SSM関連付けを作成する

前項まででRaspberry PiをSystems Managerで管理できる状態になりました。
本ページでは管理の一例として、Systems Managerの機能を使用して、Raspberry Piにおいてコマンドを実行します。

ここでもCloudFormationを使用して、SSM関連付けを作成し、Run Commandで実行します。

Resources:
  RunShellScriptAssociation:
    Type: AWS::SSM::Association
    Properties:
      AssociationName: !Sub "${Prefix}-shellscript-association"
      Name: AWS-RunShellScript
      Parameters:
        commands:
          - "touch /tmp/test.txt"
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref NodeId
      WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)

ターゲットに対してSSMドキュメントAWS-RunShellScriptを実行する内容です。

ポイントは2点です。

1点目は実行するスクリプトの内容です。
今回は検証ということで、touchコマンドを使用して、/tmpディレクトリにtest.txtを作成します。

2点目はインスタンスの指定方法です。
Targetsプロパティで対象のインスタンスを指定します。
通常、本プロパティにはEC2インスタンスのIDを指定しますが、今回はハイブリッドアクティベーションを有効化したRaspberry Piに割り当てられたManaged NodeのIDを指定します。
今回は「mi-0eb22a29750efc73c」です。

CloudFormationスタック作成

今回もAWS CLIを使用して、CloudFormationスタックを作成します。
上記のテンプレートファイルを任意のS3バケットに設置した上で、以下のコマンドを実行します。

aws cloudformation create-stack \
--stack-name fa-152-02 \
--template-url https://[bucket-name].s3.[region].amazonaws.com/fa-152/fa-152-02.yaml
Code language: Bash (bash)

作成されたリソースを確認します。

SSM State Managerを確認します。

Detail of SSM 03.
Detail of SSM 04.

確かにManaged NodeであるRaspberry PiとSSMドキュメントAWS-RunShellScriptとの間にアソシエーションが作成されています。

SSM Run Commandを確認します。

Detail of SSM 05.

こちらを見るとRaspberry Piに対してRun Commandが実行されて、正常に完了したことがわかります。
つまりState Managerを通じてRun Commandが実行されたということです。

最後にRaspberry Piにアクセスして、Run Commandの実行結果を確認します。

awstut@raspberrypi:~ $ ls /tmp
ssh-XXXXXX1IKqpP
ssm
systemd-private-17099a81f2ff43059a22854d9f6f1f63-bluetooth.service-Z7slQf
systemd-private-17099a81f2ff43059a22854d9f6f1f63-ModemManager.service-SWnEDs
systemd-private-17099a81f2ff43059a22854d9f6f1f63-systemd-logind.service-44pZrv
systemd-private-17099a81f2ff43059a22854d9f6f1f63-systemd-timesyncd.service-kTxxXi
test.txt
tmp.iATINIcQGt
Code language: Bash (bash)

確かにRaspberry Piの/tmpディレクトリにtest.txtが配置されています。
Systems Managerの機能を使用して、Raspberry Piにおいてコマンドを実行できたことが確認できました。

まとめ

Raspberry Pi 5の初期セットアップ方法を確認しました。
CloudFormationカスタムリソースを使用することで、SSMハイブリッドアクティベーションの作成/削除方法を確認しました。
Raspberry Piにアクティベーションを使用しつつ、SSM Agentをインストールすることで、Systems Managerに登録する方法を確認しました。
Raspberry PiをSystems Managerで管理する一例として、Systems Managerの機能を使用して、Raspberry Piにおいてコマンドを実行する方法を確認しました。