FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

ECS。Taskスケールアウトのテンプレート

たかこです。
弊社のインフラは、今までAWSのserviceは全てCloudFormationで管理・運用していて、
各サーバー(Gateway(nat兼任)・Frontend・Backend・Batch)の構築はChefを使っていました。
そこからDockerを利用して使い捨ての環境を作っていこう、DevOpsをスムーズにしよう!と変化し、
Chef(Gateway(nat兼任))+ECS(Frontend・Backend・Batch)の環境を構築することにしました。
今回はTaskスケールアウトのCloudFormationのTemplateを書きます。

テンプレート作成

まず、ロールを作成します。
EC2のAutoScalingのタイプとは別で、ECSのスケーリングはApplicationAutoScalingを利用します。
なので、application-autoscaling.amazonaws.comをエンティティとして、
Serviceの数の参照と更新、cloudwatchのalarmの参照を許可します。

    "ECSAutoScaleRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [ {
            "Effect": "Allow",
              "Principal": { "Service": [ "application-autoscaling.amazonaws.com" ] },
              "Action": [ "sts:AssumeRole" ]
            }
          ]
        },
        "Path": "/",
        "Policies": [ {
          "PolicyName": "ecs-autoscale",
          "PolicyDocument": {
            "Statement": [ {
               "Effect": "Allow",
               "Action": [
                  "ecs:DescribeServices",
                  "ecs:UpdateService",
                  "cloudwatch:DescribeAlarms"
               ],
               "Resource": "*"
            } ]
          }
        } ]
      }
    },

次に、スケーリングのトリガーを設定します。
この設定だと、最低1・最大2のタスクが実行されます。 ResourceIdは「service/cluster名/サービス名」で、ScalableDimensionとServiceNamespaceはいまのところ固定です。

    "ServiceAutoScalingTarget": {
      "Type" : "AWS::ApplicationAutoScaling::ScalableTarget",
      "Properties" : {
        "MaxCapacity" : 2,
        "MinCapacity" : 1,
        "ResourceId" : { "Fn::Join": [ "", [ "service/", { "Ref": "Cluster" }, "/", { "Ref": "Service" } ] ] },
        "RoleARN" : { "Ref" : "ServiceRoleARN" },
        "ScalableDimension" : "ecs:service:DesiredCount",
        "ServiceNamespace" : "ecs"
      }
    },

ポリシーの設定をします。
ポリシーのResourceId・ScalableDimension・ServiceNamespaceはトリガーと同じものを設定します。
MinAdjustmentMagnitudeはリソースの最低限の個数で、最低1つは動いて欲しいので1にします。
ScalingAdjustmentは追加するタスクの数で、1つ追加したいので100%にしました。
ポリシーのテンプレートの作成はトリガーがなければ失敗するので、"DependsOn" : "ServiceAutoScalingTarget" をつけています。

    "ServiceScaleUpPolicy": {
      "Type" : "AWS::ApplicationAutoScaling::ScalingPolicy",
      "DependsOn" : "ServiceAutoScalingTarget",
      "Properties" : {
        "PolicyName" : "scale-up-policy",
        "PolicyType" : "StepScaling",
        "ResourceId" : { "Fn::Join": [ "", [ "service/", { "Ref": "Cluster" }, "/", { "Ref": "Service" } ] ] },
        "ScalableDimension" : "ecs:service:DesiredCount",
        "ServiceNamespace" : "ecs",
        "StepScalingPolicyConfiguration" : {
          "AdjustmentType" : "PercentChangeInCapacity",
          "Cooldown" : 60,
          "MetricAggregationType" : "Average",
          "MinAdjustmentMagnitude": 1,
          "StepAdjustments" : [ {
            "MetricIntervalLowerBound" : 0,
            "ScalingAdjustment" : 100
          } ]
        }
      }
    },

cloudwatchの設定をします。
これはEC2のAutoScalingでも同じです。
スケールを試したいので閾値を20にしています。
あとは負荷をかければserviceの希望数が変更されて、Taskが2つ起動されます。
※1つのサーバーに同じportのコンテナは立てれないので、私はEC2のAutoScalingと組み合わせて設定しました。

    "ServiceCPUAlarmHigh": {
      "Type": "AWS::CloudWatch::Alarm",
      "Properties": {
        "MetricName": "CPUUtilization",
        "Namespace": "AWS/ECS",
        "Statistic": "Maximum",
        "Period": "300",
        "EvaluationPeriods": "1",
        "Threshold": "20",
        "AlarmActions": [ { "Ref": "ServiceScaleUpPolicy" } ],
        "Dimensions": [ {
          "Name": "ClusterName",
          "Value": { "Ref": "Cluster" }
        }, {
          "Name": "ServiceName",
          "Value": { "Ref": "Service" }
        } ],
        "ComparisonOperator": "GreaterThanOrEqualToThreshold"
      }
    }

全部まとめると

つなげるとこんな感じです。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "service cloudformation stack",
  "Parameters" : {
    "Cluster" : {
      "Type" : "String",
      "Default": "【Cluster名】"
    },
    "Service" : {
      "Type" : "String",
      "Default": "【Service名】"
    }
  },
  "Resources" : {
    "ECSAutoScaleRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [ {
            "Effect": "Allow",
              "Principal": { "Service": [ "application-autoscaling.amazonaws.com" ] },
              "Action": [ "sts:AssumeRole" ]
            }
          ]
        },
        "Path": "/",
        "Policies": [ {
          "PolicyName": "ecs-autoscale",
          "PolicyDocument": {
            "Statement": [ {
               "Effect": "Allow",
               "Action": [
                  "ecs:DescribeServices",
                  "ecs:UpdateService",
                  "cloudwatch:DescribeAlarms"
               ],
               "Resource": "*"
            } ]
          }
        } ]
      }
    },
    "ServiceAutoScalingTarget": {
      "Type" : "AWS::ApplicationAutoScaling::ScalableTarget",
      "Properties" : {
        "MaxCapacity" : 2,
        "MinCapacity" : 1,
        "ResourceId" : { "Fn::Join": [ "", [ "service/", { "Ref": "Cluster" }, "/", { "Ref": "Service" } ] ] },
        "RoleARN" : {"Fn::GetAtt" : ["ECSAutoScaleRole", "Arn"] },
        "ScalableDimension" : "ecs:service:DesiredCount",
        "ServiceNamespace" : "ecs"
      }
    },
    "ServiceScaleUpPolicy": {
      "Type" : "AWS::ApplicationAutoScaling::ScalingPolicy",
      "DependsOn" : "ServiceAutoScalingTarget",
      "Properties" : {
        "PolicyName" : "scale-up-policy",
        "PolicyType" : "StepScaling",
        "ResourceId" : { "Fn::Join": [ "", [ "service/", { "Ref": "Cluster" }, "/", { "Ref": "Service" } ] ] },
        "ScalableDimension" : "ecs:service:DesiredCount",
        "ServiceNamespace" : "ecs",
        "StepScalingPolicyConfiguration" : {
          "AdjustmentType" : "PercentChangeInCapacity",
          "Cooldown" : 60,
          "MetricAggregationType" : "Average",
          "MinAdjustmentMagnitude": 1,
          "StepAdjustments" : [ {
            "MetricIntervalLowerBound" : 0,
            "ScalingAdjustment" : 100
          } ]
        }
      }
    },
    "ServiceCPUAlarmHigh": {
      "Type": "AWS::CloudWatch::Alarm",
      "Properties": {
        "MetricName": "CPUUtilization",
        "Namespace": "AWS/ECS",
        "Statistic": "Maximum",
        "Period": "300",
        "EvaluationPeriods": "1",
        "Threshold": "18",
        "AlarmActions": [ { "Ref": "ServiceScaleUpPolicy" } ],
        "Dimensions": [ {
          "Name": "ClusterName",
          "Value": { "Ref": "Cluster" }
        }, {
          "Name": "ServiceName",
          "Value": { "Ref": "Service" }
        } ],
        "ComparisonOperator": "GreaterThanOrEqualToThreshold"
      }
    }
  },
  "Outputs": {
    "ECSAutoScaleRole": {
      "Value": { "Ref" : "ECSAutoScaleRole" }
    },
    "ServiceAutoScalingTarget": {
      "Value": { "Ref" : "ServiceAutoScalingTarget" }
    },
    "ServiceScaleUpPolicy": {
      "Value": { "Ref" : "ServiceScaleUpPolicy" }
    },
    "ServiceCPUAlarmHigh": {
      "Value": { "Ref" : "ServiceCPUAlarmHigh" }
    }
  }
}

参考

Amazon ECSでAuto Scaling
 https://aws.amazon.com/jp/blogs/news/automatic-scaling-with-amazon-ecs
・CloudFormation - Template Reference
 AWS::ApplicationAutoScaling::ScalableTarget
 https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalabletarget.html
AWS::ApplicationAutoScaling::ScalingPolicy
 https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalingpolicy.html