5.2. オートスケーリングの利用¶
目次
5.2.1. Overview¶
本ガイドラインは、AWSのオートスケーリングを利用する際に、アプリケーションで独自に作成したメトリクスの使用について説明する。 AWSのオートスケーリングについては、概要のみとし詳細は割愛するため、Auto Scaling とはを参照されたい。
5.2.1.1. オートスケーリングとは¶
オートスケーリングとは,Amazon EC2のインスタンスを,自動的に縮小・拡張することができる機能である。 また、インスタンスの数を常に一定に保つ為の利用も可能である。 詳細については、Auto Scaling とはを参照されたい。
5.2.1.2. オートスケーリングのメリット¶
オートスケーリングを利用するメリットは、主に耐障害性の向上、可用性の向上、及びコスト管理の強化である。 詳細については、Auto Scaling のメリットを参照されたい。
5.2.1.3. オートスケーリング構成¶
アプリケーションのカスタムメトリクスを使用したAWS上での構成を以下に示す。
項番 説明 (1) アプリケーションで作成したカスタムメトリクスを送信する。送信頻度については、スケジューラなどを使用して定期的に実行する。 (2) Amazon EC2 はメトリクスを Amazon CloudWatch に送信する。詳細は、CloudWatch を使用したインスタンスのモニタリングを参照されたい。また、EC2 インスタンスから利用可能なメトリクスについては、Amazon EC2 メトリクスを参照されたい。 (3) CloudWatch は、指定した閾値にメトリクスが達すると、自動的にCloudWatch アラームに通知を送信する。 (4) CloudWatch アラームは 1 つのメトリクスを監視し、ポリシーで指定した閾値にメトリクスが違反すると、メッセージを Auto Scaling に送信する。詳細は、CloudWatch アラームを作成するインスタンス用を参照されたい。 (5) Auto Scaling は関連付けられたポリシーを実行して、グループをスケールイン(インスタンスを終了)するか、グループをスケールアウト(インスタンスを起動)する。
5.2.1.4. 利用上の注意事項¶
EC2インスタンスのステートレス化を行う必要がある。 具体的には、サーバが状態を持ってしまうと、スケールインの際に状態として保持しているデータがロストする。状態とは、アプリケーションのセッション情報やログファイル、あとは EC2 のホスト名やプライベート IP が決め打ちになっていることを指す。 また、スケールアウトの際も状態をもってしまうと、新たに起動したインスタンスに対して、リクエストを振り分けた際にデータが存在しないなどのエラーが発生する。
5.2.2. How to use¶
AWSに標準で用意されているメトリクスを使用する場合、APに変更を加えることなく利用することができる。
AWSのオートスケーリングを標準のメトリクスで使用する方法は、AWS公式ドキュメントAuto Scalingの使用開始を参照されたい。
5.2.3. How to extend¶
本ガイドラインでは、Amazon EC2 メトリクスで提供されていないメトリクスを、アプリケーションで実装する方法を示す。
5.2.3.1. カスタムメトリクスセンダの作成¶
AWS SDK for Javaを使用してカスタムメトリクスを作成した実装例を以下に示す。
CloudWatchMetricSender.java
@ConfigurationProperties(prefix = "custom.metric") public class CloudWatchMetricSender implements InitializingBean { @Value("${cloud.aws.cloudwatch.region:}") String region; // (1) @Value("${spring.application.name:autoScalingGroupName}") String autoScalingGroupName; @Inject CloudWatchMetricProperties cloudWatchMetricProperties; private AmazonCloudWatch amazonCloudWatch; private String instanceId; // (2) @Override public void afterPropertiesSet() throws Exception { if (StringUtils.isEmpty(region)) { this.amazonCloudWatch = AmazonCloudWatchClientBuilder .defaultClient(); } else { this.amazonCloudWatch = AmazonCloudWatchClientBuilder .standard().withRegion(region).build(); } try { InstanceInfo instanceInfo = EC2MetadataUtils.getInstanceInfo(); if (Objects.isNull(instanceInfo)) { resolveInstanceIdWithLocalHostAddress(); } else { this.instanceId = instanceInfo.getInstanceId(); } } catch (AmazonClientException e) { resolveInstanceIdWithLocalHostAddress(); } } @Scheduled(fixedRate = 5000) // (3) public void sendCloudWatch() { MemoryMXBean mBean = ManagementFactory.getMemoryMXBean(); MemoryUsage heapUsage = mBean.getHeapMemoryUsage(); // (4) Dimension InstanceIdDimension = new Dimension().withName("instanceId") .withValue(instanceId); // (5) Dimension AutoScalingGroupNameDimension = new Dimension().withName( "AutoScalingGroupName").withValue(autoScalingGroupName); // (6) // (7) PutMetricDataRequest request = new PutMetricDataRequest() .withNamespace(cloudWatchMetricProperties.getNamespace()) .withMetricData( // Used new MetricDatum().withDimensions(InstanceIdDimension, AutoScalingGroupNameDimension).withMetricName( "HeapMemory.Used").withUnit( StandardUnit.Bytes.toString()).withValue( (double) heapUsage.getUsed()), // Max new MetricDatum().withDimensions(InstanceIdDimension, AutoScalingGroupNameDimension).withMetricName( "HeapMemory.Max").withUnit( StandardUnit.Bytes.toString()).withValue( (double) heapUsage.getMax()), // Committed new MetricDatum().withDimensions(InstanceIdDimension, AutoScalingGroupNameDimension).withMetricName( "HeapMemory.Committed").withUnit( StandardUnit.Bytes.toString()).withValue( (double) heapUsage.getCommitted()), // Utilization new MetricDatum() .withDimensions(InstanceIdDimension, AutoScalingGroupNameDimension) .withMetricName("HeapMemory.Utilization") .withUnit(StandardUnit.Percent.toString()) .withValue( 100 * ((double) heapUsage.getUsed() / (double) heapUsage .getMax())) ); AmazonCloudWatch.putMetricData(request); // (8) } private void resolveInstanceIdWithLocalHostAddress() { try { this.instanceId = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e1) { this.instanceId = "localhost"; } } public String getAutoScalingGroupName() { return autoScalingGroupName; } public void setAutoScalingGroupName(String autoScalingGroupName) { this.autoScalingGroupName = autoScalingGroupName; } }
項番 説明 (1) Auto Scaling グループメトリクスのディメンションで使用するAutoScalingGroupName ディメンションの設定であるcustom.metric.auto-scaling-group-name
が設定されていない場合に、spring.application.name
をデフォルト値としてautoScalingGroupName
に設定する。 (2) AmazonCloudWatchで指定されたリージョンが存在する場合は設定して生成する。アプリケーションがEC2インスタンス上で実行されている場合は、EC2インスタンスを識別する為の、InstanceIdを取得する。EC2インスタンス上でない場合は、localhost文字列を設定する。 (3) CloudWatchメトリクスに送信するスケジュールを指定する。 (4) メモリ使用量を取得するためのMemoryUsage
を取得する。 (5) Amazon EC2 メトリクスのディメンションのInstanceIdディメンションを生成する。 (6) Amazon EC2 メトリクスのディメンションのAutoScalingGroupNameディメンションを生成する。 (7) CloudWatchメトリクスに送信するPutMetricDataRequest
を生成して、メトリクス名にHeapMemory.Utilization
、単位にパーセント表記する為のStandardUnit.Percent.toString()
、値にメモリ使用率を計算指定して設定する。 (8)AmazonCloudWatch#putMetricData
を使用して、CloudWatchメトリクスに送信する。
Note
Spring Cloud AWSにはメトリクスを送信するためのCloudWatchMetricSender
インタフェースとその実装であるBufferingCloudWatchMetricSender
が用意されている。
しかし、BufferingCloudWatchMetricSender
はエンドユーザのリクエストが発生しないとメトリクス情報を送信することができない。
また、CloudWatchMetricSender
インタフェースではメトリクスを一回につき1つしか送信できず、冗長なデータ送信を伴ってしまう。
以上の制約があるため本ガイドラインでは、Spring Cloud AWSのCloudWatchMetricSender
は使用せず、カスタムメトリクスセンダを独自に実装する方法を紹介している。
applicationContext.xml
<!-- (1) --> <bean id="cloudWatchMetricSender" class="com.example.xxx.common.metrics.CloudWatchMetricSender" />
項番 説明 (1) 作成したカスタムメトリクスを使用する場合は、Bean定義ファイルにCloudWatchMetricSender
クラスのbean定義が必要となる。
application.yml
custom: metric: auto-scaling-group-name: autoScalingGroupName
プロパティ名 説明 custom.metric.auto-scaling-group-name Auto Scaling グループメトリクスのディメンションで使用するAutoScalingGroupName ディメンションを明示的に設定する場合に使用する。 指定しない場合はデフォルト値としてspring.application.name
で指定した値が使用される。