영기
article thumbnail

이슈 설명

- 테라폼 코드 수정 없이, terraform apply 를 실행할 때마다 EC2 인스턴스와 관련된 리소스들이 삭제되고 새로 생성

 

 

문제의 코드

# AWS에서 PEM 키 파일의 존재 여부를 확인하는 null_resource
resource "null_resource" "check_aws_key_pair" {
  provisioner "local-exec" {
    command = <<EOT
      if aws ec2 describe-key-pairs --key-names ${var.pem_file_name} 2>/dev/null; then
        echo 'key_pair_exists=true' > ${local.key_pair_check_file}
      else
        echo 'key_pair_exists=false' > ${local.key_pair_check_file}
      fi
    EOT
  }

  # [문제 원인] always_run 트리거가 매번 현재 시간을 사용하여 강제로 트리거를 실행
  triggers = {
    always_run = "${timestamp()}"
  }
}

# 새로운 키를 생성하는 null_resource (AWS에서 PEM 키 파일이 없는 경우)
# [문제 원인] null_resource가 항상 변경된 것으로 간주되어 재생성됨
resource "null_resource" "create_new_key" {
  provisioner "local-exec" {
    command = <<EOT
      if [ "${local.key_pair_exists}" == "false" ]; then
        aws ec2 create-key-pair --key-name ${local.new_key_name} --query 'KeyMaterial' --output text > ${local.new_key_file_path}
        chmod 400 ${local.new_key_file_path}
      fi
    EOT
  }

  triggers = {
    key_pair_exists = local.key_pair_exists ? "true" : "false",
    create_key = "${local.random_suffix}"
  }
}

 

문제의 전체 코드

더보기
provider "aws" {
  region = var.region
}

variable "pem_file_name" {
  description = "The name of the PEM key file (without .pem extension)"
  type        = string
  default     = "my-tf-test-2ada6"
}

variable "ec2_instance_name" {
  description = "The name of the EC2 instance"
  type        = string
  default     = "my-ec2-instance"
}

variable "region" {
  description = "The AWS region to create resources in"
  type        = string
  default     = "us-east-2"
}

variable "file_key" {
  description = "The key (name) of the file to be uploaded to S3"
  type        = string
  default     = "test_2.txt"
}

variable "instance_count" {
  description = "The number of EC2 instances to create"
  type        = number
  default     = 3
}

locals {
  key_pair_check_file = "${path.module}/key_pair_exists.txt"
}

# AWS에서 PEM 키 파일의 존재 여부를 확인하는 null_resource
resource "null_resource" "check_aws_key_pair" {
  provisioner "local-exec" {
    command = <<EOT
      if aws ec2 describe-key-pairs --key-names ${var.pem_file_name} 2>/dev/null; then
        echo 'key_pair_exists=true' > ${local.key_pair_check_file}
      else
        echo 'key_pair_exists=false' > ${local.key_pair_check_file}
      fi
    EOT
  }

  triggers = {
    always_run = "${timestamp()}"
  }
}

# 결과 파일을 읽기 위한 data 소스
data "local_file" "key_pair_exists_file" {
  filename = local.key_pair_check_file
  depends_on = [null_resource.check_aws_key_pair]
}

# 결과를 로컬 변수로 정의
locals {
  key_pair_exists = trimspace(data.local_file.key_pair_exists_file.content) == "key_pair_exists=true"
  random_suffix = substr(uuid(), 0, 5)
  new_key_name = "${var.pem_file_name}-${local.random_suffix}"
  new_key_file_path = "${path.module}/${local.new_key_name}.pem"
}

# EC2 인스턴스를 생성
resource "aws_instance" "ec2_instance" {
  count         = var.instance_count
  ami           = "ami-0c55b159cbfafe1f0"  # 원하는 AMI ID로 변경하세요
  instance_type = "t2.micro"
  key_name      = local.key_pair_exists ? var.pem_file_name : local.new_key_name

  tags = {
    Name = "${var.ec2_instance_name}-${count.index}"
  }

  depends_on = [null_resource.create_new_key]
}

# 새로운 키를 생성하는 null_resource (AWS에서 PEM 키 파일이 없는 경우)
resource "null_resource" "create_new_key" {
  provisioner "local-exec" {
    command = <<EOT
      if [ "${local.key_pair_exists}" == "false" ]; then
        aws ec2 create-key-pair --key-name ${local.new_key_name} --query 'KeyMaterial' --output text > ${local.new_key_file_path}
        chmod 400 ${local.new_key_file_path}
      fi
    EOT
  }

  triggers = {
    key_pair_exists = local.key_pair_exists ? "true" : "false",
    create_key = "${local.random_suffix}"
  }
}

# 키 페어 존재 여부를 출력하는 output
output "key_pair_exists" {
  value = local.key_pair_exists
}

# 새로운 키 이름을 출력하는 output (AWS에서 PEM 키 파일이 없는 경우)
output "new_key_name" {
  value = local.key_pair_exists ? "" : local.new_key_name
}

# 새로운 키 파일 경로를 출력하는 output (AWS에서 PEM 키 파일이 없는 경우)
output "new_key_file_path" {
  value = local.key_pair_exists ? "" : local.new_key_file_path
}

 

원인 분석

- null_resource 리소스의 triggers 속성에서 always_run 트리거가 사용된 것.
- always_run 트리거는 매번 현재 시간을 사용하여 트리거를 강제로 실행하도록 설정되어 있음.
   즉, null_resource 리소스가 항상 변경된 것으로 간주됩니다.
   이에 따라 해당 리소스(pem key)에 의존하는 다른 리소스(EC2)들도 재생성됩니다.

문제의 코드 - terraform plan

 

문제 해결을 위해 필요한 배경 지식

 

  • Terraform의 triggers 속성
    • triggers 속성은 특정 조건이 변경될 때 null_resource 리소스를 다시 생성하도록 합니다.
      이 속성을 올바르게 사용하는 방법을 이해해야 합니다.
  • Terraform의 리소스 의존성 관리
    • depends_on 속성을 사용하여 리소스 간의 의존성을 관리하는 방법을 알아야 합니다.
  • Terraform의 lifecycle 블록
    • lifecycle 블록을 사용하여 리소스의 변경 사항을 무시하거나 생성 전에 특정 조건을 확인하는 방법을 이해해야 합니다.

 

문제 해결

  • always_run 트리거를 제거 > null_resource 리소스의 재생성을 방지
  • depends_on 속성을 사용하여 리소스 간의 의존성을 정확히 명시함

 

수정된 코드

# 수정 부분: always_run 트리거 제거하고, PEM 키 파일 이름을 트리거로 사용
resource "null_resource" "check_aws_key_pair" {
  provisioner "local-exec" {
    command = <<EOT
      if aws ec2 describe-key-pairs --key-names ${var.pem_file_name} 2>/dev/null; then
        echo 'key_pair_exists=true' > ${local.key_pair_check_file}
      else
        echo 'key_pair_exists=false' > ${local.key_pair_check_file}
      fi
    EOT
  }

  triggers = {
    key_pair_name = var.pem_file_name
  }
}

# 수정 부분: null_resource의 의존성을 명확히 하고 불필요한 재생성을 방지
resource "null_resource" "create_new_key" {
  provisioner "local-exec" {
    command = <<EOT
      if [ "${local.key_pair_exists}" == "false" ]; then
        aws ec2 create-key-pair --key-name ${local.new_key_name} --query 'KeyMaterial' --output text > ${local.new_key_file_path}
        chmod 400 ${local.new_key_file_path}
      fi
    EOT
  }

  triggers = {
    key_pair_exists = local.key_pair_exists ? "true" : "false",
    create_key = "${local.random_suffix}"
  }

  depends_on = [null_resource.check_aws_key_pair]
}

 

수정된 전체 코드

더보기
provider "aws" {
  region = var.region
}

variable "pem_file_name" {
  description = "The name of the PEM key file (without .pem extension)"
  type        = string
  default     = "my-tf-test-2ada6"
}

variable "ec2_instance_name" {
  description = "The name of the EC2 instance"
  type        = string
  default     = "my-ec2-instance"
}

variable "region" {
  description = "The AWS region to create resources in"
  type        = string
  default     = "us-east-2"
}

variable "file_key" {
  description = "The key (name) of the file to be uploaded to S3"
  type        = string
  default     = "test_2.txt"
}

variable "instance_count" {
  description = "The number of EC2 instances to create"
  type        = number
  default     = 3
}

locals {
  key_pair_check_file = "${path.module}/key_pair_exists.txt"
}

# AWS에서 PEM 키 파일의 존재 여부를 확인하는 null_resource
resource "null_resource" "check_aws_key_pair" {
  provisioner "local-exec" {
    command = <<EOT
      if aws ec2 describe-key-pairs --key-names ${var.pem_file_name} 2>/dev/null; then
        echo 'key_pair_exists=true' > ${local.key_pair_check_file}
      else
        echo 'key_pair_exists=false' > ${local.key_pair_check_file}
      fi
    EOT
  }

  triggers = {
    key_pair_name = var.pem_file_name
  }
}

# 결과 파일을 읽기 위한 data 소스
data "local_file" "key_pair_exists_file" {
  filename = local.key_pair_check_file
  depends_on = [null_resource.check_aws_key_pair]
}

# 결과를 로컬 변수로 정의
locals {
  key_pair_exists = trimspace(data.local_file.key_pair_exists_file.content) == "key_pair_exists=true"
  random_suffix = substr(uuid(), 0, 5)
  new_key_name = "${var.pem_file_name}-${local.random_suffix}"
  new_key_file_path = "${path.module}/${local.new_key_name}.pem"
}

# 새로운 키를 생성하는 null_resource (AWS에서 PEM 키 파일이 없는 경우)
resource "null_resource" "create_new_key" {
  provisioner "local-exec" {
    command = <<EOT
      if [ "${local.key_pair_exists}" == "false" ]; then
        aws ec2 create-key-pair --key-name ${local.new_key_name} --query 'KeyMaterial' --output text > ${local.new_key_file_path}
        chmod 400 ${local.new_key_file_path}
      fi
    EOT
  }

  triggers = {
    key_pair_exists = local.key_pair_exists ? "true" : "false",
    create_key = "${local.random_suffix}"
  }

  depends_on = [null_resource.check_aws_key_pair]
}

# EC2 인스턴스를 생성
resource "aws_instance" "ec2_instance" {
  count         = var.instance_count
  ami           = "ami-0c55b159cbfafe1f0"  # 원하는 AMI ID로 변경하세요
  instance_type = "t2.micro"
  key_name      = local.key_pair_exists ? var.pem_file_name : local.new_key_name

  tags = {
    Name = "${var.ec2_instance_name}-${count.index}"
  }

  depends_on = [null_resource.create_new_key]
}

# 키 페어 존재 여부를 출력하는 output
output "key_pair_exists" {
  value = local.key_pair_exists
}

# 새로운 키 이름을 출력하는 output (AWS에서 PEM 키 파일이 없는 경우)
output "new_key_name" {
  value = local.key_pair_exists ? "" : local.new_key_name
}

# 새로운 키 파일 경로를 출력하는 output (AWS에서 PEM 키 파일이 없는 경우)
output "new_key_file_path" {
  value = local.key_pair_exists ? "" : local.new_key_file_path
}

 

profile

영기

@yeongki0944

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그