kirin-ri / memo

0 stars 0 forks source link

cicd #34

Open kirin-ri opened 1 week ago

kirin-ri commented 1 week ago
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
import * as cwactions from "aws-cdk-lib/aws-cloudwatch-actions";
import * as sns from "aws-cdk-lib/aws-sns";
import * as subscription from "aws-cdk-lib/aws-sns-subscriptions";
import * as chatbot from "aws-cdk-lib/aws-chatbot";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as path from "path";
import * as logs from "aws-cdk-lib/aws-logs";
import * as events from "aws-cdk-lib/aws-events";
import * as targets from "aws-cdk-lib/aws-events-targets";
import * as iam from "aws-cdk-lib/aws-iam";
import * as nag from "cdk-nag"; // cdk-nagモジュールのインポート

/**
 * MonitorCommonクラスの初期化に必要なパラメータ定義
 */
export interface MonitorCommonProps {
  /**  
   * 環境名を小文字の英字で指定する。  
   * リソースの物理名やNameタグに利用される。
   */
  envName: string;
  /**  
   * SecurityHubイベントの監視対象アカウントIDを指定する。  
   */
  accountId: string[];
  /**  
   * SecurityHubイベントの監視対象リージョンを指定する。  
   */
  region: string[];
  /**  
   * 監視アラーム通知リソースに異常が発生した場合の通知先E-mailアドレスを指定する。  
   */  
  monitorFailedNotificationEmail: string;
  /**  
   * 監視アラーム通知先に使用するWebHookのURLを指定する。  
   */ 
  webhookURL: string;  
  /**  
   * NotificationToTeams Function Durationアラーム閾値を指定する。  
   */ 
  notificationToTeamsFunctionDurationAlarmThreshold: number;
  /**  
   * SecurityHubEventNotification EventBridgeRule DeadLetterInvocationアラーム閾値を指定する。  
   */ 
  securityHubEventNotificationRuleDeadLetterInvocationAlarmThreshold: number;
}

/**
 * 監視共通要素を展開するConstruct
 * 
 * @remarks 
 * 監視に必要なAlarm通知先トピックやTeams通知用Lambda、SecurityHub監視用Ruleリソースを展開する。
 * - **Teams通知用Lambda**  
 *   SNSから受け取ったイベントをTeamsに通知するLambdaを構成するための関連要素をDeployする。
 *   - Teams通知Lambda用Log GP  
 *   - Teams通知Lambda用Role
 *   - Teams通知Lambda
 *   - Teams通知LambdaのDLQ
 * 
 * - **Alarm通知先トピック**  
 *   監視Alarmの通知先として利用するSNSTopicをDeployする。
 *   - Alarm通知Topic
 *   - Subscriptionに関連付けるDLQ
 * 
 * - **監視通知用リソースの異常通知先トピック**  
 *   監視通知用リソースに異常が発生した場合の通知先として利用するSNSTopicをDeployする。
 *   - 監視リソース異常通知Topic
 * 
 * - **SecurityHub監視Rule**  
 *   SecurityHubのイベントソースから要通知であるイベントを通知するEventBridge RuleをDeployする。
 *   - SecurityHub Event監視Rule
 * 
 * - **各種リソース毎の監視アラーム**   
 *   Alarm通知トピックに対して以下のメトリクスに対する監視アラームを生成する。  
 *   - NumberOfNotificationsFailed
 *   - NumberOfNotificationsFailedToRedriveToDlq  
 * 
 *   Teams通知用Lambdaに対して以下のメトリクスに対する監視アラームを生成する。  
 *   - Errors
 *   - DeadLetterErrors
 *   - DestinationDeliveryFailures
 *   - Throttles
 *   - Duration
 *   - AsyncEventsDropped  
 * 
 *   SecurityHub監視Ruleに対して以下のメトリクス監視アラームを生成する。  
 *   - DeadLetterInvocations  
 *   - FailedInvocations  
 *   - InvocationsFailedToBeSentToDlq 
 *   - ThrottledRules
 */
export class MonitorCommon extends Construct {
  /**
   * 監視通知用のトピック
   */
  public alarmTopic: sns.Topic;

  /**
   * 監視共通リソースを展開する。  
   * 初期化時に指定する{@link MonitorCommonProps 引数}で生成される環境のパラメータを調整する。
   * 
   * @param props - 
   * MonitorCommonクラスインスタンス初期化用のプロパティを指定する。詳細は{@link MonitorCommonProps}を参照すること。
   */
  constructor(scope: Construct, id: string, props: MonitorCommonProps) {
    super(scope, id);

    /**
     * ===============================
     * Temas通知用Lambda("Incoming Webhook"を使用)
     * ===============================
     */
    // Lambda LogGroup
    const myNotificationToTeamsFunctionLogGp = new logs.LogGroup(this, "NotificationToTeamsFunctionLogGp", {
      logGroupName: "/gc-std-archi1/" + props.envName + "/NotificationToTeamsFunctionLog",
      retention: logs.RetentionDays.THREE_MONTHS,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    cdk.Tags.of(myNotificationToTeamsFunctionLogGp).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionLog"
    );    

    // Lambda Role
    const myNotificationToTeamsFunctionRole = new iam.Role(this, "NotificationToTeamsFunctionRole", {
      roleName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionRole",
      assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
    });
    cdk.Tags.of(myNotificationToTeamsFunctionRole).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionRole"
    );
    // LogsへのWrite権限追加
    myNotificationToTeamsFunctionLogGp.grantWrite(myNotificationToTeamsFunctionRole);

    // DeadLetterQueueの作成
    const myNotificationToTeamsFunctionDLQueue = new sqs.Queue(this, "NotificationToTeamsFunctionDLQueue" , {
      queueName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDLQueue",
      enforceSSL: true,
    });
    cdk.Tags.of(myNotificationToTeamsFunctionDLQueue).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDLQueue"
    );

    // Lambda Function
    const myNotificationToTeamsFunction = new lambda.Function(this, "NotificationToTeamsFunction", {
      functionName: `gc-std-archi1-${props.envName}-notification-to-Teams`,
      code: lambda.Code.fromAsset(path.join(__dirname, "../../lambda/notification_to_teams/")),
      handler: "main.lambda_handler",
      runtime: lambda.Runtime.PYTHON_3_8,
      environment: {
        TARGET_URL: props.webhookURL,
      },
      role: myNotificationToTeamsFunctionRole,
      logGroup: myNotificationToTeamsFunctionLogGp,
      deadLetterQueueEnabled: true,
      deadLetterQueue: myNotificationToTeamsFunctionDLQueue
    });
    cdk.Tags.of(myNotificationToTeamsFunction).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-notification-to-Teams"
    );

    // nagエラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      myNotificationToTeamsFunction, 
      [
        { id: "AwsSolutions-L1", reason: "Because of implemented in python3.8."},
      ],
      false
    );    

    // QueueのDLQ設定エラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      myNotificationToTeamsFunctionDLQueue, 
      [
        { id: "AwsSolutions-SQS3", reason: "Because This Queue is DeadLetterQueue."}
      ],
      false
    );

    /**
     * ===============================
     * 通知用SNSトピック作成
     * ※通知用Lambdaとの関連付けも実施する
     * ===============================
     */
    // 監視アラーム通知用トピック作成
    this.alarmTopic = new sns.Topic(this, "AlarmTopic", {
      topicName: `gc-std-archi1-${props.envName}-AlarmTopic`
    })
    cdk.Tags.of(this.alarmTopic).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-AlarmTopic"
    );

    // 送信時のデータ暗号化を強制するポリシー(「AwsSolutions-SNS3」対応)
    this.alarmTopic.addToResourcePolicy(new iam.PolicyStatement({
      actions: ["sns:Publish"],
      effect: iam.Effect.DENY,
      resources: [this.alarmTopic.topicArn],
      conditions: {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      },
      principals: [new iam.AnyPrincipal()]
    }))

    // DeadLetterQueueの作成
    const alarmTopicDLQueue = new sqs.Queue(this, "AlarmTopicDLQueue" , {
      queueName: "gc-std-archi1-" + props.envName + "-AlarmTopicDLQueue",
      enforceSSL: true,
    });
    cdk.Tags.of(alarmTopicDLQueue).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-AlarmTopicDLQueue"
    );

    // サブスクリプションに「Teams通知用Lambda関数」を追加
    this.alarmTopic.addSubscription(new subscription.LambdaSubscription(myNotificationToTeamsFunction, {
      deadLetterQueue: alarmTopicDLQueue
    }));

    // CloudWatchからトピックにイベント発行できるようにポリシーを追加
    this.alarmTopic.grantPublish(new iam.ServicePrincipal("cloudwatch.amazonaws.com"))

    // SNSトピックのサーバサイド暗号化エラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      this.alarmTopic, 
      [
        { id: "AwsSolutions-SNS2", reason: "Because the SNS Topic must require publishers to use SSL."}
      ],
      false
    );

    // QueueのDLQ設定エラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      alarmTopicDLQueue, 
      [
        { id: "AwsSolutions-SQS3", reason: "Because this Queue is DeadLetterQueue."}
      ],
      false
    );

    /**
     * ===============================
     * 監視用リソース異常時のSNSトピック作成
     * ===============================
     */
    // 監視用リソース異常時の通知用トピック作成
    const monitorFailedTopic = new sns.Topic(this, "MonitorFailedTopic", {
      topicName: `gc-std-archi1-${props.envName}-MonitorFailedTopic`
    })
    cdk.Tags.of(monitorFailedTopic).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-MonitorFailedTopic"
    );

    // 送信時のデータ暗号化を強制するポリシー(「AwsSolutions-SNS3」対応)
    monitorFailedTopic.addToResourcePolicy(new iam.PolicyStatement({
      actions: ["sns:Publish"],
      effect: iam.Effect.DENY,
      resources: [monitorFailedTopic.topicArn],
      conditions: {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      },
      principals: [new iam.AnyPrincipal()]
    }))

    // サブスクリプションに「Eメール」を追加
    monitorFailedTopic.addSubscription(new subscription.EmailSubscription(props.monitorFailedNotificationEmail));

    // CloudWatchからトピックにイベント発行できるようにポリシーを追加
    monitorFailedTopic.grantPublish(new iam.ServicePrincipal("cloudwatch.amazonaws.com"))    

    // SNSトピックのサーバサイド暗号化エラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      monitorFailedTopic, 
      [
        { id: "AwsSolutions-SNS2", reason: "Because the SNS Topic must require publishers to use SSL."}
      ],
      false
    );

    /**
     * ===============================
     * AWS Chatbot(Teams連携)作成
     * ※通知用SNSトピックとの関連付けも行う
     * ※TeamsにChatbot連携アプリ(aws)を導入している必要があるが、全社ポリシーにより原則導入不可のためコメントアウト
     * ===============================
     */ 
    // // AWS Chatbot用のIAM Role作成
    // const myChatbotRole = new iam.Role(this, "ChatbotRole", {
    //   description: `Permission sets for read-only`,
    //   roleName: `gc-std-archi1-${props.envName}-chatbotRole`,
    //   assumedBy: new iam.ServicePrincipal("chatbot.amazonaws.com"),
    //   managedPolicies: [
    //     iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess"),
    //   ],
    // });

    // // nagエラー抑止
    // nag.NagSuppressions.addResourceSuppressions(
    //   myChatbotRole, 
    //   [
    //     { id: "AwsSolutions-IAM4", reason: "Use AWS Managed Policies.", appliesTo: ["Policy::arn:<AWS::Partition>:iam::aws:policy/ReadOnlyAccess"]},
    //   ],
    //   false
    // );

    // AWS Chatbot作成
    // 予めChatbotマネジメントコンソール上でTemas初期認証を済ませておく必要があり、初期認証後にCDKでデプロイする
    /*
    const myTeamsChatbot = new chatbot.CfnMicrosoftTeamsChannelConfiguration(this, "TeamsChatbot", {
      configurationName: `gc-std-archi1-${props.envName}-chatbot`,
      iamRoleArn: myChatbotRole.roleArn,
      teamId: "8fb2fb73-3892-4013-a1d9-2c0ab1f8d461", // TemasチャネルURL中の「groupId=xxxxx」
      teamsChannelId: "19%3a525651d941884ec5b6c243dd301a187e%40thread.tacv2",  // TemasチャネルURL中の「threadId=xxxxx」
      teamsTenantId: "1fcf450d-bb71-4efd-ae5d-90c7be757e12", // TemasチャネルURL中の「tenantId=xxxxx」

      // オプションパラメータ
      loggingLevel: "ERROR",
      snsTopicArns: [ this.alarmTopic.topicArn ],
    });
    */

    /**
     * ===============================
     * EventBridge Rule(Security Hubイベント通知)
     * ※通知用SNSトピックとの関連付けも行う
     * ===============================
     */ 
    const mySecurityHubEventNotificationRule = new events.Rule(this, "SecurityHubEventNotification", {
      ruleName: `gc-std-archi1-${props.envName}-security-hub-notification`,
      eventPattern: {
        source: ["aws.securityhub"],
        detailType: ["Security Hub Findings - Imported"],
        account: props.accountId,
        region: props.region,
        detail: {
          "$or": [
            // SecurityHubの「統合」を無効に設定している場合のイベントパターン
            {
              "findings": {
                // 基礎セキュリティのベストプラクティスの範囲
                "ProductFields": {
                  "StandardsArn": [ "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0" ]
                },
                // コンプライアンス順守状況が"PASSED"以外
                "Compliance": {
                  "Status": [ "FAILED", "WARNING", "NOT_AVAILABLE" ]
                },
                // 重要度が"HIGH"(優先事項として要対処)以上
                "Severity": {
                  "Label": [ "CRITICAL", "HIGH" ]
                },
                // ステータスが"ACTIVE"なもの
                "RecordState": [ "ACTIVE" ],
                // フロー状態が"NEW"であるもの
                "Workflow": {
                  "Status": [ "NEW" ]
                }
              }
            },
            // SecurityHubの「統合」を有効に設定している場合のイベントパターン
            {
              "findings": {
                // コンプライアンス順守状況が"PASSED"以外
                // 基礎セキュリティのベストプラクティスの範囲
                "Compliance": {
                  "Status": [ "FAILED", "WARNING", "NOT_AVAILABLE" ],
                  "AssociatedStandards": {
                    "StandardsId": ["standards/aws-foundational-security-best-practices/v/1.0.0"]
                  }  
                },
                // 重要度が"HIGH"(優先事項として要対処)以上
                "Severity": {
                  "Label": [ "CRITICAL", "HIGH" ]
                },
                // ステータスが"ACTIVE"なもの
                "RecordState": [ "ACTIVE" ],
                // フロー状態が"NEW"であるもの
                "Workflow": {
                  "Status": [ "NEW" ]
                }
              }
            }
          ]
        }
      },
      targets: [ new targets.SnsTopic(this.alarmTopic) ]
    });

    /**
     * ===============================
     * アラーム通知用SNSTopic アラーム監視定義
     * ===============================
     */
    // NumberOfNotificationsFailed のアラーム作成
    const alarmTopicNumberOfNotificationsFailedAlarm = this.alarmTopic.metricNumberOfNotificationsFailed({
      period: cdk.Duration.minutes(1),
      statistic: cloudwatch.Stats.SUM,    
    })
    .createAlarm(this, "AlarmTopicNumberOfNotificationsFailedAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-AlarmTopic-NumberOfNotificationsFailed",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));

    // NumberOfNotificationsFailedToRedriveToDlq のアラーム作成
    const alarmTopicNumberOfNotificationsFailedToRedriveToDlqAlarm = this.alarmTopic.metric(
      "NumberOfNotificationsFailedToRedriveToDlq",
      {
        period: cdk.Duration.minutes(5),
        statistic: cloudwatch.Stats.SUM,    
      }
    )
    .createAlarm(this, "AlarmTopicNumberOfNotificationsFailedToRedriveToDlqAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-AlarmTopic-NumberOfNotificationsFailedToRedriveToDlq",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));    

    /**
     * ===============================
     * Temas通知用Lambda アラーム監視定義
     * ===============================
     */ 
    // Errors のアラーム作成
    const myNotificationToTeamsFunctionErrorsAlarm = myNotificationToTeamsFunction.metricErrors({
      period: cdk.Duration.minutes(5),
      statistic: cloudwatch.Stats.SUM,
    })
    .createAlarm(this, "NotificationToTeamsFunctionErrorsAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionErrors",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // DeadLetterErrors のアラーム作成
    const myNotificationToTeamsFunctionDeadLetterErrorsAlarm = myNotificationToTeamsFunction.metric(
      "DeadLetterErrors",
      {
        period: cdk.Duration.minutes(5),
        statistic: cloudwatch.Stats.SUM,
      }
    )
    .createAlarm(this, "NotificationToTeamsFunctionDeadLetterErrorsAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDeadLetterErrors",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // DestinationDeliveryFailures のアラーム作成
    const myNotificationToTeamsFunctionDestinationDeliveryFailuresAlarm = myNotificationToTeamsFunction.metric(
      "DestinationDeliveryFailures",
      {
        period: cdk.Duration.minutes(5),
        statistic: cloudwatch.Stats.SUM,
      }
    )
    .createAlarm(this, "NotificationToTeamsFunctionDestinationDeliveryFailuresAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDestinationDeliveryFailures",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // Throttles のアラーム作成
    const myNotificationToTeamsFunctionThrottlesAlarm = myNotificationToTeamsFunction.metricThrottles({
      period: cdk.Duration.minutes(5),
      statistic: cloudwatch.Stats.SUM,
    })
    .createAlarm(this, "NotificationToTeamsFunctionThrottlesAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionThrottles",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // Duration のアラーム作成
    const myNotificationToTeamsFunctionDurationAlarm = myNotificationToTeamsFunction.metricDuration({
      period: cdk.Duration.minutes(5),
      statistic: cloudwatch.Stats.AVERAGE,
    })
    .createAlarm(this, "NotificationToTeamsFunctionDurationAlarm", {
      evaluationPeriods: 1,
      threshold: props.notificationToTeamsFunctionDurationAlarmThreshold,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDuration",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // AsyncEventsDropped のアラーム作成
    const myNotificationToTeamsFunctionAsyncEventsDroppedAlarm = myNotificationToTeamsFunction.metric(
      "AsyncEventsDropped",
      {
        period: cdk.Duration.minutes(5),
        statistic: cloudwatch.Stats.SUM,
      }
    )
    .createAlarm(this, "NotificationToTeamsFunctionAsyncEventsDroppedAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionAsyncEventsDropped",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    /**
     * ===============================
     * SecurityHubイベント監視Rule アラーム監視定義
     * ===============================
     */     
    // DeadLetterInvocationsの監視アラーム作成
    const mySecurityHubEventNotificationRuleDeadLetterInvocationAlarm = new cloudwatch.Metric({
      namespace: "AWS/Events",
      metricName: "DeadLetterInvocations",
      dimensionsMap: {
        RuleName: mySecurityHubEventNotificationRule.ruleName
      },
      statistic: cloudwatch.Stats.MAXIMUM,
      period: cdk.Duration.minutes(5)
    })
    .createAlarm(this, "SecurityHubEventNotificationRuleDeadLetterInvocationAlarm", {
      alarmName: `gc-std-archi1-${props.envName}-SecurityHubEventNotificationRule-DeadLetterInvocations`,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
      threshold: props.securityHubEventNotificationRuleDeadLetterInvocationAlarmThreshold,
      evaluationPeriods: 1,
    });
    // Alarmアクション追加
    mySecurityHubEventNotificationRuleDeadLetterInvocationAlarm.addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));  

    // FailedInvocationsの監視アラーム作成
    const mySecurityHubEventNotificationRuleFailedInvocationsAlarm = new cloudwatch.Metric({
      namespace: "AWS/Events",
      metricName: "FailedInvocations",
      dimensionsMap: {
        RuleName: mySecurityHubEventNotificationRule.ruleName
      },
      statistic: cloudwatch.Stats.SUM,
      period: cdk.Duration.minutes(5)
    })
    .createAlarm(this, "SecurityHubEventNotificationRuleFailedInvocationsAlarm", {
      alarmName: `gc-std-archi1-${props.envName}-SecurityHubEventNotificationRule-FailedInvocations`,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
      threshold: 0,
      evaluationPeriods: 1,
    });
    // Alarmアクション追加    
    mySecurityHubEventNotificationRuleFailedInvocationsAlarm.addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));

    // InvocationsFailedToBeSentToDlqの監視アラーム作成
    const mySecurityHubEventNotificationRuleInvocationsFailedToBeSentToDlqAlarm = new cloudwatch.Metric({
      namespace: "AWS/Events",
      metricName: "InvocationsFailedToBeSentToDlq",
      dimensionsMap: {
        RuleName: mySecurityHubEventNotificationRule.ruleName
      },
      statistic: cloudwatch.Stats.SUM,
      period: cdk.Duration.minutes(5)
    })
    .createAlarm(this, "SecurityHubEventNotificationRuleInvocationsFailedToBeSentToDlqAlarm", {
      alarmName: `gc-std-archi1-${props.envName}-SecurityHubEventNotificationRule-InvocationsFailedToBeSentToDlq`,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
      threshold: 0,
      evaluationPeriods: 1,
    });
    // Alarmアクション追加    
    mySecurityHubEventNotificationRuleInvocationsFailedToBeSentToDlqAlarm.addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));

    // ThrottledRulesの監視アラーム作成
    const mySecurityHubEventNotificationRuleThrottledRulesAlarm = new cloudwatch.Metric({
      namespace: "AWS/Events",
      metricName: "ThrottledRules",
      dimensionsMap: {
        RuleName: mySecurityHubEventNotificationRule.ruleName
      },
      statistic: cloudwatch.Stats.SUM,
      period: cdk.Duration.minutes(5)
    })
    .createAlarm(this, "SecurityHubEventNotificationRuleThrottledRulesAlarm", {
      alarmName: `gc-std-archi1-${props.envName}-SecurityHubEventNotificationRule-ThrottledRules`,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
      threshold: 0,
      evaluationPeriods: 3,
    });
    // Alarmアクション追加
    mySecurityHubEventNotificationRuleThrottledRulesAlarm.addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));
  };
}
kirin-ri commented 1 week ago

btrikra:~/environment $ npm install -g aws-cdk@2.84.0 npm error code EACCES npm error syscall mkdir npm error path /usr/local/lib/node_modules/aws-cdk npm error errno -13 npm error [Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/aws-cdk'] { npm error errno: -13, npm error code: 'EACCES', npm error syscall: 'mkdir', npm error path: '/usr/local/lib/node_modules/aws-cdk' npm error } npm error npm error The operation was rejected by your operating system. npm error It is likely you do not have the permissions to access this file as the current user npm error npm error If you believe this might be a permissions issue, please double-check the npm error permissions of the file and its containing directories, or try running npm error the command again as root/Administrator. npm error A complete log of this run can be found in: /home/ec2-user/.npm/_logs/2024-11-07T00_31_46_911Z-debug-0.log

kirin-ri commented 1 week ago

btrikra:~/environment $ sudo npm install -g aws-cdk@2.84.0 npm error code EEXIST npm error path /usr/local/bin/cdk npm error EEXIST: file already exists npm error File exists: /usr/local/bin/cdk npm error Remove the existing file and try again, or run npm npm error with --force to overwrite files recklessly. npm error A complete log of this run can be found in: /root/.npm/_logs/2024-11-07T00_36_30_609Z-debug-0.log

kirin-ri commented 6 days ago

btrikra:~/environment $ git clone https://git-codecommit.us-east-2.amazonaws.com/v1/repos/FBS-viz-trial Cloning into 'FBS-viz-trial'... fatal: unable to access 'https://git-codecommit.us-east-2.amazonaws.com/v1/repos/FBS-viz-trial/': The requested URL returned error: 403

kirin-ri commented 6 days ago

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudwatch.GraphWidget.html グラフ等Widgetを作っていく ※prposは別ページに遷移 https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudwatch.GraphWidgetProps.html ここでグルーピングが出来ると思っている。。。 ※折れ線と棒グラフを同じグループで取り扱い時どうするの?とかが謎 leftとrightがあるんだけど、説明文がほぼ同じでちゃんと理解出来ない。 多分右にY軸か、左にY軸なのかだとは思うんだが。。

配置はIMetrics[]に入れた順に表示されると思うんだが。。。

という具合にはっきり分からないからサンプルで組んでみたいです。

kirin-ri commented 6 days ago
CloudWatch DashBoard作り方調査
 ∟L2で作れるけど、具体的にサンプル描いて作り方をfix

・構成検討
 ∟configから必要な情報(Metric名とか)とる?
  メインコード側(標準アーキ側)でSSMに入れて、可視化APPでfromで取得するのもあり
  ※fromでmetricまで取れるか不明 要確認
  調査結果から後者で行きたい
  検討結果をまとめて、やり方のfix
kirin-ri commented 6 days ago
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";

export class CustomDashboard extends Construct {
  constructor(scope: Construct, id: string, envName: string) {
    super(scope, id);

    // CloudWatch Dashboardの作成
    const dashboard = new cloudwatch.Dashboard(this, "CustomDashboard", {
      dashboardName: `custom-${envName}-dashboard`,
    });

    // EC2インスタンスのCPU使用率メトリクスを設定
    const cpuUtilizationMetric = new cloudwatch.Metric({
      namespace: "AWS/EC2",
      metricName: "CPUUtilization",
      dimensionsMap: { InstanceId: "i-0123456789abcdef0" },  // インスタンスIDを指定
      statistic: "Average",
      period: cdk.Duration.minutes(5),
    });

    // グラフウィジェットの作成
    const cpuWidget = new cloudwatch.GraphWidget({
      title: "CPU Utilization",
      left: [cpuUtilizationMetric],
    });

    // ウィジェットをダッシュボードに追加
    dashboard.addWidgets(cpuWidget);
  }
}
kirin-ri commented 6 days ago

aws ec2 describe-instances --query "Reservations[].Instances[].InstanceId" --output text

kirin-ri commented 6 days ago
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { GcStdArchi1Stack } from "../lib/gc-std-archi1-stack";
import * as nag from "cdk-nag"; // cdk-nagモジュールのインポート
import * as config from "config"; // node-configモジュールのインポート

/**
 * 環境変数設定確認
 * Deploy環境の環境変数を参照し、紐つけるパラメータファイルを制御するため、環境変数"NODE_ENV"に値が設定されているか確認
 */
const envName = process.env.NODE_ENV;
if (envName == null) {
  console.error(
    "Error: 環境変数'NODE_ENV'にパラメータファイル名となる環境名を設定してください。\n \
      例: export NODE_ENV=Dev"
  );
  process.exit(1);
}

const app = new cdk.App();

/**
 * 標準アーキ1 CDK APPクラス
 *
 * @remarks
 * 標準アーキ1環境をDeployするための最上位となるAPPクラス
 * このクラスには、「標準アーキ1 Project tagのvalue」、「標準アーキ1 Environment tagのvalue」のtagが付与される。
 *
 *  **実行時の必要パラメータ**
 *    - description : 標準アーキ1 CFn Stackのdescription
 *    - env : Deploy先リージョン、アカウント リージョンの指定はALBからログ出力のため必須  
 *    - terminationProtection : 標準アーキ1 Stackの削除保護 アセットでは無効化
 */
export const GcStdArchiStack = new GcStdArchi1Stack(app, `${envName}-GcStdArchi1Stack`, {
  description: "The Main-Stack for the Government-Cloud standard architecture - No.1",
  env: {
    region: "ap-northeast-1",
    account: config.get("common.accountId")
  },  
  // terminationProtection: true,
});

/**
 * 標準アーキ1 内リソース共通tagの付与
 */
cdk.Tags.of(app).add("Project", config.get("common.project"));
cdk.Tags.of(app).add("Environment", config.get("common.envPrefix"));

// cdk-nagによるセキュリティ静的解析を有効化
cdk.Aspects.of(app).add(new nag.AwsSolutionsChecks({ verbose: true }));
kirin-ri commented 2 days ago
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { Nw } from "./resources/gc-std-archi1-nw";
import { AppS3Bucket } from "./resources/gc-std-archi1-s3";
import { MainAurora } from "./resources/gc-std-archi1-mainaurora";
import { EcsTourreservation } from "./resources/gc-std-archi1-ecs-tourreservation";
import { EcsHotelmanagement } from "./resources/gc-std-archi1-ecs-hotelmanagement";
import { EcsMastermanagement } from "./resources/gc-std-archi1-ecs-mastermanagement";
import { EcsScheduleLaunch } from "./resources/gc-std-archi1-ecs-schedulelaunch";
import { Logarchive } from "./resources/gc-std-archi1-logarchive";
import { MonitorCommon } from "./resources/gc-std-archi1-monitor";
import { DataInput } from "./resources/gc-std-archi1-datainput";
import * as nag from "cdk-nag"; // cdk-nagモジュールのインポート
import config = require("config"); // node-configモジュールのインポート

/**
 * 標準アーキ1 CDK Stackクラス
 *
 * @remarks
 * 標準アーキ1環境をDeployするためのCDKのStackクラス詳細が定義されるResource Constructを生成し、必要に応じてobjectの引き渡しを行う。  
 *  **monitorCommon Constructs構成物**  
 *   - 監視通知用SNSトピック
 *   - Teams通知Lambda用Log GP
 *   - Teams通知Lambda用Role
 *   - Teams通知Lambda
 *   - SecurityHubのEvent監視Rule
 * 
 *  **nw Constructs構成物**
 *   - サーバアクセスログバケット
 *   - VPC FlowLog用バケット
 *   - VPC関連リソース
 *   - 各種Endpoint
 *   - Endpoint SG
 *   - ECRプルスルーキャッシュ
 *   - ADOTイメージECRリポジトリ情報
 *   - ECRプルスルーキャッシュ要件のIAMポリシーステートメント
 *   - ADOTタスク要件のIAMポリシーステートメント
 *   - 監視Alarm(サーバアクセスログバケット、VPC FlowLog用バケット)  
 * 
 * **appS3Bucket Constructs構成物**
 *   - APP用S3バケット
 *   - GCログ格納用S3バケット
 *   - 監視Alarm(APP用S3バケット、GCログ格納用S3バケット)  
 * 
 * **main aurora Constructs構成物**
 *   - Main Aurora用SG,
 *   - Main Aurora用のパラメターGP
 *   - Main Aurora用Subnet GP
 *   - Main Aurora資格情報格納用Secrets Manager
 *   - Main Aurora資格情報ローテンション
 *   - Aurora Serverless v2
 *   - 監視Alarm(Aurora Serverless v2)  
 * 
 * **ecsHotelmanagement Constructs構成物**
 *   - Internal ALB用SG
 *   - Internal ALB
 *   - Internal ALB用ログバケット
 *   - Hotel DynamoDB
 *   - Hotel Management用SG
 *   - Hotel Management用Log GP
 *   - Hotel Management taskdef
 *   - Hotel Management ALB連携サービス
 *   - Hotel Management AAS
 *   - 監視Alarm(Internal ALB、Internal ALB用ログバケット、Hotel DynamoDB、Hotel Management ALB連携サービス)
 * 
 * **ecsMastermanagement Constructs構成物**
 *   - Master Mnagement SQS(DLQ含む)
 *   - Master Management用SG
 *   - Master Management用Log GP
 *   - Master Management Cluster
 *   - Master Management taskdef
 *   - Master Management SQS連携サービス
 *   - Master Management AAS
 *   - 監視Alarm(Master Mnagement SQS(DLQ含む)、Master Management SQS連携サービス)
 * 
 *  **ecsScheduleLaunch Constructs構成物**
 *   - スケジュールバッチ用SG
 *   - スケジュールバッチ用Log GP
 *   - スケジュールバッチ taskdef
 *   - スケジュールバッチタスク起動Rule
 *   - 監視Alarm(スケジュールバッチタスク起動Rule)
 * 
 * **ecsTourreservation Constructs構成物**
 *   - Redis用SG
 *   - Redis用Subnet GP
 *   - Redis用パラメータGP
 *   - Redis Cluster
 *   - Global ALB SG
 *   - Global ALB
 *   - Global ALB ログ用バケット
 *   - Global ALB, WAF用ログバケット
 *   - WAF用Web ACL
 *   - Tour Reservation用SG
 *   - Tour Reservation用Log GP
 *   - Tour Reservation用 taskdef
 *   - Tour Reservation用ALB連携サービス
 *   - Tour Reservation用AAS
 *   - 監視Alarm(Redis Cluster、Global ALB、Global ALB ログ用バケット、Global ALB、WAF用ログバケット、Tour Reservation用ALB連携サービス)
 * 
 * **logarchive Constructs構成物**
 *   - Log Archive用S3バケット
 *   - Log Archive用KDF
 *   - メトリックフィルター設定用Lambda
 *   - 監視Alarm(Log Archive用S3バケット、メトリックフィルター設定用Lambda)
 * 
 * **dataInput Constructs構成物**
 *   - 初期データ投入Lambda用SG  
 *   - 初期データ投入Lambda用Log GP  
 *   - 初期データ投入Lambda用Role  
 *   - 初期データ投入Lambda用DynamoDB初期データのLayer  
 *   - 初期データ投入Lambda用Aurora初期データのLayer  
 *   - 初期データ投入Lambda用psycopg2モジュールのLayer  
 *   - 初期データ投入Lambda
 *   - Stack作成検知Rule
 *   - 監視Alarm(Lambda、EventBridge Rule)
 * 
 */
export class GcStdArchi1Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: cdk.StackProps) {
    super(scope, id, props);
    /**
     * 監視用共通リソースConstructクラス
     */
    const monitorCommon = new MonitorCommon(this, "MonitorCommon", {
      envName: config.get("common.envPrefix"),
      accountId: [ this.account ],
      region: [ this.region ],
      monitorFailedNotificationEmail: config.get("monitorCommon.monitorFailedNotificationEmail"),
      webhookURL: config.get("monitorCommon.webhookURL"),
      notificationToTeamsFunctionDurationAlarmThreshold: config.get("monitorCommonAlarm.notificationToTeamsFunctionDurationAlarmThreshold"),
      securityHubEventNotificationRuleDeadLetterInvocationAlarmThreshold: config.get("monitorCommonAlarm.securityHubEventNotificationRuleDeadLetterInvocationAlarmThreshold"),
    });

    /**
     * 標準アーキ1NW共通関連リソースContructクラス
     */
    const nw = new Nw(this, "Nw", {
      envName: config.get("common.envPrefix"),
      vpcCider: config.get("nw.vpcCider"),
      vpcMaxAZs: config.get("nw.vpcMaxAZs"),
      vpcPublicSubnetCidrMask: config.get("nw.vpcPublicSubnetCidrMask"),
      vpcPrivateSubnetCidrMask: config.get("nw.vpcPrivateSubnetCidrMask"),
      alarmTopic: monitorCommon.alarmTopic,

      // アラーム定義の閾値
      serverAccessLogBucket4xxErrorsAlarmThreshold: config.get("nwAlarm.serverAccessLogBucket4xxErrorsAlarmThreshold"),
      serverAccessLogBucket5xxErrorsAlarmThreshold: config.get("nwAlarm.serverAccessLogBucket5xxErrorsAlarmThreshold"),
      serverAccessLogBucketAllRequestsAlarmThreshold: config.get("nwAlarm.serverAccessLogBucketAllRequestsAlarmThreshold"),
      flowLogBucket4xxErrorsAlarmThreshold: config.get("nwAlarm.flowLogBucket4xxErrorsAlarmThreshold"),
      flowLogBucket5xxErrorsAlarmThreshold: config.get("nwAlarm.flowLogBucket5xxErrorsAlarmThreshold"),
      flowLogBucketAllRequestsAlarmThreshold: config.get("nwAlarm.flowLogBucketAllRequestsAlarmThreshold"),
    });

    /**
    * 標準アーキ1共通S3関連リソースContructクラス
    */
    const appS3Bucket = new AppS3Bucket(this, "AppS3Bucket", {
      envName: config.get("common.envPrefix"),
      serverAccessLogBucket: nw.myServerAccessLogBucket,
      alarmTopic: monitorCommon.alarmTopic,

      // アラーム定義の閾値
      appBucket4xxErrorsAlarmThreshold: config.get("appS3BucketAlarm.appBucket4xxErrorsAlarmThreshold"),
      appBucket5xxErrorsAlarmThreshold: config.get("appS3BucketAlarm.appBucket5xxErrorsAlarmThreshold"),
      appBucketAllRequestsAlarmThreshold: config.get("appS3BucketAlarm.appBucketAllRequestsAlarmThreshold"),
      appGCLogBucket4xxErrorsAlarmThreshold: config.get("appS3BucketAlarm.appGCLogBucket4xxErrorsAlarmThreshold"),
      appGCLogBucket5xxErrorsAlarmThreshold: config.get("appS3BucketAlarm.appGCLogBucket5xxErrorsAlarmThreshold"),
      appGCLogBucketAllRequestsAlarmThreshold: config.get("appS3BucketAlarm.appGCLogBucketAllRequestsAlarmThreshold"),
    });

    /**
    * 標準アーキ1Main Aurora関連リソースContructクラス
    */
    const mainaurora = new MainAurora(this, "Aurora", {
      envName: config.get("common.envPrefix"),
      vpc: nw.myVpc,
      clusterParam: config.get("mainAurora.clusterParam"),
      maxAurora: config.get("mainAurora.maxAurora"),
      minAurora: config.get("mainAurora.minAurora"),
      alarmTopic: monitorCommon.alarmTopic,

      // アラーム定義の閾値
      mainAuroraInstanceAuroraReplicaLagAlarmThreshold: config.get("mainAuroraAlarm.mainAuroraInstanceAuroraReplicaLagAlarmThreshold"),
      mainAuroraInstanceCPUUtilizationAlarmThreshold: config.get("mainAuroraAlarm.mainAuroraInstanceCPUUtilizationAlarmThreshold"),
      mainAuroraInstanceDatabaseConnectionsAlarmThreshold: config.get("mainAuroraAlarm.mainAuroraInstanceDatabaseConnectionsAlarmThreshold"), // ACU4の場合の最大コネクション数の80%
      mainAuroraClusterVolumeBytesUsedAlarmThreshold: config.get("mainAuroraAlarm.mainAuroraClusterVolumeBytesUsedAlarmThreshold"), // 325.6G
    });

    // Aurora Secrets Rotation Nested Stackエラー抑止
    nag.NagSuppressions.addResourceSuppressionsByPath(
      this,
      // "Dev-GcStdArchi1Stack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/Resource",
      `${this.stackName}/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/Resource`,
      [
        {id: "AwsSolutions-IAM4", reason: "Because of NestedStack"},
      ]
    );

    nag.NagSuppressions.addResourceSuppressionsByPath(
      this,
      // "/Dev-GcStdArchi1Stack/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource",
      `${this.stackName}/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/ServiceRole/DefaultPolicy/Resource`,
      [
        {id: "AwsSolutions-IAM5", reason: "Because of NestedStack"},
      ]
    );

    /**
    * 標準アーキ1Hotel Management関連リソースContructクラス
    */
    const ecsHotelmanagement = new EcsHotelmanagement(this, "EcsHotelmanagement", {
      envName: config.get("common.envPrefix"),
      vpc: nw.myVpc,
      appBucket: appS3Bucket.myAppBucket,
      appGCLogBucket: appS3Bucket.myAppGCLogBucket,
      serverAccessLogBucket: nw.myServerAccessLogBucket,
      vpcEndpointSg: nw.myVpcEndpointSg,
      myAdotEcrRepository: nw.myAdotEcrRepository,
      pullThryoughCachePolicyStatement: nw.pullThryoughCachePolicyStatement,
      awsOTelTaskRolePolicyStatement: nw.awsOTelTaskRolePolicyStatement,
      alarmTopic: monitorCommon.alarmTopic,

      hotelTableReadCapacity: config.get("hotelmanagement.hotelTableReadCapacity"),
      hotelTableWriteCapacity: config.get("hotelmanagement.hotelTableWriteCapacity"),
      hotelTableReadMinCapacity: config.get("hotelmanagement.hotelTableReadMinCapacity"),
      hotelTableReadMaxCapacity: config.get("hotelmanagement.hotelTableReadMaxCapacity"),
      hotelTableReadTarget: config.get("hotelmanagement.hotelTableReadTarget"),
      hotelTableWriteMinCapacity: config.get("hotelmanagement.hotelTableWriteMinCapacity"),
      hotelTableWriteMaxCapacity: config.get("hotelmanagement.hotelTableWriteMaxCapacity"),
      hotelTableWriteTarget: config.get("hotelmanagement.hotelTableWriteTarget"),
      hotelmanagementCPU: config.get("hotelmanagement.hotelmanagementCPU"),
      hotelmanagementMEM: config.get("hotelmanagement.hotelmanagementMEM"),
      hotelmanagementEcrRepositoryArn: config.get("hotelmanagement.hotelmanagementEcrRepositoryArn"),
      // hotelmanagementEnvSpringLifecycleTimeoutPerShutdownPhase: config.get("hotelmanagement.hotelmanagementEnvSpringLifecycleTimeoutPerShutdownPhase"), // チューニングする時のみ使用
      hotelmanagementEnvSpringProfilesActive: config.get("hotelmanagement.hotelmanagementEnvSpringProfilesActive"),
      hotelmanagementEnvGcUploadIntervalSec: config.get("hotelmanagement.hotelmanagementEnvGcUploadIntervalSec"),
      hotelmanagementEnvGcLoglevel: config.get("hotelmanagement.hotelmanagementEnvGcLoglevel"),
      hotelmanagementEnvOtelResourceAttributes: config.get("hotelmanagement.hotelmanagementEnvOtelResourceAttributes"),
      hotelmanagementDesiredCount: config.get("hotelmanagement.hotelmanagementDesiredCount"),
      hotelmanagementFargateAASMin: config.get("hotelmanagement.hotelmanagementFargatAASMin"),
      hotelmanagementFargateAASMax: config.get("hotelmanagement.hotelmanagementFargatAASMax"),
      hotelmanagementFargateAASTarget: config.get("hotelmanagement.hotelmanagementFargatAASMax"),

      // アラーム定義の閾値
      hotelmanagementCPUThreshold: config.get("hotelmanagementAlarm.hotelmanagementCPUThreshold"),
      hotelmanagementMemThreshold: config.get("hotelmanagementAlarm.hotelmanagementMemThreshold"),
      internalAlbCode4XX: config.get("hotelmanagementAlarm.internalAlbCode4XX"),
      internalAlbCode5XX: config.get("hotelmanagementAlarm.internalAlbCode5XX"),
      internalAlbCode500: config.get("hotelmanagementAlarm.internalAlbCode500"),
      internalAlbCode502: config.get("hotelmanagementAlarm.internalAlbCode502"),
      internalAlbCode503: config.get("hotelmanagementAlarm.internalAlbCode503"),
      internalAlbCode504: config.get("hotelmanagementAlarm.internalAlbCode504"),
      internalAlbRejectCount: config.get("hotelmanagementAlarm.internalAlbRejectCount"),
      hotelmanagementTGCode2XX: config.get("hotelmanagementAlarm.hotelmanagementTGCode2XX"),
      hotelmanagementTGCode3XX: config.get("hotelmanagementAlarm.hotelmanagementTGCode3XX"),
      hotelmanagementTGCode4XX: config.get("hotelmanagementAlarm.hotelmanagementTGCode4XX"),
      hotelmanagementTGCode5XX: config.get("hotelmanagementAlarm.hotelmanagementTGCode5XX"),
      hotelmanagementTGResponseTime: config.get("hotelmanagementAlarm.hotelmanagementTGResponseTime"),
      dynamoDBRCUUtilization: config.get("hotelmanagementAlarm.dynamoDBRCUUtilization"),
      dynamoDBWCUUtilization: config.get("hotelmanagementAlarm.dynamoDBWCUUtilization"),
      hotelTableConsumedWCU: config.get("hotelmanagementAlarm.hotelTableConsumedWCU"),
      hotelTableConsumedRCU: config.get("hotelmanagementAlarm.hotelTableConsumedRCU"),
      dynamoDBMaxProvisionedTableRCUUtilization: config.get("hotelmanagementAlarm.dynamoDBMaxProvisionedTableRCUUtilization"),
      dynamoDBMaxProvisionedTableWCUUtilization: config.get("hotelmanagementAlarm.dynamoDBMaxProvisionedTableWCUUtilization"),
      hotelTableSuccessfulRequestLatency: config.get("hotelmanagementAlarm.hotelTableSuccessfulRequestLatency"),
      internalAlbLogBucket4xxErrors: config.get("hotelmanagementAlarm.internalAlbLogBucket4xxErrors"),
      internalAlbLogBucket5xxErrors: config.get("hotelmanagementAlarm.internalAlbLogBucket4xxErrors"),
      internalAlbLogBucketAllRequests: config.get("hotelmanagementAlarm.internalAlbLogBucketAllRequests"),
    });
    /**
    * 標準アーキ1Master Managementn関連リソースContructクラス
    */
    const ecsMastermanagement = new EcsMastermanagement(this, "EcsMastermanagement", {
      //共通リソース
      envName: config.get("common.envPrefix"), 
      vpc: nw.myVpc,
      appBucket: appS3Bucket.myAppBucket,
      appGCLogBucket: appS3Bucket.myAppGCLogBucket,
      vpcEndpointSg: nw.myVpcEndpointSg,
      mainAuroraSecret: mainaurora.myMainAuroraSecret,
      internalAlb: ecsHotelmanagement.myInternalAlb,
      mainAuroraCluster: mainaurora.myMainAuroraCluster,
      myAdotEcrRepository: nw.myAdotEcrRepository,
      pullThryoughCachePolicyStatement: nw.pullThryoughCachePolicyStatement,
      awsOTelTaskRolePolicyStatement: nw.awsOTelTaskRolePolicyStatement,
      alarmTopic: monitorCommon.alarmTopic,

      //サービス
      mastermanagementDesiredCount: config.get("mastermanagement.mastermanagementDesiredCount"),
      mastermanagementAASMax: config.get("mastermanagement.mastermanagementAASMax"),
      mastermanagementAASMin: config.get("mastermanagement.mastermanagementAASMin"),
      mastermanagementAASTarget: config.get("mastermanagement.mastermanagementAASTarget"),

      //タスク定義
      masterManagementCpu: config.get("mastermanagement.masterManagementCpu"),
      masterManagementMem: config.get("mastermanagement.masterManagementMem"),

      //コンテナ定義(Mastermanagement)
      mastermanagementEcrRepositoryArn: config.get("mastermanagement.mastermanagementEcrRepositoryArn"),
      mastermanagementEnvSpringProfilesActive: config.get("mastermanagement.mastermanagementEnvSpringProfilesActive"),
      // mastermanagemnetEnvDatasourceDriverclassname: config.get("mastermanagement.mastermanagemnetEnvDatasourceDriverclassname"), // チューニングする時のみ使用
      // mastermanagemnetEnvApiRetryMaxAttempts: config.get("mastermanagement.mastermanagemnetEnvApiRetryMaxAttempts"), // チューニングする時のみ使用v
      // mastermanagemnetEnvApiRetryMinBackoff: config.get("mastermanagement.mastermanagemnetEnvApiRetryMinBackoff"), // チューニングする時のみ使用
      mastermanagemnetEnvResilience4JTimeout: config.get("mastermanagement.mastermanagemnetEnvResilience4JTimeout"), // チューニングする時のみ使用
      // mastermanagementEnvJBA020201_JdbcBatchUpdateSize: config.get("mastermanagement.mastermanagementEnvJBA020201_JdbcBatchUpdateSize"), // チューニングする時のみ使用
      // mastermanagementEnvJBA020301_ChunkSize: config.get("mastermanagement.mastermanagementEnvJBA020301_ChunkSize"), // チューニングする時のみ使用
      // mastermanagementEnvSqsListenerConcurrency: config.get("mastermanagement.mastermanagementEnvSqsListenerConcurrency"), // チューニングする時のみ使用
      // mastermanagementEnvSqsNumberOfMessageStopRefetch: config.get("mastermanagement.mastermanagementEnvSqsNumberOfMessageStopRefetch"), // チューニングする時のみ使用
      mastermanagementEnvGcUploadIntervalSec: config.get("mastermanagement.mastermanagementEnvGcUploadIntervalSec"),
      mastermanagementEnvGcLogleve: config.get("mastermanagement.mastermanagementEnvGcLogleve"),
      mastermanagementEnvOtelResourceAttributes: config.get("mastermanagement.mastermanagementEnvOtelResourceAttributes"),

      // アラーム定義の閾値
      mastermanagementFargateServiceCPUUtilizationAlarmThreshold: config.get("mastermanagementAlarm.mastermanagementFargateServiceCPUUtilizationAlarmThreshold"),
      mastermanagementFargateServiceMemoryUtilizationAlarmThreshold: config.get("mastermanagementAlarm.mastermanagementFargateServiceMemoryUtilizationAlarmThreshold"),
      mastermanagementSQSApproximateNumberOfMessagesVisibleAlarmThreshold: config.get("mastermanagementAlarm.mastermanagementSQSApproximateNumberOfMessagesVisibleAlarmThreshold"),
    });

    /**
    * 標準アーキ1スケジュールバッチ関連リソースContructクラス
    */
    const ecsScheduleLaunch = new EcsScheduleLaunch(this, "EcsScheduleLaunch", {
      // 共通リソースパラメータ
      envName: config.get("common.envPrefix"), 
      vpc: nw.myVpc,
      appGCLogBucket: appS3Bucket.myAppGCLogBucket,
      vpcEndpointSg: nw.myVpcEndpointSg,
      mastermanagementSQS: ecsMastermanagement.myMastermanagementSQS,
      mastermanagementFargateCluster: ecsMastermanagement.myMastermanagementFargateCluster,
      myAdotEcrRepository: nw.myAdotEcrRepository,
      pullThryoughCachePolicyStatement: nw.pullThryoughCachePolicyStatement,      
      awsOTelTaskRolePolicyStatement: nw.awsOTelTaskRolePolicyStatement,
      alarmTopic: monitorCommon.alarmTopic,

      // ECSタスク定義
      schedulelaunchTaskCpu: config.get("schedulelaunch.schedulelaunchTaskCpu"),
      schedulelaunchTaskMemoryLimit: config.get("schedulelaunch.schedulelaunchTaskMemoryLimit"),

      // ECSコンテナ定義:Tourreservatisonコンテナ用パラメータ
      schedulelaunchEcrRepositoryArn: config.get("schedulelaunch.schedulelaunchEcrRepositoryArn"),
      schedulelaunchEnvSpringProfilesActive: config.get("schedulelaunch.schedulelaunchEnvSpringProfilesActive"),
      schedulelaunchEnvBatchScheduleTargetId: config.get("schedulelaunch.schedulelaunchEnvBatchScheduleTargetId"),
      schedulelaunchEnvOtelResourceAttributes: config.get("schedulelaunch.schedulelaunchEnvOtelResourceAttributes"),

      // EventBridge用の定義
      schedulelaunchScheduleRate: config.get("schedulelaunch.schedulelaunchScheduleRate"),

      // アラーム定義の閾値
      schedulelaunchRuleDeadLetterInvocationAlarmThreshold: config.get("schedulelaunchAlarm.schedulelaunchRuleDeadLetterInvocationAlarmThreshold"),
    });

    /**
    * 標準アーキ1Tour Reservation関連リソースContructクラス
    */
    const ecsTourreservation = new EcsTourreservation(this, "EcsTourreservation", {
      envName: config.get("common.envPrefix"), 
      vpc: nw.myVpc,
      appBucket: appS3Bucket.myAppBucket,
      appGCLogBucket: appS3Bucket.myAppGCLogBucket,
      serverAccessLogBucket: nw.myServerAccessLogBucket,
      vpcEndpointSg: nw.myVpcEndpointSg,
      mainAuroraSecret: mainaurora.myMainAuroraSecret,
      mastermanagementSQS: ecsMastermanagement.myMastermanagementSQS,
      internalAlb: ecsHotelmanagement.myInternalAlb,
      mainAuroraCluster: mainaurora.myMainAuroraCluster,
      myAdotEcrRepository: nw.myAdotEcrRepository,
      pullThryoughCachePolicyStatement: nw.pullThryoughCachePolicyStatement,      
      awsOTelTaskRolePolicyStatement: nw.awsOTelTaskRolePolicyStatement,
      alarmTopic: monitorCommon.alarmTopic,

      // ホワイトリストIPアドレス
      whitelistAddress: config.get("tourreservation.whitelistAddress"),

      // Redis用パラメータ
      redisReplicationGroupCacheNodeType: config.get("tourreservation.redisReplicationGroupCacheNodeType"),
      redisReplicationGroupCacheClustersNumber: config.get("tourreservation.redisReplicationGroupCacheClustersNumber"),

      // ECSサービス定義:Tourreservatison用パラメータ
      tourreservationDesiredCount: config.get("tourreservation.tourreservationDesiredCount"),
      tourreservationAASMin: config.get("tourreservation.tourreservationAASMin"),
      tourreservationAASMax: config.get("tourreservation.tourreservationAASMax"),
      tourreservationAASRequestsPerTarget: config.get("tourreservation.tourreservationAASRequestsPerTarget"),

      // ECSタスク定義
      tourreservationTaskCpu: config.get("tourreservation.tourreservationTaskCpu"),
      tourreservationTaskMemoryLimit: config.get("tourreservation.tourreservationTaskMemoryLimit"),

      // ECSコンテナ定義:Tourreservatisonコンテナ用パラメータ
      tourreservationEcrRepositoryArn: config.get("tourreservation.tourreservationEcrRepositoryArn"),
      tourreservationEnvSpringProfilesActive: config.get("tourreservation.tourreservationEnvSpringProfilesActive"),
      // tourreservationEnvSpringLifecycleTimeoutPerShutdownPhase: config.get("tourreservation.tourreservationEnvSpringLifecycleTimeoutPerShutdownPhase"), // チューニングする時のみ使用
      // tourreservationEnvDatasourceDriverclassname: config.get("tourreservation.tourreservationEnvDatasourceDriverclassname"), // チューニングする時のみ使用
      // tourreservationEnvRedisPort: config.get("tourreservation.tourreservationEnvRedisPort"), // チューニングする時のみ使用
      // tourreservationEnvApiRetryMaxAttempts: config.get("tourreservation.tourreservationEnvApiRetryMaxAttempts"), // チューニングする時のみ使用
      // tourreservationEnvApiRetryMinBackoff: config.get("tourreservation.tourreservationEnvApiRetryMinBackoff"), // チューニングする時のみ使用
      tourreservationEnvResilience4JTimeout: config.get("tourreservation.tourreservationEnvResilience4JTimeout"), // チューニングする時のみ使用
      tourreservationEnvGcUploadIntervalSec: config.get("tourreservation.tourreservationEnvGcUploadIntervalSec"),
      tourreservationEnvGcLogleve: config.get("tourreservation.tourreservationEnvGcLogleve"),
      tourreservationEnvOtelResourceAttributes: config.get("tourreservation.tourreservationEnvOtelResourceAttributes"),

      // アラーム定義の閾値
      publicAlbLogBucketAllRequestsAlarmThreshold: config.get("tourreservationAlarm.publicAlbLogBucketAllRequestsAlarmThreshold"), // 永野さんに実案件での実績確認中
      publicAlbLogBucket4xxErrorsAlarmThreshold: config.get("tourreservationAlarm.publicAlbLogBucket4xxErrorsAlarmThreshold"),
      publicAlbLogBucket5xxErrorsAlarmThreshold: config.get("tourreservationAlarm.publicAlbLogBucket5xxErrorsAlarmThreshold"),
      wafLogBucketAllRequestsAlarmThreshold: config.get("tourreservationAlarm.wafLogBucketAllRequestsAlarmThreshold"), // 永野さんに実案件での実績確認中
      wafLogBucket4xxErrorsAlarmThreshold: config.get("tourreservationAlarm.wafLogBucket4xxErrorsAlarmThreshold"),
      wafLogBucket5xxErrorsAlarmThreshold: config.get("tourreservationAlarm.wafLogBucket5xxErrorsAlarmThreshold"),
      tourreservationFargateServiceCPUUtilizationAlarmThreshold: config.get("tourreservationAlarm.tourreservationFargateServiceCPUUtilizationAlarmThreshold"),
      tourreservationFargateServiceMemoryUtilizationAlarmThreshold: config.get("tourreservationAlarm.tourreservationFargateServiceMemoryUtilizationAlarmThreshold"),
      publicAlbHTTPCodeELB4XXCountAlarmThreshold: config.get("tourreservationAlarm.publicAlbHTTPCodeELB4XXCountAlarmThreshold"),
      publicAlbHTTPCodeELB5XXCountAlarmThreshold: config.get("tourreservationAlarm.publicAlbHTTPCodeELB5XXCountAlarmThreshold"),
      publicAlbHTTPCodeELB500CountAlarmThreshold: config.get("tourreservationAlarm.publicAlbHTTPCodeELB500CountAlarmThreshold"),
      publicAlbHTTPCodeELB502CountAlarmThreshold: config.get("tourreservationAlarm.publicAlbHTTPCodeELB502CountAlarmThreshold"),
      publicAlbHTTPCodeELB503CountAlarmThreshold: config.get("tourreservationAlarm.publicAlbHTTPCodeELB503CountAlarmThreshold"),
      publicAlbHTTPCodeELB504CountAlarmThreshold: config.get("tourreservationAlarm.publicAlbHTTPCodeELB504CountAlarmThreshold"),
      publicAlbRejectedConnectionCountAlarmThreshold: config.get("tourreservationAlarm.publicAlbRejectedConnectionCountAlarmThreshold"),
      tourreservationTargetGroupHealthyHostCountAlarmThreshold: config.get("tourreservationAlarm.tourreservationTargetGroupHealthyHostCountAlarmThreshold"), // オートスケーリングの下限
      tourreservationTargetGroupHTTPCodeELB2XXCountAlarmThreshold: config.get("tourreservationAlarm.tourreservationTargetGroupHTTPCodeELB2XXCountAlarmThreshold"),
      tourreservationTargetGroupHTTPCodeELB3XXCountAlarmThreshold: config.get("tourreservationAlarm.tourreservationTargetGroupHTTPCodeELB3XXCountAlarmThreshold"),
      tourreservationTargetGroupHTTPCodeELB4XXCountAlarmThreshold: config.get("tourreservationAlarm.tourreservationTargetGroupHTTPCodeELB4XXCountAlarmThreshold"),
      tourreservationTargetGroupHTTPCodeELB5XXCountAlarmThreshold: config.get("tourreservationAlarm.tourreservationTargetGroupHTTPCodeELB5XXCountAlarmThreshold"),
      tourreservationTargetGroupTargetResponseTimeAlarmThreshold: config.get("tourreservationAlarm.tourreservationTargetGroupTargetResponseTimeAlarmThreshold"),
      redisNodeCPUUtilizationAlarmThreshold:  config.get("tourreservationAlarm.redisNodeCPUUtilizationAlarmThreshold"),
      redisNodeFreeableMemoryAlarmThreshold: config.get("tourreservationAlarm.redisNodeFreeableMemoryAlarmThreshold"),
      redisNodeSwapUsageAlarmThreshold: config.get("tourreservationAlarm.redisNodeSwapUsageAlarmThreshold"),
      redisNodeCurrConnectionsAlarmThreshold: config.get("tourreservationAlarm.redisNodeCurrConnectionsAlarmThreshold"),
      redisNodeDatabaseMemoryUsagePercentageAlarmThreshold: config.get("tourreservationAlarm.redisNodeDatabaseMemoryUsagePercentageAlarmThreshold"),
      redisNodeMemoryFragmentationRatioAlarmThreshold: config.get("tourreservationAlarm.redisNodeMemoryFragmentationRatioAlarmThreshold"), // 1を超えるとフラグメント発生しているが、標準アーキだとインスタンスタイプが小さいため2.5で設定する。
      redisNodeReplicationBytesAlarmThreshold: config.get("tourreservationAlarm.redisNodeReplicationBytesAlarmThreshold"),
      redisNodeReplicationLagAlarmThreshold: config.get("tourreservationAlarm.redisNodeReplicationLagAlarmThreshold"),
      redisNodeStringBasedCmdsLatencyAlarmThreshold: config.get("tourreservationAlarm.redisNodeStringBasedCmdsLatencyAlarmThreshold"),  
    });

    /**
    * 標準アーキ1Log Archive関連リソースContructクラス
    */
    const logarchive = new Logarchive(this, "Logarchive", {
      envName: config.get("common.envPrefix"),
      vpc: nw.myVpc,
      serverAccessLogBucket: nw.myServerAccessLogBucket,
      alarmTopic: monitorCommon.alarmTopic,
      logarchiveMemorySize: config.get("logarchive.logarchiveMemorySize"),

      // アラーム定義の閾値
      LogarchiveBucket4xxErrorsAlarmThreshold: config.get("logarchiveAlarm.LogarchiveBucket4xxErrorsAlarmThreshold"),
      LogarchiveBucket5xxErrorsAlarmThreshold: config.get("logarchiveAlarm.LogarchiveBucket5xxErrorsAlarmThreshold"),
      LogarchiveBucketAllRequestsAlarmThreshold: config.get("logarchiveAlarm.LogarchiveBucketAllRequestsAlarmThreshold"),
      LogarchiveFunctionDurationAlarmThreshold: config.get("logarchiveAlarm.LogarchiveFunctionDurationAlarmThreshold"),
    });

    /**
    * 標準アーキ1初期データ自動投入関連リソースContructクラス
    */
    const dataInput = new DataInput(this, "DataInput", {
      envName: config.get("common.envPrefix"),
      vpc: nw.myVpc,
      vpcEndpointSg: nw.myVpcEndpointSg,
      mainAuroraCluster: mainaurora.myMainAuroraCluster,
      mainAuroraSecret: mainaurora.myMainAuroraSecret,
      myHotelDynamoTable: ecsHotelmanagement.myHotelDynamoTable,
      alarmTopic: monitorCommon.alarmTopic,
    });

  }
}
kirin-ri commented 2 days ago

gc-std-archi1-stack

kirin-ri commented 2 days ago
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as ssm from "aws-cdk-lib/aws-ssm";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";

export interface GcStdArchi1SsmProps {
  instanceId: string;
}

export class GcStdArchi1Ssm extends Construct {
  constructor(scope: Construct, id: string, props: GcStdArchi1SsmProps) {
    super(scope, id);

    // CPU 使用率メトリクスの作成
    const cpuMetric = new cloudwatch.Metric({
      namespace: "AWS/EC2",
      metricName: "CPUUtilization",
      dimensionsMap: { InstanceId: props.instanceId },
      statistic: "Average",
    });

    // SSM パラメータにメトリクス情報を保存
    new ssm.StringParameter(this, "EC2MetricNamespace", {
      parameterName: "/myapp/ec2/namespace",
      stringValue: cpuMetric.namespace,
    });

    new ssm.StringParameter(this, "EC2MetricName", {
      parameterName: "/myapp/ec2/metric_name",
      stringValue: cpuMetric.metricName,
    });

    new ssm.StringParameter(this, "EC2MetricDimensions", {
      parameterName: "/myapp/ec2/dimensions",
      stringValue: JSON.stringify(cpuMetric.dimensionsMap),
    });
  }
}
kirin-ri commented 2 days ago

lib/resources/GcStdArchi1Ssm.ts

kirin-ri commented 2 days ago
    new GcStdArchi1Ssm(this, "GcStdArchi1Ssm", {
      instanceId: "i-0123456789", // 必要に応じて実際のインスタンス ID に置き換え
    });
kirin-ri commented 2 days ago
btrikra:~/environment/FBS-viz-trial/gc-std-archi1-app (master) $ cdk deploy GcStdArchi1Stack                                                                                                                            
npm warn exec The following package was not found and will be installed: ts-node@10.9.2
/home/ec2-user/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:859
    return new TSError(diagnosticText, diagnosticCodes, diagnostics);
           ^
TSError: ⨯ Unable to compile TypeScript:
bin/gc-std-archi1-app.ts:6:25 - error TS2307: Cannot find module 'config' or its corresponding type declarations.

6 import * as config from "config"; // node-configモジュールのインポート
                          ~~~~~~~~
bin/gc-std-archi1-app.ts:12:17 - error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.

12 const envName = process.env.NODE_ENV;
                   ~~~~~~~
bin/gc-std-archi1-app.ts:18:3 - error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.

18   process.exit(1);
     ~~~~~~~

    at createTSError (/home/ec2-user/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:859:12)
    at reportTSError (/home/ec2-user/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:863:19)
    at getOutput (/home/ec2-user/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:1077:36)
    at Object.compile (/home/ec2-user/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:1433:41)
    at Module.m._compile (/home/ec2-user/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:1617:30)
    at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
    at Object.require.extensions.<computed> [as .ts] (/home/ec2-user/.npm/_npx/1bf7c3c15bf47d04/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:1203:32)
    at Function.Module._load (node:internal/modules/cjs/loader:1019:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:128:12) {
  diagnosticCodes: [ 2307, 2580, 2580 ]
}
****************************************************
*** Newer version of CDK is available [2.166.0]  ***
*** Upgrade recommended (npm install -g aws-cdk) ***
****************************************************

Subprocess exited with error 1
kirin-ri commented 2 days ago
btrikra:~/environment/FBS-viz-trial/gc-std-archi1-app (master) $ cdk deploy GcStdArchi1Stack
/home/ec2-user/environment/FBS-viz-trial/gc-std-archi1-app/node_modules/ts-node/src/index.ts:859
    return new TSError(diagnosticText, diagnosticCodes, diagnostics);
           ^
TSError: ⨯ Unable to compile TypeScript:
lib/resources/gc-std-archi1-dashboard.ts:35:45 - error TS2551: Property 'dimensionsMap' does not exist on type 'Metric'. Did you mean 'dimensions'?

35       stringValue: JSON.stringify(cpuMetric.dimensionsMap),
                                               ~~~~~~~~~~~~~

  node_modules/aws-cdk-lib/aws-cloudwatch/lib/metric.d.ts:227:14
    227     readonly dimensions?: DimensionHash;
                     ~~~~~~~~~~
    'dimensions' is declared here.

    at createTSError (/home/ec2-user/environment/FBS-viz-trial/gc-std-archi1-app/node_modules/ts-node/src/index.ts:859:12)
    at reportTSError (/home/ec2-user/environment/FBS-viz-trial/gc-std-archi1-app/node_modules/ts-node/src/index.ts:863:19)
    at getOutput (/home/ec2-user/environment/FBS-viz-trial/gc-std-archi1-app/node_modules/ts-node/src/index.ts:1077:36)
    at Object.compile (/home/ec2-user/environment/FBS-viz-trial/gc-std-archi1-app/node_modules/ts-node/src/index.ts:1433:41)
    at Module.m._compile (/home/ec2-user/environment/FBS-viz-trial/gc-std-archi1-app/node_modules/ts-node/src/index.ts:1617:30)
    at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
    at Object.require.extensions.<computed> [as .ts] (/home/ec2-user/environment/FBS-viz-trial/gc-std-archi1-app/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:1203:32)
    at Function.Module._load (node:internal/modules/cjs/loader:1019:12)
    at Module.require (node:internal/modules/cjs/loader:1231:19) {
  diagnosticCodes: [ 2551 ]
}

Subprocess exited with error 1
kirin-ri commented 2 days ago

btrikra:~/environment/FBS-viz-trial/gc-std-archi1-app (master) $ cdk deploy GcStdArchi1Stack

This CDK CLI is not compatible with the CDK library used by your application. Please upgrade the CLI to the latest version. (Cloud assembly schema version mismatch: Maximum schema version supported is 32.0.0, but found 36.0.0) btrikra:~/environment/FBS-viz-trial/gc-std-archi1-app (master) $ cdk --version 2.84.0 (build f7c792f)

kirin-ri commented 2 days ago

btrikra:~/environment/FBS-viz-trial/gc-std-archi1-app (master) $ npm install -g aws-cdk npm error code EACCES npm error syscall rename npm error path /usr/local/lib/node_modules/aws-cdk npm error dest /usr/local/lib/node_modules/.aws-cdk-kzzr8ey1 npm error errno -13 npm error [Error: EACCES: permission denied, rename '/usr/local/lib/node_modules/aws-cdk' -> '/usr/local/lib/node_modules/.aws-cdk-kzzr8ey1'] { npm error errno: -13, npm error code: 'EACCES', npm error syscall: 'rename', npm error path: '/usr/local/lib/node_modules/aws-cdk', npm error dest: '/usr/local/lib/node_modules/.aws-cdk-kzzr8ey1' npm error } npm error npm error The operation was rejected by your operating system. npm error It is likely you do not have the permissions to access this file as the current user npm error npm error If you believe this might be a permissions issue, please double-check the npm error permissions of the file and its containing directories, or try running npm error the command again as root/Administrator. npm error A complete log of this run can be found in: /home/ec2-user/.npm/_logs/2024-11-11T03_10_18_853Z-debug-0.log

kirin-ri commented 2 days ago
// resources/gc-std-archi1-dashboard.ts
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import { Construct } from 'constructs';

export interface DashboardProps {
  dashboardName: string; // ダッシュボード名を外部から指定可能にする
}

export class GcStdArchi1Dashboard extends Construct {
  public readonly dashboard: cloudwatch.Dashboard;

  constructor(scope: Construct, id: string, props: DashboardProps) {
    super(scope, id);

    // ダッシュボードの作成
    this.dashboard = new cloudwatch.Dashboard(this, 'Dashboard', {
      dashboardName: props.dashboardName
    });

    // CPU メトリクスの例
    const cpuMetric = new cloudwatch.Metric({
      namespace: 'AWS/EC2',
      metricName: 'CPUUtilization',
      dimensionsMap: { InstanceId: 'i-0123456789' }, // 適切なインスタンスIDに置き換え
      statistic: 'Average',
    });

    // グラフウィジェットの追加
    this.dashboard.addWidgets(
      new cloudwatch.GraphWidget({
        title: 'EC2 CPU Utilization',
        left: [cpuMetric],
      })
    );
  }
}
kirin-ri commented 2 days ago
// GcStdArchi1Stack ファイル
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { GcStdArchi1Dashboard } from "./resources/gc-std-archi1-dashboard"; // 作成したダッシュボードをインポート

export class GcStdArchi1Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // CloudWatch ダッシュボードのインスタンスを作成し、スタックに追加
    const dashboard = new GcStdArchi1Dashboard(this, 'MyAppDashboard', {
      dashboardName: 'GcStdArchi1Dashboard'
    });

    // 既存のリソース作成コードを保持
    // 例: MonitorCommon、Nw、AppS3Bucket などのインスタンス化コード
  }
}
kirin-ri commented 2 days ago
import * as cdk from "aws-cdk-lib";
import * as s3 from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";

import * as nag from "cdk-nag"; // cdk-nagモジュールのインポート
import * as path from "path";

/**
 * Dashboardクラスの初期化に必要なパラメータ定義
 */
export interface DashboardProps {
  /**  
   * 環境名を小文字の英字で指定する。  
   * リソースの物理名やNameタグに利用される。
   */
  envName: string;
}

/**
 * ダッシュボードを展開するConstruct。
 * 
 * @remarks 
 * 以下のリソースを展開する。   
 *   - 表示メトリクス用S3バケット
 *   - 表示メトリクス用Lambda
 *   - 各種ウィジェット
 *   - ダッシュボード
 */
export class Dashboard extends Construct {

  /**
   * ダッシュボードを展開する。  
   * 初期化時に引数で与える{@link DashboardProps}の値に応じて環境パラメータが設定される。
   * 
   * @param props - 
   * AppS3Bucketクラスインスタンス初期化用のプロパティを指定する。詳細は{@link DashboardProps}を参照すること。
   */
  constructor(scope: Construct, id: string, props: DashboardProps) {
    super(scope, id);

    // 表示メトリクス用S3バケット作成
    const myVizBucket = new s3.Bucket(this, "VizBucket", {
      bucketName: `viz-${props.envName}-vizbucket`,
      enforceSSL: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });
    cdk.Tags.of(myVizBucket).add(
      "Name",
      "viz-" + props.envName + "-vizbucket"
    );
    myVizBucket.addMetric({id: "EntireBucket"});

    nag.NagSuppressions.addResourceSuppressions(
      myVizBucket,
      [
        { id: "AwsSolutions-S1", reason: "Because of test",},
      ],
      true
    );

    // 表示メトリクス用Lambda作成
    const myTestFunction = new lambda.Function(this, "TestFunction", {
      functionName: `viz-${props.envName}-test`,
      code: lambda.Code.fromAsset(path.join(__dirname, "../../lambda/test/")),
      handler: "main.lambda_handler",
      runtime: lambda.Runtime.PYTHON_3_8,
    });
    cdk.Tags.of(myTestFunction).add(
      "Name",
      "viz-" + props.envName + "-testFnction"
    );

    nag.NagSuppressions.addResourceSuppressions(
      myTestFunction,
      [
        { id: "AwsSolutions-IAM4", reason: "Use AWS Managed Policies.", appliesTo: ["Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]},
        { id: "AwsSolutions-L1", reason: "Because of test",},
      ],
      true
    );

    // NumberOfObjectsメトリクスのウィジェット作成
    const allRequestsMetric = new cloudwatch.Metric({
      namespace: 'AWS/S3',
      metricName: 'AllRequests',
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket"
      },
      statistic: 'Average',
      period: cdk.Duration.minutes(5),
    });

    const allRequestsWidget = new cloudwatch.GraphWidget({
      title: 'All Requests in S3 Bucket',
      left: [allRequestsMetric],
    });

    // CloudWatchダッシュボードの作成
    new cloudwatch.Dashboard(this, 'MyDashboard', {
      dashboardName: 'MyDashboard',
      widgets: [
        [allRequestsWidget]
      ],
    });
  };
}
kirin-ri commented 2 days ago
・折れ線と棒グラフを同じウィジェットに表示する方法
・leftとrightの違い 
・ウィジェットの配置(addWidgetの仕様?)
kirin-ri commented 2 days ago

AllRequests: バケットへのすべてのリクエスト数。S3 の利用頻度を把握するために使用されます。 GetRequests: GET リクエストの数。オブジェクトの読み取り操作の頻度を把握するために使用します。 PutRequests: PUT リクエストの数。オブジェクトの追加や更新頻度を把握するために使用されます。 4xxErrors: クライアントエラー(4xx)のリクエスト数。ユーザーのアクセスエラーや設定ミスなど、アクセスに関する問題がないか監視します。 5xxErrors: サーバーエラー(5xx)のリクエスト数。S3 サーバー側でのエラーが発生していないか監視するために使用します。 BytesDownloaded: バケットからダウンロードされたデータ量。データ転送量を把握し、トラフィックやコストの管理に役立ちます。 BytesUploaded: バケットにアップロードされたデータ量。アップロードのトラフィック量を確認するために使用します。

kirin-ri commented 2 days ago
// CloudWatch メトリクスの追加
const allRequestsMetric = new cloudwatch.Metric({
  namespace: 'AWS/S3',
  metricName: 'AllRequests',
  dimensionsMap: {
    BucketName: myVizBucket.bucketName,
    FilterId: "EntireBucket"
  },
  statistic: 'Sum',
  period: cdk.Duration.minutes(5),
});

const getRequestMetric = new cloudwatch.Metric({
  namespace: 'AWS/S3',
  metricName: 'GetRequests',
  dimensionsMap: {
    BucketName: myVizBucket.bucketName,
    FilterId: "EntireBucket"
  },
  statistic: 'Sum',
  period: cdk.Duration.minutes(5),
});

const putRequestMetric = new cloudwatch.Metric({
  namespace: 'AWS/S3',
  metricName: 'PutRequests',
  dimensionsMap: {
    BucketName: myVizBucket.bucketName,
    FilterId: "EntireBucket"
  },
  statistic: 'Sum',
  period: cdk.Duration.minutes(5),
});

const clientErrorMetric = new cloudwatch.Metric({
  namespace: 'AWS/S3',
  metricName: '4xxErrors',
  dimensionsMap: {
    BucketName: myVizBucket.bucketName,
    FilterId: "EntireBucket"
  },
  statistic: 'Sum',
  period: cdk.Duration.minutes(5),
});

const serverErrorMetric = new cloudwatch.Metric({
  namespace: 'AWS/S3',
  metricName: '5xxErrors',
  dimensionsMap: {
    BucketName: myVizBucket.bucketName,
    FilterId: "EntireBucket"
  },
  statistic: 'Sum',
  period: cdk.Duration.minutes(5),
});

const bytesDownloadedMetric = new cloudwatch.Metric({
  namespace: 'AWS/S3',
  metricName: 'BytesDownloaded',
  dimensionsMap: {
    BucketName: myVizBucket.bucketName,
    FilterId: "EntireBucket"
  },
  statistic: 'Sum',
  period: cdk.Duration.minutes(5),
});

const bytesUploadedMetric = new cloudwatch.Metric({
  namespace: 'AWS/S3',
  metricName: 'BytesUploaded',
  dimensionsMap: {
    BucketName: myVizBucket.bucketName,
    FilterId: "EntireBucket"
  },
  statistic: 'Sum',
  period: cdk.Duration.minutes(5),
});

// 折れ線と棒グラフを同じウィジェットに表示
const requestsAndErrorsWidget = new cloudwatch.GraphWidget({
  title: 'S3 Requests and Errors',
  left: [allRequestsMetric, getRequestMetric, putRequestMetric],
  right: [clientErrorMetric, serverErrorMetric],  // エラーを右Y軸に表示
  view: cloudwatch.GraphWidgetView.BAR, // 棒グラフを指定
});

const bytesWidget = new cloudwatch.GraphWidget({
  title: 'S3 Data Transfer',
  left: [bytesDownloadedMetric],
  right: [bytesUploadedMetric],
  view: cloudwatch.GraphWidgetView.TIME_SERIES, // 折れ線グラフを指定
});

// CloudWatch ダッシュボードにウィジェットを配置
new cloudwatch.Dashboard(this, 'MyDashboard', {
  dashboardName: 'MyDashboard',
  widgets: [
    [requestsAndErrorsWidget],
    [bytesWidget]
  ],
});
kirin-ri commented 2 days ago
import * as cdk from "aws-cdk-lib";
import * as s3 from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
import * as nag from "cdk-nag"; // cdk-nagモジュールのインポート
import * as path from "path";

export interface DashboardProps {
  envName: string;
}

export class Dashboard extends Construct {
  constructor(scope: Construct, id: string, props: DashboardProps) {
    super(scope, id);

    // 表示メトリクス用S3バケット作成
    const myVizBucket = new s3.Bucket(this, "VizBucket", {
      bucketName: `viz-${props.envName}-vizbucket`,
      enforceSSL: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });
    cdk.Tags.of(myVizBucket).add("Name", `viz-${props.envName}-vizbucket`);
    myVizBucket.addMetric({ id: "EntireBucket" });

    nag.NagSuppressions.addResourceSuppressions(
      myVizBucket,
      [{ id: "AwsSolutions-S1", reason: "Because of test" }],
      true
    );

    // 表示メトリクス用Lambda作成
    const myTestFunction = new lambda.Function(this, "TestFunction", {
      functionName: `viz-${props.envName}-test`,
      code: lambda.Code.fromAsset(path.join(__dirname, "../../lambda/test/")),
      handler: "main.lambda_handler",
      runtime: lambda.Runtime.PYTHON_3_8,
    });
    cdk.Tags.of(myTestFunction).add("Name", `viz-${props.envName}-testFnction`);

    nag.NagSuppressions.addResourceSuppressions(
      myTestFunction,
      [
        {
          id: "AwsSolutions-IAM4",
          reason: "Use AWS Managed Policies.",
          appliesTo: [
            "Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
          ],
        },
        { id: "AwsSolutions-L1", reason: "Because of test" },
      ],
      true
    );

    // S3の基本メトリクスの設定
    const allRequestsMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "AllRequests",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const getRequestMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "GetRequests",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const putRequestMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "PutRequests",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const clientErrorMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "4xxErrors",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const serverErrorMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "5xxErrors",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const bytesDownloadedMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "BytesDownloaded",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const bytesUploadedMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "BytesUploaded",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    // リクエスト数とエラー数を同じウィジェットに表示
    const requestsAndErrorsWidget = new cloudwatch.GraphWidget({
      title: "S3 Requests and Errors",
      left: [allRequestsMetric, getRequestMetric, putRequestMetric], // リクエスト数を左Y軸に表示
      right: [clientErrorMetric, serverErrorMetric], // エラー数を右Y軸に表示
      view: cloudwatch.GraphWidgetView.BAR, // 棒グラフで表示
    });

    // データ転送量(アップロードとダウンロード)を同じウィジェットに表示
    const bytesWidget = new cloudwatch.GraphWidget({
      title: "S3 Data Transfer",
      left: [bytesDownloadedMetric], // ダウンロードを左Y軸に表示
      right: [bytesUploadedMetric], // アップロードを右Y軸に表示
      view: cloudwatch.GraphWidgetView.TIME_SERIES, // 折れ線グラフで表示
    });

    // CloudWatch ダッシュボードの作成とウィジェットの追加
    new cloudwatch.Dashboard(this, "MyDashboard", {
      dashboardName: "MyDashboard",
      widgets: [
        [requestsAndErrorsWidget], // 1行目のウィジェット
        [bytesWidget], // 2行目のウィジェット
      ],
    });
  }
}
kirin-ri commented 1 day ago
    // 折れ線と棒グラフを1つのウィジェットにまとめて表示
    const combinedWidget = new cloudwatch.GraphWidget({
      title: "S3 Metrics: Requests, Errors, and Data Transfer",
      left: [allRequestsMetric, getRequestMetric, putRequestMetric],  // 左Y軸に棒グラフ形式
      right: [clientErrorMetric, serverErrorMetric, bytesDownloadedMetric, bytesUploadedMetric],  // 右Y軸に折れ線グラフ形式
      leftYAxis: { label: "Requests (Bar)" },   // 左Y軸ラベル
      rightYAxis: { label: "Errors & Transfer (Line)" }, // 右Y軸ラベル
      view: cloudwatch.GraphWidgetView.BAR,  // デフォルトの表示形式をBARに設定
    });

    // CloudWatch ダッシュボードにウィジェットを追加
    new cloudwatch.Dashboard(this, "MyDashboard", {
      dashboardName: "MyDashboard",
      widgets: [
        [combinedWidget],
      ],
    });
  }
}
kirin-ri commented 1 day ago
    // 左側に表示する棒グラフウィジェット
    const leftBarWidget = new cloudwatch.GraphWidget({
      title: "S3 Requests (Bar)",
      left: [allRequestsMetric, getRequestMetric],  // リクエスト系メトリクス
      view: cloudwatch.GraphWidgetView.BAR,
    });

    // 右側に表示する折れ線グラフウィジェット
    const rightLineWidget = new cloudwatch.GraphWidget({
      title: "S3 Errors and Data Transfer (Line)",
      right: [clientErrorMetric, serverErrorMetric],  // エラー系メトリクス
      view: cloudwatch.GraphWidgetView.TIME_SERIES,
    });

    // CloudWatch ダッシュボードの作成とウィジェットの追加
    new cloudwatch.Dashboard(this, "MyDashboard", {
      dashboardName: "MyDashboard",
      widgets: [
        [leftBarWidget],
        [rightLineWidget],
      ],
    });
  }
}
kirin-ri commented 1 day ago

現在のCDKのGraphWidgetでは、1つのウィジェットで左側を棒グラフ、右側を折れ線グラフに設定する方法は提供されていません。CDKでのGraphWidgetの表示形式はウィジェット全体に対して適用されるため、左右で異なる表示形式(左が棒グラフ、右が折れ線グラフ)を設定することができないのです。

kirin-ri commented 1 day ago

CloudWatchのウィジェットにおいて、左右のY軸に異なるメトリクスを表示するための設定です

const widget = new cloudwatch.GraphWidget({ title: "S3 Requests and Errors", left: [allRequestsMetric, getRequestMetric], // 左Y軸にリクエスト数を表示 right: [clientErrorMetric, serverErrorMetric], // 右Y軸にエラー数を表示 });

kirin-ri commented 20 hours ago
プロパティ

end
height
left
leftAnnotations
leftYAxis
legendPosition
liveData
period
region
right
rightAnnotations
rightYAxis
setPeriodToTimeRange
stacked
start
statistic
title
verticalAnnotations
view
width

メソッド

addLeftMetric(metric)
addRightMetric(metric)
position(x, y)
toJson()
kirin-ri commented 20 hours ago

用途 影響箇所 使用例 必須 データ型 デフォルト 備考

kirin-ri commented 18 hours ago
GraphWidgetのプロパティ
end

用途: グラフの終了時間を指定します。この設定により、データが表示される時間範囲の終了時刻を制限できます。
影響箇所: グラフの表示範囲
使用例: end: '2023-12-31T23:59:59Z'
必須: いいえ
データ型: string
デフォルト: undefined
備考: startプロパティと組み合わせることで、特定の期間のデータを表示可能です。
height

用途: グラフの縦方向のサイズをピクセル単位で指定します。
影響箇所: グラフの表示サイズ
使用例: height: 300
必須: いいえ
データ型: number
デフォルト: 6
備考: 値が大きいほどグラフが高く表示されます。
left

用途: グラフの左Y軸に表示するメトリクス(指標)を指定します。
影響箇所: 左側Y軸
使用例: left: [metric1, metric2]
必須: いいえ
データ型: IMetric[]
デフォルト: []
備考: addLeftMetricメソッドで動的に追加できます。
leftAnnotations

用途: 左Y軸に表示する水平線(注釈)を設定します。
影響箇所: 左Y軸の注釈
使用例: { value: 70, color: 'red' }
必須: いいえ
データ型: HorizontalAnnotation[]
デフォルト: []
備考: 視覚的な目標値や基準値を示すのに便利です。
leftYAxis

用途: 左Y軸のスケールや表示設定を指定します。
影響箇所: 左Y軸
使用例: { min: 0, max: 100 }
必須: いいえ
データ型: YaxisProps
デフォルト: undefined
備考: Y軸の範囲を固定して表示したい場合に使用します。
legendPosition

用途: 凡例(ラベル)の表示位置を指定します。
影響箇所: 凡例の位置
使用例: legendPosition: 'bottom'
必須: いいえ
データ型: LegendPosition
デフォルト: 'right'
備考: 'bottom'や'right'などが利用可能です。
liveData

用途: グラフをリアルタイムデータに更新するかどうかを設定します。
影響箇所: リアルタイム更新
使用例: liveData: true
必須: いいえ
データ型: boolean
デフォルト: false
備考: trueにすると、最新のデータが自動的に表示されます。
period

用途: データ取得の間隔を秒単位で設定します。
影響箇所: データの間隔
使用例: period: 300(5分間隔)
必須: いいえ
データ型: Duration
デフォルト: undefined
備考: より短い期間で更新する場合に便利です。
region

用途: メトリクスを取得するAWSリージョンを指定します。
影響箇所: データの取得元リージョン
使用例: region: 'us-west-2'
必須: いいえ
データ型: string
デフォルト: undefined
備考: デフォルトリージョン以外のリージョンのメトリクスを使用したい場合に指定します。
right

用途: グラフの右Y軸に表示するメトリクスを指定します。
影響箇所: 右側Y軸
使用例: right: [metric3]
必須: いいえ
データ型: IMetric[]
デフォルト: []
備考: addRightMetricメソッドで動的に追加可能です。
rightAnnotations

用途: 右Y軸に注釈を追加します。
影響箇所: 右Y軸の注釈
使用例: { value: 80, color: 'blue' }
必須: いいえ
データ型: HorizontalAnnotation[]
デフォルト: []
備考: 視覚的な基準値を示す場合に使用されます。
rightYAxis

用途: 右Y軸のスケールや設定を調整します。
影響箇所: 右Y軸
使用例: { min: 0, max: 200 }
必須: いいえ
データ型: YaxisProps
デフォルト: undefined
備考: メトリクスが多い場合の範囲設定に便利です。
setPeriodToTimeRange

用途: 自動で期間を指定した時間範囲に設定します。
影響箇所: データの表示範囲
使用例: setPeriodToTimeRange: true
必須: いいえ
データ型: boolean
デフォルト: false
備考: 時間範囲に応じて動的に期間が変更されます。
stacked

用途: グラフを積み上げ表示するか設定します。
影響箇所: グラフの表示形式
使用例: stacked: true
必須: いいえ
データ型: boolean
デフォルト: false
備考: 積み上げグラフで複数メトリクスの合計値を視覚化可能。
start

用途: グラフの開始時間を指定します。
影響箇所: グラフの表示範囲
使用例: start: '2023-01-01T00:00:00Z'
必須: いいえ
データ型: string
デフォルト: undefined
備考: endと組み合わせて特定の期間を表示します。
statistic

用途: メトリクスの統計タイプを指定します。
影響箇所: データの統計表示
使用例: statistic: 'Average'
必須: いいえ
データ型: string
デフォルト: 'Average'
備考: 'Sum'、'Maximum'なども設定可能です。
title

用途: グラフのタイトルを設定します。
影響箇所: グラフの見出し
使用例: title: 'Monthly Sales Metrics'
必須: いいえ
データ型: string
デフォルト: ''
備考: グラフ上部に表示されます。
verticalAnnotations

用途: グラフ内に縦方向の注釈を追加します。
影響箇所: グラフ内の注釈
使用例: { value: '2023-06-15', label: 'Mid-year' }
必須: いいえ
データ型: VerticalAnnotation[]
デフォルト: []
備考: 特定のイベント日を示す場合に便利です。
view

用途: グラフの表示形式を指定します。
影響箇所: グラフの表示方法
使用例: view: 'timeSeries'
必須: いいえ
データ型: string
デフォルト: 'timeSeries'
備考: 'singleValue'も選択可能です。
width

用途: グラフの横幅をピクセル単位で指定します。
影響箇所: グラフの表示サイズ
使用例: width: 400
必須: いいえ
データ型: number
デフォルト: 6
備考: 値が大きいほどグラフが横に広く表示されます。
GraphWidgetのメソッド
addLeftMetric(metric)

用途: 左Y軸に表示するメトリクスを追加します。
影響箇所: 左側Y軸
使用例: widget.addLeftMetric(metric1)
必須: いいえ
データ型: IMetric
デフォルト: なし
備考: メトリクスを複数追加して視覚的に比較可能です。
addRightMetric(metric)

用途: 右Y軸に表示するメトリクスを追加します。
影響箇所: 右側Y軸
使用例: widget.addRightMetric(metric3)
必須: いいえ
データ型: IMetric
デフォルト: なし
備考: 左右のメトリクスで異なるスケールのデータを表示可能です。
position(x, y)

用途: ダッシュボード上でのグラフの位置を指定します。
影響箇所: ダッシュボードの配置
使用例: widget.position(2, 3)
必須: いいえ
データ型: number, number
デフォルト: なし
備考: 座標指定で柔軟に配置を調整可能です。
toJson()

用途: グラフをJSON形式に変換して出力します。
影響箇所: データ出力
使用例: widget.toJson()
必須: いいえ
データ型: string
デフォルト: なし
備考: ダッシュボードの設定をJSONでエクスポート可能です。
kirin-ri commented 17 hours ago
import * as cdk from 'aws-cdk-lib';
import { Duration } from 'aws-cdk-lib';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import { Construct } from 'constructs';

export class MyDashboardStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // S3 Requests and Errors Widget
    const requestsAndErrorsWidget = new cloudwatch.GraphWidget({
      title: "S3 Requests and Errors",
      left: [allRequestsMetric, getRequestMetric, putRequestMetric], // リクエスト数を左Y軸に表示
      right: [clientErrorMetric, serverErrorMetric], // エラー数を右Y軸に表示
      view: cloudwatch.GraphWidgetView.BAR, // 棒グラフで表示
      height: 8, // ウィジェットの高さ
      width: 12, // ウィジェットの幅
      start: '2023-01-01T00:00:00Z', // 開始時間
      end: '2023-12-31T23:59:59Z', // 終了時間
      legendPosition: cloudwatch.LegendPosition.BOTTOM, // 凡例の位置
      liveData: true, // リアルタイムデータ
      setPeriodToTimeRange: true, // 時間範囲に基づいて期間自動設定
      stacked: true, // 積み上げグラフ表示
      leftYAxis: {
        min: 0,
        max: 1000,
      },
      rightYAxis: {
        min: 0,
        max: 500,
      },
      leftAnnotations: [
        { value: 800, color: 'red', label: 'High Request Volume' }
      ],
      rightAnnotations: [
        { value: 100, color: 'blue', label: 'Error Threshold' }
      ],
      verticalAnnotations: [
        { value: '2023-06-15T00:00:00Z', label: 'Mid-year' }
      ],
      period: Duration.minutes(5), // データ取得の間隔を指定
      region: 'us-west-2', // リージョン
    });

    // データ転送量(アップロードとダウンロード)ウィジェット
    const bytesWidget = new cloudwatch.GraphWidget({
      title: "S3 Data Transfer",
      left: [bytesDownloadedMetric], // ダウンロードを左Y軸に表示
      right: [bytesUploadedMetric], // アップロードを右Y軸に表示
      view: cloudwatch.GraphWidgetView.TIME_SERIES, // 折れ線グラフで表示
      height: 6,
      width: 6,
      start: '2023-01-01T00:00:00Z',
      end: '2023-12-31T23:59:59Z',
      legendPosition: cloudwatch.LegendPosition.RIGHT,
      liveData: false,
      setPeriodToTimeRange: false,
      stacked: false,
      leftYAxis: {
        min: 0,
        max: 1000,
      },
      rightYAxis: {
        min: 0,
        max: 2000,
      },
      leftAnnotations: [
        { value: 500, color: 'green', label: 'Download Target' }
      ],
      rightAnnotations: [
        { value: 1500, color: 'orange', label: 'Upload Warning' }
      ],
      verticalAnnotations: [
        { value: '2023-09-01T00:00:00Z', label: 'Peak Season Start' }
      ],
      period: Duration.minutes(10),
      region: 'us-east-1',
    });

    // addLeftMetric と addRightMetric メソッドの使用例
    requestsAndErrorsWidget.addLeftMetric(additionalRequestMetric);
    requestsAndErrorsWidget.addRightMetric(additionalErrorMetric);

    // CloudWatch ダッシュボードの作成とウィジェットの追加
    const dashboard = new cloudwatch.Dashboard(this, "MyDashboard", {
      dashboardName: "MyDashboard",
      widgets: [
        [requestsAndErrorsWidget.position(0, 0)], // 1行目のウィジェット
        [bytesWidget.position(1, 0)], // 2行目のウィジェット
      ],
    });

    // JSON 表示
    console.log(requestsAndErrorsWidget.toJson());
    console.log(bytesWidget.toJson());
  }
}
kirin-ri commented 16 hours ago
Dev-VizStack: deploying... [1/1]
Dev-VizStack: creating CloudFormation changeset...
8:02:40 AM | UPDATE_FAILED        | AWS::CloudWatch::Dashboard  | DashboardMyDashboardDB214A95
Resource handler returned message: "The dashboard body is invalid, there are 2 validation errors:
[
{
"dataPath": "/widgets/0/properties",
"message": "Should match exactly one schema in oneOf"
},
{
"dataPath": "/widgets/1/properties",
"message": "Should match exactly one schema in oneOf"
}
] (Service: CloudWatch, Status Code: 400, Request ID: c776ca3b-07bc-452b-ab3e-3d3e13e25916)" (RequestToken: bfafd4cd-6599-329f-d9d5-b3a8564cc635, HandlerErrorCode: GeneralServiceException)

❌  Dev-VizStack failed: The stack named Dev-VizStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Resource handler returned message: "The dashboard body is invalid, there are 2 validation errors:
[
  {
    "dataPath": "/widgets/0/properties",
    "message": "Should match exactly one schema in oneOf"
  },
  {
    "dataPath": "/widgets/1/properties",
    "message": "Should match exactly one schema in oneOf"
  }
] (Service: CloudWatch, Status Code: 400, Request ID: c776ca3b-07bc-452b-ab3e-3d3e13e25916)" (RequestToken: bfafd4cd-6599-329f-d9d5-b3a8564cc635, HandlerErrorCode: GeneralServiceException)
kirin-ri commented 16 hours ago
import * as cdk from "aws-cdk-lib";
import * as s3 from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";

import * as nag from "cdk-nag"; // cdk-nagモジュールのインポート
import * as path from "path";
import { Duration } from "aws-cdk-lib";

/**
 * Dashboardクラスの初期化に必要なパラメータ定義
 */
export interface DashboardProps {
  /**  
   * 環境名を小文字の英字で指定する。  
   * リソースの物理名やNameタグに利用される。
   */
  envName: string;
}

/**
 * ダッシュボードを展開するConstruct。
 * 
 * @remarks 
 * 以下のリソースを展開する。   
 *   - 表示メトリクス用S3バケット
 *   - 表示メトリクス用Lambda
 *   - 各種ウィジェット
 *   - ダッシュボード
 */
export class Dashboard extends Construct {

  /**
   * ダッシュボードを展開する。  
   * 初期化時に引数で与える{@link DashboardProps}の値に応じて環境パラメータが設定される。
   * 
   * @param props - 
   * AppS3Bucketクラスインスタンス初期化用のプロパティを指定する。詳細は{@link DashboardProps}を参照すること。
   */
  constructor(scope: Construct, id: string, props: DashboardProps) {
    super(scope, id);

    // 表示メトリクス用S3バケット作成
    const myVizBucket = new s3.Bucket(this, "VizBucket", {
      bucketName: `viz-${props.envName}-vizbucket`,
      enforceSSL: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });
    cdk.Tags.of(myVizBucket).add(
      "Name",
      "viz-" + props.envName + "-vizbucket"
    );
    myVizBucket.addMetric({id: "EntireBucket"});

    nag.NagSuppressions.addResourceSuppressions(
      myVizBucket,
      [
        { id: "AwsSolutions-S1", reason: "Because of test",},
      ],
      true
    );

    // 表示メトリクス用Lambda作成
    const myTestFunction = new lambda.Function(this, "TestFunction", {
      functionName: `viz-${props.envName}-test`,
      code: lambda.Code.fromAsset(path.join(__dirname, "../../lambda/test/")),
      handler: "main.lambda_handler",
      runtime: lambda.Runtime.PYTHON_3_8,
    });
    cdk.Tags.of(myTestFunction).add(
      "Name",
      "viz-" + props.envName + "-testFnction"
    );

    nag.NagSuppressions.addResourceSuppressions(
      myTestFunction,
      [
        { id: "AwsSolutions-IAM4", reason: "Use AWS Managed Policies.", appliesTo: ["Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]},
        { id: "AwsSolutions-L1", reason: "Because of test",},
      ],
      true
    );

    // メトリクスのウィジェット作成
    const allRequestsMetric = new cloudwatch.Metric({
      namespace: 'AWS/S3',
      metricName: 'AllRequests',
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket"
      },
      statistic: 'Average',
      period: cdk.Duration.minutes(5),
    });

    const getRequestMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "GetRequests",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const putRequestMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "PutRequests",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const clientErrorMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "4xxErrors",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const serverErrorMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "5xxErrors",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const bytesDownloadedMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "BytesDownloaded",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    const bytesUploadedMetric = new cloudwatch.Metric({
      namespace: "AWS/S3",
      metricName: "BytesUploaded",
      dimensionsMap: {
        BucketName: myVizBucket.bucketName,
        FilterId: "EntireBucket",
      },
      statistic: "Sum",
      period: cdk.Duration.minutes(5),
    });

    // const RequestsWidget = new cloudwatch.GraphWidget({
    //   title: "S3 Requests",
    //   left: [allRequestsMetric, getRequestMetric, putRequestMetric],  // リクエスト系メトリクス
    //   view: cloudwatch.GraphWidgetView.BAR,
    // });

    // const ErrorsWidget = new cloudwatch.GraphWidget({
    //   title: "S3 Errors",
    //   left: [clientErrorMetric, serverErrorMetric],  // エラート系メトリクス
    //   view: cloudwatch.GraphWidgetView.BAR,
    // });

    // const BytesWidget = new cloudwatch.GraphWidget({
    //   title: "Data Transfer",
    //   right: [bytesDownloadedMetric, bytesUploadedMetric],  // データ量系メトリクス
    //   view: cloudwatch.GraphWidgetView.TIME_SERIES,
    // });

    // // CloudWatch ダッシュボードの作成とウィジェットの追加
    // new cloudwatch.Dashboard(this, "MyDashboard", {
    //   dashboardName: "MyDashboard",
    //   widgets: [
    //     [RequestsWidget,ErrorsWidget], //一行目
    //     [BytesWidget], //二行目
    //   ],
    // });
    // S3 Requests and Errors Widget
    const requestsAndErrorsWidget = new cloudwatch.GraphWidget({
      title: "S3 Requests and Errors",
      left: [allRequestsMetric, getRequestMetric, putRequestMetric], // リクエスト数を左Y軸に表示
      view: cloudwatch.GraphWidgetView.BAR, // 棒グラフで表示
      height: 8, // ウィジェットの高さ
      width: 12, // ウィジェットの幅
      start: '2023-01-01T00:00:00Z', // 開始時間
      end: '2023-12-31T23:59:59Z', // 終了時間
      legendPosition: cloudwatch.LegendPosition.BOTTOM, // 凡例の位置
      liveData: true, // リアルタイムデータ
      setPeriodToTimeRange: true, // 時間範囲に基づいて期間自動設定
      stacked: true, // 積み上げグラフ表示
      leftYAxis: {
        min: 0,
        max: 1000,
      },
      rightYAxis: {
        min: 0,
        max: 500,
      },
      leftAnnotations: [
        { value: 800, color: 'red', label: 'High Request Volume' }
      ],
      rightAnnotations: [
        { value: 100, color: 'blue', label: 'Error Threshold' }
      ],
      verticalAnnotations: [
        { date: '2023-06-15T00:00:00Z', label: 'Mid-year' }
      ],
      period: Duration.minutes(5), // データ取得の間隔を指定
      region: 'us-west-2', // リージョン
    });

    // データ転送量(アップロードとダウンロード)ウィジェット
    const bytesWidget = new cloudwatch.GraphWidget({
      title: "S3 Data Transfer",
      left: [bytesDownloadedMetric], // ダウンロードを左Y軸に表示
      right: [bytesUploadedMetric], // アップロードを右Y軸に表示
      view: cloudwatch.GraphWidgetView.TIME_SERIES, // 折れ線グラフで表示
      height: 6,
      width: 6,
      start: '2023-01-01T00:00:00Z',
      end: '2023-12-31T23:59:59Z',
      legendPosition: cloudwatch.LegendPosition.RIGHT,
      liveData: false,
      setPeriodToTimeRange: false,
      stacked: false,
      leftYAxis: {
        min: 0,
        max: 1000,
      },
      rightYAxis: {
        min: 0,
        max: 2000,
      },
      leftAnnotations: [
        { value: 500, color: 'green', label: 'Download Target' }
      ],
      rightAnnotations: [
        { value: 1500, color: 'orange', label: 'Upload Warning' }
      ],
      verticalAnnotations: [
        { date: '2023-09-01T00:00:00Z', label: 'Peak Season Start' }
      ],
      period: Duration.minutes(10),
      region: 'us-east-1',
    });

    // addLeftMetric と addRightMetric メソッドの使用例
    requestsAndErrorsWidget.addLeftMetric(clientErrorMetric);
    requestsAndErrorsWidget.addRightMetric(serverErrorMetric);

    new cloudwatch.Dashboard(this, "MyDashboard", {
      dashboardName: "MyDashboard",
      widgets: [
        [requestsAndErrorsWidget], // 1行目のウィジェット
        [bytesWidget], // 2行目のウィジェット
      ],
    });

    // JSON 表示
    console.log(requestsAndErrorsWidget.toJson());
    console.log(bytesWidget.toJson());
  }
}
kirin-ri commented 16 hours ago
    const requestsAndErrorsWidget = new cloudwatch.GraphWidget({
      title: "S3 Requests and Errors",
      left: [allRequestsMetric, getRequestMetric],
      view: cloudwatch.GraphWidgetView.BAR,
      height: 8,
      width: 12,
      start: "-24h", // 过去24小时
      legendPosition: cloudwatch.LegendPosition.BOTTOM,
      liveData: true,
      setPeriodToTimeRange: true,
      stacked: true,
      leftYAxis: { min: 0, max: 1000 },
      rightYAxis: { min: 0, max: 500 },
      leftAnnotations: [{ value: 800, color: "red", label: "High Request Volume" }],
      rightAnnotations: [{ value: 100, color: "blue", label: "Error Threshold" }],
      verticalAnnotations: [
        { date: Date.now() / 1000 - 30 * 24 * 60 * 60, label: "1 Month Ago" }, // 1个月前
      ],
      period: Duration.minutes(5),
    });

    const bytesWidget = new cloudwatch.GraphWidget({
      title: "S3 Data Transfer",
      left: [clientErrorMetric],
      view: cloudwatch.GraphWidgetView.TIME_SERIES,
      height: 6,
      width: 6,
      start: "-7d", // 过去7天
      legendPosition: cloudwatch.LegendPosition.RIGHT,
      liveData: false,
      setPeriodToTimeRange: false,
      stacked: false,
      leftYAxis: { min: 0, max: 1000 },
      period: Duration.minutes(10),
    });
kirin-ri commented 16 hours ago
btrikra:~/environment/FBS-viz-trial (template) $ cdk deploy Dev-VizStack
[
  {
    type: 'metric',
    width: 12,
    height: 8,
    x: undefined,
    y: undefined,
    properties: {
      view: 'bar',
      title: 'S3 Requests and Errors',
      region: '${Token[AWS.Region.12]}',
      stacked: true,
      metrics: [Array],
      annotations: [Object],
      yAxis: [Object],
      legend: [Object],
      liveData: true,
      setPeriodToTimeRange: true,
      period: 300,
      stat: undefined,
      start: '-24h',
      end: undefined
    }
  }
]
[
  {
    type: 'metric',
    width: 6,
    height: 6,
    x: undefined,
    y: undefined,
    properties: {
      view: 'timeSeries',
      title: 'S3 Data Transfer',
      region: '${Token[AWS.Region.12]}',
      stacked: false,
      metrics: [Array],
      annotations: undefined,
      yAxis: [Object],
      legend: [Object],
      liveData: false,
      setPeriodToTimeRange: false,
      period: 600,
      stat: undefined,
      start: '-7d',
      end: undefined
    }
  }
]

✨  Synthesis time: 20.26s

Dev-VizStack: start: Building 3c3ab79ea898ed38cf6ba93bfa8c89a1bc5372b141b0cf34b746c3fb556018db:656100244460-ap-northeast-1
Dev-VizStack: success: Built 3c3ab79ea898ed38cf6ba93bfa8c89a1bc5372b141b0cf34b746c3fb556018db:656100244460-ap-northeast-1
Dev-VizStack: start: Publishing 3c3ab79ea898ed38cf6ba93bfa8c89a1bc5372b141b0cf34b746c3fb556018db:656100244460-ap-northeast-1
Dev-VizStack: success: Published 3c3ab79ea898ed38cf6ba93bfa8c89a1bc5372b141b0cf34b746c3fb556018db:656100244460-ap-northeast-1
Dev-VizStack: deploying... [1/1]
Dev-VizStack: creating CloudFormation changeset...
8:13:34 AM | UPDATE_FAILED        | AWS::CloudWatch::Dashboard  | DashboardMyDashboardDB214A95
Resource handler returned message: "The dashboard body is invalid, there are 1 validation errors:
[
{
"dataPath": "/widgets/0/properties",
"message": "Should match exactly one schema in oneOf"
}
] (Service: CloudWatch, Status Code: 400, Request ID: 296bbaa3-7c18-407d-962f-ec5ec805d597)" (RequestToken: 4a80c488-4055-7e13-6f55-b24e31b10890, HandlerErrorCode: GeneralServiceException)

❌  Dev-VizStack failed: The stack named Dev-VizStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Resource handler returned message: "The dashboard body is invalid, there are 1 validation errors:
[
  {
    "dataPath": "/widgets/0/properties",
    "message": "Should match exactly one schema in oneOf"
  }
] (Service: CloudWatch, Status Code: 400, Request ID: 296bbaa3-7c18-407d-962f-ec5ec805d597)" (RequestToken: 4a80c488-4055-7e13-6f55-b24e31b10890, HandlerErrorCode: GeneralServiceException)
kirin-ri commented 16 hours ago
import * as cdk from "aws-cdk-lib";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
import { Duration } from "aws-cdk-lib";

// 示例度量和 `GraphWidget`
const exampleMetric = new cloudwatch.Metric({
  namespace: "AWS/S3",
  metricName: "AllRequests",
  statistic: "Sum",
  period: Duration.minutes(5),
});

const myWidget = new cloudwatch.GraphWidget({
  title: "S3 Requests and Errors",
  view: cloudwatch.GraphWidgetView.BAR,
  left: [exampleMetric],
  stacked: true,
  height: 8,
  width: 12,
  start: "-24h",
  legendPosition: cloudwatch.LegendPosition.BOTTOM,
  liveData: true,
  setPeriodToTimeRange: true,
  verticalAnnotations: [
    {
      label: "1 Month Ago",
      value: Math.floor(new Date("2023-06-15T00:00:00Z").getTime() / 1000), // 使用 UNIX 时间戳
    },
  ],
});

new cloudwatch.Dashboard(this, "MyDashboard", {
  dashboardName: "MyDashboard",
  widgets: [
    [myWidget],
  ],
});