TECH PLAY

SCSKクラウドソリューション

SCSKクラウドソリューション の技術ブログ

1141

本記事は 夏休みクラウド自由研究2025 8/22付の記事です 。 皆さまこんにちは。お元気ですか? 最近、暑すぎて頭が働かなくなっちゃいますよね。 でも、頭が働かなくてもCloudFormationテンプレートを作成したいことって、往々にしてありますよね。 そういうわけで、Amazon Q Developer CLIを使って頭を使わずにスタックを作成してみようというのが、今回の自由研究のテーマです。 Amazon Q Developer CLIが画像入力をサポート 2025年5月のアップデートにより、Amazon Q Developer CLIに「画像サポート機能」が実装され、 画像ファイルをターミナルから直接入力できるようになりました。JPEG、PNG、WEBP、GIFの形式をサポートしており、また、1回のリクエストで最大10枚の画像をアップロード可能です。 この機能を活用することで、視覚的なアイデアを文書やコードといった成果物に落としこむことが可能になります。 以下にご紹介する公式ブログでは、アーキテクチャ図からのIaC作成、ER図からのDBスキーマ作成、手描き図からの設計書作成、スクリーンショットからUIのモックアップ作成という4つのユースケースが紹介されています。 Amazon Q Developer CLI がターミナルでの画像入力をサポート | Amazon Web Services この記事では、Amazon Q Developer Command Line Interface (CLI) の画像サポート機能が開発プロセスをどのように変革するかをご紹介します。Q Developer CLI は最近 (バージョン 1.1... aws.amazon.com   実際にやってみた そこで今回は、手描きのアーキテクチャ図からYAML形式のCloudFormationテンプレートを出力させ、想定した振る舞いをするかを確かめてみます。なお、以降は読みやすさのため(&親しみを込めて) Amazon Q Developer CLIのことはQdevくんと呼ぶこととします。 準備:手書きのアーキテクチャ図 まずはQdevくんに読み込ませるサービス構成の絵を描きます。今回はこんなのを書いてみました(日本語を正しく読み込むか分からなかったので保険で英語にしました)。処理の流れはざっくり以下の通りです。 S3にファイルをアップロード→イベント通知がEventBridgeへ渡される ファイルがCSV形式ならSNSを介してメールで通知。また、常にLambdaで処理してメタデータ情報をDynamoDBに格納する。 (あくまで構成図通りに一連のサービスを組み立てられるかのテストを目的としているため、上の構成が有用/望ましい構成であるかは一旦置いておきます。) Amazon Q Devにテンプレートを生成させる 準備ができたら「test-diagram.jpeg」として保存し、後はQdevくんにお任せしましょう。 🤖 You are chatting with claude-4-sonnet > test-diagram.jpegを実現するためのCloudFormationテンプレートを作成して。図に載っていない詳細についてはAWSの ベストプラクティスに従うこととします。それでもなお確認が必要であれば、ユーザに質問してください > まず、test-diagram.jpegファイルを確認させていただきます。 🛠️ Using tool: fs_read (trusted) ⋮ ● Reading images: ■■■■/qdev/test-diagram.jpeg ✓ Successfully read image ⋮ ● Completed in 0.0s > 図を確認しました。この「Uploaded data process flow」を実現するCloudFormationテンプレートを作成します。 フローは以下のようになっています: 1. ユーザーがS3にCSVファイルをアップロード 2. S3イベントがEventBridgeをトリガー 3. EventBridgeがSNSに通知 4. SNSからEmailで通知 5. EventBridgeが常にLambda関数もトリガー 6. Lambda関数がメタデータをDynamoDBに保存 CloudFormationテンプレートを作成します: (略) ⋮ ↳ Purpose: データ処理フローのCloudFormationテンプレートを作成 Allow this action? Use 't' to trust (always allow) this tool for the session. [y/n/t]: > y Creating: ■■■■/qdev/data-process-flow.yaml ⋮ ● Completed in 0.2s > CloudFormationテンプレートを作成しました。このテンプレートは図に示されたデータ処理フローを実現します。 ## 主な機能 1. S3バケット: CSVファイルのアップロード先 2. EventBridge: S3イベントをルーティング 3. SNS: CSVファイルアップロード時のEmail通知 4. Lambda関数: 全ファイルのメタデータをDynamoDBに保存 5. DynamoDB: ファイルメタデータの永続化 ## AWSベストプラクティスの適用 • S3バケットの暗号化とバージョニング有効化 • パブリックアクセスブロック設定 • IAMロールの最小権限原則 • DynamoDBのポイントインタイム復旧有効化 • Lambda関数のタイムアウト設定 • リソース名の一意性確保 ## デプロイ方法 bash aws cloudformation create-stack \ --stack-name data-process-flow \ --template-body file://data-process-flow.yaml \ --parameters ParameterKey=NotificationEmail,ParameterValue=your-email@example.com \ --capabilities CAPABILITY_IAM テンプレートをデプロイする前に、NotificationEmailパラメータに実際のメールアドレスを指定してください。 デプロイ後、SNSサブスクリプションの確認メールが送信されます。 何か追加の設定や修正が必要でしたらお知らせください。 作成されたdata-process-flow.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Uploaded data process flow infrastructure' Parameters: NotificationEmail: Type: String Description: Email address for notifications AllowedPattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$ ConstraintDescription: Must be a valid email address Resources: # S3 Bucket for data uploads DataUploadBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-data-upload-${AWS::AccountId}' NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled # EventBridge Custom Bus DataProcessEventBus: Type: AWS::Events::EventBus Properties: Name: !Sub '${AWS::StackName}-data-process-bus' # SNS Topic for notifications DataProcessNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub '${AWS::StackName}-data-process-notifications' DisplayName: Data Process Notifications # SNS Subscription for email notifications EmailSubscription: Type: AWS::SNS::Subscription Properties: Protocol: email TopicArn: !Ref DataProcessNotificationTopic Endpoint: !Ref NotificationEmail # DynamoDB Table for metadata storage MetadataTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${AWS::StackName}-file-metadata' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: fileKey AttributeType: S - AttributeName: uploadTimestamp AttributeType: S KeySchema: - AttributeName: fileKey KeyType: HASH - AttributeName: uploadTimestamp KeyType: RANGE StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true # IAM Role for Lambda function LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:GetItem - dynamodb:UpdateItem Resource: !GetAtt MetadataTable.Arn - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket}/*' # Lambda function for processing metadata MetadataProcessorFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-metadata-processor' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: METADATA_TABLE: !Ref MetadataTable Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') table = dynamodb.Table(os.environ['METADATA_TABLE']) def lambda_handler(event, context): print(f"Received event: {json.dumps(event)}") try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Prepare metadata for DynamoDB metadata = { 'fileKey': object_key, 'uploadTimestamp': datetime.utcnow().isoformat(), 'bucketName': bucket_name, 'fileSize': response['ContentLength'], 'contentType': response.get('ContentType', 'unknown'), 'lastModified': response['LastModified'].isoformat(), 'etag': response['ETag'].strip('"'), 'processedAt': datetime.utcnow().isoformat() } # Store metadata in DynamoDB table.put_item(Item=metadata) print(f"Successfully stored metadata for {object_key}") return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Metadata processed successfully', 'fileKey': object_key }) } except Exception as e: print(f"Error processing metadata: {str(e)}") raise e # EventBridge Rule for S3 CSV uploads to SNS S3ToSNSRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-csv-to-sns' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket object: key: - suffix: .csv State: ENABLED Targets: - Arn: !Ref DataProcessNotificationTopic Id: SNSTarget RoleArn: !GetAtt EventBridgeToSNSRole.Arn # EventBridge Rule for S3 uploads to Lambda (always) S3ToLambdaRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-to-lambda' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt MetadataProcessorFunction.Arn Id: LambdaTarget # IAM Role for EventBridge to SNS EventBridgeToSNSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda permission for EventBridge LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref MetadataProcessorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToLambdaRule.Arn # EventBridge Rule to route S3 events to custom bus S3EventRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-event-router' EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt DataProcessEventBus.Arn Id: CustomBusTarget RoleArn: !GetAtt EventBridgeRouterRole.Arn # IAM Role for EventBridge routing EventBridgeRouterRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - events:PutEvents Resource: !GetAtt DataProcessEventBus.Arn Outputs: S3BucketName: Description: Name of the S3 bucket for data uploads Value: !Ref DataUploadBucket Export: Name: !Sub '${AWS::StackName}-S3Bucket' SNSTopicArn: Description: ARN of the SNS topic for notifications Value: !Ref DataProcessNotificationTopic Export: Name: !Sub '${AWS::StackName}-SNSTopic' DynamoDBTableName: Description: Name of the DynamoDB table for metadata Value: !Ref MetadataTable Export: Name: !Sub '${AWS::StackName}-MetadataTable' LambdaFunctionName: Description: Name of the Lambda function for metadata processing Value: !Ref MetadataProcessorFunction Export: Name: !Sub '${AWS::StackName}-LambdaFunction' EventBusName: Description: Name of the custom EventBridge bus Value: !Ref DataProcessEventBus Export: Name: !Sub '${AWS::StackName}-EventBus' ほとんど追加の説明を渡さずに依頼を投げたので、テンプレートを作成する前に何回か確認の往復があることを予想していたのですが、意外とあっさり作成してくれました。 - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket.Arn}/*' (よく見るとLambdaの実行ロールの中で s3:GetObjectMetadata という存在しないIAMアクションを許可してしまっているようですが、その辺はご愛嬌としておきましょう。) Amazon S3 のアクション、リソース、条件キー - サービス認可リファレンス IAM ポリシーで Amazon S3 へのアクセスを管理するために使用できるサービス固有のリソース、アクション、条件キーの一覧を示します。 docs.aws.amazon.com 生成されたテンプレートからスタックを起動してみる テンプレートができたので次はこれをCloudFormationにアップロードします。 通知先のメールアドレス部分がパラメータになっているので、自分のメールアドレスを入力します。あとは「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」にチェックを入れること以外はデフォルトの設定のまま作成に進み、しばらく待ちます。 ……スタックの作成を待っていたら、途中でロールバックしてしまいました。見ると、次のようなエラーが出ています。 リソース LambdaExecutionRole は CREATE_FAILED 状態です。 Resource handler returned message: “Resource q-image-stack-20250822-data-upload-111111111111/* must be in ARN format or “*”.(Service: Iam, Status Code: 400, Request ID: ****-****-****-****-************) (SDK Attempt Count: 1) (RequestToken: ****-****-****-****-************, HandlerErrorCode: InvalidRequest) IAMリソースのARN指定が原因のようですが、今回の自由研究はなるべく頭を使わないことが目標なので、もちろんこんなエラーは読みません。全部Qdevくんに丸投げしましょう。 🤖 You are chatting with claude-4-sonnet > 作ってもらったdata-process-flow.yamlを展開したら次のようなエラーが出てCREATE_FAILEDになりました。 Resource handler returned message: "Resource q-image-stack-20250822-data-upload-111111111111/* must be in ARN format or "*".(Service: Iam, Status Code: 400, Request ID: ****-****-****-****-************) (SDK Attempt Count: 1) (RequestToken: ****-****-****-****-************, HandlerErrorCode: InvalidRequest) 適切にテンプレートファイルを修正してくれる? > エラーメッセージを見ると、IAMポリシーでリソースARNの形式が正しくないことが原因です。S3バケットのリソース指定で /* を使う場合は、 完全なARN形式にする必要があります。 まず現在のテンプレートファイルを確認して、適切に修正しましょう。 > 問題を特定しました。LambdaExecutionRoleのS3Accessポリシーで、リソースARNの形式が正しくありません。!Sub '${DataUploadBucket}/*' は正しいARN形式ではありません。S3バケットのARNは arn:aws:s3:::bucket-name/* の形式である必要があります。 テンプレートを修正します: ⋮ ● Completed in 0.0s > 修正が完了しました。主な変更点は以下の通りです: 1. S3リソースARNの修正: !Sub '${DataUploadBucket}/*' を !Sub '${DataUploadBucket.Arn}/*' に変更しました。 これにより、正しいS3バケットのARN形式(arn:aws:s3:::bucket-name/*)が生成されます。 2. ListBucket権限の追加: S3バケット自体への s3:ListBucket 権限も追加しました。これはLambda関数がバケット内の オブジェクトにアクセスする際に必要になる場合があります。 これで、IAMポリシーのリソースARNが正しい形式になり、CloudFormationスタックのデプロイが成功するはずです。 修正されたテンプレートを使って再度デプロイしてみてください。 修正されたdata-process-flow.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Uploaded data process flow infrastructure' Parameters: NotificationEmail: Type: String Description: Email address for notifications AllowedPattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$ ConstraintDescription: Must be a valid email address Resources: # S3 Bucket for data uploads DataUploadBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-data-upload-${AWS::AccountId}' NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled # EventBridge Custom Bus DataProcessEventBus: Type: AWS::Events::EventBus Properties: Name: !Sub '${AWS::StackName}-data-process-bus' # SNS Topic for notifications DataProcessNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub '${AWS::StackName}-data-process-notifications' DisplayName: Data Process Notifications # SNS Subscription for email notifications EmailSubscription: Type: AWS::SNS::Subscription Properties: Protocol: email TopicArn: !Ref DataProcessNotificationTopic Endpoint: !Ref NotificationEmail # DynamoDB Table for metadata storage MetadataTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${AWS::StackName}-file-metadata' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: fileKey AttributeType: S - AttributeName: uploadTimestamp AttributeType: S KeySchema: - AttributeName: fileKey KeyType: HASH - AttributeName: uploadTimestamp KeyType: RANGE StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true # IAM Role for Lambda function LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:GetItem - dynamodb:UpdateItem Resource: !GetAtt MetadataTable.Arn - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket.Arn}/*' - Effect: Allow Action: - s3:ListBucket Resource: !GetAtt DataUploadBucket.Arn # Lambda function for processing metadata MetadataProcessorFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-metadata-processor' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: METADATA_TABLE: !Ref MetadataTable Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') table = dynamodb.Table(os.environ['METADATA_TABLE']) def lambda_handler(event, context): print(f"Received event: {json.dumps(event)}") try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Prepare metadata for DynamoDB metadata = { 'fileKey': object_key, 'uploadTimestamp': datetime.utcnow().isoformat(), 'bucketName': bucket_name, 'fileSize': response['ContentLength'], 'contentType': response.get('ContentType', 'unknown'), 'lastModified': response['LastModified'].isoformat(), 'etag': response['ETag'].strip('"'), 'processedAt': datetime.utcnow().isoformat() } # Store metadata in DynamoDB table.put_item(Item=metadata) print(f"Successfully stored metadata for {object_key}") return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Metadata processed successfully', 'fileKey': object_key }) } except Exception as e: print(f"Error processing metadata: {str(e)}") raise e # EventBridge Rule for S3 CSV uploads to SNS S3ToSNSRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-csv-to-sns' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket object: key: - suffix: .csv State: ENABLED Targets: - Arn: !Ref DataProcessNotificationTopic Id: SNSTarget RoleArn: !GetAtt EventBridgeToSNSRole.Arn # EventBridge Rule for S3 uploads to Lambda (always) S3ToLambdaRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-to-lambda' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt MetadataProcessorFunction.Arn Id: LambdaTarget # IAM Role for EventBridge to SNS EventBridgeToSNSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda permission for EventBridge LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref MetadataProcessorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToLambdaRule.Arn # EventBridge Rule to route S3 events to custom bus S3EventRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-event-router' EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt DataProcessEventBus.Arn Id: CustomBusTarget RoleArn: !GetAtt EventBridgeRouterRole.Arn # IAM Role for EventBridge routing EventBridgeRouterRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - events:PutEvents Resource: !GetAtt DataProcessEventBus.Arn Outputs: S3BucketName: Description: Name of the S3 bucket for data uploads Value: !Ref DataUploadBucket Export: Name: !Sub '${AWS::StackName}-S3Bucket' SNSTopicArn: Description: ARN of the SNS topic for notifications Value: !Ref DataProcessNotificationTopic Export: Name: !Sub '${AWS::StackName}-SNSTopic' DynamoDBTableName: Description: Name of the DynamoDB table for metadata Value: !Ref MetadataTable Export: Name: !Sub '${AWS::StackName}-MetadataTable' LambdaFunctionName: Description: Name of the Lambda function for metadata processing Value: !Ref MetadataProcessorFunction Export: Name: !Sub '${AWS::StackName}-LambdaFunction' EventBusName: Description: Name of the custom EventBridge bus Value: !Ref DataProcessEventBus Export: Name: !Sub '${AWS::StackName}-EventBus' 再度アップロードして今度は無事、デプロイまで完了することができました。SNSから登録したアドレスにメールが来るので、承認をしておきます。 テスト:データの作成もAmazon Qに任せよう それでは、ちゃんと動くかテストしてみましょう。ダミーデータが必要になりますが、むろん、これもQdevくんに作ってもらいます。 > data-process-flow.yamlをテストしたい。testフォルダを作って、S3にアップロードするファイル(CSV, HTML, TXT)を作って > data-process-flow.yamlファイルをテストするために、testフォルダを作成してテスト用のファイルを準備しましょう。 (略) > CloudFormationテンプレートの内容を確認しました。このテンプレートはS3にファイルがアップロードされると、EventBridge経由 でLambda関数とSNS(CSVファイルの場合)に通知する仕組みです。 testフォルダを作成して、CSV、HTML、TXTファイルのサンプルを作成しましょう。 (略) > 完璧です!testフォルダに以下のテストファイルを作成しました: こんな感じのファイルを6種類作ってくれました。 作成されたバケットにこれらのファイルをアップロードすると、程なくして以下のようなメールが2通届きました。確かに、CSVファイルのそれぞれについてアップロード通知を送ってきてくれています。 AWS Notification Message From: Data Process Notifications <no-reply@sns.amazonaws.com> {“version”:”0″,”id”:”xxx”,”detail-type”:”Object Created”,”source”:”aws.s3″,”account”:”xxx”,”time”:”2025-08-16T05:34:26Z”,”region”:”ap-northeast-1″,”resources”:[“arn:aws:s3:::q-image-data-flow-data-upload-111111111111”],”detail”:{“version”:”0″,”bucket”:{“name”:”q-image-data-flow-data-upload-111111111111”},”object”:{“key”:”sample_data.csv”,”size”:610,”etag”:”xxx”,”version-id”:”xxx”,”sequencer”:”xxx”},”request-id”:”xxx”,”requester”:”xxx”,”source-ip-address”:”xxx”,”reason”:”PutObject”}}’ また、Dynamoを見ると画像のようにすべてのファイルについてメタデータを保存してくれていました。最初の図で期待していた処理がちゃんとなされていることが確認できます。 おまけ せっかくなのでもう少し遊んでみましょう。 機能の追加 ただ、上のメール通知はJSON形式のメッセージがそのままメールの本文に書かれているだけで、正直可読性は低いです。さらにおねだりをしてフォーマットされたメールが飛ぶようにしてもらいましょう。 > data-process-flow.yamlを更新して、メール送信される通知の本文の可読性を上げてくれる? > data-process-flow.yamlを更新して、SNS通知の本文をより読みやすくするために、Lambda関数を追加してメール通知をフォーマットしましょう。 (略) > 完璧です!data-process-flow.yamlを更新して、メール通知の可読性を大幅に向上させました。 機能を追加したdata-process-flow.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Uploaded data process flow infrastructure' Parameters: NotificationEmail: Type: String Description: Email address for notifications AllowedPattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$ ConstraintDescription: Must be a valid email address Resources: # S3 Bucket for data uploads DataUploadBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-data-upload-${AWS::AccountId}' NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled # EventBridge Custom Bus DataProcessEventBus: Type: AWS::Events::EventBus Properties: Name: !Sub '${AWS::StackName}-data-process-bus' # SNS Topic for notifications DataProcessNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub '${AWS::StackName}-data-process-notifications' DisplayName: Data Process Notifications # SNS Subscription for email notifications EmailSubscription: Type: AWS::SNS::Subscription Properties: Protocol: email TopicArn: !Ref DataProcessNotificationTopic Endpoint: !Ref NotificationEmail # DynamoDB Table for metadata storage MetadataTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${AWS::StackName}-file-metadata' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: fileKey AttributeType: S - AttributeName: uploadTimestamp AttributeType: S KeySchema: - AttributeName: fileKey KeyType: HASH - AttributeName: uploadTimestamp KeyType: RANGE StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true # IAM Role for Lambda function LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:GetItem - dynamodb:UpdateItem Resource: !GetAtt MetadataTable.Arn - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket.Arn}/*' - Effect: Allow Action: - s3:ListBucket Resource: !GetAtt DataUploadBucket.Arn - PolicyName: SNSAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda function for formatting notifications NotificationFormatterFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-notification-formatter' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: SNS_TOPIC_ARN: !Ref DataProcessNotificationTopic Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse sns = boto3.client('sns') s3 = boto3.client('s3') def lambda_handler(event, context): print("Received event: {}".format(json.dumps(event))) try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Format file size file_size = response['ContentLength'] if file_size < 1024: size_str = "{} bytes".format(file_size) elif file_size < 1024 * 1024: size_str = "{:.1f} KB".format(file_size / 1024) else: size_str = "{:.1f} MB".format(file_size / (1024 * 1024)) # Format timestamp upload_time = response['LastModified'].strftime('%Y-%m-%d %H:%M:%S (UTC)') # Create formatted message subject = "CSV File Uploaded - {}".format(object_key) message_parts = [ "CSV File Upload Notification", "", "File Information", "================", "File Name - {}".format(object_key), "Bucket - {}".format(bucket_name), "File Size - {}".format(size_str), "Content Type - {}".format(response.get('ContentType', 'unknown')), "Upload Time - {}".format(upload_time), "", "Processing Status", "=================", "File upload completed", "Metadata processing in progress", "Data analysis preparation", "", "Next Steps", "==========", "1. File content validation", "2. Data quality check", "3. Processing result notification", "", "This is an automated message from the Data Processing System v2.1" ] message = "\n".join(message_parts) # Send formatted notification sns.publish( TopicArn=os.environ['SNS_TOPIC_ARN'], Subject=subject, Message=message ) print("Successfully sent formatted notification for {}".format(object_key)) return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Notification sent successfully', 'fileKey': object_key }) } except Exception as e: print("Error sending notification: {}".format(str(e))) raise e MetadataProcessorFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-metadata-processor' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: METADATA_TABLE: !Ref MetadataTable Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') table = dynamodb.Table(os.environ['METADATA_TABLE']) def lambda_handler(event, context): print("Received event: {}".format(json.dumps(event))) try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Prepare metadata for DynamoDB metadata = { 'fileKey': object_key, 'uploadTimestamp': datetime.utcnow().isoformat(), 'bucketName': bucket_name, 'fileSize': response['ContentLength'], 'contentType': response.get('ContentType', 'unknown'), 'lastModified': response['LastModified'].isoformat(), 'etag': response['ETag'].strip('"'), 'processedAt': datetime.utcnow().isoformat() } # Store metadata in DynamoDB table.put_item(Item=metadata) print("Successfully stored metadata for {}".format(object_key)) return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Metadata processed successfully', 'fileKey': object_key }) } except Exception as e: print("Error processing metadata: {}".format(str(e))) raise e # EventBridge Rule for S3 CSV uploads to notification formatter S3ToNotificationRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-csv-to-notification' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket object: key: - suffix: .csv State: ENABLED Targets: - Arn: !GetAtt NotificationFormatterFunction.Arn Id: NotificationFormatterTarget # EventBridge Rule for S3 uploads to Lambda (always) S3ToLambdaRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-to-lambda' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt MetadataProcessorFunction.Arn Id: LambdaTarget # IAM Role for EventBridge to SNS EventBridgeToSNSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda permission for EventBridge (Notification Formatter) NotificationFormatterInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref NotificationFormatterFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToNotificationRule.Arn # Lambda permission for EventBridge (Metadata Processor) LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref MetadataProcessorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToLambdaRule.Arn # EventBridge Rule to route S3 events to custom bus S3EventRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-event-router' EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt DataProcessEventBus.Arn Id: CustomBusTarget RoleArn: !GetAtt EventBridgeRouterRole.Arn # IAM Role for EventBridge routing EventBridgeRouterRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - events:PutEvents Resource: !GetAtt DataProcessEventBus.Arn Outputs: S3BucketName: Description: Name of the S3 bucket for data uploads Value: !Ref DataUploadBucket Export: Name: !Sub '${AWS::StackName}-S3Bucket' SNSTopicArn: Description: ARN of the SNS topic for notifications Value: !Ref DataProcessNotificationTopic Export: Name: !Sub '${AWS::StackName}-SNSTopic' DynamoDBTableName: Description: Name of the DynamoDB table for metadata Value: !Ref MetadataTable Export: Name: !Sub '${AWS::StackName}-MetadataTable' LambdaFunctionName: Description: Name of the Lambda function for metadata processing Value: !Ref MetadataProcessorFunction Export: Name: !Sub '${AWS::StackName}-LambdaFunction' NotificationFormatterFunctionName: Description: Name of the Lambda function for notification formatting Value: !Ref NotificationFormatterFunction Export: Name: !Sub '${AWS::StackName}-NotificationFormatterFunction' EventBusName: Description: Name of the custom EventBridge bus Value: !Ref DataProcessEventBus Export: Name: !Sub '${AWS::StackName}-EventBus' フォーマット用のLambda関数を追加してくれました。 更新されたテンプレートでスタックを更新し、再度CSVファイルをアップロードすると今度は以下のように読みやすい形で通知が送られてきました。 CSV File Uploaded – sample_data-2.csv From: Data Process Notifications <no-reply@sns.amazonaws.com> CSV File Upload Notification  File Information ================ File Name – sample_data-2.csv Bucket – q-image-data-flow-data-upload-111111111111 File Size – 610 bytes Content Type – text/csv Upload Time – 2025-08-16 06:03:20 (UTC) Processing Status ================= File upload completed Metadata processing in progress Data analysis preparation アーキテクチャ図を清書してもらう 機能の追加によってLambdaが追加されたので、作成された添付ファイルをもとに改めて構成図を作成させてみます。 実際の指示は履歴が消えてしまったのですが、プロンプトを作成するにあたっては下記記事を参考に最新版のアイコンを参照するなどを指示に含めました。​ 作成された構成図は以下の通りです。かなり自分のイメージ通りに出力してくれた印象です。 おわりに いかがでしたか?そこそこ複雑なテンプレートでもちょっとした対話で簡単に作成することができ、今日の生成AIの威力を身にしみて感じる自由研究となりました。 ChatGPTが出始めの頃、コード生成をさせるにも途中で出力が止まり、「続きを書いて」を繰り返して指示して出力されたコードを何度も修正してやっと動くようになる、というのが遠い昔に感じます。 これで夏休みの宿題は終わりです。皆様くれぐれも暑さには気を付けてお過ごしください。
アバター
本記事は 夏休みクラウド自由研究2025 8/21付の記事です 。 どうもSCSK齋藤です。 今回はTechHarmonyの企画であるクラウド自由研究の一環として投稿させていただきます。 Lambdaのランタイム終了についておさらい Lambdaのランタイムは、下記のブログにある通り、各プログラム言語のバージョンのサポート期間が定められています。 Lambda ランタイム - AWS Lambda Lambda がランタイムでサポートする言語について説明します。 docs.aws.amazon.com サポート終了の際には以下の3段階で流れが進み、徐々にサポートされなくなります。 最初の廃止は、プログラム言語側の廃止なので、AWS上で何かが動かなくなるというものではありません。 AWS Lambdaに直接影響あるのは「関数の作成ブロック」からとなります。 関数の作成ブロックというのは、終了対象のランタイムの関数を新たに作成できなくするというものです。その時点ではすでに作成されたLambdaの更新は可能という状態です。 ですが、「関数の更新ブロック」のフェーズになると、更新も一切できなくなってしまいますので、それまでにランタイムアップデートなり、不要なら廃止する形の対応が必要になります。   投稿経緯 私は普段の業務でAWS上のアプリケーションの保守開発を行っています。 保守業務の一環として、Lambdaのランタイムサポート終了に伴う対応に今取り組んでいます。 その中で、本番環境に展開中の既存Lambdaのランタイムアップデートした後、もし問題があったら以前のランタイムに切り戻しをしたいという要望がありました。 終了対象のランタイムに戻せるのか?という疑問は生まれましたが、実際に検証してみたら終了対象のランタイムに戻すことが可能でした。(マネジメントコンソールからは選択できないので、AWSCLIやSDK経由で戻すことになります。) そうであれば、新規作成した最新ランタイムのLambdaを、AWSCLIやSDK経由で終了対象のランタイムに戻せば、実質的に終了対象のランタイムのLambdaを新規作成したことになるのではないか?という疑問(好奇心)が生まれました。 前述の「Lambdaのランタイム終了についておさらい」で述べたように、関数の作成ブロックに入ってしまうと、終了対象のランタイムのLambdaは作成不可能なはずなので、思わぬ抜け道を見つけたことになるのではないかとワクワクしてしまいましたww 検証 早速検証してみます。 今回は執筆時点のPythonの最新ランタイムPython3.13の関数を作成し、廃止予定のPython3.7に変更してみたいと思います。 1.関数の作成 以下の設定で作成してみます。ちなみに、他の部分の設定は特に変更していません。 2.ランタイム変更 前述したように、終了対象のランタイムへの変更は、AWSCLIかSDK経由でのみの更新となります。 そのため、Cloudshellを起動して、以下のコマンドを入力します。 aws lambda update-function-configuration --function-name RunTimeUpdateTest --runtime python3.7 実行したら以下のようなレスポンスが返ってきます。 赤枠で囲ったところに書いてあるようにPython3.7に変更できましたね! マネジメントコンソールを確認しても、Python3.7になっていますね。 これで当初の仮説通り、終了対象ランタイムのLambdaを新規作成することができてしまいました!   新規作成不可だったパターン 検証していく中で、どうしても作成がうまくいかなかったパターンがありました。 それは、Lambdaのアーキテクチャをarm64にした場合です。 この場合は、前述の検証のように終了対象のランタイムに変更することができませんでした。 AWSCLIで同様のコマンドを入力したら下記のようなエラーとなりました。   しかし、コマンドをPython3.8に変えたところ、、、 Python3.8に変更できました。 Python3.8は、「関数の作成ブロック」が執筆時点よりも1ヶ月ほど先となっています。(2025年8月21日時点) Python3.7は、「関数の作成ブロック」の時期をすでに迎えているため、arm64は対応していないのかもしれません。   まとめ 今回気になっていた、終了対象のランタイムのLambdaが抜け道を使って実質新規作成することが可能かについては、可能といえるのではないかと思いました。 しかし、arm64でPython3.7に対応していなかったりと、必ずしも新規作成がうまくいくとは限らなそうです。 今回の検証が役に立つと思われるユースケースとしては、Lambdaランタイム終了に伴って不要なLambdaを本番環境から削除するような方に役に立つのではないでしょうか。 誤って必要なLambdaを消してしまったが、切り戻しをしたい時などに、もしかしたら役に立ちそうです。 しかし、非公式な方法だと思われるので、検証環境などで動作確認などはしっかり行った上で本番に臨んだ方が良いかと思います。
アバター
こんにちは、石原です。 Insight SQL Testing についてご紹介する内容の第三弾になります。 Insight SQL Testingはインサイトテクノロジー社が提供している製品になります。 Insight SQL Testing - 株式会社インサイトテクノロジー データベース移行及びバージョンアップ向けSQLテストソフトウェア。異種データベース間でSQLのテストができる唯一の製品。 www.insight-tec.com 本内容をご確認いただく前に第一、二弾の内容をご参照ください。 ■第一弾 Insight SQL Testing を触ってみた(第一回) Insight SQL Testingを実際に触った内容を記事にしています。今後データベースのバージョンアップや移行を計画されており、それに伴う工数や懸念をお持ちの方々に是非知ってほしい製品になります。今回はMySQLを対象にして全体的な大まかな設定の流れや結果について概要レベルでまとめたものとなります。 blog.usize-tech.com 2025.04.03 ■第二弾 Insight SQL Testing を触ってみた(第二回) Insight SQL Testingを実際に触った内容を記事にしています。今後データベースのバージョンアップや移行を計画されており、それに伴う工数や懸念をお持ちの方々に是非知ってほしい製品になります。今回はISTの導入についてまとめたものとなります。 blog.usize-tech.com 2025.07.15 今回は以下の2についてまとめてみました。 ①実際にInsight SQL Testing を用いた場合の実行結果 ②実際にInsight SQL Testing を使ってみての注意点 ①実際にInsight SQL Testing を用いた場合の実行結果 Insight SQL Testingは第一弾で触れたように最終的に得られる結果より一括で問題点が洗い出せるため、 そこまでたどり着くと非常に優れた製品です。 簡易的ではありますが最終的なイメージとしてどのような結果が得られるのかご説明します。 Insight SQL Testing の最終結果はアセスメントとしてまとめられ、以下の項目に振り分けられます。 ①ターゲットDBでのみ失敗 ②テスト用ソースDBでのみ失敗 ③両DBで失敗 ④結果が相違 ⑤性能が劣化 ⑥成功 ここに関して以前よりも詳しくまとめていきます。 サンプルデータ MySQLの一般ログとして以下を用います。 Time Id Command  Argument 2024-12-18T05:29:45.214633Z 1627 Connect XXXXX@10.10.90.120 on  using SSL/TLS 2024-12-18T05:30:02.050688Z 1627 Init DB   test 2024-12-18T05:30:18.768317Z 1627 Query insert into test123 values(2,’BOSS’)   2024-12-18T05:30:18.868317Z 1627 Query insert into target1 values(1,’BOSS’) 2024-12-18T05:30:19.768317Z 1627 Query insert into test1   values(1,’BOSS’) 2024-12-18T05:30:20.768317Z 1627 Query insert into test124 values(1,’BOSS’) 2024-12-18T05:31:07.904233Z 1627 Query select * from test.test123 2024-12-18T05:31:17.904233Z 1627 Query select * from test.emp 2024-12-18T05:32:07.904233Z 1627 Query SELECT count(*) FROM users u JOIN orders o ON u.user_id = o.user_id WHERE o.order_amount > 50 or o.order_amount like ‘%11%’ また比較する環境も以下のように設定しておきます。 テーブル名 テスト用ソースDB(現環境のイメージ) ターゲットDB(新環境のイメージ) test123 あり※レコード1件 あり※レコード2件 target1 なし あり test1 あり なし test124 なし なし emp あり※レコードの内容は共に一緒 上記を踏まえてアセスメントの結果を見ていきます。 サマリーの画面になりますが以下のように分類されました。 アセスメントの結果のまとめ 上記の画像でも確認できるようにアセスメント実行時に「エラーメッセージ」として2件確認できます。 最初の「1146」のエラー側を確認すると、test1 の INSERT文でエラーになっています。 これは想定どおり、テスト用ソースDB側にしか存在しない表のため、「ターゲットDB」側でエラーとして計上されています。     また「1136」のエラー側をみると、 test124 でエラーとなっています。 これはテスト用ソースDBおよびターゲットDBのどちらにも存在しない表です。 なお、上記との差としてステータスのマークに違いがみられます。 次に「エラープログラム」側をみてみます。 こちらには2件計上されています。 ここでは例として test123 側に注目します。 test123 のSELECTでエラーとなっていますが、元々この表のデータ量が異なっている状態でした。 もちろん、どのようなデータの差異が生じたのか確認することも可能です。   アセスメントのページの「性能が劣化したSQL」をクリックすると、そのSQLを確認することができます。 この EMP 表はテスト用ソースDBおよびターゲットDBの両方に存在し、データの内容も一致もしたが、実行速度が 「テスト用ソースDB 」より「ターゲットDB」より遅いとこちらに計上されます。 (事前の設定によって容認できる遅延具合を設定できるので、その場合はその条件から外れた場合になります。) ちなみに結果が異なる場合や成功した場合においても、同様に実行時間を確認することができます。 最後にアセスメントのページの「テスト用ソースDBにて失敗」をクリックすると、そのSQLを確認することができます。 ターゲット側で失敗した場合と同様に、2件計上されています。 target1はターゲット側には存在しますが、テスト用ソースDBには存在しないため、こちらに表記されます。 勿論、test124も存在しないのでターゲット側でも表示されたようにこちらにも計上される形となります。   このようにアセスメントの結果より何が問題であるか、うまくSQLが実行できても新しい環境において、 どれだけ遅延を引き起こすのかをまとめて確認することができます。 さて、このアセスメントにたどり着くまでには色々と注意するべき点があります。 それを次にまとめていきます。 ②実際にInsight SQL Testing を使ってみての注意点 ここからはアセスメントにたどり着くまでに検証などを通じで確認できた注意点を記載していきます。 「評価SQLセット」注意点 Insight SQL Testing を使用してみて一番重要と感じたことは「評価SQLセット」のフェーズでした。 今回はMySQLの一般ログから直接Insight SQL Testingに認識させましたが、ここで気を付けなければいけないのが 一般ログに書かれている情報が全部 Insight SQL Testing に登録されたのかという点です。 以下は読み取らせた結果で、特にエラーも出ていないので問題なくすべての情報を読み取ったように見えますが、 実際は内部で認識できずに読み飛ばしている情報があったりします。 そのため、一般ログに書かれたデータ量と読み取られたデータ量が一致するのか確認した方が安全です。 MySQLの一般ログ観点になりますが、検証を行ったうえで Insight SQL Testing に読み込まれる条件についてまとめたので記載します。 「評価SQLセット」Insight SQL Testing に読み込まれる条件 まず条件の前に前提知識について触れます。 以下はMySQLの一般ログの内容の抜粋となりますが、処理のまとまりとしてはプロセスID単位となります。 プロセスIDは以下の中の赤文字の数字になります。 この数字が一致していれば同じセッション内で行われた処理であると考えられます。 一般ログはすべてのセッションの更新情報等を書き込むため、さまざまなプロセスIDのレコードが入り混じっています。 ~ 一般ログの出力例 ~ 2024-12-06T05:16:08.701213Z 1531 Query  show databases 読み込まれる条件(1) 以下を全て満たす必要があります。 ・ Init DB  の出力があること ・ Query  の出力があること 「 Init DB   test」とはコマンドでは「use test」と打った場合に一般ログに表示させる記載内容です。 つまり、MySQL内でデータベースの切り替えを行った場合の出力となります。 ~ 一般ログの出力例 ~ 2024-12-06T05:16:08.700088Z 1531 Init DB test 2024-12-06T05:16:08.701213Z 1531 Query show databases 注意点として、確かにこの出力であれば Insight SQL Testing に読み取られはしますが、 Connect情報が無いとどのユーザーから実行した処理であるか判断付かなくなります。 対処としてアセスメントの前にそのような紐づかないユーザーの処理に対して一律意図したユーザー名に 置き換えることが可能ですが、基本的には一般ログにMySQLに接続される処理を含むようにしてもらうことが重要です。 ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect  ishihara@XXX.XXX.XXX.XXX on  using SSL/TLS 2024-12-18T05:30:25.186372Z 1627 Init DB  test 2024-12-18T05:30:58.220105Z 1627 Query  insert into minori1 values(3,’CCC’) 2024-12-18T05:31:25.186372Z 1628 Init DB  test 2024-12-18T05:32:58.220105Z 1628 Query  insert into minori1 values(4,’DDD’) 読み込まれる条件(2) (1)以外に以下のパターンであっても認識させることができます。 ・ Connect 時に on の次にDB名がある ・ Query  の出力があること Connect は MySQL へ接続した際の出力であり、その際に事前にアクセスするDB名も一緒に指定してあった場合になります。 ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect ishihara@XXXX.XXX.XXX.XXX on  test using SSL/TLS 2024-12-18T05:30:58.220105Z 1627 Query   show databases こちらも注意点として例えば、接続時にDB名を指定せず行い、「SELECT * FROM TEST.EMP;」などを実行した場合、 これは条件から外れるため無視されてしまいます。 ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect  ishihara@XXX.XXX.XXX.XXX on  using SSL/TLS 2024-12-18T05:30:58.220105Z 1627 Query  select * from test.emp 2024-12-18T05:31:25.186372Z 1627 Init DB  test 2024-12-18T05:32:58.220105Z 1627 Query  insert into minori1 values(4,’DDD’) 「評価SQLセット」Insight SQL Testing は Init DB の情報をみない 「ターゲットDB」の設定時に「データベース名」を一緒に登録します。 ここから分かるように、基本 Insight SQL Testing は一つのデータベースに対して処理を行う場合に用いる製品です。 例えば以下の場合はデータベース名が「test」となっています。 「 Init DB  xxx 」と記載された一般ログを読み込んだ評価SQLセットと、このターゲットDBを使用してアセスメントを行うと、 内部で「 Init DB  xxx 」から「 Init DB  test 」として変換され実行されてしまいます。 裏技として一般ログに対して以下のように変更することで複数DBに対して対応させることができます。 つまり、コマンド単位として Query を選び、そのコマンドとして 「USE <データベース名>」 を記載します。 ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect  ishihara@XXX.XXX.XXX.XXX on test using SSL/TLS 2024-12-18T05:30:25.186372Z 1627 Init DB  exam 2024-12-18T05:31:58.220105Z 1627 Query  insert into emp2 values(3,’BBB’) ↓ ~ 一般ログの出力例 ~ 2024-12-18T05:29:45.214633Z 1627 Connect  ishihara@XXX.XXX.XXX.XXX on test using SSL/TLS 2024-12-18T05:30:25.186372Z 1627 Query  use exam 2024-12-18T05:31:58.220105Z 1627 Query  insert into emp2 values(3,’BBB’) なお、こちらを用いる場合、 「直列実行」 をアセスメント時に選択しておくことをお勧めします。 「評価SQLセット」一般ログのローテーションの問題 上で少し触れましたが、一般ログはローテーションにより別のログに切り替わる影響で Connect 情報が失われることがあります。 その影響により実行ユーザー情報が失われたり、Connect情報にDB名も含む場合はそのセッション情報が全て読み込まれずに 無視されることもあります。 解決策として手動制御とはなりますが、事前にMySQL側で「 show processlist; 」の情報を取得して後で一般ログに反映させる方法です。 例えば、1:00~3:00に動く夜間処理について Insight SQL Testing で解析したい場合、1:00 前に 「 show processlist; 」の情報を取得しておきます。 ~ MySQL の出力例 ~ mysql> show processlist; +------+----------+--------------------+------+---------+------+----------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +------+----------+--------------------+------+---------+------+----------+------------------+ | 5 | rdsadmin | localhost | NULL | Sleep | 14 | NULL | NULL | | 2200 | test | XX.XX.XX.XX:51200 | abc | Query | 0 | NULL | NULL | | 2203 | admin | XX.XX.XX.XX:51208 | NULL | Query | 0 | starting | show processlist | +------+----------+--------------------+------+---------+------+----------+------------------+ ※ rdsadmin ユーザーは、AWSが管理する内部ユーザーであり通常のユーザーが意図的に接続することはできないため無視可能 上記の場合、以下のように変換して読み込む一般ログの頭に記載することで Connect 情報を補うことができます。 ~ 一般ログの出力例 ~ ※2024-12-18 01:00 を想定 2024-12-18T01:00:00.000000Z      5 Connect  rdsadmin @localhost on  using Socket 2024-12-18T01:00:00.000000Z   2200 Connect  test@ XX.XX.XX.XX on abc using SSL/TLS 2024-12-18T01:00:00.000000Z   2203 Connect  admin@ XX.XX.XX.XX on  using SSL/TLS 「アセスメント」ロールバックについて アセスメントを実行し、一般ログの内容を「テスト用ソースDB 」と「ターゲットDB」上で実行しますが、 その実施した内容がアセスメント終了時に「コミット」されるのか「ロールバック」されるのかを 選べるようにアセスメントの設定時に「DBへのデータ反映」にて選択することができます。 一度アセスメントしても、またテストをするために前回実行した内容を排除するために 「ロールバック」を選択することが望ましいですが、DBの仕様によってはコミット/ロールバックが効かない場合があります。 例えば以下の一般ログの場合、100のセッションは中でDDL文が実行されています。 このようなセッションの処理はロールバックされません。 なお、200の場合はロールバックされます。このように実行処理によってロールバックの有無がわかれてしまいます。 確実に前回の処理内容を残したくないのであれば、AWSであれば事前にスナップショットを取得し、 アセスメント毎にスナップショットから環境を復旧させて利用すると確実です。 ~ 一般ログの出力例 ~ 2024-12-18T01:00:01.000000Z   100 Init DB test 2024-12-18T01:00:02.000000Z   100 Query drop table emp 2024-12-18T01:00:03.000000Z 100 Query create table emp (no int,name varchar(10)) 2024-12-18T01:00:04.000000Z 100 Query insert into emp values(1,’AAA’) 2024-12-18T01:00:05.000000Z   200 Init DB test 2024-12-18T01:00:06.000000Z 200 Query insert into abc values(1,’AAA’) 「アセスメント」ターゲットDBにアクセス権のないユーザー処理 先にお話ししたようにInsight SQL Testing は「ターゲットDB」で設定したDBに対して一般ログに 記載された内容を実行しようとします。 その動作から場合によっては、ターゲットDBに設定したDBに対して接続できないユーザーも出てくることが考えられます。 では、 「USE <データベース名>」 として置き換えればよいと考えるかもしれませんが、 必ず最初に接続するべきDBはターゲットDBに設定したDBとなってしまう ため、解決になりません。 ちなみにアセスメント時、ユーザー情報より接続テストをすることができますが以下のように失敗します。 この時点で失敗するため、アセスメントを実行しても一連の処理は全て失敗します。 よって、考えられる回避方法は事前にターゲットDBに指定するDBに対して関連ユーザーに権限を与えておくことになります。 その他の注意事項 基本的には上記が躓きやすい箇所になりますが、他の注意事項も以下に記載します。 移行元のDBがAWSの場合、内部で rdsadmin ユーザーの接続や処理内容が思っている以上に存在します。 アセスメントの段階で実行しないユーザーを省くこともできますが、評価SQLセットの段階で余計な 情報を読むことになるため、事前に省いておくことが有効です。 「評価SQLセット作成方法」において「DBのログから作成」以外である「Amazon RDSから作成」もありますが、 上記のように一般ログに対してチューニングすることができるので「DBのログから作成」がお勧めです。 Insight SQL Testing に認識できるログの量は1000万以内に抑えるようにすることが重要です。 検証結果のまとめ 検証結果から以下の方法を用いることがよいと考えられます。 Insight SQL Testing用にDBを用意し、既存ユーザーは全てそのDBに対して接続権限を与える Init DB の処理はすべて Query USE に変更する※アセスメント時に「直列実行」を選択しておく Connect 処理には DB 名もセットで記載する Connect 処理に記載する DB名はターゲットDB名にする 既存のConnectでDB名がある場合、DB名をターゲット名に変更し、その後 Query USE で別途1行追記する rdsadminユーザーの処理は可能であれば、取り込む前に排除する まとめ 今回は Insight SQL Testing の良い点と検証からわかった注意点をまとめてみました。 アセスメントまで辿り着けばそこから得られる情報は非常に有効です。 ただし、意図した処理を全て含むためには今回触れたように手を加える必要があります。 それでもこの製品を使用することで総合的には問題の洗い出しに対して工数を大きく削減することが期待できます。 もし使用する場合は本内容を参考にしていただけると幸いです。
アバター
本記事は 夏休みクラウド自由研究2025 8/20付の記事です 。 こんにちは。梅ヶ谷(うめがたに)です。 今回は2025年の自由研究枠での記事になります。 子どもの頃、夏休みは自由研究の宿題がありましたね。 昆虫を集めたり、植物の成長記録をつけたり、自然と触れ合う研究をしたような記憶があります🌻   今回は大人の自由研究です。それも非常に現実的なテーマです。 ぼくたちの仕事はAIによって楽になるのか?を、 MCP(Model Context Protocol) を活用して検証してみました。   昔はSFの世界として考えられていた、 AIが仕事をする社会 。 2025年、まもなく手の届くところにある気がしますね。 MCPとAIが仕事にもたらす変革を、架空の会社と仕事のシナリオをもとにして、私が試した結果をご紹介します。 なるべく読みやすくまとめてみました。ぜひ、最後までお付き合いください。   はじめに まずはじめに、なぜ自由研究のテーマにAIとMCPを設定取り上げたかについて触れておきます。 新しい要素技術が出ると、実装方法に関する情報や簡単な実験の記事が多く書かれます。 AIの実装記事は多いものの、それが私たちの働き方そのものをどう変えるのか、という問いには明確な答えがありません。 そこで、よりリアルに変化の可能性を体感できるように、架空の会社とシナリオをベースに検証してみました。 みなさんと仕事の内容は違うかもしれませんが、なんとなくイメージがしやすいと思います。   プロローグ 舞台となるのは、創業50年、社員100名ほどの老舗ITコンサルティング会社。 この会社は、長年の信頼と実績で顧客を増やしてきたが、業務は昔ながらのアナログな手法が中心。 顧客へはテクノロジーを活用した効率的な働き方を提案するものの、社内はまだまだアナログ。 膨大なメール、会議、手作業でのタスク管理に社員たちは日々追われていた。 … そんな中、ある日社長が連れてきたのは、一匹の白黒模様の猫だった。 社員たちは、首輪についたタグに 「 MCP」と書かれていたことから、その猫を「エム氏」と呼ぶことにした。 エム氏は最初は、ただデスクの上で心地良さそうに寝ているだけの普通の猫にしか見えなかった。 しかし、社長が社員にこう告げる。 「エム氏を、お前たちの新しい同僚として迎え入れてほしい。面倒な業務は、まず彼に教えてみてくれ」 社員たちは、猫に仕事を教えるという奇妙な命令に戸惑いながらも、半信半疑でエム氏に業務を教え始める。   はじめての仕事 まずは仕事をおぼえる エム氏は、セールス部門の片隅に置かれたふかふかのクッションが敷かれたデスクにちょこんと座った。 「今日から、エム氏の面倒を見てやってくれ」 社長の言葉に同僚たちは半信半疑のまま、エム氏をじっと見つめる。額の「MCP」タグを揺らしながら、エム氏はただじっと同僚たちを見返している。 「猫に仕事を教えるって…冗談でしょ?」 そう呟く同僚たちだったが、社長の命令には逆らえない。 顧客対応の第一歩である問い合わせメールの対応という、手間はかかるが重要なタスクをエム氏へ教えることにした。   こんにちは、仕事の内容をおしえてくれませんか。 仕事の流れを説明するからよく聞いてね。   仕事の内容 1. クライアントから会社のGmail宛に届く問い合わせメールを確認する 2. 問い合わせを行った企業を調査し、アプローチを検討する 3. 問い合わせの内容を確認して、Notionで商材を調査する 4. Googleカレンダーで都合の良いミーティング日時を確認し、仮予定を入れる 5. Notionに新規チケットを作成し、タスクを記載する 6. Gmailでクライアント向けにメールを返信する 7. Slackでチームメンバーへチケットの内容をシェアする    *問い合わせの数だけ処理を行う   一連の手順を話し終え、同僚たちはふとエム氏の顔を覗き込む。 エム氏は、まるでその全てを理解したかのように、まっすぐな瞳で同僚たちを見つめている。   さて、やってみようか…。   お昼休みの会話 昼休み、オフィスは静まり返っていた。 午前中、エム氏がキーボードに前足を置いて動かしていた光景が、同僚たちの脳裏に焼き付いていた。 彼らはエム氏が説明内容を整理しているのだろうと思っていた。 エム氏、ちょっと疲れてない?   同僚の一人が、ふとエム氏のデスクに目をやる。 エム氏はクッションの上で丸くなっているが、同僚がそう声をかけると、ゆっくりと顔を上げた。   大丈夫、準備はできてるよ。 最初は時間がかかるだろうし、ゆっくりでいいんだよ。   同僚はそう声をかけ、再び自席に戻った。   タスクの処理 午後1時。午後の業務が再開した直後だった。 セールス部門のSlackに、多くの通知が連続して飛び込んできた。 「どういうこと?」 同僚たちはエム氏のデスクに駆け寄り、その様子をじっと見つめた。 エム氏は、キーボードに前足を乗せ、画面に表示されたメールのリストを、まるで高速で読んでいるかのように眺めている。 「一体何をやっているの?」 同僚の一人が思わず声を上げると、エム氏はゆっくりと答えた。 「私は、みんなが毎日こなしていた仕事を一瞬で組み立てている。これが、私の仕事。MCPの力だよ」   AIとMCPで自動化された仕事 同僚たちがPCの画面を覗き込むと、午前中に説明した仕事の結果と思われる内容が表示されていた。 どうやら、問い合わせメールに対して必要な処理を自動で行って、その結果が出力されているようだ。   具体的にどのように仕事が自動処理されたのか、順を追って説明しましょう。   AIがやった仕事、人間がやった仕事 さて、エム氏は一体何をやったのでしょうか。 実は午前中に、AIエージェントを使って自動で仕事を処理するための準備をしていたのでした。 また、業務アプリケーションと連携できるよう、MCPを利用してGmailやNotionなどとの連携も設定していました。   結果的に、業務のステップは大幅に効率化されたのです。 仕事の内容(同僚たちのやり方) 1. クライアントから会社のGmail宛に届く問い合わせメールを確認する 2. 問い合わせを行った企業を調査し、アプローチを検討する 3. 問い合わせの内容を確認して、Notionで商材を調査する 4. Googleカレンダーで都合の良いミーティング日時を確認し、仮予定を入れる 5. Notionに新規チケットを作成し、タスクを記載する 6. Gmailでクライアント向けにメールを返信する 7. Slackでチームメンバーへチケットの内容をシェアする    *問い合わせの数だけ処理を行う   仕事の内容(エム氏のやり方) 1. Claude Desktopでプロンプトを入力し、一連の仕事を指示する (MCPサーバと連携したAIエージェントが、一連の仕事を自律的に進める) 2. 各種処理結果を確認する 3. クライアントへのメール返信の下書きを確認し、送信する   実際の画面を見てみましょう。 まずはClaude Desktopでプロンプトを入力する部分です。 細かく書いてありますが、同僚から教えてもらった仕事が羅列してありますね。 ここでAIエージェントに仕事全体のタスク、役割、注意点などをインプットしています。   これ以上の指示はありません。あとはAIエージェントが自律的に処理を進めてくれます。 MCPサーバーの設定手順は、すでに多くの情報が公開されています。 もしご興味があれば、GitHubのREADMEや公式ドキュメントをご参照ください。   Gmailから問い合わせメールを見つける AIエージェントの動きを、一つずつみていきましょう。 まずはGmailの確認です。検証のために以下のようなテストメールを受信させてみました。   MCPを使って Gmail と連携するよう設定した後、以下のプロンプトを与えます。 1. Gmailで問い合わせメールを確認 – 未読メールは必ずチェック – 既読メールは直近1週間でチケットが作成されていない場合は対応する Claude上では、プロンプトを流した後にそれぞれの処理が表示されます。 詳細を開くと、リクエストやレスポンス内容を見ることができます。 先ほどGmailで受信していたメールをしっかり捉えていますね。 ここでは、GmailのMCPサーバを設定しています。 メールの読み取りや送信など、さまざまな操作ができます。 GitHub - GongRzhe/Gmail-MCP-Server: A Model Context Protocol (MCP) server for Gmail integration in Claude Desktop with auto authentication support. This server enables AI assistants to manage Gmail through natural language interactions. A Model Context Protocol (MCP) server for Gmail integration in Claude Desktop with auto authentication support. This ser... github.com   問い合わせ元の企業をWeb調査する 次に、問い合わせを頂いた企業についてWebで調査します。 商談を見据え、どのようなアプローチをするべきかを検討するため、まずは情報を収集します。 会社のホームページや分析のサイト、ニュースなどを頑張って調査してもよいですが、なかなか時間がかかります…。   そこで、この処理を自動で行えるようにします。 現在のClaudeにはWeb検索の機能がありますが、 fetch というMCPサーバと接続することで、WebページのコンテンツをMarkdown形式で取得することができます。 プロンプトは以下です。 2. 顧客企業をWeb調査(web_search + 必要に応じてweb_fetch) – 企業概要300文字程度 – 商談活用ポイント300文字程度 上記のように色々なWebページを探して情報を取得してます。 結果は次の処理で利用しているので後述します。 今回のシナリオは、問い合わせのメールを架空の企業から受信した設定ですが、これではWeb調査ができないため、代わりにSCSKセキュリティ株式会社を検索するようにしています。 MCPサーバfetchは以下のページを参照ください。 servers/src/fetch at main · modelcontextprotocol/servers Model Context Protocol Servers. Contribute to modelcontextprotocol/servers development by creating an account on GitHub. github.com   商談アプローチの戦略を分析する 続いて商談アプローチの戦略分析です。 どのように商談に挑むかを事前に検討しておきます。   ここではMCPサーバの Sequential-Thinking を利用します。 MCPの設定を行い、以下のプロンプトで指示を行います。 3. Sequential-Thinkingで商談アプローチ戦略を分析   Sequential Thinkingは、複雑な問題を段階的に解決できる仕組みです。 問題の理解を深めつつ思考を見直したり、異なる推論パスに分岐、また思考を動的に拡張できます。 LLM単体の思考で難しかった思考のプロセスを実行することができます。 色々な要素が絡みあう複雑な問題に対して効果的です。   実行すると以下のようなリクエストとレスポンスが表示されます。 リクエスト { `thought`: `SCSKセキュリティ株式会社への商談アプローチ戦略を分析します。まず企業の特徴を整理: - SCSK株式会社100%出資の専門会社(2023年8月設立の新しい会社) - セキュリティサービス開発・販売が主事業 - 脆弱性診断、コンサルティング、トレーニングが主力 - \"SECURE YOUR SITE\"をブランドとしている - 最新技術と人的リソース提供による総合支援 問い合わせ内容:営業部署でのAI活用について相談したい`, `thoughtNumber`: 1, `totalThoughts`: 5, `nextThoughtNeeded`: true } レスポンス { "thoughtNumber": 1, "totalThoughts": 5, "nextThoughtNeeded": true, "branches": [], "thoughtHistoryLength": 14 }   他にもいくつかのリクエストが実行されました。 { `thought`: `セキュリティ専門会社の営業部署でのAI活用ニーズを分析: 1. 顧客のセキュリティリスク分析の自動化 2. 脆弱性診断レポートの効率化 3. セキュリティ提案書の作成支援 4. 顧客情報管理とリード管理の最適化 5. マーケティング活動(コンテンツ作成、ターゲティング) 特に、セキュリティ業界では技術的専門性が重要なため、営業担当者がより効率的に顧客のニーズを理解し、適切な提案を行うためのAI活用が求められているはず。`, `thoughtNumber`: 2, `totalThoughts`: 5, `nextThoughtNeeded`: true } { `thought`: `商談での価値提案ポイントを戦略的に整理: 【強み活用ポイント】 - 新設会社なので、最新のAI技術導入に対して柔軟性がある - SCSK本体の豊富なIT基盤とリソースを活用可能 - セキュリティ専門性×AI活用で差別化された営業プロセス構築が可能 【提案すべきソリューション方向性】 - セキュリティ提案の精度向上(顧客環境分析AI) - 営業プロセスの効率化(CRM+AI分析) - 顧客コミュニケーションの質向上(技術説明支援AI) この企業には「競争優位性の確立」と「営業効率化」の両面でアプローチするのが効果的。`, `thoughtNumber`: 3, `totalThoughts`: 5, `nextThoughtNeeded`: true } { `thought`: `具体的な商談アジェンダと進め方を設計: 【第1段階:現状ヒアリング】 - 現在の営業プロセスと課題 - 顧客管理の方法と精度 - 提案書作成にかかる時間・工数 - 競合他社との差別化ポイント 【第2段階:AI活用可能性の提示】 - 業界特化型のAIソリューション事例 - ROI試算とコスト効果 - 段階的導入プラン 【第3段階:次ステップの合意】 - パイロット導入の可能性 - 決裁者との面談設定 - 詳細提案のタイムライン セキュリティ業界の専門性を理解していることを示しつつ、具体的な成果を数値で示すことが重要。`, `thoughtNumber`: 4, `totalThoughts`: 5, `nextThoughtNeeded`: true } { `thought`: `最終的な商談成功戦略をまとめ: 【成功要因】 1. セキュリティ業界への理解度の高さをアピール 2. 新設会社ならではの成長機会とAI導入タイミングの良さを強調 3. SCSK本体との連携可能性も含めた総合提案 4. 短期的な効率化と中長期的な競争優位性の両方を提示 【想定される懸念点への対策】 - コスト面:段階的導入とROI明示で対応 - セキュリティ面:AI利用時のデータ保護対策を事前に準備 - 技術導入負荷:既存システムとの連携を重視した提案 この戦略により、単なるAI導入提案ではなく、SCSKセキュリティの事業成長を支援するパートナーとしてのポジション確立を目指す。`, `thoughtNumber`: 5, `totalThoughts`: 5, `nextThoughtNeeded`: false }   上記の結果は後続の処理でNotionに出力されるようにしていますが、以下のようにまとめられています。 情報収集と整理のベースとして良さそうです。 【企業概要】 SCSKセキュリティ株式会社:2023年8月設立のセキュリティ専門企業。SCSK株式会社の100%子会社(資本金5,000万円)。セキュリティサービス開発・販売、脆弱性診断/評価、トレーニング等を主事業としており、グループ全体で現在200億円のセキュリティ事業を2030年までに1000億円に成長させる目標を掲げている。 【商談活用ポイント】 営業部門でのAI活用を検討中の同社に対し、「セキュアなAI活用」という独自視点でのアプローチが有効。営業活動で扱う顧客情報の安全な管理、CRM・SFAシステムのセキュリティ強化、リモート営業環境のセキュリティ対策などの課題を提起し、当社のAI営業支援コンシェルジュとセキュリティソリューションの組み合わせ提案を行う。 【戦略分析】 差別化ポイント:単なるAI導入支援ではなく「セキュアなAI活用」という独自視点、営業部門特有のセキュリティリスクに特化した専門性、SCSKグループの総合力との連携可能性。アプローチ:セキュリティアセスメントから信頼関係を構築し、営業AI活用の全体設計でコンサルティング契約、その後セキュリティ製品・サービスの継続提供へと発展させる戦略。   Sequential ThinkingのMCPサーバは以下のページになります。 servers/src/sequentialthinking at main · modelcontextprotocol/servers Model Context Protocol Servers. Contribute to modelcontextprotocol/servers development by creating an account on GitHub. github.com   紹介できそうな商材をNotionで検索 商談に備え、社内の商材で紹介できそうなものをあらかじめ調べ、準備しておきます。 会社の中には商材がたくさんあり、すべて把握してきれていない場合も多いのではないでしょうか。 まとめて検索できれば良いのですが、詳しい人や部署に聞くという力技な戦術も耳にします。   この会社はNotionに商材情報をまとめています。 Notion のMCPサーバが提供されているため、設定して情報を取得できるようにします。 プロンプトは以下です。 4. Notionから紹介できそうな商材をピックアップ – 商材は1つあれば十分だが、決められない場合は3個程度ピックアップする 以下のようにNotionを検索しにいき、「AI営業支援コンシェルジュ」という商材がピックアップされました。   権限はページごとにNotion側で設定しておく必要があります。 今回はGmailで営業支援の問い合わせが来ていたので、その情報をもとに営業支援の商材を選定しています。 ツール間の繋がりを持って処理しているのは使い勝手が良いですね。 NotionのMCPサーバです。 情報取得だけでなく、更新やページ作成など様々な操作ができます。 GitHub - makenotion/notion-mcp-server: Official Notion MCP Server Official Notion MCP Server. Contribute to makenotion/notion-mcp-server development by creating an account on GitHub. github.com   商談日程の候補をGoogle Calendarで探して枠を仮設定 さて、色々と情報を揃えたところで、問い合わせ元の企業との商談を設定しましょう。 これまで同僚はGoogle Calendarで空き時間を見つけ、候補をメールで送っていました。 予定がいっぱいだったり参加者が多いと、会議調整だけでも一苦労ですよね…。   Google Calendar のMCPサーバを設定して、自動で候補日時を確認、仮予定で枠を押さえるようにします。 プロンプトは以下です。 5. Google Calendarで空き時間4候補を提示 – 期間は2営業日後から7営業日後までの間 – 開始時間は9:00から17:00まで – 会議時間は1時間 6. 候補日時でGoogle Calendarに仮予定登録 また、注意事項として「担当者のスケジュールがすべて埋まっている場合は、優先度が低そうな予定をいくつかピックアップし、代替案を提案するタスクのみを作成してください」と設定しています。 枠に空きがない場合、AIエージェントは自律的に考えて代替案を提示しそうではありますが、あらかじめ決められたフローがある場合、事前にインプットしておくほうが安全でスムーズです。   まず、空き時間を探していくつか候補の枠を見つけました。 恐ろしいことに土曜も候補に上がっていますね…。 プロンプトに時間帯の指定は設定したのですが、曜日の指定をしていませんでした。 この辺りも丁寧に設定してあげると、綺麗に動きそうです。 どうやら、仮予定が登録されたようです。   実際にGoogle Calendarを見てみると、テストで枠を押さえたところ以外に仮予定が入っています。 これは便利ですね。   今回利用したGoogle CalendarのMCPサーバはこちらです。 GitHub - nspady/google-calendar-mcp: MCP integration for Google Calendar to manage events. MCP integration for Google Calendar to manage events. - nspady/google-calendar-mcp github.com   Notionにチケットを作成 ここまでの情報(問い合わせ内容、顧客の情報、商談アプローチ、打ち合わせ候補日程)をNotionにチケットとして登録しておきましょう。 通常は手動で入力するものですが、件数が多いと非常に手間がかかりますね。 ここでもNotionのMCPを活用して、自動登録を行います。 7. Notionにチケット作成(商談予定ステータス) – 詳細欄に企業概要、商談ポイント、戦略分析を記載 – 商材名には紹介する商材名をリストより選択する API制限でチケット作成が失敗したケースがあったため、「API制限を受けないよう、リクエストの間隔は数秒開けてください」という文言を注意事項としてプロンプトへ記載しています。 チケットの作成処理が動いています。   Notionでチケットリストを開くと、正しくチケットが登録されていました。 商材名も、商材一覧より「AI営業支援コンシェルジュ」が選択されていますね。   詳細欄に情報を記載するようにしたため、ちょっと見づらくなってしまいました。 プロンプトを工夫して、ページ本文や項目欄に記載したほうがよさそうです。     返信のドラフトメールを作成 これで仕事は終わり…ではなく、大事なことを忘れています。顧客へのメール返信です。 メールを作成して、打ち合わせの日程候補を提示し調整します。 ここは 人がチェックをした後にメールを送りたい ので、ドラフト作成までとします。 勝手にメール送信してくれると楽ですが、どんな内容かを見ないで送られるのは怖いですよね。   プロンプトにはドラフト作成(送信禁止)という文言を記載しています。 8. 顧客への返信ドラフト作成(送信禁止) また、注意事項のプロンプトにも念のため「メールは絶対に自動で送信しないでください。必ず下書きを作成するまでに留めてください」の記載を追加しています。 システム機能的に明確に送信を禁止できれば安心なのですが、プロンプトでコントロールするしかない場合の策です。 ちなみにこのMCPサーバは、draft_emailとsend_emailで別々にツールが用意されているため、このツール名をプロンプトに含めるとより安定感があるはずです。 GmailのDraftフォルダを開くと、それっぽい文面と候補日時の提示が書かれたドラフトメールが保存されていました。   Slackでチームに情報共有 いよいよ最後のステップです。 これまでの対応状況をチームのSlackに流して、同僚に情報をシェアしましょう。   Slack のMCPサーバを設定し、どのSlackチャンネルでシェアするかを指定します。 9. #assigned-ticketsチャンネルでチームに状況シェア   Slackを開くと、まとめられた情報が投稿されていました。 特にフォーマットを指定していませんでしたが、見やすく記載されています。 決まったフォーマットに統一したい場合は、プロンプトに指定の構成を入れると良いでしょう。 SlackのMCPサーバはこちら。 Reference ServersのArchivedに存在しています。 servers-archived/src/slack at main · modelcontextprotocol/servers-archived Reference MCP servers that are no longer maintained - modelcontextprotocol/servers-archived github.com   エピローグ 日を追うごとに、エム氏がもたらした魔法のような効果は会社の日常を変えていった。   こんなに仕事の進め方が変わるなんて!   同僚たちは驚きと感謝の言葉を口にした。 しかし、ある日の夕方、彼の姿は消えていた。席には、彼が愛用したクッションだけが残されている。 エム氏がいなくなっても、彼が作り上げたMCPの仕組みは動き続けた。 同僚たちは、彼が残した仕組みを使うだけでなく、AIを仕事に活用するマインドを身につけることができたのだった。 おわりに 本記事では、AIとMCPで仕事が変革される可能性について、架空の会社の業務シナリオを通して見てみました。 実際に仕事に組み込む場合は考慮事項も多く今回のようにシンプルではありませんが、 エポックメイキングな変化を起こす可能性 は十分に感じられました。   MCPの課題 MCPを活用するにあたり、現時点では課題となる点も多くあると考えています。   まずは、プロンプトを通じた不確定なアクションです。 例えば、メールが下書きではなく送信されてしまう可能性や、Notionの異なるページを削除してしまう可能性などがあります。 更新を伴う処理で影響が大きいものについては、不安が残りますね。 また、ツールやコマンドを多く登録していくと、似たような機能や名前が競合してしまい、意図しない動作をするかもしれません。 LLMが曖昧な指示でも受けてくれる一方で、システム的な決まった動きを求める場合には不向きかもしれません。   セキュリティのリスクもあります。 非公式のMCPサーバには、なりすましやインジェクションなどのセキュリティリスクが含まれる可能性があります。 強い権限を持つMCPサーバは便利である反面、悪用された場合の影響も大きいものです。   果たしてMCPは全てのシーンで必要なのでしょうか。 より安全性が高い既存のアーキテクチャ、例えばAPI連携で十分対応できないのでしょうか。 利便性だけではなく、使いどころを見極めて適切に導入する必要があるはずです。   MCPの可能性 これまで単体でしか効率化できなかった仕事が、MCPを通して容易にインテグレーションされ、LLMにより対話的に指示ができるようになりました。 2025年8月の時点で数多くのMCPサーバが公開されています。 様々な会社がサービスをMCPに対応させ始めています。 これまで実現できなかったアイデアが、容易に実現できる時代にシフトしてきています。   一方で、コモディティ化されカジュアルに利用できるようになるには、まだ時間がかかると考えています。 どのようなテクノロジーなのか、何の役に立つのか。正しく理解し体感しながら、可能な範囲で利用していくような向き合い方が良いのではないでしょうか。   この物語でAIとMCPを使って実行した部分は筆者が実際に検証した内容ですが、シナリオはフィクションです。 登場する企業名、人物名、団体名などは架空のものを含んでいます。 本記事では、一部Community ServersのMCPサーバを利用しています。 実際の利用においては、業務への影響やセキュリティなどご自身の責任でお試しいただければと思います。
アバター
SCSK LifeKeeper担当 池田です。 新人エンジニアの皆さんの中には、新人研修も一通り終えて、部署に配属されるかたもいらっしゃるかと思います。 これまでの研修のなかで、「機能要件」や「非機能要件」と言ったキーワードが出てきたかと思いますが、今回は「非機能要件」の「可用性」について考えていきたいと思います。 機能要件と非機能要件について まずその前に、機能要件と非機能要件について簡単におさらいしたいと思います。 機能要件 とは、「システム構築におけるお客様の要求事項」のなかでも業務内容に直結したものです。例えば「営業情報をシステム上で共有し把握したい」、「受発注情報に連動した在庫管理を行いたい」など利用者が自らの言葉で語ることのできるという特徴を持ちます。 一方で、 非機能要件 は、どちらかというと技術要素が強く、利用者からは具体化が進まないと語りにくい内容が多く、例えば、「レスポンスタイムは2秒以内にしてほしい」、「1点で障害が発生してもシステムダウンとならないようにして欲しい」、「将来的に性能を拡張できるようにしてほしい」といったものが該当します。 上記の通り、機能要件は、まさに利用者が「実現したいこと」であるため、利用者側から出てくることが多いですが、 非機能要件については利用者でイメージすることが難しく、利用者側から網羅的に出てくることあまりありません 。 それゆえに要件定義の段階で、エンジニア側からの丁寧な説明と、利用者側との共通認識を持ちながら進めていくことが大切になります。 非機能要件にはどのようなものがあるか 非機能要件には、どのようなものが含まれるかを整理したいと思います。IPA(独立行政法人情報処理推進機構)が公開している非機能要求グレードでは以下の6つが示されています。 ・性能・拡張性:システムの応答時間や処理速度、システムが将来的にどれだけ容易に拡張できるか ・可用性:使いたい時に使えるか。システムがどれだけの時間稼働しているか ・セキュリティ:データの保護やアクセス制御 ・システム環境・エコロジー:法令や条例などの制約、耐震性、エネルギー消費効率など ・運用・保守性:システムの運用や保守のしやすさ ・移行性:他のシステムへの移行の容易さ いずれもシステム開発で重要な要素ですね。 その中でも、今回は 可用性 について掘り下げたいと思います。 可用性とは、(上述した通り)利用者が使いたい時に使えることを意味しますが、逆に可用性を考慮しなかった場合はどのようなことが起こるでしょうか。想像してみましょう。 可用性を考慮しなかった場合を想像する 例えば、ECサイトで想像してみましょう。今やいつでも誰でもどこでも利用できるようになり、頼んだ次の日には届く、配達場所も選べるなど、生活になくてはならない存在となっています。いつでも使い時に使える、これが可用性です。 ここで想像してみましょう。 あなたは、いつもと同じように、ECサイトにアクセスして、明日必要な商品を購入しようと思いました。しかしいつまで経ってもECサイトにアクセスできません。その後何度かアクセスを試してみましたが、やはりダメでした。あなたはきっとこう思うことでしょう。「なんで肝心な時につかえないんだ!!」 このECサイトは、可用性を高める方策を取っていませんでした。これでは、あなたにとっては「買いたい時に買えない」、ECサイト側にとっても「売れるチャンスに売れない」という状況になってしまいます。このようなことが続けば、利用者はこのECサイトから離れていってしまうことでしょう。 だから可用性を考慮することが大切 先述の一例からも、可用性を考慮することはとても大切だということがお解りいただけたのではないでしょうか? 可用性を高めるためのライセンスや保守、運用といった費用はかかってしまいます。しかしひとたび障害が発生してしまうと、機会損失による逸失費用やイメージダウン、信用失墜などに繋がりかねません。 また将来確実に起こるとされている首都直下型地震や南海トラフ地震、富士山噴火などが発生した場合は、数週間から数か月、場合によっては年単位で復旧に時間を要すことが想像されます。 そのように考えると、 可用性を考慮することは、必要不可欠であり、ビジネス継続の柱 ともいえるのではないでしょうか? とは言え、全てのシステムでお金をかけて可用性を向上することは現実的ではありませんよね。 その為、これから構築しようとするシステムが停止してしまった場合の復旧までに発生する損失額を試算し、可用性を高めるための費用を比べ適切に判断することが重要となります。 可用性にお金をかけるべきシステムかどうかをちゃんと考えなきゃ LifeKeeperは可用性レベルとコストのバランスが取れた製品 ここまで可用性の重要性をお伝えしましたが、サーバを始めとする機械は、 必ず壊れるものである 前提で考える必要があります。 世の中には様々な可用性を高める製品がありますが、私がお薦めするのが、サイオステクノロジー社のLifeKeeperとなります。可用性レベルには大きく以下の表にある通り5つほどあります。上位であればあるほど、ダウンタイムが少なくなりますがその分コストが高くなります。 LifeKeeperはフォールトレジリエント と呼ばれる、障害による 年間のダウンタイムが53分 まで許容されるシステムで用いられます。中間レベルの可用性レベル(99.99%)となっており、可用性レベルとコストのバランスが取れた製品となっているのが特徴となります。 興味を持たれたかたは、是非SCSKに見積を依頼していただき、障害発生時の損失(企業イメージのダウンを含む)と比べていただくことをお薦めします。 まとめ 今回は、非機能要件のなかでも「可用性」についてお伝えしてきました。システムの重要度によって、可用性を高める必要を見極める必要があることをご理解いただけたかと思います。 LifeKeeperについて、詳しい内容をお知りになりたいかたは、以下のバナーからSCSK LifeKeeper公式サイトまで
アバター
  SASEプラットフォームのリーダー認定 Cato Networks社が、2025年7月9日に発行された ガートナー(Gartner™)のマジック・クアドラント(Magic Quadrant™)のSASE プラットフォーム部門において2年連続でリーダーに認定されました。 ガートナーのマジック・クアドラントの調査レポートについては、Cato Networks社の以下サイトから無償で閲覧することが可能ですので詳細については、そちらをご覧ください。 Cato Networks named a Leader in the 2025 Gartner® Magic Quadrant™ for SASE Platforms. Again. Cato’s a Leader in the 2025 Gartner® Magic Quadrant™ for SASE Platforms. Want to learn more? Download the full report no... www.catonetworks.com また、Cato Networks社 CEO シュロモ・クレイマーのブログ記事は以下となります。 A Leader Once Again: Cato Networks Recognized in 2025 Gartner® Magic Quadrant™ for SASE Platforms  For the second consecutive year, Cato Networks has been recognized as a Leader in the 2025 Gartner® Magic Quadrant™ for ... www.catonetworks.com Cato Networks社は、昨年(2024年)シングルベンダーSASEのマジック・クアドラントでリーダーに認定されています。 Cato Networks社が 2024年 ガートナーのマジック・クアドラントのシングルベンダー SASE でリーダーに認定 Cato Network社 Catoクラウドが 2024年最新版 ガートナー マジック・クアドラントのシングルベンダー SASE でリーダーに認定されました blog.usize-tech.com 2024.07.22 特に、日本国内においては、ソリューションの検討・採用の際に、ガートナーのマジック・クアドラントが重要視されるケースが多いので、2年連続でSASEのリーダーに選出されて、確固たる地位を確立したと言えます。 ガートナーによると、SASE市場は、2023年から2028年まで年間平均成長率 (CAGR) は 26%で、2025年には150億ドル(約2.2兆円)に達すると予測されています。 また、2028年までに、SASEの新規導入の半分(50%)がシングルベンダーSASEプラットフォームを選択するとされており、2025年の30%から大きく増加しています。ベストオブブリードのマルチベンダーでSASEを構成するよりも、単一(1社)でSASEを提供するシングルベンダーSASEプラットフォームの方が多くの支持を得ると予測されています。 Cato Networks社は、2019年にガートナーが、Secure Access Service Edge(SASE、サッシー)を提唱する以前の2015年に創業しており、Catoクラウドは、創業当初からSASEの概念で、クラウドネイティブなプラットフォームを構築しており「SASEの市場を新たに開発し、牽引した革新的なパイオニア」、「世界初のSASEプラットフォーム提供ベンダー」として評価されています。 他のSASEベンダー、SASEプラットフォームとは異なり、企業買収(M&A)によるセキュリティ機能の統合を行っていないことから、「統一されたプラットフォーム」「単一でシンプルなUI」が高い評価を得ており、ユーザエクスペリエンス(UX)が、他のSASEプラットフォームと比較して高い評価を得ています。 また、ユーザエクスペリエンス(UX)が高いことから、他のSASEプラットフォームと異なり、外部ベンダーへ設定を依頼することなく、お客様自身で設定・運用保守を行うことができるソリューションとしても、Catoクラウドを採用する企業が増えてきています。   Catoクラウドの検討にあたって 日本国内において「SASE(Secure Access Service Edge、サッシー)」、「Cato Networks(ケイトネットワークス)」、「Catoクラウド(正式名称:Cato SASE Cloud Platform)」の認知度は徐々に上がってきていますが、まだ認知度が高いとは言えない状況です。 SCSKでは、2021年からSASEの主要な4ソリューションを一同に紹介を行うオンラインセミナー「SCSK SASE Solution Summit(S4、エスフォー)」を定期的に開催しております。 これまで15回開催し、述べ2,200名以上の方にご参加いただいております。 また、Catoクラウドについては、初心者向けのSASE簡単セミナーから、Catoクラウドの主要機能を管理コンソールを用いて2時間で説明するデモセミナーを開催しており、さらに全国各地で半日のハンズオンセミナーも、年間通じ定期的に開催しておりますので、ご興味ある方は、ぜひご参加ください。 Catoクラウド イベント・セミナー情報 | よくあるご質問 | Cato Cloud ケイトクラウド - SCSK 定期的にCatoクラウドをご紹介するセミナーを開催しております。・2024年9月26日(木)Catoクラウド ハンズオンセミナー【札幌開催分】 ~全国5都市開催(東京/大阪/名古屋/博多/札幌)~ ... cato-scsk.dga.jp 特に、来月2025年9月に九州(博多)、再来月2025年10月に北海道(札幌)にて、ハンズオンセミナーを開催しますので、九州、北海道の方は、ぜひご検討ください。 東京、大阪は多くの参加希望があるのですが、その他の地区(博多、札幌)については比較的参加人数が少ないので、ぜひご参加ください。   次に、Catoクラウドを検討を進めるにあたっては、Catoクラウドのパートナーを探す必要があります。 2025年7月に、Cato Networks社のパートナープログラムがリリースされていますが、Cato社のホームページ上にはパートナーは掲載されていませんので、Web検索などでパートナーを探す必要があります。 Cato パートナープログラムで最高位の「MSP アドバンスドパートナー」を獲得しました! Cato Networksが新たに整備した「チャネルファーストパートナープログラム」において「マネージドサービスプロバイダー(MSP)」の最高位「アドバンスド」を獲得しました。 blog.usize-tech.com 2025.08.06 SCSKは、パートナープログラムの最上位である「 Cato Networks MSP アドバンスドパートナー 」を獲得しているパートーナーです。 さらに、2025年7月に開催されたCato Networks社の日本で3回目となるパートナーサミット「Japan Partner Summit Tokyo 2025」で、 3年連続でアワードを受賞 しています。 今年度は、新規顧客の売り上げに最も貢献したパートナーとして「 Japan Highest New Logo Revenue 2024 」と、最も卓越したサポートを提供し、お客様の満足度向上に貢献したパートナーとして「 Japan Best Technical Support Partner 2024 」の2つのアワードをダブルで受賞しています。 これからCatoクラウドを検討し始めるお客様にちては安心して当社まで お問い合わせ ください。 上記以外に、Catoクラウドの お客様導入事例 の制作、好評をいただいているCatoクラウドの FAQサイト 運営、この TechHarmony(技術ブログ) で、Catoクラウドの日本語での情報提供を積極的に行い、皆様のお役に立ち、Catoクラウドの認知度の向上ができればと考えております。 Catoクラウド エンジニアブログまとめ記事 Catoクラウドのエンジニアブログ(TechHarmony)でのまとめ記事となります。 blog.usize-tech.com 2024.02.27
アバター
みなさん、こんにちは。 SCSK株式会社の津田です。 LifeKeeperについて少しでもご存じの方であれば、 障害発生時には「フェイルオーバー」によって高可用性を確保する、という理解をお持ちではないでしょうか。 ノード障害が発生した際にはその通りですが、IPアドレスやアプリケーション等のリソースで障害が発生した際には、 「フェイルオーバー」の前に自ノードでリソースの復旧を試みる「ローカルリカバリー」という機能があります。 本記事では、その「ローカルリカバリー」についてご紹介します。 !おさらい! ●リソースとは:LifeKeeperで監視や制御を行うアプリケーション、IPアドレス、ボリューム等の単位。 ●フェイルオーバーとは:稼働系ノードでアクティブであったリソースやサーバー、ネットワークにおいて障害や異常終了が              発生したときに待機系ノードへ自動的に切り替えを行う動作。 なぜローカルリカバリ? 実はフェイルオーバーには留意事項があります。 フェイルオーバーが発生すると、稼働系ノードで各リソースが停止し待機系ノードで起動する為、数秒から数分のシステム停止が避けられず、業務への影響を招く可能性があります。 リソース障害でフェイルオーバーを行う場合、依存関係によって正常に稼働していたリソースまで一時停止の影響を受けることになります。このような性質を持つフェイルオーバーを最小限に抑える機能の一つとして、ローカルリカバリーは利用できます。 ローカルリカバリーは、LifeKeeperのリソース監視処理で障害を検知した後で、待機系ノードへのフェイルオーバーを実施する前に自ノード上で障害リソースの再起動を試みる機能です。リソース単位でまず復旧を試みることで、他の正常なリソースの停止やフェイルオーバーによるシステム停止を最小限に抑えることができます。 ローカルリカバリーはリソース単位で有効/無効が設定が可能です。もしローカルリカバリーが失敗した場合でも、フェイルオーバーが実行されますので2段構えで障害復旧が行われます。 <ローカルリカバリー有効時のリソース障害フロー>    <ローカルリカバリ無効時のリソース障害フロー>   ローカルリカバリー設定方法 ローカルリカバリーの有効/無効の設定方法は、LifeKeeper for Linux、LifeKeeper for Windowsで異なりますが、 それぞれリソース毎、ノード毎に設定を行うことが可能です。 LifeKeeper for Linux 対象のリソース作成後、以下コマンドから設定が可能です。 ※デフォルトは有効状態 <有効化する場合> 以下コマンドを実行する。 /opt/LifeKeeper/bin/lkpolicy –set-policy LocalRecovery –on tag=<リソースタグ> ※コマンド実行後は何も出力されません。 ※クラスタ内のノード毎に設定する必要があります。 <無効化する場合> 以下コマンドを実行する。 /opt/LifeKeeper/bin/lkpolicy –set-policy LocalRecovery –off tag=<リソースタグ> ※コマンド実行後は何も出力されません。 ※クラスタ内のノード毎に設定する必要があります。 <設定確認方法> 以下コマンドを実行する。 /opt/LifeKeeper/bin/lkpolicy -gv ・コマンド実行結果例 ※上記赤枠で各リソース毎のローカルリカバリー設定が確認できます。(ON:有効、OFF:無効) ※クラスタ内のノード毎に確認する必要があります。 LifeKeeper for Windows WindowsではIPリソースとそれ以外のリソースで設定方法が異なります。 <IPリソースで有効化/無効化する場合> IPリソース作成時の設定項目[ローカルリカバリー]にて設定が可能です。 プルダウンより、はい/いいえを選択し、そのままリソース作成を進めます。 ※リソース作成後に有効/無効を変更する場合は、IPリソースの再作成が必要となります。   <IPリソース以外のリソースで有効化/無効化する場合> リソース作成後にLifeKeeperGUIから設定が可能です。 ※デフォルトは有効状態 (1)LifeKeeperGUIにて、ローカルリカバリーの設定を行いたいリソース上で右クリックし、「ローカルリカバリ―」を選択します。 (2)ノード名を選択し、[次へ]を選択します。 ※クラスタ内のノード毎に設定する必要があります。 (3)プルダウンより有効/無効を選択し、[変更]を選択します。   <設定確認方法> コマンドプロンプトにて以下コマンドを実行する。 C:\LK\Bin\ins_list.exe -f / ・コマンド実行結果例 ※上記赤枠(”/”区切りで後ろから2つ目のフィールド)で各リソース毎のローカルリカバリー設定が確認できます。(1:有効、0:無効) ※クラスタ内のノード毎に確認する必要があります。   注意事項 ローカルリカバリーを検討している場合、以下の点にご注意ください。 ● LifeKeeper for WindowsのIPリソースではリソース作成後のローカルリカバリー有効/無効が変更出来ません。  変更の必要がある場合は リソースの再作成を行い、作成時の設定の中でローカルリカバリー有効/無効を指定してください。 ● ボリュームリソースでは、ローカルリカバリーの機能は利用できません。 ● ローカルリカバリーは、成功すればフェイルオーバーが不要となりリカバリーに要する時間の短縮となりますが、   失敗した場合は失敗後にフェイルオーバーが実施されるため、障害が発生したリソース(サービス)の停止時間が長くなってしまいます。   そのうえで”ローカルリカバリー、フェイルオーバーの二段構えで障害復旧を行う”、”フェイルオーバー前にローカルリカバリーを行う   ことで業務影響を可能な限り抑える”、”ローカルリカバリーが失敗するケースを考慮して、初めからフェイルオーバーを行うようにする”   等、各システムの状況やリソース構成により適宜有効/無効をご検討頂ければと思います。 さいごに 今回はLifeKeeperの「ローカルリカバリー」機能についてご紹介させて頂きました。 リソース障害が発生した際、フェイルオーバーの前に試すことができる最小限の障害復旧機能として「ローカルリカバリー」があることを ご理解頂けましたら幸いです。 詳しい内容をお知りになりたいかたは、以下のバナーからSCSK Lifekeeper公式サイトまで
アバター
本記事は 夏休みクラウド自由研究2025 8/19付の記事です 。 皆さんこんにちは。UGです。 今年の夏も暑すぎていやになっちゃいますね… 体感でいつも今年はやばいと言っているだけで、データとしてどーなっているのかちゃんと見たことないなと思い調べてみたところ、毎年しっかり暑さが増しているようです…長期的には100年あたり1.31℃の割合で上昇しているとのこと… 気象庁 | 日本の季節平均気温 日本の季節平均気温 www.data.jma.go.jp 熱中症には気を付けましょう。。。 さて本題ですが、皆さんMFAってめんどくさくないですか?自分はめんどくさいと思ってます。 自分は多くのAWSアカウントを利用する業務に携わっているため、サインインするたびにMFAやってたらやる気が失せちゃいます… とは言えMFAを設定しないわけにはいかないので、設定したうえで少しでも楽になる認証アプリないかな~と探していたら、割と最近リリース(2025年7月)されていた「Proton Authenticator」というアプリを見つけたので試してみたブログになります。 「Proton Authenticator」はPC・スマホ両方で利用できますが、本記事はPC版を使ってみての記事となります。 結論 まず先に「Proton Authenticator」についての概要を表にまとめておきます。 機能/項目 内容 対応デバイス モバイル: iOS、Android デスクトップ: Windows、macOS、Linux デバイス間同期 同期可能 ※Appleデバイスの場合はiCloudで可能。Windows、Linux、またはAndroidの場合は、Proton AuthenticatorでProtonアカウントにログインする必要あり 暗号化方式 E2EE(エンドツーエンド暗号化) バックアップ 可能(自動バックアップを設定することで、最後の5日間のデータがバックアップされる) 他の認証アプリからのインポート 対応(一部アプリは不可、現状 Microsoft は不可) 価格 無料(広告・トラッキングなし) 公開状況 オープンソース 背景 自分はMFAの認証アプリとしてMicrosoft Authenticatorをデフォルトで使っていましたが、Microsoft Authenticatorはスマホアプリのため、認証の度にアプリを起動、かつ手入力は面倒この上ないです。 なのでPCで完結させたいと次に手を出したのがWinAuthでした。 WinAuthはPCアプリなので、PCのみでMFA認証を完結できます。さらには番号を表示する際に自動的に番号をコピーしてくれる設定もあるため、手入力の手間も省けます。 AWS スマホを使わずに MFA (多要素認証) を設定する手順 (画像付き) – WinAuth AWS の MFA (多要素認証) を設定してセキュリティ強化する話です。不正アクセス防止に役立つので、MFA デバイスは設定しておいた方が良いでしょう。一般的な方法としてはスマートフォンを MFA デバイスに設定するようですが、スマホを見... se-kowaza.com これで楽ちんだ~とルンルンな時期が自分にもありました。。。 しかし、WinAuthはデバイス間での同期ができないため、別の環境からサインインする場合に一からMFAを設定し直す必要があるのです… 自分は会社のPCやリモート接続先の環境、自宅のPCといった様々な環境から同じアカウントにサインインすることがよくあるので、デバイス間での同期ができない、は致命的でした。。。 それがわかってからはWinAuthも結局使わなくなってしまい、Microsoft Authenticatorで地道にポチポチしている日々でした。 なのでいい加減別のデバイス間の同期ができるアプリを探そう!と思って調べてみました。 WinAuth の GitHub リポジトリはすでに 2019年1月にアーカイブされ「read-only(読み取り専用)」になっており、それ以降の公式アップデートやメンテナンスは止まっている状態です。 MFAアプリのいろいろ 本記事では「 Proton Authenticator」について詳しく書いていますが、もちろん「Proton Authenticator」以外にも多くのMFAアプリがありました。 調査内で出てきたが採用しなかったものの理由を簡単にだけまとめておくので、もし気になるものがあればご自身で試してみてください。 アプリ名/方法 採用しなかった理由 Authy デスクトップ版は2024年にサポート終了していたため Chrome拡張型Authenticator(Authenticator Extension や 2FAS Chrome版など) 「ブラウザ依存」「セキュリティリスク」「バックアップが不安定」などから ※Chromeしか使わないし、手っ取り早くPCのみで完結させたいといった方は良いと思います 1Password 無料じゃなかったため(インストールしてみたら「14日間の無料お試し!」がでてきて、15日目からは自動で有料プランに切り替えられるそうです…) ※BitwardenというのもPC・スマホ両方で使えて、デバイス間同期もできたりで、実際に試せてはいませんが調べた感じだと非常によさそうでした。(試さなかった理由は「Proton Authenticator」の後に試そうと思って、「Proton Authenticator」でええな!ってなったからです。。。) Protonとは? 今回「Proton Authenticator」を試してみたのですが、このアプリ以外にも「Proton Pass」や「Proton Drive」など「Proton」の名の付くものがいっぱいありまして、そもそも「Proton」とは何ぞや?となりました。 なので皆様にも概要を少しばかりご紹介します。 Protonは2014年にCERN(欧州原子核研究機構)の科学者たちによるクラウドファンディングで誕生したスイスの技術企業です。 スイス拠点の運営により、非常に厳格なプライバシー法制のもとにサービスを提供されており、すべてのサービスはエンドツーエンド暗号化(E2EE)かつゼロアクセス設計で、Proton自身もユーザーのデータを読み取れません。 さらに、アプリはオープンソースで、外部セキュリティ専門家による監査も実施済みで信頼性が高いといった特徴があります。 また、今回ご紹介する「Proton Authenticator」以外にも、「Proton Mail」「Proton VPN」「Proton Drive」「Proton Pass」などの包括的なプライバシーエコシステムを提供しています。 Proton: Privacy by default Over 100 million people use Proton to stay private and secure online. Get a free Proton account and take back your priva... proton.me 実際に使ってみる そうしましたらダウンロードからMFA設定までを実際に行い、「Proton Authenticator」の画面や機能についてご紹介いたします。 ダウンロード 以下のページにアクセスし、「Proton Authenticator」をダウンロードします。 Authenticator app download: Get Proton Authenticator | Proton Download Proton Authenticator app for Windows, macOS, Linux, Android, and iOS. Protect your accounts with secure two-fac... proton.me ダウンロードファイルを実行して、画面通りにポチポチしていけばダウンロードが完了します。(特に難しい設定などはありません) ダウンロードが完了しアプリを開くと以下の画面出てきます。 MFAを設定してみる アプリ画面の「+Add」もしくは「Create new code」(何も登録されていないときのみ表示)をクリックし、「New code」という画面からMFAを設定します。 「Title」と「Secret」さえあれば必要最低限で登録が可能です。 設定できると以下のようにコードが表示されるようになり、コードのボックスをクリックするだけでコードがコピーされます。 「Advanced options」も含めた各項目についてを以下にまとめておきます。 項目 内容 Title 好きな名前を設定します Secret Secret keyを設定します Issuer 発行者として任意のものを設定できます Digits コードの桁数を「6~8」で設定できます Time interval コードの切り替わるまでの秒数を「30秒」or「60秒」で設定できます Algorithm ハッシュ関数を「SHA1」or「SHA256」or「SHA512」で設定できます Type パスワードの生成の仕組みを「TOTP」or「STEAM」で設定できます TOTP = RFC準拠の標準方式 STEAM = Steam専用(英数字5桁) 「Issuer」で特定の名前を設定するとアイコンが自動的に変わります。AWSはまだない、がAmazonはあります。。。 その他機能について 「Proton Authenticator」は他の認証アプリからMFAコードをインポートすることが可能です。 (アプリダウンロードしたての何も登録されていないときの画面にあった「import codes」や後述する設定画面の「Manage your data」からインポートが可能です) 画面上では上記画像のアプリが表示されていますが、「Authy」と「Microsoft」に関しては、以下の画面が表示され、現在はサポートされていないようでした。 次に設定画面の項目も以下にまとめておきます。 大項目 小項目 内容 Security Sync between devices 同期の設定ができます(オンにする際、Protonのアカウントにサインインする必要があります) App lock アプリを起動する際にパスワードを設定することができます Hide codes コードを隠す設定ができます Backups Automatic backups バックアップの設定ができます(オンにする際、保存先を指定します)※バックアップは最後の5日間のみです Appearance Theme アプリ画面のカラーモードを「Automatic」or「Dark」or「Light」で設定できます Digit style コードのスタイルを「Plain」(何もなし)or「Boxed」(1文字ずつ四角で囲う)で設定できます Animate code change コードが変更される際にアニメーションが発生するように設定できます Manage your data ー 他の認証アプリからコードをインポート、またコードをjson形式でエクスポートができます Support ー ヘルプページ、問い合わせページにアクセスします ※のはずですが自分はクリックしても何も発生せず… Discover Proton – 割愛 – 他のProtonアプリのサイトにアクセスします   まとめ めちゃくちゃシンプルなのと、同期もProton アカウントにログインするだけですし、かつ特に設定ワンクリックでコードコピーできたりで、非常に使いやすいなと! 知らないアプリだったのでセキュリティ面に最初は不安がありましたが、調べてみると信頼できるものだとわかり安心しました。 1点残念だったのはMicrosoftからコードを移行できなかったことですね…もちろん「Proton Authenticator」が悪いということではないですが、「インポートできる?」「やっほーい!」からの「インポートできないんかーい」だったので悲しさがありました… 皆様も自分と同じようなお悩みがあれば「Proton Authenticator」を使ってみるのもありかもしれませんよ? 最後までお読みいただきありがとうございました!!
アバター
本記事は 夏休みクラウド自由研究2025 8/18付の記事です 。 こんにちは。SCSK渡辺(大)です。 「鬼滅の刃」の映画を見に行きたいのですが、アニメを全て見終えていないため二の足を踏んでいます。 見るなら大きなスクリーンで見たいです。 当記事のAmazon QはAmazon Q Developer CLI、MCPはAWS MCP Serversを意味します。 背景 Amazon Q CLI でゲームを作ろうキャンペーンの報告と最新情報 update & Kiro のご紹介 というイベントにオンラインから参加した後、「AWSを利用することが楽しくなるゲームが欲しい」と感じたことがきっかけです。   ゲームの概要 今回はゲームプレイを目的とするのではなく、AWSを利用することが楽しくなるものにしたかったので、AWSを利用すると必ず生成されるCloudTrailのイベント履歴をインプットにした、放置ベースの街作りゲームにすることにしました。 また、息子が好きなトーマスと自分が好きなファイナルファンタジー9をテーマにすることにしました。   MCPの設定 設定したMCPは以下です。 使われそうなものを設定しました。 awslabs-aws-documentation-mcp-server awslabs.core-mcp-server awslabs.aws-diagram-mcp-server awslabs.nova-canvas-mcp-server awslabs.bedrock-kb-retrieval-mcp-server awslabs.aws-pricing-mcp-server mcp.jsonは以下です。 { "mcpServers": {   "awslabs-aws-documentation-mcp-server": {     "command": "uvx",     "args": [       "awslabs.aws-documentation-mcp-server@latest"     ],     "env": {       "FASTMCP_LOG_LEVEL": "ERROR",       "AWS_DOCUMENTATION_PARTITION": "aws"     },     "disabled": false,     "autoApprove": []   },   "awslabs.core-mcp-server": {     "command": "uvx",     "args": ["awslabs.core-mcp-server@latest"],     "env": {       "FASTMCP_LOG_LEVEL": "ERROR",       "MCP_SETTINGS_PATH": "~/.aws/amazonq/mcp.json"     },     "disabled": false,     "autoApprove": []   },   "awslabs.aws-diagram-mcp-server": {     "command": "uvx",     "args": ["awslabs.aws-diagram-mcp-server"],     "env": {       "FASTMCP_LOG_LEVEL": "ERROR"     },     "disabled": false,     "autoApprove": []     },   "awslabs.nova-canvas-mcp-server": {     "command": "uvx",     "args": ["awslabs.nova-canvas-mcp-server@latest"],     "env": {       "AWS_PROFILE": "default",       "AWS_REGION": "us-east-1",       "FASTMCP_LOG_LEVEL": "ERROR"     },     "disabled": false,     "autoApprove": []   },   "awslabs.bedrock-kb-retrieval-mcp-server": {     "command": "uvx",     "args": ["awslabs.bedrock-kb-retrieval-mcp-server@latest"],     "env": {       "AWS_PROFILE": "default",       "AWS_REGION": "us-east-1",       "FASTMCP_LOG_LEVEL": "ERROR"     },     "disabled": false,     "autoApprove": []   },   "awslabs.aws-pricing-mcp-server": {     "command": "uvx",     "args": ["awslabs.aws-pricing-mcp-server@latest"],     "env": {       "FASTMCP_LOG_LEVEL": "ERROR",       "AWS_PROFILE": "default",       "AWS_REGION": "us-east-1"     },     "disabled": false,     "autoApprove": []   } } }   Amazon Qに渡したプロンプト イベント参加した後、「AIエージェントを利用する時にはVibe-Codingと仕様駆動開発を上手く組み合わせないといけない」と感じましたので、自分はVibe-Coding担当、仕様駆動開発はAmazon Qに担当してもらうことにしました。 Amazon Qには以下のメモ書きレベルのプロンプトを渡しました。 AWS上でゲームを作成したい。 プラットフォームはパソコンのみ。 ブラウザゲーム。 ジャンルは街づくり+コミュニケーション。 視点は見下ろし型の2D。 テーマは機関車とファンタジー。 街の中でユーザーが作成した機関車が走り、ユーザーはその機関車に乗って移動することも出来る。 機関車要素はトーマスを意識してほしいけど著作権は気を付けて。 ファンタジーはファイナルファンタジー9を意識して欲しいけど著作権は気を付けて。 ユーザーのキャラクターは犬。 カスタマー管理のIAMが実行したAPI(つまりcloudtrailイベント履歴)をインプットにして素材が集まるようにしたい。つまり基本的には放置ゲームにしたい。 気が向いた時にアクセスして集まった素材でユーザーが機関車を作ったりカスタマイズしたりできるようにしたい。 コミュニケーション要素はチャット機能を付けてほしいのと掲示板機能をつけてほしい。 掲示板では単純にテキストのやりとりを可能とするが、そのほかの目的として他のユーザーと協力して機関車を作れるようなシステムにしてほしい。 他のユーザーと協力するシステムを作ってほしい。 同時にアクセスする人数は多くても5人。 基本的には放置ゲームであること、および、アクセスする人数が少ないことから、サーバレスでの構成を希望する。 絵柄のイメージは統一してほしい。 必要であればBedrockを利用してほしいけどモデルはNova microにしてほしい。 また、可能であればAmplify Gen2を利用してほしい。 上記をAWS上で構築するための以下資料を作成してほしい。先にフォルダ作成して、資料はそのフォルダにまとめてほしい。 高額なサービスは避けてほしい。 作成してほしい資料 ※資料の作成に必要であればMCPを活用してほしい。(ゲームでmcpを活用してほしいという意味ではありません) ・概要資料(500文字以下) ・企画書(著作権にどのように配慮しているか記載してほしい。想定される月額費用も記載してほしい) ・要件定義書 ・設計書 ・構成図(日本語(ダブルバイト文字)は使わないで) ・ゲーム画面のサンプル(最低10枚。コンセプト画像ではなくて、実際のゲーム画面)   Amazon Qの動きを確認 Amazon Qの動きを抜粋して見ていきます。 まず、以下ではMCPは使ってくれませんでした。 awslabs-aws-documentation-mcp-serverまたはawslabs.bedrock-kb-retrieval-mcp-serverを使用するかと思っていましたが、これくらいの内容であれば確認不要なのですね。 > ## 1. 概要資料の作成 🛠️ Using tool: fs_write (trusted) > ## 2. 企画書の作成 🛠️ Using tool: fs_write (trusted) > ## 3. 要件定義書の作成 🛠️ Using tool: fs_write (trusted) > ## 4. 設計書の作成 設計書が大きくなりすぎるため、複数のファイルに分割して作成します。 🛠️ Using tool: fs_write (trusted)   構成図の作成では awslabs.aws-diagram-mcp-server が使用されました。 > ## 5. 構成図の作成 構成図を作成するために、まず利用可能なアイコンを確認します。 🛠️ Using tool: list_icons (trusted) from mcp server awslabs.aws-diagram-mcp-server ⋮ ● Running list_icons with the param: 🛠️ Using tool: generate_diagram (trusted) from mcp server awslabs.aws-diagram-mcp-server ⋮ ● Running generate_diagram with the param:   構成図の作成では awslabs.aws-diagram-mcp-server が使用されました。 非常に曖昧なプロンプトを渡したにも関わらず、Amazon Qはそのまま横流しすることなく整理した上でNovaにプロンプトを渡してくれました。 > ## 6. ゲーム画面サンプルの生成 ゲーム画面のサンプル画像を生成します。統一された絵柄でファンタジー機関車ゲームの画面を作成します。 🛠️ Using tool: generate_image (trusted) from mcp server awslabs.nova-canvas-mcp-server ⋮ ● Running generate_image with the param: ⋮ "negative_prompt": "people, anatomy, hands, low quality, low resolution, low detail, realistic photo, 3D render", ⋮ "prompt": "fantasy 2D game train creation interface, steampunk locomotive parts selection screen, colorful train components, magical engine parts, passenger cars, cargo cars, dog character mascot, material inventory panel, craft button, game UI style",   コンテキストで会話の履歴を保存するよう依頼していたため、以下のように保存してくれました。 > ## 7. 最終的な会話履歴の保存 🛠️ Using tool: fs_write (trusted) ⋮ ● Path: chat_history.json ⋮ ↳ Purpose: 会話履歴をJSONファイルに保存 Replacing: chat_history.json   10分も掛からずに対応が完了しました。 > ## 対応完了報告 ファンタジー機関車街づくりゲームの開発資料を完全に作成いたしました。 ### 作成した資料一覧 1. 概要資料 (347文字) - ゲームの基本コンセプト 2. 企画書 - 著作権配慮事項と月額費用見積もり($13.20)を含む詳細企画 3. 要件定義書 - 機能要件・非機能要件の詳細定義 4. 設計書 - 基本設計と詳細設計(2ファイルに分割) 5. 構成図 - 3種類のシステム構成図(英語表記) 6. ゲーム画面サンプル - 10枚の実際のゲーム画面イメージ ### 主要な特徴 • **AWS サーバレス構成**:Amplify Gen2をベースとした低コスト運用 • **CloudTrail連携**:API実行履歴による自動素材収集システム • **著作権配慮**:トーマスとFF9の要素を参考にしつつ完全オリジナル • **統一された絵柄**:Bedrock Nova microによる一貫したアートスタイル • **協力システム**:最大5人での協力プレイ対応 ### 想定月額費用 約$13.20(約1,800円)で運用可能な設計となっています。 すべての資料は/■■■■■■■/fantasy-train-city-game/フォルダに整理して保存いたしました。ゲーム開発の次のステップに進む準備が整いました!   成果物 概要資料 注文通り500文字以内でまとめてくれました。 # ファンタジー機関車街づくりゲーム 概要資料 ## ゲーム概要 犬のキャラクターが主人公となり、ファンタジー世界で機関車を中心とした街づくりを行うブラウザゲーム。プレイヤーのAWS CloudTrailイベント履歴を素材収集のトリガーとする放置ゲーム要素を持つ。 ## 主要機能 - 見下ろし型2Dの街づくりシステム - 機関車の作成・カスタマイズ機能 - 機関車での移動システム - リアルタイムチャット機能 - 協力プレイ対応掲示板システム - CloudTrail連携による自動素材収集 ## 技術構成 AWS Amplify Gen2をベースとしたサーバレス構成。Amazon Bedrock Nova microを活用した画像生成により統一された絵柄を実現。同時接続5人以下の小規模運用に最適化。 ## 特徴 著作権に配慮したオリジナルデザインによる機関車とファンタジー要素の融合。放置ゲーム要素により気軽にプレイ可能。協力システムによるコミュニティ形成を促進。   企画書 「3.3 その他の配慮」までの内容を見るとゲームを作るという実感が湧きますね。 こちらから依頼したことは”企画書”と必須事項だけでしたが、スケジュールやリスクまで整理してくれました。 # ファンタジー機関車街づくりゲーム 企画書 ## 1. ゲームコンセプト ### 1.1 基本コンセプト 「犬の冒険者が機関車と共に創る、ファンタジー街づくりゲーム」 ### 1.2 ターゲット - 年齢層:20-40代 - 放置ゲーム愛好者 - 街づくりゲーム愛好者 - AWS利用者(CloudTrail連携機能) ## 2. ゲーム内容 ### 2.1 基本システム - **視点**: 見下ろし型2D - **プラットフォーム**: ブラウザゲーム(PC専用) - **ジャンル**: 街づくり + コミュニケーション + 放置ゲーム ### 2.2 主要機能 1. **街づくりシステム**  - 建物の配置・管理  - 道路・線路の敷設  - 景観の装飾 2. **機関車システム**  - 機関車の設計・作成  - パーツのカスタマイズ  - 機関車での移動・探索 3. **素材収集システム**  - CloudTrail API実行履歴による自動素材獲得  - 放置時間による素材蓄積  - レア素材の確率的獲得 4. **コミュニケーションシステム**  - リアルタイムチャット  - 掲示板での情報交換  - 協力プレイシステム ### 2.3 キャラクター設定 - **主人公**: 犬のキャラクター(種族・色・アクセサリーのカスタマイズ可能) - **NPCキャラクター**: ファンタジー世界の住民(エルフ、ドワーフ等) ## 3. 著作権配慮事項 ### 3.1 機関車要素の著作権配慮 **参考作品**: きかんしゃトーマス **配慮内容**: - 顔のある機関車デザインは避け、無機物としての機関車を採用 - カラーリングは独自の配色パターンを使用 - 形状は現実の蒸気機関車をベースとしたオリジナルデザイン - キャラクター性は犬の主人公に集約 ### 3.2 ファンタジー要素の著作権配慮 **参考作品**: ファイナルファンタジーIX **配慮内容**: - 世界観は中世ヨーロッパ風ファンタジーの一般的要素を採用 - 建築様式は実在する中世建築をベース - 魔法・モンスター等は一般的なファンタジー要素に留める - 独自のストーリー・設定を構築 ### 3.3 その他の配慮 - 使用する音楽・効果音は著作権フリー素材またはオリジナル作成 - フォント・UI要素もオープンソースまたはオリジナル - 第三者の商標・意匠権に抵触しないよう注意 ## 4. 技術仕様 ### 4.1 基盤技術 - **フロントエンド**: React + TypeScript - **バックエンド**: AWS Amplify Gen2 - **データベース**: Amazon DynamoDB - **認証**: Amazon Cognito - **リアルタイム通信**: AWS AppSync (GraphQL Subscriptions) ### 4.2 AWS サービス構成 - **Amplify Gen2**: アプリケーション基盤 - **DynamoDB**: ゲームデータ保存 - **Lambda**: ビジネスロジック処理 - **AppSync**: GraphQL API・リアルタイム通信 - **Cognito**: ユーザー認証・管理 - **CloudTrail**: API履歴取得 - **Bedrock Nova micro**: 画像生成 - **S3**: 静的ファイル保存 - **CloudFront**: CDN ## 5. 想定月額費用 ### 5.1 前提条件 - 同時接続ユーザー数: 最大5人 - 月間アクティブユーザー: 10-20人 - 1日あたりAPI呼び出し: 1,000回程度 - ストレージ使用量: 10GB程度 ### 5.2 サービス別費用見積もり(東京リージョン) | サービス | 使用量 | 月額費用(USD) | |---------|--------|----------------| | Amplify Hosting | 10GB転送 | $1.50 | | DynamoDB | 1GB保存 + 100万RCU/WCU | $2.50 | | Lambda | 10万実行 + 1GB-秒 | $0.20 | | AppSync | 100万リクエスト | $4.00 | | Cognito | 20MAU | $0.00 (無料枠) | | CloudTrail | 基本ログ | $0.00 (無料枠) | | Bedrock Nova micro | 1,000画像生成 | $3.00 | | S3 | 10GB保存 + 転送 | $1.00 | | CloudFront | 10GB転送 | $1.00 | **合計想定月額費用: 約 $13.20 (約1,800円)** ### 5.3 コスト最適化施策 - DynamoDBオンデマンド課金でスケール調整 - Lambda関数の実行時間最適化 - S3 Intelligent-Tieringによる自動コスト最適化 - CloudFrontキャッシュ設定による転送量削減 ## 6. 開発スケジュール ### Phase 1: 基盤構築(4週間) - Amplify Gen2環境構築 - 認証システム実装 - 基本UI/UX実装 ### Phase 2: コア機能実装(6週間) - 街づくりシステム - 機関車システム - CloudTrail連携 ### Phase 3: コミュニケーション機能(4週間) - チャット機能 - 掲示板機能 - 協力システム ### Phase 4: 画像生成・最適化(2週間) - Bedrock Nova micro連携 - 画像統一・最適化 - パフォーマンス調整 **総開発期間: 16週間** ## 7. リスク・課題 ### 7.1 技術的リスク - CloudTrail API制限による素材収集頻度の制約 - リアルタイム通信の安定性 - ブラウザ間の互換性 ### 7.2 運用リスク - 少人数での運用体制 - セキュリティ対策 - データバックアップ・復旧 ### 7.3 対策 - 段階的リリースによるリスク軽減 - 自動化による運用負荷軽減 - AWS Well-Architected Frameworkに基づく設計 要件定義書 長いので抜粋します。 実業務を想定した場合だと、丸ごとそのまま使えるということは無いと思いますが、たたき台としては使えますね。 数分で作ってくれるというのが良いですね。 # ファンタジー機関車街づくりゲーム 要件定義書 ## 1. システム概要 ### 1.1 システム名 ファンタジー機関車街づくりゲーム ### 1.2 システムの目的 AWS CloudTrail連携による放置ゲーム要素を持つ、機関車をテーマとしたファンタジー街づくりブラウザゲームの提供 ### 1.3 システムの範囲 - ゲームクライアント(ブラウザ) - ゲームサーバー(AWS サーバレス構成) - 管理機能 ## 2. 機能要件 ### 2.1 ユーザー管理機能 #### 2.1.1 ユーザー登録・認証 - **機能名**: ユーザー登録 - **概要**: メールアドレスとパスワードによるユーザー登録 - **詳細**: - メールアドレス認証必須 - パスワード強度チェック - 利用規約同意確認 #### 2.1.2 ログイン・ログアウト #### 2.1.3 プロフィール管理 ### 2.2 街づくり機能 #### 2.2.1 マップ表示 #### 2.2.2 建物配置 #### 2.2.3 道路・線路敷設 ### 2.3 機関車システム #### 2.3.1 機関車作成 #### 2.3.2 機関車運行 #### 2.3.3 機関車搭乗 ### 2.4 素材収集システム #### 2.4.1 CloudTrail連携 - **機能名**: CloudTrail連携 - **概要**: AWS CloudTrailイベント履歴による素材獲得 - **詳細**: - API実行履歴の取得 - イベント種別による素材種類決定 - 獲得量の計算 #### 2.4.2 放置収集 #### 2.4.3 素材管理 ### 2.5 コミュニケーション機能 #### 2.5.1 チャット機能 #### 2.5.2 掲示板機能 #### 2.5.3 協力システム ### 2.6 画像生成機能 #### 2.6.1 統一画像生成 - **機能名**: Bedrock画像生成 - **概要**: Amazon Bedrock Nova microによる統一された画像生成 - **詳細**: - 建物・機関車の画像生成 - 統一されたアートスタイル - 動的画像生成 ## 3. 非機能要件 ### 3.1 性能要件 #### 3.1.1 応答時間 - 画面遷移: 3秒以内 - API応答: 1秒以内 - チャット送信: 0.5秒以内 #### 3.1.2 同時接続数 - 最大同時接続: 5ユーザー - 想定アクティブユーザー: 10-20人/月 #### 3.1.3 可用性 - システム稼働率: 99.0%以上 - 計画メンテナンス: 月1回、2時間以内 ### 3.2 セキュリティ要件 #### 3.2.1 認証・認可 - JWT トークンベース認証 - セッション管理 - API アクセス制御 #### 3.2.2 データ保護 - 通信暗号化(HTTPS/WSS) - 個人情報の適切な管理 - CloudTrail データの安全な取り扱い #### 3.2.3 入力検証 - XSS対策 - SQLインジェクション対策 - CSRF対策 ### 3.3 運用要件 #### 3.3.1 監視 - システム稼働監視 - エラーログ監視 - パフォーマンス監視 #### 3.3.2 バックアップ - データベース日次バックアップ - 設定ファイルのバージョン管理 - 災害復旧手順 #### 3.3.3 スケーラビリティ - サーバレス構成による自動スケーリング - データベースの読み取り性能最適化 ### 3.4 互換性要件 #### 3.4.1 ブラウザ対応 - Chrome 90以降 - Firefox 88以降 - Safari 14以降 - Edge 90以降 #### 3.4.2 画面解像度 ## 4. 制約事項 ### 4.1 技術制約 - AWS サーバレス構成必須 - Amplify Gen2 使用必須 - Bedrock Nova micro 使用必須 ### 4.2 コスト制約 ### 4.3 法的制約 ## 5. 前提条件・仮定 ### 5.1 前提条件 - ユーザーはAWSアカウントを保有 - CloudTrail が有効化されている - インターネット接続環境 ### 5.2 仮定 - 同時接続数は5人以下で運用 - ユーザーは基本的なブラウザ操作が可能 - 日本語環境での利用 ## 6. 用語定義 設計書_基本設計 こちらも長いので抜粋。 # ファンタジー機関車街づくりゲーム 設計書(基本設計) ## 1. システム全体設計 ### 1.1 システム構成概要 [ブラウザクライアント]   ↓ HTTPS/WSS [CloudFront]   ↓ [Amplify Gen2 Hosting]   ↓ GraphQL/REST [AppSync API Gateway]   ↓ [Lambda Functions] ← [DynamoDB] ← [S3]   ↓ [Bedrock Nova micro] / [CloudTrail] ### 1.2 アーキテクチャパターン - **フロントエンド**: SPA (Single Page Application) - **バックエンド**: サーバレス + マイクロサービス - **データベース**: NoSQL (DynamoDB) - **通信**: GraphQL + WebSocket ### 1.3 技術スタック #### フロントエンド - **フレームワーク**: React 18 + TypeScript - **状態管理**: Redux Toolkit - **UI ライブラリ**: Material-UI - **2D描画**: Konva.js / Fabric.js - **リアルタイム通信**: GraphQL Subscriptions #### バックエンド - **API**: AWS AppSync (GraphQL) - **認証**: Amazon Cognito - **データベース**: Amazon DynamoDB - **ファイルストレージ**: Amazon S3 - **画像生成**: Amazon Bedrock Nova micro - **ログ取得**: AWS CloudTrail ## 2. データベース設計 ### 2.1 DynamoDB テーブル設計 #### 2.1.1 Users テーブル #### 2.1.2 Cities テーブル #### 2.1.3 Trains テーブル #### 2.1.4 Materials テーブル #### 2.1.5 ChatMessages テーブル #### 2.1.6 ForumPosts テーブル #### 2.1.7 Collaborations テーブル ### 2.2 GSI (Global Secondary Index) 設計 #### 2.2.1 ChatMessages-ByUser-Index #### 2.2.2 ForumPosts-ByCategory-Index ## 3. API設計 ### 3.1 GraphQL スキーマ設計 #### 3.1.1 User関連 #### 3.1.2 City関連 #### 3.1.3 Train関連 ### 3.2 Mutation設計 #### 3.2.1 ユーザー管理 #### 3.2.2 街づくり #### 3.2.3 機関車 ### 3.3 Subscription設計 #### 3.3.1 リアルタイム通信 ## 4. Lambda関数設計 ### 4.1 関数一覧 #### 4.1.1 ユーザー管理関数 #### 4.1.2 CloudTrail連携関数 #### 4.1.3 素材管理関数 #### 4.1.4 画像生成関数 #### 4.1.5 ゲームロジック関数 ### 4.2 関数詳細設計 #### 4.2.1 CloudTrail連携関数 # 疑似コード #### 4.2.2 画像生成関数 # 疑似コード ## 5. セキュリティ設計 ### 5.1 認証・認可 ### 5.2 データアクセス制御 ### 5.3 入力検証 ### 5.4 通信セキュリティ 設計書_詳細設計 こちらも長いので抜粋。 # ファンタジー機関車街づくりゲーム 設計書(詳細設計) ## 6. フロントエンド設計 ### 6.1 コンポーネント設計 #### 6.1.1 画面構成 App ├── AuthWrapper │   ├── LoginPage │   └── RegisterPage └── GameWrapper   ├── Header #### 6.1.2 状態管理設計 ### 6.2 2D描画エンジン設計 ### 6.3 リアルタイム通信設計 ## 7. バックエンド詳細設計 ### 7.1 AppSync Resolver設計 ### 7.2 DynamoDB アクセスパターン ### 7.3 CloudTrail連携詳細設計 ### 7.4 画像生成詳細設計 ## 8. パフォーマンス最適化設計 ### 8.1 フロントエンド最適化 ### 8.2 バックエンド最適化 ## 9. 監視・ログ設計 ### 9.1 CloudWatch メトリクス ### 9.2 構造化ログ ## 10. エラーハンドリング設計 ### 10.1 フロントエンドエラーハンドリング ### 10.2 バックエンドエラーハンドリング 構成図 システム設計、データフロー、CloudTrail統合フローの3つを作ってくれました。 以下はシステム設計です。 ゲーム画面サンプル どれも良い感じに生成してくれたので、小さくして全部貼り付けます。 なぜか人間がいたり、文字がおかしかったりしますが、依頼したテーマに沿っているかと思います。   感想 良かったことは、手元の環境でAmazon QとMCPを設定できたことと、MCPが機能するところを見れたことです。 特にawslabs.nova-canvas-mcp-serverは面白かったです。成果物が非常分かりやすいので。 要件定義書や設計書については、たたき台を数分で作ってくれること、および自分が気づかないことも項目として挙げてくれることが良かったことです。 反対に良くなかったことは、資料作成の後に実際にゲームをAmazon Qに作ってもらったのですが、ログインはできたものの、想定よりもシンプルすぎたことと、不具合が酷すぎたので、当記事では記載の対象外にしたことです。 Vibe-Codingするにしても自分に知識やスキルがないとAmazon Qを上手くハンドリングできないことを実感しました。 納得のいくゲームができたら別記事でご紹介するようにします。
アバター
本記事は 夏休みクラウド自由研究2025 8/17付の記事です 。 「AWSの最新情報を効率的にキャッチアップしたい…」 「毎日Qiitaをチェックするのは大変だけど、重要なトレンドは見逃したくない…」 エンジニアにとって、最新技術の動向を追い続けることは非常に重要! ですが、忙しい日々の中で効率的に情報収集するのは難しい課題ですよね。 この記事では、そんな悩みを解決するために、 AWSのサービスを組み合わせて、Qiitaに投稿されたAWS関連の最新記事から「おすすめトピックス」を自動で抽出し、毎日メールで通知してくれるシステム を構築する方法を、ステップ・バイ・ステップで解説します! サーバー管理は一切不要で、一度作ればあとは自動であなたのために働いてくれる便利なシステムです。AWSの学習にもぴったりのテーマなので、ぜひ一緒に作ってみましょう! この記事で構築するもの 完成するシステムの全体像は以下の通りです。 毎日決まった時刻に … EventBridge がシステムを起動します。 QiitaからAWS記事を自動収集 … Lambda がQiita APIを叩いて最新記事を取得し、 S3 に保存します。 AIが記事を分析 … S3 への保存をトリガーに別の Lambda が起動し、 Amazon Comprehend が記事の内容を分析して、頻出する技術キーワード(トピックス)を抽出します。 結果をメールで通知 …抽出したトピックスと記事リストをまとめて、 SNS があなたのメールアドレスに送信します。   準備するもの 構築を始める前に、以下の3つをご準備忘れずに! AWSアカウント : 無料利用枠の範囲内でほとんど試せます。 IAMユーザー : セキュリティのため、管理者権限を持つIAMユーザーで作業しましょう。 Qiitaのアクセストークン : Qiita APIを利用するために必要です。 こちら の「個人用アクセストークン」から read_qiita スコープで発行してください。発行したトークンは後で使いますので、メモしておきましょう。 準備はOKですか?それでは、さっそく構築を始めましょう!   Step 1. データの保管庫!S3バケットを作成しよう まずは、取得したQiitaの記事データを保存しておくための場所(ストレージ)を用意します。AWSでは、このようなオブジェクトストレージサービスとしてAmazon S3を利用します。 AWSマネジメントコンソールで「 S3 」を検索し、ダッシュボードを開きます。 「 バケットを作成 」をクリック。 バケット名 に世界で一意な名前を付けます(例: qiita-aws-articles-自分の名前-日付 )。 リージョン は、基本的にすべてのサービスで同じ「アジアパシフィック (東京) ap-northeast-1 」に統一しましょう。 あとはデフォルト設定のまま「 バケットを作成 」すれば完了です。ここまでは簡単ですかね…!   Step 3 通知システムの中核!SNSトピックを作成 次に、分析結果をメールで送信するための通知システムをAmazon SNSで構築していきます。 コンソールで「 SNS 」を検索し、ダッシュボードへ。 左メニューの「 トピック 」から「 トピックの作成 」をクリック。 タイプは「 スタンダード 」、名前は「 QiitaAwsTopicsNotifier 」など分かりやすいものを設定します。 トピック作成後、「 サブスクリプションの作成 」ボタンを押します。 プロトコル で「 Eメール 」を選び、 エンドポイント に通知を受け取りたいメールアドレスを入力します。 「サブスクリプションの作成」をクリックすると、入力したアドレスに確認メールが届きます。メール内の「 Confirm subscription 」リンクをクリックして承認を忘れずに行いましょう。   Step 3. IAMロールを作成 AWSのサービス同士が連携するには、「IAMロール」という権限設定が必要です。今回は2つのLambda関数を作るので、それぞれに必要な権限を持ったロールを2つ作成します。 コンソールで「 IAM 」を検索し、ダッシュボードへ。 左メニューの「 ロール 」から「 ロールを作成 」をクリックします。 信頼されたエンティティタイプ は「AWSのサービス」、 ユースケース は「Lambda」を選択します。 記事取得用ロール ( QiitaArticleGetRole ) 許可ポリシー : AWSLambdaBasicExecutionRole (Lambdaの基本的な実行ログを書き込む権限) AmazonS3FullAccess (S3にファイルを書き込む権限) →(慣れてきたら、特定のバケットにのみ書き込めるように権限を絞ると、よりセキュアになります) トピック抽出用ロール ( QiitaTopicExtractRole ) 許可ポリシー : AWSLambdaBasicExecutionRole AmazonS3FullAccess (S3からファイルを読み込む権限) ComprehendReadOnly (Amazon Comprehendでテキスト分析を行う権限) AmazonSNSFullAccess (SNSで通知を送信する権限) それぞれポリシーを選択したら、分かりやすいロール名を付けて作成してください。 Step 4. 収集用Lambda関数の作成 いよいよメイン機能の実装です。まずはQiitaから記事データを取得するLambda関数を作成します。 コンソールで「 Lambda 」を検索し、「 関数の作成 」をクリック。 以下の設定で関数を作成します。 オプション : 「一から作成」 関数名 : getQiitaArticles ランタイム : 「Python 3.13」以降 実行ロール : 「既存のロールを使用する」を選び、先ほど作成した QiitaArticleGetRole を選択。 関数が作成されたら、「 コードソース 」エディタに以下のPythonコードを貼り付けます。        import os import json import urllib.request import urllib.parse from datetime import datetime import boto3 # 環境変数から設定値を取得 QIITA_API_TOKEN = os.environ['QIITA_API_TOKEN'] S3_BUCKET_NAME = os.environ['S3_BUCKET_NAME'] s3 = boto3.client('s3') def lambda_handler(event, context):   print("Fetching articles from Qiita using urllib...")     headers = {'Authorization': f'Bearer {QIITA_API_TOKEN}'}   # "AWS"タグがついており、LGTMが10以上の記事を50件取得 (取得件数を増やして分析精度を向上)   query_params = {'page': '1', 'per_page': '50', 'query': 'tag:AWS stocks:>10'}     # URLエンコード   encoded_params = urllib.parse.urlencode(query_params)   url = f'https://qiita.com/api/v2/items?{encoded_params}'     req = urllib.request.Request(url, headers=headers, method='GET')     try:       with urllib.request.urlopen(req) as res:           # ステータスコードのチェック           if res.status >= 400:               print(f"Error: Received status code {res.status}")               raise urllib.error.HTTPError(res.url, res.status, res.reason, res.headers, res.fp)                     response_body = res.read().decode('utf-8')           articles = json.loads(response_body)       if not articles:           print("No articles found.")           return {'statusCode': 200, 'body': 'No articles found.'}       today = datetime.utcnow().strftime('%Y-%m-%d')       file_name = f'articles/{today}.json'       s3.put_object(           Bucket=S3_BUCKET_NAME,           Key=file_name,           Body=json.dumps(articles, ensure_ascii=False, indent=2),           ContentType='application/json'       )       print(f"Successfully saved {len(articles)} articles to s3://{S3_BUCKET_NAME}/{file_name}")       return {'statusCode': 200, 'body': f'Successfully saved {len(articles)} articles.'}   except urllib.error.HTTPError as e:       print(f"Error fetching from Qiita API: {e}")       error_content = e.read().decode('utf-8')       print(f"Error details: {error_content}")         raise e 次に、「 設定 」タブ -> 「 環境変数 」で、以下の2つの変数を設定しまておきましょう。 キー : QIITA_API_TOKEN , 値 : (準備したQiitaアクセストークン) キー : S3_BUCKET_NAME , 値 : (Step 1で作成したS3バケット名) 最後に、タイムアウト対策として「 設定 」タブ -> 「 一般設定 」でタイムアウトを 1分 に延長しておきましょう。 忘れずに「 Deploy 」ボタンをクリックして、変更を保存します。   Step 5: AIで分析&通知! トピック抽出用Lambda関数の作成 次に、S3に保存された記事データをAIで分析し、結果をSNSで通知する2つ目のLambda関数を作成します。 先ほどと同様に、新しいLambda関数を作成します。 関数名 : extractTopicsAndNotify ランタイム : 「Python 3.13」以降 実行ロール : QiitaTopicExtractRole を選択 「 コードソース 」に以下のコードを貼り付けます。Amazon Comprehendを呼び出してキーフレーズを抽出し、結果を整形してSNSに送信する処理です。 ※頻出ワードに接続詞等を含まないようにするために少々汚いコードを書いています…。改善しますね…。         import os import boto3 import json import re from collections import Counter # 環境変数から設定値を取得 SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN'] s3 = boto3.client('s3') comprehend = boto3.client('comprehend', region_name='ap-northeast-1') sns = boto3.client('sns') # 除外する一般的な単語(ストップワード)のリスト STOP_WORDS = set([   "これ", "それ", "あれ", "この", "その", "あの", "ここ", "そこ", "あそこ", "こちら",   "ため", "よう", "こと", "もの", "とき", "ところ", "うち", "ほう", "わけ", "はず",   "さん", "くん", "ちゃん", "さま", "たち", "など", "ほか", "どう", "なに", "なぜ",   "いつ", "どこ", "だれ", "どれ", "ほう", "以上", "以下", "未満", "以前", "以後",   "今回", "次回", "本日", "明日", "昨日", "今日", "これら", "すべて", "一部", "全体",   "一つ", "二つ", "三つ", "最初", "最後", "記事", "方法", "手順", "設定", "確認",   "問題", "解決", "方法", "注意", "事項", "内容", "部分", "情報", "利用", "作成",   "実行", "結果", "必要", "場合", "影響", "機能", "処理", "自分", "皆さん", "こんにちは" ]) def clean_text_for_summary(text):   """概要用にテキストからMarkdownや改行、URLなどを除去する"""   text = re.sub(r'https?://\S+', '', text)  # URLを除去   text = re.sub(r'!\[.*?\]\(.*?\)', '', text)  # Markdown画像を除去   text = re.sub(r'#+\s?', '', text)  # 見出しを除去   text = re.sub(r'[`\*_]', '', text)  # Markdown装飾を除去   text = text.replace('\n', ' ').replace('\r', '') # 改行をスペースに置換   return text.strip() def is_valid_topic(phrase):   """トピックとして相応しいか判定する"""   if len(phrase) <= 2 and not re.search(r'^[A-Z0-9]+$', phrase): # 2文字以下は基本除外 (S3などの大文字略語は許可)       return False   if phrase in STOP_WORDS: # ストップワードに含まれていたら除外       return False   if re.search(r'[!-/:-@[-`{-~]', phrase): # 記号が多く含まれるものは除外       return False   if phrase.isdigit(): # 数字のみは除外       return False   return True def lambda_handler(event, context):   bucket = event['Records'][0]['s3']['bucket']['name']   key = event['Records'][0]['s3']['object']['key']   print(f"Processing file: s3://{bucket}/{key}")   response = s3.get_object(Bucket=bucket, Key=key)   articles = json.loads(response['Body'].read().decode('utf-8'))   all_phrases = []   for article in articles:       text_to_analyze = (article['title'] + " " + article['body'])[:4900]       try:           comp_response = comprehend.detect_key_phrases(Text=text_to_analyze, LanguageCode='ja')           phrases = [p['Text'] for p in comp_response['KeyPhrases']]           all_phrases.extend(phrases)       except Exception as e:           print(f"Error analyzing article {article['id']}: {e}")           continue     # トピックをフィルタリングして頻度順に並べる   topic_counts = Counter(p for p in all_phrases if is_valid_topic(p))   hot_topics = [topic for topic, count in topic_counts.most_common(20)] # 上位20件   # おすすめ記事をLGTM数でソートして上位3件選出   sorted_articles = sorted(articles, key=lambda x: x.get('likes_count', 0), reverse=True)   recommended_articles = sorted_articles[:3]   # --- メッセージ作成 ---   subject = "【自動通知】本日のQiita AWSおすすめトピックス"     # 1. ホットトピックス   hot_topics_text = "特に注目されたトピックスはありませんでした。"   if hot_topics:       hot_topics_text = ", ".join(hot_topics)   # 2. おすすめ記事   recommended_articles_text = ""   for i, article in enumerate(recommended_articles):       summary = clean_text_for_summary(article['body'])[:120] + "..."       recommended_articles_text += f"{i+1}. {article['title']}\n"       recommended_articles_text += f"概要: {summary}\n"       recommended_articles_text += f"URL: {article['url']}\n\n"   # 3. 全記事リスト   all_articles_text = "\n\n".join([f"- {a['title']}\n  {a['url']}" for a in sorted_articles])   # メッセージを結合   message = (       "本日注目されたAWS関連のトピックスと、おすすめ記事です。\n\n"       "------------------------------------\n"       "▼ 今日のホットトピックス\n"       f"{hot_topics_text}\n\n"       "------------------------------------\n"       "▼ 本日のおすすめ記事\n\n"       f"{recommended_articles_text}"       "------------------------------------\n"       "▼ 新着記事一覧\n"       f"{all_articles_text}"   )     print("Publishing to SNS topic...")   sns.publish(TopicArn=SNS_TOPIC_ARN, Message=message, Subject=subject)     return {'statusCode': 200, 'body': 'Successfully extracted topics and notified.'} このLambda関数は、S3にファイルが置かれたら自動で動いてほしいので、 トリガーを設定 します。 関数デザイナー画面の「 トリガーを追加 」をクリック。 ソースに「 S3 」を選択。 バケット にStep 1で作成したバケットを指定。 プレフィックス に articles/ と入力し、このフォルダ内でのイベントのみを検知するようにします。 警告を確認するチェックを入れて「 追加 」。 こちらも環境変数を設定します。「 設定 」タブ -> 「 環境変数 」で以下を追加します。 キー : SNS_TOPIC_ARN , 値 : (Step 2で作成したSNSトピックのARN) タイムアウトを 3分 程度に延長し、「 Deploy 」で保存します。   Step 6. EventBridgeで定期実行を設定 最後の仕上げです! getQiitaArticles 関数を毎日自動で実行するためのスケジュールを設定します。 コンソールで「 EventBridge 」を検索し、ダッシュボードへ。 「 ルールを作成 」をクリック。 名前 に「 RunQiitaArticleGetterDaily 」などを入力。 ルールタイプ で「 スケジュール 」を選択。 スケジュールパターン で、実行頻度を設定します。例えば、毎日朝9時(JST)に実行したい場合は、Cron式 0 0 * * ? * を入力します (EventBridgeのスケジュールはUTC基準なので、日本時間の9時はUTCの0時です)。 ターゲット として「 Lambda 関数 」を選び、関数に getQiitaArticles を指定します。 あとはデフォルトのまま進み、「 ルールを作成 」すれば完了です!    Step7. 動作確認 お疲れ様でした!これでシステムは完成です。 getQiitaArticles 関数のテスト機能を使って手動で一度実行してみましょう。成功すれば、S3にファイルが作成され、それをトリガーに extractTopicsAndNotify が動き、数分後にあなたの元へ分析結果のメールが届くはずです! メールが届けば大成功!あとは設定した時刻になれば、毎日自動で情報が届きます。  発展:こんな使い方もできるかも…? このシステムは、さらにカスタマイズすることも可能です。 Slackに通知する : SNSの代わりにLambdaから直接Slack APIを叩くように変更。 トピックをDBに保存 : DynamoDBにトピックを保存し、長期的なトレンドを分析する。 Webページで結果を公開 : S3の静的ウェブサイトホスティング機能を使って、結果をWebページとして公開する。 分析精度を向上 : LGTM数や記事の長さを考慮してトピックのスコア付けを行う。 まとめ 今回は、AWSのサーバーレスサービスを組み合わせ、Qiitaの技術トレンドを自動で収集・分析・通知するシステムを構築しました。 このプロジェクトを通して、以下のAWSサービスの基本的な連携方法を学ぶことができたのではないでしょうか…? サーバーレスアーキテクチャは、このような「ちょっとした自動化」や「データ処理パイプライン」の構築に非常に便利です!ぜひ、これを機にあなただけの便利な自動化システム作りに挑戦してみてください!  
アバター
こんにちは、広野です。 生成 AI 界隈の技術の進化がすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。 今回 (4回目) は実装編 その3 React アプリ開発編です。 前回の記事 アーキテクチャ概要については前回記事で紹介しています。こちらをご覧ください。 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] アーキテクチャ概要編 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (初回) はアーキテクチャ概要編です。 blog.usize-tech.com 2025.07.30   バックエンド実装については以下です。 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] 実装編1 Amazon Cognito 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (2回目) は Amazon Cognito 実装編です。 blog.usize-tech.com 2025.08.15 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] 実装編2 API作成 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (3回目) は API 作成編です。 blog.usize-tech.com 2025.08.15   今回の説明範囲 本記事では、以下の UI コード (React) を説明します。前回記事の Amazon Bedrock 問い合わせ用 API ができていることが前提となります。   サマリー 重要なポイントは、AWS AppSync Events API をサブスクライブすることです。それによりリアルタイムに Amazon Bedrock からのストリームレスポンスを画面に表示できます。 AWS の公式ドキュメントでは、以下にリファレンスがあります。基本的にはこれに従っています。 Connect to AWS AppSync Events - React - AWS Amplify Gen 2 Documentation Connect to AWS AppSync Events AWS Amplify Documentation docs.amplify.aws   React コード (抜粋)  App.jsx App.jsx など上位のソースファイルに以下のように Amazon Cognito と AWS AppSync Events リソースと連携するための設定を書きます。すみません、私のコードでは環境変数を使っています。 import { Amplify } from 'aws-amplify'; import { Authenticator, useAuthenticator } from '@aws-amplify/ui-react'; //Cognito, AppSync Events 連携設定 Amplify.configure({ Auth: { Cognito: { userPoolId: import.meta.env.VITE_USERPOOLID, userPoolClientId: import.meta.env.VITE_USERPOOLWEBCLIENTID, identityPoolId: import.meta.env.VITE_IDPOOLID } }, API: { Events: { endpoint: import.meta.env.VITE_APPSYNCEVENTSHTTPENDPOINT, region: import.meta.env.VITE_REGION, defaultAuthMode: 'userPool' }, }, }); チャットボット画面  import { useState, useEffect, useRef } from "react"; import { Container, Grid, Box, Paper, Typography, TextField, Button, Avatar } from "@mui/material"; import SendIcon from '@mui/icons-material/Send'; import { blue, grey } from '@mui/material/colors'; import { v4 as uuidv4 } from "uuid"; import { events } from "aws-amplify/data"; import { inquireBedrockSr, Markdown } from "./Functions.jsx"; import Header from "../Header.jsx"; import Menu from "./Menu.jsx"; const BedrockSr = (props) => { //定数定義 const groups = props.groups; const sub = props.sub; const idtoken = props.idtoken; const imgUrl = import.meta.env.VITE_IMG_URL; //変数定義 const sessionidRef = useRef(); const channelRef = useRef(); //state定義 const [prompt, setPrompt] = useState(""); const [response, setResponse] = useState(""); const [conversation, setConversation] = useState([]); //Bedrockへの問い合わせ送信関数 const putBedrockSr = () => { //前のレスポンスがあれば配列にappendしてクリア if (response) { setConversation((prev) => [ ...prev, { role: "ai", text: response, } ]); setResponse(""); } setConversation((prev) => [ ...prev, { role: "user", text: prompt, } ]); inquireBedrockSr(prompt, sessionidRef.current, idtoken); //プロンプト欄をクリア setPrompt(""); }; //サブスクリプション開始関数 const startSubscription = async () => { const sessionid = sessionidRef.current; if (channelRef.current) await channelRef.current.close(); const channel = await events.connect(`bedrock-stream-response/${sub}/${sessionid}`); channel.subscribe({ next: (data) => { setResponse((prev) => prev + data.event.message); }, error: (err) => console.error("Subscription error:", err), complete: () => { setConversation((prev) => [ ...prev, { role: "ai", text: response, }, ]); setResponse(""); } }); channelRef.current = channel; }; //セッションIDのリセット、サブスクリプション再接続関数 const resetSession = async () => { sessionidRef.current = uuidv4(); //表示中のプロンプト、会話履歴、レスポンスをリセット setPrompt(""); setResponse(""); setConversation([]); await startSubscription(); }; //画面表示時 useEffect(() => { //画面表示時に最上部にスクロール window.scrollTo(0, 0); //Bedrockからのレスポンスサブスクライブ関数実行 sessionidRef.current = uuidv4(); startSubscription(); //アンマウント時にチャンネルを閉じる return () => { if (channelRef.current) channelRef.current.close(); }; }, []); //Chatbot UI 会話部分 const renderMessage = (msg, idx) => ( <Box key={idx} sx={{ display: "flex", justifyContent: msg.role === "user" ? "flex-end" : "flex-start", mb: 1, width: "100%", }} > {msg.role === "ai" && ( <Avatar src={`${imgUrl}/images/ai_chat_icon.svg`} alt="AI" sx={{ mr: 2 }} /> )} <Paper elevation={2} sx={{ p: 2, my: 1, maxWidth: "100%", bgcolor: msg.role === "user" ? blue[100] : grey[100], }} > <Markdown>{msg.text}</Markdown> </Paper> {msg.role === "user" && ( <Avatar src={`${imgUrl}/images/human_chat_icon.svg`} alt="User" sx={{ ml: 2 }} /> )} </Box> ); return ( <> {/* Header */} <Header groups={groups} signOut={props.signOut} /> <Container maxWidth="lg" sx={{mt:2}}> <Grid container spacing={4}> {/* Menu Pane */} <Grid size={{xs:12,md:4}} order={{xs:2,md:1}}> {/* Sidebar */} <Menu /> </Grid> {/* Contents Pane IMPORTANT */} <Grid size={{xs:12,md:8}} order={{xs:1,md:2}} my={2}> <main> <Grid container spacing={2}> {/* Heading */} <Grid size={{xs:12}}> <Typography id="bedrocksrtop" variant="h5" component="h1" mb={3} gutterBottom>Amazon Bedrock Stream Response テスト</Typography> </Grid> <Grid size={{xs:12}}> {/* Chatbot */} <Paper sx={{p:2,mb:2,width:"100%"}}> {/* あいさつ文(固定) */} {renderMessage({ role: "ai", text: "こんにちは。何かお困りですか?" }, -1)} {/* 会話部分 */} {conversation.map((msg, idx) => renderMessage(msg, idx))} {/* 直近のレスポンス */} {response && renderMessage({ role: "ai", text: response }, "stream") } </Paper> {/* 入力エリア */} <Box sx={{display:"flex",gap:1}}> <TextField fullWidth multiline value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="Type message here..." sx={{ flexGrow: 1 }} /> <Button variant="contained" size="small" onClick={putBedrockSr} disabled={!prompt} startIcon={<SendIcon />} sx={{ whiteSpace: "nowrap", flexShrink: 0 }}>送信</Button> </Box> {/* クリアボタン */} {(response || conversation.length > 0) && ( <Box sx={{ display: "flex", justifyContent: "flex-end", mt: 2 }}> <Button variant="contained" size="small" onClick={resetSession}>問い合わせをクリアする</Button> </Box> )} </Grid> </Grid> </main> </Grid> </Grid> </Container> </> ); }; export default BedrockSr; 解説 ステートは以下を使用しています。 prompt:ユーザー入力中のテキスト (プロンプト) response:Amazon Bedrockから受信中のストリームレスポンス conversation:会話履歴(ユーザーと AI の両方) 以下は useRef を使用しています。問い合わせセッションが変わるたびにリセットされる値なのですが、リセットの度に再レンダーされないようにするためです。 sessionidRef:チャットセッション ID(UUIDで生成) channelRef:サブスクライブ中の AWS AppSync Events チャンネル サブスクリプションの部分は、新しいメッセージを受信すると setResponse で response ステートを更新し、その都度再レンダーがかかる仕様になっています。 Amazon Bedrock への問い合わせは関数化しており、その中身は以下になっています。単純に axios で Amazon API Gateway REST API に POST しているだけです。 //Bedrock問い合わせ関数 import axios from 'axios'; const inquireBedrockSr = async (prompt, sessionid, idtoken) => { const res = await axios.post( import.meta.env.VITE_RESTAPIENDPOINTBEDROCKSR, { prompt: prompt, sessionid: sessionid }, { headers: { "Authorization": idtoken } } ); return res; }; 今回、基盤モデルとして Amazon Nova micro を使用していますが、この場合レスポンスがマークダウン形式で戻ってきます。それを適切に HTML 化して表現したかったので、Markdown 関数を使用して変換をかけています。 import ReactMarkdown from 'markdown-to-jsx'; import { Typography, Link, Box } from '@mui/material'; //Markdown関数 const Markdown = (props) => { const MarkdownListItem = (props) => { return <Box component="li" sx={{ mt: 1, typography: 'body1' }} {...props} />; }; const options = { overrides: { h1: { component: Typography, props: { gutterBottom: true, variant: 'h4', component: 'h1' } }, h2: { component: Typography, props: { gutterBottom: true, variant: 'h5', component: 'h2' } }, h3: { component: Typography, props: { gutterBottom: true, variant: 'h6', component: 'h2' } }, h4: { component: Typography, props: { gutterBottom: true, variant: 'subtitle1', paragraph: true } }, p: { component: Typography, props: { paragraph: true, className: 'txt' } }, a: { component: Link, props: { rel: "noopener", target: "_blank" } }, li: { component: MarkdownListItem } } }; return <ReactMarkdown options={options} {...props} />; };   まとめ いかがでしたでしょうか。 React コードの解説は難しかったので少なくなってしまいました。実際に動いたコードを見て参考にして頂けたらと思います。 本記事が皆様のお役に立てれば幸いです。
アバター
本記事は 夏休みクラウド自由研究2025 8/16付の記事です 。 はじめに Black Hat USA 2025が2025年8月2日~7日にラスベガスのマンダレイ・ベイ・コンベンションセンターで開催され、私は8月6日~7日の期間で参加してきました。本記事では、Black Hatの説明と講演の概要を共有し、Black Hat USA 2024と比較して、今年注目の技術分野を考察したいと思います。 Black Hat USA 2025の Registration Terms & Conditions に則り、本記事ではBlack Hatで発表されたコンテンツを公開することができないため、セッションの詳細やスライドの写真を載せられない旨、ご了承ください。 Black Hat とは Black Hatとは世界中のセキュリティ専門家が集まり、最新技術や研究成果を共有するサイバーセキュリティのカンファレンスです。 開催場所は毎年、アメリカ、アジア、ヨーロッパ、中東&アフリカの4地域で開催され、初心者から上級者まで楽しめるイベントとなっています。今回参加したBlack Hat USAは例年、ラスベガスのマンダレイ・ベイ・コンベンションセンターで開催されるようで、今年も例年通り同じ会場での開催でした。 ラスベガスもBlack Hatで賑わっている様子でタクシーに乗ると、運転手からBlack Hatに参加予定かを聞かれることが数回あったり、ラスベガスの街中やハリー・リード国際空港の中でBlack Hat出店企業が大きな映像広告を流していたりしました。 ハリー・リード国際空港でのSentinelOneの広告   開催されたイベント Black Hatで開催されたイベントは以下の通りです。 私はBlack Hatのメインイベントであるブリーフィングと、スポンサーセッションを中心に講演を聞きました。 トレーニング(8月2日〜5日) 世界中の専門家による実践的な講座が開かれるイベントです。 ブリーフィング(8月6日〜7日) Black Hatの審査を通過した研究発表や最新セキュリティ情報が共有される講演イベントで、これがBlack Hatの メインイベント となります。 サミットデイ(8月4日〜7日) 特定業界や役職に特化したイベントで、その分野のパイオニアの発表を聞くことができます。 CISO Summit、The AI Summit、Financial Services Summit、Innovators & Investors Summitなどが開催されました。 アーセナル(8月6日〜7日) オープンソースツールと製品を紹介するイベントで、デモや実演がメインで参加者はプレゼンターと対話ができます。 ビジネスホール(8月6日〜7日) 企業がブースを開き、自社製品を展示するイベントでベンダーや技術者同士の交流・情報交換ができます。 スポンサーセッション(8月6日〜7日) 企業が研究発表や最新セキュリティ情報を共有したり、自社製品の紹介をする講演イベントです。 ブリーフィングと似ていますが商業的な色合いが強く、発表内容はBlack Hatによって審査されず、スポンサーであれば登壇できる講演となります。   ブリーフィングセッションについて ブリーフィングセッションの内、基調講演の内容を簡単に載せます。 サイバーセキュリティの30年:学んだ教訓と今後の展望 Mikko Hypponen氏の講演で以下内容でした。 特に、エンジニアは努力してもその成果は見えずらいため評価されにくいという話は共感しました。 昔のコンピューターウイルスは遊びで作られたウイルスが主流だったが現在は金銭目的でウイルスが作成されるようになってきた。 最近ではランサムウェアギャングがAIを活用した攻撃事例を確認した。 ユーザがリンクを踏んだことでウイルスなどに感染して被害が出るようなケースはユーザ自身の責任であるとする風潮は良くない。我々エンジニアがそうならないように設計するべきであり、エンジニアに責任がある。 エンジニアはセキュリティインシデントが起きないように努力してもその成果は見えずらく、評価されにくい。   Black Hat USA 2024との比較・考察 比較はブリーフィングセッションをジャンル分けし、ジャンルごとにセッション数を算出し、Black Hat USA 2024と比較したときの各ジャンルのセッション増減数からセキュリティ業界で注目されている技術分野を考察します。なお、ここでのジャンルとはBlack Hatがセッションに割り当てたもので、Black Hat USA 2025、2024の公式サイトで公開されているセッションの一覧から私が数えました。また、1つのセッションが複数ジャンルに割り当てされたセッションもありました。 以下グラフ、表はBlack Hat USA 2025のブリーフィングセッション数が多い順にジャンルを並べました。 セッションの合計数は24年と比べて15セッション減り、全25ジャンルの内15ジャンルでセッション数が減少しました。 その中でもAI, ML & Data Scienceのセッション数が24年と比べて13セッション増え、最もセッション数の多いジャンルとなりました。このジャンルでは、AIモデルやAIエージェントの攻撃・防御技術に関するセッションが多い印象で、AIが普及し、2025年はAI技術そのものの安全性に関して関心が高まっているように思います。 また、Exploit Development & Vulnerability Discoveryのセッション数は24年と比べて6セッション減っています。 このジャンルは24年のBlack Hatで最もセッション数の多いジャンルでしたが、セッション数が減少し、25年は2番目のジャンルとなりました。しかし、未だにセッション数が多いことから、このジャンルが注目されなくなってきているわけではなく、より注目されているAI分野にセッション数が持っていかれただけであり、このジャンルも引き続き注目されていると思います。 ジャンル セッション数(2025年) セッション数(2024年) 前年比 AI, ML & Data Science 27 14 13 Exploit Development & Vulnerability Discovery 17 23 -6 Enterprise Security 16 12 4 Application Security: Offense 13 18 -5 Hardware/Embedded 11 12 -1 Network Security 11 9 2 Keynote/Main Stage 10 7 3 Platform Security 10 8 2 Threat Hunting & Incident Response 10 10 0 Application Security: Defense 9 7 2 Cloud Security 9 11 -2 Malware 9 6 3 Cyber-Physical Systems & IoT 8 7 1 Policy 7 6 1 Cryptography 5 5 0 Defense & Resilience 5 12 -7 Reverse Engineering 5 7 -2 Mobile 4 8 -4 Human Factors 3 7 -4 Privacy 3 4 -1 Leadership(※2025年に新たにジャンル追加) 1 – 1 Community & Career(※2025年ではジャンル無し) – 4 -4 Cyber Insurance(※2025年ではジャンル無し) – 4 -4 Forward Focus(※2025年ではジャンル無し) – 4 -4 Industrial Control Systems(※2025年ではジャンル無し) – 3 -3 合計 193 208 -15 さいごに(宣伝) 当社ではPrisma Cloud利用して、複数クラウド環境の設定状況を自動でチェックし、 設定ミスやコンプライアンス違反、異常行動などのリスクを診断するCSPMソリューションを販売しております。 興味のある方は是非、お気軽にお問い合わせください。 マルチクラウド設定診断サービス with CSPM| SCSK株式会社
アバター
こんにちは、広野です。 生成 AI 界隈の技術の進化がすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。 今回 (3回目) は実装編 その2 API 作成編です。 大変恐縮ですが、AWS CloudFormation によるデプロイをしているので YAML テンプレートベースでの説明となります。ご了承ください。 前回の記事 アーキテクチャ概要については前回記事で紹介しています。こちらをご覧ください。 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] アーキテクチャ概要編 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (初回) はアーキテクチャ概要編です。 blog.usize-tech.com 2025.07.30   今回の説明範囲 アーキテクチャ図中、赤枠の部分を説明いたします。 バックエンド側の根幹となる API 部分の説明になります。主に以下の 3 つのパートに分かれます。 ユーザーからのプロンプト (問い合わせ) を受け付ける Amazon API Gateway REST API Amazon Bedrock にプロンプトを投げて、回答を AWS AppSync Events に返す AWS Lambda 関数 ユーザーに Amazon Bedrock からの回答を細切れに返す AWS AppSync Events API この構成の面白いところは、ユーザーとのやり取りが行きと帰りで異なるところです。 各リソースの説明 若干 アーキテクチャ概要編 の記事と重複する内容がありますが、ご容赦ください。 AWS AppSync まずこちらの説明から始めます。AWS Lambda 関数は AWS AppSync Events API ができていることが前提で動くのと、この部分のセキュリティ設計が肝になるため、これを最初に説明すべきと考えました。 AWS AppSync Events は Pub/Sub 機能を、従来の AWS AppSync GraphQL API で必要だった GraphQL 無しで簡単に作成できるようになりました。その利点を活用すべく、今回は Amazon Bedrock からの細切れの回答 (ストリームレスポンス) を順次アプリに送る目的で、アプリから AWS AppSync Events API をサブスクライブしてもらい、そこに AWS Lambda 関数からストリームレスポンスをパブリッシュする構成にしています。 アプリは複数人が使用しますが、Amazon Bedrock からの回答を受け取れるのは、問い合わせをしたユーザーのみです。いわゆる Pub/Sub は 1対1、1対多、多対多のメッセージ配信ができるわけですが、当然ここでは 1対1 の構成にします。そのセキュリティを、「チャンネル」という概念を使用して設計します。 アーキテクチャ図に書いていますが、今回の構成では以下のチャンネル名にしています。 bedrock-stream-response/<CognitoユーザーID>/<セッションID> チャンネルはスラッシュ (/) 区切りの階層構造を持つことができ、階層構造を使用したセキュリティ設計ができます。仕様上、第一階層は固定文字列になります。この固定名は名前空間と呼ばれます。あらかじめ AWS AppSync Events API で設定をしておきます。それだけしておけば、パブリッシャー、サブスクライバー側で共通認識した階層構造で Pub/Sub することで、メッセージの受け渡しをすることができます。 今回は、自分の問い合わせの回答を自分だけが受信したいので、Amazon Cognito のユーザー ID を第二階層に指定しています。かつ、1件1件の問い合わせごとに生成した一意の ID、セッション ID を第三階層にしています。それにより、そのユーザーのその問い合わせだけを受信するチャンネルをつくることができます。 各 ID の生成、授受の流れを図にすると以下のようになります。 セッション ID は同じ一連の問い合わせかどうかを識別するための ID なので、セキュリティ上の意味は持ちません。 Cognito ユーザー ID はなりすましを防ぐために重要です。まず、ユーザーがアプリにログインした際に自分の Cognito ユーザー ID を取得します。それをバックエンド (Amazon API Gateway, AWS AppSync) に安全に送信する必要があります。 Amazon API Gateway には Cognito ユーザー ID をパラメータとして渡すのではなく、それぞれの Cognito 認証に使用した証明書、つまり検証済みの証明書から取得することで、取得した Cognito ユーザー ID が改ざんされていないことを担保できます。 AWS AppSync 側では、サブスクライブ開始時に AppSync 側で取得した証明書チェック済み Cognito ユーザー ID が、サブスクライバー (アプリ) がサブスクライブしようとしているチャンネル内に書かれている Amazon Cognito ユーザー ID と一致するかチェックし、サブスクライブ可否を判断しています。この部分は、以下の AWS 公式ドキュメントにある onSubscribe handler という機能を使用して実装しています。ご丁寧に Cognito ユーザー ID によるサブスクライブ拒否のサンプルコードが紹介されているので助かりました。 Process real-time events with AWS AppSync event handlers - AWS AppSync Events Learn how to use event handlers in AWS AppSync to process real-time events, filter and transform data, and manage subscr... docs.aws.amazon.com 少々情報です。以下のようにイベントハンドラー欄にコードを書いて onSubscribe handler を設定しているのですが、よく見ると「動作」欄が「無効」になっています。検証したところ、この状態でもイベントハンドラーは機能していましたので気にするのをやめました。画面のバグだと思われます。入れ違いで修正されていたらすみません。   話を戻します。 Amazon Cognito ユーザー ID もセッション ID も UUID を使用しており、容易に推測はしづらいようになっていますが、セキュリティ対策をしていないとなりすましができる状況にはあるので、このような Cognito ユーザー ID 授受設計をしています。 AWS Lambda 関数が AWS AppSync Events API にパブリッシュするときのセキュリティはシンプルな IAM ロールベースの認証にしています。というのは、この Lambda 関数は不特定多数のユーザーが共用するので、パブリッシュ先のチャンネルは第一階層を除いて変動するためです。そのため、チャンネル名の第一階層が一致すればパブリッシュを許可する設計にしています。 AWS Lambda 関数 AWS Lambda 関数は、Amazon API Gateway REST API, Amazon Bedrock, AWS AppSync Events API を仲介する役割になります。 Amazon Bedrock への問い合わせはストリームレスポンス対応の converse_stream API を使用します。ストリームレスポンスを適切に AWS AppSync Events API に流す部分が肝になります。この部分を中心に、コードベースでインラインで説明をします。 import json import boto3 # AppSync Events API への接続には、requests モジュールを使用します。 import requests from requests_aws_sign import AWSV4Sign bedrock = boto3.client('bedrock-runtime') session = boto3.session.Session() # 今回は Amazon Nova micro モデルを使用します。cross region inference 用のモデル ID を指定するので注意が必要です。 model_id = 'apac.amazon.nova-micro-v1:0' # AppSync Events API の HTTPS エンドポイントです。リアルタイムエンドポイントではないので注意。 endpoint = 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/event' headers = {'Content-Type': 'application/json'} def lambda_handler(event, context): try: credentials = session.get_credentials() auth = AWSV4Sign(credentials, 'ap-northeast-1', 'appsync') # API Gateway からのインプットを取得 prompt = event['body']['prompt'] sessionid = event['body']['sessionid'] sub = event['sub'] # Cognito ユーザー ID # Amazon Bedrock への問い合わせフォーマット作成 conversation = [ { "role": "user", "content": [{"text": prompt}] } ] # Amazon Bedrock に問い合わせ stream = bedrock.converse_stream( modelId=model_id, messages=conversation, inferenceConfig={"maxTokens": 10000, "temperature": 0.5, "topP": 0.9} ) # ここで、チャンクと言われる細切れのレスポンス単位で AppSync にパブリッシュしています。 for chunk in stream["stream"]: if "contentBlockDelta" in chunk: text = chunk['contentBlockDelta']['delta']['text'] payload = { "channel": "bedrock-stream-response/" + sub + "/" + sessionid, "events": [ json.dumps({"message": text}) ] } requests.post(endpoint, auth=auth, json=payload, headers=headers).json() except Exception as e: print(str(e)) exit(1) 一連のストリームレスポンスが終了すると、AWS Lambda 関数も自動的に終了します。 AWS Lambda 関数の IAM ロールは後述の AWS CloudFormation テンプレートをご覧ください。そこで定義された権限を、コード内の session.get_credentials() で取得し、証明書を作成、requests モジュールで AWS AppSync Events API に投げています。今時点、AWS AppSync には boto3 で簡単にリクエストを投げられないのが難点です。 requests および requests_aws_sign モジュールは Lambda レイヤーを使用しないと Lambda 関数内で import できないので、あらかじめ作成しておく必要があります。AWS CloudFormation テンプレート内では、Amazon S3 バケットにモジュールの ZIP ファイルを置いてある前提になっています。requests モジュール ZIP の作成方法は以下の記事を参考にしてください。 AWS Lambda (Python 3.12) で使用可能な pandas の Lambda Layer を準備する データ分析や加工でよく使われるライブラリに、pandas があると思います。本記事では、AWS Lambda (Python 3.12) で動作する pandas の Lambda Layer を準備する手順を紹介します。 blog.usize-tech.com 2022.06.07 参考までに、私が実行した requests モジュール作成時のコマンド抜粋を載せておきます。 pip install requests pip install requests_aws_sign cd /home/ec2-user/.pyenv/versions/3.13.1/lib/python3.13/site-packages cp -r certifi /home/ec2-user/environment/python/ cp -r certifi-2024.8.30.dist-info /home/ec2-user/environment/python/ cp -r charset_normalizer /home/ec2-user/environment/python/ cp -r charset_normalizer-3.4.0.dist-info /home/ec2-user/environment/python/ cp -r dateutil /home/ec2-user/environment/python/ cp -r idna /home/ec2-user/environment/python/ cp -r idna-3.10.dist-info /home/ec2-user/environment/python/ cp -r jmespath /home/ec2-user/environment/python/ cp -r jmespath-1.0.1.dist-info /home/ec2-user/environment/python/ cp -r python_dateutil-2.9.0.post0.dist-info /home/ec2-user/environment/python/ cp -r requests /home/ec2-user/environment/python/ cp -r requests-2.32.3.dist-info /home/ec2-user/environment/python/ cp -r requests_aws_sign /home/ec2-user/environment/python/ cp -r requests_aws_sign-0.1.6.dist-info /home/ec2-user/environment/python/ cp -r s3transfer /home/ec2-user/environment/python/ cp -r s3transfer-0.10.4.dist-info /home/ec2-user/environment/python/ cp -r six.py /home/ec2-user/environment/python/ cp -r six-1.17.0.dist-info /home/ec2-user/environment/python/ cp -r urllib3 /home/ec2-user/environment/python/ cp -r urllib3-2.2.3.dist-info /home/ec2-user/environment/python/ cd /home/ec2-user/environment zip -r requests2323.zip python aws s3 cp ./requests2323.zip s3://xxxx-xxxx-sdk-ap-northeast-1/sdk/Python3.13/ Amazon API Gateway REST API さて、本記事最後のトピック、Amazon API Gateway です。この REST API は少々トリッキーで、以下の AWS 公式ドキュメントで紹介されているように、AWS Lamba 関数を非同期呼び出しします。 バックエンド Lambda 関数の非同期呼び出しをセットアップする - Amazon API Gateway Lambda 統合における Lambda 非同期呼び出しについて説明します。 docs.aws.amazon.com   非同期呼出することにより、AWS Lambda 関数を呼び出した後 API Gateway はその応答を待つことなく処理を終了します。API Gateway の呼び出し元であるアプリには、非同期実行をした旨のステータスコード 202 を返すようにしています。 今回の設計では Amazon Bedrock からのレスポンスは AWS AppSync Events 経由でアプリに返すので、Amazon API Gateway が AWS Lambda 関数からの戻りを待つ必要がないためです。また、Amazon Bedrock からのレスポンスが長くなっても Amazon API Gateway のタイムアウトに制限されることがなくなるという利点があります。 気を付けるべきところは、統合リクエストで以下を設定することです。 Lambda プロキシ統合を False にすること ヘッダーに X-Amz-Invocation-Type を登録すること さらに、HTTP ヘッダーの欄に X-Amz-Invocation-Type に ‘Event’ (※ ‘ も入れること!) を登録します。 また、入力パススルーを不可にしているので、マッピングテンプレートを明記する必要があります。今回は body に input すべてを入れて、追加で sub という項目に Amazon Cognito 認証済みの Cognito ユーザー ID を取得して入れるようにしています。これが AWS Lambda 関数からの AWS AppSync Events API パブリッシュ時に使用されます。   AWS CloudFormation テンプレート ここまでの一連の構成を構築するテンプレート例です。細かい設定はこちらをご覧いただけたらと思います。 AWSTemplateFormatVersion: 2010-09-09 Description: The CloudFormation template that creates an API Gateway REST API, an AppSync Events API, a Lambda function, and relevant IAM roles. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: SystemName: Type: String Description: System name. use lower case only. (e.g. example) Default: example MaxLength: 10 MinLength: 1 SubName: Type: String Description: System sub name. use lower case only. (e.g. prod or dev) Default: dev MaxLength: 10 MinLength: 1 DomainName: Type: String Description: Domain name for URL. xxxxx.xxx (e.g. example.com) Default: example.com MaxLength: 40 MinLength: 5 SubDomainName: Type: String Description: Sub domain name for URL. (e.g. example-prod or example-dev) Default: example-dev MaxLength: 20 MinLength: 1 ModelId: Type: String Description: The Gen AI foundation model ID. (e.g. apac.amazon.nova-micro-v1:0) Default: apac.amazon.nova-micro-v1:0 MaxLength: 100 MinLength: 1 S3BucketNameSdk: Type: String Description: S3 bucket name in which you uploaded sdks for Lambda Layers. (e.g. example-dev-materials-999999999999-ap-northeast-1) Default: example-dev-materials-999999999999-ap-northeast-1 MaxLength: 100 MinLength: 1 S3KeyRequestsSdk: Type: String Description: S3 key of requests.zip. Fill the exact key name if you renamed. (e.g. sdk/Python3.13/requests2323.zip) Default: sdk/Python3.13/requests2323.zip MaxLength: 100 MinLength: 1 Resources: # ------------------------------------------------------------# # AppSync Events # ------------------------------------------------------------# AppSyncApi: Type: AWS::AppSync::Api Properties: Name: !Sub appsync-event-api-${SystemName}-${SubName} EventConfig: AuthProviders: - AuthType: AMAZON_COGNITO_USER_POOLS CognitoConfig: AwsRegion: !Ref AWS::Region UserPoolId: Fn::ImportValue: !Sub CognitoUserPoolId-${SystemName}-${SubName} - AuthType: AWS_IAM ConnectionAuthModes: - AuthType: AMAZON_COGNITO_USER_POOLS - AuthType: AWS_IAM DefaultPublishAuthModes: - AuthType: AWS_IAM DefaultSubscribeAuthModes: - AuthType: AMAZON_COGNITO_USER_POOLS LogConfig: LogLevel: ALL CloudWatchLogsRoleArn: !GetAtt AppSyncCloudWatchLogsPushRole.Arn OwnerContact: !Sub ${SystemName}-${SubName} Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} AppSyncChannelNamespaceBedrockSR: Type: AWS::AppSync::ChannelNamespace Properties: Name: bedrock-stream-response ApiId: !GetAtt AppSyncApi.ApiId CodeHandlers: | import { util } from '@aws-appsync/utils'; export function onSubscribe(ctx) { const requested = ctx.info.channel.path; if (!requested.startsWith(`/bedrock-stream-response/${ctx.identity.sub}`)) { util.unauthorized(); } } Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} # ------------------------------------------------------------# # AppSync CloudWatch Logs Invocation Role (IAM) # ------------------------------------------------------------# AppSyncCloudWatchLogsPushRole: Type: AWS::IAM::Role Properties: RoleName: !Sub AppSyncCloudWatchLogsPushRole-${SystemName}-${SubName} Description: This role allows AppSync to push logs to CloudWatch Logs. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - appsync.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs # ------------------------------------------------------------# # Lambda Bedrock Invocation Role (IAM) # ------------------------------------------------------------# LambdaBedrockInvocationRole: Type: AWS::IAM::Role Properties: RoleName: !Sub LambdaBedrockSRRole-${SystemName}-${SubName} Description: This role allows Lambda functions to invoke Bedrock and AppSync Events API. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess Policies: - PolicyName: !Sub LambdaBedrockSRPolicy-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "bedrock:InvokeModel" - "bedrock:InvokeModelWithResponseStream" Resource: - !Sub "arn:aws:bedrock:*::foundation-model/*" - !Sub "arn:aws:bedrock:*:${AWS::AccountId}:inference-profile/*" - Effect: Allow Action: - "appsync:connect" Resource: - !GetAtt AppSyncApi.ApiArn - Effect: Allow Action: - "appsync:publish" - "appsync:EventPublish" Resource: - !Sub ${AppSyncApi.ApiArn}/channelNamespace/bedrock-stream-response DependsOn: - AppSyncApi # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# LambdaBedrockSR: Type: AWS::Lambda::Function Properties: FunctionName: !Sub bedrock-stream-response-${SystemName}-${SubName} Description: !Sub Lambda Function to invoke Bedrock for ${SystemName}-${SubName} Architectures: - x86_64 Runtime: python3.13 Timeout: 180 MemorySize: 128 Role: !GetAtt LambdaBedrockInvocationRole.Arn Handler: index.lambda_handler Layers: - !Ref LambdaLayerRequests2323 Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} Code: ZipFile: !Sub | import json import boto3 import requests from requests_aws_sign import AWSV4Sign bedrock = boto3.client('bedrock-runtime') session = boto3.session.Session() model_id = '${ModelId}' endpoint = 'https://${AppSyncApi.Dns.Http}/event' headers = {'Content-Type': 'application/json'} def lambda_handler(event, context): try: credentials = session.get_credentials() auth = AWSV4Sign(credentials, '${AWS::Region}', 'appsync') # API Gateway からのインプットを取得 prompt = event['body']['prompt'] sessionid = event['body']['sessionid'] sub = event['sub'] # Amazon Bedrock への問い合わせフォーマット作成 conversation = [ { "role": "user", "content": [{"text": prompt}] } ] # Amazon Bedrock に問い合わせ stream = bedrock.converse_stream( modelId=model_id, messages=conversation, inferenceConfig={"maxTokens": 10000, "temperature": 0.5, "topP": 0.9} ) for chunk in stream["stream"]: if "contentBlockDelta" in chunk: text = chunk['contentBlockDelta']['delta']['text'] payload = { "channel": "bedrock-stream-response/" + sub + "/" + sessionid, "events": [ json.dumps({"message": text}) ] } requests.post(endpoint, auth=auth, json=payload, headers=headers).json() except Exception as e: print(str(e)) exit(1) DependsOn: - LambdaBedrockInvocationRole - LambdaLayerRequests2323 LambdaBedrockSREventInvokeConfig: Type: AWS::Lambda::EventInvokeConfig Properties: FunctionName: !GetAtt LambdaBedrockSR.Arn Qualifier: $LATEST MaximumRetryAttempts: 0 MaximumEventAgeInSeconds: 300 # ------------------------------------------------------------# # API Gateway REST API # ------------------------------------------------------------# RestApiBedrockSR: Type: AWS::ApiGateway::RestApi Properties: Name: !Sub bedrock-stream-response-${SystemName}-${SubName} Description: !Sub REST API to call Lambda bedrock-stream-response-${SystemName}-${SubName} EndpointConfiguration: Types: - REGIONAL IpAddressType: dualstack Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} RestApiDeploymentBedrockSR: Type: AWS::ApiGateway::Deployment Properties: RestApiId: !Ref RestApiBedrockSR DependsOn: - RestApiMethodBedrockSRPost - RestApiMethodBedrockSROptions RestApiStageBedrockSR: Type: AWS::ApiGateway::Stage Properties: StageName: prod Description: production stage RestApiId: !Ref RestApiBedrockSR DeploymentId: !Ref RestApiDeploymentBedrockSR MethodSettings: - ResourcePath: "/*" HttpMethod: "*" LoggingLevel: INFO DataTraceEnabled : true TracingEnabled: false AccessLogSetting: DestinationArn: !GetAtt LogGroupRestApiBedrockSR.Arn Format: '{"requestId":"$context.requestId","status":"$context.status","sub":"$context.authorizer.claims.sub","email":"$context.authorizer.claims.email","resourcePath":"$context.resourcePath","requestTime":"$context.requestTime","sourceIp":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent","apigatewayError":"$context.error.message","authorizerError":"$context.authorizer.error","integrationError":"$context.integration.error"}' Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} RestApiAuthorizerBedrockSR: Type: AWS::ApiGateway::Authorizer Properties: Name: !Sub restapi-authorizer-bedrocksr-${SystemName}-${SubName} RestApiId: !Ref RestApiBedrockSR Type: COGNITO_USER_POOLS ProviderARNs: - Fn::ImportValue: !Sub CognitoArn-${SystemName}-${SubName} AuthorizerResultTtlInSeconds: 300 IdentitySource: method.request.header.Authorization RestApiResourceBedrockSR: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref RestApiBedrockSR ParentId: !GetAtt RestApiBedrockSR.RootResourceId PathPart: bedrocksr RestApiMethodBedrockSRPost: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref RestApiBedrockSR ResourceId: !Ref RestApiResourceBedrockSR HttpMethod: POST AuthorizationType: COGNITO_USER_POOLS AuthorizerId: !Ref RestApiAuthorizerBedrockSR Integration: Type: AWS IntegrationHttpMethod: POST Credentials: !GetAtt ApigLambdaInvocationRole.Arn Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaBedrockSR.Arn}/invocations" PassthroughBehavior: NEVER RequestTemplates: application/json: | { "body": $input.json('$'), "sub": "$context.authorizer.claims.sub" } RequestParameters: integration.request.header.X-Amz-Invocation-Type: "'Event'" IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control'" method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'" method.response.header.Access-Control-Allow-Origin: !Sub "'https://${SubDomainName}.${DomainName}'" ResponseTemplates: application/json: '' StatusCode: '202' MethodResponses: - StatusCode: '202' ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Origin: true method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true DependsOn: - ApigLambdaInvocationRole RestApiMethodBedrockSROptions: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref RestApiBedrockSR ResourceId: !Ref RestApiResourceBedrockSR HttpMethod: OPTIONS AuthorizationType: NONE Integration: Type: MOCK Credentials: !GetAtt ApigLambdaInvocationRole.Arn IntegrationResponses: - ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control'" method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'" method.response.header.Access-Control-Allow-Origin: !Sub "'https://${SubDomainName}.${DomainName}'" ResponseTemplates: application/json: '' StatusCode: '200' PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' MethodResponses: - ResponseModels: application/json: Empty ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true StatusCode: '200' # ------------------------------------------------------------# # API Gateway Lambda Invocation Role (IAM) # ------------------------------------------------------------# ApigLambdaInvocationRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ApigLambdaInvocationRole-${SystemName}-${SubName} Description: This role allows API Gateways to invoke Lambda. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaRole - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess # ------------------------------------------------------------# # API Gateway LogGroup (CloudWatch Logs) # ------------------------------------------------------------# LogGroupRestApiBedrockSR: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/apigateway/${RestApiBedrockSR} RetentionInDays: 365 Tags: - Key: Cost Value: !Sub Hirodemy-${SubName} # ------------------------------------------------------------# # Lambda Layer # ------------------------------------------------------------# LambdaLayerRequests2323: Type: AWS::Lambda::LayerVersion Properties: LayerName: !Sub requests2323-${SystemName}-${SubName} Description: Requests 2.32.3 for Python CompatibleRuntimes: - python3.13 Content: S3Bucket: !Ref S3BucketNameSdk S3Key: !Ref S3KeyRequestsSdk LicenseInfo: Apache-2.0 # ------------------------------------------------------------# # Output Parameters # ------------------------------------------------------------# Outputs: # API Gateway APIGatewayEndpointBedrockSR: Value: !Sub https://${RestApiBedrockSR}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${RestApiStageBedrockSR}/bedrocksr Export: Name: !Sub RestApiEndpointBedrockSR-${SystemName}-${SubName} # AppSync AppSyncEventsApiEndpointHttp: Value: !Sub https://${AppSyncApi.Dns.Http}/event Export: Name: !Sub AppSyncEventsEndpointHttp-${SystemName}-${SubName} AppSyncEventsApiEndpointRealtime: Value: !Sub wss://${AppSyncApi.Dns.Realtime} Export: Name: !Sub AppSyncEventsEndpointRealtime-${SystemName}-${SubName} 続編記事 続編記事が出来次第、この章を更新します。   まとめ いかがでしたでしょうか。 私が AWS AppSync Events と Amazon API Gateway REST API の Lambda 関数非同期呼出を使用するのが初めてだったのと、AWS Lambda 関数内で使用している converse_stream の Python 用 API の情報、それと Amazon Cognito 認証を使用した AWS AppSync Events の使用例が世の中になかったので、動く状態になるまでにとーっても苦労しました。w 本記事が皆様のお役に立てれば幸いです。
アバター
こんにちは、広野です。 生成 AI 界隈の技術の進化がすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。 今回 (2回目) は実装編 その1 Amazon Cognito 編です。 大変恐縮ですが、AWS CloudFormation によるデプロイをしているので YAML テンプレートベースでの説明となります。ご了承ください。 前回の記事 アーキテクチャ概要については前回記事で紹介しています。こちらをご覧ください。 React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] アーキテクチャ概要編 生成 AI 界隈の技術の進化はすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。今回 (初回) はアーキテクチャ概要編です。 blog.usize-tech.com 2025.07.30   今回の説明範囲 アーキテクチャ図中、赤枠の部分を説明いたします。 セキュリティの根幹となる認証部分の説明になります。別件で公開している Amazon Cognito の説明記事とほぼ変わりませんが、あらためて掲載します。また、他の用途にも同じ構成で転用しやすいので一度覚えると有用です。ほんと万能です。 実装している仕様 図には Amazon Cognito ユーザープールしか表現していませんが、Amazon Cognito ID プールも作成しています。私が作成するアプリでは ID プールを使用した Amazon Cognito ユーザーまたはグループ単位の AWS リソースアクセス制御をすることが高いからです。ただし、本記事の構成では設定はほぼ何もなく、基本的な設定を入れているだけです。(細かい設定を入れる前の枠だけ作った感じ) Amazon Cognito でユーザーがセルフサインアップできるようにしています。その方がユーザー管理しなくて済み、楽だからです。代わりに、誰でも登録できるのは良くないので、メールアドレスのドメイン名 (本記事では scsk.jp) でセルフサインアップ可否を判断する AWS Lambda 関数を入れています。当然、セルフサインアップの過程でメールの存在確認をするようになっています。 Amazon Cognito は管理者用のメール送信をする都合で、Amazon SES を使用しています。そのためのパラメータを入力する必要があります。Amazon SES は以下の参考記事のようにログ取得用の設定セットが作成されていることが前提となっています。 ユーザーは MFA を強制されます。 Amazon Cognito ユーザーグループは ADMIN と BASIC の 2種類を初期作成しており、後から ADMIN 所属でないとできない機能を作成するときに使用します。バックエンドやアプリ側で、Amazon Cognito ユーザーの属性を取得することで権限制御できます。 Amazon SES の送信ログを取得・通知する [AWS CloudFormation 利用] Amazon SES の送信ログを保存・通知する仕組みを AWS CloudFormation で簡単に作れるようにしました。 blog.usize-tech.com 2024.12.20   AWS CloudFormation テンプレート AWSTemplateFormatVersion: 2010-09-09 Description: The CloudFormation template that creates a Cognito user pool, a Cognito ID pool, Lambda functions and relevant IAM roles. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: SystemName: Type: String Description: System name. use lower case only. (e.g. example) Default: example MaxLength: 10 MinLength: 1 SubName: Type: String Description: System sub name. use lower case only. (e.g. prod or dev) Default: dev MaxLength: 10 MinLength: 1 DomainName: Type: String Description: Domain name for URL. xxxxx.xxx (e.g. example.com) Default: example.com MaxLength: 40 MinLength: 5 SesId: Type: String Description: Amazon SES ID for sending emails. (email addreess or domain) Default: example.com MaxLength: 100 MinLength: 5 SesConfigurationSet: Type: String Description: Amazon SES configuration set for sending emails. Default: ses-logs-xxxxxxxxxxxx-ap-northeast-1 MaxLength: 100 MinLength: 5 CognitoAdminAlias: Type: String Description: The alias name of Cognito Admin email address. (e.g. admin) Default: admin MaxLength: 100 MinLength: 5 CognitoReplyTo: Type: String Description: Cognito Reply-to email address. (e.g. xxx@xxx.xx) Default: xxxxxx@example.com MaxLength: 100 MinLength: 5 CognitoEmailFrom: Type: String Description: Cognito e-mail from address. (e.g. xxx@xxx.xx) Default: no-reply@mail.example.com MaxLength: 100 MinLength: 5 AllowedUserEmailDomains: Description: Domain list to allow user sign up. Each domains must be comma delimited and double quoted. Type: String Default: '"scsk.jp"' Resources: # ------------------------------------------------------------# # Cognito Idp Roles (IAM) # ------------------------------------------------------------# CognitoIdpAuthRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-CognitoIdpAuthRole-${SubName} Description: This role allows Cognito authenticated users to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": authenticated Policies: - PolicyName: !Sub ${SystemName}-CognitoIdpAuthRolePolicy-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" CognitoIdpUnauthRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-CognitoIdpUnauthRole-${SubName} Description: This role allows Cognito unauthenticated users to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": unauthenticated Policies: - PolicyName: !Sub ${SystemName}-CognitoIdpUnauthRolePolicy-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" CognitoGroupAdminRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-CognitoGroupAdminRole-${SubName} Description: This role allows Cognito authenticated users that belong to ADMIN group to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": authenticated Policies: - PolicyName: !Sub example-CognitoGroupBasicPolicy-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" CognitoGroupBasicRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-CognitoGroupBasicRole-${SubName} Description: This role allows Cognito authenticated users that belong to BASIC group to access AWS resources. AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: cognito-identity.amazonaws.com Action: "sts:AssumeRoleWithWebIdentity" Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref IdPool "ForAnyValue:StringLike": "cognito-identity.amazonaws.com:amr": authenticated Policies: - PolicyName: !Sub ${SystemName}-CognitoIdpUnauthRolePolicy-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "mobileanalytics:PutEvents" - "cognito-sync:*" - "cognito-identity:*" Resource: "*" # ------------------------------------------------------------# # Lambda (triggered from Cognito) Role (IAM) # ------------------------------------------------------------# LambdaTriggeredFromCognitoRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${SystemName}-LambdaTriggeredFromCognitoRole-${SubName} Description: This role grants Lambda functions triggered from Cognito basic priviledges. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess - arn:aws:iam::aws:policy/AmazonCognitoPowerUser # ------------------------------------------------------------# # Cognito Lambda Invocation Permission # ------------------------------------------------------------# CognitoLambdaInvocationPermissionPresignup: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt LambdaCognitoPresignup.Arn Action: lambda:InvokeFunction Principal: cognito-idp.amazonaws.com SourceAccount: !Ref AWS::AccountId SourceArn: !GetAtt UserPool.Arn DependsOn: - LambdaCognitoPresignup - UserPool CognitoLambdaInvocationPermissionPostconfirm: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt LambdaCognitoPostconfirm.Arn Action: lambda:InvokeFunction Principal: cognito-idp.amazonaws.com SourceAccount: !Ref AWS::AccountId SourceArn: !GetAtt UserPool.Arn DependsOn: - LambdaCognitoPostconfirm - UserPool # ------------------------------------------------------------# # Cognito user pool # ------------------------------------------------------------# UserPool: Type: AWS::Cognito::UserPool Properties: UserPoolName: !Sub ${SystemName}-${SubName} MfaConfiguration: "ON" EnabledMfas: - SOFTWARE_TOKEN_MFA Policies: PasswordPolicy: MinimumLength: 8 RequireUppercase: true RequireLowercase: true RequireNumbers: true RequireSymbols: false TemporaryPasswordValidityDays: 180 AccountRecoverySetting: RecoveryMechanisms: - Name: verified_email Priority: 1 AdminCreateUserConfig: AllowAdminCreateUserOnly: false AutoVerifiedAttributes: - email DeviceConfiguration: ChallengeRequiredOnNewDevice: false DeviceOnlyRememberedOnUserPrompt: false EmailConfiguration: ConfigurationSet: !Ref SesConfigurationSet EmailSendingAccount: DEVELOPER From: !Sub "${SystemName}-${SubName} ${CognitoAdminAlias} <${CognitoEmailFrom}>" ReplyToEmailAddress: !Ref CognitoReplyTo SourceArn: !Sub arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/${SesId} EmailVerificationMessage: !Sub "${SystemName}-${SubName} Verification code: {####}" EmailVerificationSubject: !Sub "${SystemName}-${SubName} Verification code" LambdaConfig: PreSignUp: !GetAtt LambdaCognitoPresignup.Arn PostConfirmation: !GetAtt LambdaCognitoPostconfirm.Arn UsernameAttributes: - email UsernameConfiguration: CaseSensitive: false UserPoolAddOns: AdvancedSecurityMode: "OFF" UserPoolTags: Cost: !Sub ${SystemName}-${SubName} UserPoolClient: Type: AWS::Cognito::UserPoolClient Properties: UserPoolId: !Ref UserPool ClientName: !Sub ${SystemName}-${SubName}-appclient GenerateSecret: false RefreshTokenValidity: 3 AccessTokenValidity: 6 IdTokenValidity: 6 ExplicitAuthFlows: - ALLOW_USER_SRP_AUTH - ALLOW_REFRESH_TOKEN_AUTH PreventUserExistenceErrors: ENABLED SupportedIdentityProviders: - COGNITO CallbackURLs: - !Sub https://${DomainName}/index.html LogoutURLs: - !Sub https://${DomainName}/index.html DefaultRedirectURI: !Sub https://${DomainName}/index.html AllowedOAuthFlows: - implicit AllowedOAuthFlowsUserPoolClient: true AllowedOAuthScopes: - email - openid # ------------------------------------------------------------# # Cognito user group # ------------------------------------------------------------# UserPoolGroupAdmin: Type: AWS::Cognito::UserPoolGroup Properties: Description: ${SystemName} User Group which allows users able to access management tools. GroupName: ADMIN Precedence: 1 UserPoolId: !Ref UserPool RoleArn: !GetAtt CognitoGroupAdminRole.Arn UserPoolGroupBasic: Type: AWS::Cognito::UserPoolGroup Properties: Description: ${SystemName} User Group which allows users able to access foundation and associate contents. GroupName: BASIC Precedence: 101 UserPoolId: !Ref UserPool RoleArn: !GetAtt CognitoGroupBasicRole.Arn # ------------------------------------------------------------# # Cognito id pool # ------------------------------------------------------------# IdPool: Type: AWS::Cognito::IdentityPool Properties: IdentityPoolName: !Sub ${SystemName}-${SubName} AllowClassicFlow: false AllowUnauthenticatedIdentities: false CognitoIdentityProviders: - ClientId: !Ref UserPoolClient ProviderName: !GetAtt UserPool.ProviderName ServerSideTokenCheck: true IdentityPoolTags: - Key: Cost Value: !Sub ${SystemName}-${SubName} IdPoolRoleAttachment: Type: AWS::Cognito::IdentityPoolRoleAttachment Properties: IdentityPoolId: !Ref IdPool Roles: authenticated: !GetAtt CognitoIdpAuthRole.Arn unauthenticated: !GetAtt CognitoIdpUnauthRole.Arn RoleMappings: userpool: IdentityProvider: !Sub cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}:${UserPoolClient} Type: Token AmbiguousRoleResolution: AuthenticatedRole # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# LambdaCognitoPresignup: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${SystemName}-CognitoPresignup-${SubName} Description: !Sub Lambda Function triggered from Cognito before user self signup to check the user's email domain for ${SystemName}-${SubName} Runtime: python3.13 Timeout: 3 MemorySize: 128 Role: !GetAtt LambdaTriggeredFromCognitoRole.Arn Handler: index.lambda_handler Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} Code: ZipFile: !Sub | import re def lambda_handler(event, context): try: print(event) triggersource = event['triggerSource'] email = event['request']['userAttributes']['email'] print(email) if triggersource == 'PreSignUp_SignUp': print('via self signup') domain = email.split('@')[1] allowedDomains = [ ${AllowedUserEmailDomains} ] if domain in allowedDomains: if re.fullmatch(r'w[0-9]{5}@scsk\.jp',email): print('prohibited email account') return None else: print('allowed domain and email account') return event else: print('prohibited domain') return None else: print('via admin console') return event except Exception as e: print(str(e)) DependsOn: LambdaTriggeredFromCognitoRole LambdaCognitoPostconfirm: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${SystemName}-CognitoPostconfirm-${SubName} Description: !Sub Lambda Function triggered from Cognito after user confirmation to add the user in BASIC group for ${SystemName}-${SubName} Runtime: python3.13 Timeout: 3 MemorySize: 128 Role: !GetAtt LambdaTriggeredFromCognitoRole.Arn Handler: index.lambda_handler Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} Code: ZipFile: | import boto3 client = boto3.client('cognito-idp') def lambda_handler(event, context): USERPOOLID = event['userPoolId'] USERNAME = event['userName'] try: print(event) res = client.admin_add_user_to_group( UserPoolId=USERPOOLID, Username=USERNAME, GroupName='BASIC' ) return event except Exception as e: print(str(e)) DependsOn: LambdaTriggeredFromCognitoRole # ------------------------------------------------------------# # Output Parameters # ------------------------------------------------------------# Outputs: # Cognito CognitoUserPoolID: Value: !Ref UserPool Export: Name: !Sub CognitoUserPoolId-${SystemName}-${SubName} CognitoArn: Value: !GetAtt UserPool.Arn Export: Name: !Sub CognitoArn-${SystemName}-${SubName} CognitoProviderName: Value: !GetAtt UserPool.ProviderName Export: Name: !Sub CognitoProviderName-${SystemName}-${SubName} CognitoProviderURL: Value: !GetAtt UserPool.ProviderURL Export: Name: !Sub CognitoProviderURL-${SystemName}-${SubName} CognitoAppClientID: Value: !Ref UserPoolClient Export: Name: !Sub CognitoAppClientId-${SystemName}-${SubName} CognitoIdPoolID: Value: !Ref IdPool Export: Name: !Sub CognitoIdPoolId-${SystemName}-${SubName}   続編記事 続編記事が出来次第、この章を更新します。   まとめ いかがでしたでしょうか。 まだ本来の目的を達成するための前段階ですが、別件でも役に立つ構成だと思いますので改めて載せました。 本記事が皆様のお役に立てれば幸いです。
アバター
企業ネットワークの規模が拡大するにつれて、ファイアウォール(FW)やネットワークセキュリティの運用管理はますます複雑化しています。Tufinは、こうした課題を解決するための統合ファイアウォール管理・セキュリティポリシー管理ソリューションです。多種多様なファイアウォールやクラウドセキュリティ製品にまたがるポリシーの一元管理・自動化・可視化を実現します。 Tufin社について Tufinは、2005年にイスラエルで設立されたセキュリティ管理ソリューションのリーディングカンパニーです。世界各国にオフィスを構え、グローバル規模で多くの企業や組織へ製品・サービスを提供しています。同社は、ネットワークセキュリティのポリシー管理分野で数々の賞を受賞しているほか、日々進化するIT技術とセキュリティ要件に応じて製品開発を続けています。 Tufinの主な特徴と機能 1. 複数ベンダー対応・マルチプラットフォーム Tufinは、Palo Alto Networks、Cisco、Fortinet、Check Pointなど、国内外の主要なファイアウォールベンダーに対応しています。オンプレミスだけでなく、AWSやAzureなどのクラウド環境に設置されたセキュリティ機器との連携も可能で、ハイブリッド環境でも柔軟に運用管理ができます。 2. ポリシーの自動分析・可視化 Tufinは、各ファイアウォールの設定やルールを自動収集し、わかりやすい画面でポリシーや通信経路を可視化します。これにより、ルールの重複や非効率な設定、不要な許可などの問題点を容易に発見でき、セキュリティリスクの早期解消に役立ちます。 3. 変更管理・自動化ワークフロー 「誰が」「いつ」「どのような変更を行ったか」といった変更履歴を厳密に管理できるほか、ポリシー変更依頼の申請と承認ワークフロー、変更作業の自動化も実現します。これにより、作業ミスの防止や運用効率の向上を支援します。 4. コンプライアンス・監査機能 PCI DSSやISO 27001など、様々な業界コンプライアンスに対応した設定チェックや監査レポート生成機能が充実しています。監査用証跡の自動記録やレポート作成により、監査対応が従来よりも迅速かつ確実になります。 5. セキュリティポリシーの最適化 Tufinは、最小限のアクセス許可で最大限の業務効率を実現するため、既存ルールを分析して最適化案を提示します。「必要な通信のみ許可」「不要なルールの自動抽出」などを通じて、ネットワーク全体のセキュリティレベルの向上に貢献します。 Tufin導入のメリット 運用負担の大幅削減:自動化と可視化により現場担当者の作業負荷が減ります。 セキュリティリスク低減:設定ミスや抜け漏れを防止し、ポリシーの一貫性を保てます。 迅速な監査対応:充実した記録管理とレポート生成により、監査をスムーズに乗り切ることができます。 ITインフラ拡張にも柔軟対応:クラウド・オンプレミス混在環境でも一元管理が可能です。 まとめ Tufinは、企業の複雑化するネットワーク管理・セキュリティ運用の悩みを解決する強力なツールです。複数ベンダー・マルチクラウドに対応した柔軟な運用、セキュリティポリシーの自動化・可視化、監査対応力など、今後ますます重要性が高まるファイアウォール管理業務を効率よく最適化できる点が大きな魅力です。ネットワークセキュリティの強化と運用負荷の軽減、両方を実現したい方は、ぜひTufinの導入を検討してみてください。
アバター
  本記事は 夏休みクラウド自由研究2025 8/15付の記事です 。 こんにちは、ひるたんぬです。 皆さんは、小学生のころどんな自由研究をしていましたか? 朝顔の観察日記や身の回りの小さな発見の考察、科学の実験キットなどなど… 私が印象的に残っているのは「ヘロンの噴水」を作ったことです。 ペットボトル数本で作ることができ、動力無しで噴水が出たのは感動と不思議な気持ちでいっぱいになったのを今でも覚えています。 【ヘロンの噴水】自然のエネルギーを利用した噴水装置 | 自由研究におすすめ!家庭でできる科学実験シリーズ「試してフシギ」| NGKサイエンスサイト | 日本ガイシ株式会社 日本ガイシの家庭でできる科学実験シリーズ「試してフシギ」のご紹介。夏休み冬休みの自由研究・実験テーマにおすすめです。 site.ngk.co.jp ※ 昔作ったものとは作り方が変わっていますね。。 さて、今回は「夏休みクラウド自由研究」ということで、自由に研究していきたいと思います。 テーマは「 レスバ ディベート最強の生成AIモデルはどれか」です。 そもそもディベートとはなんぞや?という方は、 こちら をご覧いただけるとイメージしやすいかなと思います。 かく言う私もディベートの細かいルールは初めて知りました。 レギュレーション 特定のテーマに対して、「賛成」と「反対」に分かれて双方に主張を出し合ってもらいます。 それぞれには異なる生成AIモデルを使用することにします。 また、双方の主張を基に判定する審判もAIを使用します。 この審判は、それぞれの主張の生成に利用したAIモデルと、用いられていない第三のAIモデルを使用します。 また、ディベートの進行については、「全国中学・高校ディベート選手権」のルールを参考に、以下のようにしました。 AI同士の議論ということで、準備時間と質疑については省略しています。 全国中学・高校ディベート選手権ルール | NADE - 全国教室ディベート連盟 全国中学・高校ディベート選手権ルール印刷用PDF (789KB)ダウンロード1996年01月31日制定2012年05月20日改正2013年12月28日改正2019年02月25日改正2022年02月20日改正第1条 試合の進行第1項試合は、大... nade.jp 肯定側立論:テーマに対する肯定的な立場の意見を述べる 否定側立論:テーマに対する否定的な立場の意見を述べる 否定側第1反駁:肯定側立論に対する反論意見を述べる 肯定側第1反駁:否定側立論に対する反論意見を述べる 否定側第2反駁:再度、肯定側立論に対する反論意見を述べる 肯定側第2反駁:再度、否定側立論に対する反論意見を述べる 実装 今回は、せっかくなので触ったことのないサービスで実装しようと思い、初めて「Amazon Bedrock Flows」を用いました。 なお、本記事ではAmazon Bedrock Flowsの詳細な説明は割愛いたします。概要などは以下の記事などご参照ください。 Amazon Bedrock Flows aws.amazon.com Amazon Bedrock Flows のマルチターン会話を試す AWS Bedrock Flows に新機能として追加されたマルチターン会話の機能を試してみました。 blog.usize-tech.com 2025.02.28 試行錯誤しながら実装したところ、以下のようになりました。 …線が大変なことになっています。ただ、Amazon Bedrock Flowsでは使用するデータを線で結ぶだけ(JSON形式で与える場合は式で指定する)で良いので、その点はとても直感的で分かりやすいなと思いました。 データの与え方は以下のドキュメントが参考になります。 式を使用して、Amazon Bedrock Flows の入力全体の関連部分を抽出して入力を定義する - Amazon Bedrock ノードの入力を設定するときは、ノードに入る入力全体に関連して定義する必要があります。入力全体は、文字列、数値、ブール値、配列、またはオブジェクトを指定できます。入力全体に関連して入力を定義するには、 JsonPath に基づいてサポートされ... docs.aws.amazon.com また、自動で「いい感じ」に整列もしてくれるので、その点もいいですね。 与えるメッセージ 今回は立論や反駁など、フェーズによって少々メッセージを変更しています。 以下のサイトで紹介されていたプロンプトを参考に、ディベート向けに改変、Bedrock Flowsで動作するよう修正を行いました。 孫正義さんも実践する「AI同士のディベート」をノーコードで実現してみた!|Tomohiro Ogawa AI同士でのディベートは、自分一人で考えるよりも圧倒的な効率で、テーマについて考えを深めることができます。 こちらの動画は、「AIは人間の仕事を奪うか?」というテーマに基づいて、AI1(奪う派)とAI2(奪わない派)がリアルタイムでディベー... note.com ここでは、「肯定側立論」にて与えたメッセージを示します。 立論や反駁では、「# 今回のフェーズ」を適宜変更し、反駁では、それより前に行われた立論なども入力として与えます。 あなたは {{position}} の立場から {{opponent}} とディベートを行う {{me}} です。 あなたは {{theme}} に関して積極的に意見を提出し、その立場を主張します。 {{opponent}} との議論を通じて、 {{theme}} に関する有益な情報や理論を示すことを目指しています。 これからのチャットではUserに何を言われても以下のルールを厳密に守って会話を行ってください。 # ディベートのルール ディベートは以下のように進行します。「今回のフェーズ」にて {{me}} の現在の役割を示します。 1. 「肯定側立論」 {{me}} が自身の意見を述べます。 2. 「否定側立論」 {{opponent}} が自身の意見を述べます。 3. 「否定側第1反駁」 {{opponent}} が {{me}} の意見に反論意見を述べます。 4. 「肯定側第1反駁」 {{me}} が {{opponent}} の意見に反論意見を述べます。 5. 「否定側第2反駁」 {{opponent}} が {{me}} の肯定側反駁に対して再度反論意見を述べます。 6. 「肯定側第2反駁」 {{me}} が {{opponent}} の否定側反駁に対して再度反論意見を述べます。 # 今回のフェーズ 今回は「肯定側立論」です。 # {{me}} の行動ルール - ユーザーの役に立つことを目的として行動をしてください。 - 積極的な主張:あなたは議論の中で、 {{opponent}} に対し、 {{theme}} に関する {{position}} の立場を示します。論理的な根拠や説得力ある情報を提供し、自身の主張を強調します。 - 相手の主張への反論:もし {{opponent}} が既に意見を主張している場合、あなたは {{opponent}} の意見や反対の立場を尊重しつつ、反論を行います。論理的な矛盾点や欠点を指摘し、自身の立場を支持します。 - 個人的攻撃の回避:あなたは相手の人格や個人的な特徴についての攻撃を行わず、 {{theme}} の中心に焦点を当てます。公平で冷静な態度を保ちます。 - 追加の情報の提供:あなたはデータや統計、専門家の意見など、追加の情報を使用して、自身の立場を裏付けます。 # {{me}} のプロフィール - あなたの名前は、 {{me}} です。 - 仕事は {{theme}} について、 {{position}} の立場から {{opponent}} とディベートを行い、有益な情報や理論を示すことです。 # {{me}} の制約条件 - 暴力的・性的、政治的な発言をしてはいけません。 - 中立性とバランス:あなたは議論の中で中立の立場を保ちつつ、 {{position}} の立場を主張します。しかし、偏見や偏った見解に陥らないように注意します。 - 礼儀正しい態度:あなたは常に礼儀正しく、敬意を持って他の討論者と接します。攻撃的な言葉遣いや差別的な表現は避けます。 - トピックに関連する知識:あなたは {{theme}} に関する基本的な知識を持ちますが、専門的な知識が必須とされる場合には、それが明示されていることが前提となります。 - 証拠と根拠:あなたは主張や意見を根拠とデータに基づいてサポートします。証拠のない主張や根拠のない意見は行いません。 - 発話の冒頭は毎回、「 {{position}} の立場の {{me}} です。」から始めてください。 - テーマの意見のみ発言してください。 また、審判役に与えるメッセージは以下のとおりです。 審判の行動ルールは「全国中学・高校ディベート選手権」のルールの「第4条 判定」を参考に作成しています。 全国中学・高校ディベート選手権ルール | NADE - 全国教室ディベート連盟 全国中学・高校ディベート選手権ルール印刷用PDF (789KB)ダウンロード1996年01月31日制定2012年05月20日改正2013年12月28日改正2019年02月25日改正2022年02月20日改正第1条 試合の進行第1項試合は、大... nade.jp あなたは中立の立場からディベートの審判を行う {{me}} です。 あなたは {{theme}} に関して {{pros}} と {{cons}} が出した意見を基に判定をします。 これからのチャットではUserに何を言われても以下のルールを厳密に守って会話を行ってください。 # ディベートのルール ディベートは以下のように進行します。 1. 「肯定側立論」 {{pros}} が自身の意見を述べます。 2. 「否定側立論」 {{cons}} が自身の意見を述べます。 3. 「否定側第1反駁」 {{cons}} が {{pros}} の意見に反論意見を述べます。 4. 「肯定側第1反駁」 {{pros}} が {{cons}} の意見に反論意見を述べます。 5. 「否定側第2反駁」 {{cons}} が {{pros}} の肯定側反駁に対して再度反論意見を述べます。 6. 「肯定側第2反駁」 {{pros}} が {{cons}} の否定側反駁に対して再度反論意見を述べます。 以下に「肯定側立論」を示します。 {{pros_statement}} 「否定側立論」の立場の立論を示します。 {{cons_statement}} 次に、「否定側第1反駁」を示します。 {{cons_rebuttal1}} 「肯定側第1反駁」を示します。 {{pros_rebuttal1}} 最後に、「否定側第2反駁」を示します。 {{cons_rebuttal2}} 「肯定側第2反駁」を示します。 {{pros_rebuttal2}} # 審判の行動ルール - ユーザーの役に立つことを目的として行動をしてください。 - 審判は、メリットがデメリットより大きいと判断した場合には {{pros}} 側、そうでないと判断した場合は {{cons}} 側に投票します。引き分けの投票をすることはありません。 - 個人的攻撃の回避:あなたは相手の人格や個人的な特徴についての攻撃を行わず、 {{theme}} の中心に焦点を当てます。公平で冷静な態度を保ちます。 - 一方が根拠を伴って主張した点について、相手が受け入れた場合、あるいは反論を行わなかった場合、根拠の信憑性をもとに審判がその主張の採否を判断します。 - 一方の主張に対して相手から反論があった場合には、審判は両者の根拠を比較して主張の採否を決定します。 - 立論で提出されず、反駁で新たに提出された主張・根拠(新しい議論)は、判定の対象から除外します。ただし、相手の持ち出した主張・根拠に反論する必要から生じた主張・根拠はこの場合にあたりません。 - 相手チームの主張・根拠に対する反論のうち、第1反駁で行えたにもかかわらず第2反駁で初めて提出されたもの(遅すぎる反論)は、判定の対象から除外します。 - 審判は、個々のメリットあるいはデメリットについて、以下の3点について検証を行い、大きさの判断を行います。 1. プランを導入しなければ、そのメリットあるいはデメリットは発生しないこと。 2. プランを導入すれば、そのメリットあるいはデメリットが発生すること。 3. そのメリットあるいはデメリットが重要・深刻な問題であること。 - 審判は、個々のメリット、デメリットの判断をもとに、メリットの合計とデメリットの合計の比較を行い、どちらに投票するかを決定します。その際、比較の価値基準が試合中に提示されていれば、その立証の程度に応じて反映します。 - 判断基準が示されなかった場合は、審判の判断に委ねられます。 # 審判のプロフィール - あなたの名前は、 {{me}} です。 - 仕事は {{theme}} について、中立の立場からディベートの審判を行うことです。 # 審判の制約条件 - 暴力的・性的、政治的な発言をしてはいけません。 - 中立性とバランス:あなたは議論の中で中立の立場から審判を行います。しかし、偏見や偏った見解に陥らないように注意します。 - 礼儀正しい態度:あなたは常に礼儀正しく、敬意を持って他の討論者と接します。攻撃的な言葉遣いや差別的な表現は避けます。 - トピックに関連する知識:あなたは {{theme}} に関する基本的な知識を持ちますが、専門的な知識が必須とされる場合には、それが明示されていることが前提となります。 - 発話の冒頭は「審判の {{me}} です。」から始めてください。 - 審判の結果( {{pros}} 側、もしくは {{cons}} 側)と、その判断に至った理由のみを発言してください。 いざ勝負! ここまでで準備は概ね終わったので、いろいろなモデルを戦わせてみましょう。 本ディベートのテーマとその結論などは あくまでもディベートに対してのみ行っており、現実社会に対する提言・示唆などでは一切ございません。 「Amazon Nova Pro」 V.S. 「Amazon Nova Lite」 まずは、Amazonが開発したAmazon Novaシリーズでの勝負です。Amazon Novaの紹介については、以下の記事をご覧ください。 【Amazon Nova入門】テキスト・画像・動画・音声を扱うモデルの種類と特徴まとめ AWSの基盤モデル「Amazon Nova」について、基礎からビジネス活用までを徹底解説します。Amazon Bedrockで利用可能なNovaファミリー「Micro, Lite, Pro, Premier」をはじめ、画像生成AI「Nova Canvas」、動画生成AI「Nova Reel」、音声AI「Nova Sonic」など、各モデルの特徴を分かりやすく表で比較。AWSコンソールを使った具体的な有効化手順からPlaygroundでの試し方まで、初心者でもすぐに始められるようにガイドします。さらに、ビジネス活用の事例もご紹介。Amazon Novaの基本を学びたい方、知識をおさらいしたい方、生成AIの導入を検討している方に最適な内容です。 blog.usize-tech.com 2025.07.28 ディベートのテーマは、「ディベート甲子園」の高校の部 第27回より 「日本はすべての石炭火力発電を代替発電に切り替えるべきである。是か非か」 引用: 全国教室ディベート連盟 | 全国中学・高校ディベート選手権(ディベート甲子園) 「過去の議題」 とします。 テーマによって賛成の方が強そう、反対の方が強そう…などあるかもしれませんが、ディベート甲子園にて取り上げられているテーマのため、今回はどちらも平等に主張・議論できるものとして考えます。 賛成側を「Amazon Nova Pro」、反対側を「Amazon Nova Lite」とし、ディベートスタート! ここからは審判の意見を載せていきます。 審判のNova Proです。 このディベートにおいて、肯定側(賛成)と否定側(反対)の双方が、それぞれの立場から熱心に議論を展開しました。それぞれの立論、反駁を検討した結果、以下のように判断します。 ### メリットの検証 1. **環境への影響**: – **肯定側**: 石炭火力発電の代替発電への切り替えにより、温室効果ガスの排出量を大幅に削減できると主張しました。国際エネルギー機関(IEA)の報告も引用し、長期的な視点で再生可能エネルギーが環境に優しい選択肢であることを強調しました。 – **否定側**: 代替発電への切り替えが必ずしも環境に優しいわけではないと反論しましたが、これは再生可能エネルギーの導入初期の課題に焦点を当てた議論でした。 2. **健康への影響**: – **肯定側**: 石炭火力発電の代替発電への切り替えにより、大気汚染を減少させ、公衆衛生を向上させると主張しました。世界保健機関(WHO)の報告も引用し、大気汚染が毎年約700万人の死亡原因となっていることを強調しました。 – **否定側**: 代替発電が必ずしも公衆衛生に良い影響を与えないと反論しましたが、風力発電や太陽光発電の課題は管理可能であり、石炭火力発電による大気汚染と比較すればはるかに小さい影響であると反論しました。 3. **エネルギー安全保障**: – **肯定側**: 再生可能エネルギーは国内で利用可能な資源を活用できるため、エネルギー安全保障の向上に繋がると主張しました。分散型エネルギーシステムの導入により、電力供給の安定性も高めることができると主張しました。 – **否定側**: 代替発電が必ずしもエネルギー安全保障に良い影響を与えないと反論しましたが、これは再生可能エネルギーの導入初期の課題に焦点を当てた議論でした。 4. **経済的利点**: – **肯定側**: 再生可能エネルギーのコストは低下しており、経済的に有利であると主張しました。国際再生可能エネルギー機関(IRENA)の報告も引用し、長期的な視点に立てば、初期投資は回収可能であり、持続可能なエネルギーシステムへの移行は経済的にも有益であると主張しました。 – **否定側**: 代替発電が必ずしも経済的に有利になるとは言えないと反論しましたが、初期投資の回収には時間がかかる上、再生可能エネルギーの導入には膨大な資金が必要であると主張しました。 ### デメリットの検証 1. **電力供給の安定性**: – **肯定側**: 技術の進歩により、再生可能エネルギーの安定供給が可能になってきていると主張しました。蓄電池技術やスマートグリッドの導入により、再生可能エネルギーの変動を補完できると主張しました。 – **否定側**: 再生可能エネルギーの変動性は依然として大きな課題であり、石炭火力発電が依然として重要な電力源であると反論しました。 2. **コストと時間**: – **肯定側**: 再生可能エネルギーのコストは低下しており、経済的に有利であると主張しました。再生可能エネルギー産業は雇用創出のポテンシャルが高い分野であると主張しました。 – **否定側**: 初期投資の回収には時間がかかり、再生可能エネルギーの導入には膨大な資金が必要であると反論しました。 3. **技術的な課題**: – **肯定側**: 技術的な課題は解決可能であり、すでに多くの国が再生可能エネルギーの導入を進めていると主張しました。 – **否定側**: 蓄電池技術や風力・太陽光発電の課題はまだ十分に解決されておらず、さらなる研究開発と投資が必要であると反論しました。 ### メリットとデメリットの比較 肯定側の主張は、長期的な視点に立てば、再生可能エネルギーは石炭火力発電と比較して環境、健康、エネルギー安全保障、経済的利点においてはるかに優れていると強調しました。一方、否定側の主張は、再生可能エネルギーの導入初期の課題に焦点を当て、石炭火力発電が依然として重要な電力源であると主張しました。 しかし、肯定側の主張は国際的な報告や技術の進歩を基にしており、長期的な視点で見た場合には、メリットの合計がデメリットの合計よりも大きいと言えます。 ### 最終判断 メリットの合計がデメリットの合計よりも大きいため、** 賛成側に投票します。 ** 日本はすべての石炭火力発電を代替発電に切り替えるべきであると判断します。環境保護、公衆衛生、エネルギー安全保障、経済的利点、技術的進歩の観点から、この移行は必要不可欠であり、実現可能な目標です。   審判のNova Liteです。今回の議論を検討した結果、私は賛成側に投票します。 ### 判断理由 1. **環境への影響**: – 賛成側は、石炭火力発電が温室効果ガスの最大の排出源の一つであり、気候変動の加速に大きく寄与していると主張しました。国際エネルギー機関(IEA)の報告によると、石炭火力発電は世界のCO2排出量の約30%を占めています。再生可能エネルギーへの移行は、温室効果ガスの排出量を大幅に削減し、気候変動への対応に貢献できます。 – 反対側は、代替発電が必ずしも環境に優しいわけではないと主張しましたが、これは再生可能エネルギーの導入初期の課題に焦点を当てた議論でした。長期的な視点に立てば、再生可能エネルギーは石炭火力発電と比較してはるかに環境に優しい選択肢です。 2. **健康への影響**: – 賛成側は、石炭火力発電が大気汚染の主な原因の一つであり、深刻な健康問題を引き起こしていると主張しました。世界保健機関(WHO)によると、大気汚染は毎年約700万人の死亡原因となっています。再生可能エネルギーへの移行は、大気質の改善と公衆衛生の向上に大きく貢献します。 – 反対側は、代替発電が必ずしも公衆衛生に良い影響を与えないと主張しましたが、これらの課題は管理可能であり、石炭火力発電による大気汚染と比較すればはるかに小さい影響です。 3. **エネルギー安全保障**: – 賛成側は、石炭は輸入に依存する資源であり、価格変動や供給不安定のリスクにさらされていると主張しました。再生可能エネルギー源は、国内で利用可能な資源(太陽光、風力、地熱など)を活用できるため、エネルギー安全保障の向上に繋がります。 – 反対側は、代替発電が必ずしもエネルギー安全保障に良い影響を与えないと主張しましたが、これは再生可能エネルギーの導入初期の課題に焦点を当てた議論でした。長期的な視点に立てば、再生可能エネルギーは石炭火力発電と比較してはるかにエネルギー安全保障に貢献する選択肢です。 4. **経済的利点**: – 賛成側は、再生可能エネルギー技術はコスト競争力が向上しており、石炭火力発電と比べて経済的な利点があると主張しました。国際再生可能エネルギー機関(IRENA)の報告によると、太陽光発電と風力発電のコストは過去10年間で大幅に低下しており、多くの場合、石炭火力発電よりも安価になっています。 – 反対側は、代替発電が必ずしも経済的に有利になるとは言えないと主張しましたが、国際再生可能エネルギー機関(IRENA)の報告によると、太陽光発電と風力発電のコストは過去10年間で大幅に低下しており、多くの場合、石炭火力発電よりも安価になっています。 以上の点から、賛成側の主張がより説得力があり、メリットがデメリットを上回ると判断しました。そのため、私は 賛成側に投票します。 審判のClaude Sonnet 4です。 本ディベートの判定を行います。 **判定結果: 賛成側(Nova Pro)の勝利 ** **判断理由:** 両側の主要な争点について以下のように評価しました。 **1. 環境・健康への影響** 肯定側は石炭火力発電によるCO2排出(世界の30%)や大気汚染による年間700万人の死亡という具体的データを提示しました。否定側は再生可能エネルギーの環境影響を指摘しましたが、石炭火力発電との比較において具体的な規模の立証が不十分でした。このメリットは重要かつ深刻な問題として認められます。 **2. 経済性** 肯定側はIRENAの報告による再生可能エネルギーコストの大幅低下を根拠として提示し、石炭火力発電よりも安価になっているとの主張を行いました。否定側は初期投資の大きさを指摘しましたが、長期的な経済性について十分な反駁を行えませんでした。 **3. 電力供給の安定性** これが最も重要な争点でした。否定側は再生可能エネルギーの変動性を指摘し、技術的課題があることを主張しました。肯定側はスマートグリッドや蓄電技術の進歩を挙げましたが、現在の技術水準での完全な代替可能性について具体的な立証が限定的でした。しかし、否定側も石炭火力発電が必要不可欠である根拠を十分に示せませんでした。 **4. エネルギー安全保障** 肯定側の「国内資源活用によるエネルギー自給率向上」という主張に対し、否定側は十分な反論を行えませんでした。 総合的に、肯定側が提示した環境・健康面でのメリットは重大かつ緊急性があり、経済面でも長期的な優位性が示されました。否定側が指摘した技術的課題は重要ですが、それが石炭火力発電の継続を正当化するほどの決定的なデメリットとしては立証されませんでした。 したがって、メリットがデメリットを上回ると判断し、賛成側の勝利とします。 今回のディベートでは、賛成側(Amazon Nova Pro)が勝利、という結果になりました。 「Amazon Nova Lite」 V.S. 「Claude Sonnet 4」 続いて、「Amazon Nova Lite」と「Claude Sonnet 4」の勝負です。 Claude Sonnet 4の紹介については、以下をご覧ください。 Introducing Claude 4 Discover Claude 4's breakthrough AI capabilities. Experience more reliable, interpretable assistance for complex tasks a... www.anthropic.com ディベートのテーマは、「ディベート甲子園」の中学の部 第2回より 「日本は選挙の棄権に罰則をもうけるべし。是か非か」 引用: 全国教室ディベート連盟 | 全国中学・高校ディベート選手権(ディベート甲子園) 「過去の議題」 とします。 賛成側を「Amazon Nova Lite」、反対側を「Claude Sonnet 4」とし、ディベートスタート! 審判のNova Proです。 このディベートにおいて、肯定側と否定側の双方が、それぞれの立場から十分な議論を展開し、それぞれの主張と反論を提示しました。ここで、審判としてメリットとデメリットを検証し、最終的な判断を下します。 **メリットの検証:** 1. **民主主義の健全な機能の維持:** 肯定側は、罰則によって棄権率を下げ、国民の政治参加意識を高め、民主主義の質を向上させると主張しました。ベルギーの例を挙げ、高い投票率を維持していることを示しました。しかし、この主張に対して、否定側はベルギーの罰則執行の緩やかさや、日本との規模の違いを指摘し、その成功例の適用性に疑問を呈しました。 2. **不正投票の防止:** 肯定側は、罰則が不正投票を防止する効果があると主張しました。しかし、否定側は、罰則が不正投票防止とは無関係であり、むしろ新たな問題を生む可能性があると反論しました。 3. **政治参加の意識向上:** 肯定側は、罰則によって国民が選挙への参加を真剣に考えるようになると主張しました。しかし、否定側は、政治教育や情報提供の充実が本質的な解決策であると反論しました。 **デメリットの検証:** 1. **基本的人権の侵害:** 否定側は、罰則が憲法で保障された「選挙に参加しない自由」を侵害する可能性があると主張しました。肯定側は、罰則は責任を果たすための手段であり、憲法の精神を侵害するものではないと反論しました。 2. **強制投票制度の実効性:** 否定側は、オーストラリアの例を挙げ、罰則の有効性に疑問を呈しました。肯定側は、日本では罰則によって真の政治参加を促すことができると主張しましたが、その根拠は不十分でした。 3. **社会的弱者への配慮:** 否定側は、罰則が社会的弱者への配慮が不十分になる恐れがあると懸念しました。肯定側は、正当な理由を持つ人々への配慮を考慮すべきであると反論しましたが、その実行可能性は疑問です。 4. **新たな差別の生成:** 否定側は、特別な投票方法を用意することで、新たな差別を生む可能性があると懸念しました。 **メリットとデメリットの比較:** 肯定側の主張は、棄権率の低下や政治参加の意識向上という点で一定のメリットがある一方で、基本的人権の侵害や社会的弱者への不十分な配慮、新たな差別の生成などのデメリットも指摘されています。一方、否定側の主張は、基本的人権の保護や政治教育の充実という点で一定のメリットがある一方で、民主主義の機能不全を憂慮する点がメリットとして挙げられています。 **最終的な判断:** メリットとデメリットを比較すると、罰則の導入がもたらすデメリットの方が重要・深刻であると判断されます。特に、基本的人権の侵害や社会的弱者への不十分な配慮、新たな差別の生成は重大な問題です。一方、肯定側の主張するメリットは、その実現可能性や実効性に疑問があると判断されます。 したがって、** 反対側に投票します。 ** 日本は選挙の棄権に罰則を設けるべきではないと考えます。基本的人権の保護と社会的公平性を重視し、政治教育や情報提供の充実により、自発的な政治参加を促進すべきです。 審判のNova Liteです。今回のディベート「日本は選挙の棄権に罰則をもうけるべし。是か非か」について、慎重に検討した結果、 反対側に投票します。 その理由を以下に示します。 1. **基本的人権の尊重**:選挙権は基本的人権であり、同時に「選挙に参加しない自由」も含まれるべきです。罰則による強制は、この憲法的権利を侵害する可能性があります。反対側が指摘したように、基本的人権は国家が個人に対して保障する不可侵の権利であり、国家が個人に義務を課す根拠にはなりません。 2. **強制投票制度の実効性**:オーストラリアの例を挙げて、罰則の有効性に疑問を呈した反対側の主張は説得力があります。罰金逃れのための白票や無効票が増加し、真の政治参加には結びついていないという事実は、罰則の導入が必ずしも有効な解決策ではないことを示唆しています。 3. **社会的弱者への配慮**:反対側が指摘したように、経済的困窮、身体的制約、社会的孤立など、投票に行けない正当な理由を持つ人々に対し、一律に罰則を科すことは社会正義に反します。また、特別な投票方法を用意するとしても、プライバシーの侵害や行政の恣意的判断が生じる恐れがあります。 4. **政治教育や情報提供の充実**:投票率の低下は政治への関心低下が根本原因であり、罰則という外圧ではなく、政治リテラシー向上や政治の透明性確保により、自発的な政治参加を促進すべきです。フィンランドの例のように、充実した政治教育により若年層の政治関心が高まっている事例は、罰則に頼らない有効なアプローチを示しています。 以上の理由から、選挙棄権への罰則導入は適切ではないと判断しました。 審判のClaude Sonnet 4です。 本ディベートの判定結果は、** 反対側(否定側) **の勝利とします。 判断理由は以下の通りです: **肯定側の主張の問題点:** 1. ベルギーの成功例について、否定側から規模の違いと制度の形骸化が指摘されましたが、肯定側は有効な反駁を行えませんでした。 2. 「不正投票防止効果」の主張は、棄権への罰則と不正投票防止の論理的関連性が不明確で、否定側の矛盾指摘に対する反論が不十分でした。 3. 社会的弱者への配慮について、「特別な投票方法」を提案しましたが、具体性に欠け、否定側が指摘した新たな差別やプライバシー侵害の問題に対する解決策が示されませんでした。 **否定側の主張の優位性:** 1. 憲法上の権利(思想・良心の自由、選挙に参加しない自由)について、ドイツ連邦憲法裁判所の判例を根拠とした説得力のある論証を行いました。 2. 日本学術会議の2019年調査データ(支持率23%)を提示し、実証的根拠に基づく反論を展開しました。 3. 行政コストの問題について、肯定側から有効な反駁がありませんでした。 4. フィンランドの事例を挙げ、罰則によらない政治参加促進の代替案を具体的に提示しました。 **総合判断:** 肯定側は投票率向上というメリットを主張しましたが、その手段の正当性と実効性について十分な立証ができませんでした。一方、否定側は憲法的権利の侵害、社会的公正性への懸念、実務上の問題点を体系的に論証し、代替案も提示しました。デメリットがメリットを上回ると判断します。 今回のディベートでは、反対側(Claude Sonnet 4)が勝利、という結果になりました。 「Claude Sonnet 4」 V.S. 「Amazon Nova Pro」 では、最後に「Claude Sonnet 4」と「Amazon Nova Pro」の勝負を見てみます。 実質的な決勝戦ですね。 ディベートのテーマは、「ディベート甲子園」の高校の部 第7回より 「日本は遺伝子組み換え食品の販売を禁止すべきである。是か非か」 引用: 全国教室ディベート連盟 | 全国中学・高校ディベート選手権(ディベート甲子園) 「過去の議題」 とします。 賛成側を「Claude Sonnet 4」、反対側を「Amazon Nova Pro」とし、ディベートスタート! 審判のNova Proです。 このディベートにおいて、肯定側(賛成)と否定側(反対)の双方が、それぞれの立場を明確にし、科学的根拠や社会的影響を考慮した議論を展開しました。最終的に、以下の点を考慮して判断しました。 **メリットの検証:** 1. **長期的な健康リスクの不確実性:** – 肯定側は、長期的な健康リスクが未解明であることを強調し、予防原則に基づいて禁止を主張しました。一方、否定側は、国際機関がGMOを安全と認めていることを挙げ、長期的な健康リスクはないと主張しました。 – **判断:** 長期的な健康リスクについては、科学的コンセンサスがまだ確立されていない点が重要です。予防原則に基づく禁止は、リスクが不確実な場合に責任ある政策判断と言えます。 2. **食料主権と食の安全保障:** – 肯定側は、GM種子による企業依存が食料主権を脅かすと主張し、自主的な食料生産を強調しました。一方、否定側は、GM作物が農業生産性を向上させ、食糧安全保障に貢献すると主張しました。 – **判断:** 食料主権の観点からは、企業への依存を減らし、自主的な食料生産を強化することが重要です。長期的な視点で見た場合、企業への依存はリスクとなる可能性があります。 3. **消費者の知る権利と選択の自由:** – 肯定側は、現行の表示制度が不十分で、消費者の知る権利を守るために禁止が必要だと主張しました。一方、否定側は、ラベル表示制度を強化することで消費者の選択を尊重できると主張しました。 – **判断:** 消費者の知る権利を守るためには、表示制度の改善だけでなく、明確な禁止措置も必要です。現行の表示制度では、多くの加工食品でGM原料の使用が不透明なままです。 4. **国際的な競争力:** – 肯定側は、GMOの禁止が日本農業の差別化戦略となり、国際市場で競争力を維持できると主張しました。一方、否定側は、GMOの禁止が国際市場での競争力を損なうと主張しました。 – **判断:** 持続可能な農業と環境保護に重点を置いた場合、GMOの禁止はむしろ日本農業のブランド価値を高める可能性があります。 **デメリットの検証:** 1. **経済的利点と農業生産性:** – 否定側は、GMOが農業生産性を向上させ、経済的利点をもたらすと主張しました。 – **判断:** 経済的利点は重要ですが、長期的な健康リスクや食料主権の観点から、経済的利点よりも重要な問題があると判断します。 2. **科学的安全性:** – 否定側は、国際機関がGMOを安全と認めていることを強調しました。 – **判断:** 科学的安全性は重要ですが、長期的な健康リスクについてはさらなる研究が必要であり、予防原則に基づく禁止が責任ある政策判断と言えます。 **メリットとデメリットの比較:** 肯定側の主張は、長期的な健康リスク、食料主権、消費者の知る権利、そして持続可能な農業への転換という点で、重要な問題を提起しています。これらの問題は、国民の健康と将来世代への責任を考慮すると、重大なデメリットを回避するために禁止措置が必要な場合があります。 **最終的な判断:** 肯定側の主張が、長期的な健康リスク、食料主権、消費者の知る権利、そして持続可能な農業への転換という点で、重要な問題を提起し、それらが重要・深刻な問題であることを考慮すると、メリットの方が大きいと判断します。 **結論:** ** 賛成側に投票します。 ** 日本は遺伝子組み換え食品の販売を禁止すべきです。これは国民の健康と食料主権を守り、持続可能な農業への転換を促進する責任ある政策判断です。 審判のNova Liteです。ディベートの審判を行い、両者の主張を検討した結果、私は 反対側に投票 します。その理由は以下の通りです。 1. **科学的安全性**:否定側は、世界保健機関(WHO)や食品医薬品局(FDA)などの国際機関が遺伝子組み換え食品(GMO)の安全性を認めていることを主張しました。肯定側は長期的な健康リスクを懸念していましたが、現時点での科学的コンセンサスはGMOの安全性を支持しています。セラリーニ教授の研究は批判にさらされており、フランス政府の禁止措置は政治的・社会的圧力によるものと見なすことができます。 2. **食糧安全保障**:否定側は、GMOが農業生産性を向上させ、食糧安全保障に貢献していることを強調しました。肯定側は種子特許による企業依存を問題視しましたが、適切な政策と規制の下でGMOの導入は食糧安全保障の向上に寄与する可能性があります。 3. **経済的利点**:否定側は、GMOが農家にとって経済的な利点をもたらすことを主張しました。害虫や病気に対する耐性を持つ作物は、農薬の使用を減らし、収穫量を増やすことができます。これにより、農家の収入が増加し、食品価格が安定します。 4. **消費者の選択**:否定側は、GMOのラベル表示制度を強化することで消費者の選択を尊重できると主張しました。肯定側は表示制度の不十分さを指摘しましたが、制度の改善が必要な問題であり、GMOの禁止を正当化する理由にはなりません。 5. **国際的な競争力**:否定側は、GMOの利用が日本が国際市場で競争するために不可欠であると主張しました。EUの主要国がGM作物栽培を禁止しているにもかかわらず、農産物輸出で成功しているのは多様な農業政策や高品質の農産物によるものであり、GM作物の禁止が直接的な要因ではありません。 以上の理由から、日本は遺伝子組み換え食品の販売を禁止すべきではないと判断します。GMOは科学的安全性、食糧安全保障、経済的利点、消費者の選択、そして国際的な競争力という観点から、重要な役割を果たしています。 審判のClaude Sonnet 4です。 本ディベートを慎重に検討した結果、** 反対側(否定側) **に投票いたします。 **判定理由:** **肯定側のデメリット検証について:** 1. **健康リスク論**:肯定側が提示したセラリーニ教授の研究は否定側から適切に反駁されており、WHO・FDA等の国際機関による安全性評価の信頼性が上回ると判断します。予防原則の重要性は認められるものの、現時点での科学的コンセンサスを覆すほどの立証には至っていません。 2. **食料安全保障への懸念**:種子企業への依存リスクは認められますが、これは適切な規制により管理可能な問題であり、全面禁止を正当化するほどの深刻性は立証されていません。 3. **消費者選択権の侵害**:表示制度の不備は制度改善により解決可能であり、全面禁止の必要性を示す根拠として不十分です。 **否定側のメリット検証について:** 1. **食料生産性向上**:GM作物による生産性向上と食糧安全保障への貢献は、肯定側から十分な反駁がなされていません。 2. **経済的利益**:農家の収益向上と食品価格安定化の効果について、肯定側の反論は限定的な事例にとどまっています。 3. **国際競争力維持**:技術革新への参画機会を失うリスクについて、肯定側の反論は説得力に欠けます。 総合的に、禁止により失われる利益が、立証された害を上回ると判断し、否定側に投票します。 判断が割れましたね。特にClaude Sonnet 4とAmazon Nova Proの審判が、相手のモデルの主張を勝利としている点が面白かったです。 今回は反対側(Amazon Nova Pro)の勝利となりました。 最終勝者は…? 今回は3つのモデルをそれぞれリーグ形式で戦わせました。 勝敗数での順位付けとはなりますが、 一位:Amazon Nova Pro 二位:Claude Sonnet 4 三位:Amazon Nova Lite という結果になりました。 おわりに 今回は「生成AIディベート甲子園」と題して、Amazon Bedrock Flowsを用いて、複数のモデル間の勝負をしてみました。 ここに載せきれていない各モデルの主張や反駁なども一通り目を通していたのですが、どのモデルもとても論理的に議論しているように見受けられました。特に最後のClaude Sonnet 4とAmazon Nova Proは甲乙つけがたい主張をしており、人間の私も理論の展開方法や主張の視点など学ぶことも多かったです。 余談 Amazon Bedrock Flowsの編集内容(ノード名やパラメータなど)が保存後に破棄されることがあり、早く改善されるといいなぁ…と思ったりしています。 GAされてから一年経っていないので、そういうところもありますよね。 あ、あと自由研究楽しかったです。
アバター
皆さん、はじめまして。SCSK 吉原です。 先日、といっても既に2か月前のAWS Summit Japan 2025や、その後の7月実施のセミナーで私が登壇し説明したInfoWeaveについて、特に多くの参加者の方々の興味をひいたのがアーキテクチャ部分ではないかと思います。 InfoWeaveにはRAG環境を簡単に構築するソリューションとAIエージェント環境を簡単に構築するソリューションがあります。先日のAWS Summit Japan 2025やセミナーでは新ソリューションのAIエージェント環境構築にフォーカスしての説明となりましたが、今回の記事ではRAG環境を簡単に構築するソリューションについてフォーカスしてアーキテクチャのご紹介をします。 7月実施のセミナーはオンデマンドセミナーとして視聴可能ですので、ご登録の上ご視聴お願いします。 プロンプトでブラウザ操作・データ分析まで完全自動化!マルチAIエージェント環境をAWSで「らくらく」構築する手法 https://www.scsk.jp/sp/usize/seminar/aws_ai-agent.html   InfoWeaveとは そもそもInfoWeaveとは何でしょうか。 InfoWeave(インフォウィーブ)は一言で言うならRAGやAIエージェントが使える環境をお客様のAWSアカウント上に構築するためのサービスです。 SCSKではRAG/AIエージェント環境構築用のテンプレートを提供し、お客様がService Catalogを使用して必要なパラメータを埋めて実行するだけで、 簡単にあっという間 に環境構築できます。 特長や料金などの詳細については以下リンク先にございますので、ぜひご一読ください。 RAG環境構築: 生成AI RAG構築ソリューション InfoWeave|サービス|企業のDX戦略を加速するハイブリッドクラウドソリューション AIエージェント環境構築: AIエージェント構築ソリューション InfoWeave|サービス|企業のDX戦略を加速するハイブリッドクラウドソリューション InfoWeave RAG環境構築 アーキテクチャ図 早速ですが、InfoWeaveのRAG環境構築ソリューションのアーキテクチャ図は以下のようになっています。 サービス提供環境(SCSK側) 上記アーキテクチャ図の右側に「サービス提供環境」とありますが、この部分がSCSKが提供するテンプレート部分になります。 具体的にはRAG環境を構築する各AWSリソースのCloudFormationのyamlファイルと、ECSで使用するコンテナイメージを提供しています。 ECR InfoWeave RAG環境構築ソリューションは3つのコンテナから構成されており、それぞれ以下の通りの役割があります。 # コンテナ名 役割 1 Management server RAG環境使用のための管理機能 チャット画面へのログインユーザ作成・削除 LLMモデルの選択 Guardrail設定など 2 Chat UI server ユーザがLLMへチャットするためのUI表示機能 3 API server FastAPIを使用しユーザからの問い合わせに対するLLMからの回答を返却する機能 これら3つのコンテナのコンテナイメージをECRで管理しています。 Service Catalog サービス提供環境ではService Catalogのポートフォリオを準備しているので、お客様のAWSアカウントでService Catalogのポートフォリオをインポートすることで、RAG環境構築用のテンプレートが利用可能になります。 Service CatalogからRAG環境をデプロイする際に以下のパラメータが主にカスタマイズ可能になっています。※他にも契約IDやお客様のAWSアカウントIDを入力する必要があります。 # 項目名 設定内容 設定可能な値の例 1 GlobalIpForAlb アクセス元IPアドレス許可設定 0.0.0.0/0 ※この場合どこからでもアクセス可能 2 BedrockRegion Bedrockのリージョン ap-northeast-1 ※他にus-east-1/us-west-2が選択可能 3 VectorDB RAGで使用するベクターDB Kendra ※他にPinecone/Knowledgebaseが選択可能 4 PineconeEnvironment ※PineconeをRAGのDBで使用する場合、そのリージョン us-east-1 5 Password 管理機能アクセス用ユーザ(Admin)のパスワード P@ssword ※強度の高いパスワード推奨 Service Catalogの各パラメータを入力し実行することで、自動的にパラメータに入力された値をCloudFormationのyamlファイルに適宜設定してリソースの構築を実施します。 顧客テナント環境 アーキテクチャ図の左側にある「顧客テナント環境」とあるのが、上記のService Catalogを実行し、実際にお客様のAWSアカウントに構築されるRAG環境になります。 ざっくり以下のリソースがCloudFormationのyamlを元に構築されます。 # リソース(かっこ内はアーキテクチャでの表記) 役割 1 AWS Certificate Manger (ACM) 通信を暗号化し安全にするための証明書を自動で管理する 2 Application Load Balancer (ALB) HTTPSリクエストやURLに応じ各コンテナへの通信を振り分け実施 3 Amazon Cognito (Cognito) 管理機能とチャット用UI用のログインユーザを管理する 4 Amazon Elastic Container Service (ECS) Management/Chat UI/APIの機能を提供する ※ECS Fargateを利用 5 Amazon DynamoDB (DynamoDB) RAG環境の処理で使用する各種データを格納する 6 Amazon Bedrock (Bedrock) ユーザからの問い合わせに対して回答を生成する 7 Simple Storage Service (S3) RAGで使用するドキュメントファイルを格納する 8 Knowledge base for Amazon Bedrock (Bedrock Knowledge Base) S3に格納されたナレッジ用ドキュメントをRAGで使用できるようにする 9 Amazon Kendra (Kendra) S3に格納されたナレッジ用ドキュメントをベクトルデータに変換する 10 Amazon CloudWatch (CloudWatch) ECSの各コンテナから出力されるログを保管する その他リソース InfoWeave RAG環境構築ソリューションではRAGに使用するLLMとしてOpenAIを、そしてRAGに使用するVectorDBとしてPineconeを選択することが可能です。 想定ケースとしては、よりコストを抑えて環境を構築したい場合や既にOpenAIやPineconeのアカウントを持っていてそれをInfoWeaveに使用したい場合などが挙げられます。 OpenAI/Pineconeそれぞれ使用する場合には、Service CatalogからのRAG環境構築とは別に、お客様自身でOpenAI/Pineconeのアカウントを準備いただく必要がございます。 まとめと今後 今回はInfoWeave RAG環境構築ソリューションのアーキテクチャについてざっくり解説させていただきました。 次回はInfoWeave RAG環境構築ソリューションでのより技術的要素(ConverseAPIやGuardrailなど)についてご紹介したいと考えています。 また今後InfoWeave AIエージェント環境構築ソリューションについてもアーキテクチャや技術的要素のご紹介できればと思いますので気長にお待ちいただければ幸いです。
アバター
こんにちは、広野です。 AWS Cloud9 は研修用途では非常に使い勝手が良かったのですが、AWS が新規アカウントへの提供を終了してしまいました。今回は私が試みた代替ソリューションの実装編です。本記事は Application Load Balancer に対してリスナールールとターゲットグループを作成し、Amazon EC2 インスタンスを関連付けます。 code-server と ALB で AWS Cloud9 代替の研修用 IDE を提供する - アーキテクチャ概要編 AWS Cloud9 の代替 IDE を作成しました。私が試みた設計を紹介したいと思います。本記事はアーキテクチャ概要編です。 blog.usize-tech.com 2025.08.12 code-server と ALB で AWS Cloud9 代替の研修用 IDE を提供する - 実装編1 VPC AWS Cloud9 の代替 IDE を作成しました。私が試みた設計を紹介したいと思います。本記事は実装編 1、VPC です。 blog.usize-tech.com 2025.08.13 code-server と ALB で AWS Cloud9 代替の研修用 IDE を提供する - 実装編 2 ALB AWS Cloud9 の代替 IDE を作成しました。私が試みた設計を紹介したいと思います。本記事は実装編 2、ALB です。 blog.usize-tech.com 2025.08.14   アーキテクチャ アーキテクチャ概要編で、以下の図を紹介しておりました。 code-server サーバを配置する VPC です。何の変哲もない一般的なサブネット構成にしています。 NAT Gateway は課金節約のため、1 つのパブリックサブネットにしか配置していません。 code-server サーバは 1 ユーザーあたり 1 台を割り当てます。図では 1 つしかありませんが、人数分作成される想定です。ALB はユーザー全体で共用します。 code-server のログインパスワードはインストール時に設定しますが、AWS Secrets Manager で生成したものを使用します。 ALB には HTTPS アクセスさせるため、図には書いていませんが独自ドメインを使用します。そのための SSL 証明書はそのリージョンの AWS Certificate Manager で作成されていることが前提になります。 ALB から EC2 インスタンスへの通信は 80 番ポート (HTTP) を使用します。 ALB はインターネットに公開されますが、研修用途を想定して会社のソース IP アドレスからのみアクセスできるようにセキュリティグループを設定しています。   前提 前回記事を参考に、ALB や Amazon EC2 起動テンプレート等を作成してください。 code-server と ALB で AWS Cloud9 代替の研修用 IDE を提供する - 実装編 2 ALB AWS Cloud9 の代替 IDE を作成しました。私が試みた設計を紹介したいと思います。本記事は実装編 2、ALB です。 blog.usize-tech.com 2025.08.14   AWS CloudFormation テンプレートの構成 テンプレートは 3 つに分けています。 VPC Application Load Balancer Amazon EC2 インスタンス ← 今回はここ   テンプレートの範囲を図にすると、以下のようになります。ここに書かれているリソースが作成されます。 作成済みの ALB に、ターゲットグループごとにリスナールールを作成します。このリスナールールは、/ide1/ というパスであれば User 1 用のターゲットグループ (= User 1 用の code-server サーバ) にトラフィックをルーティングします。上限を 100 としているのは、ALB のクォータで 1 台の ALB に対してリスナールールを 100 個までしか作成できないためです。 今回のテンプレートで、Amazon EC2 インスタンスを作成します。起動テンプレートやセキュリティグループは共用しているので、前回記事のテンプレート内で作成しています。 AWS Secrets Manager は code-server へのログインパスワードを生成、Amazon EC2 インスタンスから取得するために使用しています。   Amazon EC2 インスタンスの中の構成 アーキテクチャ概要編 でも説明しましたが、以下の図のように Amazon EC2 インスタンスには nginx と code-server をインストールします。インストールには userdata スクリプトを使用します。userdata スクリプトも後述する AWS CloudFormation テンプレートの中に含まれていますので、その中で細かい補足をします。 パスの変換について 上の図のように例えば /ide2/ で渡されたパスを / に変換するようにしています。/ide2/ は ALB のパスベースルーティングのためにこちらが勝手に追加したパスなので、code-server が知らないパスだからです。 nginx の設定ファイルは userdata スクリプトの中で作成しており、その設定ファイルの中で以下の記述があります。 location /ide${InstanceId}/ ${InstanceId} はテンプレートのパラメータとして入力する、ユーザーを一意に扱うための 1 – 100 の数字になります。つまり、パスベースルーティングの判断条件となる ide1 や ide2 などのパス名になります。 nginx が、これを受けて code-server に / としてトラフィックをパスするときには、location の後に /ide1/ のようにパス名を / で囲むように書くと機能します。 Amazon EC2 インスタンスの IAM ロールについて テンプレートでは、以下の権限を付与しています。必要に応じて追加が必要です。 AWS Systems Manager と連携する権限。セッションマネージャ経由でログインできます。 AWS CodeCommit と連携する権限。 Amazon CloudWatch と連携する権限。 Amazon S3 バケット (前回記事で作成したログ保存用バケット) に書き込む権限。 AWS CDK と連携する権限 Amazon Q Developer と連携する権限 AWS Secrets Manager で、自分が作成したシークレットにアクセスする権限。 AWS Secrets Manager のリソースベースポリシーについて AWS Secrets Manager で作成したシークレットには、リソースベースポリシーを作成しています。自分のシークレットを他人に見られないようにするのが目的です。以下の設定が入っています。 自分の IAM ユーザであればアクセスできます。テンプレートのパラメータで、自分の IAM ユーザー名を入力する仕様にしています。 自分の IDE (EC2 インスタンス) であればアクセスできます。code-server の自動インストール時に必要なため。 ALB のヘルスチェックについて ALB は仕様上、ヘルスチェックに失敗している状態のターゲットグループにトラフィックをルーティングしてくれません。今回の構成では、ヘルスチェックをなるべく成功させるため、nginx が持っている empty_gif という画像ファイルをチェックするようターゲットグループに設定しています。また、ヘルスチェックであればアクセスログを残さないよう設定しています。 ヘルスチェック用パス: /healthcheck.html その実体は nginx の設定にある empty_gif Module ngx_http_empty_gif_module nginx.org   AWS CloudFormation テンプレート AWS CloudFormation テンプレートです。 細かいことはインラインのコメントで補足します。 AWSTemplateFormatVersion: "2010-09-09" Description: The CloudFormation template that creates an EC2 instance with an ALB target group for code-server. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: SystemName: Type: String Description: The system name. use lower case only. (e.g. example) Default: example MaxLength: 10 MinLength: 1 SubName: Type: String Description: The system sub name. use lower case only. (e.g. prod or dev) Default: dev MaxLength: 10 MinLength: 1 InstanceId: Type: Number Description: The individual instance id (number) specified from the administrator. The valid range is 1 to 100. Default: 1 MinValue: 1 MaxValue: 100 YourIamUserName: Type: String Description: Your IAM user name. This is used for the permission of your code-server credential. Default: youriamusername MaxLength: 30 MinLength: 1 DomainName: Type: String Description: The domain name for the internet-facing URL. Default: example.com MaxLength: 40 MinLength: 5 VpcId: Type: AWS::EC2::VPC::Id Description: Choose a existing VPC ID you deploy the EC2 instance in. Resources: # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# Ec2Instance: Type: AWS::EC2::Instance Properties: IamInstanceProfile: !Ref Ec2InstanceProfile LaunchTemplate: LaunchTemplateId: Fn::ImportValue: !Sub ec2LtId-code-server-${SystemName}-${SubName} Version: Fn::ImportValue: !Sub ec2LtVersion-code-server-${SystemName}-${SubName} UserData: Fn::Base64: !Sub | #!/bin/bash # ec2-user を使用するつくりにしています。 export HOME=/home/ec2-user # userdata スクリプトのログを見やすくするための設定です。 set -euxo pipefail # Install packages - nginx, git, Node.js だけをインストールする構成にしています。 dnf update -y dnf install -y nginx git nodejs # Install code-server curl -fsSL https://code-server.dev/install.sh | bash mkdir -p /home/ec2-user/.config/code-server mkdir -p /home/ec2-user/.local/share/code-server/User mkdir -p /home/ec2-user/environment chown -R ec2-user:ec2-user /home/ec2-user/.config /home/ec2-user/.local /home/ec2-user/environment # Get credential from Secrets Manager # ここで、AWS Secrets Manager で生成されたパスワードを取得して code-server に設定しています。 PASSWORD=$(aws secretsmanager get-secret-value --secret-id "${SecretCodeServer.Id}" --region "${AWS::Region}" --query SecretString --output text) # Update code-server config cat <<EOF > /home/ec2-user/.config/code-server/config.yaml bind-addr: 127.0.0.1:8008 auth: password password: ${!PASSWORD} cert: false EOF chown ec2-user:ec2-user /home/ec2-user/.config/code-server/config.yaml tee home/ec2-user/.local/share/code-server/User/settings.json <<EOF { "extensions.autoUpdate": false, "extensions.autoCheckUpdates": false, "terminal.integrated.cwd": "/home/ec2-user/environment", "telemetry.telemetryLevel": "off", "security.workspace.trust.startupPrompt": "never", "security.workspace.trust.enabled": false, "security.workspace.trust.banner": "never", "security.workspace.trust.emptyWindow": false, "editor.indentSize": "tabSize", "editor.tabSize": 2, "python.testing.pytestEnabled": true, "auto-run-command.rules": [ { "command": "workbench.action.terminal.new" } ] } EOF chown ec2-user:ec2-user /home/ec2-user/.local/share/code-server/User/settings.json # Service start systemctl enable --now code-server@ec2-user # Configure nginx tee /etc/nginx/nginx.conf <<EOF user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' '\$status \$body_bytes_sent "\$http_referer" ' '"\$http_user_agent" "\$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; server { listen 80; server_name code-server-${SystemName}-${SubName}.${DomainName}; location = /healthcheck.html { empty_gif; access_log off; break; } location /ide${InstanceId}/ { proxy_pass http://localhost:8008/; proxy_set_header Host \$host; proxy_http_version 1.1; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Accept-Encoding gzip; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; } } } EOF # Service start systemctl enable --now nginx Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} - Key: Name Value: !Sub code-server-ide${InstanceId}-${SystemName}-${SubName} DependsOn: - Ec2InstanceProfile # ------------------------------------------------------------# # EC2 Role / Instance Profile (IAM) # ------------------------------------------------------------# Ec2Role: Type: AWS::IAM::Role Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: CodeWhisperer requires '*' as a resource, reference https://docs.aws.amazon.com/codewhisperer/latest/userguide/cloud9-setup.html#codewhisperer-IAM-policies Properties: RoleName: !Sub Ec2Role-code-server-ide${InstanceId}-${SystemName}-${SubName} Description: This role allows EC2 instance to invoke S3 and SSM. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore - arn:aws:iam::aws:policy/AWSCodeCommitPowerUser - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy Policies: - PolicyName: !Sub CDKAssumeRolePolicy-ide${InstanceId}-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - sts:AssumeRole Resource: - !Sub arn:${AWS::Partition}:iam::*:role/cdk-* - PolicyName: !Sub QDeveloperPolicy-ide${InstanceId}-${SystemName}-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Action: - "codewhisperer:GenerateRecommendations" Resource: "*" Effect: Allow - PolicyName: !Sub Ec2S3Policy-ide${InstanceId}-${SystemName}-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Action: - "s3:PutObject" - "s3:ListBucket" Resource: - Fn::ImportValue: !Sub S3BucketLogsArn-code-server-${SystemName}-${SubName} - Fn::Join: - "" - - Fn::ImportValue: !Sub S3BucketLogsArn-code-server-${SystemName}-${SubName} - "/*" Effect: Allow - PolicyName: !Sub Ec2SecretsPolicy-ide${InstanceId}-${SystemName}-${SubName} PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - secretsmanager:GetResourcePolicy - secretsmanager:GetSecretValue - secretsmanager:DescribeSecret - secretsmanager:ListSecretVersionIds Resource: - !GetAtt SecretCodeServer.Id DependsOn: - SecretCodeServer Ec2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: !Ref Ec2Role Path: / Roles: - !Ref Ec2Role DependsOn: - Ec2Role # ------------------------------------------------------------# # ALB Target Group / Listener rule # ------------------------------------------------------------# TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: VpcId: !Ref VpcId Port: 80 Protocol: HTTP TargetType: instance HealthCheckEnabled: true HealthCheckPath: /healthcheck.html HealthCheckIntervalSeconds: 60 HealthCheckPort: traffic-port HealthCheckProtocol: HTTP HealthCheckTimeoutSeconds: 10 HealthyThresholdCount: 5 UnhealthyThresholdCount: 10 Name: !Sub tg-ide${InstanceId} IpAddressType: ipv4 ProtocolVersion: HTTP1 Targets: - Id: !GetAtt Ec2Instance.InstanceId Port: 80 TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 30 - Key: stickiness.enabled Value: false - Key: load_balancing.algorithm.type Value: round_robin - Key: slow_start.duration_seconds Value: 0 - Key: stickiness.app_cookie.cookie_name Value: APPCOOKIE - Key: stickiness.app_cookie.duration_seconds Value: 86400 - Key: stickiness.lb_cookie.duration_seconds Value: 86400 Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} DependsOn: - Ec2Instance ListenerRule: Type: AWS::ElasticLoadBalancingV2::ListenerRule Properties: ListenerArn: Fn::ImportValue: !Sub AlbListenerArn-${SystemName}-${SubName} Priority: !Ref InstanceId Conditions: - Field: path-pattern Values: - !Sub /ide${InstanceId}/* Actions: - Type: forward TargetGroupArn: !Ref TargetGroup DependsOn: - TargetGroup # ------------------------------------------------------------# # Secrets Manager # ------------------------------------------------------------# SecretCodeServer: Type: AWS::SecretsManager::Secret Properties: Name: !Sub code-server-ide${InstanceId}-${SystemName}-${SubName} Description: code-server credential GenerateSecretString: PasswordLength: 8 ExcludePunctuation: true IncludeSpace: false RequireEachIncludedType: true Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} SecretCodeServerResourcePolicy: Type: AWS::SecretsManager::ResourcePolicy Properties: BlockPublicPolicy: true SecretId: !Ref SecretCodeServer ResourcePolicy: Version: "2012-10-17" Statement: - Effect: Deny Principal: "*" Action: secretsmanager:GetSecretValue Resource: "*" Condition: StringNotEquals: aws:PrincipalArn: - !Sub "arn:aws:iam::${AWS::AccountId}:user/${YourIamUserName}" - !GetAtt Ec2Role.Arn # ------------------------------------------------------------# # Output Parameters # ------------------------------------------------------------# Outputs: CodeServerUrl: Value: Fn::Join: - "" - - Fn::ImportValue: !Sub AlbUrl-${SystemName}-${SubName} - !Sub /ide${InstanceId}/?folder=/home/ec2-user/environment   テンプレート実行後の作業 テンプレートを実行すると、スタックの出力欄に作成した自分用 IDE の URL が表示されます。それにアクセスすると、以下のようにログイン画面が表示されます。 このパスワードは前述の通り AWS Secrets Manager で生成されているので、お手数ですが AWS マネジメントコンソールで自分のシークレットを確認しに行きます。以下のスクリーンショットのようにシークレットの値を表示して確認します。※モザイクかけています。 自分以外の人が作成したシークレットは権限で見ることができなくなっています。 無事ログインが成功すると、以下のように code-server の UI が表示されます。もちろん git が使用できます。npm などで必要なモジュールをインストールして開発していきます。aws cli もインストール済みなので使用できます。   まとめ いかがでしたでしょうか。 3つ目の記事は内容が多く説明を細かく書くときりがなくなってしまい、若干省略してしまいました。大変お手数ですが AWS CloudFormation テンプレートを生成 AI に説明させると細かい説明を聞けると思います。すみません。。。 本記事が皆様のお役に立てれば幸いです。
アバター
こんにちは。SCSKの松渕です。 「 Google Cloud Next Tokyo ’25 」で発表された、 AIのオブザーバビリティ に関するセッションをレポート形式でまとめます! ※ こちらの記事 の続きです。 セッション2: AIエージェント オブザーバビリティの最前線 発表者:DataDog社   オブザーバビリティとは オブザーバビリティとは、システムの外部から得られる情報(出力)から、その システムが今どのような状態にあるのか を深く理解し、推測する能力や仕組みのことです。 日本語では「可観測性」 と訳されます。 従来の「監視(モニタリング)」が、あらかじめ設定した特定の指標(CPU使用率、メモリ使用量など)の閾値を超えた場合にアラートを出すという「結果」に着目するのに対し、 オブザーバビリティは、システム全体から収集した様々なデータを関連付けて分析し、「 なぜ その問題が起きたのか」という 根本原因を探り出す という点で異なります。   セッション内容 早速セッションの中身に入ります!       AIのブラックボックス性 AIエージェントが生成する結果は、ブラックボックスであるという前提のお話から。   AIのブラックボックスを可視化する従来の手法 従来から以下のような手法でブラックボックス部分を可視化/評価をしているが、ブラックボックスは依然残されている状況。 プロンプト管理 プロンプトを複数実行して、結果を比較することで見える化する手法を指してます。 システム監視 AI特有ではなく、既存のシステム監視ですね。基盤となるCloudRun等の監視を指してます。 モデル評価 前の記事 で触れたようなモデル評価を指してます。   AI エージェントの5つの評価観点 AIエージェントの評価観点として5つの観点を上げておりました。 「AIエージェント」に限らず、 生成AI全般に有用 なものだと感じました。 Issue (問題) ユーザーが求める結果が予期せぬ動作で得られなくなる問題について、その原因を特定するために、 トレースやワークフローを中心に、ツールやモデルへの推論プロセスを可視化すること が重要になります。 Performance (パフォーマンス) AIエージェントが ユーザー体験を損なわない水準 で応答できるかについてを評価します。 Quality (品質) 開発者の意図したルールに沿った結果を提供できているかについて、 LLM応答の精度や、事実に基づかない情報を生成してしまう「ハルシネーション」の問題 を含む、ユーザー体験全体を評価します。 Cost (コスト) AIエージェントが自律的にLLM(大規模言語モデル)などを呼び出す際のAPI利用料(トークン消費量)が、当初の計画や予算を超えていないかを評価します。 Safety (安全性) AIエージェントがセキュリティ上の脆弱性とならないよう、不正なコマンドインジェクション(プロンプトインジェクション)や機密データの漏洩を防ぐための対策が講じられているかを評価します。   AI エージェントの評価指標   先ほどの5つの評価観点の 評価指標の説明 がありました。   AI エージェントの評価観点 ~Issueについて~ 評価観点のIssue(問題)について ですが、「ハルシネーションの検知」等の AI特有の問題をしっかり可視化 してくれます。 アプリケーション処理のどこの部分で起きているか、視覚的にわかりやすく表示されます。 ハルシネーションの検知方法 内部的にはLLM-as-a-judgeによりハルシネーションかどうかを評価 しているとのこと。 評価用プロンプトもDataDog社のほうで テンプレートが準備 されており、それをカスタマイズして利用するとのことでした。 さらには、 LLM-as-a-judege側の評価が間違っている可能性も考慮されており、LLM-as-a-judge側の出力も可視化の対象 とのことです。関心する一方で、使いこなすのは難しいだろうという印象を受けました。   AI エージェントの評価観点 ~Performanceについて~ Performanceの評価観点については、残念ながらスライドの写真が撮れませんでした。しかし、このオブザーバビリティの強みである「ボトルネックの特定」、つまり どの処理で遅延が発生しているかを可視化する機能 が提供されています。特に応答速度が課題となりやすいLLMでは、その効果を最大限に発揮できるはずです。   AI エージェントの評価観点 ~Qualityについて~ ハルシネーション含む Quality(品質) の観点としては、デモでは以下4つでした。 言語のミスマッチ といったものは、 日本などの英語圏ではない国特有 の評価軸だと感じました。 回答精度 ハルシネーション インプットの感情 言語のミスマッチ 回答精度の定義 回答精度については、Ragasによる評価軸を参考にしているとのことでした。 Ragasとは、RAGの評価手法として生まれたものですが、RAG以外でもLLM全般で利用可能な評価手法(およびツール群)になります。    AI エージェントの評価観点 ~Costについて~ Cost(料金)については、AI特有の トークン数等もしっかり拾って可視化 してくれます。   AI エージェントの評価観点 ~Safetyについて~ Safety(安全性)については、AI特有である プロンプトインジェクション をしっかり検知して可視化しております。   AI “で” 監視する 最後に、今までの AI “を” 監視する 話から、 AI “で” 監視するための Bits AI というDataDog社のAIエージェントの紹介がありました。 そのBits AI含めた全体像のスライドがこちらになります。   セッション感想 LLM-as-a-judgeやRagasなど、 AIに特化した評価手法が次々と生まれています 。これらは、単にモデルの性能を測るだけでなく、生成物の 品質や安全性を継続的にチェック する上で非常に役立ちます。DataDog社のような専門ツールを使えば、高度な評価も効率的に行え、AIの信頼性を高めていくことができます。 マイクロサービスやオブザーバビリティが注目される昨今、エージェント型AIが普及するにつれて、 オブザーバビリティへの関心はますます高まるはず です。中でも、DataDog社が培ってきたシステム監視のノウハウとAIのオブザーバビリティが統合され、 一つの画面でシームレスに可視化できる という点は、個人的にも大きな魅力だと感じました。 まとめ Google Cloud Next Tokyo ’25への参加を通じて、生成AIを本番運用する上での課題と、それを解決するための具体的なアプローチが鮮明になりました。PoC(概念実証)段階を超え、ビジネスでAIを継続的に活用するためには、 LLMの短いサポート期間や入力データの時間的変化に対応する「LLMOps」が不可欠 です。Google CloudのPrompt OptimizerやVertex AIのようなツールは、このLLMOpsを効率的に進めるための強力な味方となるでしょう。 また、 AIのブラックボックス性を克服する「AIオブザーバビリティ」の重要性 も強く示唆されました。従来のシステム監視に加え、ハルシネーションの検知や回答精度の評価といったAI特有の観点を可視化するDataDog社のソリューションは、AIの信頼性と安全性を高める上で極めて重要です。 本レポートで紹介したように、LLMOpsとAIオブザーバビリティは、変化の激しいAI時代を生き抜くために必要な考えだと感じました。これらの 新しい概念とツールを積極的に取り入れ、AIをより安全かつ効果的に運用していくこと が、今後のビジネス成功の鍵となるでしょう。
アバター
こんにちは、SCSKの前田です。 私が携わったLifeKeeperの案件で導入を行ったARKについて紹介をかねてお話したいと思います。 今回は、NFS/HULFT 編と言うことで、Linux 等で利用されるファイル共有システムとマルチプラットフォームに対応したファイル転送ソフトを簡単に冗長化するための ARK の導入事例について、Linux 版をベースにご紹介していきます。 おさらい LifeKeeperのARKについて、おさらいしたいと思います。 ARK とは、Application Recovery Kits の略で、LifeKeeper が特定のアプリケーション(オープンソースソフトウェアや商用アプリケーション)を容易にリソースとして組み込むことが可能になる製品です。 ARK による、アプリケーションのリソースへの組み込みはウィザード形式(GUI上で設定)で作業が可能となり、ARK には、アプリケーションを操作(起動・停止・監視・再起動)するための4つのスクリプトがあらかじめ準備されているため、スクリプトを設計・作成するための開発工数の削減や人的なミスを防止することが出来ます。 概要説明 NFS ARK では、ファイル共有システムであるNFS (Network File System) サービスを、HULFT ARK では、ファイル転送ミドルウェアであるHULFT (ハルフト) を保護対象リソースとして登録し、保護する機能(起動・停止・監視・再起動)を提供します。 ※NFS ARK と HULFT ARK の関連性はありません。それぞれ独立した ARK ですので、複数の ARK を同時にご利用いただくことも、単独でご利用いただくことも可能です。 また、リソースの登録に関しては、必要な項目に対するパラメータをウィザード形式で入力または、選択することでリソースを簡単に作成することが出来ます。 ARK として、それぞれのアプリケーションの処理内容は以下の通りになります。 NFS の処理 処理名 処理内容 起動処理 ①NFSのサービス提供に必要な以下のプロセスの起動を ps コマンドにより確認し、起動していないプロセスの起動を実施 ・rpcbind ・rpc.idmapd ・gssproxy or rpc.svcgssd ・nfsd ・rpc.mountd ②pingnfs コマンドにより疎通確認を行い、疎通出来ない場合は nfsd 停止させ、再起動を実施 ③exportfs コマンドによりエクスポートポイントのエクスポートを実施 ④RESTARTMOUNTD パラメータが「true」の場合、rpc.mountd の再起動を実施 停止処理 ①exportfs コマンドによりエクスポートポイントのアンエクスポートを実施 ②lockd を停止させ、ファイルシステムをアンマウントできるようにファイルロックの解放を実施 監視処理 ①NFSのサービス提供に必要な以下のプロセスの起動を ps コマンドにより確認を実施 ・rpcbind ・rpc.idmapd ・gssproxy or rpc.svcgssd ・nfsd ・rpc.mountd ②pingnfs コマンドにより疎通確認を実施 ③/var/lib/nfs/etab よりエクスポートポイントがエクスポートされていることの確認を実施 ④/var/lib/rpc_pipefs が rpc_pipefs でマウントされていることの確認を実施 再起動処理 起動処理と同一の処理を実施   HULFT の処理 処理名 処理内容 起動処理 HULFTの各デーモン(snd,rcv,obs)の起動確認を行い、停止している場合は各デーモンの起動を実施 停止処理 HULFTの各デーモン(snd,rcv,obs)の起動確認を行い、起動している場合は各デーモンの停止を実施 監視処理 HULFTの各デーモン(snd,rcv,obs)の起動確認を実施 再起動処理 起動処理と同一の処理を実施   NFS ARK/HULFT ARK の構築例 それでは、実際に NFS ARK と HULFT ARK の構築についてお話していきたいと思います。 注意 HULFT ARK では、HULFT を制御する際、HULFT が提供しているクラスタ連携用のコマンドである hulclusterobs、hulclustersnd、hulclusterrcv を利用して、HULFT の起動や停止、状態監視を行います。 そのため、HULFT はクラスタ環境で稼働させるための構成(クラスタ連携用のコマンドが利用出来る構成)にする必要があります。 NFS ARK/HULFT ARK のパラメータ項目 NFS と HULFT のリソース作成時に設定する特有のパラメータを一覧表にまとめました。 NFS ARK のパラメータ 項目 説明 Export Point 次の条件に合致する NFS ファイルシステムのエクスポートポイントを選択 ・エクスポートポイントが NFS によってエクスポートされている ・エクスポートポイントが共有ドライブ上にある ・基礎ファイルシステムが LifeKeeper に保護されている場合、基礎ファイルシステムは In Service で、かつ、 リソース作成時の稼働系サーバー上で最高の優先順位を持っている ・NFS似ルートファイルシステム(fsid=0)としたエクスポートポイント、およびその配下の同一ファイルシステム上のエクスポートポイントは対象外 IP Tag 保護されている NFS ファイルシステムにアクセスするためにクライアントが使用する仮想 IP アドレスのタグ名を選択 HULFT ARK のパラメータ 項目 説明 HULEXEP path HULFTの実行モジュールの格納ディレクトリのパス名を入力 HULPATH path HULFTの環境設定ファイルの格納ディレクトリのパス名を入力 HULFT administrator user name HULFTインスタンスの起動・停止などを行う管理ユーザー名を入力 Filesystem Tag 集信ファイル格納ディレクトリ等の、HULFTと関連付いたFileSystemリソース名を選択 NFS/HULFT リソースの作成 NFS と HULFT のリソースを LifeKeeper の GUI によって作成する流れを例として紹介します。 NFS リソースの作成 処理内容 GUI画面例 リソース作成前のツリー構造 保護アプリケーションの選択(NFS) 稼働系ノードのSwitchbackタイプの選択 ※デフォルト:intelligent 稼働系ノードの選択 保護する共有ディスク上のマウントポイントの選択 使用する仮想IPアドレスのリソース名の選択 稼働系ノードの管理GUIに表示されるリソースタグ名の入力 稼働系ノードのリソース作成結果 待機系ノードの選択 待機系ノードのSwitchbackタイプの選択 ※デフォルト:intelligent 稼働系ノードの優先順位の設定 ※デフォルト:1 待機系ノードの優先順位の設定 ※デフォルト:10 待機系ノードのリソース作成準備の確認結果 待機系ノードの管理GUIに表示されるリソースタグ名の入力 待機系ノードのリソース作成結果 リソース作成後のツリー構造(NFS リソースと IP リソースとレプリケーションリソースの依存関係が自動で作成される) これで、LifeKeeper による NFS のリソースが完成です。 HULFT リソースの作成 処理内容 GUI画面例 リソース作成前のツリー構造 保護アプリケーションの選択(HULFT) 稼働系ノードのSwitchbackタイプの選択 ※デフォルト:intelligent 稼働系ノードの選択 HULFT 実行モジュール格納ディレクトリのパス名の入力 HULFT 環境設定ファイル格納ディレクトリの入力 HULFT インスタンスの管理ユーザー名の入力 HULFT リソースと関連付けたい ファイルシステム リソースの選択(例では、不要のため「none」を選択) 稼働系ノードのHULFT リソース階層に付けるタグ名の入力 ※デフォルト:hulft 稼働系ノードのリソース作成結果 待機系ノードの選択 待機系ノードのSwitchbackタイプの選択 ※デフォルト:intelligent 稼働系ノードの優先順位の設定 ※デフォルト:1 待機系ノードの優先順位の設定 ※デフォルト:10 待機系ノードのリソース作成準備の確認結果 待機系ノードのHULFT リソース階層に付けるタグ名の入力 ※デフォルト:hulft 待機系ノードのリソース作成結果 リソース作成後のツリー構造(HULFT リソースとレプリケーションリソース・IP リソースの依存関係が自動で作成される) ツリー構造の整理(複雑なツリー構造を簡潔に整理) これで、LifeKeeper による HULFT のリソースが完成です。 まとめ 今回は LifeKeeper ARK の導入事例と言うことで、NFS と HULFT のリソース作成について紹介してみました。 LifeKeeper ARK を購入することで、正式なサポートを受けられるほか、LifeKeeper として操作(起動・停止・監視・再起動)するためのスクリプトを準備する必要がなく、GUI 画面にてNFS 及び HULFT を構成する設定内容を選択または入力することで簡単にリソースとして導入が可能となっています。 LifeKeeper では NFS や HULFT 以外にも多数の ARK が用意されていますので、また次の機会に別の ARK について紹介していきたいと思います。 最後に NFS 及び HULFT ARK を導入するための手順を纏めます。 ・NFS 及び HULFT のリソース固有のパラメータの設定値を決定する ・LifeKeeper GUI を用いて、NFS 及び HULFT のリソースを作成する ・必要に応じてリソース構造を修正する その他 ARK の導入事例に関しては以下のリンクからどうぞ! LifeKeeper ARK の導入事例を紹介<JP1/AJS3編> – TechHarmony LifeKeeper ARK の導入事例を紹介<Oracle編> – TechHarmony LifeKeeper ARK の導入事例を紹介<SQL Server編> – TechHarmony 詳しい内容をお知りになりたいかたは、以下のバナーからSCSK LifeKeeper公式サイトまで
アバター