TECH PLAY

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

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

1226

今回は、Amazon EC2 Windows インスタンスのシステムリソース監視、状態監視、イベントログモニタリング、異常時のアラート通知までを Amazon CloudWatch を利用して一元的に AWS CDK で実装する方法をまとめました。 はじめに 今回は、Windows EC2インスタンスの包括的な監視システムをAWS CDKで実装していきます。システムリソース監視、死活監視、プロセス監視、Windowsイベントログ監視まで、運用に必要な監視項目を実装します。 また、今回は既に構築されているEC2のインスタンスIDを指定して監視設定を追加していきます。   今回作成するリソース SNSトピック : CloudWatchアラームの通知先 CloudWatchアラーム : システム/インスタンス死活監視、リソース使用率監視、プロセス監視 CloudWatch Logs : Windowsイベントログ収集とエラー監視 メトリクスフィルター : イベントログのエラーパターン検出   アーキテクチャ概要   AWS CDK ソースコード インスタンスIDインポート const ec2Instance = 'i-xxxxxxxxxxxxxxxxx' // 監視対象のインスタンスIDを指定 SNS通知設定 const emailAddresses = [ // SNS通知先メーリングリスト(通知先が複数ある場合アドレスを追加) 'xxxxxx@example.com', 'xxxxxxx@example.com', ]; // CloudWatchアラーム用トピック const alarmTopic = new sns.Topic(this, 'AlarmTopic', { topicName: 'clw-alertnotification', // トピック名 displayName: 'Cloudwatch Alert Notifications' // 表示名 }); // CloudWatchアラーム用サブスクリプション emailAddresses.forEach(email => { alarmTopic.addSubscription( new subscriptions.EmailSubscription(email) // プロトコル:EMAIL ); }); ポイント: 複数の管理者への通知配信 アラーム発生時に通知するメールアドレスを指定 CloudWatchアラーム設定 システム死活監視 const statusCheckFailedSystemAlarm = new cloudwatch.Alarm(this, 'StatusCheckFailedSystemAlarm', { alarmName: 'alarm-ec2-scfs', metric: new cloudwatch.Metric({ namespace: 'AWS/EC2', metricName: 'StatusCheckFailed_System', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, }, statistic: 'sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); statusCheckFailedSystemAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 statusCheckFailedSystemAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先   インスタンス死活監視 const statusCheckFailedInstanceAlarm = new cloudwatch.Alarm(this, 'StatusCheckFailedInstanceAlarm', { alarmName: 'alarm-ec2-scfi', metric: new cloudwatch.Metric({ namespace: 'AWS/EC2', metricName: 'StatusCheckFailed_Instance', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID }, statistic: 'sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); statusCheckFailedInstanceAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 statusCheckFailedInstanceAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先   CPU使用率監視 const cpuAlarm = new cloudwatch.Alarm(this, 'CpuAlarm', { alarmName: 'alarm-ec2-cpu', metric: new cloudwatch.Metric({ namespace: 'AWS/EC2', metricName: 'CPUUtilization', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID }, statistic: 'Average', // 統計:平均 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 90, // アラームの閾値(CPU使用率90%) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); cpuAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 cpuAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先   メモリ使用率監視 const memoryAlarm = new cloudwatch.Alarm(this, 'MemoryAlarm', { alarmName: 'alarm-ec2-mem', metric: new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'Memory % Committed Bytes In Use', // メトリクス名 dimensionsMap: { // 取得に必要な変数 InstanceId: ec2Instance, // 監視対象のEC2インスタンスID(config.jsonのappend_dimensionsで指定) objectname: 'Memory' // オブジェクト名(config.jsonのmetrics_collectedで指定) }, statistic: 'Average', // 統計:平均 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 90, // アラームの閾値(メモリ使用率90%) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); memoryAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 memoryAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先   ディスク使用率監視 const diskAlarmC = new cloudwatch.Alarm(this, 'DiskAlarmC', { alarmName: 'alarm-ec2-dsk-c', metric: new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'LogicalDisk % Free Space', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID(config.json側のappend_dimensionsで指定) instance: 'C:', // 対象ディスク objectname: 'LogicalDisk' // オブジェクト名(config.json側のmetrics_collectedで指定) }, statistic: 'Average', // 統計:平均 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 10, // アラームの閾値 evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, // アラーム条件: より小さい treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); diskAlarmC.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 diskAlarmC.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先   ディスク使用率監視(Dドライブ) const diskAlarmD = new cloudwatch.Alarm(this, 'diskDAlarmD', { alarmName: 'alarm-ec2-dsk-d', metric: new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'LogicalDisk % Free Space', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID(config.json側のappend_dimensionsで指定) instance: 'D:', // 対象ディスク objectname: 'LogicalDisk' // オブジェクト名(config.json側のmetrics_collectedで指定) }, statistic: 'Average', // 統計:平均 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 10, // アラームの閾値 evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, // アラーム条件: より小さい treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); diskAlarmD.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 diskAlarmD.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先   プロセス監視 const svchostServiceAlarm = new cloudwatch.Alarm(this, 'SVCHostServiceAlarm', { alarmName: 'alarm-svchost', metric: new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'procstat_lookup pid_count', // メトリクス名(プロセスに関連付けられたプロセスIDの数) dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID(config.json側のappend_dimensionsで指定) exe: 'svchost.exe', // 監視対象のプロセス名 pid_finder: 'native' // プロセスの検出方法:native(OSのネイティブAPIを使用) }, statistic: 'Min', // 統計: 最小 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値 evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件:閾値以下 treatMissingData: cloudwatch.TreatMissingData.BREACHING, // 欠落データを不正(しきい値を超えている)として処理 }); svchostServiceAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 svchostServiceAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先   イベントログ(システムログ)用のロググループのエラー監視 // イベントログ(システムログ)用のロググループのエラー監視アラーム const sytemLogErrorsAlarm = new cloudwatch.Alarm(this, 'SystemLogErrorsAlarm', { alarmName: 'os-eventlog-system', metric: new cloudwatch.Metric({ namespace: 'os-cloudwatchlogs', // カスタムメトリクスの名前空間(CloudwachLogsで作成した名前空間を指定) metricName: 'OS-Eventlog-System', // メトリクス名 statistic: 'Sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); sytemLogErrorsAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 ポイント: 名前空間 : システムロググループの名前空間を指定 メトリクス名 : システムロググループのメトリクス名を指定    イベントログ(アプリケーションログ)用のロググループのエラー監視 // イベントログ(アプリケーションログ)用のロググループのエラー監視アラーム const appLogErrorsAlarm = new cloudwatch.Alarm(this, 'ApplicationLogErrorsAlarm', { alarmName: 'os-eventlog-application', metric: new cloudwatch.Metric({ namespace: 'os-cloudwatchlogs', // カスタムメトリクスの名前空間(CloudwachLogsで作成した名前空間を指定) metricName: 'OS-Eventlog-Application', // メトリクス名 statistic: 'Sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); appLogErrorsAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 ポイント: 名前空間 : アプリケーションロググループの名前空間を指定 メトリクス名 : アプリケーションロググループのメトリクス名を指定   イベントログ(セキュリティ)用のロググループのエラー監視 // イベントログ(セキュリティログ)用のロググループのエラー監視アラーム const securityLogErrorsAlarm = new cloudwatch.Alarm(this, 'SecurityLogErrorsAlarm', { alarmName: 'os-eventlog-security', metric: new cloudwatch.Metric({ namespace: 'os-cloudwatchlogs', // カスタムメトリクスの名前空間(CloudwachLogsで作成した名前空間を指定) metricName: 'OS-Eventlog-Security', // メトリクス名 statistic: 'Sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); securityLogErrorsAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 ポイント: 名前空間 : イベントロググループの名前空間を指定 メトリクス名 : イベントロググループのメトリクス名を指定   CloudWatch Logs設定 ロググループとメトリクスフィルター // OSイベントログ(システムログ)用のロググループ const systemLogGroup = new logs.LogGroup(this, 'SystemLogGroup', { logGroupName: 'loggroup-os-eventlog-system', retention: logs.RetentionDays.ONE_MONTH, // 保持期間: 1ヵ月 removalPolicy: cdk.RemovalPolicy.DESTROY }); const systemErrorMetricFilter = new logs.MetricFilter(this, 'SystemErrorMetricFilter', { // イベントログ(システムログ)用メトリクスフィルター logGroup: systemLogGroup, filterName: 'filter-os-eventlog-system', // フィルター名 filterPattern: logs.FilterPattern.literal('?ERROR ?Error ?error ?FAIL ?Fail ?fail'), // フィルターパターン metricNamespace: 'os-cloudwatchlogs', // メトリクス名前空間 metricName: 'OS-Eventlog-System', // メトリクス名 metricValue: '1', // メトリクス値 defaultValue: 0 }); // OSイベントログ(アプリケーションログ)用のロググループ const applicationLogGroup = new logs.LogGroup(this, 'ApplicationLogGroup', { logGroupName: 'loggroup-os-eventlog-application', retention: logs.RetentionDays.ONE_MONTH, // 保持期間: 1ヵ月 removalPolicy: cdk.RemovalPolicy.DESTROY }); const applicationErrorMetricFilter = new logs.MetricFilter(this, 'ApplicationErrorMetricFilter', { // イベントログ(アプリケーションログ)用メトリクスフィルター logGroup: applicationLogGroup, filterName: 'filter-os-eventlog-application',        // フィルター名 filterPattern: logs.FilterPattern.literal('?ERROR ?Error ?error ?FAIL ?Fail ?fail'),    // フィルターパターン metricNamespace: 'os-cloudwatchlogs',      // メトリクス名前空間 metricName: 'OS-Eventlog-Application', // メトリクス名 metricValue: '1', // メトリクス値 defaultValue: 0 }); // OSイベントログ(セキュリティログ)用のロググループ const securityLogGroup = new logs.LogGroup(this, 'SecurityLogGroup', { logGroupName: 'loggroup-os-eventlog-security', retention: logs.RetentionDays.ONE_MONTH, // 保持期間: 1ヵ月 removalPolicy: cdk.RemovalPolicy.DESTROY }); const securityErrorMetricFilter = new logs.MetricFilter(this, 'SecurityErrorMetricFilter', { // イベントログ(セキュリティログ)用メトリクスフィルター logGroup: securityLogGroup, filterName: 'filter-os-eventlog-security', // フィルター名 filterPattern: logs.FilterPattern.literal('?ERROR ?Error ?error ?FAIL ?Fail ?fail'), // フィルターパターン metricNamespace: 'os-cloudwatchlogs', // メトリクス名前空間 metricName: 'OS-Eventlog-Security', // メトリクス名 metricValue: '1', // メトリクス値 defaultValue: 0 }); } } ポイント: ログ種別 : System、Application、Securityの3種類 メトリクスフィルター : ERROR/Error/error/FAIL/Fail/failパターンを検出 保持期間 : 1ヶ月(要件に応じて調整可能) 本番環境では :  removalPolicy: cdk.RemovalPolicy.RETAIN に変更 監視項目一覧 監視区分 監視項目 閾値 説明 死活監視 システムステータスチェック 失敗検知 AWS基盤レベルの問題検出 死活監視 インスタンスステータスチェック 失敗検知 インスタンスレベルの問題検出 リソース監視 CPU使用率 90%以上 プロセッサーの負荷監視 リソース監視 メモリ使用率 90%以上 メモリリソースの使用量監視 リソース監視 ディスク使用率(C:) 10%未満 システムドライブの空き容量 リソース監視 ディスク使用率(D:) 10%未満 データドライブの空き容量 プロセス監視 svchost.exe 1以下 重要システムプロセスの監視 イベントログ監視 システムログ エラー検出 システムレベルのエラー監視 イベントログ監視 アプリケーションログ エラー検出 アプリケーションエラー監視 イベントログ監視 セキュリティログ エラー検出 セキュリティ関連エラー監視   今回実装したコンストラクトファイルまとめ import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as sns from 'aws-cdk-lib/aws-sns'; import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; import * as actions from 'aws-cdk-lib/aws-cloudwatch-actions'; import * as logs from 'aws-cdk-lib/aws-logs'; export interface EC2WinMonitoringConstructProps { // 必要に応じて追加のプロパティを定義 } export class EC2WinMonitoringConstruct extends Construct { constructor(scope: Construct, id: string, props?: EC2WinMonitoringConstructProps) { super(scope, id); //=========================================== // EC2インスタンス //=========================================== const ec2Instance = 'i-xxxxxxxxxxxxxxxxx' // 監視対象のインスタンスIDを指定 //=========================================== // SNS //=========================================== const emailAddresses = [ // SNS通知先メーリングリスト(通知先が複数ある場合アドレスを追加) 'xxxxxx@example.com', 'xxxxxxx@example.com', ]; // CloudWatchアラーム用トピック const alarmTopic = new sns.Topic(this, 'AlarmTopic', { topicName: 'clw-alertnotification', // トピック名 displayName: 'Cloudwatch Alert Notifications' // 表示名 }); // CloudWatchアラーム用サブスクリプション emailAddresses.forEach(email => { alarmTopic.addSubscription( new subscriptions.EmailSubscription(email) // プロトコル:EMAIL ); }); //=========================================== // CloudWatchアラーム //=========================================== // システム死活監視 const statusCheckFailedSystemAlarm = new cloudwatch.Alarm(this, 'StatusCheckFailedSystemAlarm', { alarmName: 'alarm-ec2-scfs', metric: new cloudwatch.Metric({ namespace: 'AWS/EC2', metricName: 'StatusCheckFailed_System', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, }, statistic: 'sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); statusCheckFailedSystemAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 statusCheckFailedSystemAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先 // インスタンス死活監視 const statusCheckFailedInstanceAlarm = new cloudwatch.Alarm(this, 'StatusCheckFailedInstanceAlarm', { alarmName: 'alarm-ec2-scfi', metric: new cloudwatch.Metric({ namespace: 'AWS/EC2', metricName: 'StatusCheckFailed_Instance', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID }, statistic: 'sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); statusCheckFailedInstanceAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 statusCheckFailedInstanceAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先 // CPU使用率アラーム const cpuAlarm = new cloudwatch.Alarm(this, 'CpuAlarm', { alarmName: 'alarm-ec2-cpu', metric: new cloudwatch.Metric({ namespace: 'AWS/EC2', metricName: 'CPUUtilization', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID }, statistic: 'Average', // 統計:平均 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 90, // アラームの閾値(CPU使用率90%) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); cpuAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 cpuAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先 // メモリ使用率アラーム const memoryAlarm = new cloudwatch.Alarm(this, 'MemoryAlarm', { alarmName: 'alarm-ec2-mem', metric: new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'Memory % Committed Bytes In Use', // メトリクス名 dimensionsMap: { // 取得に必要な変数 InstanceId: ec2Instance, // 監視対象のEC2インスタンスID(config.jsonのappend_dimensionsで指定) objectname: 'Memory' // オブジェクト名(config.jsonのmetrics_collectedで指定) }, statistic: 'Average', // 統計:平均 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 90, // アラームの閾値(メモリ使用率90%) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); memoryAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 memoryAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先 // ディスク使用率アラーム const diskAlarmC = new cloudwatch.Alarm(this, 'DiskAlarmC', { alarmName: 'alarm-ec2-dsk-c', metric: new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'LogicalDisk % Free Space', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID(config.json側のappend_dimensionsで指定) instance: 'C:', // 対象ディスク objectname: 'LogicalDisk' // オブジェクト名(config.json側のmetrics_collectedで指定) }, statistic: 'Average', // 統計:平均 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 10, // アラームの閾値 evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, // アラーム条件: より小さい treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); diskAlarmC.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 diskAlarmC.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先 const diskAlarmD = new cloudwatch.Alarm(this, 'diskDAlarmD', { alarmName: 'alarm-ec2-dsk-d', metric: new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'LogicalDisk % Free Space', // メトリクス名 dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID(config.json側のappend_dimensionsで指定) instance: 'D:', // 対象ディスク objectname: 'LogicalDisk' // オブジェクト名(config.json側のmetrics_collectedで指定) }, statistic: 'Average', // 統計:平均 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 10, // アラームの閾値 evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, // アラーム条件: より小さい treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); diskAlarmD.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 diskAlarmD.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先 // プロセス監視アラーム const svchostServiceAlarm = new cloudwatch.Alarm(this, 'SVCHostServiceAlarm', { alarmName: 'alarm-svchost', metric: new cloudwatch.Metric({ namespace: 'CWAgent', metricName: 'procstat_lookup pid_count', // メトリクス名(プロセスに関連付けられたプロセスIDの数) dimensionsMap: { InstanceId: ec2Instance, // 監視対象のEC2インスタンスID(config.json側のappend_dimensionsで指定) exe: 'svchost.exe', // 監視対象のプロセス名 pid_finder: 'native' // プロセスの検出方法:native(OSのネイティブAPIを使用) }, statistic: 'Min', // 統計: 最小 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値 evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件:閾値以下 treatMissingData: cloudwatch.TreatMissingData.BREACHING, // 欠落データを不正(しきい値を超えている)として処理 }); svchostServiceAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 svchostServiceAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // アラームがOK状態に戻った時のSNS通知先 // イベントログ(システムログ)用のロググループのエラー監視アラーム const sytemLogErrorsAlarm = new cloudwatch.Alarm(this, 'SystemLogErrorsAlarm', { alarmName: 'os-eventlog-system', metric: new cloudwatch.Metric({ namespace: 'os-cloudwatchlogs', // カスタムメトリクスの名前空間(CloudwachLogsで作成した名前空間を指定) metricName: 'OS-Eventlog-System', // メトリクス名 statistic: 'Sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); sytemLogErrorsAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 // イベントログ(アプリケーションログ)用のロググループのエラー監視アラーム const appLogErrorsAlarm = new cloudwatch.Alarm(this, 'ApplicationLogErrorsAlarm', { alarmName: 'os-eventlog-application', metric: new cloudwatch.Metric({ namespace: 'os-cloudwatchlogs', // カスタムメトリクスの名前空間(CloudwachLogsで作成した名前空間を指定) metricName: 'OS-Eventlog-Application', // メトリクス名 statistic: 'Sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); appLogErrorsAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 // イベントログ(セキュリティログ)用のロググループのエラー監視アラーム const securityLogErrorsAlarm = new cloudwatch.Alarm(this, 'SecurityLogErrorsAlarm', { alarmName: 'os-eventlog-security', metric: new cloudwatch.Metric({ namespace: 'os-cloudwatchlogs', // カスタムメトリクスの名前空間(CloudwachLogsで作成した名前空間を指定) metricName: 'OS-Eventlog-Security', // メトリクス名 statistic: 'Sum', // 統計:合計 period: cdk.Duration.minutes(5), // メトリクスの収集間隔(期間):5分 }), threshold: 1, // アラームの閾値(1以上=失敗あり) evaluationPeriods: 1, datapointsToAlarm: 1, comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, // アラーム条件: 閾値以上 treatMissingData: cloudwatch.TreatMissingData.MISSING, // 欠落データを見つかりませんとして処理 }); securityLogErrorsAlarm.addAlarmAction(new actions.SnsAction(alarmTopic)); // アラーム発生時のSNS通知先 //================================================ // CloudWatchLogs作成 //================================================ // OSイベントログ(システムログ)用のロググループ const systemLogGroup = new logs.LogGroup(this, 'SystemLogGroup', { logGroupName: 'loggroup-os-eventlog-system', retention: logs.RetentionDays.ONE_MONTH, // 保持期間: 1ヵ月 removalPolicy: cdk.RemovalPolicy.DESTROY }); const systemErrorMetricFilter = new logs.MetricFilter(this, 'SystemErrorMetricFilter', { // イベントログ(システムログ)用メトリクスフィルター logGroup: systemLogGroup, filterName: 'filter-os-eventlog-system', // フィルター名 filterPattern: logs.FilterPattern.literal('?ERROR ?Error ?error ?FAIL ?Fail ?fail'), // フィルターパターン metricNamespace: 'os-cloudwatchlogs', // メトリクス名前空間 metricName: 'OS-Eventlog-System', // メトリクス名 metricValue: '1', // メトリクス値 defaultValue: 0 }); // OSイベントログ(アプリケーションログ)用のロググループ const applicationLogGroup = new logs.LogGroup(this, 'ApplicationLogGroup', { logGroupName: 'loggroup-os-eventlog-application', retention: logs.RetentionDays.ONE_MONTH, // 保持期間: 1ヵ月 removalPolicy: cdk.RemovalPolicy.DESTROY }); const applicationErrorMetricFilter = new logs.MetricFilter(this, 'ApplicationErrorMetricFilter', { // イベントログ(アプリケーションログ)用メトリクスフィルター logGroup: applicationLogGroup, filterName: 'filter-os-eventlog-application',        // フィルター名 filterPattern: logs.FilterPattern.literal('?ERROR ?Error ?error ?FAIL ?Fail ?fail'),    // フィルターパターン metricNamespace: 'os-cloudwatchlogs',      // メトリクス名前空間 metricName: 'OS-Eventlog-Application', // メトリクス名 metricValue: '1', // メトリクス値 defaultValue: 0 }); // OSイベントログ(セキュリティログ)用のロググループ const securityLogGroup = new logs.LogGroup(this, 'SecurityLogGroup', { logGroupName: 'loggroup-os-eventlog-security', retention: logs.RetentionDays.ONE_MONTH, // 保持期間: 1ヵ月 removalPolicy: cdk.RemovalPolicy.DESTROY }); const securityErrorMetricFilter = new logs.MetricFilter(this, 'SecurityErrorMetricFilter', { // イベントログ(セキュリティログ)用メトリクスフィルター logGroup: securityLogGroup, filterName: 'filter-os-eventlog-security', // フィルター名 filterPattern: logs.FilterPattern.literal('?ERROR ?Error ?error ?FAIL ?Fail ?fail'), // フィルターパターン metricNamespace: 'os-cloudwatchlogs', // メトリクス名前空間 metricName: 'OS-Eventlog-Security', // メトリクス名 metricValue: '1', // メトリクス値 defaultValue: 0 }); } }   Cloud Watch Agent Jsonサンプル { "agent": { "metrics_collection_interval": 60, "run_as_user": "System" }, "logs": { "logs_collected": { "windows_events": { "collect_list": [ { "event_name": "System", "event_levels": [ "ERROR", "WARNING", "INFORMATION" ], "log_group_name": "loggroup-os-eventlog-system", "log_stream_name": "{local_hostname}_{instance_id}" }, { "event_name": "Application", "event_levels": [ "ERROR", "WARNING", "INFORMATION" ], "log_group_name": "loggroup-os-eventlog-application", "log_stream_name": "{local_hostname}_{instance_id}" }, { "event_name": "Security", "event_levels": [ "ERROR", "WARNING", "INFORMATION", "CRITICAL" ], "log_group_name": "loggroup-os-eventlog-security", "log_stream_name": "{local_hostname}_{instance_id}" } ] } } }, "metrics": { "namespace": "CWAgent", "metrics_collected": { "Memory": { "measurement": [ "% Committed Bytes In Use" ], "metrics_collection_interval": 60 }, "LogicalDisk": { "measurement": [ "% Free Space" ], "metrics_collection_interval": 60, "resources": [ "*" ] }, "Processor": { "measurement": [ "% Processor Time" ], "metrics_collection_interval": 60, "resources": [ "*" ] }, "procstat": [ { "exe": "svchost.exe", "measurement": [ "pid_count" ], "metrics_collection_interval": 60 } ] }, "append_dimensions": { "InstanceId": "${aws:InstanceId}" } } }   まとめ 今回は、Windows EC2インスタンスの包括的な監視システムをAWS CDKで実装しました。 本実装ではリソースはマネジメントコンソールで作成する場合でも監視をAWS CDKを用いて構築することが出来ます。 IaCとして管理することで、環境間での一貫した監視設定の展開や、監視ルールの変更履歴管理も可能になります。また、CloudWatch Agent設定ファイルと組み合わせることで、Windows固有のメトリクスやログを効率的に収集・監視できます。 皆さんのお役に立てれば幸いです。
アバター
こんにちは、広野です。 まとまった数の PowerPoint 資料 (PPTX ファイル) を PDF に変換したくて、AWS Lambda 関数をつくってみました。 記事を 環境構築編 と Lambda 関数編 (本記事) に分けて説明します。 やりたかったこと 多くの PowerPoint 資料 (PPTX) があり、それを RAG (Amazon Bedrock Knowledge Bases) に食わせたい。のですが、RAG が PPTX をソースデータファイルとしてサポートしておらず、一度 PDF に変換しないといけない事情がありました。簡単に変換できるよう、Amazon S3 バケットに置いたら自動変換してくれる処理をつくりました。PPTX – PDF 変換には LibreOffice を使用します。 Amazon S3 バケットにファイルを置くと、イベント通知が発行されます。ファイルは input フォルダに置きます。 Amazon EventBridge ルールで、input フォルダ内の .pptx ファイルであれば AWS Lambda 関数を呼び出します。 Lambda 関数は、EventBridge から当該 PPTX ファイルのメタデータを受け取っているので、それをもとに PPTX ファイルを取得します。 Lambda 関数内で、LibreOffice をヘッドレスで (No GUI で) 実行し、PPTX を PDF に変換します。 作成された PDF ファイルを Amazon S3 バケットの output フォルダに保存します。名前は元ファイル名の拡張子が .pdf に変わっただけのものです。 LibreOffice について LibreOffice はオープンソースの Office ソフトウェアです。Word, Excel, PowerPoint などの Microsoft 製品と互換性があります。そのため、PowerPoint のファイルを扱うことができます。 ホーム | LibreOffice - オフィススイートのルネサンス ja.libreoffice.org この LibreOffice はヘッドレス、つまりコマンドで操作することができ、PowerPoint を PDF 変換する機能を利用します。 環境について 環境については、以下の記事をご覧ください。 PowerPoint ファイルを PDF に自動変換する AWS Lambda 関数をつくる -環境構築編- まとまった数の PowerPoint 資料 (PPTX ファイル) を PDF に変換したくて、AWS Lambda 関数をつくってみました。 blog.usize-tech.com 2026.01.05 環境をご理解いただいた上で、本記事の Lambda 関数の説明をお読みいただくことをお勧めします。   Lambda 関数について Lambda 関数はコンテナ化するので、コンテナイメージの中に Python スクリプト (.py) が格納されています。LibreOffice 含む必要なモジュールがインストールされたコンテナイメージに。 必要なソースコードは以下の構成になっています。 Dockerfile ビルドフェーズで実行するコマンドが書かれています。主にコンテナイメージをビルドし、Amazon ECR に保存するのが目的です。 buildspec.yml ビルドフェーズでコンテナのベースイメージにモジュールをインストールしたり配置したりするコマンドが書かれています。 lambda_function.py Lambda 関数です。今回は Python で書かれており、Amazon EventBridge ルールから Amazon S3 オブジェクトのメタデータを受け取り、Amazon S3 へのファイル読み書きや LibreOffice の PDF 変換コマンドを実行します。 cfn_container_lambda.yml ビルドフェーズでデプロイされた Amazon ECR 内のコンテナイメージと、Lambda 関数を関連付けます。また、Amazon S3 バケットから発行されたイベント通知を受け取るための Amazon EventBridge ルールをデプロイします。 以下のように、AWS CodeCommit リポジトリにはこれらソースコードを特にフォルダー分けせず放り込んでいます。AWS CodeCommit リポジトリ内の main ブランチのソースコードが更新されると、 環境構築編 で構築した CI/CD パイプラインが動き出しコンテナ Lambda 関数が自動でデプロイされる仕組みです。 中のコードを紹介します。ところどころインラインでコメントします。 Dockerfile FROM public.ecr.aws/shelf/lambda-libreoffice-base:25.8-python3.14-x86_64 COPY lambda_function.py ${LAMBDA_TASK_ROOT} CMD [ "lambda_function.handler" ] ものすごくシンプルです。 元々は自分で Amazon Linux 2023 や Python 用のベースイメージを使用していろいろインストールして動くものを作ったのですが、後から有志の方が LibreOffice 用のベースイメージを公開してくれていることに気付きました。ほんとよく出来ているので、それを使わせてもらっています。 GitHub - shelfio/libreoffice-lambda-base-image Contribute to shelfio/libreoffice-lambda-base-image development by creating an account on GitHub. github.com この GitHub の中を覗くと、このベースイメージを作成するためのコマンドも書いてあり、もしフォントを追加したいなどあれば自分で加工したものを作れると思います。※フォント追加の必要性については後述します。 buildspec.yml version: 0.2 phases: pre_build: commands: - echo Logging in to Amazon ECR... - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com build: commands: - echo Building the Docker image... - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG post_build: commands: - echo Pushing the Docker image... - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG artifacts: files: - cfn_container_lambda.yml 環境変数多めです。どうしてもコマンド実行の際に環境特有の情報が必要になるので。それ以外は特別なことはしていません。 lambda_function.py import json import os import subprocess import boto3 s3 = boto3.client("s3") WORKDIR = "/tmp" def handler(event, context): print("Event:", json.dumps(event)) # EventBridge - S3 情報取得 bucket = event["detail"]["bucket"]["name"] key = event["detail"]["object"]["key"] filename = os.path.basename(key) local_pptx = f"{WORKDIR}/{filename}" # S3 - /tmp にダウンロード s3.download_file(bucket, key, local_pptx) # LibreOffice変換 subprocess.run([ 'libreoffice25.8', '--headless', '--invisible', '--nodefault', '--view', '--nolockcheck', '--nologo', '--norestore', '--convert-to', 'pdf', '--outdir', WORKDIR, local_pptx ], check=True) pdf_name = filename.replace(".pptx", ".pdf") local_pdf = f"{WORKDIR}/{pdf_name}" # 出力 S3 の output フォルダへ output_key = f"output/{pdf_name}" s3.upload_file(local_pdf, bucket, output_key) return { "status": "ok", "input": key, "output": output_key } こちらも特段特別なことはしていません。 Amazon EventBridge ルールから Amazon S3 の PPTX ファイルのメタデータを受け取り、それを元にファイルを取得。LibreOffice をヘッドレス実行して PDF 変換します。PDF を Amazon S3 バケットに戻す処理だけです。 cfn_container_lambda.yml インラインでコメントします。Lambda 関数の箱の設定です。中身はコンテナイメージになるのでここには記述されません。 AWSTemplateFormatVersion: 2010-09-09 Description: The CloudFormation template that creates a Lambda function on container and a relevant IAM role. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# # 以下のパラメータは自動で AWS CodePipeline から環境変数を受け取ります。 # デフォルト値は気にする必要はありませんが、定義を削除するとエラーになります。 Parameters: SystemName: Type: String Description: System name. use lower case only. (e.g. example) Default: example MaxLength: 10 MinLength: 1 SubName: Type: String Description: System sub name. use lower case only. (e.g. prod or dev) Default: dev MaxLength: 10 MinLength: 1 ImageTag: Type: String Default: xxxxxxxxxxxxxxxxxxxx MaxLength: 100 MinLength: 1 ImgRepoName: Type: String Default: xxxxxxxxxxxxxxxxxxxx MaxLength: 100 MinLength: 1 S3BucketDocs: Type: String Default: xxxxxxxxxxxxxxxxxxxx MaxLength: 200 MinLength: 1 Resources: # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# Lambda: Type: AWS::Lambda::Function Properties: FunctionName: !Sub ${SystemName}-${SubName}-pptx-pdf-conv Description: !Sub Lambda Function to convert pptx to pdf for ${SystemName}-${SubName} PackageType: Image Timeout: 60 # メモリは 1024 MB にしました。670 MB ほど使用していましたので。512 MB だと処理に時間がかかりました。 # 1024 MB で、1 MB の PPTX の処理が 20 秒ほどかかりました。 # 10 MB を超える PPTX ファイルだと 1024 MB メモリをフルに消費し、時間も 60 秒タイムアウトを超過してしまいました。 # 取り扱うファイルサイズによってメモリサイズとタイムアウトは調整する必要があります。 MemorySize: 1024 EphemeralStorage: Size: 512 Architectures: - x86_64 # 環境変数として HOME を /tmp として設定しないと LibreOffice の実行が失敗します。 Environment: Variables: HOME: "/tmp" Role: !GetAtt LambdaRole.Arn Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} # ここで、コンテナイメージを Lambda 関数にするように関連付けています。 Code: ImageUri: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ImgRepoName}:${ImageTag} DependsOn: - LambdaRole # ------------------------------------------------------------# # Lambda Role (IAM) # ------------------------------------------------------------# LambdaRole: Type: AWS::IAM::Role Properties: RoleName: !Sub LambdaRole-pptx-pdf-conv-${SystemName}-${SubName} Description: This role allows Lambda functions to access S3 bucket. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess Policies: - PolicyName: !Sub LambdaPolicy-pptx-pdf-conv-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "s3:PutObject" - "s3:GetObject" Resource: - !Sub "arn:aws:s3:::${S3BucketDocs}/*" # ------------------------------------------------------------# # EventBridge Rule for starting Lambda function # ------------------------------------------------------------# EventBridgeRuleStartLambda: Type: AWS::Events::Rule Properties: Name: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-start-lambda Description: !Sub This rule starts pptx pdf converter Lambda function for ${SystemName}-${SubName}. The trigger is the S3 event notifications. EventBusName: !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default" EventPattern: source: - "aws.s3" detail-type: - "Object Created" detail: bucket: name: - !Ref S3BucketDocs object: key: - wildcard: "input/*.pptx" State: ENABLED Targets: - Arn: !GetAtt Lambda.Arn Id: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-start-lambda RoleArn: !GetAtt EventBridgeRuleLambdaRole.Arn DependsOn: - EventBridgeRuleLambdaRole # ------------------------------------------------------------# # EventBridge Rule Invoke Lambda Role (IAM) # ------------------------------------------------------------# EventBridgeRuleLambdaRole: Type: AWS::IAM::Role Properties: RoleName: !Sub EventBridgeLambdaRole-${SystemName}-${SubName} Description: !Sub This role allows EventBridge to invoke pptx pdf converter Lambda for ${SystemName}-${SubName}. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - events.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: !Sub EventBridgeLambdaPolicy-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "lambda:InvokeFunction" Resource: - !GetAtt Lambda.Arn DependsOn: - Lambda   変換した PDF  結局、この方法で PPTX を PDF に変換するとどうなるのか。PPTX と PDF のスクリーンショットを撮って比較してみました。 コンテナイメージに Noto Sans CJK フォントが入っていたので、日本語変換は問題ありません。しかし元々使用していたフォントと異なるので、ところどころにレイアウト崩れが起きてしまいました。見た目を気にする資料だとフォントを合わせないと実用的ではなさそうです。フォント以外は特段問題なさそうだと感じました。 PPTX PDF Noto Sans フォントの Noto は、No Tofu の意味です。環境にフォントが無いと文字化けした文字が四角形 (=豆腐) で表示されてしまうことがありましたが、もう豆腐は無くしたいという思いから、No Tofu -> Noto というフォントが作られたそうです。こんなところで日本語が使われていて面白いですね。   まとめ いかがでしたでしょうか。 本記事はコンテナ Lambda 関数の中身にフォーカスしていました。簡単でしたが LibreOffice の活用に触れられたと思います。アイデア次第で他の用途にも使えると思います。 本記事が皆様のお役に立てれば幸いです。
アバター
こんにちは、広野です。 まとまった数の PowerPoint 資料 (PPTX ファイル) を PDF に変換したくて、AWS Lambda 関数をつくってみました。 記事を環境構築編 (本記事) と Lambda 関数編 に分けて説明します。 やりたかったこと 多くの PowerPoint 資料 (PPTX) があり、それを RAG (Amazon Bedrock Knowledge Bases) に食わせたい。のですが、RAG が PPTX をソースデータファイルとしてサポートしておらず、一度 PDF に変換しないといけない事情がありました。簡単に変換できるよう、Amazon S3 バケットに置いたら自動変換してくれる処理をつくりました。PPTX – PDF 変換には LibreOffice を使用します。 Amazon S3 バケットにファイルを置くと、イベント通知が発行されます。ファイルは input フォルダに置きます。 Amazon EventBridge ルールで、input フォルダ内の .pptx ファイルであれば AWS Lambda 関数を呼び出します。 Lambda 関数は、EventBridge から当該 PPTX ファイルのメタデータを受け取っているので、それをもとに PPTX ファイルを取得します。 Lambda 関数内で、LibreOffice をヘッドレスで (No GUI で) 実行し、PPTX を PDF に変換します。 作成された PDF ファイルを Amazon S3 バケットの output フォルダに保存します。名前は元ファイル名の拡張子が .pdf に変わっただけのものです。 LibreOffice について LibreOffice はオープンソースの Office ソフトウェアです。Word, Excel, PowerPoint などの Microsoft 製品と互換性があります。そのため、PowerPoint のファイルを扱うことができます。 ホーム | LibreOffice - オフィススイートのルネサンス ja.libreoffice.org この LibreOffice はヘッドレス、つまりコマンドで操作することができ、PowerPoint を PDF 変換する機能を利用します。   環境について この実行環境は、大きく以下の 3つに分かれています。 Amazon S3 バケットに PPTX を保存し、PDF を受け取るインターフェースとしての Amazon S3 PPTX を PDF に変換する AWS Lambda 関数 -> Lambda 関数編の記事 で詳細を説明します。 AWS Lambda 関数をビルド、デプロイするための CI/CD 環境 全体像は以下の図のようになります。 図の右上の方に、インターフェースとしての Amazon S3 バケットがあります。 図の右下の方に、PPTX を PDF に変換する AWS Lambda 関数があります。Lambda 関数を呼び出すための Amazon EventBridge ルールとセットで、CI/CD パイプラインからデプロイされます。 ここで、なぜ CI/CD パイプラインを構築しているかというと。 LibreOffice の処理は通常の Lambda 関数にとっては重い処理になるので、コンテナ Lambda を使用することにしました。Docker コンテナイメージを作成する必要があり、イメージ置き場としての Amazon ECR、イメージをビルドする CI/CD パイプラインを構築しています。 ソースコードは大きく 2種類に分かれます。 ビルドフェーズで使用するコンテナイメージ構築用ファイル デプロイフェーズで使用する AWS CloudFormation テンプレート これらを開発者が AWS CodeCommit で管理しており、ソースコードが更新されると CI/CD パイプラインが動き出します。 ビルドフェーズでビルドされたコンテナイメージ (Lambda 関数の実体) は Amazon ECR に保存されます。そのままでは Lambda 関数として機能しないので、デプロイフェーズで AWS CloudFormation により Lambda 関数としてデプロイされます。   環境構築 (AWS CloudFormation) 上述の環境を AWS CloudFormation でデプロイしています。 Amazon S3 バケットに PPTX を保存し、PDF を受け取るインターフェースとしての Amazon S3 AWS Lambda 関数をビルド、デプロイするための CI/CD 環境 これができあがると、AWS CodeCommit でコンテナイメージを含む Lambda 関数コードを自由に開発、デプロイできます。 AWSTemplateFormatVersion: 2010-09-09 Description: The CloudFormation template that creates a CI/CD environment for a container Lambda function. It provides converting pptx to PDF. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: SystemName: Type: String Description: System name. use lower case only. (e.g. example) Default: example MaxLength: 10 MinLength: 1 SubName: Type: String Description: System sub name. use lower case only. (e.g. prod or dev) Default: dev MaxLength: 10 MinLength: 1 Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "General Configuration" Parameters: - SystemName - SubName Resources: # ------------------------------------------------------------# # S3 # ------------------------------------------------------------# S3BucketDocs: Type: AWS::S3::Bucket Properties: BucketName: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-docs LifecycleConfiguration: Rules: - Id: AutoDelete Status: Enabled ExpirationInDays: 14 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} S3BucketArtifact: Type: AWS::S3::Bucket Properties: BucketName: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-artifact LifecycleConfiguration: Rules: - Id: AutoDelete Status: Enabled ExpirationInDays: 14 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} S3BucketLogs: Type: AWS::S3::Bucket Properties: BucketName: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-logs LifecycleConfiguration: Rules: - Id: AutoDelete Status: Enabled ExpirationInDays: 365 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} # ------------------------------------------------------------# # ECR # ------------------------------------------------------------# EcrRepositoryContainerLambda: Type: AWS::ECR::Repository Properties: RepositoryName: !Sub ${SystemName}-${SubName}-pptx-pdf-conv EncryptionConfiguration: EncryptionType: AES256 ImageScanningConfiguration: ScanOnPush: true ImageTagMutability: IMMUTABLE LifecyclePolicy: LifecyclePolicyText: | { "rules": [ { "rulePriority": 1, "description": "Keep only 5 images, expire all others", "selection": { "tagStatus": "any", "countType": "imageCountMoreThan", "countNumber": 5 }, "action": { "type": "expire" } } ] } EmptyOnDelete: true Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} # ------------------------------------------------------------# # CodeCommit Repository # ------------------------------------------------------------# CodeCommitRepoContainerLambda: Type: AWS::CodeCommit::Repository Properties: RepositoryName: !Sub ${SystemName}-${SubName}-pptx-pdf-conv RepositoryDescription: !Sub pptx pdf converter for ${SystemName}-${SubName} Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} # ------------------------------------------------------------# # CodePipeline # ------------------------------------------------------------# CodePipelineContainerLambda: Type: AWS::CodePipeline::Pipeline Properties: Name: !Sub ${SystemName}-${SubName}-pptx-pdf-conv PipelineType: V2 ArtifactStore: Location: !Ref S3BucketArtifact Type: S3 RestartExecutionOnUpdate: false RoleArn: !GetAtt CodePipelineServiceRoleContainerLambda.Arn Stages: - Name: Source Actions: - Name: Source RunOrder: 1 ActionTypeId: Category: Source Owner: AWS Version: 1 Provider: CodeCommit Configuration: RepositoryName: !GetAtt CodeCommitRepoContainerLambda.Name BranchName: main PollForSourceChanges: false OutputArtifactFormat: CODEBUILD_CLONE_REF Namespace: SourceVariables OutputArtifacts: - Name: Source - Name: Build Actions: - Name: Build RunOrder: 1 Region: !Sub ${AWS::Region} ActionTypeId: Category: Build Owner: AWS Version: 1 Provider: CodeBuild Configuration: ProjectName: !Ref CodeBuildProjectContainerLambda BatchEnabled: false EnvironmentVariables: | [ { "name": "IMAGE_TAG", "type": "PLAINTEXT", "value": "#{codepipeline.PipelineExecutionId}" } ] Namespace: BuildVariables InputArtifacts: - Name: Source OutputArtifacts: - Name: Build - Name: Deploy Actions: - ActionTypeId: Category: Deploy Owner: AWS Provider: CloudFormation Version: 1 Configuration: StackName: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-lambda Capabilities: CAPABILITY_NAMED_IAM RoleArn: !GetAtt CodePipelineDeployCreateUpdateRoleContainerLambda.Arn ActionMode: CREATE_UPDATE TemplatePath: Build::cfn_container_lambda.yml ParameterOverrides: !Sub '{"SystemName":"${SystemName}","SubName":"${SubName}","ImageTag":"#{codepipeline.PipelineExecutionId}","S3BucketDocs":"${S3BucketDocs}","ImgRepoName":"${EcrRepositoryContainerLambda}"}' InputArtifacts: - Name: Build Name: CreateOrUpdate RoleArn: !GetAtt CodePipelineDeployCreateUpdateActionRoleContainerLambda.Arn RunOrder: 1 Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} DependsOn: - CodePipelineServiceRoleContainerLambda - CodeBuildProjectContainerLambda - CodePipelineDeployCreateUpdateActionRoleContainerLambda - EcrRepositoryContainerLambda # ------------------------------------------------------------# # CodePipeline Service Role (IAM) # ------------------------------------------------------------# CodePipelineServiceRoleContainerLambda: Type: AWS::IAM::Role Properties: RoleName: !Sub CpServiceRoleContainerLambda-${SystemName}-${SubName} Description: This role allows CodePipeline to call each stages. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codepipeline.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: !Sub CpServicePolicyContainerLambda-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "codecommit:CancelUploadArchive" - "codecommit:GetBranch" - "codecommit:GetCommit" - "codecommit:GetRepository" - "codecommit:GetUploadArchiveStatus" - "codecommit:UploadArchive" Resource: !GetAtt CodeCommitRepoContainerLambda.Arn - Effect: Allow Action: - "codebuild:BatchGetBuilds" - "codebuild:StartBuild" - "codebuild:BatchGetBuildBatches" - "codebuild:StartBuildBatch" Resource: "*" - Effect: Allow Action: - "cloudwatch:*" - "s3:*" Resource: "*" - Effect: Allow Action: - "lambda:InvokeFunction" - "lambda:ListFunctions" Resource: "*" - Effect: Allow Action: "sts:AssumeRole" Resource: - !GetAtt CodePipelineDeployCreateUpdateActionRoleContainerLambda.Arn DependsOn: - CodeCommitRepoContainerLambda - CodePipelineDeployCreateUpdateActionRoleContainerLambda # ------------------------------------------------------------# # CodePipeline Deploy Create Update Role (IAM) # ------------------------------------------------------------# CodePipelineDeployCreateUpdateRoleContainerLambda: Type: AWS::IAM::Role Properties: RoleName: !Sub CpCrUpdRoleContainerLambda-${SystemName}-${SubName} AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: cloudformation.amazonaws.com Version: "2012-10-17" Path: / Policies: - PolicyName: !Sub CpCrUpdPolicyContainerLambda-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Action: "*" Effect: Allow Resource: "*" # ------------------------------------------------------------# # CodePipeline Deploy Create Update Action Role (IAM) # ------------------------------------------------------------# CodePipelineDeployCreateUpdateActionRoleContainerLambda: Type: AWS::IAM::Role Properties: RoleName: !Sub CpCrUpdActionRoleContainerLambda-${SystemName}-${SubName} AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: AWS: Fn::Join: - "" - - "arn:" - Ref: AWS::Partition - ":iam::" - Ref: AWS::AccountId - :root Version: "2012-10-17" Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSCloudFormationFullAccess Policies: - PolicyName: !Sub CpCrUpdPolicyContainerLambda-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Action: iam:PassRole Effect: Allow Resource: !GetAtt CodePipelineDeployCreateUpdateRoleContainerLambda.Arn - Action: - s3:GetBucket* - s3:GetObject* - s3:List* Effect: Allow Resource: - !Sub arn:aws:s3:::${S3BucketArtifact} - !Sub arn:aws:s3:::${S3BucketArtifact}/* DependsOn: - CodePipelineDeployCreateUpdateRoleContainerLambda - S3BucketArtifact # ------------------------------------------------------------# # EventBridge Rule for Starting CodePipeline # ------------------------------------------------------------# EventBridgeRuleStartCodePipelineContainerLambda: Type: AWS::Events::Rule Properties: Name: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-start-codepipeline Description: !Sub This rule starts pptx pdf converter CodePipeline for ${SystemName}-${SubName}. The trigger is the source code change in CodeCommit. EventBusName: !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default" EventPattern: source: - "aws.codecommit" detail-type: - "CodeCommit Repository State Change" resources: - !GetAtt CodeCommitRepoContainerLambda.Arn detail: event: - referenceCreated - referenceUpdated referenceType: - branch referenceName: - main RoleArn: !GetAtt EventBridgeRuleStartCpRoleContainerLambda.Arn State: ENABLED Targets: - Arn: !Sub "arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipelineContainerLambda}" Id: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-start-codepipeline RoleArn: !GetAtt EventBridgeRuleStartCpRoleContainerLambda.Arn DependsOn: - EventBridgeRuleStartCpRoleContainerLambda # ------------------------------------------------------------# # EventBridge Rule Start CodePipeline Role (IAM) # ------------------------------------------------------------# EventBridgeRuleStartCpRoleContainerLambda: Type: AWS::IAM::Role Properties: RoleName: !Sub EventBridgeStartCpRoleContainerLambda-${SystemName}-${SubName} Description: !Sub This role allows EventBridge to start pptx pdf converter CodePipeline for ${SystemName}-${SubName}. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - events.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: !Sub EventBridgeStartCpPolicyContainerLambda-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "codepipeline:StartPipelineExecution" Resource: - !Sub "arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${CodePipelineContainerLambda}" DependsOn: - CodePipelineContainerLambda # ------------------------------------------------------------# # CodeBuild Project # ------------------------------------------------------------# CodeBuildProjectContainerLambda: Type: AWS::CodeBuild::Project Properties: Name: !Sub ${SystemName}-${SubName}-pptx-pdf-conv Description: !Sub The build project for ${SystemName}-${SubName}-pptx-pdf-conv ResourceAccessRole: !GetAtt CodeBuildResourceAccessRoleContainerLambda.Arn ServiceRole: !GetAtt CodeBuildServiceRoleContainerLambda.Arn ConcurrentBuildLimit: 1 Visibility: PRIVATE Source: Type: CODEPIPELINE SourceVersion: refs/heads/main Environment: Type: LINUX_CONTAINER ComputeType: BUILD_GENERAL1_SMALL Image: "aws/codebuild/amazonlinux-x86_64-standard:5.0" ImagePullCredentialsType: CODEBUILD PrivilegedMode: true EnvironmentVariables: - Name: AWS_DEFAULT_REGION Type: PLAINTEXT Value: !Sub ${AWS::Region} - Name: AWS_ACCOUNT_ID Type: PLAINTEXT Value: !Sub ${AWS::AccountId} - Name: IMAGE_REPO_NAME Type: PLAINTEXT Value: !Ref EcrRepositoryContainerLambda TimeoutInMinutes: 30 QueuedTimeoutInMinutes: 60 Artifacts: Type: CODEPIPELINE Cache: Type: NO_CACHE LogsConfig: CloudWatchLogs: GroupName: !Sub /aws/codebuild/${SystemName}-${SubName}-pptx-pdf-conv Status: ENABLED S3Logs: EncryptionDisabled: true Location: !Sub arn:aws:s3:::${S3BucketLogs}/codebuildBuildlog Status: ENABLED Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} DependsOn: - EcrRepositoryContainerLambda - CodeBuildResourceAccessRoleContainerLambda - CodeBuildServiceRoleContainerLambda # ------------------------------------------------------------# # CodeBuild Resource Access Role (IAM) # ------------------------------------------------------------# CodeBuildResourceAccessRoleContainerLambda: Type: AWS::IAM::Role Properties: RoleName: !Sub CbResourceAccessRoleContainerLambda-${SystemName}-${SubName} Description: This role allows CodeBuild to access CloudWatch Logs and Amazon S3 artifacts for the project's builds. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codebuild.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: !Sub CbResourceAccessPolicyContainerLambda-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${SystemName}-${SubName}-pptx-pdf-conv" - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${SystemName}-${SubName}-pptx-pdf-conv:*" - Effect: Allow Action: - "s3:PutObject" - "s3:GetObject" - "s3:GetObjectVersion" - "s3:GetBucketAcl" - "s3:GetBucketLocation" Resource: - !Sub arn:aws:s3:::${S3BucketLogs} - !Sub arn:aws:s3:::${S3BucketLogs}/* # ------------------------------------------------------------# # CodeBuild Service Role (IAM) # ------------------------------------------------------------# CodeBuildServiceRoleContainerLambda: Type: AWS::IAM::Role Properties: RoleName: !Sub CbServiceRoleContainerLambda-${SystemName}-${SubName} Description: This role allows CodeBuild to interact with dependant AWS services. AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codebuild.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser Policies: - PolicyName: !Sub CbServicePolicyContainerLambda-${SystemName}-${SubName} PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - "codecommit:GitPull" Resource: !GetAtt CodeCommitRepoContainerLambda.Arn - Effect: Allow Action: - "ssm:GetParameters" Resource: - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SystemName}_${SubName}_*" - Effect: Allow Action: - "s3:*" Resource: - !Sub arn:aws:s3:::${S3BucketArtifact} - !Sub arn:aws:s3:::${S3BucketArtifact}/* - !Sub arn:aws:s3:::${S3BucketLogs} - !Sub arn:aws:s3:::${S3BucketLogs}/* - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${SystemName}-${SubName}-pptx-pdf-conv" - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${SystemName}-${SubName}-pptx-pdf-conv:*" - Effect: Allow Action: - "codebuild:CreateReportGroup" - "codebuild:CreateReport" - "codebuild:UpdateReport" - "codebuild:BatchPutTestCases" - "codebuild:BatchPutCodeCoverages" Resource: - !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/${SystemName}-${SubName}-pptx-pdf-conv*" DependsOn: - CodeCommitRepoContainerLambda - S3BucketArtifact - S3BucketLogs   関連記事 Lambda 関数の中身については、以下の記事で紹介しています。 PowerPoint ファイルを PDF に自動変換する AWS Lambda 関数をつくる -Lambda関数編- まとまった数の PowerPoint 資料 (PPTX ファイル) を PDF に変換したくて、AWS Lambda 関数をつくってみました。 blog.usize-tech.com 2026.01.05   まとめ いかがでしたでしょうか。 本記事はコンテナ Lambda 関数の CI/CD 環境構築にフォーカスしていましたので、他の用途にも使えると思います。 本記事が皆様のお役に立てれば幸いです。
アバター
近年、クラウドサービスの選択肢はますます多様化しており、さまざまなクラウドが活用されています。 世界のクラウドプロバイダーのシェア上位3社を見ると、AWSが29%、Microsoft Azureが20%、Google Cloudが13%となっており※、 Microsoft AzureやGoogle Cloud Platformも成長を続けていますが、依然としてAWSがトップの座を維持しています。 (※2025年第3四半期データ  Cloud Market Growth Rate Rises Again in Q3; Biggest Ever Sequential Increase | Synergy Research Group ) 実際、当チームでもクラウド案件の多くは引き続きAWSが中心です。 そこで本記事では、LifeKeeperによる可用性対応の観点から、AWSでよく採用される代表的な構成パターンについて紹介します。 AWS環境で高可用性設計を検討されている方の参考になれば幸いです。 AWS OSごとの基本構成 Amazon EC2で冗長化構成をとる場合のOSごとの基本構成は以下の通りです。(稼働系、待機系ノード間でデータ共有を行う場合を想定。) 基本的に他のクラウドやオンプレミスの仮想環境と大きく変わりません。 Windows環境の場合はWindows標準機能のWSFCを使用することでコストを抑えた高可用性を確保することができます。 Linux Windows LifeKeeper+DataKeeperの組み合わせ LifeKeeper+DataKeeperの組み合わせ WSFC+DataKeeperの組み合わせ ※DataKeeperによるデータレプリケーションは必須ではなく、LifeKeeperのみの構成も可能です。 ※図では省略しておりますが、可用性の観点から、各クラスターノードを別々のアベイラビリティゾーン(AZ)に配置することで、物理的な障害発生時にもシステム停止リスクを最小限に抑えることが可能です。 ルートテーブルシナリオ(仮想IPとルートテーブルによる制御) この構成はクラスターを同じVPC内のクライアントから接続される際によく用いられます。 クライアント(クラスタノードと通信するマシン)は仮想IPに向けて通信することでActiveノードに到達できます。 AWS環境でAZを跨ぐとサブネットも跨いでしまうので、オンプレミスのように仮想IPだけではクライアントは正しくActiveノードへ到達できません。 そこで、VPCのCIDR外の仮想IPをルートテーブルに登録し、転送先のActive/StandbyノードのENIをクラスターの切り替え時にLifeKeeperからAWS CLIを介して書き換えることで、クライアントは常にActiveノードに到達できます。 <概要図>     ➀VPCのCIDR外の仮想IPアドレス(図では10.1.0.10)を用意して、クライアントから仮想IPに向けて通信します。 ②ルートテーブルにはあらかじめ仮想IPのTaegetとして稼働系のENI(図では10.0.2.4)を指定しておくことで、 クライアントからの通信は稼働系へ到達します。 ③フェイルオーバー時には、LifeKeeperから自動的にAWS CLIが実行され、ルートテーブルの仮想IPのターゲットが待機系ENIに書き換えられます。以降はクライアントからの通信は待機系に到達します。 この構成で利用必須となるRecovery Kit: Recovery Kit for EC2、Recovery Kit for IP Address ※注意 :AWSの仕様上、クライアントはクラスタと同じVPCに存在している必要があります。 仮想IPはVPCのCIDR外で割り当てる必要があります。   ▼参考URL ・Linux AWS EC2リソースの作成(ルートテーブルシナリオ) – LifeKeeper for Linux LIVE – 10.0 Recovery Kit for EC2™ 管理ガイド – LifeKeeper for Linux LIVE – 10.0 ・Windows Recovery Kit for Amazon EC2™ 管理ガイド – LifeKeeper for Windows LIVE – 10.0 ルートテーブルシナリオにAWS Transit Gatewayを組み合わせた制御 ルートテーブルシナリオにAWS Transit Gatewayを組み合わせることで、VPC外(オンプレミスや別VPC)からもクライアント通信に対応できます。 例えば、JP1等の統合運用管理ツール、HULFTなどのファイル転送ソフトでクライアントがVPC外にいる場合、この構成が有効です。 <概要図> ※クライアントはVPC外となりますが、仮想IPを使用したクラスターへの通信経路はルートテーブルシナリオと同様になります。 この構成で利用必須となるRecovery Kit: Recovery Kit for EC2、Recovery Kit for IP Address ※注意 :Transit Gateway向きのルートテーブル設定を行っておく必要があります。   ▼参考URL ・Linux AWS Direct Connect クイックスタートガイド – LifeKeeper for Linux LIVE – 10.0 Recovery Kit for EC2™ 管理ガイド – LifeKeeper for Linux LIVE – 10.0 ・Windows AWS Direct Connect クイックスタートガイド – LifeKeeper for Windows LIVE – 10.0 Recovery Kit for Amazon EC2™ 管理ガイド – LifeKeeper for Windows LIVE – 10.0 Route53のAレコード書き換えによる制御 Transit Gatewayが使えない場合などに、DNSサービスのRoute53での名前解決を利用する構成です。 クライアントはRoute53により名前解決された実IPに向けて通信することで、Activeノードへ到達できます。 <概要図> ➀クライアントからホスト名でクラスターノードにアクセスし、Route53で名前解決を行い、 名前解決した実IPアドレスで稼働系ノードにアクセスします。 ②Route53のAレコードには、稼働系ノードのIPアドレス(図では10.0.2.4)を指定しておくことで、クライアントからの通信は稼働系へ到達します。 ③フェイルオーバ時には、LifeKeeperからAWS CLIを実行し、Route53のAレコードを待機系ノードのIPアドレス(図では10.0.4.4)へ書き換えることで、以降のクライアントからの通信は待機系に到達します。 この構成で利用必須となるRecovery Kit: Recovery Kit for Amazon Route 53™、Recovery Kit for IP Address ※注意 :クライアントはホスト名(FQDN)でアクセスできることが前提になります。 LifeKeeper( Recovery Kit for Amazon Route 53™ )に登録するホストゾーン名がパブリックホストゾーン、プライベートホストゾーンで複数存在している場合、 Recovery Kit for Amazon Route 53™によるリソース作成は現状不可となりますのでご注意ください。 (参考: 同名のホストゾーンは使えない!? Amazon Route 53リソース作成時の注意点 – TechHarmony ) ⇒上記問題発覚後、サイオステクノロジー社に改善提案を出したところ、今後改善予定で動いているとのこと。 (2025年12月時点リリース時期未定)   ▼参考URL ・Linux AWS Route53 シナリオ – LifeKeeper for Linux LIVE – 10.0 Recovery Kit for Amazon Route 53™ 管理ガイド – LifeKeeper for Linux LIVE – 10.0 AWS Route 53リソースの作成 – LifeKeeper for Linux LIVE – 10.0 ・Windows Recovery Kit for Amazon Route 53™ 管理ガイド – LifeKeeper for Windows LIVE – 10.0            インターネットに出られない環境からプロキシ経由でRoute53にアクセスする方法 | ビジネス継続とITについて考える NLBのヘルスチェックによる制御 NLB(Network Load Balancer)のヘルスチェックを利用した構成です。 セキュリティ要件により、前述のようなAWS CLIによる構成変更が実施できない、インターネットに接続していない環境でクライアントからDNS名でアクセスしたい場合にはこの構成をご検討下さい。 クライアントからはNLBのDNS名でアクセスし (AWS内部のRoute 53経由で解決)、NLBのヘルスチェックとLB Health Check Kitを組み合わせて、ヘルスプローブを受け取ったノードにトラフィックを転送することで接続先の切り替えを実現します。 <概要図> ➀クライアントはNLBのDNS名とアプリケーションのポート番号 で接続を試みます。(図ではXXXX-nlb1-YYYY.elb.region.amazonaws.comと1521) (DNS 名はAWS内部のRoute 53経由でNLBのサブネットのIPアドレスに変換されます) ②NLBには、プロトコルとポートに対してどのターゲットグループへ転送するかが登録されています。このとき、どのノードがヘルスプローブに応答するかを確認します。 ③アクティブノードではNLBのヘルスプローブに応答します。LifeKeeperによってヘルスプローブに応答するLB Health Checkリソースは常にひとつのインスタンスでのみアクティブになっているため、NLBのヘルスプローブに応答するのはアクティブノードだけです。つまり、NLBは常にアクティブノードだけにトラフィックを転送します。 ④NLBは、Clientからの接続要求を、アクティブノードに転送します。そのため最終的に接続要求は、宛先アドレスがNLBのアドレスからアクティブノードの実IPアドレス に書き換えられて、アクティブノードに到達します。 この構成で利用必須となるRecovery Kit: LB Health Check Kit、Recovery Kit for IP Address   ▼参考URL ・Linux AWS – Network Load Balancerの使用 – LifeKeeper for Linux LIVE – 10.0 AWS Network Load Balancerシナリオ – LifeKeeper for Linux LIVE – 10.0 ・Windows AWSでロードバランサーを使用した構成 – LifeKeeper for Windows LIVE – 10.0 さいごに 今回は代表的な構成についてご紹介しましたが、 その他にもクロスリージョン構成やAWS Outpostsで共有ディスクを冗長化させるなど様々な構成にも対応しています。 ▼参考URL ・AWS Elastic IPシナリオ構成: AWS Elastic IPシナリオ – LifeKeeper for Linux LIVE – 10.0 ・クロスリージョン構成: [Linux][Windows] AWSのクロスリージョン環境において仮想IPで通信できる構成をサポート – SIOS LifeKeeper/DataKeeper User Portal ・Amazon FSx for NetApp ONTAP利用構成: Amazon FSx for NetApp ONTAPのサポート開始について | ビジネス継続とITについて考える ・AWS Outposts ラック利用構成: AWS Outposts ラックでの可用性を高めるHAクラスター構成|ユースケース|サイオステクノロジー株式会社 この記事で紹介されていない構成につきましては弊社やサイオステクノロジー社にお問い合わせ頂くことを推奨いたします 本記事がAWS環境における冗長化の参考になりましたら幸いです!
アバター
こんにちは、高坂です。 前回の記事 では、Prisma Cloudのアラート解決状況をバブルチャートで可視化する試みについてご紹介しました。 バブルチャートでの可視化は、「どの領域で」「どれくらいの量と重要度のアラートが」「どの程度放置されているか」を直感的に把握する上で非常に有効でした。しかし、2次元のグラフであるバブルチャートでは、主に扱える変数が限られるという制約がありました。例えば、「アラートの種類」と「重要度」という2つの軸で状況を見ることはできても、そこに3つ目以上の要因を加えて、より多角的に分析することは困難でした。 そこで今回は、機械学習の手法の一つである「決定木」を使い、アラートの対応条件を分析する方法を試しました。決定木を用いることで、 Policy Type (種類)、 Policy Severity (重要度)、そして Alert Time (発生時期)といった複数の変数を同時に扱い、「チームの対応ルールがいつ、どのように変化したか」を解明することを目指します。 決定木とは 決定木は、機械学習のアルゴリズムの一種で、その名の通り、データを分類するためのルールを木のような構造(ツリー構造)で表現する手法です。 詳しい仕組みは以下の記事がわかりやすいです。 決定木の基礎 #機械学習入門 – Qiita 決定木を用いる最大のメリットは、その結果の分かりやすさにあります。他の高度な機械学習モデルが、時に「ブラックボックス」として振る舞うことがあるのに対し、決定木は人間が読んで解釈できる「if-then」形式のルールを出力します。 分析の前提と注意点 ただし、この決定木が万能というわけではないことを注釈しておきます。 決定木は、分析対象のデータ、今回の場合だとチームのアラート対応状況に、何らかの一貫したパターンや傾向(=暗黙のルール)が存在することを前提としています。 今回の決定木での分析を行えば、必ずしも明確な結果が保証されるわけではないということはご了承ください。 もし、チームの対応方針が定まっておらず完全に場当たり的であったり、担当者ごとに判断基準が大きく異なっていたりする場合、決定木は明確で解釈しやすいルールを見つけ出せない可能性があります。結果として、非常に複雑で、ビジネス的な意味を見出しにくいツリーが出力されるかもしれません。 可視化の準備:分析シナリオと仮想データの作成 今回の分析では「Policy Type」、「Policy Severity」、「Alert Time」、「Alert Status」のデータを使用していきます。 決定木がどのようなルールを見つけ出すのかを具体的に見ていくために、今回は「とある組織で行われたアラート対応」という仮想的なシナリオを用意し、それに基づいてダミーデータを用意しました。 このシナリオには、明確なアラート対応と「改善前」と「改善後」のフェーズが存在します。 【Phase 1】 対応ルール導入前(〜2025年7月31日まで) この組織のセキュリティ運用チームは、日々大量に発生するアラートへの対応に追われ、疲弊していました。明確なトリアージ基準はなく、対応は一部のベテラン担当者の経験と勘に頼っている状況でした。 この時期の暗黙的な対応ルールは、非常にシンプルでした。 Critical アラートだけは絶対に対応する:  これだけは経営層からも厳しく言われていたため、何があっても必ず解決していました。 それ以外( High 以下)は、ほぼ手付かず:  チームのリソースが足りず、ほとんどのアラートは未解決(Open)のまま放置されていました。 【Phase 2】 新ルール導入の時代(2025年8月1日以降) 2025年8月1日、チームに経験豊富な新しいマネージャーが着任し、アラート対応プロセスを抜本的に見直しました。アラートの重要度とタイプに基づいた、明確なトリアージルールを導入したのです。 新しいルールは以下の通りです。 Critical と High は、最優先で必ず解決する。 Medium と Low は、タイプによって対応を分ける。 config タイプ: 新しく導入された自動修復スクリプトの対象となり、ほぼ自動で解決されるようになりました。 それ以外のタイプ( iam ,  network ,  anomaly ):  手動での調査が必要なため優先度が低く、多くが未解決のままとなりました。 データ構成のマトリクス 上記のシナリオを、ダミーデータ生成のための具体的な構成表にまとめます。 期間 条件 Policy Severity Policy Type Alert Status  の確率分布 Phase 1 (〜2025/7/31) Severity が critical critical すべて resolved : 95% open : 5%   Severity が critical でない high ,  medium ,  low ,  informational すべて open : 90% resolved : 10% Phase 2 (2025/8/1〜) Severity が high 以上 critical ,  high すべて resolved : 98% open : 2%   Severity が medium 以下 medium ,  low ,  informational config resolved : 90% open : 10%   Severity が medium 以下 medium ,  low ,  informational iam ,  network ,  anomaly open : 85% resolved : 15% Pythonによる実装 今回の可視化分析には、前回同様Pythonを使用しました。 主な利用ライブラリは、データの加工には pandas 、モデルの学習には scikit-learn を利用しました。 処理の概要 ここではコードの全ての詳細には触れませんが、実装の主要なステップと、特に工夫したポイントをご紹介します。 Step1: データの前処理 まず、 pandas を使って生のアラートデータをモデルが学習できる形式に変換します。 今回の分析の鍵である Alert Time は、そのままでは機械学習モデルが扱えません。そこで、各アラートの発生日時を、分析期間の開始日からの経過日数( days_since_start )という数値に変換しました。これにより、決定木は「開始から何日目以降」といった時間的な分岐点を見つけ出せるようになります。 また、 Policy Type や Policy Severity といったカテゴリカルなデータも、モデルが理解できる数値形式(ダミー変数)に変換しています。 Step2: モデルの学習 次に、 scikit-learn の DecisionTreeClassifier を使って、準備したデータから決定木モデルを学習させます。 この際、前章で触れたポリシーの重要度を分析に反映させるために、重要度ごとに重みづけを実装しています。 fit メソッドを呼び出す際に、 critical や high のアラートに計算した「重み」を渡すことで、実際のセキュリティー運用を考慮した分析を目指しています。 Step3: 2つのアウトプット生成 学習が完了したら、その結果出力します。 今回の実装では通常の決定木とは別で、決定木の結果を生成AIに解釈してもらうためのルールテキストをJSONで生成します。 決定木の分析もAIにさせてみようという試みで、決定木の生成されたテキストを実際に生成AIに入力してみるところまでしてみようと思います。 結果の表示 以下結果です。 決定木 前述のダミーデータで決定木をさせると、以下の結果が得られました。 画像なので文字が見にくくて申し訳ないのですが、見方はざっくり以下です。 分岐条件: ノードの一番上に書かれているテキスト(例:Severity is not ‘Critical’)は、データを分割するための質問(分岐条件)です。 分岐の方向:  この条件を満たす場合(真 / True)は左下のノードへ、満たさない場合(偽 / False)は右下のノードへとデータが振り分けられます。 samples :  そのノードに到達したアラートの総数を示しています。 value :   samples の内訳です。 class_names が  ['Open', 'Resolved']  の順であるため、例えば  value = [315, 35]  は、Openが315件、Resolvedが35件含まれていることを意味します。 class :  そのノードで最も多数派となったクラスを示します。つまり、そのノードに分類されたアラートが、最終的にどちらに予測されるかを表しています。ツリーの末端(葉ノード)では、これが最終的な結論となります。 以上を踏まえると、決定木の結果はざっくり以下の様に分析できます。 まず最初に、ポリシー重要度が”Critical”(Severity is not ‘Critical’がFalse)だとほとんどのアラートが”Resolved”となっていることがわかります。 ポリシー重要度が”Critical”じゃないアラートについては、次にAlert Timeで条件分岐が起こっています。 “Critical”以外のアラートでは、2025/7/31以前は”Open”,以降は”Resolved”となっております。 さらに2025/7/31のアラートについては、”High”アラートは”Resolved”で、”High”(とCritical)以外のアラートについては”Config”タイプのものは”Resolved”となっております。 どうでしょう、最初にご紹介したシナリオを再現できているのではないでしょうか。 ルールテキスト ルールテキストは以下の様に出力されます。 これをChatGPTのような生成AIに入力し、「結果を要約して」とお願いすると以下の結果を得ました。 以下は、提示された決定木の分析結果の要約です。 1. 「Severity(重大度)」で最初に分岐 「Critical(重大)」かどうかで大きく傾向が分かれる。 2. Severityが「Critical」でない場合 【2025-07-31以前のアラート】 ほとんどが「Open」状態(解決されていない)。 「Alert Time」が細かく過去日付に分岐されるが、どの分岐も「Open」割合が高い(約70~98%)。 【2025-07-31以降のアラート】 「Severity」が「High」でない(それ以外): 「Type」が「Config」かどうかで差が出る。 「Config」タイプは、ほぼ「Resolved」。 それ以外は「Open」割合が高い。 「Severity」が「High」: 「Alert Time(警告発生日)」が新しいほど「Resolved」(解決済み)率が極めて高い(約97~100%)。 3. Severityが「Critical」の場合 時期に関係なく「Resolved」が多い傾向。 2025-08-09以降は100%解決済み。 それより前でも90%以上が解決済み。 要点まとめ Severityが高い(Critical / High)、かつアラートの発生日が新しければ「Resolved」(解決済み)が多い。 Severityが低く、発生日が古いほど「Open」(未解決)が多い。 Configタイプは比較的解決されやすい。 アラート発生直後や重大度の高いものは、解決対応が迅速に実施されている傾向。 この結果から、 「重大なアラートほど早急に解決されるが、古い・低重大度のアラートは未解決が多い」 と言えるでしょう。運用上は、低重大度のアラートや古い未解決案件のフォローアップが課題となりそうです。   まとめ 今回は機械学習の手法の一つである「決定木」を使い、アラートの対応条件を分析する方法を試しました。 決定木分析の結果、「Severity(重大度)」「Type(アラート種別)」「Alert Time(発生日)」という三つの変数が分析結果に現れました。前回作成したバブルチャートでは主に一度に二要素までの関係しか視覚化できませんでしたが、決定木による分析により、3変数以上の要因の組み合わせの分析が可能になり、データの奥行きや傾向把握の可能性が広がったかと思います。 また、今回は決定木分析の結果をテキストに出力することで、結果の解釈と要約の部分で生成AIの活用の可能性を示せたかと思います。 今回紹介した手法は一例であり、データの性質や分析目的によっては、さらに多様な可視化や分析手法が存在します。今後もPrisma Cloudから取得できるデータのさらなる活用方法について、試行錯誤し結果を発信していければと考えております。 また、当社では、Prisma Cloudを利用して複数クラウド環境の設定状況を自動でチェックし、設定ミスやコンプライアンス違反、異常行動などのリスクを診断するCSPMソリューションを販売しております。ご興味のある方はお気軽にお問い合わせください。リンクはこちら↓ マルチクラウド設定診断サービス with CSPM| SCSK株式会社 マルチクラウド環境のセキュリティ設定リスクを手軽に確認可能なスポット診断サービスです。独自の診断レポートが、運用上の設定ミスや設計不備、クラウド環境の仕様変更などで発生し得る問題を可視化し、セキュリティインシデントの早期発見に役立ちます。 www.scsk.jp
アバター
本記事は TechHarmony Advent Calendar 2025 12/25付の記事です 。 クリスマスマーケットでプレッツェルにハマりました 皆さんどうもこんにちは。いとさんです。 メリークリスマス!……と言いたいところですが、実は今日12月25日は、世界で初めて「Webサイト」が公開された記念すべき日でもあるそうです。  1990年の12月25日、イギリスの計算機科学者ティム・バーナーズ=リーが、 世界で初めてのWebブラウザとサーバ間の通信に成功 しました。 つまり、今こうして皆さんがブログを読めている環境の「誕生日」とも言える日なんです。ネットの歴史が動いたのがクリスマス当日だったというのは、なんだかロマンチックですよね。 A short history of the Web home.cern A Little History of the World Wide Web www.w3.org さて今回はタイトルにもありますように Amazon Bedrock AgentCore & Strands Agentsを使用したAWS構築支援エージェントの構築方法についてご紹介したいと思います!  AWS環境運用支援エージェント構築手順 🎯 エージェントの目標 AWS公式ドキュメントを使用した、開発・運用チーム向けのAWS運用支援エージェントを構築します。 チャットボットとの違いとして、自ら考え、道具を使い、経験を蓄積する「AIエージェント」としての機能を備えています。 チャットボットと「AIエージェント」の違いとは? 一般的なチャットボットは、あらかじめ学習した知識の範囲内でユーザーの問いに「反応」するだけですが、今回構築するAIエージェントには決定的な3つの違いがあります。 「知識」ではなく「道具」を使いこなす (Tools) 通常のチャットボットは古い知識で答えることがありますが、このエージェントは MCPClient というツールを使い、外部にある「最新のAWS公式ドキュメント」へ自らアクセスして情報を取得します。 「会話」ではなく「経験」を記憶する (Memory) ブラウザを閉じれば忘れてしまうチャットボットと違い、 AgentCore Memory を通じて「過去のトラブル対応事例」などを長期記憶(LTM)として蓄積します。これにより、使えば使うほど使用する環境に詳しい状態へと成長します。 「思考」の方向を制御できる安全性 (Steering) 自由奔放なAIとは異なり、 LLMSteeringHandler によって「破壊的な操作の提案を禁止する」といった運用のガードレールを思考プロセスそのものに組み込んでいます。 このように、最新の公式情報(MCP)と経験(LTM)を、安全なルール(ステアリング)の上で統合して提供できるのが「AWS運用支援エージェント」なのです。 構成図 ⚠️構築前提条件  AWSアカウントを所有していること。 GitHubアカウントを所有していること。 AWSリージョンは バージニア北部 ( us-east-1 ) を利用します。(StrandsAgent使用可能リージョンのため) Administrator Access相当、もしくは以下のポリシーを適用したIAMユーザーで作業すること。 { "Version": "2012-10-17", "Statement": [ { "Sid": "BedrockAccess", "Effect": "Allow", "Action": [ "bedrock:InvokeModel", "bedrock:ListFoundationModels", "bedrock:ListCustomModels" ], "Resource": "*" }, { "Sid": "AgentCoreFullAccess", "Effect": "Allow", "Action": "bedrock-agentcore:*", "Resource": "*" }, { "Sid": "S3AndECRForDeployment", "Effect": "Allow", "Action": [ "s3:CreateBucket", "s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "ecr:GetAuthorizationToken", "ecr:CreateRepository", "ecr:DeleteRepository", "ecr:BatchCheckLayerAvailability", "ecr:PutImage" ], "Resource": "*" }, { "Sid": "CodeBuildAndIAMManagement", "Effect": "Allow", "Action": [ "codebuild:*", "iam:CreateServiceLinkedRole", "iam:CreateRole", "iam:PutRolePolicy", "iam:DeleteRole", "iam:DeleteRolePolicy" ], "Resource": "*" }, { "Sid": "CloudWatchAndBoto3Access", "Effect": "Allow", "Action": [ "logs:*", "cloudwatch:*" ], "Resource": "*" } ] } ステップ 1: 環境準備(CodeSpaceの利用) まず、開発環境としてGitHub CodeSpacesをセットアップします。 GitHubリポジトリの作成 GitHubで新しいプライベートリポジトリを作成します(例: aws-ops-agent )。 CodeSpacesの起動 作成したリポジトリの画面左上「 <> Code 」ボタンから「 Codespaces 」タブを開き、「 Create codespace on main 」を選択して起動します。ターミナルが自動的に開きます。 AWS CLIのインストールと認証 ターミナルで以下のコマンドを実行し、AWS CLIをインストールします。 # ダウンロード curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" # 解凍 unzip awscliv2.zip # インストール sudo ./aws/install AWSアカウントへの認証を設定します(ブラウザでの検証コード入力が必要です) aws login --remote デフォルトリージョンが us-east-1 でいいか聞かれたら Enter を押します。 ステップ 2: AgentCoreメモリの作成(短期・エピソード記憶の準備) エージェントが会話履歴と社内ナレッジを記憶するためのAgentCore Memoryを作成します。 AgentCoreサービスへアクセス AWSマネジメントコンソールで「AgentCore」を検索してアクセスします。 左メニューから「 メモリー 」>「 メモリを作成 」をクリックします。 メモリ設定 すべてデフォルトのまま「 メモリを作成 」をクリックします。 エピソード記憶の有効化 (LTM) 作成したメモリの詳細画面を開き、「 編集 」をクリックします。 組み込み戦略の「 Episodes 」にチェックを入れ、「 変更を保存 」します。 重要 : 作成されたメモリの Memory ID と、エピソード記憶戦略の 戦略ID を控えておきます。(エージェント本体の作成の際に使用します) ステップ 3: エージェント本体の作成( backend.py ) エージェントの処理ロジックと、必要なツール、記憶、ステアリングを設定したAPIサーバーのコードを作成します。 ディレクトリとファイルの作成 以下のコードをコピーし、 memory_XXXXX-XXXXXXXXXX (Memory ID) と episodic_builtin_XXXXX-XXXXXXXXXX (戦略ID) のプレースホルダーを、ステップ2で控えた実際のIDに置き換えて貼り付けます。 mkdir agentcore cd agentcore touch backend.py backend.py にコードを記述 # 必要なライブラリをインポート from strands import Agent from strands.tools.mcp import MCPClient from strands.models import BedrockModel from strands.experimental.steering import LLMSteeringHandler # ステアリング用 from bedrock_agentcore.runtime import BedrockAgentCoreApp from mcp.client.streamable_http import streamablehttp_client from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig, RetrievalConfig from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager # 1. モデル設定 model = BedrockModel( model_id= "us.amazon.nova-2-lite-v1:0" , max_tokens= 4096 ) # 2. メモリー設定(STM/LTM) memory_config = AgentCoreMemoryConfig( memory_id= "memory_XXXXX-XXXXXXXXXX" , # ここをあなたのMemory IDに置き換える session_id= "aws_ops_handson" , actor_id= "ops_engineer" , retrieval_config={ # エピソード記憶(LTM)の検索設定 "/strategies/episodic_builtin_XXXXX-XXXXXXXXXX/actors/ops_engineer/sessions/aws_ops_handson" : RetrievalConfig() # ここをあなたの戦略IDに置き換える } ) session_manager = AgentCoreMemorySessionManager( agentcore_memory_config=memory_config ) # 3. ツール設定 (MCPClient) mcp_client = MCPClient( lambda : streamablehttp_client( "https://knowledge-mcp.global.api.aws" ) ) # 4. ステアリング設定(ポリシーの強制) # 例: 破壊的な操作の推奨を禁止し、公式ドキュメント参照を強制 handler = LLMSteeringHandler( system_prompt= "設定変更やリソース削除につながる操作は絶対に推奨しないでください。また、回答には必ず公式ドキュメントの参照URLを含めるようにしてください。" ) # AgentCoreランタイム用のAPIサーバーを作成 app = BedrockAgentCoreApp() # エージェント呼び出し関数を、APIサーバーのエントリーポイントに設定 @app.entrypoint async def invoke_agent ( payload, context ): # リクエストごとにエージェントを作成し、設定を適用 agent = Agent( model=model, tools=[mcp_client], # MCPClientをツールとして組み込む session_manager=session_manager, # 記憶(STM/LTM)を組み込む hooks=[handler] # ステアリング(ポリシー)を組み込む ) # エージェントをストリーミング呼び出し stream = agent.stream_async( payload.get( "prompt" ) ) # ストリーミングレスポンスをフロントに返却 async for event in stream: yield event # APIサーバーを起動 app.run() 依存関係のファイル作成 agentcore/requirements.txt を作成し、必要なパッケージを記述します。 touch requirements.txt requirements.txt に以下をコピー&ペーストします。 strands-agents strands-agents-tools strands-agents[otel] bedrock-agentcore bedrock-agentcore[strands-agents] 依存関係のファイル(requirements.txt)の目的 このファイルの最大の目的は、「環境の標準化」です。 1. 必要なライブラリの明示: そのプログラムを動かすために、どの外部ライブラリ(strands, boto3, requestsなど)が必要なのかをリストアップします。 2. バージョンの固定: 「どのバージョンの道具を使うか」を指定します(例:strands==0.1.0)。これにより、ライブラリが勝手にアップデートされてプログラムが壊れるのを防ぎます。 3. セットアップの自動化 : コマンド一つ(pip install -r requirements.txt)で、必要なものを一括インストールできるようにします。 ファイルを作成する具体的な理由 なぜわざわざファイルを作る必要があるのか、3つの重要な理由があります。 1. 開発環境と本番環境(AWS)の「ズレ」をなくすため 自分のPC(CodeSpaces)で動いても、AWS上のサーバーにデプロイした時に「ライブラリが入っていない」という理由でエラーになるのを防ぎます。 理由: デプロイ時にAWS側がこのファイルを読み込み、必要な環境を自動で構築するためです。 2. 多人数・多環境での共同開発をスムーズにするため 他の人がコードをGitHubからダウンロードした際、どのライブラリをインストールすればいいか迷わずに済みます。 理由: 「READMEに手書きでライブラリ名を並べる」といった手動作業を排除し、ミスを減らすためです。 3. デプロイツール(AgentCoreなど)の仕様 今回使用している agentcore launch などのツールは、内部でこのファイルを自動的に探しに行きます。  理由: クラウド上にエージェントを構築する際、プログラムを動かすための「コンテナ」の中に、自動でライブラリをインストールする仕組みになっているからです。 *家具の組み立てキットを例にしてみましょう 依存関係のファイルは、家具の「部品リスト(材料表)」に例えられます。 プログラム (backend.py): 家具の組み立て説明書。 依存関係ファイル (requirements.txt): 「ネジ(M4サイズ)×10個」「天板×1枚」といった材料リスト。 もし材料リストがなければ、説明書だけあっても「何を用意すればいいか」分からず、作業が進みませんよね。 この「材料リスト」があるからこそ、クラウドという遠く離れた場所でも正確にプログラムを動かせるのです。 ステップ 4: AgentCoreランタイムへのデプロイ 作成したエージェントコードをAWS上で動作するAPIサーバーとしてデプロイします。 AgentCoreスターターキットのインストール pip install bedrock-agentcore-starter-toolkit==0.2.2 Amazon Bedrock AgentCore スターターキットとは Amazon Bedrock AgentCore スターターキットは、AWS上で高性能なAIエージェントを「安全に」「速く」「大規模に」構築・運用するために必要なすべてが詰まった、開発者向けの公式ツールセット(CLIおよびライブラリ)です。 これを使用することで、インフラの知識が少なくても、数行のコードでプロ仕様のエージェントをクラウド上に公開できます。 スターターキットに含まれる主な内容 このキットは主に以下の3つの要素で構成されています。 要素 内容と役割 AgentCore CLI agentcore launch コマンドなどで、AWS上のサーバー(Runtime)やメモリ、セキュリティ設定を自動で構築・デプロイします。Strands Agents SDK エージェントの「脳」を動かすためのオープンソースSDK。Claude 3.5やNovaなどのLLMを使い、ツール呼び出しや推論を制御します。 サンプルテンプレート backend.py や requirements.txt の雛形。これを書き換えるだけで、自分の用途に合わせたエージェントがすぐに作れます。 通常と使用した場合での比較 「通常(すべて自前)の開発」と「AgentCoreスターターキット + Strands」を使用した場合の比較解説します。 一言でいうと、通常の開発は「材料集めとキッチン作りから始める料理」ですが、スターターキットは「最新設備の整った厨房で、レシピ通りに作る料理」のような違いがあります。 ① 開発スピードと手間の比較 通常の開発では、AIの「脳」を作る前に「箱(インフラ)」を作る作業が膨大です。 項目 通常の開発(自前) スターターキット使用 環境構築 Docker、サーバー、権限設定を個別に構築。 agentcore launch 一発で最適環境が完成。 ライブラリ管理 各ツールの互換性を自分で検証・修正。 公式SDKが最初から最適化されている。 デプロイ 数日~数週間(インフラ知識が必要)。 数分~数時間 (Pythonの知識だけでOK)。 ② 運用性能と安全性の比較 個人開発外で使う場合、この「運用面」の差がリスクの違いになります 項目 通常の開発(自前) スターターキット使用 実行時間制限 Lambda等だと 15 分 で強制終了。 最大 8 時間 の長時間処理が可能。 セッション分離 ユーザー間のデータ混入対策を自前実装。 セッションごとに 完全隔離 された安全な環境。 監視(デバッグ) ログを自前で集計・解析。 思考プロセスが自動で可視化される。 認証管理 APIキーの管理を暗号化して自作。 AgentCore Identity でセキュアに一元管理。 ③ 機能(賢さ)の拡張性の比較 エージェントに「新しい道具(ツール)」を持たせたい時の柔軟性が違います。 ・通常の開発: 新しいツールを増やすたびに、プロンプトを調整し、API接続コードを書き、例外処理を手動で追加します。 ・スターターキット: 世界標準の MCP (Model Context Protocol) に対応。Notion、Slack、GitHubなどの公式プラグインを、コードをほぼ書かずに「差し込むだけ」でエージェントが使いこなせます。 通常開発が向いている人: ・AWSを使わない、または完全に独自のサーバー構成にこだわりがある。 ・AIの推論ロジック自体を一から研究・開発したい。 スターターキットが向いている人: ・「動くもの」を最速で作って業務に導入したい。 ・セキュリティやスケーラビリティ(大人数での利用)を重視する。 ・最新のClaude 3.5やAmazon Novaなどの高性能モデルを、最高の環境で使いこなしたい。 設定ファイルの自動生成 以下のコマンドを実行し、設定プロセスを開始します。 agentcore configure 対話形式で質問されますが、 以下以外はすべて Enter で OK です。 Entrypoint : backend.py と入力。 Existing memory resources found :ステップ2で作成したメモリーの番号(例: [1] など)を入力。 ランタイムのデプロイ 以下のコマンドで、ECR、CodeBuild、AgentCoreランタイムなどのAWSリソースが自動で作成され、デプロイが開始されます。 agentcore launch デプロイ完了まで数分かかります。完了後、 Runtime ARN が出力されるので、これを控えておきます。 このとき認証エラーになる場合があります。原因として以下の3点が挙げられます。 ・認証の有効期限切れ : 手順1から手順4までに時間が空きすぎ、一時的なログインセッションが切れてしまった場合。 ・環境の初期化 : GitHub Codespacesを一度停止・再起動し、ログイン状態(キャッシュ)が消えてしまった場合。 ・ブラウザログインの失敗 : aws login --remote を実行した際、ブラウザ側での承認が正しく完了していなかった場合。           このような場合は aws login –remote こちらの手順を再度行いましょう。 aws configureでアクセスキー・シークレットアクセスキーを使用し紐づける事も可能ですが、セキュアな構成にしたい場合は推奨しません。    ステップ 5: 動作確認用フロントエンドの作成と実行 デプロイされたAPIサーバーをテストするための簡単なWebインターフェース(Streamlitを利用)を作成します。 元の階層に戻る cd ../ frontend.py の作成 frontend.py を作成し、以下のコードをコピー&ペーストします。 touch frontend.py frontend.py の内容: # 必要なライブラリをインポート import os, boto3, json import streamlit as st # サイドバーを描画 with st.sidebar: # デプロイ後に得られたAgentCoreランタイムのARNを入力 agent_runtime_arn = st.text_input( "AgentCoreランタイムのARN" ) # タイトルを描画 st.title( "AWS環境運用支援エージェント" ) st.write( "Strands AgentsがMCP(AWSドキュメント)とLTM(社内ナレッジ)を使って運用を支援します!" ) # チャットボックスを描画 if prompt := st.chat_input( "質問を入力してください(例:IAMポリシーの最小権限原則のベストプラクティスを教えて)" ): # ユーザーのプロンプトを表示 with st.chat_message( "user" ): st.markdown(prompt) # エージェントの回答を表示 with st.chat_message( "assistant" ): try : # AgentCoreランタイムを呼び出し agentcore = boto3.client( 'bedrock-agentcore' ) payload = json.dumps({ "prompt" : prompt}) response = agentcore.invoke_agent_runtime( agentRuntimeArn=agent_runtime_arn, payload=payload.encode() ) # ストリーミングレスポンスの処理 container = st.container() text_holder = container.empty() buffer = "" for line in response[ "response" ].iter_lines(): if line and line.decode( "utf-8" ).startswith( "data: " ): data = line.decode( "utf-8" )[ 6 :] # 文字列コンテンツの場合は無視 if data.startswith( '"' ) or data.startswith( "'" ): continue # 読み込んだ行をJSONに変換 event = json.loads(data) # ツール利用を検出 if "event" in event and "contentBlockStart" in event[ "event" ]: if "toolUse" in event[ "event" ][ "contentBlockStart" ].get( "start" , {}): # 現在のテキストを確定 if buffer: text_holder.markdown(buffer) buffer = "" # ツールステータスを表示 tool_name = event[ "event" ][ "contentBlockStart" ][ "start" ][ "toolUse" ].get( "name" , "unknown" ) container.info( f"🔍 {tool_name} ツールを利用しています" ) text_holder = container.empty() # テキストコンテンツを検出 if "data" in event and isinstance (event[ "data" ], str ): buffer += event[ "data" ] text_holder.markdown(buffer) elif "event" in event and "contentBlockDelta" in event[ "event" ]: buffer += event[ "event" ][ "contentBlockDelta" ][ "delta" ].get( "text" , "" ) text_holder.markdown(buffer) # 最後に残ったテキストを表示 text_holder.markdown(buffer) except Exception as e: st.error( f"エラーが発生しました: {e} " ) Streamlitの実行 StreamlitはPythonだけでデータ分析アプリやAIアプリのフロントエンド(画面)を爆速で構築できるオープンソースのフレームワークです。 今回はAIエージェントの仕様を簡単に確認・実行するために使用します。(本番実装向きではありません) pip install streamlit streamlit run frontend.py CodeSpacesのポップアップに従ってブラウザでアプリを開くか、ターミナルに表示されたURLにアクセスします。 動作確認 アプリのサイドバーに、ステップ4で控えた Runtime ARN を貼り付けます。 以下の質問をして、 MCPClientの利用 (AWSドキュメント検索)と ステアリング が効いているか確認します。 https://blog.usize-tech.com/contents/uploads/2025/12/a1f4b32fda82bf2c124175a5c026b99e.mp4 ちゃんと危険と記述してくれていますね。 ステップ 6: エピソード記憶(LTM)へのナレッジの投入 デプロイ後、LTMに社内固有のナレッジを「記憶」させることで、提案されたLTMの役割(過去の障害対応検索)を実現します。 LTMにナレッジを投入 Streamlitのチャットで、具体的な過去の事例をエージェントに教え込みます。 例: 「先月、NATゲートウェイの誤った設定でコストが急増した。解決策は、夜間はAWS Lambdaで停止する仕組みを導入したことだ。」 LTMの検索テスト ナレッジ投入後、LTMが検索される質問をします。 例: 「私が以前話したNATゲートウェイのコスト問題について、解決策はなんだったか?」 (LTMが有効になっていれば、過去のエピソードを踏まえた回答が得られるはずです。) エピソード記憶がちゃんと実装できている事も確認できましたね。 これで、AWSの公式ドキュメントとナレッジの両方を活用し、ポリシーで制御された「AWS環境運用支援エージェント」が完成します。   所感・まとめ 開発経験はまだ浅いですが、メモリー機能を備えた簡易的なエージェントを構築することができました。 難しい言語での作業が少なく約1時間と短い時間で構築でき、今後の開発速度効率化にとても期待できました。 今後は環境操作などの機能を追加して、完全自動化を実現する「ローカル版Kiro」のような仕組みを作ってみたり、 これまではPythonで実装してきましたが、アップデートによりTypeScriptでも実装可能になったため、複数言語での開発にも挑戦してみたいなーって思います。 年末に向けてPCもお部屋も大掃除しなくてはです。 それでは皆さん良いお年を〜
アバター
本記事は TechHarmony Advent Calendar 2025 12/25付の記事です 。 こんにちは、SCSK木澤です。 12/1から続けてきた今年のアドベントカレンダーも、ついに今日でラストですね。 本日発信される記事で、合計25日間で33記事が発信されたかと思います。 年末の忙しいさなか、お読み頂いた皆様、発信いただいた寄稿者の方々ありがとうございました。 さて今年のアドベントカレンダー、私は他のテーマの検証が完了しませんでしたので、今年はAmazon SESの小ネタを発信したい思います。 メール誤送信事故の防止 私は10年ほど前まで、色々なお客様のシステム開発や保守を担当していました。 その中で、メール誤送信の事故を見かけたことがありました(大量ではないですが) 結構ありがちなのが、開発中のシステムやリリース後においては開発/検証環境など、本番環境以外からの誤送信で、テスト実施の際に不必要な宛先にメールが送信されてしまった、というケースです。 とはいえ、本番環境以外においてもシステムの動作検証・テストとして全くメールを出さない訳にもいかないことがあります。 そうした場合、メール中継をするMTA(postfix等)において、不必要な宛先への送信をブロックする設定を行うことが事故防止のため望ましく、そのような実装をよく行っていました。   Amazon SESでの実装方法 AWSにおいてはAmazon SESを送信用のMTAとして用いることがベストプラクティスとなるかと思います。 Amazon SESにおいて送信の制限には、送信承認ポリシーによって行うことができます。 Amazon SESの送信承認ポリシーの作成 - Amazon Simple Email Service アイデンティティ所有者が Amazon SES の送信承認ポリシーを作成する方法について説明します。 docs.aws.amazon.com 設定手順 Amazon SESコンソール、設定内-IDをクリックし、検証済み(であることが前提ですが)IDの設定に入ります。 「承認」タブより承認ポリシーの作成に入ります。 今回はカスタムポリシーから作成しました。 ポリシードキュメントは以下のようにします(AWSアカウントはマスクしています) この設定では、 test@scsk.jp 宛のメールのみ送信を許可するように設定しています。 { "Version": "2012-10-17", "Statement": [ { "Sid": "stmt1766585448102", "Effect": "Deny", "Principal": "*", "Action": [ "ses:SendEmail", "ses:SendRawEmail" ], "Resource": "arn:aws:ses:ap-northeast-1:123456789012:identity/scsk.jp", "Condition": { "ForAnyValue:StringNotEquals": { "ses:Recipients": "test@scsk.jp" } } } ] } 本ポリシーを設定し、適用します。 テスト ID画面上の「テストEメールの送信」をクリックします。 許可されていない宛先を指定すると、下記のようにアクセス権がないというエラーが発生するはずです。   まとめ 今回はAmazon SESの小ネタをご紹介させていただきました。 開発/検証環境のメール環境においては、転ばぬ先の杖ということで必ず設定しておきましょう。 私は今年最後の投稿になるかもしれませんが、来年もTechHarmonyエンジニアブログ共、引き続きご愛顧の程よろしくお願いします。 皆様の2026年が良い年になりますように。今年もありがとうございました。
アバター
こんにちは。SCSKの末本です。 本記事では、AWSで構築しているシステムの保守運用をする中で発生した事象と、その解決策についてご紹介します。   発生した事象 今回のシステムは、Amazon Elastic Container Service(以下 ECS)を利用しており、1タスクで複数のコンテナを構成する環境です。 発生した事象は、 AWS_ECS_TASK_PATCHING_RETIREMENT によって ECS のタスクが生まれ変わる際、クライアントからのアクセスに対して一時的に HTTP 503 エラーが返されたというものです。 この事象は、アーキテクチャの本質的な理解と、ECS タスク定義内のコンテナ依存関係が鍵を握っていました。   AWS_ECS_TASK_PATCHING_RETIREMENT  とは AWS がメンテナンスやセキュリティアップデートを行うために、実行中のタスクを強制的に停止し、新しいタスクを起動するプロセス   なぜ 503エラー が発生したのか? 今回のシステム構成と、問題についてご説明します。 システム構成(1タスク内) tomcat コンテナ:  アプリケーションを実行するバックエンドとして動作するコンテナ web(Apache)コンテナ:  フロントエンドとして動作し、ロードバランサーからのリクエストを受け取り、tomcat コンテナへ転送するコンテナ log コンテナ:  ログ収集および転送を行う サイドカーコンテナ                                  サイドカーコンテナ とは メインのアプリケーションコンテナ(ここでは tomcat・web)の補助機能を提供するコンテナ   AWSによる ECSパッチ適用時の流れ AWS が ECSのパッチ適用をするため、ECSタスクの入替(新しいタスクを起動し、古いタスクを停止)が行われます。 この流れの中で、タスクがリクエストを受け付ける準備が整う前に、クライアントからアクセスされてしまうという問題が発生しました。 新しいタスクが起動を開始する。 web(Apache)コンテナ、log コンテナが比較的早く起動する。 (この時点では、バックエンドの tomcat はまだアプリケーションレベルでの準備ができていない。) ヘルスチェックを有効化したコンテナ(web・log)の状態をもって、タスク全体が早期に起動完了(RUNNING)と見なされる。   今回、tomcat コンテナにヘルスチェックが設定されていませんでした。 そのため tomcat のアプリケーション初期化時間にかかわらず、ヘルスチェックを有効化したコンテナ(web・Log)のヘルスステータスがすべて Healthy であることで、タスク全体が起動完了したと見なされました。                              ご参考: Amazon ECS タスクライフサイクル – Amazon Elastic Container Service タスクが ターゲットグループに登録 され、クライアントからアクセスが可能な状態になる。 クライアントからのリクエストが web(Apache)に到達するが、バックエンドの tomcat コンテナはまだ起動途中。 結果として Apache はバックエンドに接続できず、クライアントに HTTP 503 Service Unavailable を返す。   今回の問題 今回の問題は、ロードバランサーのヘルスチェック(Apacheのポートをチェック)がOKでも、タスク内の コンテナ間の依存関係 が考慮されていなかったため、 サービスが利用可能な状態 になる前にアクセスされてしまった ことです。   【回避策】ECSタスク定義の依存関係(dependsOn)の活用 この問題を解決するために、 ECSタスク定義内の dependsOn プロパティと コンテナヘルスチェック を組み合わせて設定 しました。 これにより、コンテナ間の起動順序と、次のコンテナが起動を開始するための 条件 を定義できます。 20190731_AmazonECS_DeepDive_AWSBlackBelt   dependsOn パラメータ の意味については以下の通りです。 設定値(条件) 意味 START 依存するコンテナの起動開始後、実行 COMPLETE 依存コンテナの実行が完了(終了)後、実行 SUCCESS 依存コンテナの実行が正常終了後、実行(exit code:0) HEALTHY 依存コンテナに定義した healthcheck に合格後、実行   修正後のコンテナ依存関係の設計 フロントエンド(web)が起動する前にバックエンド(tomcat)が完全に起動 することで、リクエスト処理可能な状態とする。 log コンテナがログファイルを正しく転送できるよう、 各コンテナ起動後に log コンテナを起動 させ、 転送対象のファイルが存在しないというエラーの発生を防ぐ。   コンテナ 依存設定 設定値(条件) 内容 tomcat (なし) – 依存関係なしのため、まず最初に起動する。 web(Apache) tomcat HEALTHY tomcat がアプリケーションレベルで完全に起動し、 サービス可能になるまで待機する。 log web(Apache) HEALTHY web コンテナのプロセス起動が確認されてから、 ログ転送を開始する。   設定内容 単に START(コンテナプロセスが起動)を待つだけでは、アプリケーションの初期化時間などを考慮できません。 この問題を解決するために、tomcat コンテナのタスク定義内に コンテナヘルスチェック を設定し、web(Apache)コンテナおよび log コンテナの dependsOn 設定で、HEALTHY 条件を指定しました。   // ECSタスク定義(コンテナ定義の抜粋イメージ) "containerDefinitions": [ { "name": "tomcat", // ... 他の設定 // Tomcatがサービス可能であることを確認するコンテナヘルスチェック "healthCheck": { "command": [ "CMD-SHELL", "curl -f http://localhost:8080/status || exit 1" ], "interval": 30, // 30秒ごとにチェック "retries": 3, // 3回失敗したらUNHEALTHY "startPeriod": 10, // 起動直後の10秒間はヘルスチェックの失敗を無視(猶予期間) "timeout": 6 // タイムアウトは6秒 } }, { "name": "web", // ... 他の設定 // webコンテナはtomcatがHEALTHY(サービス可能)になるまで起動を待機 "dependsOn": [ { "containerName": "tomcat", "condition": "HEALTHY" // ここが重要! } ] }, { "name": "log", // ... 他の設定 // logコンテナはwebがHEALTHY(サービス可能)になるまで起動を待機 "dependsOn": [ { "containerName": "web", "condition": "HEALTHY" // ここが重要! } ] } ] 上記の設定により、 今回の事象は解決し、ECSタスクのパッチ適用時に503エラーが出なくなりました。   今回の対応から学んだ3つの設計ポイント 1. ALBヘルスチェック ≠ サービス利用可能性 ロードバランサーのヘルスチェックは、 外部から特定のポートが応答しているか を確認するもので、タスクへのトラフィックを流すかどうかを判断します。 しかしマルチコンテナタスクでは、「ポートが開いている」ことと「アプリケーションがリクエストを処理する準備ができている」ことは同義ではありません。 ALBヘルスチェック : タスクへのトラフィック受付可否 を判断 コンテナヘルスチェック : タスク内のコンポーネントのサービス提供可否 を判断   2. マルチコンテナ設計における依存関係の明示 ECSタスクの設計において、 各コンテナの起動順序 を意識して設計しなければ、今回のようなコンポーネント間の疎通エラーを招きます。 以下のように、役割に応じて依存関係を定義することが、システムの信頼性を高めると学びました。 フロントエンドは、バックエンドの HEALTHY 状態を待つ。 サイドカー コンテナは、 メインコンテナ の START または HEALTHY 状態に依存する。   3. startPeriod(起動猶予期間)の活用 Tomcat などの Javaアプリケーションは、プロセス起動からリクエスト処理可能になるまでに数十秒かかることがあります。 dependsOn パラメータで HEALTHY を使う場合、tomcat コンテナのヘルスチェック定義に startPeriod(起動猶予期間)を設定することで、コンテナが起動直後の不安定な状態で即座に失敗と見なされるのを防ぐことができます。   【重要】今回の対応によるデメリット 今回の対応で可用性を高めることができましたが、デメリットとして タスク全体の起動時間の増加 があげられます。 本システムでは、開発環境で検証した上で、起動時間の増加を許容する方針にしました。   まとめ AWS ECSのタスク入れ替え時の503エラーは、一見するとシンプルなネットワークエラーに見えますが、その裏には「複数コンテナの非同期起動」という本質的な課題が隠れていました。 本記事でご紹介した設定内容は、あくまで 今回のシステムに おける最適な設計例 です。 コンテナの依存関係は、アプリケーションの構成によって大きく異なります。ご自身のシステムに適用される際は、 各コンテナの役割 と コンテナ間の通信タイミング を考慮し、最も適切なヘルスチェック、依存関係( START、HEALTHY など)およびパラメータを設定いただければと思います。 最後までお読みいただきありがとうございました!
アバター
LifeKeeperの『困った』を『できた!』に変える!サポート事例から学ぶトラブルシューティング&再発防止策 こんにちは、SCSKの前田です。 いつも TechHarmony をご覧いただきありがとうございます。 LifeKeeperを導入し、システムの高可用性を実現する上で、アプリケーションリソースの保護は非常に重要な要素です。しかし、アプリケーションARKは、その特性ゆえに、設定のわずかな見落とし、予期せぬ通信障害、そしてバージョンアップによる仕様変更など、多岐にわたる要因で『困った』事態に直面することがありますよね。 本連載企画「LifeKeeper の『困った』を『できた!』に変える!サポート事例から学ぶトラブルシューティング&再発防止策」では、まさにそんな「なぜかアプリケーションが切り替わらない」「エラーが出て起動できない」といった、LifeKeeper運用の現場で実際に発生した問い合わせ事例を基に、トラブルの原因、究明プロセス、そして何よりも『再発防止策』に焦点を当てて深掘りしていきます。今回の第三弾では、アプリケーションARK特有の「落とし穴」に焦点を当て、その解決と再発防止の鍵を探ります。 はじめに LifeKeeperの心臓部とも言えるアプリケーションリソースの保護は、システムの高可用性を実現する上で不可欠です。専用ARKから汎用ARKまで多岐にわたりますが、その複雑さゆえに設定ミスや連携アプリケーション側の要因で予期せぬトラブルが発生しがちです。 本記事では、 LB Health Check、MySQL、IIS、そしてGeneric ARKを用いたJP1/Baseのリソース構築・運用で実際に発生したサポート事例 を元に、アプリケーションARK特有の「落とし穴」を深掘りします。 通信障害、バージョン間の仕様変更、ホスト名の制約、スクリプトの依存性など、様々な角度から原因と対策を学ぶことで、アプリケーションの安定稼働と再発防止につなげるヒントを提供します。 その他の連載企画は以下のリンクからどうぞ! 【リソース起動・フェイルオーバー失敗の深層 #1】EC2リソースが起動しない!クラウド連携の盲点とデバッグ術 – TechHarmony 【リソース起動・フェイルオーバー失敗の深層 #2】ファイルシステムの思わぬ落とし穴:エラーコードから原因を読み解く – TechHarmony 今回の「困った!」事例 ケース1:ロードバランサーとの連携トラブル – ヘルスプローブの落とし穴 lbhcリソース作成中にエラー(140251)が発生する、LB Health Checkリソース作成時のエラーについて 事象の概要:  LifeKeeper for Windows環境でLB Health Checkリソース作成・起動時にエラーが発生し、リソース作成に失敗。 ロードバランサーからの正常性プローブがLifeKeeper側で受け取れない状況 に陥りました。 発生時の状況:  LBHCリソース作成後のリソース起動でロードバランサーとの通信がタイムアウト(エラーコード140251)しました。Azure内部ロードバランサーからの通信は許可済みと認識されていたにも関わらず、プローブが到達しませんでした。また、LifeKeeperのホスト名に小文字が混在している環境でした。 原因究明のプロセス:  初期には HC_TIMEOUT の調整やデバッグログ取得を試みましたが、デバッグログからも最終的にプローブを受け取れていない状況が判明。さらに、ホスト名に小文字が含まれることがLifeKeeper for Windowsの既知の不具合であり、リソース情報取得エラーの原因であることが明らかになりました。 判明した根本原因:  1. ロードバランサーからの正常性プローブがLifeKeeper側で受け取れていない通信経路上の問題、および 2. LifeKeeper for Windowsのホスト名が小文字混在であったことによるリソース情報取得エラーが複合的に発生していました。 ケース2:データベースARK特有の設定ミス – MySQLのパスワードとログパスの罠 MySQLリソース作成時のエラーについて 事象の概要:   MySQLリソース作成時 に「Invalid parameter value」エラーが発生し、さらにその後log-binのパスに関するエラーが発生。 リソース作成が成功しません でした。 発生時の状況:   my.cnf 内のMySQLユーザーパスワードに「$」が含まれており、ダブルクォーテーションで囲んでいましたがエラーが解消されませんでした。また、 log-bin パラメータがファイル名のみで指定されていました。既存システム(LifeKeeper v9.3.2)ではファイル名のみで動作していましたが、新システム(LifeKeeper v9.9.0)ではエラーとなりました。 原因究明のプロセス:  サポートとのやり取りにより、パスワードの特殊文字(「$」)を正しく扱うためにはシングルクォーテーションで括る必要があること、および log-bin のパスはLifeKeeper for Linux v9.6以降ではフルパス指定が必須になったことが判明しました。 判明した根本原因:  1. MySQLユーザーパスワードに特殊文字(「$」)が含まれる場合の記述ルール(シングルクォーテーションで括る)の不理解、および 2. LifeKeeper for Linuxのバージョンアップに伴うMySQL ARKの仕様変更( log-bin のフルパス指定必須化)への追従不足が原因でした。 ケース3:WebサーバーARKの特性理解不足 – IISの拡張前処理スクリプト IISリソース拡張前処理スクリプト実行時のエラーメッセージについて 事象の概要:  IISリソース作成時の拡張前処理スクリプト実行中にエラーメッセージが発生しましたが、 その後の拡張はエラーなく実行され、IISリソースの作成自体は完了 しました。 発生時の状況:  既にボリュームリソースが拡張済みであったにも関わらず、IISリソースの拡張前処理スクリプト内で再度ボリューム拡張を試みていました。 原因究明のプロセス:  サポートの確認により、このエラーは既にボリュームリソースが拡張されているため発生する、運用上問題のない情報メッセージであることが確認されました。 判明した根本原因:  拡張前処理スクリプトが、既に拡張済みのボリュームに対し再度拡張処理を行おうとしたため発生する、意図されたエラーメッセージであり、IISリソースの拡張自体には影響がありませんでした。 ケース4:Generic ARK利用時の注意点 – スクリプト依存とサポート範囲 Generic ARK リソース (JP1/Base)における quickCheck の仕組みについて 事象の概要:  Generic ARK(JP1/Base)における quickCheck の動作について問い合わせ。ネットワークメンテナンスに伴う通信断の影響で、 quickCheck が失敗しOSが再起動しました。 発生時の状況:   quickCheck スクリプト内で名前解決を使用しており、通信断により名前解決ができず quickCheck が失敗したと推測されました。 原因究明のプロセス:  問い合わせたGeneric ARKがサポート対象外であることが判明し、お客様が作成したスクリプトの内容に依存するため、LifeKeeper製品としての詳細な案内はできませんでした。 判明した根本原因:  サポート対象外のGeneric ARKを利用しており、スクリプトの内容(名前解決への依存)が起因する問題であったため、LifeKeeper製品としての根本原因は特定できず、スクリプト作成者による詳細確認が必要でした。 「再発させない!」ための対応策と学び 具体的な解決策: LB Health Checkリソース関連:  ネットワークセキュリティグループ(NSG)やファイアウォールで、ILBのIPアドレスからのプローブ用ポートへの通信を確実に許可する。 HC_TIMEOUT の設定値を環境に合わせて調整する。 LifeKeeper for Windowsを導入するサーバーの ホスト名は、すべて大文字で設定 する 。 MySQLリソース関連:  MySQLユーザーのパスワードに 特殊文字(「$」など) を使用する場合は、 必ず 「’」(シングルクォーテーション)で文字列全体を括って 設定する。 my.cnf 内の datadir 、 log-bin 、 log-tc パラメータは、LifeKeeper for Linux v9.6以降では共有ディスク上の 絶対パス(フルパス) で指定する。 IISリソース関連:  IISリソース作成時の拡張前処理スクリプト実行中に、既に拡張済みのボリュームに関するエラーメッセージが表示された場合、その後の IISリソース拡張が 成功 していれば、運用上問題ないメッセージとして認識 する。 Generic ARK関連:  利用するアプリケーションがLifeKeeperのサポート対象であることを事前に確認する。Generic ARKで利用するスクリプトは、 作成者が動作内容(特に外部依存性) を詳細に把握し、デバッグログ出力やエラーハンドリングを適切に実装する。 再発防止策(チェックリスト形式):   ARK のサポート範囲と互換性確認: 専用ARK利用時もGeneric ARK利用時も、対象アプリケーションとLifeKeeperのバージョンがサポートされているか、 サポートマトリクス で事前に確認する。   OS/LifeKeeper バージョンアップ時の仕様変更確認:  OSやLifeKeeper本体のバージョンアップを行う際は、リリースノートやドキュメントを詳細に確認し、 ARK固有の仕様変更(例: MySQL ARKの log-bin パス指定) がないか確認する。   ARK 固有の設定ファイル(例: my.cnf)の厳格な管理:  アプリケーションARKが参照する設定ファイルは、LifeKeeperの要件(例: パスワードの記述ルール、パスの絶対指定)に沿って適切に設定・管理する。   通信経路とセキュリティ設定の徹底確認:  ロードバランサーのヘルスプローブやアプリケーション間の通信など、LifeKeeperが監視・利用する通信経路について、 ファイアウォールやネットワークセキュリティグループの設定を構築前に徹底的に確認 し、必要なポートが解放されていることを保証する。   命名規則(ホスト名・ユーザー名など)の遵守:  LifeKeeperが動作する環境では、ホスト名やユーザー名にLifeKeeperの制約(例: Windows版でのホスト名の大文字制約 )がないか事前に確認し、 命名規則を遵守 する。   Generic ARK 利用時のスクリプト詳細把握:  Generic ARKを利用する場合は、スクリプトの作成者が動作内容(特に外部依存性、名前解決など)を詳細に把握し、デバッグログ出力やエラーハンドリングを適切に実装する。   LifeKeeper ログの日常的な監視とエラーコード理解:  LifeKeeperログに記録されるエラーメッセージやエラーコードの意味を正確に理解し、日常的に監視することで異常を早期に検知・対処できる体制を整える。 ベストプラクティス: 設計段階での詳細な要件定義:  アプリケーションリソースを保護する際は、LifeKeeperの要件、アプリケーションの要件、ネットワークの要件を詳細に定義し、設計書に落とし込む。特に通信要件や設定ファイルのパス、パスワードポリシーは入念に検討する。 公式ドキュメントの積極的活用:  各ARKの管理ガイド、処理概要、リリースノート、動作環境リストなど、SIOSが提供する最新の公式ドキュメントを常に参照し、推奨設定や既知の制約を把握する。 検証環境での徹底的なテスト:  OSアップデート、LifeKeeperバージョンアップ、アプリケーション設定変更など、システム構成に影響を与える変更は、必ず本番適用前に検証環境でLifeKeeperリソースを含むシステム全体の動作テストを実施する。 運用フローとチェックリストの確立:  リソース作成、変更、OSアップデートなどの主要な運用タスクについて、具体的な手順と確認項目を記した運用フローとチェックリストを確立し、ヒューマンエラーを防止する。 サポート活用と情報共有:  疑問点やエラー発生時には、積極的にSIOSサポートに問い合わせ、得られた知見はチーム内で共有しナレッジとして蓄積する。 まとめ 本記事では、LB Health Check、MySQL、IIS、Generic ARKといったアプリケーションリソースに関連する様々なトラブル事例を取り上げました。 これらの事例から、アプリケーションARKの安定稼働には、LifeKeeper本体やARKの仕様への深い理解に加え、連携するOSやアプリケーションの設定、そして基盤となるネットワーク通信環境の綿密な確認が不可欠であることが明らかになりました。 特に、OSやLifeKeeperのバージョンアップに伴う仕様変更への追従、設定ファイル記述ルールへの注意、そしてGeneric ARK利用時のスクリプト内容への詳細な把握が、トラブルを未然に防ぐ鍵となります。 日々の運用でこれらの点を意識し、公式ドキュメントの活用と検証を徹底することで、「困った!」を「できた!」に変え、より堅牢なクラスタ環境を構築・維持できるでしょう。 次回予告 次回の連載では、「クラスタの予期せぬ停止を防ぐ!ネットワーク構成のトラブルシューティング」と題し、LifeKeeper環境におけるネットワーク関連のトラブル事例とその解決策、安定稼働のためのベストプラクティスを深掘りします。どうぞご期待ください! 詳しい内容をお知りになりたいかたは、以下のバナーからSCSK LifeKeeper公式サイトまで
アバター
こんにちは。SCSKの井上です。 運用効率化を実現するためには、収集したデータを適切に可視化し、現状を一目で把握できる仕組みが重要です。New Relicでは、テンプレートを使って簡単にダッシュボードを構築できます。この記事で、New Relicで収集したデータの可視化方法を習得し、運用効率化につながる一助になれば幸いです。   はじめに ダッシュボード機能を活用し、CPU使用率やメモリ、レスポンスタイム、エラー率などの指標をグラフ化し、折れ線グラフで時系列の変化を表示すれば、リアルタイム監視に役立ちます。また、しきい値を設定し、異常値を色分けすることで、問題の早期発見が可能です。NRQL(New Relic Query Language)を用いることで、柔軟なカスタムチャートも作成できます。収集したデータを関連付けてオブザーバビリティを実現するためにも、ダッシュボードを使って可視化することが運用効率化の鍵になってきます。どんな作り方があるのか、どのように活用していけばよいかを解説してきます。   ダッシュボードを作る目的 目的が曖昧なまま作成されたダッシュボードは、利用されずに陳腐化してしまいます。そのため、 ダッシュボードを作成する際には、「誰が」「何を判断するためのものか」を明確にすること が不可欠です。たとえば、運用担当者であれば障害検知やリソース使用率の監視、マネージャー層であればコスト管理やSLA遵守、経営層であればビジネスインパクトの確認といったように、利用者ごとに必要な指標は異なります。 利用者 判断すること 指標例 運用担当者 サーバ・アプリが正常稼働しているか 障害や異常の有無 CPU・メモリ使用率 レスポンスタイム エラーレート アラート件数 マネージャー層 SLA遵守状況 コストが予算内か 稼働率 クラウド利用料 リソース消費量 経営層 技術的問題のビジネス影響 売上やUXへのインパクト トランザクション数 国別、曜日ごとのアクセス数 開発チーム 新機能リリース後のパフォーマンス ボトルネック特定 APIレスポンスタイム DBクエリ遅延 エラーログ   New Relic ダッシュボード カスタマイズやフィルタリング可能なNew Relicダッシュボードを使用して、スタック全体からデータをクエリし、ユースケースに応じた正確なインサイトを見つけられます。 newrelic.com   ダッシュボード(チャート)の種類 エリアチャートやラインチャートのような時系列可視化、バーやパイチャートによるカテゴリー比較、ビルボードやブレットによる重要KPIの強調表示など、New Relicのダッシュボードではデータの性質に応じて最適な視覚化手法を選ぶことができます。 チャートタイプ 用途 具体例 Area 単一属性の時系列推移を可視化 ・トラフィック量の増減 ・レスポンス量の変化 Bar カテゴリ間の合計値を比較 ・API別リクエスト数比較 ・エラー種別件数の比較 Billboard 単一の重要指標(KPI)を強調表示 ・エラー率のモニタリング ・レスポンスタイムの重要KPI表示 Bullet 実績値と目標値(Limit)の比較 ・SLA達成状況の表示 ・KPIが目標にどれほど近いか確認 Funnel 連続するプロセスの減少を表示 ・ユーザー遷移(訪問→購入) ・処理ステップの離脱分析 Heatmap 値の分布・密度を色で可視化 ・遅延の集中箇所の特定 ・ログ頻度やデータ密度の確認 Histogram データの分布(どの範囲が多いか)を把握 ・レスポンス時間の偏り分析 ・負荷データの分布確認 JSON クエリ結果を JSON のまま表示 ・開発者向けの詳細ビュー Line 複数系列の時系列推移の比較 ・CPU/メモリの変動確認 ・エラー件数の推移分析 Pie 全体に対する割合を表示 ・エラー種別割合 ・ユーザーOS/デバイス割合 Stacked Bar 合計値と内訳を同時に表示 ・サービス別トラフィック内訳 ・エラー構成比+総量 Table データを一覧形式で表示 ・FACET結果のランキング ・詳細データ比較、ログ確認   チャートタイプ | New Relic Documentation The chart types available when building queries in New Relic. docs.newrelic.com   ダッシュボードの作り方 ダッシュボードは主に次の3つの方法で作成できます。それぞれの方法について解説します。New Relicを使い始めた場合は、テンプレートから作成するか、既存のメトリクスデータを参考にNRQLの構成を理解した上で、カスタムダッシュボードを作成すると理解を深められます。 ダッシュボードを作り始める前に、ダッシュボードの編集や公開操作の権限の種類について確認します。以下、3つの種類が用意されています。編集は自分自身だけに限定したい、ダッシュボードが作成途中のため、完成してから公開したいなどの場合に、適切に選択することができます。 Permissions設定 説明 Edit – everyone in account アカウント内のすべてのユーザーがダッシュボードを編集できます。 Read-only – everyone in account アカウント内のすべてのユーザーがダッシュボードを閲覧のみできます。編集や削除は作成した自身のみに制限されます。 Private ダッシュボードは作成者のみが閲覧・編集可能です。完全に非公開の状態です。   New Relic実践入門 第2版 オブザーバビリティの基礎と実現 | 翔泳社 あらゆるデータを収集・分析・可視化して、システム/サービスの変化に能動的に対処せよITシステムやサービスが複雑化する現代において、オブザーバビリティ(Observability:可観測性)という考え方が極めて重要になっています。オブザーバビ... www.shoeisha.co.jp テンプレートから作成 既存テンプレート100種類以上から目的に合ったダッシュボードを簡単に作成できます。ダッシュボードを作成する際は、New Relicでデータ収集ができている状態から作成することを推奨します。ここでは、すでにInfrastructureエージェントが導入済の状態で手順を進めます。 1.左メニューより「Dashboards」をクリックします。 2.右上の「+ Create a dashboard」をクリックします。 3.「Browse pre-built dashboards」をクリックします。 4.テンプレートを選択します。 5.「Skip this step」をクリックします。次のステップも同様に「Skip this step」をクリックしてください。 6.ダッシュボードが作成されたメッセージを確認後、「View dashboard」をクリックします。 7.一から作成せずに高機能なダッシュボードが簡単に作成ができました。           推奨されるダッシュボード | New Relic Documentation 推奨されるダッシュボード docs.newrelic.com   カスタムダッシュボードから作成 既存テンプレートから作成したダッシュボードでは、不要なチャートが含まれたり、NRQLの修正箇所が分からずメンテナンスが難しい場合があります。一から作成すれば、必要な情報だけを表示でき、メンテナンス性も向上します。   1.左メニューより「Dashboards」をクリックします。 2.右上の「+ Create a dashboard」をクリックします。 3.「Create a new dashboard」をクリックします。 4.ダッシュボード名の入力と操作権限を選択し、「Create」をクリックします。 5.ダッシュボードの枠が作成されましたので、「Add a new chart」をクリックします。 6.「Add a chart」をクリックします。「Add text, images, or link」はダッシュボードに説明文やハイパーリンク、画像などを追加する際に使用します。 7.NRQLの編集画面にクエリを記載します。 8.「Run」を実行後、NRQLの実行結果が表示されます。chart typeで表示されているグラフの種類を変えることもできます。 9.鉛筆マークをクリックするとチャートの名前の入力やグラフの種類などを変更することができます。完了後、「Add to dashboard」をクリックします。 10.ダッシュボードに作成したNRQLが表示して完了になります。           表示されているチャートからダッシュボードを作成 既にホスト単位やサービス単位で表示されているチャートを個別に追加することで、必要なチャートだけを選択してダッシュボードに組み込むことができます。ダッシュボードを軽量に保ちながら段階的に構築でき、構成を確認しながら進められるため、完成イメージを把握しやすくなります。 1.ダッシュボードに追加したいチャートの「・・・」から、「Add to dashboard」をクリックします。   2.「Widget title」にチャートの名前を変更する場合は、変更します(日本語可)。追加したいダッシュボードを選択し、「Copy」をクリックします。ダッシュボートにタブを作成している場合は、ダッシュボート名/タブ名でリストが表示されています。 3.右下にダッシュボード完了のポップアップが表示されます。 4.ダッシュボード一覧からデータが追加されていることを確認します。   ダッシュボードの編集 ダッシュボードを作成した後、運用効率化のために構成を変更することがあります。ここでは、どのような編集方法があるのか、一例を解説します。ダッシュボードの一覧の横にある★をクリックするとリストのトップに表示されます。よく使うダッシュボードがある場合は、実施を推奨します。お気に入り機能はユーザー個人の設定のため、他のユーザには反映されません。   ダッシュボードの編集方法 ダッシュボードの編集は編集したいダッシュボード画面の右上のアイコンから実施します。鉛筆マーク中は編集モードとなり、表示するチャートの幅の変更や表示順の変更などができます。 ダッシュボードの編集開始は「鉛筆マーク」 ダッシュボードの編集完了は「Done editing」 タブの構成 タブを使ってチャートを分けることで、表示するデータ量を調整でき、読み込み速度とともに見やすさが向上します。また、「サービス別」「環境別」「機能別」など、目的に応じてタブを構成することで、必要な情報へ簡単にアクセスできるようになります。 1.編集したいダッシュボードの右上の「鉛筆マーク」をクリックします。 2.鉛筆マークをクリック後、「Add Page」をクリックします。 3.タブの名前を入力し、「Add page」をクリックします。 4.タブが追加されます。 5.タブの名前や複製、削除はタブをクリックして行います。 【補足】編集終了後は、「Done editing」をクリックします。   ダッシュボードの名前変更と編集権限 ダッシュボードの名前を変更したい場合や、完成してからダッシュボードをチーム内に公開したい、作成者以外ダッシュボードの編集をさせたくないなどの運用が発生した場合は、以下の手順にて変更ができます。 1.対象のダッシュボードを開き、右上「・・・」から「Settings」をクリックします。 2.名前の変更やダッシュボードの編集公開の変更ができます。   ダッシュボードの管理 | New Relic Documentation Manage your dashboard in New Relic: layout, export, night mode, create/edit/delete widgets. docs.newrelic.com   ダッシュボードのエクスポート・インポート ダッシュボードをエクスポートして保存し、後で復元したり、よく使うメトリクスやウィジェット構成をJSON化してテンプレートとして他のプロジェクトに展開できます。JSONの他に、運用レポートとしてダッシュボードをPDFで出力したり、テーブル形式であればCSVとしても出力できます。ここではJSONのエクスポートとインポートを例に解説します。 1.対象のダッシュボードから「・・・」より「Copy JSON to clipboard」をクリックします。 2.右下部に以下のポップアップが表示されます。この時点でメモ帳などにJSONを貼り付けできます。 3.ダッシュボード一覧画面にもどり「Import dashboard」をクリックします。 4.コピーしたJSONを貼り付けます。必要に応じて編集します。編集エラー個所はエディター内で確認できます。 5.ブラウザの画面更新ボタンをクリックし、ダッシュボード一覧に表示されていればインポート完了です。チャートが反映されているかを確認してください。       ダッシュボードとグラフのインポート、エクスポート、追加 | New Relic Documentation Import and export data from dashboards and charts. docs.newrelic.com   ダッシュボードのビジュアルを促進させるオプション New Relicではダッシュボードを視覚的にわかりやすく表示させるためのいくつかのオプション(Customize this visualization)が用意されています。これらのオプションを使って、運用効率化につながるダッシュボードをカスタマイズすることができます。Chart typeでグラフの種類も変更することができます。デフォルトで表示されたグラフが見づらい場合は、他のグラフの種類も表示することもできます。グラフの種類によって使えるオプションは異なります。 項目名 説明 実際の画面例・使用例 Y-axis (2) 異なる単位のデータ比較を同じチャートに表示 相関分析をする際に利用。負荷とパフォーマンス、エラーとトラフィックなど。 Other groups 表示しきれないグループをまとめる サーバー別にレスポンスタイムをグループ化し、色分け表示。 Legend 凡例の表示・非表示 チャート右下に「正常」「警告」「エラー」などの色ラベルが表示される。 Dashboard options 時間の固定 ダッシュボード上部にあるタイムピッカーで選択された時間範囲を無視し、チャートごとに固定の時間範囲を設定。 Colors チャートの色をカスタマイズ エラー率が高いと赤、正常なら緑など、しきい値に応じて色が変化。 Units 数値の単位を設定 レスポンスタイムを「ms」、データ転送量を「MB」で表示。 Thresholds しきい値を設定して色やアラートを変更 エラー率が5%を超えるとチャートが赤くなる設定等。 Markers 特定のイベントを示す縦線などを追加 デプロイ日時に縦線を追加し、パフォーマンス変化を視覚的に確認。 Null values nullデータの表示方法を設定 データがない時間帯をグレーで表示、またはゼロとして扱う設定。 Tooltip カーソルを合わせたときの詳細表示 「2025/12/01 14:00 – CPU使用率:75%」などのポップアップが表示される。 Initial sorting テーブルがどの列を基準に、どんな順番で並ぶか を指定する設定 テーブルを開いた瞬間に、確認したい順序で見たい場合。 Billboard settings 数値を大きく強調する KPI や重要なメトリクスに使う場合など。   Y-axis (2):異なるスケールのデータを同じチャートに表示 Y-axis(2)で単位が違うデータを一つのグラフでそれぞれ軸の単位を変更することでわかりやすく表示します。   Other groups:表示しきれないグループをまとめる Other groupsで表示しきれない下位グループをまとめて1つのグループとして表示します。   Legend:凡例の表示・非表示 Legendで凡例の表示非表示を設定することができます。   Dashboard options:時間の固定 Dashboard options(Ignore time picker)で NRQLで指定した期間 または ダッシュボードのデフォルト期間のみに固定します。TVモード(画面右上の鉛筆マークの横)で常時表示させる際に組み合わせて使用するケースを想定しています。   Colors:チャートの色をカスタマイズ Colorsで指定した色に変えることができます。Consistentは常に色は固定、Dynamicは値の変化(傾向)によって色が変わる設定になります。   Units:数値の単位を設定 Unitsで軸の単位を表示することができます。   Thresholds:しきい値を設定して色やアラートを変更 Thresholdsを用いることで、閾値設定したWarningやCriticalがどのラインに達した場合かを視覚的にわかるようになります。   Markers:特定のイベントを示す縦線などを追加 Markersでデプロイや変更箇所のイベントがあった時間帯を示すことができます。   Null values:nullデータの表示方法を設定 Null valuesでデータが欠損している場合のグラフの表示の仕方を調整することができます。   オプション 意味 使用シーン(活用例) Leave as null (default) データが存在しない部分は空白のままにする(初期設定) 一時的なデータ欠損を視覚的に確認したいとき。例:ネットワーク障害の影響を見たい場合。 Remove Null値のデータポイントを完全に除外する 欠損データが分析に不要な場合。例:平均値計算に影響を与えたくないとき。 Preserve the last value 最後の有効な値を保持し、Null部分に適用する データが途切れても継続的な傾向を見たいとき。例:CPU使用率が安定しているかを確認したい場合。 Transform into 0 Null値を「0」として扱う 欠損をゼロとして扱うことで、チャートの連続性を保ちたいとき。例:トラフィックが完全に停止したことを示したい場合。   Dashboard Customization Options Get more flexibility to customize the look and feel of your charts for more powerful data visualization. newrelic.com   Tooltip:カーソルを合わせたときの詳細表示 グラフ内のエンティティが多い際に、どのエンティティを指しているのかをカーソルを合わせることでわかるようになります。   Initial sorting:テーブル表示の順序設定 初めから並び順を固定したい場合は、このオプションを使います。テーブルのカラム名をクリックすることで都度順序の並び替えも可能です。   Billboard settings:数字の見せ方の設定 数字を強調させるために配置や大きさなどを設定することができます。URLを記載することで、チャートをクリック後に、該当のURLへアクセスすることも可能です。   項目 意味 Display mode 値やラベルの表示方式を選ぶ(Auto, Value only, Label only 等) Alignment テキストの配置 Value size 数値の文字サイズ Label size ラベルの文字サイズ Columns amount ビルボード表示の横幅(並べる列数の調整) Title リンクの表示名 Url リンク先 URL Open in a new tab 新しいタブで開くかどうか   Markdownを使った運用効率化 作成したダッシュボードの見方や問題が発生した際のエスカレーション手順リンクなどの情報をダッシュボード内に表示することができます。必要な判断材料や対応手順を一元的に確認でき、トラブル発生時の対応スピード向上に貢献できます。また、ダッシュボードに簡単な操作ガイドやFAQを表示することで、新しいメンバーでも迷わずダッシュボードを確認することができます。作成して形骸化させないためにも、本機能を使って運用効率化を促進していきたいですね。 1.対象のダッシュボードを開き、右上の「+ Add widget」をクリックします。 2.「Add text, images, or links」をクリックします。 3.文字やハイパーリンクなどを記載することができます。右側にプレビュー画面が表示されるため、実際の表示イメージを確認しながら作成できます。編集後、「Save」をクリックします。 4.右上の鉛筆マークをクリックして、配置の調整して完了になります。   ダッシュボードの管理 | New Relic Documentation Manage your dashboard in New Relic: layout, export, night mode, create/edit/delete widgets. docs.newrelic.com   ダッシュボードの設計ステップ ダッシュボードを作成する際の基本の考え方が整理されています。扱う指標を選定し目的に沿った情報配置を計画し、次に内容を一目で状況が理解できるようにレイアウトへ落とし込みます。そして、適切なチャート選択や文脈づけ(ラベル・単位・比較軸の明確化)を行い、情報の流れが変化 → 理由 → 行動として読み解けるように仕上げるプロセスが示されています。 Step 内容 目的 / 要点 1. 目的と質問の明確化 ダッシュボードで答えるべきビジネス質問を定義する 何を判断するための画面か?を明確にすることで、情報過多を防ぐ 2. KPIと必要データの選定 目的に直結する少数の指標を選ぶ データは収集→整形→モデリング→変換→可視化で準備する 3. ストーリーライン設計 現状 → 変化 → 要因 → アクションの流れを組む 何が起きたかを短いストーリーとして理解させる 4. 図表(チャート)選定 トレンド、比較、構成比など目的に合う可視化を選ぶ 折れ線=トレンド、棒=比較、円/ドーナツ=構成比など 5. レイアウト設計 視線の動きに合わせて情報を配置する 上:要点、中央:変化、下:詳細/原因。1画面で把握できる構成にする 6. ラベル・単位・文脈の明確化 タイトル・単位・比較軸を明確にする 誤解を防ぎ、意図を瞬時に伝えるために重要 7. ユーザーテストと改善 実際の利用者に見てもらい改善する(プロトタイピング) ユーザー理解と反復改善が成功の鍵。多くの失敗は会話不足が原因 8. 継続的メンテナンス KPI見直し、データ更新、自動化などの運用改善 ダッシュボードは作って終わりではなく継続的に最適化する   Effective Dashboard Design: Principles, Best Practices, and Examples Master dashboard design principles and best practices with this step-by-step guide. Learn to select metrics, choose the ... www.datacamp.com   ダッシュボードの活用例 ダッシュボードを作成し、チーム間で共通の指標を共有することで、意思決定のスピードが向上します。ダッシュボードを確認して、実際にどうしなければらないのか、行動につながる設計を行い、継続的に改善することで、運用効率に貢献することができます。たとえばダッシュボードを作成を作成することで以下のような活用ができます。 リアルタイム監視             : フロントエンド、バックエンドの状態をリアルタイムで確認 異常検知とアラート          : しきい値を設定し、異常が発生した際に通知や問題の早期発見とダウンタイムの防止 トレンド分析                   : 過去のデータを基にパフォーマンスの傾向、キャパシティの改善検討 チーム間の情報共有          : カスタムダッシュボードを作成し、開発・運用・ビジネスチームで共有 ビジネスインパクトの把握 : システムパフォーマンスが売上やユーザー体験にどう影響するかを可視化   様々な視点からダッシュボードを作成できますが、ここでは以下の視点に基づいて目的と指標を整理しました。 視点 目的 主な 指標 インフラ サーバ・クラウドリソースの監視 CPU使用率、メモリ使用率、ディスクI/O、ネットワーク帯域、ホスト稼働率 アプリ ユーザー体験可視化、ボトルネック特定 Apdexスコア、平均レスポンスタイム、エラー率、外部サービス呼び出し時間(API) コスト クラウド利用料やリソース消費の最適化 クラウドサービス別コスト、データ転送量、New Relic有償アカウント数 セキュリティ 不正アクセス検知や脆弱性管理 ログイン失敗回数、異常IP、セキュリティ関連アラート件数、CVE件数、 ビジネス 意思決定支援 トランザクション数、ユーザーセッション数、曜日ごとのページ訪問数   さいごに 2025年最後の記事は、New Relicに送信したデータをダッシュボードでどのように可視化し、運用に活かすか、設計の考え方も含めて紹介しました。ダッシュボードとNRQLを使いこなすことで、監視だけでなく、トレンド分析や異常検知、ビジネス指標の把握など、データ活用の幅が大きく広がります。この記事が、効率的な運用や意思決定のスピード向上に向けた第一歩となり、今後の改善や最適化に役立つ一助になれば幸いです。 SCSKはNew Relicのライセンス販売だけではなく、導入から導入後のサポートまで伴走的に導入支援を実施しています。くわしくは以下をご参照のほどよろしくお願いいたします。
アバター
前回の記事 では、取り組みの背景や概要をご紹介しました。今回は、ツールを開発するにあたり検討した 要件定義 についてお話しします。   業務要件(システム要件) 今回のツールは、AWS環境の設計・構築に関わるメンバーが、 構築フェーズ完了後にパラメータシートを効率的に作成する ことを目的としています。従来は、AWSコンソールを開き、設定値を目視で確認しながらExcelに転記するという作業が必要でしたが、この工程は非常に時間がかかり、ヒューマンエラーのリスクも高いものでした。 そこで、要件定義では「誰が」「どのような場面で」「どのような目的で」このツールを使うのかを明確化しました。 Who(だれが) 主な利用者は、当部に所属するインフラエンジニアやクラウド構築担当者です。特に、AWSの設計・構築を担当するメンバーが中心となりますが、専門的なスキルを持たないメンバーでも簡単に操作できることを重視しました。将来的には、運用担当者や品質管理チームなど、幅広い部門での利用も想定しています。 When(いつ) ツールの利用タイミングは、AWS環境の構築フェーズが完了した後です。構築手法は問いません。CloudFormationやCDKなどのIaCを使った場合でも、コンソールから手動で構築した場合でも、同じように利用できることを目指しました。これにより、プロジェクトの規模や構築方法に依存せず、標準化されたパラメータシートを作成できます。 Where(どこで) 基本的には社内ネットワークやローカルPCで利用します。セキュリティを考慮し、外部環境への依存を避ける設計としました。将来的には、社内ポータルやクラウド上での利用も検討しています。 What(なにを) ツールを使って、AWSリソースの詳細情報を収集し、Excel形式のパラメータシートを自動生成します。これにより、従来の手作業による転記やフォーマット調整を不要にし、成果物の標準化を実現します。 Why(なぜ) ドキュメント作成工程を省略化し、業務の生産性を向上させることが最大の目的です。さらに、ツールによって情報の正確性を担保し、ヒューマンエラーを防止することで品質向上にもつなげます。 How(どのように) 技術的なバックグラウンドを問わず、誰でも簡単に操作できるUIを提供します。AWS CLIの知識がなくても、GUIベースでリソースを選択し、Excel出力まで完結できる仕組みを目指しました。   機能要件 業務要件で明確化した利用目的をもとに、ツールに必要な機能を整理しました。ここでは、 どのような入力を受け取り、どのような出力を行うのか 、さらに 抽出する情報の方針 について検討した内容を詳しく説明します。 まず、どのような入力を受け取るか検討しました。 機能要件 — インプット(入力)比較表 案 方式 メリット デメリット   案① AWS CLI describe の結果JSON(構築後取得) 構築後に取得するため、CloudFormationテンプレートに含まれないパラメータも取得可能 構築方法に依存しない(手動構築やIaCどちらでも対応可能) CLI実行の事前作業が必要/権限・プロファイル設定が前提 〇採用 (正確性・汎用性重視) 案② CloudFormationテンプレート(構築時の宣言) 構築時点で利用しているため、情報の取得が容易、構築作業前にドキュメント作成可能 テンプレートに含まれないパラメータは取得できない 依存関係の解決が必要(参照IDなど) Cfn以外の構築方法では利用不可 ×不採用 (実環境との差異リスク)   次にどのような出力を行うのか検討しました。 機能要件 — アウトプット(出力)比較表 案 方式 メリット デメリット   案① Excel(.xlsx) 顧客納品に適合/レビュー性が高い 書式設計のルール設計が必要 〇採用(納品適合性) 案② CSV 軽量/他ツール連携しやすい 納品体裁への変換が必要(VBA等) ×不採用 案③ Markdown ドキュメント化容易/差分管理しやすい 顧客納品には不向き/表現力に制約 ×不採用   AWSリソースのパラメータをどの範囲で抽出するかについても検討しました。 抽出するデータ 案 方式 メリット デメリット   案① 必要な情報のみ抽出 AWS側でパラメータ追加があってもコード修正不要 Excel整形が容易 想定外のパラメータに気づけない リソースごとにコードが必要 ×不採用 案② すべてのパラメータを出力 顧客に正確な情報を提供可能 リソース追加に柔軟 Excel整形に工数がかかる 〇採用 (正確性・汎用性重視)   機能の全体像 最終的に、ツールは以下の機能を備えることを目指しました。 AWS CLIのdescribeコマンド結果を取り込み、JSONを解析 必要な情報を抽出し、Excel形式で整形して出力 複数リソースの選択・一括出力に対応 誰でも簡単に操作できるGUIを提供   まとめ 今回の取り組みを通じて、業務改善の重要性を改めて実感しました。パラメータシート作成の自動化は、単なる効率化にとどまらず、品質の一貫性やヒューマンエラー防止にも大きく貢献できることがわかりました。一方で、アプリケーション開発の知見不足から、設計や要件定義で試行錯誤を重ねる場面もありましたが、その経験自体がチームの成長につながったと感じています。 このツールにはまだ改善の余地がありますので、今後も継続的に改良を重ね、より使いやすく、業務に役立つものにしていきたいと思います。 というわけで、私の投稿はここまでです! 次回は齋藤さんにバトンタッチしますので、ぜひお楽しみに!
アバター
みなさん、こんにちは。SCSKの津田です。 LifeKeeper は、オンプレミスの物理サーバから仮想基盤、各種クラウドサービスまで、幅広い環境で導入・運用できる高可用性クラスタソフトウェアです。 SIOSによると、近年ではクラウド環境への導入が全体の約半数を占めるまでに拡大しており、当チームでも設計・構築案件の多くがクラウド基盤上で展開されています。 とはいえ、多くの企業システムでは今なおオンプレミス環境が主軸として稼働しており、物理サーバや仮想基盤で LifeKeeper を利用したいというニーズも依然として高い状況です。 そこで本記事では、仮想環境においてLifeKeeperを導入する上で押さえておきたいポイントをご紹介します。 仮想環境での冗長化をご検討中の方は、ぜひご確認ください! 仮想環境のHAクラスター構成パターン 仮想環境で構成できるHAクラスター構成の基本パターンは以下となります。 共有ストレージが使えない環境では、データレプリケーション構成にすることが可能です。 構成①:共有ストレージ構成 構成②:データレプリケーション構成 構成③:データレプリケーション構成 (WSFCとの組み合わせ)     ※HAクラスター構成の他にSSP(Single Server Protection)構成も可能。 仮想環境におけるLifeKeeper障害対応 LifeKeeperを導入することで、以下のような幅広い障害を検知し、自動復旧(フェールオーバー)によるサービス継続が可能となります。 ・アプリケーション障害 ・ゲストOS障害 ・仮想マシン障害 ・仮想化ソフトウェア(ハイパーバイザ)障害 ・物理ホスト障害 ・ネットワーク障害(物理ホスト間のすべての通信経路断絶時) ※ゲストOS障害~ネットワーク障害までの範囲については、コミュニケーションパスが全て断たれた場合にノード障害として検知します。 上記の通り、物理ホストの障害にはもちろん、仮想マシンやアプリケーション単位の障害にも柔軟に対応できるため、仮想環境ならではの冗長性を最大限に活かすことができます。 特にVMwareの「vSphere HA」と比較した場合、ESXiホスト障害時にはLifeKeeperの方が障害検知から復旧までの時間が短いというメリットがあります。(LifeKeeperでは、待機系ESXiホストがあらかじめ起動状態で待機しているため、再起動が必要となるvSphere HAよりも迅速なフェールオーバーが可能。) なお、リソースの監視や障害検知の方法については、仮想環境特有のものではなく、クラウド環境や物理環境と同様の仕組みを採用しています。 【OS別】サポート内容・留意事項 昨今Broadcom社によるVMwareの買収を受けて、VMware以外の仮想環境(ハイパーバイザ)を選択されるケースも増加傾向にあります。LifeKeeperでは、VMware以外の仮想環境にも対応していますので、お客様のニーズに合わせた仮想環境で高可用性構成を実現できる点も強みとなります。 本項ではLinux/Windows毎の最新サポート状況(Ver10)や留意事項をご紹介します。 LifeKeeper for Linux  サポート対象の 仮想環境 一覧(LifeKeeper for Linux version10.0) LifeKeeper for Linuxでサポートされる仮想環境(ハイパーバイザ)は下記の通りとなります。 仮想環境 バージョン VMware vSphere 7.0, 8.0, 8.0U1, 8.0U2, 8.0U3 VMware Cloud on AWS SDDC 1.19以降 KVM RHEL 8.10以降およびRHEL 9.0以降、ならびにOracle Linux(RHCK/UEK)8.10以降およびOracle Linux 9.0以降のバージョンのみをサポートし、その他のディストリビューションは対象外です。 Nutanix Acropolis Hypervisor (AOS) 6.10, 7.0, 7.3 Hyper-V Windows Server 2022 Red Hat OpenShift Virtualization 4.17以降 ※仮想マシン上で使用できるOSついては下記をご確認ください。 オペレーティングシステム – LifeKeeper for Linux LIVE – 10.0 留意事項 サポート対象の各仮想環境に依存した制限や詳細な留意事項については、下記リンクの 「使用環境に関する制限・留意事項」 セクションをご確認ください。 仮想化環境 – LifeKeeper for Linux LIVE – 10.0 また、仮想環境として採用されることの多い VMware vSphere や Hyper‑V については、サイオステクノロジー社より個別の構成ガイドが提供されています。各ハイパーバイザでの構成例や特有の注意点が詳細にまとめられていますので、設計時には必ず確認することを推奨します。 ▼ VMware vSphere LifeKeeper for Linux 仮想環境構成ガイド (VMware vSphere編) – SIOS LifeKeeper/DataKeeper User Portal [Linux][Windows]VMware vSphere環境でRDM使用時のLifeKeeperサポート構成について – SIOS LifeKeeper/DataKeeper User Portal ▼ Hyper‑V(Linux / Windows 共通) LifeKeeper 仮想環境構成ガイド (Hyper-V編) – SIOS LifeKeeper/DataKeeper User Portal 共有ストレージ利用時の留意事項 LifeKeeper for Linuxでクラスタの共有データ領域を利用する際の構成については、以下をご参照ください。 [Linux]クラスターの共有データ領域として利用できる構成 – SIOS LifeKeeper/DataKeeper User Portal なお、LifeKeeper for Linuxでは、共有ストレージの認定が行われています。複数ノードで同一データを参照する共有ストレージ(SCSI / FC / iSCSI / SASなど)は、SCSI-2/3 ReservationによるI/Oフェンシングを前提としており、認定されたストレージを使用する必要があります。認定ストレージの一覧は、以下のリンクにある表を展開してご確認ください。 共有ストレージ – LifeKeeper for Linux LIVE – 10.0 一方、以下の構成についてはストレージ認定が不要です。 ・NASストレージ(Recovery Kit for NASが必要) ・DataKeeperによるデータレプリケーションに利用する全てのディスク装置(内蔵/外付けを問いません) ・vSphere上でVMDK ARKにより保護される共有ストレージ上の仮想ディスク ・以下の条件をすべて満たす環境で利用するストレージ(Any Storage)    ① OS・ハードウェア・プラットフォームでサポートされているストレージであること    ② LifeKeeperのSCSI Reservation機能をオフにすること    ③ LifeKeeperのQuorum/Witnessによるフェンシング機能を利用すること Any Storageでは、I/OフェンシングにSCSI ReservationではなくQuorum/Witnessを使用する点が特徴です。 そのため、認定ストレージ一覧以外のストレージも利用可能です。 また、データ破壊の危険性を低減するため、STONITH機能との併用も推奨されています。             仮想環境でAny Storageを適用する場合、以下にご注意ください。 ・仮想環境(ハイパーバイザ)やゲストOSの要件は、製品の要件に準じます。 ・接続方式(FC、iSCSI、SASなど)は不問です。 ・シングルパス・マルチパスのいずれもサポートされます。    但しマルチパスを使う場合は、DMMP(Device Mapper Multipath ARK)のみがサポートされます。 ・Quorum/Witness機能を使用しない場合は製品のサポート対象外となります。    (LifeKeeperとQuorum/Witnessとの組み合わせに関する動作や仕様⇒サポート対象、    個々の共有ストレージに依存する事象⇒サポート対象外) ・ご利用のストレージメーカーへのお問い合わせ窓口を確保の上、お客様にて十分な動作確認の上で採用されることを    推奨します。 ・STONITH機能を使用する場合、HeartBeatの相互断絶が発生すると双方のノードで電源断が発生し、確実なサービスの継続    には繋がらない可能性がありますが、これはデータ破壊を回避するためのSTONITHの動作となりますのでご了承ください。 ——————————————————————————– ■参考URL Any Storage: [Linux]ストレージサポートポリシー(Any Storage)について – SIOS LifeKeeper/DataKeeper User Portal STONITH: STONITH – LifeKeeper for Linux LIVE – 10.0 リザベーションの無効化: リザベーションの無効化 – LifeKeeper for Linux LIVE – 10.0 Quorum/Witness: Quorum/Witness – LifeKeeper for Linux LIVE – 10.0 LifeKeeper for Windows サポート対象の 仮想環境 一覧(LifeKeeper for Windows version10.0) LifeKeeper for Windowsでサポートされる仮想環境(ハイパーバイザ)は下記の通りとなります。 仮想環境 バージョン VMware vSphere 7.0, 8.0, 8.0U1, 8.0U2, 8.0U3 Red Hat OpenShift Virtualization 4.17以降 KVM RHEL 8.10以降およびRHEL 9.0以降、ならびにOracle Linux(RHCK/UEK)8.10以降およびOracle Linux 9.0以降のバージョンのみをサポートし、その他のディストリビューションは対象外です。 Microsoft Hyper-V Server 2016, 2019, 2022 Nutanix Acropolis Hypervisor (AOS) 6.10,7.0,7.3 ※仮想マシン上で使用できるOSついては下記をご確認ください。 オペレーティングシステム – LifeKeeper for Windows LIVE – 10.0 留意事項 サポート対象の各仮想環境に依存した制限や詳細な留意事項については、下記リンクの 「使用環境に関する制限・留意事項」 セクションをご確認ください。 仮想化環境 – LifeKeeper for Windows LIVE – 10.0 また、仮想環境として採用されることの多い VMware vSphere や Hyper‑V については、サイオステクノロジー社より個別の構成ガイドが提供されています。各ハイパーバイザでの構成例や特有の注意点が詳細にまとめられていますので、設計時には必ず確認することを推奨します。 ▼ VMware vSphere LifeKeeper for Windows / DataKeeper Cluster Edition 仮想環境構成ガイド (VMware vSphere編) – SIOS LifeKeeper/DataKeeper User Portal [Linux][Windows]VMware vSphere環境でRDM使用時のLifeKeeperサポート構成について – SIOS LifeKeeper/DataKeeper User Portal ▼ Hyper‑V(Linux / Windows 共通) LifeKeeper 仮想環境構成ガイド (Hyper-V編) – SIOS LifeKeeper/DataKeeper User Portal 共有ストレージ利用時の留意事項 LifeKeeper for Windowsには、Linuxのようなサイオステクノロジー社による認定ストレージリストは公開されていませんが、標準的なSCSI、FC、iSCSI等の共有ディスクが利用可能です(参考:https://www.windowsservercatalog.com/hardware)。 詳細や制約については、事前にサポートへのお問い合わせを推奨いたします。 また、LifeKeeper for WindowsではAny Storageポリシーは提供されていませんが、Quorum/Witness機能を利用することでスプリットブレインの回避に役立ち、より堅牢な運用が可能となります。 【OS共通】その他設計・運用上のポイント 仮想環境専用ライセンスの使用ルール・制限事項 仮想環境では、以下のような仮想環境特有のライセンス利用ルールが設定されていますのでご注意ください。 ・一つのライセンスは、一つの占有された物理ホスト上でのみ使用可能。 ・異なる物理ホスト間でのみ、クラスター環境を構成可能。 ・一つのライセンスで異なるバージョンのLifeKeeperを使用することは不可。 ・一つのライセンスには一つのサポートレベルのみ適用可能。 ・最低販売数量は2つ(Core 4、2クラスター環境)からとする。 LifeKeeper 設定上の考慮点 LifeKeeperのインストールおよびクラスター設定は、物理環境と同様の手順で行うことができます。具体的な手順については、オンラインマニュアルをご参照ください。 ※IPリソースの監視処理について 仮想環境では、物理的なネットワークスイッチだけでなく、仮想的なネットワークスイッチも存在するため、これらを含めたネットワーク構成全体を十分に考慮する必要があります。 さいごに 今回は仮想環境においてLifeKeeperを導入する上で押さえておきたいポイントをご紹介いたしました。 多様な選択肢と注意点が存在しますが、その分多様な要件に適した HA 構成を実現できる柔軟性を備えています。 仮想環境でLifeKeeper導入をご検討中の方にとって、少しでも参考となりましたら幸いです。   詳しい内容をお知りになりたいかたは、以下のバナーからSCSK Lifekeeper公式サイトまで
アバター
本記事は TechHarmony Advent Calendar 2025 12/24付の記事です 。 こんにちは、SCSKでAWSの内製化支援『 テクニカルエスコートサービス 』を担当している貝塚です。 re:Invent2025の直前に、Network Firewall Proxyという機能のプレビューが開始されました。 リリース記事 AWS Network Firewall Proxy のプレビューのご紹介 - AWS AWS の新機能についてさらに詳しく知るには、 AWS Network Firewall Proxy のプレビューのご紹介 aws.amazon.com ブログ記事 Securing Egress Architectures with Network Firewall Proxy | Amazon Web Services Note: Dec 4, 2025 – expanded with additional section on application networking integrations. Customers who control acces... aws.amazon.com AWS Network Firewallで機能提供していた透過型プロキシではなく、クライアント側で明示的にプロキシサーバとして指定する必要のあるタイプのプロキシです。 私の担当するお客様でも金融業を中心に過去にプロキシサーバの導入を検討された結果EC2でプロキシを構築したり、Network FirewallでのTLS検査を検討されたりしていますので、それらのお客様への導入という視点からNetwork Firewall Proxyの機能を検討してみます。 アーキテクチャ 名前にNetwork Firewallとついていますが、既存のAWS Network Firewallの一機能ではなく、独立したリソースになっているようです。NAT Gatewayとセットで動作し、クライアントは専用のVPCエンドポイント(Proxyエンドポイント)経由でプロキシを使用することになります。 シンプル(単一VPC)構成 単一VPC構成例 Architecture overview より引用   上の図がもっともシンプルな構成ですが、実際に設定してみるとNetwork Firewall Proxyと同じサブネット(パブリックサブネット)に自動的にProxyエンドポイントが作成されることが分かります。同VPC内に既にエンドポイントがあるのに別サブネットにエンドポイントを作る必要性はイマイチ理解できませんが、構成上はどちらでも動作しました。 少し調べてみたところ、 こちら のblogではProxyと同じパブリックサブネットにエンドポイントが置かれていたので、そのあたりは好きに設計すればよさそうです。 中央集約構成 他のネットワークリソース(Internet GatewayやNetwork Firewallなど)と同様に、Network Firewall Proxyも、複数のVPCのProxy機能をひとつに集約した構成を取ることができます。 そしてここが素晴らしい点なのですが、Network Firewall Proxyの機能はVPCエンドポイントとして提供されているので、通信要件がHTTP/HTTPSのみであれば、Transit GatewayやVPC PeeringなどでVPC間を接続することなく、インターネットとの通信をセキュアに実現できてしまうのです。 中央集約構成(HTTP/HTTPSのみ) Securing Egress Architectures with Network Firewall Proxy より引用   HTTP/HTTPS以外にも通信要件がある場合は、従来同様Transit GatewayでVPC間を接続し、HTTP/HTTPS通信はProxyエンドポイント経由、HTTP/HTTPS通信以外はTransit Gateway経由という方式が紹介されています。 中央集約構成(HTTP/HTTPS以外の通信要件あり) Securing Egress Architectures with Network Firewall Proxy より引用 上図構成だと、HTTP/HTTPSの検査・制御はNetwork Firewall Proxyで行う、HTTP/HTTPS以外の検査・制御はNetwork Firewallで行うことになります。ログの出力先もログのフォーマットも別になり、運用管理上の負荷が増えそうですが、これは現状仕方ありません。 一方で、Transit Gatewayで中央集約VPCとつながっているのであれば、Proxy エンドポイントは各VPCに持つのではなく、下図の通り中央集約VPCにひとつだけ置くというのも選択肢としてありえそうですが……HTTP/HTTPSトラフィックがNetwork FirewallとNetwork Firewall Proxy両方通ることになると無駄に高額なトラフィック料金を払わされることになり現実的ではないのかもしれません。まだNetwork Firewall Proxyの料金が発表されていないので、この部分は料金の発表を待たないとこれ以上議論できなさそうです。 HTTP/HTTPS以外のトラフィックもHTTP/HTTPSトラフィックも、Inspection VPCのNetwork Firewallを通過する プロキシのルールについて プロキシのルール構造は下図のようになっています。 プロキシは1つのプロキシ設定を持ちます。 プロキシ設定は複数のルールグループを持つことができます。プロキシ設定内で、複数のルールグループに0~の優先度をつけることができ、値が小さいグループから順に評価されるようになっています。また、どのルール(後述)にも合致しなかった場合のデフォルトのアクション(許可/拒否)をプロキシ設定で指定します。 ルールグループは複数のルールを持つことができます。ルールグループの設定のところで各ルールを記述して行きます。ルールは検査した通信に対するアクション(許可/拒否)と条件を記したもので、0~の優先度をつけることができ、値が小さいルールから順に評価されるようになっています。 以上をまとめると、ルールはフェーズ(後述)ごとに、以下の順番で評価されていき、どのルールにも合致しなかった場合、デフォルトのアクションが実行されるということになります。 優先度0のルールグループ:優先度0のルール → 優先度0のルールグループ:優先度1のルール → 優先度0のルールグループ:優先度2のルール → … → 優先度1のルールグループ:優先度0のルール → 優先度1のルールグループ:優先度1のルール → … → 優先度2のルールグループ:優先度0のルール → … → デフォルトのアクション フェーズ プロキシのルールは、HTTP/HTTPSの通信の段階に応じて、PreDNS、PreREQUEST、PostRESPONSEの3つのフェーズに分けて適用されます。 クライアントがHTTP CONNECTを出し、ProxyがDNS名前解決を行う前にルールを適用するのがPreDNSです。 クライアントがHTTP REQUESTを出し、Proxyがあて先にHTTP REQUESTを行う前にルールを適用するのがPreRequestです。 あて先からProxyに返ってきたHTTP RESPONSEをクライアントに返す前にルールを適用するのがPostResponseです。 Securing Egress Architectures with Network Firewall Proxy より引用 アクション アクションは、許可(allow)、アラート(alert)、拒否(deny)の3つがあります。 許可(allow)、拒否(deny)はそのままの意味ですが、アラート(alert)はアラートログに出力した上で次以降のルールの評価を継続します。許可した場合でもログを出力することができますので、許可(allow)ログは証跡としての保存用、アラート(alert)ログはCloudWatch等で捕捉してSNS連携等のアクションにつなげるという使い方が想定されているのだと考えられます。 条件 アクションの発生条件を、リクエストやレスポンスに関わる各種値を使用して指定します。現在対応しているのは以下の通りです。 request:SourceAccount – リクエスト送信元のAWSアカウントID request:SourceVpc – リクエスト送信元のVPC ID request:SourceVpce – リクエスト送信元のVPCエンドポイントID request:Time – リクエストが発生した日時 request:SourceIp – 送信元のIPアドレス request:DestinationIp – 送信先のIPアドレス request:SourcePort – 送信元のポート番号 request:DestinationPort – 送信先のポート番号 request:Protocol – 通信プロトコル(TCPなど) request:DestinationDomain – 送信先のドメイン名 request:Http:Uri – HTTPリクエストのURI(パス) request:Http:Method – HTTPリクエストメソッド(GET, POSTなど) request:Http:UserAgent – HTTPリクエストのUser-Agentヘッダーの値 request:Http:ContentType – HTTPリクエストのContent-Typeヘッダーの値 request:Http:Header/CustomHeaderName – HTTPリクエスト内の指定したヘッダーの値(CustomHeaderNameを実際のヘッダー名に置き換えて使用) response:Http:StatusCode – HTTPレスポンスのステータスコード response:Http:ContentType – HTTPレスポンスのContent-Typeヘッダーの値 response:Http:Header/CustomHeaderName – HTTPレスポンス内の指定したヘッダーの値(CustomHeaderNameを実際のヘッダー名に置き換えて使用) 設定手順 プロキシの作成は以下の手順で実施します。 プロキシルールグループの作成 ルールの作成 プロキシ設定の作成 → ここで、プロキシルールグループを指定 (まだ作成していないなら) NATゲートウェイの作成 (TLS インターセプトモードを有効化するなら) プライベートCAの作成、Resource Access Manager共有設定 プロキシの作成 → ここで、プロキシ設定、NATゲートウェイ、プライベートCAを指定 Proxyエンドポイントの作成 本稿では、具体的な操作手順については説明を致しません。( 公式ドキュメント などをご参照ください)以下、注意すべき点のみを書いて行きます。 TLSインターセプトモードについて TLS通信の中身を検査するには、プライベート証明書を発行できるプライベートCAが必要になります。 公式ドキュメントの記述 が不親切なのですが、プライベートCAを作成し、RAM (Resource Access Manager) で共有して、「下位CAは作成できず従属CA証明書の発行のみ許可する権限」(AWSRAMSubordinateCACertificatePathLen0IssuanceCertificateAuthority)を与えてあげてください。プライベートCAのことをよく知っている人には当然のことなのかもしれませんが、私はこれが理解できておらずハマりました。 TLS検査の仕組みや、クライアントPCでプライベート証明書を信頼する手順は、以下の記事もご参照ください。 AWS Network FirewallでアウトバウンドトラフィックをTLSインスペクションする AWS Network Firewallで、アウトバウンド(egress)のTLSインスペクション機能を検証しました。アウトバウンドTLSインスペクションにより、クライアントPC(社内)から外部のウェブサーバへのHTTPS通信の内容を検査することができるようになります。 blog.usize-tech.com 2023.12.27 TLSインターセプトモードが有効になると、条件 request:Http:* や  response:Http:* を使用した検査が行えるようになります。 Proxyエンドポイントの作成 プロキシが作成されると、プロキシと同じサブネットにProxyエンドポイントが作成されます。Proxyエンドポイントは追加作成することができます。プロキシの詳細に「VPC エンドポイントサービス名」という項目が表示されるので、プロキシを使用したいVPCでVPCエンドポイントを作成し、タイプに「NLB と GWLB を使用するエンドポイントサービス」、サービス名に「VPC エンドポイントサービス名」の値を入れるようにしてください。なお、最初に作成されるProxyエンドポイントは不要であれば削除しても構いません。 クライアントでプロキシ設定して使用 プロキシの詳細に「プライベート DNS 名」が表示されるので、その値( xxxxxxxx.proxy.nfw.<リージョンID>.amazonaws.com )をクライアントのプロキシ設定箇所に設定して使用します。 さいごに 顧客企業への導入検討と銘打ちながら、検討らしきものはアーキテクチャ検討部分だけとなってしまいましたが、プレビューが進みGA段階になれば、プレビュー段階に存在する様々な制約も解消され、具体的に検討が進められるかなと考えています。 現段階では、インターネットアクセス要件がHTTP/HTTPSだけであればTransit GatewayやVPC Peeringを作ることなくVPCエンドポイントだけで接続できてしまう点がメリットとして大きいように感じます。 まずはプレビューで出来る範囲でいろいろ試してみたいと思います。
アバター
だいぶご無沙汰しております。村上です。 ラスベガスで開催された「 AWS re:Invent 2025 」に参加してきましたので、複数回にわたって参加レポートをお届けします。 では早速。今回は「Self-Paced Traning」と「AWS Builder Center」についてです。 ※ちなみにタイトルにもある通り、海外渡航は人生2回目の海外初心者です・・・ コミュニケーションや現地での過ごし方で苦戦したエピソードもどこかで紹介したいと思います。 (今回の記事の内容自体にはほとんど関係ありません) 「Self-pased Traning」に参加してみた Self-pased Traningとは? re:Inventの初日、気合を入れすぎて相当早く会場へ到着。 お目当てのセッションまで時間があったので、会場を歩き回っていたところ「Self-paced Traning」というエリアを発見しました。 これは、re:Inventの開催期間中、AWSサービスに関するのクイズやハンズオン、AWS認定試験の準備など、200以上の学習コンテンツが「無料で」受けられるというもの。 「 AWS Skill Builder 」にあるものと重複しているものもありそうでしたが、このようなイベントで初披露されるコンテンツもあるとのこと。空き時間でサクッと参加するにはちょうどよいアクティビティかなと。 ▼会場の雰囲気はこんな感じ。 PCとモニターが1台ずつ用意されていますので、自身のデバイスは不要です。 受付に一言を声かけて、好きな席に座るだけでOKです。 実際にやってみた 今回「AWS ESCAPE ROOM」というRPG風ゲーム形式の学習コンテンツに挑戦しました。 その中でも「Exam Prep for AWS Certified AI Practitioner」という、AI Practitioner認定向けコンテンツを選びました。 ▼スタート画面。 ネタバレになるので詳細は割愛しますが、 AI Practitionerで求められる知識を問うクイズと、「Amazon Bedrock」を使った生成AIアプリケーション構築のハンズオンが含まれていました。 クイズはAWS初学者向けで、英語ですが難しいものはありません。 途中BGMが流れたり、動画視聴なんかもあったりするので、有線イヤホンがあると便利です。 (持っていてよかったものについては「会場の歩き方編」なんかでまとめたいと思っています) ▼ハンズオンで利用したアーキテクチャ。 操作はガイド通りにポチポチやっていけば簡単に作成できます。初学者がイメージを掴むのにはよいかもしれません。 本当は「岩に乗ったペンギン」の画像を出力させるというお題でしたが、「ラーメンを食べる日本人女性」にしてみました。 ▼こんな感じの画像が出力されました。   このように、気軽に学習できるだけでなく、海外からの参加者と同じ空間でで手を動かすのもよい刺激になりました。 空き時間を有効活用できるアクティビティとしておすすめです。 「AWS Builder Center」を利用しよう 「Self-paced Traning」を利用するには、「Builder ID」が必要になります。 これは「 AWS Builder Center 」を利用するための個人に紐づくアカウント。Amazon Q DeveloperやAWS Skill Builder、AWS re:Postなど、学習コンテンツや学習ツールを利用できるようになります。 (ちなみに私もほとんど活用したことがなかったので、これから触ってみる予定です) 「AWS Builder Center」に関しては、AWSも非常に力を入れている印象でした。 メンバー向けのイベントや休憩可能な専用ブースもあり、会場スタッフも「是非寄ってみて!」と積極的に声をかけていました。 ▼立ち寄った専用ブース。 ▼SWAGもたくさん。   ということで、第一回はこのくらいで終わります。 re:Inventに参加した際は是非「Self-paced Traning」への参加を、 また、日々の学習の際は「AWS Builder Center」を、というお話でした、 また次回作も見てくれたらうれしいです。
アバター
本記事は TechHarmony Advent Calendar 2025 12/23付の記事です 。 どうも。四国の高知県に来ている寺内です。このブログを書くために、わざわざHHKBを出張に持ってきました。 さて。どこにいても、PCが変わっても、自分の慣れ親しんだ環境を使いたいものですよね。それが作業効率のみならず心地よさに大きく影響するからです。 VSCode周りやブラウザは長い時間を育ててきている設定ファイルを複数PC間で同期できるのでよいですが、OS周りの設定同期や共有は面倒なものがあります。ブラウザで環境が完結していたCloud 9は重宝する面が多々ありました。残念ながらサ終に向かっていることもあり、代替を検討するチームも多いでしょう。 当社のAWS Ambassadors 広野さんが2025年9月に投稿した以下のブログ記事では、Ubuntuが稼働するEC2のデスクトップコンソールをブラウザで操作するサーバを構築できます。 VSCode 導入済みで Web GUI アクセス可能な Amazon EC2 Ubuntu インスタンスを一発構築する [AWS CloudFormation] AWS Cloud9 の代わりとなるクラウド IDE の構築をいろいろ試しています。そのうちの1案です。 blog.usize-tech.com 2025.09.08 大変優れた記事ですが、こちらのCFnテンプレートでは、素のUbuntu稼働のEC2インスタンスを作成し、その後デスクトップ環境の構築を行います。これはEC2インスタンスを作成するタイミングの違いで、インストールされるツール・ライブラリのバージョンが異なることになります。チーム開発をしているケースで、ある程度の環境の統制を効かせたい場合、メンバー毎にバージョンの違いが生じるのは塩梅がよくありません。 そこで本記事では、このCFnテンプレートを個人利用スコープからチーム開発スコープに拡張します。広野さんのCloudFormationテンプレートを参考にしつつ、新たに開発サーバ構築ツール「vsimage」として再構成します。 vsimageツールのアーキテクチャの考え方はCloud 9 のアーキテクチャを踏襲します。つまり、チームとしてのAMIを一つ作っておき、メンバーはそのAMIからマイ環境のEC2インスタンスを起動するという使い方です。環境をビルドしAMIを作成するフェーズと、EC2インスタンス化するフェーズを分離することで、EC2作成時にデスクトップソフトウェアのインストール時間を待つ必要がなくなります。 登場人物の役割と利用方法概略 vsimageは、以下の二種類の役割の人間を想定しています。 AMI管理者 : Ubuntu OSおよび導入ツールの管理を行い、AMIをチームメンバーへ提供する責任を持つ役割です。 開発者 : AMI管理者によって提供されるAMIを用いて、自分専用のUbuntu EC2インスタンスを作成し、VSCodeを利用する役割です。 vsimageは、以下3つのCFnテンプレートからなります。 Image Builder構築テンプレート:  imagebuilder-pipeline.yaml AMI作成テンプレート:  ami-creation-execution.yaml EC2インスタンス構築テンプレート:  ec2-launch-from-ami.yaml 1と2のCFnテンプレートは、AMI管理者が使用します。3は開発者が使用します。 アーキテクチャ概要 Ubuntu ServerにGUI環境をインストールしUbuntu Desktopにします。その後、DCVやVSCodeなどの必要なツール群をインストールします。これらの一連の作業は、EC2 Image Builderを使用します。 Image Builder とは - EC2 イメージビルダー Image Builder は、安全なカスタム Amazon マシンイメージ (AMI) またはコンテナイメージを作成し、 AWS内の宛先のアカウントとリージョンで使用できるように配布するためのフルマネージドサービスです。 docs.aws.amazon.com AMI管理者は、Image Builderのコンポーネント定義とレシピ定義を行いUbuntuにインストールするソフトウェアを定義します。その設定をパイプラインとして定義します。 パイプラインを実行し、Image Builderで作成されたAMIをチームに公開します。 開発者はそのAMIを使用して、自らのVPC環境にEC2インスタンスを起動します。 ブラウザからスムーズにアクセスできるように、EC2起動と同時にRoute 53へFQDNと新規取得したElastic IPの組み合わせをAレコードで登録します。またSSL接続を行うためのオレオレサーバ証明書を内部で作り出します。 Ubuntuサーバは、安価なGravitonインスタンスタイプで動作します。そのため、AMI作成プロセスにおけるインスタンスタイプも同じくGravitonを指定しています。 AMI作成段階で作るインスタンスタイプは、t4g.large。EC2作成段階ではパラメータで指定可能ですが、デフォルトはt4g.largeです。 パスワード管理の仕組み vsimageでは、Ubuntuのrootパスワードは以下の二段階で設定します。 AMI作成段階(アーキテクチャ図でいうSTEP 2)で、  ami-creation-execution.yaml  にCloudFormationパラメータとして、AMI管理者が設定したパラメータストアキーを与えます。AMI作成中にパラメータストアからパスワード文字列を読み出し、Ubuntu ルートパスワードを設定します。生成された直後のAMIには、AMI管理者が定義した文字列がルートパスワードとなります。 EC2インスタンス作成段階(アーキテクチャ図でいうSTEP 3)では、開発者が実行する  ec2-launch-from-ami.yaml  にCloudFormationパラメータとして、開発者が設定したパラメータストアキーを与えます。EC2インスタンス作成中にパラメータストアからパスワード文字列を読み出し、Ubuntu ルートパスワードを設定します。もしパラメータストアキーが未設定であったり、パラメータストアの読み出しが失敗した場合は、AMI管理者の設定したルートパスワードのままとなります。 どちらの段階でのルートパスワード設定方法は、  /home/ubuntu/set-password-from-parameter.sh  というスクリプトがOS起動時にsystemctrl により実行されることで設定されます。 作成されるAMIの概略 作成されるAMIは以下の内容となります。 ベースとなるOSは、ubuntu-24-lts-arm64の最新版 EBSボリュームはルート領域の1つで20GBのサイズ ubuntu-desktopパッケージ一式とX windowサーバパッケージを導入 日本語パックパッケージとibus-mozcパッケージを導入 nginxパッケージを導入 certbotパッケージを導入 VSCodeパッケージを導入 Amazon管理コンポーネントのDCVサーバを導入 Amazon管理コンポーネントのCloudWatchエージェントを導入 Amazon管理コンポーネントのAWS CLIを導入 独自にAMI内部に作られるシェルスクリプトは以下3つがあります。いずれもubuntuユーザのホームディレクトリ  /home/ubuntu  に作られます。ルートパスワード設定とSSLサーバ証明症作成はsystemctlのサービスとして追加され、OS起動直後に一度だけ自動実行されます。 前述したルートパスワード設定ファイル:  set-password-from-parameter.sh SSLサーバ証明書作成スクリプト:  setup-ssl.sh SSLサーバ証明書更新スクリプト:  renew-cert.sh vsimageツール利用手順 以下では、CloudFormationテンプレートからCloudFormationスタックを作成する手順を示します。この手順に従い、パラメータを適切に設定すれば、Ubuntuサーバが完成します。 CFnテンプレートのダウンロード ダウンロードリンク vsimageは、以下3つのCFnテンプレートから成り、zip圧縮してあります。 Image Builder構築テンプレート:  imagebuilder-pipeline.yaml AMI作成テンプレート:  ami-creation-execution.yaml EC2インスタンス構築テンプレート:  ec2-launch-from-ami.yaml こちらのリンクからzipファイルをダウンロードしてください。 vsimage-cfn   前提条件 vsimageを使用する前提は以下です。 AMI管理者は、EC2インスタンスを起動後のUbuntu ルートパスワードをパラメータストアに保存しておく必要があります。このパスワードは、実質的には開発者のルートパスワードで上書きされますが、開発者によるパスワード定義がされていない場合にこのルートパスワードが有効になるように設計してあります。 開発者は、DNSルートから検索可能な正規のドメインを持ち、Route 53でホストゾーンを定義している必要があります。これは、最終的にブラウザからアクセスするためのURLとして使うことと、SSLサーバ証明書の署名にも利用します。 開発者は、UbuntuのEC2インスタンスを起動するためのVPCおよびパブリックサブネットを用意しておく必要があります。 開発者は、EC2インスタンスを起動後のUbuntu ルートパスワードをパラメータストアに保存しておく必要があります。 コードを使用する上での注意点 このサンプルコードではツール名称を「ytera-vsimage」と仮称します。ファイル名やパイプライン名などの随所で使用されているので、実際に使用されるときは  grep  などで検索して置き換えてください。 このツールが作成するリソースは、Costタグが付与するようにハードコーディングされています。  Key: Cost   Value: y.terauchi  の部分も適宜置き換えてください。 ImageBuilderの構築手順 まずImage Builder構築テンプレート  imagebuilder-pipeline.yaml  を用いて、Image Builderのパイプライン構築を行います。このスタック実行にはおおよそ5分ほどかかります。 パラメータ一覧 CloudFormationへ与えるパラメータは以下のとおりです。 PasswordParameterName AMI作成段階で指定するUbuntuルートパスワードを収納しているパラメータストアキー名を指定します。 LoggingBucket Image Builderの動作ログを保管するS3バケットを新規作成ときのバケット名を指定します。このパラメータが有効になるのは、 CreateNewBucket をtrueとしたときです。  CreateNewBucket 上記名前で新規作成するときに true を指定します。二回目以降のスタック実行の場合はS3バケットは存在しているので false を指定します。 ExistingBucketName S3バケットを新規作成しない場合に既存バケット名を指定します。新規作成する場合はパラメータ指定をしません。 コマンド例 AWS CLIで実行するときのコマンド例を以下に示します。 【一回目の実行であり、S3バケットを新規作成する場合】 aws cloudformation create-stack \ --stack-name ytera-vsimage-pipeline \ --template-body file://imagebuilder-pipeline.yaml \ --parameters \ ParameterKey=PasswordParameterName,ParameterValue=/ytera-vsimage/ubuntu-password \ ParameterKey=CreateNewBucket,ParameterValue= true \ ParameterKey=LoggingBucket,ParameterValue=ytera-vsimage-imagebuilder-logs-ap-northeast-1 \ --capabilities CAPABILITY_NAMED_IAM \ --region ap-northeast-1 【二回目以降の実行であり、既存のS3バケットを使用する場合】 aws cloudformation create-stack \ --stack-name ytera-vsimage-pipeline \ --template-body file://imagebuilder-pipeline.yaml \ --parameters \ ParameterKey=PasswordParameterName,ParameterValue=/ytera-vsimage/ubuntu-password \ ParameterKey=CreateNewBucket,ParameterValue= false \ ParameterKey=ExistingBucketName,ParameterValue=ytera-vsimage-imagebuilder-logs-ap-northeast-1 \ --capabilities CAPABILITY_NAMED_IAM \ --region ap-northeast-1 AMI作成手順 AMI作成テンプレート  ami-creation-execution.yaml  を実行し、AMI作成を行います。このスタック実行にはおおよそ40分ほどかかります。 パラメータ一覧 PipelineStackName 前工程でパイプラインを作成したスタック名を入力します。 ImageName 作成するAMIの名前を指定します。 EnableImageTests Image Builderは作成したAMIが正常に稼働するかをテストする機能があります。その自動テストを実施するかしないかを選択します。パイプラインではテスト定義をしていないため、falseを指定します。 コマンド例 aws cloudformation create-stack \ --stack-name ytera-vsimage-ami-creation \ --template-body file://ami-creation-execution.yaml \ --parameters \ ParameterKey=PipelineStackName,ParameterValue=ytera-vsimage-pipeline \ ParameterKey=ImageName,ParameterValue=ytera-vsimage-ami \ ParameterKey=EnableImageTests,ParameterValue= false \ --region ap-northeast-1 EC2インスタンスの作成手順 AMIが正常に作成できたら、EC2インスタンス構築テンプレート  ec2-launch-from-ami.yaml  を開発者として実行します。これにより個人の環境にEC2インスタンスを起動します。このスタック実行にはおおよそ10分ほどかかります。 パラメータ一覧 AmiCreationStackName 前工程でAMIを作成したスタック名を入力します。 InstanceType Ubuntuを稼働させるインスタンスタイプを指定します。Gravitonである必要があります。初期値はt4g.large。 VpcId Ubuntuを稼働させるVPC IDを指定します。 SubnetId Ubuntuを稼働させるサブネットIDを指定します。パブリックサブネットである必要あります。 AllowedSubnet Ubuntuへ接続可能なPCのCIDRを指定します。ここで指定したCIDRからUbuntuのTCP 8443ポート、443ポートが許可されます。 DomainName Ubuntuのホスト名を含むFQDNを指定します。これがブラウザからアクセスするときのURLとなります。 Route53HostZoneId FQDNで指定したドメインを定義しているRoute 53のゾーンIDを指定します。このゾーンIDに対してAレコードを追加します。 PasswordParameterName EC2インスタンスのUbuntuルートパスワードとして設定する文字列を収納しているパラメータストアキー名を指定します。 EMAIL SSLサーバ証明書に埋め込むメールアドレスを指定します。何でもかまいません。 コマンド例 aws cloudformation create-stack \ --stack-name ytera-vsimage-user1-instance \ --template-body file://ec2-launch-from-ami.yaml \ --parameters \ ParameterKey=AmiCreationStackName,ParameterValue=ytera-vsimage-ami-creation \ ParameterKey=InstanceType,ParameterValue=t4g.large \ ParameterKey=SubnetId,ParameterValue=subnet-0d29XXXXXXXXXXXXXXd \ ParameterKey=VpcId,ParameterValue=vpc-0e98dXXXXXXXXXXXXXX \ ParameterKey=AllowedSubnet,ParameterValue=2XX.2XX.XX.XX/32 \ ParameterKey=DomainName,ParameterValue=ytera-vsimage-user1-instance.example.com \ ParameterKey=Route53HostZoneId,ParameterValue=Z09XXXXXXXXXXXXXXXXXXXX \ ParameterKey=PasswordParameterName,ParameterValue=/ytera-vsimage/user1-password \ ParameterKey=EMAIL,ParameterValue=ytera@example.com \ --capabilities CAPABILITY_NAMED_IAM \ --region ap-northeast-1 Ubuntuへアクセス EC2インスタンス構築テンプレート  ec2-launch-from-ami.yaml  が正常終了すると、EC2へアクセスするURLが指定したFQDNを下に出力されます。スタックのOutputページを見ていただき、そのURLにDCVでアクセスしてください。 DCVの認証は、Ubuntuの一般ユーザ(ubuntu)とCFnテンプレートで指定したパラメータストアのパスワードでログイン可能です。その後、Ubuntuのログイン画面が出ますが、同じIDとパスワードでログインします。 本ツールのユースケースと今後の課題 本ツールでは、開発環境となるAMI作成と、実際のインスタンス化の工程を分離しているのが特徴です。常に開発環境のバージョンをセキュアに最新に保ち開発者に提供することが比較的簡単にできます。 毎晩AMI作成を自動で実行するようにスケジュールを組んでおき、開発者は毎朝EC2を作成する運用とすることで実現可能です。EC2作成のスタックでは、AMI作成スタックが出力するAMI IDを参照しているので、開発者はどのAMIが最新であるかを意識する必要がありません。 ただ開発者の個人設定などのホームディレクトリを、インスタンス外に自動退避し、EC2作成時に自動で戻す仕組みがまだできていませんが。 ライトなユースケースでは、Ubuntuの開発者のホームディレクトリをS3マウントしておくだけでもよいかもしれません。  Mountpoint for Amazon S3 はメチャ便利だよ Mountpoint for Amazon S3 はS3オブジェクトストレージを、ローカルディスクにマウントしブロックストレージのように扱うことのできる超便利な機能です。その使い方をAmazon Linux 2023 をベースに解説します。 blog.usize-tech.com 2025.08.24 最後に。本記事は、 広野さんの秀逸なCloudFormationがあったからこそ実現できました 。ありがとうございました。
アバター
本記事は TechHarmony Advent Calendar 2025 12/23付の記事です 。 こんにちは。SCSK渡辺(大)です。 肩こりが酷すぎたのでYouTubeで首回りのストレッチ動画を参考にやってみたらスッキリしました。 首剥がしとストレートネック治しの動画はお勧めです。 サンプルのナレッジ 以下のようなナレッジを作ることが出来ました。 背景 楽してナレッジを作りたかったのです。 AIの進化のおかげで重たい腰が上がった ※あくまで一個人の見解です。 恐らく1年前だったら1つのケースをナレッジ化するのに1分掛かるか、トークンが多すぎて1度の指示で全てのケースをナレッジ化することは出来なかったかと思います。 それが今では、 スクリプトの作成から始め、スクリプトの実行、ケースをカテゴリ別に仕分け、すべてのケースのナレッジ化までを丸っとAIにお任せすることが出来ます。 ※Kiroに実行許可をしていない場合には、スクリプトの実行など必要なタイミングでは人間の介在が必要です 最早、AIが「AWS Support のケースをナレッジ化するために進化してきました!」と言っているようなものです。 なので有難く使わせてもらいました。 思い立ったが吉日 ということで早速取り掛かってみようと、AWSマネジメントコンソールの AWS Support の画面を眺めていたところ、約2年前のケースまでしか残っていませんでした。 調べてみたところ、AWS Supportのケース履歴は、AWSマネジメントコンソール上では直近24か月間(2年間)までしか残っていません。[1] また、Boto3のリファレンス[2]では直近12か月(1年間)までとなっていますが、実際にはAWSマネジメントコンソールと同様に、直近24か月間(2年間)まで取得できます。(実際に試しました) つまり、 ナレッジ化したいと感じたらすぐに取り掛かったほうが良いということです。 理由は、直近24か月を超過したケースは見れなくなってしまうためです。 概要 AWS Support でクローズ済のケースをKiroにナレッジ化してもらいました。 大まかな流れ AWS Support ケース情報の取得(リクエスト) AWS Support ケース情報の取得(レスポンス) カテゴリ分類した後、カテゴリ単位でナレッジを作成 もう少し細かい流れ Kiroに実行許可をしていない場合には、スクリプトの実行など必要なタイミングでは人間の介在が必要ですが、殆どKiroにお任せできます。 ユーザーがKiroに指示文(後述)を伝える KiroがPython スクリプトを実行してケース情報を取得 Kiroが進捗管理表を確認 存在する → 続きから処理(5 へ) 存在しない → 新規作成 Kiroが未分類のケースを読み込み、進捗管理表に記録 Kiroがカテゴリ単位で ナレッジ を作成し、進捗管理表を更新 Kiroが未処理のケースがあるか確認 ある → 3 へ戻る ない → 完了 Kiroのセッションが中断した場合 → 新しいセッションで 2 へ戻る 必要なもの ローカル Kiro(他のエージェント型IDEでも良いと思いますが私は試してません) Pythonスクリプト(AWS Support ケース情報を取得する処理) boto3(Pythonスクリプトで使うため必要) Python 3.10 以上(boto3を使うために必要[3]) AWS 認証情報(boto3を使うために必要) AWS CLI 2.32以上(aws loginでAWS認証する場合には必要) AWS AWS Support プラン(Developer/Business/Enterprise) AWS Support ケース(解決済み) IAM権限(以下) { "Version": "2012-10-17", "Statement": [   {     "Effect": "Allow",     "Action": [       "sts:GetCallerIdentity",       "support:DescribeCases",       "support:DescribeCommunications"     ],     "Resource": "*"   } ] } Pythonスクリプト(AWS Support ケース情報を取得する処理) 畳んでいます。 Pythonスクリプト #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ AWS Support ケースダウンローダー サポートケースをダウンロードし、 各ケースをテキストファイルとして保存する。 """ import json import logging import os import sys from datetime import datetime from pathlib import Path import boto3 from botocore.exceptions import NoCredentialsError, ClientError, BotoCoreError OUTPUT_DIR = 'support_cases' EXCLUDED_KEYWORDS = [ "[除外したいケース名に含むキーワード1]", "[除外したいケース名に含むキーワード2]" ] def setup_logging() -> logging.Logger: """ロギング設定""" os.makedirs(OUTPUT_DIR, exist_ok=True) timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') log_file = f"{OUTPUT_DIR}/download_{timestamp}.log" logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_file, encoding='utf-8'), logging.StreamHandler(sys.stdout) ] ) return logging.getLogger(__name__) def get_aws_credentials(): """認証情報を取得""" # ~/.aws/credentials があればboto3デフォルトに任せる creds_file = Path.home() / '.aws' / 'credentials' if creds_file.exists(): return 'default' # aws login キャッシュから取得 cache_dir = Path.home() / '.aws' / 'login' / 'cache' if not cache_dir.exists(): return None cache_files = list(cache_dir.glob('*.json')) if not cache_files: return None latest_file = max(cache_files, key=lambda f: f.stat().st_mtime) with open(latest_file, 'r', encoding='utf-8') as f: data = json.load(f) token = data.get('accessToken', {}) return { 'aws_access_key_id': token.get('accessKeyId'), 'aws_secret_access_key': token.get('secretAccessKey'), 'aws_session_token': token.get('sessionToken'), } def create_session(logger): """boto3 セッション作成""" creds = get_aws_credentials() if creds == 'default': logger.info("Using ~/.aws/credentials") return boto3.Session(region_name='us-east-1') if not creds or not creds.get('aws_access_key_id'): raise NoCredentialsError() logger.info("Using aws login cache") return boto3.Session( aws_access_key_id=creds['aws_access_key_id'], aws_secret_access_key=creds['aws_secret_access_key'], aws_session_token=creds['aws_session_token'], region_name='us-east-1' ) def validate_credentials(session, logger): """認証情報の有効性をテスト""" try: sts = session.client('sts') identity = sts.get_caller_identity() logger.info(f"認証成功: {identity.get('Arn', 'Unknown ARN')}") return True except ClientError as e: error_code = e.response['Error']['Code'] if error_code in ['InvalidUserID.NotFound', 'AccessDenied', 'TokenRefreshRequired', 'ExpiredToken', 'ExpiredTokenException']: logger.error("AWS認証情報が無効または期限切れです。認証情報を確認してください。") else: logger.error(f"AWS API エラー: {error_code} - {e.response['Error']['Message']}") return False except BotoCoreError as e: logger.error(f"AWS接続エラー: {e}") logger.error("ネットワーク接続またはAWS認証情報を確認してください。") return False except Exception as e: logger.error(f"認証テスト中の予期しないエラー: {e}") import traceback logger.error(traceback.format_exc()) return False def download_cases(session, logger): """全サポートケースをダウンロードして保存""" try: client = session.client('support', region_name='us-east-1') except Exception as e: logger.error(f"Supportクライアント作成エラー: {e}") raise logger.info("Fetching cases...") cases = [] next_token = None try: while True: params = {'maxResults': 100, 'includeResolvedCases': True} if next_token: params['nextToken'] = next_token response = client.describe_cases(**params) cases.extend(response.get('cases', [])) next_token = response.get('nextToken') if not next_token: break except ClientError as e: error_code = e.response['Error']['Code'] if error_code == 'SubscriptionRequiredException': logger.error("AWS Supportプランが必要です。Basic以上のサポートプランに加入してください。") return 0 else: logger.error(f"ケース取得エラー: {error_code} - {e.response['Error']['Message']}") return 0 except Exception as e: logger.error(f"ケース取得中の予期しないエラー: {e}") return 0 logger.info(f"Found {len(cases)} cases") saved_count = 0 for idx, case in enumerate(cases, 1): case_id = case['caseId'] subject = case.get('subject', '') logger.info(f"[{idx}/{len(cases)}] Processing: {case_id}") # 除外キーワードのチェック if any(kw in subject for kw in EXCLUDED_KEYWORDS): logger.info(f" -> Excluded (keyword): {subject[:50]}") continue # 進行中のケース(クローズしていないケース)を除外 status = case.get('status', '').lower() if status != 'resolved': logger.info(f" -> Excluded (not resolved): {subject[:50]} (status: {status})") continue try: comms = [] comm_token = None while True: params = {'caseId': case_id} if comm_token: params['nextToken'] = comm_token resp = client.describe_communications(**params) comms.extend(resp.get('communications', [])) comm_token = resp.get('nextToken') if not comm_token: break filename = f"{OUTPUT_DIR}/case_{case_id}.txt" with open(filename, 'w', encoding='utf-8') as f: f.write(f"ケースID: {case_id}\n") f.write(f"件名: {subject}\n") f.write(f"ステータス: {case.get('status', '')}\n") f.write(f"作成日時: {case.get('timeCreated', '')}\n") f.write(f"サービス: {case.get('serviceCode', '')}\n") f.write(f"カテゴリ: {case.get('categoryCode', '')}\n") f.write(f"重要度: {case.get('severityCode', '')}\n") f.write("=" * 60 + "\n\n") for i, comm in enumerate(comms, 1): f.write(f"--- メッセージ {i} ---\n") f.write(f"送信者: {comm.get('submittedBy', '')}\n") f.write(f"日時: {comm.get('timeCreated', '')}\n") f.write(f"内容:\n{comm.get('body', '')}\n\n") saved_count += 1 logger.info(f" -> Saved: {filename}") except Exception as e: logger.error(f" -> Error: {e}") logger.info(f"Completed: {saved_count} cases saved to {OUTPUT_DIR}/") return saved_count def main(): logger = setup_logging() logger.info("Starting download...") try: session = create_session(logger) # 認証情報の有効性をテスト if not validate_credentials(session, logger): return count = download_cases(session, logger) logger.info(f"Done: {count} cases saved to {OUTPUT_DIR}/") except NoCredentialsError: logger.error("AWS認証情報が見つかりません。認証情報を設定してください。") except Exception as e: logger.error(f"予期しないエラー: {e}") logger.error("詳細なエラー情報:") import traceback logger.error(traceback.format_exc()) if __name__ == "__main__": main() Kiroへの指示文 畳んでいます。 指示文 # AWS Support ケースダウンロード & ナレッジ 化 指示書 ## 重要な制約事項(必ず遵守) ### 禁止事項 - **追加のスクリプト(Python、シェルスクリプト等)を新規作成しないこと** - 既存のスクリプト `aws_support_knowledge_generator_cli.py` を勝手に修正しないこと - 指示書に記載されていない処理を勝手に追加しないこと - 出力ディレクトリ以外の場所にファイルを作成しないこと - **Kiro の SPEC 機能を使用しないこと**(この指示書に従って直接処理を行うこと) ### 許可される操作 - `aws_support_knowledge_generator_cli.py` の実行(ケースダウンロード) - `support_cases_knowledge/` ディレクトリへの ナレッジ Markdown ファイルの作成のみ - 既存ファイルの読み取り --- ## 1. ケースダウンロード(スクリプト実行) ### 実行コマンド ```bash python aws_support_knowledge_generator_cli.py ``` ### 動作内容 - 全サポートケースを `support_cases/` にテキストファイルとして保存 - ログは `support_cases/download_YYYYMMDD_HHMMSS.log` に出力 ### 出力形式 各ケースファイル (`case_{ID}.txt`) には以下を含む: - ケース ID、件名、ステータス、作成日時 - サービス、カテゴリ、重要度 - 全メッセージ履歴(送信者、日時、内容) --- ## 2. ナレッジ 化(Kiro による処理) ### 前提条件 - 事前に `python aws_support_knowledge_generator_cli.py` を実行してケースをダウンロード済みであること - `support_cases/` ディレクトリにケースファイルが存在すること ### 処理手順(重要:セッション中断に備えた段階的処理) **基本方針**: ファイル読み込み → 即座に進捗管理表に記載 → カテゴリ単位で ナレッジ 生成 → 次のファイル読み込み #### ステップ 1: 初期確認 1. `support_cases_knowledge/progress_management.md`(進捗管理表)が存在するか確認する 2. 存在する場合は進捗管理表を読み込み、「分類済み」列と「ナレッジ 生成済み」列を確認する #### ステップ 2: ファイル読み込みと即時分類(繰り返し) 1. `support_cases/` から**未分類のケースファイル**を可能な限り読み込む 2. **読み込んだら即座に**進捗管理表に追記する(「分類済み」に `✓`、「ナレッジ 生成済み」は空欄) 3. この時点でセッションが中断しても、次回は進捗管理表を見て続きから処理できる #### ステップ 3: カテゴリ単位での ナレッジ 生成(繰り返し) 1. 進捗管理表から「ナレッジ 生成済み」が空欄のケースを**カテゴリ単位で**抽出する 2. そのカテゴリの ナレッジ ファイルを生成(または追記)する 3. ナレッジ 生成完了後、該当ケースの「ナレッジ 生成済み」列に `✓` を付ける 4. **1 つのカテゴリの ナレッジ 生成が完了してから**、次のカテゴリに進む #### ステップ 4: 未読み込みファイルがあれば繰り返し - ステップ 2〜3 を、全ケースファイルの処理が完了するまで繰り返す ### 進捗管理表(progress_management.md) セッションをまたいでも処理状況を正確に把握できるよう、進捗管理表を管理する。 #### 進捗管理表の形式 ```markdown # 進捗管理表 最終更新: YYYY-MM-DD HH:MM:SS ## 処理状況サマリー - 総ケース数: XX 件 - 分類済み: XX 件 - ナレッジ 生成済み: XX 件 ## 分類済みケース一覧 | ケースファイル | カテゴリ | 件名 | 分類済み | ナレッジ 生成済み | | -------------- | ----------------- | ---------------- | -------- | ------------ | | case_xxx.txt | コスト・請求 | 請求に関する質問 | ✓ | ✓ | | case_yyy.txt | セキュリティ・IAM | IAM 権限の設定 | ✓ | | | case_zzz.txt | ネットワーク | VPC 設定について | ✓ | | ``` #### 進捗管理表の運用ルール(必須) 1. **即時記載の原則**: ケースファイルを読み込んだら、**その時点で即座に**進捗管理表に追記すること(全ファイル読み込み後ではない) 2. **2 段階チェック**: - 「分類済み」列: ファイルを読み込んでカテゴリを判定したら `✓` - 「ナレッジ 生成済み」列: そのケースを含む ナレッジ を生成したら `✓` 3. **カテゴリ単位処理**: 1 つのカテゴリの ナレッジ 生成を完了してから、次のカテゴリのファイル読み込みに進むこと 4. **セッション再開時**: まず進捗管理表を確認し、「分類済み」が空欄のケースのみ読み込み、「ナレッジ 生成済み」が空欄のカテゴリから処理を再開すること 5. **サマリー更新**: 進捗管理表を更新するたびに、処理状況サマリーも更新すること ### 応答言語 **Kiro からの応答は日本語で返すこと** --- ## 3. カテゴリ分類ルール 以下の 8 カテゴリに分類すること。複数カテゴリに該当する場合は、最も関連性の高い 1 つを選択する。 | カテゴリ名 | 対象サービス・キーワード | | --------------------------- | ----------------------------------------------------------------------------- | | 1. アカウント・サポート | アカウント設定、Organizations、サポートプラン、上限緩和申請、Service Quotas | | 2. セキュリティ・IAM | IAM、認証、暗号化、セキュリティグループ、WAF、KMS、Secrets Manager、GuardDuty | | 3. ネットワーク | VPC、Route 53、CloudFront、Direct Connect、ELB、ALB、NLB、Transit Gateway | | 4. コンピューティング | EC2、Lambda、ECS、EKS、Batch、Fargate、Auto Scaling | | 5. ストレージ・データベース | S3、EBS、RDS、DynamoDB、ElastiCache、Aurora、Redshift、EFS | | 6. 開発・CICD | CodePipeline、CodeBuild、CodeDeploy、CloudFormation、CDK、CodeCommit | | 7. 監視・運用 | CloudWatch、CloudTrail、Systems Manager、Config、EventBridge、X-Ray | | 8. コスト・請求 | 料金、請求、Cost Explorer、Savings Plans、Reserved Instance、Billing、Budget | --- ## 4. ナレッジ ファイル生成ルール ### 出力先 - `support_cases_knowledge/` ディレクトリに出力すること(ディレクトリが存在しない場合は作成する) - 他のディレクトリには出力しないこと ### ファイル命名規則 - `ナレッジ_カテゴリ名.md` 形式(例: `ナレッジ_コスト・請求.md`) ### 必須構成要素(すべて必須) > **⚠️ 重要: 目次は必ず作成すること。目次がない ナレッジ ファイルは不完全とみなす。** #### 4.1 目次(ファイル冒頭に必須) **すべての ナレッジ ファイルの冒頭には必ず目次を設けること。** 目次がないファイルは作成完了とみなさない。 ```markdown # ナレッジ: カテゴリ名 最終更新: YYYY-MM-DD ## 目次 - [Q1: 質問タイトル](#q1-質問タイトル) - [Q2: 質問タイトル](#q2-質問タイトル) ... --- (以下、各 ナレッジ の本文) ``` #### 4.2 各 ナレッジ の構成 ```markdown ## Q1: 質問タイトル ### 質問 (背景・状況を含めて、ユーザーからの質問内容を具体的に記載します。どのような状況で何を知りたいのか明確にします) ### 回答 (AWS サポートからの回答を詳細に記載します。手順がある場合はステップバイステップで記載します) ### 関連リンク - [AWS 公式ドキュメント](https://docs.aws.amazon.com/...) ### 元ケース - ファイル: `case_xxx.txt` ``` ### 記載ルール #### 文体 - **「です」「ます」調で統一**(質問、回答、説明文すべて) #### 自己完結性(最重要) - **ナレッジ 単体で問題と解決策が完全に理解できること** - 元ケースを参照しなくても実用的な情報として成立させる - 質問には背景・状況を含める(なぜその質問が発生したか) - 回答は要約ではなく、実際に役立つ詳細さで記載 - 手順がある場合は番号付きリストでステップバイステップで記載 - AWS CLI コマンド、設定値、パラメータは省略せず完全な形で記載 #### 汎用性(重要) - **特定環境に依存しない、誰でも使える ナレッジ にすること** - 以下の情報は汎用的な表現に置き換える: | 置換対象 | 置換後の表現 | | ---------------------- | --------------------------------- | | アカウント ID | `your-account-id` | | ARN | `arn:aws:service:::your-resource` | | バケット名・リソース名 | `your-bucket-name` 等 | | メールアドレス | `your-email@example.com` | | IP アドレス | `xxx.xxx.xxx.xxx` | | 組織 ID | `o-xxxxxxxxxx` | #### 禁止事項 - 「〜については元ケースを参照してください」という記載 - 具体的な値やコマンドを省略した曖昧な説明 - 元のケース内容に勝手な解釈を加えること --- ## 5. エラー対処 | エラー種別 | 対処方法 | | ---------- | ------------------------------------------------------------ | | 認証エラー | AWS 認証情報が不足しています。適切な認証設定を行ってください | | 権限エラー | Business/Enterprise Support プランが必要 | 学んだこと 今回の学びで大きかったことは、Kiroの利用においては 要約機能[4]が動くことを前提にした指示文にすべき だと感じました。 クレジット垂れ流し事件を起こさない方法 2025年12月12日にAWS IAM Identity CenterのユーザーでもClaude Opus 4.5が使えるようになった[5]ので、良い機会なのでClaude Opus 4.5のみ使用するように設定して指示文書を渡してみたのですが、なんとケース情報ファイルの読み込みだけでコンテキストを食い潰し、いざナレッジを作ろうとなった時にコンテキストの上限(80%)に達してしまいました。 それだけならまだ良かったのですが、要約機能が動いた後に再度ケース情報ファイルの読み込みが始まってしまい、またコンテキストの上限に達してしまい要約機能が動いて・・・という負のループに陥ってしまい、クレジットが垂れ流し状態になってしまいました。 上記の対応策として進捗管理表を作りました。 進捗管理表などを作成して随時更新するなどして要約機能が動いたとしても次のセッションで進捗が引き継がれるようにするべきであると、既に様々なところで言われていることですが今回とても痛感しました。 一方で、仕様書や設計書も作る必要があるような場合にはSpecs機能を使ってタスク管理しながら進めるほうが良いと思います。 Claude Opus 4.5 を利用した時のクレジット消費量が凄まじい 上記に関連してお伝えすると、Claude Opus 4.5 を利用した時のクレジット消費量が凄まじいです。 当記事に載せた指示文で実行した場合、完了するまでに約40クレジット消費しました。 約40ケースがナレッジ化の対象だったので、偶然かもしれませんが1クレジット/ケースでした。 Kiroを使ったことがある人は以下の画像を見ると震えるかもしれません。 ファイル名には「/」を含めないように指示するべき ファイル名に「/」を含めるように指示をしてしまうと、「/」より前がディレクトリで後ろがファイル名として判断されてしまいます。 対応策の具体例は、「開発・CI/CD.md」は避けて「開発・CICD.md」で指示すべきです。  そうしないと以下の画像のようになってしまいます。 まとめ 業務におけるナレッジ作成は大変なので、AWS Support のケースだけでも楽にナレッジ化できそうであることが分かって良かったです。 後続の運用を考えた時には最新化や棚卸も必要になると思いますが、再度 AWS Support に問い合わせするようなことはしなくても、MCPサーバーを使って最新の情報を確認できるようにしてあげれば、それらの作業もAIで楽に出来そうですね。 私が実際の業務で使うかどうかは別問題ですが、当記事がどなたかの役に立ちましたら幸いです。 Kiro に興味を持った方は、ぜひ  Kiro 公式サイト をチェックしてみてください。 参考情報 [1] AWS プレミアムサポートのよくある質問 –抜粋——————————————————- ケース履歴の保存期間はどれほどですか? ケース履歴情報は、作成後 24 か月間ご利用いただけます。 ————————————————————– [2] describe_cases – Boto3 1.42.9 documentation –抜粋(日本語訳)—————————————— ケースデータは作成後12か月間利用可能です。12か月以上前に作成されたケースの場合、リクエストがエラーを返す可能性があります。 ————————————————————– [3] Quickstart – Boto3 1.42.9 documentation –抜粋(日本語訳)—————————————— Pythonのインストールまたはアップデート Boto3をインストールする前に、Python 3.10以降を使っていることを確認してください。  ————————————————————– [4] Summarization – IDE – Docs – Kiro –抜粋(日本語訳)—————————————— 要約 すべての言語モデルには「コンテキストウィンドウ」があり、これはモデルが一度に処理できる最大テキスト量です。コンテキストウィンドウの長さはモデルによって異なります。 Kiroと会話すると、その会話の過去のすべてのメッセージをメモリーとしてモデルに送信し、モデルが最新の応答を生成する際にそれらを考慮できるようにします。会話が長くなると、モデルのコンテキストウィンドウの制限に近づき始めます。この時、Kiroは会話内のすべてのメッセージを自動的に要約し、コンテキストの長さを制限以下に戻します。 チャットパネルのコンテキスト使用メーターを使って、モデルのコンテキスト制限のどのくらいの割合が使われているかを把握できます。使用率がモデルの上限の80%に達すると、Kiroは自動的に会話をまとめます。 ————————————————————– [5] Claude Opus 4.5 support for AWS IAM Identity Center users – Kiro –抜粋(日本語訳)—————————————— AWS IAM Identity Centerユーザー向けのClaude Opus 4.5サポート us-east-1 および eu-central-1 リージョンの両方で、AWS IAM Identity Centerユーザー向けにClaude Opus 4.5のサポートを追加しました。Claude Opus 4.5は Kiro IDE と Kiro CLI の両方で利用可能です。 ————————————————————–
アバター
  本記事は TechHarmony Advent Calendar 2025 12/22付の記事です 。 こんにちは、稲葉です。 アドベントカレンダーの機会で、普段触っていないコンテナに触れてみようと思いました。 本記事ではコンテナでWordPressサイトを作成し、Amazon ECSにデプロイするところまで試してみようと思います。 構成 本記事で試す構成です。 検証用にローカルPCのDockerでWordPressサイトを構築します。 その後、本番用にECSでWordPressを公開します。 ローカルPCにWordPressサイトを構築する まずはdocker composeでWordPress環境を作成してみました。 下記のような構成で作成しました。 . ├── docker-compose.yml ├── .env ├── nginx │ ├── default.conf │ └── nginx.conf └── php-fpm ├── Dockerfile └── php.ini ファイル名 内容 ./docker-compose.yml 複数コンテナをまとめて管理するためのdocker compose用設定ファイル .env docker-compose.yml用の環境変数を記載するファイル (コード例は本記事の最後) ./nginx/default.conf nginxのサーバー設定用ファイル、php-fpm コンテナへ転送する設定などを記載 (コードは本記事の最後) ./nginx/nginx.conf nginxの基本設定ファイル(コードは本記事の最後) ./php-fpm/Dockerfile WordPressとphp-fpmの環境を作るためのDockerファイル ※nginx上でWordPressを公開する場合、WordPressはphp-fpmと同時に使用する必要がある (コードは本記事の最後) ./php-fpm/php.ini phpの設定ファイル(コードは本記事の最後) docker-compose.yml 下記設定docker-compose.ymlファイルで、ローカルPCのWordPressサイトを構築します。 services: nginx: image:nginx:alpine ports: - "8080:80" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf - ./nginx/default.conf:/etc/nginx/conf.d/default.conf - wordpress_data:/var/www/html depends_on: - php-fpm networks: - wordpress-network php-fpm: build: context:./php-fpm dockerfile:Dockerfile volumes: - wordpress_data:/var/www/html environment: WORDPRESS_DB_HOST: ${WORDPRESS_DB_HOST} WORDPRESS_DB_USER: ${WORDPRESS_DB_USER} WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD} WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME} depends_on: - mysql networks: - wordpress-network mysql: image:mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} volumes: - mysql_data:/var/lib/mysql networks: - wordpress-network volumes: wordpress_data: mysql_data: networks: wordpress-network: driver:bridge このdocker-compose.ymlでは3つのコンテナを立てる設定が記載されています。 nginx php-fpm mysql それぞれのコンテナの設定について簡単に記載します。 nginxコンテナ image:nginx:alpineで、nginxのコンテナはnginx:alpineというイメージを元にコンテナを作成する設定をしています。 ports: “8080:80″で、ローカルホストの8080番ポートにアクセスした際にコンテナの80番ポートに転送する設定をしています。 volumes: ./nginx/nginx.conf:/etc/nginx/nginx.confで、ローカルPCの./nginx/nginx.confとコンテナ環境の/etc/nginx/nginx.confを同期しています。 volumes: ./nginx/default.conf:/etc/nginx/conf.d/default.confでも同様です。 volumes: wordpress_data:/var/www/htmlでは、wordpress_data名前付きボリュームでコンテナ内の/var/www/html にマウントする設定をしています。 名前付きボリュームとは、ローカルPCのDockerで名前を付けて管理するボリュームになります。ローカルPC内の他のDockerコンテナからもマウントして使用可能です。 depends_on: php-fpmで、php-fpmのコンテナを作成した後にnginxコンテナを作成する設定をしています。 networks: wordpress-networkで、wordpress-networkというDockerネットワークにnginxコンテナを属させる設定を行っています。 Dockerネットワークに属すことで、属しているコンテナ間でサービス名を使って名前解決可能になります。 php-fpmコンテナ build: context:./php-fpmで、./php-fpmディレクトリをビルドコンテキストとして使用する設定をしています。 build: dockerfile:Dockerfileで、コンテナを立てるときに使用するDockerfileを指定しています。 volumesは、nginxと同様でコンテナ内の/var/www/html に名前付きボリュームのwordpress_dataをマウントしています。 この設定によって、php-fpmコンテナとnginxコンテナの/var/www/htmlは同期されます。 environment: WORDPRESS_DB_HOST:${WORDPRESS_DB_HOST}で、環境変数WORDPRESS_DB_HOSTの値に.envファイルのWORDPRESS_DB_HOSTで指定している値を使用する設定をしています。 他も同様になります。 mysqlコンテナ volumes: mysql_data:/var/lib/mysqlで、このデータベースのデータをmysql_dataという名前付きボリュームで管理することで永続化しています。 他の設定は、他のコンテナのところで説明しているため省略します。 ローカルPCのWordpressサイトに接続する コードができたら下記コマンドで、nginxとphp-fpm、mysqlの3つのコンテナを立てて、ローカルPCのWordPressサイトを構築できます。 docker compose up -d http://localhost:8080でローカルサーバーにアクセスすると、WordPressの初期設定画面が開きます。 まずは言語設定が聞かれるので、日本語を選択します。 次にデータベースの接続設定画面になりますが、こちらは.envに書いてある下記内容で入力します。 項目名 値 データベース名 wordpress ユーザー名 wordpress パスワード wordpress_password データベースホスト mysql テーブル接頭辞 wp_ そのまま初期設定とテスト記事を書くと、このように記事を表示できました。 これにてローカルPCのWordPress環境構築は完了です。 AWS環境にWordPressサイトを構築する それでは、AWS環境にWordPressサイトを構築していきます。 ECRリポジトリを作成する まずはECSで実行するためのコンテナイメージファイルを配置するためのECRリポジトリを作成します。 AWSの環境では、MySQLデータベースのところはRDSが担当するので、mysqlコンテナは立てません。 下記のシェルスクリプトを作成し、AWS CLIでECRリポジトリを作成しました。 ※下記シェルスクリプトを実行する前に、aws loginなどでAWSの認証を通してください。 . ├── docker-compose.yml ├── .env ├── nginx ├── php-fpm └── create-ecr.sh <- new #!/bin/bash # 設定 AWS_REGION="ap-northeast-1" PROJECT_NAME="wordpress" echo "=== ECRリポジトリを作成 ===" # Nginx用ECRリポジトリを作成 echo "Nginx用ECRリポジトリを作成中..." aws ecr create-repository \ --repository-name ${PROJECT_NAME}-nginx \ --region ${AWS_REGION} # PHP用ECRリポジトリを作成 echo "PHP用ECRリポジトリを作成中..." aws ecr create-repository \ --repository-name ${PROJECT_NAME}-php \ --region ${AWS_REGION} # アカウントIDを取得 ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) echo "=== ECRリポジトリ作成完了 ===" echo "Nginx ECR URL: ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${PROJECT_NAME}-nginx" echo "PHP ECR URL: ${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${PROJECT_NAME}-php" echo "" echo "次に ./image-push.sh を実行してイメージをプッシュしてください" ECRにpushするイメージを作成する ECRにプッシュするために、nginx用とphp-fpm(とwordpress)用のイメージを作成します。 . ├── docker-compose.yml ├── .env ├── nginx ├── php-fpm ├── create-ecr.sh └── aws-ecs <- new ├── Dockerfile.nginx <- new └── Dockerfile.php <- new Dockerfile.nginx FROM nginx:alpine # 必要なパッケージをインストール(EFS用) RUN apk add --no-cache nfs-utils # Nginxの設定ファイルをコピー COPY nginx/nginx.conf /etc/nginx/nginx.conf COPY nginx/default.conf /etc/nginx/conf.d/default.conf # WordPressファイル用のディレクトリを作成 RUN mkdir -p /var/www/html # EFS用のマウントポイントを作成 VOLUME ["/var/www/html"] # ヘルスチェック用の設定を追加 RUN echo 'server {' > /etc/nginx/conf.d/health.conf && \ echo ' listen 80;' >> /etc/nginx/conf.d/health.conf && \ echo ' server_name _;' >> /etc/nginx/conf.d/health.conf && \ echo ' location /health {' >> /etc/nginx/conf.d/health.conf && \ echo ' access_log off;' >> /etc/nginx/conf.d/health.conf && \ echo ' return 200 "healthy\\n";' >> /etc/nginx/conf.d/health.conf && \ echo ' add_header Content-Type text/plain;' >> /etc/nginx/conf.d/health.conf && \ echo ' }' >> /etc/nginx/conf.d/health.conf && \ echo '}' >> /etc/nginx/conf.d/health.conf EXPOSE 80 # Nginxをフォアグラウンドで実行 CMD ["nginx", "-g", "daemon off;"] Dockerfile.php FROM php:8.2-fpm # 必要なパッケージをインストール RUN apt-get update && apt-get install -y \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ libzip-dev \ unzip \ nfs-common \ gettext-base \ && docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j$(nproc) gd \ && docker-php-ext-install pdo_mysql \ && docker-php-ext-install mysqli \ && docker-php-ext-install zip \ && docker-php-ext-install opcache \ && rm -rf /var/lib/apt/lists/* # WordPressをダウンロードしてインストール RUN curl -O https://wordpress.org/latest.tar.gz \ && tar xzf latest.tar.gz \ && mkdir -p /var/www/html \ && mkdir -p /tmp/wordpress \ && cp -R wordpress/* /tmp/wordpress/ \ && cp -R wordpress/* /var/www/html/ \ && rm -rf wordpress latest.tar.gz \ && chown -R www-data:www-data /var/www/html # wp-config.phpのテンプレートは不要(起動時に作成) # PHP設定をコピー COPY php-fpm/php.ini /usr/local/etc/php/ # 起動スクリプトを作成 RUN echo '#!/bin/bash' > /start.sh && \ echo 'set -e' >> /start.sh && \ echo 'echo "Starting WordPress PHP-FPM container..."' >> /start.sh && \ echo 'echo "DB Host: $WORDPRESS_DB_HOST"' >> /start.sh && \ echo 'echo "DB Name: $WORDPRESS_DB_NAME"' >> /start.sh && \ echo 'echo "DB User: $WORDPRESS_DB_USER"' >> /start.sh && \ echo '' >> /start.sh && \ echo '# WordPressファイルがEFSにない場合はコピー' >> /start.sh && \ echo 'if [ ! -f /var/www/html/wp-config-sample.php ]; then' >> /start.sh && \ echo ' echo "Copying WordPress files to EFS..."' >> /start.sh && \ echo ' cp -R /tmp/wordpress/* /var/www/html/' >> /start.sh && \ echo 'fi' >> /start.sh && \ echo '' >> /start.sh && \ echo '# wp-config.phpを作成' >> /start.sh && \ echo 'cp /var/www/html/wp-config-sample.php /var/www/html/wp-config.php' >> /start.sh && \ echo 'sed -i "s/database_name_here/$WORDPRESS_DB_NAME/" /var/www/html/wp-config.php' >> /start.sh && \ echo 'sed -i "s/username_here/$WORDPRESS_DB_USER/" /var/www/html/wp-config.php' >> /start.sh && \ echo 'sed -i "s/password_here/$WORDPRESS_DB_PASSWORD/" /var/www/html/wp-config.php' >> /start.sh && \ echo 'sed -i "s/localhost/$WORDPRESS_DB_HOST/" /var/www/html/wp-config.php' >> /start.sh && \ echo '' >> /start.sh && \ echo '# ファイルの権限を設定' >> /start.sh && \ echo 'chown -R www-data:www-data /var/www/html' >> /start.sh && \ echo '' >> /start.sh && \ echo 'echo "Starting PHP-FPM..."' >> /start.sh && \ echo 'exec php-fpm' >> /start.sh && \ chmod +x /start.sh WORKDIR /var/www/html EXPOSE 9000 CMD ["/start.sh"] 作成したイメージをECRにpushする 次は、作成したDockerfileでイメージを作成し、ECRリポジトリにプッシュします。 下記のシェルスクリプトを作成し、AWS CLIでプッシュしました。 ※下記シェルスクリプトを実行する前に、aws loginなどでAWSの認証を通してください。 . ├── docker-compose.yml ├── .env ├── nginx ├── php-fpm ├── create-ecr.sh ├── image-push.sh <- new └── aws-ecs #!/bin/bash # 設定 AWS_REGION="ap-northeast-1" ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) ECR_REPO_BASE="${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com" NGINX_REPO="${ECR_REPO_BASE}/wordpress-nginx" PHP_REPO="${ECR_REPO_BASE}/wordpress-php" echo "=== DockerイメージをECRにプッシュ ===" # ECRにログイン echo "ECRにログイン中..." aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REPO_BASE} # Nginxイメージをビルド・プッシュ echo "Nginxイメージをビルド中..." docker build -f aws-ecs/Dockerfile.nginx -t ${NGINX_REPO}:latest . docker push ${NGINX_REPO}:latest # PHPイメージをビルド・プッシュ echo "PHPイメージをビルド中..." docker build -f aws-ecs/Dockerfile.php -t ${PHP_REPO}:latest . docker push ${PHP_REPO}:latest echo "イメージプッシュ完了!" echo "次にTerraformでECS環境を構築してください" TerraformでAWSリソースを構築する ECSでWordpressサイトを公開するために必要なAWSリソースを構築します。 量が多いのでTerraformで構築します。 . ├── docker-compose.yml ├── .env ├── nginx ├── php-fpm ├── create-ecr.sh ├── image-push.sh ├── aws-ecs └── terraform <- new ├── main.tf <- new ├── output.tf <- new ├── variables.tf <- new └── terraform.tfvars <- new 各コードについては、本記事の最後に記載しています。 次のコマンドを叩くことでAWSリソースを構築します。 cd terraform terraform init terraform plan terraform apply AWS環境のWordPressサイトに接続する albのエンドポイントにアクセスするとWordPressの初期設定画面が開きます。 ローカルPCのWordPressサイトと同様に設定することで、WordPressサイトを公開できました。 終わりに Kiroと二人三脚で構築することができました! Dockerに触れた経験が少なかったので、WordPress環境を作ることで理解が深まったような気がします。 アドベントカレンダー当日の朝まで書いていました。。 コード ./.env # MySQL設定 MYSQL_ROOT_PASSWORD=root_password MYSQL_DATABASE=wordpress MYSQL_USER=wordpress MYSQL_PASSWORD=wordpress_password # WordPress設定 WORDPRESS_DB_HOST=mysql WORDPRESS_DB_USER=wordpress WORDPRESS_DB_PASSWORD=wordpress_password WORDPRESS_DB_NAME=wordpress ./nginx/default.conf server { listen 80; server_name localhost; root /var/www/html; index index.php index.html index.htm; client_max_body_size 100M; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { fastcgi_pass php-fpm:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /\. { deny all; } location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } } ./nginx/nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/conf.d/*.conf; } ./php-fpm/Dockerfile FROM php:8.2-fpm # 必要なパッケージをインストール RUN apt-get update && apt-get install -y \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ libzip-dev \ unzip \ && docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j$(nproc) gd \ && docker-php-ext-install pdo_mysql \ && docker-php-ext-install mysqli \ && docker-php-ext-install zip \ && docker-php-ext-install opcache # WordPressをダウンロード RUN curl -O https://wordpress.org/latest.tar.gz \ && tar xzf latest.tar.gz \ && cp -R wordpress/* /var/www/html/ \ && rm -rf wordpress latest.tar.gz \ && chown -R www-data:www-data /var/www/html # PHP設定 COPY php.ini /usr/local/etc/php/ WORKDIR /var/www/html EXPOSE 9000 ./php-fpm/php.ini upload_max_filesize = 100M post_max_size = 100M memory_limit = 256M max_execution_time = 300 max_input_vars = 3000 ; OPcache設定 opcache.enable=1 opcache.memory_consumption=128 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=4000 opcache.revalidate_freq=2 opcache.fast_shutdown=1 ./terraform/output.tf output "alb_dns_name" { description ="DNS name of the load balancer" value =aws_lb.main.dns_name } output "aurora_cluster_endpoint" { description ="Aurora cluster endpoint" value =aws_rds_cluster.main.endpoint } output "aurora_reader_endpoint" { description ="Aurora reader endpoint" value =aws_rds_cluster.main.reader_endpoint } output "ecs_cluster_name" { description ="Name of the ECS cluster" value =aws_ecs_cluster.main.name } output "efs_file_system_id" { description ="EFS file system ID" value =aws_efs_file_system.wordpress.id } output "efs_dns_name" { description ="EFS DNS name" value =aws_efs_file_system.wordpress.dns_name } ./terraform/variables.tf variable "aws_region" { description ="AWS region" type =string default ="ap-northeast-1" } variable "project_name" { description ="Project name for resource naming" type =string default ="wordpress" } variable "db_username" { description ="Database username" type =string default ="wordpress" } variable "db_password" { description ="Database password" type =string sensitive =true } ./terraform/terraform.tfvars aws_region = "ap-northeast-1" project_name = "wordpress" db_username = "wordpress" db_password = "your-secure-password-here" ./terraform/main.tf terraform { required_providers { aws ={ source="hashicorp/aws" version="~> 5.57.0" } } } provider "aws" { region = var.aws_region profile ="inaba" } # Data sources data "aws_availability_zones" "available" { state ="available" } data "aws_caller_identity" "current" {} # VPC resource "aws_vpc" "main" { cidr_block ="10.0.0.0/16" enable_dns_hostnames =true enable_dns_support =true tags ={ Name="${var.project_name}-vpc" } } # Internet Gateway resource "aws_internet_gateway" "main" { vpc_id =aws_vpc.main.id tags ={ Name="${var.project_name}-igw" } } # Public Subnets resource "aws_subnet" "public" { count =2 vpc_id =aws_vpc.main.id cidr_block ="10.0.${count.index+1}.0/24" availability_zone = data.aws_availability_zones.available.names[count.index] map_public_ip_on_launch =true tags ={ Name="${var.project_name}-public-subnet-${count.index+1}" } } # Private Subnets resource "aws_subnet" "private" { count =2 vpc_id =aws_vpc.main.id cidr_block ="10.0.${count.index+10}.0/24" availability_zone = data.aws_availability_zones.available.names[count.index] tags ={ Name="${var.project_name}-private-subnet-${count.index+1}" } } # Route Table for Public Subnets resource "aws_route_table" "public" { vpc_id =aws_vpc.main.id route { cidr_block ="0.0.0.0/0" gateway_id =aws_internet_gateway.main.id } tags ={ Name="${var.project_name}-public-rt" } } # Route Table Associations for Public Subnets resource "aws_route_table_association" "public" { count =2 subnet_id =aws_subnet.public[count.index].id route_table_id =aws_route_table.public.id } # NAT Gateway for Private Subnets (1台のみ) resource "aws_eip" "nat" { domain ="vpc" depends_on =[aws_internet_gateway.main] tags ={ Name="${var.project_name}-nat-eip" } } resource "aws_nat_gateway" "main" { allocation_id =aws_eip.nat.id subnet_id =aws_subnet.public[0].id tags ={ Name="${var.project_name}-nat-gateway" } depends_on =[aws_internet_gateway.main] } # Route Table for Private Subnets (共通) resource "aws_route_table" "private" { vpc_id =aws_vpc.main.id route { cidr_block ="0.0.0.0/0" nat_gateway_id =aws_nat_gateway.main.id } tags ={ Name="${var.project_name}-private-rt" } } # Route Table Associations for Private Subnets resource "aws_route_table_association" "private" { count =2 subnet_id =aws_subnet.private[count.index].id route_table_id =aws_route_table.private.id } # Security Group for ALB resource "aws_security_group" "alb" { name_prefix ="${var.project_name}-alb-" vpc_id =aws_vpc.main.id ingress { description ="HTTP from allowed IP" from_port =80 to_port =80 protocol ="tcp" cidr_blocks =["x.x.x.x7/32"] } ingress { description ="HTTPS from allowed IP" from_port =443 to_port =443 protocol ="tcp" cidr_blocks =["x.x.x.x/32"] } egress { description ="All outbound traffic" from_port =0 to_port =0 protocol ="-1" cidr_blocks =["0.0.0.0/0"] } tags ={ Name="${var.project_name}-alb-sg" } lifecycle { create_before_destroy =true } } # Security Group for ECS resource "aws_security_group" "ecs" { name_prefix ="${var.project_name}-ecs-" vpc_id =aws_vpc.main.id ingress { description ="HTTP from ALB" from_port =80 to_port =80 protocol ="tcp" security_groups =[aws_security_group.alb.id] } egress { description ="All outbound traffic" from_port =0 to_port =0 protocol ="-1" cidr_blocks =["0.0.0.0/0"] } tags ={ Name="${var.project_name}-ecs-sg" } lifecycle { create_before_destroy =true } } # Security Group for EFS resource "aws_security_group" "efs" { name_prefix ="${var.project_name}-efs-" vpc_id =aws_vpc.main.id ingress { description ="NFS from ECS" from_port =2049 to_port =2049 protocol ="tcp" security_groups =[aws_security_group.ecs.id] } egress { description ="All outbound traffic" from_port =0 to_port =0 protocol ="-1" cidr_blocks =["0.0.0.0/0"] } tags ={ Name="${var.project_name}-efs-sg" } lifecycle { create_before_destroy =true } } # Security Group for RDS resource "aws_security_group" "rds" { name_prefix ="${var.project_name}-rds-" vpc_id =aws_vpc.main.id ingress { description ="MySQL from ECS" from_port =3306 to_port =3306 protocol ="tcp" security_groups =[aws_security_group.ecs.id] } egress { description ="All outbound traffic" from_port =0 to_port =0 protocol ="-1" cidr_blocks =["0.0.0.0/0"] } tags ={ Name="${var.project_name}-rds-sg" } lifecycle { create_before_destroy =true } } # EFS File System resource "aws_efs_file_system" "wordpress" { creation_token ="${var.project_name}-efs" performance_mode ="generalPurpose" throughput_mode ="provisioned" provisioned_throughput_in_mibps =10 encrypted =true tags ={ Name="${var.project_name}-efs" } } # EFS Mount Targets resource "aws_efs_mount_target" "wordpress" { count =2 file_system_id =aws_efs_file_system.wordpress.id subnet_id =aws_subnet.private[count.index].id security_groups =[aws_security_group.efs.id] } # Aurora Subnet Group resource "aws_db_subnet_group" "main" { name ="${var.project_name}-aurora-subnet-group" subnet_ids =aws_subnet.private[*].id tags ={ Name="${var.project_name}-aurora-subnet-group" } } # Aurora Cluster resource "aws_rds_cluster" "main" { cluster_identifier ="${var.project_name}-aurora-cluster" engine ="aurora-mysql" engine_version ="8.0.mysql_aurora.3.04.2" database_name ="wordpress" master_username = var.db_username master_password = var.db_password db_subnet_group_name =aws_db_subnet_group.main.name vpc_security_group_ids =[aws_security_group.rds.id] backup_retention_period =7 preferred_backup_window ="03:00-04:00" skip_final_snapshot =true deletion_protection =false tags ={ Name="${var.project_name}-aurora-cluster" } } # Aurora Instance (1台のみ) resource "aws_rds_cluster_instance" "main" { identifier ="${var.project_name}-aurora-instance" cluster_identifier =aws_rds_cluster.main.id instance_class ="db.r5.large" engine =aws_rds_cluster.main.engine engine_version =aws_rds_cluster.main.engine_version tags ={ Name="${var.project_name}-aurora-instance" } } # CloudWatch Log Group resource "aws_cloudwatch_log_group" "ecs" { name ="/ecs/${var.project_name}" retention_in_days =7 tags ={ Name="${var.project_name}-log-group" } } # ECS Cluster resource "aws_ecs_cluster" "main" { name ="${var.project_name}-cluster" tags ={ Name="${var.project_name}-cluster" } } # IAM Role for ECS Execution resource "aws_iam_role" "ecs_execution" { name ="${var.project_name}-ecs-execution-role" assume_role_policy =jsonencode({ Version="2012-10-17" Statement= [ { Action="sts:AssumeRole" Effect="Allow" Principal= { Service="ecs-tasks.amazonaws.com" } } ] }) tags ={ Name="${var.project_name}-ecs-execution-role" } } resource "aws_iam_role_policy_attachment" "ecs_execution" { role =aws_iam_role.ecs_execution.name policy_arn ="arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" } # CloudWatch Logs用のIAMポリシー resource "aws_iam_role_policy" "ecs_logs" { name ="${var.project_name}-ecs-logs-policy" role =aws_iam_role.ecs_execution.id policy =jsonencode({ Version="2012-10-17" Statement= [ { Effect="Allow" Action= [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ] Resource="arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*" } ] }) } # ECS Task Definition resource "aws_ecs_task_definition" "main" { family = var.project_name network_mode ="awsvpc" requires_compatibilities =["FARGATE"] cpu ="512" memory ="1024" execution_role_arn =aws_iam_role.ecs_execution.arn volume { name ="wordpress-efs" efs_volume_configuration { file_system_id =aws_efs_file_system.wordpress.id root_directory ="/" } } container_definitions =jsonencode([ { name="nginx" image="${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/${var.project_name}-nginx:latest" portMappings= [ { containerPort=80 protocol="tcp" } ] essential= true dependsOn= [ { containerName="php-fpm" condition="START" } ] mountPoints= [ { sourceVolume="wordpress-efs" containerPath="/var/www/html" readOnly= false } ] logConfiguration= { logDriver="awslogs" options= { "awslogs-group" = aws_cloudwatch_log_group.ecs.name "awslogs-region" = var.aws_region "awslogs-stream-prefix" = "nginx" } } }, { name="php-fpm" image="${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/${var.project_name}-php:latest" essential= true mountPoints= [ { sourceVolume="wordpress-efs" containerPath="/var/www/html" readOnly= false } ] environment= [ { name="WORDPRESS_DB_HOST" value= aws_rds_cluster.main.endpoint }, { name="WORDPRESS_DB_NAME" value= aws_rds_cluster.main.database_name }, { name="WORDPRESS_DB_USER" value= aws_rds_cluster.main.master_username }, { name="WORDPRESS_DB_PASSWORD" value=var.db_password } ] logConfiguration= { logDriver="awslogs" options= { "awslogs-group" = aws_cloudwatch_log_group.ecs.name "awslogs-region" = var.aws_region "awslogs-stream-prefix" = "php-fpm" } } } ]) tags ={ Name="${var.project_name}-task" } } # Application Load Balancer resource "aws_lb" "main" { name ="${var.project_name}-alb" internal =false load_balancer_type ="application" security_groups =[aws_security_group.alb.id] subnets =aws_subnet.public[*].id enable_deletion_protection =false tags ={ Name="${var.project_name}-alb" } } # Target Group resource "aws_lb_target_group" "main" { name ="${var.project_name}-tg" port =80 protocol ="HTTP" vpc_id =aws_vpc.main.id target_type ="ip" health_check { enabled =true healthy_threshold =2 interval =30 matcher ="200,302" path ="/" port ="traffic-port" protocol ="HTTP" timeout =5 unhealthy_threshold =2 } tags ={ Name="${var.project_name}-tg" } } # ALB Listener resource "aws_lb_listener" "main" { load_balancer_arn =aws_lb.main.arn port ="80" protocol ="HTTP" default_action { type ="forward" target_group_arn =aws_lb_target_group.main.arn } } # ECS Service resource "aws_ecs_service" "main" { name ="${var.project_name}-service" cluster =aws_ecs_cluster.main.id task_definition =aws_ecs_task_definition.main.arn desired_count =1 launch_type ="FARGATE" network_configuration { subnets =aws_subnet.private[*].id security_groups =[aws_security_group.ecs.id] assign_public_ip =false } load_balancer { target_group_arn =aws_lb_target_group.main.arn container_name ="nginx" container_port =80 } depends_on =[ aws_lb_listener.main, aws_efs_mount_target.wordpress ] tags ={ Name="${var.project_name}-service" } }
アバター
本記事は TechHarmony Advent Calendar 2025 12/21付の記事です 。 皆さんこんにちは。UGです。 クリスマスが今年もやってくるということで、サンタクロースはなぜ 赤い服 を着ているのでしょうか? と今ではサンタクロースよりもカーネル・サンダースという白髭を好む私はふと疑問に思いました。 カーネルサンダースも思えば白と赤だなぁ?というのはおいておいて、サンタクロースの服が赤というのは、サンタクロースのモデルとされるセント・ニコラスが生前、教会の儀式の際に着ていた服の色がもとになったと言われているみたいです。 あとはコカ・コーラの宣伝によるものらしいです。 サンタはどうして「赤い衣装」を着ているの? あなたの朝がいつもイイ朝でありますように—ニッポン放送『羽田美智子のいってらっしゃい』。12月21日放送分のテーマは「サンタクロースの疑問」です。 「サンタクロースの服=赤」というイメージがあります。なぜ赤な... news.1242.com ちなみに私のチャッピー君は「20世紀初頭の印刷技術で赤が最も再現しやすかったから」と教えてくれましたが、調べても出てこなかったので問い詰めたところ嘘と白状しました。。。 皆さんもハルシネーションには気を付けましょう。 さて本題ですが、今回は新規作成したIAMユーザのMFA設定を自動でチェックし、設定されていなければ自動で無効化する仕組みを実装してみたのでご紹介出来たらなと思います! 結論 まず先に構成やコードを載せておきますので、説明よりも早く実装したいという方は参考にしていただければと思います。 前提:CloudTrailが有効化されていること 構成:EventBrige + Step Functions 注意点:バージニア北部(us-east-1)で構築すること EventBridgeルール: { "source": ["aws.iam"], "detail-type": ["AWS API Call via CloudTrail"], "detail": { "eventName": ["CreateUser"] } } Step Functions ASL: { "Comment": "A description of my state machine", "StartAt": "Wait", "States": { "Wait": { "Type": "Wait", "Seconds": 86400, "Next": "ListMFADevices", "Assign": { "UserName.$": "$.detail.requestParameters.userName" } }, "ListMFADevices": { "Type": "Task", "Parameters": { "UserName.$": "$UserName" }, "Resource": "arn:aws:states:::aws-sdk:iam:listMFADevices", "Next": "GetMfaDeviceCount" }, "GetMfaDeviceCount": { "Type": "Pass", "Next": "CheckMFA", "Parameters": { "MfaDeviceCount.$": "States.ArrayLength($.MfaDevices)" } }, "CheckMFA": { "Type": "Choice", "Choices": [ { "Next": "成功", "Variable": "$.MfaDeviceCount", "NumericGreaterThan": 0 } ], "Default": "DeleteLoginProfile" }, "DeleteLoginProfile": { "Type": "Task", "Parameters": { "UserName.$": "$UserName" }, "Resource": "arn:aws:states:::aws-sdk:iam:deleteLoginProfile", "Next": "Disabled Notification" }, "Disabled Notification": { "Type": "Task", "Resource": "arn:aws:states:::sns:publish", "Parameters": { "TopicArn": "<トピックARN>", "Message.$": "States.Format('User {} did not set MFA and was disabled.', $UserName)", "Subject": "IAM User MFA Not Set" }, "End": true }, "成功": { "Type": "Succeed" } }, "QueryLanguage": "JSONPath" }こ ※SNSはおまけなので、削除するか通知させたければ”TopicArn”の部分をご自身で作成したものに変更してください。   背景 どこの環境でもMFAを設定することはセキュリティを担保するために必須なものとなっているかと思います。 とは言えMFAを設定するのはめんどくさいですし、純粋に設定し忘れていたという問題もあります。 (狙ったわけではないですが、思えば前回MFAめんどくさいでProtonのブログ書いてました笑) Proton Authenticator を使ってみる AWSやクラウド作業でのMFAが面倒な方必見。PCでもスマホでも使える「Proton Authenticator」の導入方法を解説します。デバイス間同期、コードコピー、他アプリからのインポート、バックアップ設定まで、実務でMFAを効率的に使うためのポイントをまとめました。 blog.usize-tech.com 2025.08.19 そのための対策として、MFAを設定しないとリソースを操作することができないIAMポリシーを設定しているところも多いのではないでしょうか? しかし、IAMポリシー対策はリソース操作はできないものの、マネージメントコンソールにアクセスすることは可能です。 そういったリスクも回避したいといった場合に、MFAを設定していないIAMユーザーを無効化してしまうというのも1つの対策となります。 ただMFAが設定されているのかを確認して設定されていなければ無効化、なんてことを手動でやっていたら大変です。。。 なので手動が大変なら自動化してしまえ!ということで自動化させてみました。   前提 前提事項として、CloudTrailが有効化されている必要があります。 理由としては、CloudTrailで記録される「CreateUser」というIAMユーザーが作成された時のイベントをトリガーに今回の自動化フローは動くためです。 みなさん問題はないかと思いますが、CloudTrailは有効化必須のサービスなので、もし有効化されていないので、本ブログ内容関係なくすぐ有効化しましょう!   構成説明 今回の自動化のざっくり流れは 新規IAMユーザーを作成 ※こちらは手動 EventBrigeでIAMユーザの作成イベントを検出してStep Functionsを起動 ←ここから自動 Step Functionsのフロー内で、一定時間待ったのちMFAの設定有無を確認、設定されていなければ無効化 となっており、EventBrigeとStep Functionsのみで実現することができます。 では詳しくご紹介していきます。 IAMユーザ作成をトリガーとして Step Functions を起動 前提の節でも触れましたが、IAMユーザーが作成されると「CreateUser」イベントが記録されます。 このイベントをEventBridgeルールで検出し、Step Functionsを開始します。 EventBridgeルール: { "source": ["aws.iam"], "detail-type": ["AWS API Call via CloudTrail"], "detail": { "eventName": ["CreateUser"] } } Step Functions のステートマシン構成 Step Functionsのステートマシンの構成は以下のようになっています。 細かい設定の説明は後回しにして、先に役割単位でまとめて説明していきます。 まず、「Wait」でMFA登録有無の確認までの時間を設定しています。 例えば、IAMユーザー作成が24時間以内にMFAを設定することといったルールの場合は、Waitに86400秒を設定します。 次に、「IAM: ListMFADevices」「Pass state」「Choice state」の部分で、IAMユーザーのMFAの設定有無の確認を行っています。 設定されているのであれば、「Succeed state」に遷移し、特に何も行いません。 設定されていなければ、「DeleteLoginProfile」でIAMユーザーの無効化をします。 「SNS: Publish」はおまけですが、「無効化しましたよ」の通知用となります。 ざっくりだと以上のようになっていて、各ステートごとの細かい設定についても説明します。 まず、「Wait」では待機時間の秒数とともに、EventBrigeからの入力値である”detail.requestParameters.userName”を変数へ格納しています。 “detail.requestParameters.userName”は作成されたIAMユーザーの名前となっており、他のステートで利用するため最初に変数へ格納しています。 次に、「IAM: ListMFADevices」でIAMユーザーのMFAデバイスリストを取得してきています。 ListMFADevices - AWS Identity and Access Management Lists the MFA devices for an IAM user. If the request includes a IAM user name, then this operation lists all the MFA de... docs.aws.amazon.com MFA設定がされていない場合の出力結果: { "IsTruncated": false, "MfaDevices": [] } しかし、「IAM: ListMFADevices」の出力結果からでは、「Choice state」で分岐をさせることができません。 そのため、「Pass state」の中で”States.ArrayLength($.MfaDevices)”によってパラメーターの変換を行っています。 パラメータ変換後: { "MfaDeviceCount": 0 } これにより、MFAの登録デバイスを数値で表せるようにして、「Choice state」にて”$.MfaDeviceCount > 0″の条件でMFAの登録有無の分岐を実現しています。 そして、MFA登録がされていなければ、「DeleteLoginProfile」でIAMユーザーを無効化しています。 DeleteLoginProfile - AWS Identity and Access Management Deletes the password for the specified IAM user or root user, For more information, see Managing passwords for IAM users... docs.aws.amazon.com   注意点 最後に注意点ですが、今回ご紹介した構成は 『 バージニア北部(us-east-1) 』 で構築する必要がございます。 と言いますのも、「CreateUser」イベントはバージニア北部リージョンに記録されるためです。 そのため、そのほかのリージョンのEventBrigeで検出しようとしても、検出されず、となってしまいます。 CloudTrail concepts - AWS CloudTrail This page summarizes basic concepts related to CloudTrail such as describing the types of CloudTrail events. docs.aws.amazon.com 公式ドキュメントにも⚠重要と明記されていましたが、自分もこの仕様を知らず、最初に東京リージョンに作成して、なんで動かないんだ?とつまづきました笑 今回のメインサービスであるIAMがグローバルサービスなので、バージニア北部リージョンで実装することに大きな問題はないかと思いますが、ご注意いただければと思います。   まとめ ということでMFA設定をしていなければ自動で無効化をご紹介させていただきました。 MFAが本当に役に立っているのか?みたいな話もありますが、昨今大きなセキュリティ問題も発生しており、セキュリティの意識や要望は強くなってくると思われます。 そんな中でこんなのもあるんだな~と本ブログが一助になればよいかな~と。 最後までお読みいただきありがとうございました!!
アバター
本記事は TechHarmony Advent Calendar 2025 12/20付の記事です 。 高知の喫茶店からこんにちは。出張中のMasedatiです。 高知には独自のモーニング文化があるようで、今日はおにぎりと味噌汁、オムレツとゆで卵、バターたっぷりの食パンとサラダをモリモリ食べながらこの記事を執筆しています。 さて、今回の内容は以下のAWSアップデートについてです。 Amazon Quick Suite browser extension now supports Quick Flows - AWS Discover more about what's new at AWS with Amazon Quick Suite browser extension now supports Quick Flows aws.amazon.com ざっくり言うと「 ブラウザの中で、そのままAIアシスト(要約・Q&A・社内ナレッジ参照・アクション実行・ファイル分析)ができ、その拡張機能が Quick Flows でもサポート可能になった 」とのこと。 Quick Suiteブラウザ拡張機能とは ChromeやFirefoxのブラウザ内でQuick Suiteと対話することができます。 ブラウザ拡張機能を インストール して、Quick Suiteのアカウント名とユーザ名でサインインしてみましょう。 無事サインインが完了すると、現在開いているWebページが表示されます。 +ボタン を押すことで、現在開いているブラウザのタブをチャットの会話コンテキストに追加できます。 追加後は、そのウェブページの内容について質問したり、分析したりできるようになります。 例えば、Techブログを書くとき参考にしたAWSドキュメント等を+ボタンでQuick Suiteに登録しておけば、ブログ執筆途中の画面で記載内容の添削やレビューを行うことが可能となります。(今この記事を執筆に使ってみましたが、とても便利です) Quick Flowsのサポートが追加されました さて、本題ですが、このブラウザ拡張機能からQuick Flowsを実施することができるようになりました。 Amazon Quick Flowsは、Amazon Quick Suiteの機能の1つで、自然言語で命令するだけで、複数のステップのワークフローを作成することができるものです。 今回Quick Flowsをサポートすることになり、ブラウザを離れることなくQuick Flowsを直接実行できます。 やってみた デモとして、ブログ記事がガイドラインに違反していないかどうかチェックするワークフローを作成してみます。 まず、Quick Suiteのスペースに「ブログ執筆ガイドライン」を格納します。 # ブログ執筆ガイドライン ## 文体・トーン * 文体: です・ます調で統一 * トーン: 親しみやすく、かつ専門的 * 一人称: 「私」または「筆者」 ## 表記ルール * 製品名・サービス名は正確であること * 半角数字を使用すること: 「3つ」「20個」 * 単位は全角: 「10MB」「30日間」 * 括弧: 全角()を使用 * 引用符: 「」を使用(""は避ける) * 中黒: ・を使用 ## 禁止用語・推奨表現 * 過度にカジュアルな表現は用いない(「めっちゃ」「ヤバい」など) * 曖昧な表現は用いない(「たぶん」「かもしれない」など) * 禁止用語 - 「簡単」→ 「シンプル」「わかりやすい」を使用 - 「誰でも」→ 「技術的な知識がなくても」など具体的に - 「すごい」→ 「強力な」「効果的な」など具体的に - 「便利」→ 具体的なメリットを記述   次にFlowを作成します。 上記のとおりフローの作成は自然言語で行います。 ブログ記事を、スペースに格納した執筆ガイドラインに基づいてレビューを行ってもらいます。 「生成」ボタンを押すと以下のようなワークフローが完成しました。 拡張機能の「F」ボタンから上記ワークフローを実施することができます。 さっそくワークフローを実行して、過去の私の記事をレビューしてもらいました。 Hey Kiro, Spotifyのプレイリストで打線組んで。 AWSが発表した次世代エージェント型IDE「Kiro」を実際に触ってみて、Webアプリケーションを構築してみた体験記となります。 blog.usize-tech.com 2025.08.14 結果 では、緊急対応の修正をしていきます。
アバター
本記事は TechHarmony Advent Calendar 2025 12/19付の記事です 。 こんにちは。社会人歴1年目、AWS歴も1年目の新人tknこと髙野です。 今年初めて紅茶のアドベントカレンダーを朝食のお供に買ったのですが、先日何の偶然かお味噌汁とハーブティーという組合せが誕生しました。食の常識を覆す出来事に、朝からとてもよく目が覚めました。アドベントカレンダーは偉大です。。 さて先日、私がとある指令を受けたことからこのブログは始まります。 指令内容 名前が異なっているが、役割が同じAmazon EventBridgeのルールが2つ作成されている(EventRule-a, EventRule-b)。なぜ作成されたのか、どちらが現在使用されているものなのか調査してほしい。 もちろん実際はとても詳細にご説明いただきましたが、、ざっくりお伝えするとこのような指令を受けました。 以下のアーキテクチャ図は私が今回調査した環境を抜粋して表現したものであり、水色の丸で囲まれたEventRuleが今回の調査対象です。 また、調査対象のリソースの特徴・設定は以下の通りです。 EventRule-a / EventRule-b AWS Lambdaの関数のプログラムにより動的に作成 されたEventRule イベントパターン:AWS CodeCommitのあるブランチへのマージ ターゲット:Lambda 2つのLambda どちらも関数内で Amazon CloudWatch Logsへのログの出力処理 あり この環境を見たとき私は「AWS CloudTrailを見ればとりあえず何か分かるかな?」と単純に考えて調査を進め、行き詰まることになりました…。 そこで今回のブログでは、 AWS初心者が上記環境の リソースの作成履歴を調べた際に役立ったログ や、 ログに含めるべきだと思った情報 についてお伝えしたいと思います。 調査で最終的に分かったこと 私の調査方法をご説明する前に、今回の調査で最終的に分かったことをお伝えします。 調査対象のEventRule-a/bが作成された日時 が分かった(EventRule-aが先、EventRule-bが後) EventRule-aと同じ命名規則で作成された 他の役割のEventRuleは既に削除されている ことが分かった EventRule-bの作成日以降に、ルール作成を担うLambda関数のコードを記録するCodeCommitに該当ファイルのマージが見られた 以上の結果より、私は 「ルールの作成を担うLambda関数において、命名規則のコードが変更されたことで別名で同じ役割のEventRuleが作成された」 という仮説を最有力のEventRule誕生理由としてこの調査を終えることになりました。 最終的に、謎の大解明まで持っていくことができなかったのが悔しいところです。。   著者が躓いた調査環境の特徴 さて、私が躓いたポイントについてお話するにあたり、調査環境の特徴について追加で以下に示します。 対象環境の作成は半年以上前に行われており、活発に稼働していた期間も同時期であったことから、デフォルトで保存期間が90日の CloudTrailには情報が残っていなかった 対象のEventRuleが監視していたブランチにはマージが行われておらず、 EventRule呼び出しのログ自体が残っていなかった ここですね!このポイントに気づくのに私は多くの時間を使いました。。 AWS初心者ひいてはログ調査初心者でもある私は、 最初に環境の作成時期や稼働時期を調べることをせずに 、とりあえず色々なログを見る!という行動に突っ走っておりました。その結果、ルールの稼働記録を見るための操作・呼び出しログばかりを探しては見つからずに困惑することになりました。 もし頻繁に稼働しているリソースを調査する場合は、CloudTrailで以下のようなログを中心に調査を進めることができるでしょう。 CodeCommitの挙動に基づくログ ・GitPush:コミットのプッシュ ・CreatePullRequest:プルリクエストの作成 ・MergePullRequestByFastForward/MergePullRequestBySquash:プルリクエストのマージ ・UpdatePullRequestStatus:プルリクエストのクローズ(マージまたは却下) EventRuleの挙動に基づくログ ・PutRule:EventRuleの作成・更新 ・DeleteRule:EventRuleの削除 ・EnableRule/DisableRule:EventRuleの有効化/無効化 ・PutTargets:EventRuleのターゲットの追加・更新 ・PutEvents:イベントバスへのカスタムイベントの発行 なお、 EventRuleのターゲットとして呼び出されるLambda関数のAPIコールは「データイベント」に分類される ため、デフォルトで管理イベントのみを記録するCloudTrailではログが残らないため注意しましょう。代わりにCloudWatchでログを確認することができます。(参照: Lambda 関数ログの操作 – AWS Lambda ) ここで、対象のEventRuleはどちらも同じイベントパターンであるため、今から動作検証しても両方とも検知・作動してしまいます。そこで私は、EventRuleやLambdaの動作状況に関する調査から、誕生の歴史に関する調査に移ることにしました。   リソース作成履歴の調査に活躍したログ 以下では、リソースの作成履歴を調査する中で役立ったログについてご紹介します。 Amazon CloudWatchのロググループ ログストリームとは、CloudWatch Logsが取集したログの中で同一のソースをもつログイベントのことであり、ロググループとは「 保持、モニタリング、アクセス制御について同じ設定を共有するログストリームのグループ 」のことです。(参照: ロググループとログストリームの操作 – Amazon CloudWatch Logs ) 今回の調査環境では、EventRuleの作成用とターゲット用のLambdaそれぞれに、 CloudWatch Logsへのログの書き込み権限を付与 していました。CloudWatch Logsへのログの書き込みに必要な権限は、 logs:CreateLogGroup , logs:CreateLogStream , logs:PutLogEvents の3つであり、これらはAWSLambdaBasicExecutionRoleというAWSマネージドポリシーをLambdaに付与することで簡単に実現できます。(参照: AWSLambdaBasicExecutionRole – AWS 管理ポリシー ) 上記の権限を付与することで、以下のようにLambda関数がいつ実行されたのかが自動で記録され、 EventRuleの作成日時 を知ることができました。 また、Lambda関数ではPythonの標準ライブラリであるloggingモジュールを使用して、 logger.info(f"event: {json.dumps(event)}") のようにイベント内容を出力することで、以下のような イベントの詳細な処理内容を確認 することができました。 {   "originalEvent": {       "version": "0",       "id": "XXXXXXXX",       "detail-type": "API Call",       "source": "XXXXXXXX",       "account": "123456789012",       "time": "2025-12-15T06:57:58Z",       "region": "ap-northeast-1",       "resources": [],       "detail": {           "eventVersion": "1.0",           "userIdentity": {},           "eventTime": "2025-12-15T06:57:58Z",           "eventSource": "XXXXXXXX",           "eventName": "CreateEventRule",           "awsRegion": "ap-northeast-1",           "sourceIPAddress": "XXXXXXXX",           "userAgent": "XXXXXXXX",           "requestParameters": {/* イベントの処理内容 */},           "requestID": "XXXXXXXX",           "eventID": "XXXXXXXX", "readOnly": false,           "eventType": "AwsApiCall",           "managementEvent": true,           "recipientAccountId": "123456789012",           "eventCategory": "Management",           "sessionCredentialFromConsole": "true"       }   },   "createRuleName": "EventRuleFunction" } しかし、上記のようなルールログはEventRuleのプロパティなどをもとに出力するものであり、ルール名の情報は反映されません。そこで調査環境では、Lambda関数内に logger.info(f"EventRule created. : {rule_name}") を追加することで、作成したルール名を含むログを出力していました。 このコードが無ければEventRule-aとbどちらの作成日時か正確に判別できなかった ため、ログ設計の重要性を感じました。 また、この ログを EventRule created という キーワードでフィルターすることで、各EventRuleの作成日時を時系列順で取得 することができ、命名規則が途中からEventRule-a→EventRule-bのように変化していることに気づくことができました。Lambda関数の作者の方に感謝しかありません。。 CloudWatchのログツールにはCloudWatch Logsだけでなく、メトリクスが存在します。 しかし、EventBridgeやLambdaに関してCloudWatchの標準メトリクスが集められるのは、以下のような リソースの呼び出しイベント であり、調査環境のケースには適しませんでした。      EventBridge関連のメトリクス ・Invocations:EventRuleがターゲット(Lamba関数)の呼び出しに成功した関数 ・TriggeredRules:EventRuleがトリガーされた回数 ・MatchedEvents:イベントがEventRuleのイベントパターンと一致した回数 ・InvocationsLatency:イベントを検知してからターゲットを呼び出すまでの時間 Lambda関連のメトリクス ・Invocations:Lambda関数が呼び出された回数 ・Errors:Lambda関数の実行がエラーで終了した回数 ・Duration:Lambda関数が実行開始から終了までにかかった時間 AWS Config AWS ConfigとはAWSアカウント内のリソースの設定変更を監視し、記録するサービスです。ConfigはAWSアカウント発行後に一度有効化し、記録する対象リソースを指定することで情報の記録が始まります。 ここでは、リソースタイムラインを確認することで、あるリソースの作成時の構成から現在の構成までの変更履歴を見ることができます。 しかし、ここで注意すべきは 必ずしもリソース情報が最も古い地点=リソースの作成日時とは限らない ことです。 リソースタイムラインで確認できるリソースの設定情報の中に、 "configurationItemStatus": "ResourceDiscovered" がある場合、それは Configが対象リソースを初めて検出したこと を示します。そのため、Configが有効化される前に作成されたリソースなどは、Configを有効化してリソースが発見された時点で追跡が開始されるため、Configのログをリソース作成日時の根拠とすることは望ましくありません。 一方で、Configの便利な点として 削除したリソースの構成履歴も確認できる 点が挙げられます。 以下のように削除したリソースも含めて検索できるうえ、存在した期間、変更履歴、削除日などを確認することが可能です。 今回の調査環境では、このリソース履歴において EventRule-aと 同一の命名規則で他の役割をもつEventRuleが全て削除済 であることが確認 できました。一方で、EventRule-bと同一の命名規則のEventRuleは現役であったことから、 EventRule-aが不要な関数である可能性が高い と判断することができました。   調査結果のまとめ これまでの調査結果より、「EventRule-aはEventRule-bより先に作成された」、「EventRule-aと同様の命名規則のEventRuleは既に全て削除されている」という点から、EventRule-aが不要なEventRuleである可能性が高まりました。 そこで仮説をより強固にするべく、EventRuleを作成するLambda関数の命名規則コードの変遷を追跡しようと、 Lambda関数のバージョン や CodeCommitのコミット履歴 の確認を試みました。が、、 Lambda:バージョン履歴なし CodeCommit:ある日あるコミットでLambda関数のコード関連ファイルが全て誕生 ただし、EventRule-b作成日以降に該当ファイルのマージを確認 上記の結果より、「EventRule-aが作成された後に、Lambda関数においてEventRuleの命名規則の変更があり、EventRule-bが作成されてその後使用されている… のではないか? 」という結論に終わりました。。 ログに含めるべきだと思った情報 以上の結果より、各リソースにおいてログに含めてあると調査がしやすいのではないかと感じた情報をまとめます。 EventBridge/Lambda: 呼び出し元のリソース名 方法1:EventRuleのイベントパターンにおいて、detail-typeにEventRule名を含めた文字列を設定 方法2:EventRuleの 入力トランスフォーマー で、Lambdaに渡す情報にリソース名を含めるように設定 CodeCommit: こまめな コミット履歴 EventBridge/Lambdaについては、どちらの手法も動的にEventRuleを作成する調査対象のような環境では適用しやすいうえ、これによりLambdaのCloudWatchロググループにおいて、 Lambdaを呼び出したEventRule名を 無期限 の間確認可能になる ことが期待できます。 CodeCommitについては、developやmasterなど複数人で共有するブランチでは完成版のコミットを意識する必要があるものの、 featureなど一時的/個人的なブランチではこまめにコミット して履歴を残すことで、 将来の自身・チームメンバーがコード設計の意図を辿りやすくなる ことが考えられます。   おわりに 今回、調査を通してログが環境理解にどのように役立つかを身をもって学ぶことができました。今回学んだ”将来環境を引き継ぐ人に向けた開発”の視点を、今後の開発業務に活かしていきたいと思います。 初めてのブログで拙いところも多々あったかと思いますが、いかがでしたでしょうか。 今後、様々なAWSリソースについて学びを深めるとともに、このようなアウトプットのスキルも向上させていきたいと願うばかりです。 最後までお付き合いいただき、誠にありがとうございました。 クリスマス・イブまであと6日、皆さま体調に気を付けてお過ごしください!
アバター