이슈 설명
- 테라폼 코드 수정 없이, 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의 triggers 속성
- triggers 속성은 특정 조건이 변경될 때 null_resource 리소스를 다시 생성하도록 합니다.
이 속성을 올바르게 사용하는 방법을 이해해야 합니다.
- 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
}