You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Terraform中aws_scheduler_schedule与IAM角色循环依赖问题求解

解决AWS调度器与IAM角色的Terraform循环依赖问题

问题概述

创建用于启停EC2实例的aws_scheduler_schedule资源时,配置对应的IAM角色后执行terraform plan报错:

Error: Cycle: aws_scheduler_schedule.gh_runners_ubuntu_stop, aws_iam_role.gh_runners_ubuntu_scheduler_role, aws_scheduler_schedule.gh_runners_ubuntu_start

原配置代码如下:

# ---------------------------------------------------------------------------------------------------------------------
# GITHUB-RUNNER EC2 SCHEDULERS
# ---------------------------------------------------------------------------------------------------------------------

resource "aws_scheduler_schedule" "gh_runners_ubuntu_start" {
  for_each = var.gh_runners
  name     = format("%s-%s", "gh_runners_ubuntu_start", each.value.runner_id)

  flexible_time_window {
    mode = "OFF"
  }

  schedule_expression = each.value.start_time

  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
    role_arn = aws_iam_role.gh_runners_ubuntu_scheduler_role[each.key].arn

    input = jsonencode({
      InstanceIds = [
        module.gh_runners_ubuntu[each.key].id
      ]
    })
  }
}

resource "aws_scheduler_schedule" "gh_runners_ubuntu_stop" {
  for_each = var.gh_runners
  name     = format("%s-%s", "gh_runners_ubuntu_stop", each.value.runner_name)

  flexible_time_window {
    mode = "OFF"
  }

  schedule_expression = each.value.stop_time

  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ec2:stopInstances"
    role_arn = aws_iam_role.gh_runners_ubuntu_scheduler_role[each.key].arn

    input = jsonencode({
      InstanceIds = [
        module.gh_runners_ubuntu[each.key].id
      ]
    })
  }
}

resource "aws_iam_role" "gh_runners_ubuntu_scheduler_role" {
  for_each = var.gh_runners
  name     = format("%s-%s", "gh_runners_ubuntu_scheduler_role", each.value.runner_name)

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "events.amazonaws.com"
        }
        Condition = {
          ArnEquals = {
            "aws:SourceArn" = [
              aws_scheduler_schedule.gh_runners_ubuntu_start[each.key].arn,
              aws_scheduler_schedule.gh_runners_ubuntu_stop[each.key].arn,
            ]
          }
        }
      }
    ]
  })
}

resource "aws_iam_role_policy" "gh_runners_ubuntu_scheduler_role_policy" {
  for_each = var.gh_runners
  name     = format("%s-%s", "gh_runners_ubuntu_scheduler_role_policy", each.value.runner_name)
  role     = aws_iam_role.gh_runners_ubuntu_scheduler[each.key].id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["ec2:StopInstances", "ec2:StartInstances"]
        Resource = module.gh_runners_ubuntu[each.key].arn
      }
    ]
  })
}

问题原因

循环依赖产生的核心逻辑:

  • 调度器资源(aws_scheduler_schedule)需要IAM角色的ARN才能创建,因为目标配置中指定了role_arn
  • IAM角色的信任策略又需要调度器的ARN作为aws:SourceArn条件,来限制只有特定调度器能扮演该角色
    两者互相依赖,导致Terraform无法确定资源的创建顺序。

解决方案

方案1:移除信任策略中的源ARN限制(快速解决)

如果不需要严格限制信任源,直接删除IAM角色中的Condition块,打破循环:

resource "aws_iam_role" "gh_runners_ubuntu_scheduler_role" {
  for_each = var.gh_runners
  name     = format("%s-%s", "gh_runners_ubuntu_scheduler_role", each.value.runner_name)

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "events.amazonaws.com"
        }
      }
    ]
  })
}

方案2:手动构造调度器ARN(保持权限严格性)

若需保留aws:SourceArn的安全限制,可通过Terraform数据源获取账户ID和区域,手动构造调度器的ARN(AWS调度器ARN格式固定:arn:aws:scheduler:<region>:<account-id>:schedule/<schedule-name>):

  1. 添加数据源获取当前账户和区域信息:
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
  1. 修改IAM角色的信任策略,用构造的ARN替代对调度器资源的直接引用:
resource "aws_iam_role" "gh_runners_ubuntu_scheduler_role" {
  for_each = var.gh_runners
  name     = format("%s-%s", "gh_runners_ubuntu_scheduler_role", each.value.runner_name)

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "events.amazonaws.com"
        }
        Condition = {
          ArnEquals = {
            "aws:SourceArn" = [
              format("arn:aws:scheduler:%s:%s:schedule/%s-%s",
                data.aws_region.current.name,
                data.aws_caller_identity.current.account_id,
                "gh_runners_ubuntu_start",
                each.value.runner_id
              ),
              format("arn:aws:scheduler:%s:%s:schedule/%s-%s",
                data.aws_region.current.name,
                data.aws_caller_identity.current.account_id,
                "gh_runners_ubuntu_stop",
                each.value.runner_name
              )
            ]
          }
        }
      }
    ]
  })
}

额外修复:IAM角色策略的引用错误

原代码中aws_iam_role_policy的role字段引用名称错误,需修正为正确的IAM角色资源名:

resource "aws_iam_role_policy" "gh_runners_ubuntu_scheduler_role_policy" {
  for_each = var.gh_runners
  name     = format("%s-%s", "gh_runners_ubuntu_scheduler_role_policy", each.value.runner_name)
  role     = aws_iam_role.gh_runners_ubuntu_scheduler_role[each.key].id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["ec2:StopInstances", "ec2:StartInstances"]
        Resource = module.gh_runners_ubuntu[each.key].arn
      }
    ]
  })
}

内容的提问来源于stack exchange,提问作者pkaramol

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 21:12:13