2023-11-30

terraform moduleを勉強してスケジュールされたECSタスクを簡単に作れるようにする

terraform

株の自動取引関連の話。 各種取引のバッチ処理のためによくECS + Event Schedulerの構成を利用している。 Fargateの上でECSを動かすと高くなるイメージだが、短時間の処理+低スペックのインスタンスを使えば殆ど気にならない。 短時間のバッチであればlambdaのほうが良いという意見もあるかもしれないが、 lambdaは多重起動の問題と処理時間が最長15分という制約があるため株取引のバッチ処理としてはやや使いづらい。

ただterraformでECSとEvent Schedulerを組み合わせたタスクを定義しようとするとaws_ecs_task_definitionaws_scheduler_scheduleの2つを都度定義する必要があり記述が冗長になる。 気軽にタスクを作成してあれこれ動かせるようにしたいので、ECSタスク定義の心理的なハードルを下げたい。 そこでこれら2つをまとめたlocal moduleを作成し、見通し良くECSタスクを定義できるようにした。

terraform moduleのお勉強

公式のドキュメント見ながら勉強する。

Creating Modules | terraform

moduleを作るためにはディレクトリを作って、その中にresourceとvariableとoutputを定義していく。 resourceは使い慣れているが、これまで基本的にベタでresourceを書いてきたのでvariable句やoutput句を使ったことが無かった。

variable句はその名前の通りmoduleに対して外部から与える変数を定義する。 variable句を駆使することでmoduleから多様なリソースを生成できる。

output句ではmodule外部に対して公開するresourceの属性などを定義する。 呼び出し側からmodule内で作成したresourceのarnを使いたい場合にoutput句で公開しているイメージだろう。

ドキュメントではresource, variable, outputをそれぞれmain.tf, variables.tf, outputs.tfというファイル名で管理することを推奨している。

実践

なんとなく全体像がつかめたので実際にaws_ecs_task_definitionaws_scheduler_scheduleをまとめたlocal moduleを作ってみる。 今回はECSの定期実行用のmoduleなのでcronjobというフォルダを作ってそこに必要なファイルを配置する。 外部に対して公開したい情報は無いのでoutputは使わずにresourceとvariableだけを定義していく。

まずはresourceの定義。 特に気にすることなくresourceを定義していく。 可変にしたいところはvar.変数名という形で記載しておいてvariable.tfで具体的な型定義を与える。

txt
Copied!
resource "aws_ecs_task_definition" "cronjob_ecs" {
  family                   = var.ecs_family_name
  container_definitions    = file(var.task_definition)
  execution_role_arn       = var.deployment_role_arn
  task_role_arn            = var.execution_role_arn
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]

  cpu    = 1024
  memory = 8192
}


resource "aws_scheduler_schedule" "cronjob_schedule" {
  name                         = format("%s%s", var.ecs_family_name, "scheduler")
  group_name                   = "default"
  schedule_expression_timezone = "Asia/Tokyo"

  flexible_time_window {
    mode = "OFF"
  }

  schedule_expression = var.schedule_expression

  target {
    # ecs clusterの名前
    arn      = var.cluster_name
    role_arn = var.eventbridge_role_arn
    ecs_parameters {
      enable_ecs_managed_tags = true
      enable_execute_command  = false
      launch_type             = "FARGATE"
      task_definition_arn     = aws_ecs_task_definition.cronjob_ecs.arn

      network_configuration {
        assign_public_ip = true
        security_groups  = var.security_groups
        subnets          = var.subnets
      }
    }
    retry_policy {
      maximum_event_age_in_seconds = 86400
      maximum_retry_attempts       = 0
    }
  }
}

次に各引数の定義。名称と型を定義していく。 descriptionは任意だが、インフラをコード化して管理可能性を高めるというterraformの目的を考えれば定義したほうが無難だろう。 ただ正直めんどくさくて割と適当。

txt
Copied!
variable "cluster_name" {
  type        = string
  description = "deployment target cluster name"
}
variable "ecs_family_name" {
  type        = string
  description = "ecs task family name"
}

variable "task_definition" {
  type        = string
  description = "path to the ecs task definition file"
}

variable "eventbridge_role_arn" {
  type        = string
  description = "IAM role used to trigger ecs in the eventbridge"
}

variable "deployment_role_arn" {
  type        = string
  description = "IAM role used when ECS task is deployed"
}

variable "execution_role_arn" {
  type        = string
  description = "IAM role used in the ECS task"
}

variable "schedule_expression" {
  type        = string
  description = "cron like schedule expression. e.g. cron(00 8 ? * * *)"
}

variable "security_groups" {
  type        = set(string)
  description = "security group id list"
}

variable "subnets" {
  type        = set(string)
  description = "subnet id list"
}

これでmoduleの定義が終わったのでroot moduleから呼び出す。 moduleを呼び出す際はmoduleを使う。sourceで作成したmoduleのパスを指定してあげて、他にも必要な変数があれば渡してあげる。 moduleを定義した後の初回実行時はterraform planを実行する前にterraform initを実行が必要になる。 無事terraform initが終われば、terraform planで差分が見えるようになる。

txt
Copied!
module "daily_report" {
  source = "./crobjob"

  cluster_name         = aws_ecs_cluster.walker-ecs-cluster.arn
  ecs_family_name      = "walker-daily-report"
  task_definition      = "./tasks/task_daily_report.json"
  schedule_expression  = "cron(00 17 ? * * *)"
  eventbridge_role_arn = aws_iam_role.iam_role_for_event_bridge.arn
  deployment_role_arn  = aws_iam_role.iam_role_for_ecs_task_deployment.arn
  execution_role_arn   = aws_iam_role.iam_role_for_ecs_task_execution.arn

  security_groups = [aws_security_group.security-group.id]
  subnets         = [aws_subnet.subnet-2.id, aws_subnet.subnet-3.id, aws_subnet.subnet-4.id]
}

おわりに

moduleを用意できたのでかなり気軽にECSのタスクを定義できるようになった。 terraformのドキュメントを読むと知らない機能が結構あったので、どこかでまとめてキャッチアップしたい。