オンプレミスのUbuntuをCloudFomation一撃でAWS Systems Managerに登録してみた

CloudFomation を使用して、AWS Systems Manager のアクティベーションコードを発行するまでをカスタムリソースで実装しました。
2022.06.13

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

最近どうもLinux端末(特にUbuntu)にご縁があるAWS事業本部の梶原@福岡オフィスです。

  • 家の中にあるLinux端末って把握してますか?
  • ユーザー管理とかどうしてます?
  • IP管理とかどうしてます?
  • 端末にディスプレイついてます?
  • 出先からアクセスしたくないですか?
  • このRaspberry Pi 達パスワード分かんないな。
  • 東京に出張した際に、家の機械学習PCのトレーニング状況を知りたいな
  • Linux端末セットアップしてくんない?リモートから。
  • AWSDeepRacer 車両。。。

そんなあなたに、オンプレミスのマシンの状態を把握できる AWS Systems Manager !

アドバンスドオンプレミスインスタンスにすれば、セッションマネージャーを使ってSSH接続できます。 スタンダードでAWS Systems Manager に登録するだけであれば、追加料金はかかりません!

ということで、とりあえず登録はしておこうかな。

となるんですが、手順や、IAMRole作成などちょっぴり手間ですよね。わかります。

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

アクティベーションコードはまとめて発行もできるんですが、忘れるんですよね。

ロールも個別作成は結構面倒くさいです。でも基本別々にしたいです。

やりたいことは

  1. アクティベーションコードを発行する
  2. アクティベーションする(AWS Systems Manager に登録する)

なんよ。

ということで、CloudFormation一撃化したので、共有します。

やってみた

前提条件

  • AWS Systems Managerに登録する端末はインターネット接続が必要です
    • もしくは別のネットワーク経路(VPNなど)でAWS Systems Managerのエンドポイントに接続できる必要があります
  • AWS Systems ManagerがサポートしているOSである必要があります
    • Amazon Linux 2, Amazon Linux, RHEL, Oracle Linux, CentOS, and SLES
    • Ubuntu Server
    • Debian Server
    • Raspberry Pi OS (formerly Raspbian)
    • Windows Server
    • 上記OSでもバージョン、ハードウェアによってはサポートされていないことがありますので詳しくはサポートされているOSを確認してください

アクティベーションコードを発行する

CloudFormation スタックの作成

CloudFormation のテンプレートを作成しているので下記リンクをクリックして、CloudFormationスタックを作成してください

https://ap-northeast-1.console.aws.amazon.com/cloudformation/home?region=ap-northeast-1#/stacks/quickcreate?templateUrl=https%3A%2F%2Fpub-devio-blog-qrgebosd.s3.ap-northeast-1.amazonaws.com%2Ftemplate%2Fcfn-ssm-activation.yml&stackName=cfn-ssm-activation&param_MyHybridInstanceName=my-hybrid-instance&param_MyHybridInstanceRoleName=my-hybrid-instance-role&param_SsmActivationDescription=My Hybrid Instanse Description

項目 内容 備考
スタックの名前 cfn-ssm-activation スタックの名称を入力します。
MyHybridInstanceName my-hybrid-instance SSMに登録する際の名称を入力します
MyHybridInstanceRoleName my-hybrid-instance-role ハイブリッドインスタンスに割り当てるRole名を入力します
SsmActivationDescription My Hybrid Instanse Description アクティベーションのメモです、登録対象のわかりやすい記述を入力してください

AWS CloudFormation によって IAM リソースがカスタム名で作成される場合があることを承認します。

にチェックを入れて作成します

CloudFormationスタックが正常に作成され以下のリソースが作成されれば成功です。

また、出力に作成したアクティベーションコードが記載されていますので、メモしてください

こちらのアクティベーションコードの有効期間は発行から1日となります。また、アクティベーションの有効数もつとしていますのですみやかにご使用ください

変更したい方はコードのcreate_activation のパラメータを変更すればカスタム可能です

セキュリティ上保持させたくない場合もあるかと思いますのでその場合は、発行処理から直接Systems Mangaerのパラメータ等に直接保存する処理などもご検討ください

作成されるリソースについて

以下のリソースが作成されます

項目 内容 備考
LambdaIAMRole AWS::IAM::Role カスタムリソースで必要となるRole
MyHybridInstanceRole AWS::IAM::Role ハイブリッドインスタンスに割り当てるRole
SsmActivation Custom::SsmActivation Lambda-backledカスタムリソース(アクティベーションの登録処理を行います)
SsmActivationLambdaFunction AWS::Lambda::Function アクティベーションの登録処理を行うLambda

ハイブリッドインスタンスに割り当てるRole(MyHybridInstanceRole)について

以下の権限を割り当てています。

  • AmazonSSMManagedInstanceCore
  • CloudWatchAgentServerPolicy
    • CloudWatchLogsにログ等を出力する際に必要
  • KmsDecrypt (全キーのDecrypt権限)
    • セッションマネージャーで接続する際にkms暗号化を使用する場合に必要

参考

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/getting-started-add-permissions-to-existing-profile.html

また、AWS Systems Manager のアクティベーションのコンソールを確認すると作成されていると思いますので、ご確認ください。

AWS Systems Manager >アクティベーション

https://ap-northeast-1.console.aws.amazon.com/systems-manager/activations?region=ap-northeast-1

アクティベーションする

正確にはハイブリッド環境 (Linux) に SSM Agent をインストール する作業になります

他のOS等、最新の手順は以下公式ドキュメントを参考にしてください

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

Ubuntu を実行しているマシンにログインします

先程メモした、アクティベーションID,アクティベーションコードを使用します

以下コマンドの activation-code, activation-id を 置き換えてください

また、リージョンはCloudFromationスタックを作成したリージョンとなります (東京の場合はap-northeast-1)

sudo snap install amazon-ssm-agent --classic
sudo systemctl stop snap.amazon-ssm-agent.amazon-ssm-agent.service
sudo /snap/amazon-ssm-agent/current/amazon-ssm-agent -register -code "activation-code" -id "activation-id" -region "region" 
sudo systemctl start snap.amazon-ssm-agent.amazon-ssm-agent.service

正常に登録されると登録済インスタンスが0から1になります。

AWS Systems Manager >アクティベーション

https://ap-northeast-1.console.aws.amazon.com/systems-manager/activations?region=ap-northeast-1

AWS Systems Manager で管理する

正常に登録されていれば

AWS Systems Manager > フリートマネージャー

の方にも表示されるかと思います。

スクロールすると状況やSSM Agentのバージョンなどもあります

また、ノードIDをクリックし対象のインスタンスの詳細が確認できるかとおもいます。

ノードの概要

また、スタンダードでは表示できませんが、高度なインスタンス枠に変更すると、パフォーマンスカウンターなども表示することができます

  • ファイル転送
  • プロセス作成(高度なインスタンス)
  • ユーザー、グループ作成

も行えますので、Systems Mangerを使用して管理してみてください。

作成したCloudFormationのスタックを消す(オプション)

今回使用したスタックでは、アクティベーションまた、ハイブリッドインスタンスが使用しているRoleは

DeletionPolicy: Retain

で定義していますので、CloudFormationスタックを消しても残ります。

カスタムリソースなどはアクティベーション後には不要になりますので、消していただいて問題ありません。

逆に、アクティベーションまた、ハイブリッドインスタンスは残るので手作業で消して頂く必要があります。 まとめて消したい場合は、DeletionPolicyの設定を削除するか変更してください

料金について

スタンダードであれば、現在(2022/06/12) 1000台/リージョンごと まで追加料金なしで登録できます!

インスタンスに対して、SSH接続、ポートフォワード等をする場合にアドバンスドインスタンスを有効化する必要があります。スタンダードとアドバンスドの切り替え の実施はアドバンスドからスタンダードに変更する際に時間がかかりますので、(1時間以下)、ポチポチすることはできませんが、アドバンスドに変更する操作はすぐに行えますので、通常はスタンダード、インスタンスに接続したいというときにアドバンスドに変更する運用の方がリーズナブルかと思います。

https://aws.amazon.com/jp/systems-manager/pricing/#On-Premises_Instance_Management

オンプレミスインスタンスティア(アカウントレベルおよびリージョンレベルの設定) 料金
スタンダード 追加料金なし アカウントごとにリージョンあたり最大 1,000 までの制限
アドバンスド アドバンスドオンプレミスインスタンスごとに時間あたり 0.00695 USD 無料利用枠なし

まとめ

管理対象のインスタンスにログインすることができれば、登録まで5分程度で行けます!

IoTなどで初期化スクリプトなどで組み込めばもっと早いかもしれません。

ロールや、記述などインスタンス毎に別々にしたかったのでサクッといけるようにしてみました。

若干、アクティベーションコードの発行と、アクティベーションの2ステップだと1撃じゃないなと言う気もしなくもないですがご容赦ください。

アカウント迷子のraspiなどが1匹でも少なくなると嬉しいです。

参考情報

ハイブリッド環境で AWS Systems Manager を設定する

CreateActivation API

テンプレート

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  SsmActivationDescription:
    Type: String
    Default: 'My Hybrid Instanse Description'
  MyHybridInstanceName:
    Type: String
    Default: my-hybrid-instance
  MyHybridInstanceRoleName:
    Type: String
    Default: my-hybrid-instance-role
Resources:
  MyHybridInstanceRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      Description: Provides permissions for on-premises machines
      RoleName: !Ref MyHybridInstanceRoleName 
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: ssm.amazonaws.com
            Action: sts:AssumeRole
            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
        - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
      Policies:
        - PolicyName: KmsDecrypt
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - kms:Decrypt
                Resource: !Sub 'arn:aws:kms:*:${AWS::AccountId}:key/*'

  SsmActivation:
    Type: 'Custom::SsmActivation'
    DeletionPolicy: Retain
    Properties:
      ServiceToken: !GetAtt SsmActivationLambdaFunction.Arn
      Id: !Sub
        - SsmActivation-${UniqueId}
        - UniqueId: !Select [0, !Split ['-', !Select [2, !Split [/, !Ref 'AWS::StackId']]]]
      Description: !Ref SsmActivationDescription
      InstanceName: !Ref MyHybridInstanceName
      IamRole: !Ref MyHybridInstanceRole
      LambdaArn: !GetAtt SsmActivationLambdaFunction.Arn

  LambdaIAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'iam:PassRole'
                  - 'ssm:CreateActivation'
                  - 'ssm:DeleteActivation'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: 'arn:aws:logs:*:*:*'

  SsmActivationLambdaFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Handler: index.lambda_handler
      Role: !GetAtt LambdaIAMRole.Arn
      Code:
        ZipFile: |
          import json
          import boto3
          import cfnresponse

          SUCCESS = "SUCCESS"
          FAILED = "FAILED"

          print('Loading function')
          ssm = boto3.client('ssm')

          def lambda_handler(event, context):
              print("Received event: " + json.dumps(event, indent=2))
              responseData = {}
              physicalResourceId = None
              try:
                  print("Request Type:",event['RequestType'])
                  if event['RequestType'] == 'Delete':
                      activation_id = event['PhysicalResourceId']
                      delete_activation(activation_id)
                      print("Sending response to custom resource after Delete")
                  elif event['RequestType'] == 'Create':
                      description = event['ResourceProperties']['Description']                      
                      instance_name = event['ResourceProperties']['InstanceName']                      
                      iam_role = event['ResourceProperties']['IamRole']                      
                      responseData = create_activation(description, instance_name, iam_role)
                      physicalResourceId = responseData['ActivationId']
                      print("Sending response to custom resource")

                  responseStatus = 'SUCCESS'
              except Exception as e:
                  print('Failed to process:', e)
                  responseStatus = 'FAILED'
                  responseData = {'Failure': 'Something bad happened.'}
              cfnresponse.send(event, context, responseStatus, responseData, physicalResourceId, True)

          def create_activation(description, instance_name, iam_role):
              response = ssm.create_activation(
                Description = description,
                DefaultInstanceName = instance_name,
                IamRole = iam_role
              )
              ## print(response)

              print("Create request completed....")

              return response
            
          def delete_activation(activation_id):

              ssm.delete_activation(
                ActivationId = activation_id
              )

              print("Delete request completed....")
      Runtime: python3.9
      Timeout: 60
Outputs:
  ActivationId:
    Value: !GetAtt 'SsmActivation.ActivationId'
  ActivationCode:
    Value: !GetAtt 'SsmActivation.ActivationCode'