APIGatewayとStepFunctionを使い、EC2の起動から停止時間までを自在に操る

#はじめに
「EC2を起動し、特定の時間経過後、EC2を停止する」仕組みをAPIGatewayとStepFunctionを使い、実現します。

#完成図
`https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/start-limit?instance_id=i-hogehoge&time=10`をリクエストすると、インスタンスIDが`i-hogehoge`のEC2を起動し、`10`秒後に停止する挙動になります。

**挙動の詳細について説明しますと、**
起動するインスタンス`instance_id`で指定し、と起動してから停止するまでの時間を`time`で指定しています。

APIGatewayからこれらの2つの値をStepFunctionに渡し、StepFunction内で、Lambdaに`instance_id`、とWaitに`time`を渡して実行する挙動になります。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b9a2e3d6-1bf7-8a24-1daf-332cc04760bd.png">


#作業の流れ流れ
①Lambda用のIAMポリシーを作成
②Lambdaを2つ作成
③Stepfunction作成
④APIGateway用のIAMロール作成
⑤APIGateway作成

#Lambda用のIAMポリシーを作成
LambdaでEC2を起動・停止するためのぽりが必要です。
jsonで下記のコードをコピペして、ポリシーを作成しましょう。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
     "Sid": "VisualEditor0",
     "Effect": "Allow",
     "Action": [
         "logs:CreateLogStream",
         "ec2:DescribeInstances",
         "ec2:StopInstances",
         "ec2:StartInstances",
         "logs:CreateLogGroup",
         "logs:PutLogEvents"
     ],
     "Resource": "*"
    }
  ]
}
```


#Lambdaを2つ作成
起動と停止するLambdaを作成します。
・関数を作成：`一から作成`
・関数名：`適切に`
・ランタイム：`Python`
・アクセス権限：`基本的な Lambda アクセス権限で新しいロールを作成`

```python:EC2Start
import json
import boto3
def lambda_handler(event, context):
    ec2 = boto3.client('ec2', region_name='ap-northeast-1')
    ec2.start_instances(InstanceIds=[event['instance_id']])
```

```python:EC2Stop
import json
import boto3
def lambda_handler(event, context):
    ec2 = boto3.client('ec2', region_name='ap-northeast-1')
    ec2.stop_instances(InstanceIds=[event['instance_id']])
```

作成後、`設定`→`実行ロールの編集`から、先程作成したポリシーをIAMロールにアタッチしてください。

#Stepfunction作成
ステートマシンを作成します。
・`ワークフローを視覚的に設計`
・`タイプ`：`標準`
次へ

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/5f7dc1e5-9bb3-8c49-fbb3-2dd292ff0563.png">

ワークフローを設計
StartとEndの間に以下の通りにフローを設定します。
・`アクション`：`Lambda Invoke`
・`フロー`：`Wait`
・`アクション`：`Lambda Invoke`

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/61c1b9a1-249b-a0d0-5fcf-9421bbb31a44.png">

###第一工程（EC2の起動）
設定
・`状態名`：`startEC2`
・`Integration Type`：`Optimized`
・`Function name`：`先程作成したEC2の起動Lambda`
・`ペイロード`：`Use state input as payload`
・`追加の状態`：`Wait`

入力
・`InputPath で入力をフィルタリング`：`$.InputParams`
入力をInputParamsに絞ることで、Lambdaに渡す値がわかりやくすなります。
Lambdaは、`InputParams.instance_id`を参照します。

```json
"InputParams": {
  "instance_id": "i-xxxxxxxxxxxx",
  "time": 10
}
```

出力
・`ResultPath を使用して元の入力を出力に追加`：`Discard result and keep original input`
元の入力からそのまま出力されます。
元の入力を変えると、Waitや次のLambdaが値を参照できなくなります。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/a20b970f-c945-f79b-ce8a-003a2b9fd37e.png">
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/29022c97-a9f0-9ca5-21a4-9d5343619b84.png">
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/c17c1eff-6e65-032c-efbc-319338e58f64.png">


###第二工程（待機時間）
設定
・`状態名`：`stopEC2`
・`オプション`：`Wait for a fixed interval`
・`Get seconds from state input`：`$.InputParams.time`
`InputParams`の`time`の値を参照して待機時間の値が決定されます。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/6ee99877-a387-fc6e-6e6a-2039ed8f1d64.png">




入力
・`チェックしません`

出力
・`チェックしません`

###第三工程（EC2の停止）
設定
・`状態名`：`stopEC2`
・`Integration Type`：`Optimized`
・`Function name`：`作成したEC2の停止Lambda`
・`ペイロード`：`Use state input as payload`

入力
・`InputPath で入力をフィルタリング`：`$.InputParams`

出力
・`デフォルト`
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b039db6c-9865-855c-9d8d-bcec1602e0c5.png">



次へ
・`名前`：`EC2-Control`
・`実行ロール`：`新しいロールを作成`

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/595ed585-78eb-5a0c-eb7d-017abb6cdfb1.png">

ステートマシンが作成されました。

ARNは、後で使いますのでコピーしておいてください。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/3721427e-3a45-2ffd-052b-3d7f5170a166.png">

###StepFunctionの定義
stepfunctionの定義になります。LambdaのARN2つを貼り付けると、コピペで作成できます。

```json
{
  "Comment": "This is your state machine",
  "StartAt": "startEC2",
  "States": {
    "startEC2": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "Payload.$": "$",
        "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxx:function:test-steppfunction:$LATEST"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "Next": "Wait",
      "ResultPath": null,
      "InputPath": "$.InputParams"
    },
    "Wait": {
      "Type": "Wait",
      "Next": "stopEC2",
      "SecondsPath": "$.InputParams.time"
    },
    "stopEC2": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "Payload.$": "$",
        "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxx:function:stop-stepfunction:$LATEST"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "End": true,
      "InputPath": "$.InputParams"
    }
  }
}
```

#APIGateway用のIAMロール作成
StepFunctionsの実行権限を持ったロールが必要なため、API Gateway用のIAMロールを作成します

ロールの作成
・`AWSサービス`
・`APIGateway`
次のステップ
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/7c6f3d20-bdc7-6588-add9-bcae273af341.png">

ポリシーの選択
・`AmazonAPIGatewayPushToCloudWatchLogs`が選択済み
追加せずそのまま、次のステップをクリック

下記の通り、ロールを作成する
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/7208caaa-39f1-44b3-cb06-59086113b4a1.png">

作成したロールに`AWSStepFunctionsFullAccess`をアタッチします。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/32659991-214d-3fe0-ec98-c7a8d73ed829.png">

ロールのARNをコピーしておきます。


#APIGateway作成
`REST API`の`構築`を選択します。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/6c874a66-98c1-3b49-fb49-3fa1030efb63.png">

・`プロトコル`：`REST`
・`新しいAPI`
・`API名`：`EC2-Start-StepFunction`

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/2f85d0d5-70d3-0d9b-b278-76833b046eeb.png">

リソースの作成
・`リソース名`：`start-limit`

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/6903abbf-e7da-9fbd-43c8-f57387b8b072.png">

メソッドの作成
・`メソッド`：`GET`
・`結合タイプ`：` AWS サービス`
・`AWS リージョン`：`適切な値`
・`AWS サービス`：`Step Functions`
・`HTTP メソッド`：`POST`
・`アクションの種類`：` アクション名の使用`
・`アクション `：`StartExecution `
（LambdaをキックするAPIにはStartExecution）
・`実行ロール`：`先程作成したAPIGateway用のロールのARN`

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/37858445-a66e-e806-05ab-5bd58c68bab6.png">



`統合リクエスト`をクリックします。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/419eeee8-d68a-1a07-2824-086cc893a90b.png">

下側にある`マッピングテンプレート`をクリックし、`application/json`名で、下記のコードを入力し、保存します。
先程コピーしたStepFunctionのARNが必要です。


```json:application/json
#set( $body = $util.escapeJavaScript($input.json('$')) )
{
  "input": "{\"input\":$body,\"InputParams\":{\"instance_id\":\"$input.params('instance_id')\",\"time\":$input.params('time')}}",
  "stateMachineArn": "StepFunctionのARNを入力！"
} 
```
起動するインスタンス`instance_id`と起動してから停止するまでの時間`time`をAPIGatewayからStepFunctionに渡します。

**StepFunction側は、以下のように受け取ります。**

```json:stepfuctionで受け取る
{
  "input": {},
  "InputParams": {
    "instance_id": "i-xxxxxxx",
    "time": 10
  }
}
```


APIをデプロイすると、URLが発行されます。
デプロイのステージ名を`prod`とすると、
URLは`https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/prod`になります。

`https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/start-limit?instance_id=i-xxxxxxx&time=100`
とすると、インスタンスが起動し、100秒後にインスタンスが停止します。
URLをコピペして、エンターキーをおしてみましょう。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/45092018-97d2-5b7f-d425-d10798d4b034.png">



#参考
[Step Functionsを使い倒す](https://messerarche.hatenablog.com/entry/2020/07/17/163824)
[API Gateway を使用したStep Functions API の作成](https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/tutorial-api-gateway.html)
[ParametersやらResultPath](https://dev.classmethod.jp/articles/stepfunctions-parameters-inter-states/#toc-8)
[StepFunctionsの入力方法サンプルメモ集](https://qiita.com/kure/items/6d09e4550814c3a17ed0#inputpath)
[API GatewayでStepFunctionsと統合する時のTips
](https://qiita.com/kammy1231/items/c0528fcdb3ee33c3c2b2)