Help us understand the problem. What is going on with this article?

terraform で ECS Fargateキャパシティープロパイダーを意識しつつECSクラスターを構築する

ECSにはデフォルトで2つのキャパシティープロパイダーが用意されており、1つは通常の FARGATE で、もうひとつは FARGATE_SPOT です。
FARGATE_SPOT により起動されたタスクは、中断されてしまうリスクはありますが、お値段が7割引となっており、 autoscaling などと合わせてうまく使わない手はありません。

この記事では、terraform により、Fargateキャパシティープロパイダーを意識してECSクラスターを構築する方法について考えたいと思います。

サンプルコードについて

この記事のためにサンプルコードを用意しました。
https://github.com/kawahara/ecs-fargate-autoscale

このサンプルコードは、VPC, ALB, NATゲートウェイなどを含めて、動く一歩手前のところまでを構築するものです。
他にも多くのサンプルはありますが、Single AZ, Public Subnet のみの例が多く、
今回は、複数のAZに展開し、Fargateにより展開するアプリケーションを Private Subnet 上に展開します。
(ちょっとサボってて、NATゲートウェイが Single AZ なのはご了承ください。。)

image.png

また、複数環境に対応するために、 workspaceを利用する前提をしております。

terraform のバージョンは 0.12.29 を利用しております。

サンプルコードでの環境構築

特に、workspace を指定しない場合 (default という名前がそれぞれのリソースに付きます) は、以下のような手順で環境を構築できます。
事前に、以下の環境が揃っているものとします。

  • dockerのインストール
  • terraform 0.12.29 のインストール
  • aws cli によるプロファイル設定

AWS Systems Manager (SSM) によるパラメータの設定

後述していますが、環境変数の割り当てを確認するため、このサンプルコードではSSMを利用して情報を取得しています。
このため、事前に手動で環境変数を設定する必要があります。

AWSのコンソールから、AWS Systems Manager に行き、「パラメータストア」を選びます。
そこから、「パラメータの作成」を選び、名前を /my-sample-app/default/sample として、
タイプを 「安全な文字列」を選び、値に適当なものを設定します。

image.png

KMSのARNを探す

次にAWSコンソールから、Key Management Service に行き、「AWSマネージド型キー」を選びます。
エイリアスが、aws/ssm となっているものを探し、そのキーのARNをコピーしておきます。

この情報は先程SSMに保存した暗号化された値を、復号するために利用されます。
このため、このARNは環境構築上必要です。

適用

terraform init
AWS_DEFAULT_REGION='ap-northeast-1' TF_VAR_ssm_kms_arn='SSMに利用しているKMSキーのARN' terraform apply

TF_VAR_ssm_kms_arn については、terraform.tfvars を作成し、以下の内容を記載することで省略ができます。

ssm_kms_arn=SSMに利用しているKMSキーのARN

Docker image の push

構築した直後では、Docker image がまだpushされていないため、ECSでタスクを開始することができません。
サンプルコードでは、PHPスクリプトで phpinfo(); を表示するための、シンプルな Dockerfile を用意しています。

push の手順は、AWSコンソール上のECRリポジトリの画面に記載されています。

cd docker
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxx.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t my-sample-app/default/server .
docker tag my-sample-app/default/server:latest xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-sample-app/default/server:latest
docker push xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-sample-app/default/server:latest

ALBからの動作確認

今回の環境では、Private Subnet 側にタスクを展開しているため、外からは個別の環境にアクセスすることができません。
ALBからリクエストして確認します。

AWSコンソール上のECSクラスターのタスクから、タスクの状態が RUNNING になっていることを確認します。

image.png

その上で、AWSコンソールからEC2に行き、ロードバランサーの設定を見ます。

image.png

ブラウザーから記載されているアドレス(DNS名) にアクセスして、 /sample.php が正しく表示されることを確認しましょう。

image.png

2タスク動いているので、リロードするとホストが変化するのがわかるかと思います。

また、SSMによって設定されている、環境変数の値もバッチリ設定されていますね。

image.png

削除

実験が終わった時にリソースを削除するには、単純に destroy を実行して削除できます。

AWS_DEFAULT_REGION='ap-northeast-1' TF_VAR_ssm_kms_arn='SSMに利用しているKMSキーのARN' terraform destroy

以降、今回のサンプルコードで注目するポイントについて解説します。

サービスのキャパシティープロパイダーの設定

aws_ecs_service リソースの作成時に、capacity_provider_strategyweight を指定することで、通常のFargateとFargate spot での起動するタスクの割合を調整することができます。
例えば、1:1 のときに、4タスク起動する場合は、2タスクは FARGATE で、残り2つのタスクは FARGATE_SPOT により起動されます。
capacity_provider_strategy では、 base も指定することができますが、2020年8月現在では、
run-task の場合にのみ利用されるもので、サービスからタスクを起動する場合は weight のみが参照されます。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/cluster-capacity-providers.html

キャパシティープロバイダー戦略を指定する場合、base 値はタスクの実行時にのみサポートされます。サービスを作成する場合、キャパシティープロバイダー戦略 base パラメータはサポートされていません。

今現在、 capacity_provider_strategy については、terraform 上で変更してしまうと、 service が更新ではなく作り直しになってしまう問題があるので、ignore_changes を設定しています。
この値を構築後に変更する場合は、コンソール上のサービスから更新するか、CLIを使う必要がありそうです。

https://github.com/terraform-providers/terraform-provider-aws/issues/11351

resource "aws_ecs_service" "service" {
  name            = "my-sample-app-${terraform.workspace}"
  cluster         = aws_ecs_cluster.cluster.id
  task_definition = aws_ecs_task_definition.task.arn
  desired_count   = 0

  capacity_provider_strategy {
    capacity_provider = "FARGATE"
    base              = 0
    weight            = var.ecs_cluster.fargate_weight
  }

  capacity_provider_strategy {
    capacity_provider = "FARGATE_SPOT"
    base              = 0
    weight            = var.ecs_cluster.fargate_spot_weight
  }

  ...

  lifecycle {
    ignore_changes = [
      desired_count,
      task_definition,
      # NOTE: https://github.com/terraform-providers/terraform-provider-aws/issues/11351
      capacity_provider_strategy
    ]
  }
}

ちょっと実験: キャパシティープロパイダー の weight による挙動

どっちのキャパシティープロパイダーで起動したのかの確認

クラスター -> タスク から確認できるようです。

image.png

コンソール画面上には現状、どの起動タイプで起動したかを一覧でみる画面がないのがやや不便ですね。
weight の設定で、タスクがどのように起動するのかを観察してみます。

FARGATE:FARGATE_SPOT = 1:1 で3台起動する

image.png

Fargate 2台、Fargate Spot 1台で起動しました。
サンプルコードでは、デフォルトでこの設定を利用しています。

FARGATE_SPOT:FARGATE = 1:1 で3台起動する

image.png

Fargete Spot2台、Fargate1台で起動しました。
したがって、記載順序は大いに関係するようです。
タスクの数が奇数の場合にFargate Spot を多めに起動しつつ、Fargateも起動しておきたい場合は、
この順序で設定すると良さそうです。

FARGATE_SPOT:FARGATE = 1:0 で3台起動する

image.png

この場合、すべてのタスクはFargate Spot で起動します。
本番環境でなければこれで問題ないかもしれません。

【参考にした記事】

Fargate + autoscaling

CPU利用率が高くなってしまった場合は、サービスの必要タスク数を増やすして、スケールアウトする設定をしましょう。
サンプルコードでは、CPU使用率が75%を超えた場合に、desired count を増やす設定をしています。

スケールアウトだけでなく、CPU使用率が低いときに desired count を減らす設定もしておきましょう。

# オートスケールターゲット
resource "aws_appautoscaling_target" "ecs" {
  service_namespace  = "ecs"
  resource_id        = "service/${aws_ecs_cluster.cluster.name}/${aws_ecs_service.service.name}"
  scalable_dimension = "ecs:service:DesiredCount"
  min_capacity       = var.ecs_autoscale.min
  max_capacity       = var.ecs_autoscale.max
}

# スケールアウトを行う設定
resource "aws_appautoscaling_policy" "out" {
  name               = "my-sample-app-out-${terraform.workspace}"
  resource_id        = "service/${aws_ecs_cluster.cluster.name}/${aws_ecs_service.service.name}"
  scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
  service_namespace  = aws_appautoscaling_target.ecs.service_namespace

  step_scaling_policy_configuration {
    adjustment_type         = "ChangeInCapacity"
    cooldown                = 300
    metric_aggregation_type = "Average"

    step_adjustment {
      metric_interval_upper_bound = 0
      scaling_adjustment          = 1
    }
  }

  depends_on = [aws_appautoscaling_target.ecs]
}

# CPU使用率によるアラーム設定
resource "aws_cloudwatch_metric_alarm" "out" {
  alarm_name          = "my-sample-app-${terraform.workspace}-ecs-cpu-gt-75"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "1"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/ECS"
  period              = "300"
  statistic           = "Average"
  threshold           = "75"

  dimensions = {
    ClusterName = aws_ecs_cluster.cluster.name
    ServiceName = aws_ecs_service.service.name
  }

  alarm_actions = [aws_appautoscaling_policy.out.arn]
}

【参考にした記事】

SSMから環境変数に設定する値を取得する

本題とは関係が無いですが、パスワード情報などの機密性の高い情報については、
AWS Systems Manager (SSM) のパラメータストアを利用して環境変数に設定しています。

SSMからデータを取得してタスクを起動する場合は、タスク定義のタスク起動ロールで、
SSM利用と、暗号キー利用の権限が必要です。

今回は、SSMにおけるパラメーター名を、/my-sample-app/${環境名}/${パラメータ名} としたため、
/my-sample-app/${環境名}/* をについて、ssm:GetParameters を許可するようにします。

以下のようにIAMロールを作成する設定を行いました。

resource "aws_iam_role" "execution" {
  name = "execution"
  path = "/my-sample-app/${terraform.workspace}/ecs/"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ExecutionRole",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs-tasks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "execution" {
  role       = aws_iam_role.execution.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

data "aws_iam_policy_document" "ecs_get_ssm" {
  statement {
    sid = "KSMDecrypt"
    actions = [
      "kms:Decrypt"
    ]
    resources = [
      var.ssm_kms_arn
    ]
  }

  statement {
    sid = "SSMGetParameters"
    actions = [
      "ssm:GetParameters"
    ]
    resources = [
      "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/my-sample-app/${terraform.workspace}/*"
    ]
  }
}

resource "aws_iam_policy" "ecs_execution_ssm" {
  name        = "my-sample-app-execution-ssm-${terraform.workspace}"
  description = "read permission to fetch ssm secret params"
  policy      = data.aws_iam_policy_document.ecs_get_ssm.json
}

ここで作成したIAMロールを、タスク定義で利用しています。

【参考にした記事】

ooharabucyou
地球に住んでいます。
http://www.bucyou.net
codeal
即戦力複業ならコデアル。高収入×リモートワークの複業・フリーランス・転職求人多数
https://www.codeal.work
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした