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上での構成を以下に示す。

Screen image of Session management.

項番 説明
(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;

    @Value("${cloud.aws.cloudwatch.namespace:}")
    String namespace;

    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(namespace)
                .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で指定した値が使用される。