オンプレミスとEC2 AutoScalingで負荷分散できる構成を検討した

# 概要
負荷が小さい夜間はオンプレミスのみ、負荷が大きい日中はEC2 AutoScalingでインスタンスを増やしハイブリッドに処理を分散させたいという要件があり、構成を検討しました。

## 課題
日中のみAutoScalingを稼働させるのはScheduled scalingで実現できるので、オンプレミスとEC2で負荷分散させる方法を考えました。
httpリクエストを受け付けるシステムのため、前段にはALBを配置します。オンプレミスのサーバーをターゲットにするため、ターゲットグループはIPターゲットにする必要があります。しかしIPターゲットにするとAutoScalingが新しいインスタンスをターゲットグループに自動登録してくれません。

https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/getting-started-elastic-load-balancing.html
> ターゲットグループは、`instance` のターゲットタイプを指定する必要があります。Auto Scaling グループを使用する場合、`ip`のターゲットタイプを指定することはできません。

そのためターゲットグループへの登録はStepFunctionsで行うことにしました。

## 構成図
![Untitled (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48250/eb563fea-bad3-a0a4-3d7f-f5602623a668.png)
![stepfunctions_graph (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48250/96c8bbdf-c0a4-4eb3-f0ff-36d62fc9a7ed.png)

## 実装
StepFunctionsでステートマシンを実装します。LambdaではなくSDK統合を使用します。
```json:ステートマシン
{
  "StartAt": "DescribeInstances",
  "States": {
    "DescribeInstances": {
      "Type": "Task",
      "Parameters": {
        "InstanceIds.$": "States.Array($.detail.EC2InstanceId)"
      },
      "Resource": "arn:aws:states:::aws-sdk:ec2:describeInstances",
      "Next": "RegisterTargets",
      "ResultSelector": {
        "PrivateIpAddress.$": "$.Reservations[0].Instances[0].NetworkInterfaces[0].PrivateIpAddress"
      }
    },
    "RegisterTargets": {
      "Type": "Task",
      "End": true,
      "Parameters": {
        "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-3:138067193396:targetgroup/asg-ip-targets/73a5kssn5s42t96u",
        "Targets": [
          {
            "Id.$": "$.PrivateIpAddress"
          }
        ]
      },
      "Resource": "arn:aws:states:::aws-sdk:elasticloadbalancingv2:registerTargets"
    }
  }
}
```

StepFunctionsをトリガーするイベントとしてAutoScalingの`EC2 Instance Launch Successful`を使用します。
```json:AWSコンソールで確認できるサンプルイベント
{
  "version": "0",
  "id": "3e3c153a-8339-4e30-8c35-687ebef853fe",
  "detail-type": "EC2 Instance Launch Successful",
  "source": "aws.autoscaling",
  "account": "123456789012",
  "time": "2015-11-11T21:31:47Z",
  "region": "us-east-1",
  "resources": ["arn:aws:autoscaling:us-east-1:123456789012:autoScalingGroup:eb56d16b-bbf0-401d-b893-d5978ed4a025:autoScalingGroupName/sampleLuanchSucASG", "arn:aws:ec2:us-east-1:123456789012:instance/i-b188560f"],
  "detail": {
    "StatusCode": "InProgress",
    "AutoScalingGroupName": "sampleLuanchSucASG",
    "ActivityId": "9cabb81f-42de-417d-8aa7-ce16bf026590",
    "Details": {
      "Availability Zone": "us-east-1b",
      "Subnet ID": "subnet-95bfcebe"
    },
    "RequestId": "9cabb81f-42de-417d-8aa7-ce16bf026590",
    "EndTime": "2015-11-11T21:31:47.208Z",
    "EC2InstanceId": "i-b188560f",
    "StartTime": "2015-11-11T21:31:13.671Z",
    "Cause": "At 2015-11-11T21:31:10Z a user request created an AutoScalingGroup changing the desired capacity from 0 to 1.  At 2015-11-11T21:31:11Z an instance was started in response to a difference between desired and actual capacity, increasing the capacity from 0 to 1."
  }
}
```

当初DescribeInstancesにインスタンスIDを渡す部分を以下のようにしており、エラーで保存できませんでしたが
> The value for the field 'InstanceIds.$' must be a STRING that contains a JSONPath but was an ARRAY
```json:NG
      "Parameters": {
        "InstanceIds.$": [
          "$.detail.EC2InstanceId"
        ]
      },
```
組み込み関数のStates.Arrayを使うことで解決できました。

https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
```json:OK
      "Parameters": {
        "InstanceIds.$": "States.Array($.detail.EC2InstanceId)"
      },
```

## 課題
本番運用する場合はリトライやエラー処理、スケールイン時のターゲット削除を追加したほうが良いでしょう。