Carpe Diem

備忘録

TerraformでECS環境の構築【オートスケール編】

概要

TerraformでECS環境の構築 - Carpe Diemでは書いてなかったオートスケールについてです。
terraform 0.7からServiceでのオートスケールにも対応したので、それを使って構築します。

環境

  • Ubuntu 14.04
  • Terraform 0.7.3

実装

完成形はこちら github.com

Autoscale用のIAMの追加

Amazon ECS サービスの Auto Scaling IAM Role - Amazon EC2 Container Serviceを参考に用意します。

まずはJSONでPolicyの用意

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "application-autoscaling.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

次にRoleを用意

resource "aws_iam_role" "ecs_autoscale_role" {
    name = "ecs_autoscale_role"
    assume_role_policy = "${file("policies/autoscale-assume-role.json")}"
}

最後に先ほどのPolicyをRoleへattachする設定をします。

resource "aws_iam_policy_attachment" "ecs_autoscale_role_attach" {
    name = "ecs-autoscale-role-attach"
    roles = ["${aws_iam_role.ecs_autoscale_role.name}"]
    policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole"
}

ECS cluster用のpolicyの追加

resource "aws_autoscaling_policy" "scale_out" {
    name = "Instance-ScaleOut-CPU-High"
    scaling_adjustment = 1
    adjustment_type = "ChangeInCapacity"
    cooldown = 300
    autoscaling_group_name = "${aws_autoscaling_group.dev_api.name}"
}

resource "aws_autoscaling_policy" "scale_in" {
    name = "Instance-ScaleIn-CPU-Low"
    scaling_adjustment = -1
    adjustment_type = "ChangeInCapacity"
    cooldown = 300
    autoscaling_group_name = "${aws_autoscaling_group.dev_api.name}"
}

ECS cluster用のalarmの追加

スケールアウトでは5分間で平均でCPU使用率が30%以上になったらアクションを起こすようにします。
スケールインでは5分間で平均でCPU使用率が5%以下になったらアクションを起こすようにします。

resource "aws_cloudwatch_metric_alarm" "dev_api_cluster_high" {
    alarm_name = "dev-api-cluster-CPU-Utilization-High-30"
    comparison_operator = "GreaterThanOrEqualToThreshold"
    evaluation_periods = "1"
    metric_name = "CPUUtilization"
    namespace = "AWS/ECS"
    period = "300"
    statistic = "Average"
    threshold = "30"
    dimensions {
        ClusterName = "${aws_ecs_cluster.api_cluster.name}"
    }
    alarm_actions = ["${aws_autoscaling_policy.scale_out.arn}"]
}

resource "aws_cloudwatch_metric_alarm" "dev_api_cluster_low" {
    alarm_name = "dev-api-cluster-CPU-Utilization-Low-5"
    comparison_operator = "LessThanThreshold"
    evaluation_periods = "1"
    metric_name = "CPUUtilization"
    namespace = "AWS/ECS"
    period = "300"
    statistic = "Average"
    threshold = "5"
    dimensions {
        ClusterName = "${aws_ecs_cluster.api_cluster.name}"
    }
    alarm_actions = ["${aws_autoscaling_policy.scale_in.arn}"]
}

注意点は以下。

  • dimensionsClusterNameにしています。この場合EC2インスタンスの使用率ではなく、クラスターとしての使用率を元にしているので、stressコマンドでCPUを上げても反応しないです。abでちゃんとcluster自体の使用率を上げる必要があります。

ECS service用のpolicyの追加

resource "aws_appautoscaling_target" "target" {
    service_namespace = "ecs"
    resource_id = "service/${aws_ecs_cluster.api_cluster.name}/${aws_ecs_service.api_service.name}"
    scalable_dimension = "ecs:service:DesiredCount"
    role_arn = "${aws_iam_role.ecs_autoscale_role.arn}"
    min_capacity = 1
    max_capacity = 2
}

resource "aws_appautoscaling_policy" "scale_out" {
    name = "scale-out"
    resource_id = "service/${aws_ecs_cluster.api_cluster.name}/${aws_ecs_service.api_service.name}"
    adjustment_type = "ChangeInCapacity"
    cooldown = 300
    metric_aggregation_type = "Average"
    step_adjustment {
        metric_interval_lower_bound = 0
        scaling_adjustment = 1
    }
    depends_on = ["aws_appautoscaling_target.target"]
}

resource "aws_appautoscaling_policy" "scale_in" {
    name = "scale-in"
    resource_id = "service/${aws_ecs_cluster.api_cluster.name}/${aws_ecs_service.api_service.name}"
    adjustment_type = "ChangeInCapacity"
    cooldown = 300
    metric_aggregation_type = "Average"
    step_adjustment {
        metric_interval_upper_bound = 0
        scaling_adjustment = -1
    }
    depends_on = ["aws_appautoscaling_target.target"]
}

注意点は以下。

  • role_arnにはautoscaleのIAMを設定
  • aws_appautoscaling_policyにはaws_appautoscaling_targetが必須
  • metric_interval_lower_boundmetric_interval_upper_boundの設定値。aws_cloudwatch_metric_alarmのthresholdが50のとき、以下のように扱われます。

f:id:quoll00:20160907121615p:plain

ref: http://docs.aws.amazon.com/ja_jp/autoscaling/latest/userguide/as-scale-based-on-demand.html#as-scaling-steps

なのでスケールアウトの時にmetric_interval_upper_bound = 0としちゃったり、逆にスケールインの時にmetric_interval_lower_bound = 0としちゃうと、スケールポリシーが発火しなくなってしまいます。


ECS service用のalarmの追加

resource "aws_cloudwatch_metric_alarm" "dev_api_service_high" {
    alarm_name = "dev-api-service-CPU-Utilization-High-30"
    comparison_operator = "GreaterThanOrEqualToThreshold"
    evaluation_periods = "1"
    metric_name = "CPUUtilization"
    namespace = "AWS/ECS"
    period = "300"
    statistic = "Average"
    threshold = "30"
    dimensions {
        ClusterName = "${aws_ecs_cluster.api_cluster.name}"
        ServiceName = "${aws_ecs_service.api_service.name}"
    }
    alarm_actions = ["${aws_appautoscaling_policy.scale_out.arn}"]
}

resource "aws_cloudwatch_metric_alarm" "dev_api_service_low" {
    alarm_name = "dev-api-service-CPU-Utilization-Low-5"
    comparison_operator = "LessThanThreshold"
    evaluation_periods = "1"
    metric_name = "CPUUtilization"
    namespace = "AWS/ECS"
    period = "180"
    statistic = "Average"
    threshold = "5"
    dimensions {
        ClusterName = "${aws_ecs_cluster.api_cluster.name}"
        ServiceName = "${aws_ecs_service.api_service.name}"
    }
    alarm_actions = ["${aws_appautoscaling_policy.scale_in.arn}"]
}

注意点は以下。

  • dimensionsにはClusterNameServiceNameの両方が必要。ServiceNameだけだと検知されませんでした。
  • Service側のスケールインのperiodはCluster側のスケールインの時間より短いほうがいい。

スケール検証

abを使って負荷を上げます。ただabだけだとCPU使用率30まで上げるのが大変なので、今回は手動でthresholdを10%に下げて検証しました。

$ sudo yum install -y httpd24-tools

負荷をかけます。

$ ab -n 100000 -c 1000 インスタンスのURL

問題なく設定できていればスケールアウト時は

  1. EC2のインスタンス増加
  2. ECSのserviceのdesired count増加
  3. task増加

スケールイン時は

  1. ECSのserviceのdesired count減少
  2. task減少
  3. EC2のインスタンス減少

となります。

ソース