Amazon Web Services ブログ

Amazon CodeWhisperer による生成 AI を活用した Amazon CloudWatch カスタムダッシュボードおよびカスタムウィジェットの作成

オブザーバビリティ(可観測性)とは、システムで何が起こっているかをどれだけ把握できるかを表します。多くの場合、メトリクス、ログ、トレースを収集するために計装することで実現します。運用体験の向上や、業務目標の達成を実現するには、システムがどのように動作しているかを理解する必要があります。これを実現するために、多くのお客様がリアルタイムのモニタリング、アラートとアラーム、カスタマイズ可能なダッシュボードの利用や、ビジネスの意思決定を支援を目的としたメトリクスの可視化を行うために、 Amazon CloudWatch を利用しています。組織では、カスタムパラメータを使用して任意の AWS Lambda 関数を呼び出すことができる CloudWatch ダッシュボードウィジェットを利用することが可能です。これにより、ダッシュボード上でデータを分析、および表示できる可能性が大きく広がります。しかし、組織内にこれらの関数を作成する専門知識がない場合はどうすれば良いでしょうか。ここで、 Amazon CodeWhisperer がこれらの関数の作成を支援するコード提案を行うことで、オブザーバビリティの向上に役立ちます。

Amazon CloudWatch ダッシュボードを使用すると、 AWS 環境全体のさまざまなメトリクスとリソースを一元的にモニタリングすることができるようになります。これにより、システムや組織全体でのアプリケーションの健全性とパフォーマンスを把握することができます。カスタムウィジェットにより、これらのダッシュボードの柔軟性が大幅に向上し、高度なレベルの監視と可視化が可能になります。 Amazon CodeWhisperer は、統合開発環境 (IDE) でリアルタイムに 1 行または関数コード全体の提案を生成することができる AI サービスです。これにより、ソフトウェアの構築を迅速に行うことができます。

この投稿では、 CodeWhisperer を利用して、カスタムウィジェット、 CloudWatch ダッシュボードの開発およびデプロイに使用されるコードの作成を支援する方法を示します。 またこの投稿では AWS Cloud Development Kit (CDK) を使用します。CDK は、近代的なプログラミング言語を使用してインフラストラクチャをコードとして定義し、 AWS CloudFormation を通じてデプロイするためのオープンソースのソフトウェア開発フレームワークです。

ソリューション全体像

このソリューションでは、 Amazon CodeWhisperer を使用して、 CDK コードの記述に関するコード提案を行います。 AWS CDK を使用して、さまざまなタイプの 3 つのウィジェットを持つダッシュボードをコーディングおよびデプロイします。 例では、テキストウィジェットと 2 つのカスタムウィジェットの作成方法を示します。 以下の図は、 CDK を介して作成されるリソースの概要を示しています。

図1. CDK によって作成される Amazon CloudWatch ダッシュボードアーキテクチャ

この投稿のすべての手順に沿って操作を行うと、CloudWatch ダッシュボードは次のようになります。2 つのカスタムウィジェットと 1 つのテキストウィジェットが含まれます。

図2. CloudWatch テキストウィジェット

この投稿全体を通して行うことは以下のとおりです。

前提条件

この投稿に沿って作業を進める場合、ローカルマシン上の統合開発環境 (IDE) で作業を行なっていきます。 CodeWhisperer は、 Visual Studio Code (VS Code) 、 PyCharm などの JetBrains 製品など、複数の IDE で動作します。この記事では VS Code を使用します。他の IDE の使用手順は、 Amazon CodeWhisperer のドキュメントで確認できます。
また、以下についても必要になります。

ビルダー ID を使用するように IDE を設定

AWS Builder ID を使用して、 VS Code で CodeWhisperer を設定しましょう。AWS Builder ID は、 AWS 上で開発するすべてのユーザーのための新しい個人プロファイルです。

  1. まず最初に、AWS Toolkit がインストールされていることを確認する必要があります。 VS Code IDE の左側のパネルで [Extensions] に移動します。検索バーに「AWS Toolkit」と入力し、以下のように拡張機能をインストールしてください。AWS Extension set up in VS Code
    図3. VS CodeでのAWS拡張機能の設定
  2. これで、左側のパネルに AWS Toolkit アイコンが表示されるはずです。 Visual Studio Code の AWS Toolkit で、[Developer tools] の [CodeWhisperer] を選択し、 [Start] をクリックします。すると、 VS Code の上部にドロップダウンメニューが表示されます。
    Start CodeWhisperer from developer tools図4. CodeWhispererの開始
  3. ドロップダウンメニューから、 [Use a personal email to sign up and sign in with AWS Builder ID] を選択します。
    Choose to use personal email図5. 個人メールアドレスの設定
  4. [Copy Code for AWS Builder ID] のプロンプトが表示されたら、 [Copy Code] と [Proceed] を選択します。
  5. [Do you want Code to open the external website?] のプロンプトが表示されたら、 [Open] を選択します。
  6. ブラウザタブが開き、 [Authorize request] ウィンドウが表示されます。コードはすでにクリップボードにコピーされているはずです。それを貼り付けて、 [Next] を選択します。
  7. [Create AWS Builder ID] ページがブラウザタブで開きます。メールアドレスを入力し、 [Next] を選択します。
  8. [Your name] のフィールドが表示されるので、名前を入力し、 [Next] を選択します。
  9. 入力したメールアドレスに確認コードが届きます。確認画面で届いたコードを入力し、 [Verify] を選択します。
  10. [Choose your password] 画面で、パスワードを入力し、確認入力してから、 [Create AWS Builder ID] を選択します。
  11. ブラウザタブが開き、 Visual Studio Code の AWS Toolkit にデータにアクセスすることを許可するよう求めるメッセージが表示されます。 [Allow] を選択します。
  12. VS Code に戻ります。すでに IAM を使用して他の AWS ツールに認証している場合は、すべての AWS サービスで Builder ID を使用するかどうかを尋ねられます。状況に適したオプションを選択します。

AWS CLI 、 AWS CDK 、 AWS IAM の設定

このセクションでは、AWS CLI、CDK、CDKのフォルダ構造を設定します。

  1. AWS CLIを設定するには、「AWS CLI を設定する」の手順に従ってください。
  2. 新しいフォルダを作成し、 sample1 と名前を付けます。このフォルダで CDK プロジェクトを作成します。 sample1 フォルダにディレクトリを変更します。
    cd sample1
  3. cdk をブートストラップします。
    cdk bootstrap aws://< アカウント番号 >/< リージョン名 >
  4. cdk python 環境を作成します。
    cdk init --language python

    これにより、sample1 フォルダに必要な python cdk ファイルが作成されます。
  5. venv をアクティベートします。
    source .venv/bin/activate
  6. 必要なパッケージをインストールします。
    pip install -r requirements.txt

Amazon CodeWhisperer を使用したコードの作成

このセクションでは、 CodeWhisperer を使用して CDK コードを記述する方法を説明します。
CDK を使用して、次の 6 つのリソースを作成する方法を説明していきます。

  • 2 つの Lambda 関数
    • 1 つの Lambda 関数は、ユーザー入力を受け取り、対応する出力を生成します。
    • もう 1 つのLambda関数は、 S3 バケットから HTML を取得します。
  • 3 つの CloudWatch ウィジェット
    • 1 つ目の Lambda 関数と統合するカスタムウィジェット。ユーザー入力を受け付け、 Lambda 関数からデータを読み取って出力を提供します。
    • 2 つ目の Lambda 関数と統合するカスタムウィジェット。 S3 バケットから HTML ファイルを読み取ります。
    • 「Hello World」と表示するテキストウィジェット
  • 1 つの CloudWatch ダッシュボード

Visual Studio Codeの利用

  1. 左上の [File] メニューから、 [Open Folder] を選択します。(ステップ 2 で作成した sample1 フォルダを選択します。)
  2. assets という名前の新しいフォルダを作成し、 HelloWorld.html という名前の html ファイルを作成します。
    <!DOCTYPE html>
     <html>
     <body>
     <h1>Hello!! This is an HTML file from S3 bucket</h1>
     </body>
     </html>
     

    このファイルは S3 バケットにアップロードされ、 Lambda 関数によって読み込まれます。

  3. 再び sample1 フォルダに移動し、 sample1 という名前のサブフォルダを探します。
  4. そのサブフォルダにあるスタックファイルを編集します。
    ・import 文を追加します(入力時に CodeWhisperer が自動補完を支援します)

    from aws_cdk import (
    
    aws_lambda, Stack, App, Duration, aws_iam, aws_cloudwatch, aws_s3, RemovalPolicy, aws_s3_deployment
    
    )
  5. 必要なモジュールをインポートしたら、次の行を探します。# The code that defines your stack goes here

    この行を削除し、スタックコードを記述します。

  6. CodeWhisperer にコメントを入力して、 AWS CDK を使用した最初の Lambda 関数の作成を提案させます。# define lambda function that has inline code

    入力を開始すると、 CodeWhisperer が提案を開始します。 Lambda 関数の作成に関するヘルプが表示されたら、 Lambda 関数の CDK コードを次のように記述します。

    	    # define lambda function that has inline code
            lambda_function = aws_lambda.Function(self, 'HelloWorldFunction',
                code=aws_lambda.Code.from_inline("""
    
    def lambda_handler(event, context):
        showme = event['widgetContext']['forms']['all'].get('petType') 
        # create a 2D array for pets and count
        data = [['puppy','10'],['bunny','5'],['kitten','6']] 
        rowdata = ""
        for row in data:
            if (showme and showme==row[0]):
            # assume only 2 elements in the data
            rowdata += f'''
                <tr>
                    <td>{row[0]}</td>
                    <td>{row[1]}</td>
                </tr>'''
            response = f'''
                <p>Which pet do you want to see? valid inputs: puppy/bunny/kitten
                    <form><input name="petType" value="{showme}" size="10"></form>
                </p> 
                <table>
                <tr>
                    <th>Pets</th>
                    <th>Count</th>
                </tr>
        {rowdata}
                </table>
                    <a class="btn btn-primary">Run query</a>
                    <cwdb-action action="call" endpoint="{context.invoked_function_arn}"></cwdb-action>
        '''
        return response
                    """
                ),
                description="CloudWatch Custom Widget sample: simple hello world",
                function_name=self.stack_name,
                handler='index.lambda_handler',
                memory_size=128,
                runtime=aws_lambda.Runtime.PYTHON_3_9,
                timeout=Duration.seconds(60)'
            )

    コードをカスタマイズするために入力を追加すると、 CodeWhisperer が提案を行うことに気づくでしょう。この例では、ペットの数の 2 次元配列を作成し、ユーザーに取得するデータを入力するよう求めています。ユースケースに基づいてデータを変更できます。

  7. 最初の CloudWatch カスタムウィジェットを作成するためのコメントの入力を開始します。このウィジェットでは、入力を要求し、出力として子犬の数を提供するコードを使用します。# define custom widget to display lambda function

    CodeWhisperer の提案を使用して、カスタムウィジェットを作成する CDK コードを記述します。

    	    # define custom widget to display lambda function
            my_custom_widget = aws_cloudwatch.CustomWidget(
            function_arn=lambda_function.function_arn,
            title="Hello, World!")
    
  8. CloudWatch のテキストウィジェットを作成するためのコメントの入力を開始します。# define a text widget that displays "Hello World"

    CodeWhisperer の提案を利用しながら、テキストウィジェットのコードを完成させます。

    	    # define a text widget that displays "Hello World"
            my_text_widget = aws_cloudwatch.TextWidget(markdown='# Hello World',
                width=6,
                height=2
            )
  9. assets フォルダのファイルをロードする S3 バケットを作成しましょう。
    	    # define parameter for s3 bucket name
            s3_bucket_name = f'helloworld-{self.account}'
            s3_file_name = 'HelloWorld.html'
    
            # define s3 bucket with account id as suffix for hello world with no public access
            s3_bucket = aws_s3.Bucket(self, "S3Bucket",
                versioned=False,
                    block_public_access=aws_s3.BlockPublicAccess.BLOCK_ALL,
                    encryption=aws_s3.BucketEncryption.KMS_MANAGED,
                    auto_delete_objects=True,
                    removal_policy=RemovalPolicy.DESTROY,
                    bucket_name=s3_bucket_name
            )
    
            # define s3 bucket deployment to deploy files under assets folder
            aws_s3_deployment.BucketDeployment(self, "S3BucketDeployment",
                                    sources=[aws_s3_deployment.Source.asset("assets")],
                                    destination_bucket=s3_bucket
    	)
  10. S3 バケットからファイルを読み取る第 2 の Lambda 関数を作成しましょう。関連する IAM ロールも作成します。
    	    # define IAM role for lambda that allows to read s3_file_name from s3 bucket with name s3_bucket_name
            s3GetObjectLambda_IAM_role = aws_iam.Role(self, "S3GetObjectLambda_IAM_role",
                assumed_by=aws_iam.ServicePrincipal("lambda.amazonaws.com"),
                inline_policies={
                    "S3GetObjectLambda_IAM_role_policy": aws_iam.PolicyDocument(
                        statements=[
                            aws_iam.PolicyStatement(
                                actions=["s3:GetObject"],
                                effect=aws_iam.Effect.ALLOW,
                                resources=[
                                    f'arn:aws:s3:::{s3_bucket_name}/{s3_file_name}'
                                ]
                            )
                        ]
                    )
                }
            )
  11. 2 つ目のカスタムウィジェットを作成しましょう。
    	    # define s3GetObject custom widget to display s3GetObjectlambda_function
            s3GetObject_custom_widget = aws_cloudwatch.CustomWidget(
                function_arn=s3GetObjectlambda_function.function_arn,
                width=6,
                height=4)
  12. 次に、作成した 3 つのウィジェットをホストする CloudWatch ダッシュボードを作成します。コメントで CodeWhisperer に提案させることで、ダッシュボードのコードを作成します。# define cloudwatch dashboard

    CodeWhisperer のコード提案が表示されたら、以前に作成したウィジェットを使用するダッシュボードのコードを完成させます。

    	    # define CloudWatch dashboard
            dashboard = aws_cloudwatch.Dashboard(
                self, 'HelloWorldDashboard',
                dashboard_name='HelloWorldDashboard'
            )
  13. 最後に、作成した 2 つのカスタムウィジェットと 1 つのテキストウィジェットをダッシュボードに追加します。
    	    # add the 3 widgets to the dashboard
            dashboard.add_widgets(my_custom_widget)
            dashboard.add_widgets(my_text_widget)
            dashboard.add_widgets(s3GetObject_custom_widget)

    コードは次の例のようになるはずです。

    from aws_cdk import (
        Stack, Duration, aws_lambda, aws_cloudwatch, aws_s3, RemovalPolicy, aws_iam, aws_s3_deployment
    )
    from constructs import Construct
    import os
    
    class Sample1Stack(Stack):
    
        def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
            super().__init__(scope, construct_id, **kwargs)
            
            cwd = os.getcwd()
            # define lambda function that has inline code
            lambda_function = aws_lambda.Function(self, 'HelloWorldFunction',
                code=aws_lambda.Code.from_inline(
                    """
    def lambda_handler(event, context):
        showme = event['widgetContext']['forms']['all'].get('petType') 
        # create a 2D array for pets and count
        data = [['puppy','10'],['bunny','5'],['kitten','6']] 
        rowdata = ""
        for row in data:
            if (showme and showme==row[0]):
                # assume only 2 elements in the data
                rowdata += f'''
                    <tr>
                        <td>{row[0]}</td>
                        <td>{row[1]}</td>
                    </tr>'''
        response = f'''
            <p>Which pet do you want to see? valid inputs: puppy/bunny/kitten
              <form><input name="petType" value="{showme}" size="10"></form>
            </p> 
            <table>
                <tr>
                    <th>Pets</th>
                    <th>Count</th>
                </tr>
                {rowdata}
            </table>
            <a class="btn btn-primary">Run query</a>
            <cwdb-action action="call" endpoint="{context.invoked_function_arn}"></cwdb-action>
            '''
        return response
    """
                ),
                # code=aws_lambda.Code.from_asset(os.path.join(cwd, "lambdafolder/package.zip"))
                description="CloudWatch Custom Widget sample: simple hello world",
                function_name=self.stack_name,
                handler='index.lambda_handler',
                memory_size=128,
                runtime=aws_lambda.Runtime.PYTHON_3_9,
                timeout=Duration.seconds(60)
            )
    
            # define a CustomWidget that uses the Lambda function
            my_custom_widget = aws_cloudwatch.CustomWidget(
                function_arn=lambda_function.function_arn,
                title="Hello, World!")
            
            # define a text widget that displays "Hello World"
            my_text_widget = aws_cloudwatch.TextWidget(markdown='Hello World',
                width=6,
                height=2
            )
    
            # define parameter for s3 bucket name
            s3_bucket_name = f'helloworld-{self.account}'
            s3_file_name = 'HelloWorld.html'
            # define s3 bucket with account id as suffix for hello world with no public access
            s3_bucket = aws_s3.Bucket(self, "S3Bucket",
                versioned=False,
                block_public_access=aws_s3.BlockPublicAccess.BLOCK_ALL,
                encryption=aws_s3.BucketEncryption.KMS_MANAGED,
                auto_delete_objects=True,
                removal_policy=RemovalPolicy.DESTROY,
                bucket_name=s3_bucket_name
            )
    
             # define s3 bucket deployment to deploy files under assets folder
            aws_s3_deployment.BucketDeployment(self, "S3BucketDeployment",
                                    sources=[aws_s3_deployment.Source.asset("assets")],
                                    destination_bucket=s3_bucket
            )      
    
            # define IAM role for lambda that allows to read s3_file_name from s3 bucket with name s3_bucket_name
            s3GetObjectLambda_IAM_role = aws_iam.Role(self, "S3GetObjectLambda_IAM_role",
                assumed_by=aws_iam.ServicePrincipal("lambda.amazonaws.com"),
                inline_policies={
                    "S3GetObjectLambda_IAM_role_policy": aws_iam.PolicyDocument(
                        statements=[
                            aws_iam.PolicyStatement(
                                actions=["s3:GetObject"],
                                effect=aws_iam.Effect.ALLOW,
                                resources=[
                                    f'arn:aws:s3:::{s3_bucket_name}/{s3_file_name}'
                                ]
                            )
                        ]
                    )
                }
            )
    
            # define lambda function S3GetObject
            s3GetObjectlambda_function = aws_lambda.Function(self, "S3GetObjectLambdaFunction",
                #inline code to read file from s3 bucket
                code=aws_lambda.Code.from_inline("""
    import boto3
    import json
    import os
    
    s3 = boto3.client('s3')
    def lambda_handler(event, context):
        #read environment variable S3_BUCKET_NAME
        bucket = os.environ['S3_BUCKET_NAME']
        key = os.environ['S3_FILE_NAME']
        response = s3.get_object(Bucket=bucket, Key=key)
        file_content = response['Body'].read().decode('utf-8')
    
        return file_content
                    """ ),
                runtime=aws_lambda.Runtime.PYTHON_3_9,
                handler="index.lambda_handler",
                timeout=Duration.seconds(60),
                memory_size=128,
                description="Lambda function that reads file from S3 bucket and returns file content",
                # define IAM role for S3GetObject
                role=s3GetObjectLambda_IAM_role,
                #environment variables
                environment={
                    'S3_BUCKET_NAME': s3_bucket_name,
                    'S3_FILE_NAME': s3_file_name
                },
                # define function name as s3GetObject
                function_name=f's3GetObject-{self.stack_name}'
            )
    
            # define s3GetObject custom widget to display s3GetObjectlambda_function
            s3GetObject_custom_widget = aws_cloudwatch.CustomWidget(
                function_arn=s3GetObjectlambda_function.function_arn,
                width=6,
                height=4,
                title="S3 Get Object"
            )
    
            # define a CloudWatch dashboard
            dashboard = aws_cloudwatch.Dashboard(
                self, 'HelloWorldDashboard',
                dashboard_name='HelloWorldDashboard'
            )
    
            # add the 3 widgets to the dashboard
            dashboard.add_widgets(my_custom_widget)
            dashboard.add_widgets(my_text_widget)
            dashboard.add_widgets(s3GetObject_custom_widget)
  14. コードを検証します。
    Visual Studio Code のターミナルウィンドウで次のコマンドを実行します。
    cdk ls
  15. コードをデプロイします。
    Visual Studio Code のターミナルウィンドウで次のコマンドを実行します。
    cdk deploy
  16. これで次のリソースがデプロイされているはずです。
    • 2 つの Lambda 関数
    • 3 つのウィジェットを持つ CloudWatch ダッシュボード
      • 1 つのテキストウィジェット
      • Lambda に接続された 2 つの CustomWidget
        • S3 の HTML ファイルから読み取れる 1 つのカスタムウィジェット
        • ユーザー入力を要求し、 Lambda から関連値を取得する 1 つのカスタムウィジェット

他のウィジェットの作成方法はこちらを参考にしてください。

環境の削除

今後課金されないようにするには、Visual Studio Code のターミナルウィンドウで次のコマンドを実行してリソースを削除します。
cdk destroy

まとめ

この投稿では、 Amazon CodeWhisperer を使用して CloudWatch ダッシュボードを作成する CDK コードを記述する方法を学びました。ご覧のとおり、 CodeWhisperer は開発者の生産性を向上させるだけでなく、より速く成果を出し、ソフトウェア開発を加速し、開発労力を削減する提案を行い、アイデア創出や複雑な問題解決のための時間を確保できるなど、多くのメリットがあります。詳細は CodeWhisperer のドキュメントをご覧ください。

本記事は、「Leverage generative AI to create custom dashboard widgets in Amazon CloudWatch using Amazon CodeWhisperer」を翻訳したものです。翻訳は ソリューションアーキテクト津郷が担当しました。