lifecycle 메타-인자
lifecycle 메타-인자 (lifecycle reference)
lifecycle 블록으로 각 리소스의 라이프사이클 단계 수행 방식을 커스터마이즈하는 방법을 설명해 드릴게요. create_before_destroy, prevent_destroy, ignore_changes 등을 다뤄요.
출처: 문서
본문
Terraform은 구성을 적용할 때 다음 연산을 수행해요:
- 구성에 정의되었지만 state의 실제 인프라 객체와 연결되지 않은 리소스를 생성.
- state에는 있지만 구성에는 없는 리소스를 파괴.
- 인자가 변경된 리소스를 제자리(in-place)에서 업데이트.
- 인자가 변경되었지만 원격 API 제한 때문에 Terraform이 제자리에서 업데이트할 수 없는 리소스를 파괴·재생성.
- apply 연산 중 실행되도록 구성된 동작을 호출.
lifecycle 블록은 각 리소스에 대해 Terraform이 라이프사이클 단계를 수행하는 방식을 커스터마이즈하는 규칙을 받아들여요. 각 lifecycle 규칙의 지원 여부는 Terraform 구성 블록에 따라 달라져요. 구성에 추가하는 Terraform 블록에 대한 레퍼런스 문서를 참고해요.
state (State)
create_before_destroy를 제외하고 Terraform은 리소스의 lifecycle 규칙을 state에 명시적으로 기록하지 않아요. 그래서 prevent_destroy가 활성화되어 있더라도 리소스 구성을 제거하면 apply 연산 중 실제 인프라를 파괴해요. 실제 리소스를 파괴하지 않고 state에서 리소스를 제거하는 방법은 state에서 리소스 제거를 참고해요. Terraform은 precondition 및 postcondition 검사의 결과는 state에 기록하지만 검사 내용은 기록하지 않아요.
사용법 (Usage)
모든 lifecycle 설정은 Terraform이 의존성 그래프를 구성하고 탐색하는 방식에 영향을 줘요. 그래서 처리 시점이 임의 표현식 평가에는 너무 이르기 때문에 리터럴 값만 사용할 수 있어요.
action_trigger
action_trigger는 지정한 조건에 따라 Terraform이 동작을 자동으로 호출하도록 지시해요. action_trigger 규칙은 다음 인자를 지원하는 블록이에요:
| 인자 | 설명 | 데이터 타입 | 필수 |
|---|---|---|---|
events |
동작을 호출할 라이프사이클 이벤트의 필수 목록. 다음 이벤트를 선언할 수 있어요: before_create(리소스 생성 전 동작 호출), after_create(생성 후), before_update(구성과 일치하도록 인프라 업데이트 전), after_update(업데이트 후), before_destroy(파괴 전), after_destroy(파괴 후). |
list | 필수 |
condition |
동작을 호출하려면 true여야 하는 선택적 표현식. |
expression | 선택 |
actions |
events와 condition 인자가 충족될 때 트리거할 동작의 순서 있는 목록. |
list | 필수 |
on_failure |
동작이 실패할 때 Terraform이 어떻게 동작하는지 지정하는 선택적 키워드. 유효 값: halt(기본 — apply 연산을 멈추고 오류 보고, 리소스는 taint되지 않음), continue(오류 기록 후 apply 계속), taint(apply 연산 멈추고 오류 보고, 다음 apply에서 Terraform이 교체하도록 리소스를 taint로 표시). |
keyword | 선택 |
자세한 내용은 동작 호출을 참고해요. action_trigger는 resource 블록에서 사용할 수 있어요.
create_before_destroy
기본적으로 원격 API 제한 때문에 제자리에서 업데이트할 수 없는 리소스 인자를 바꿔야 할 때, Terraform은 기존 객체를 파괴한 다음 새 구성 인자로 새 대체 객체를 만들어요. create_before_destroy 규칙을 사용해 현재 리소스를 파괴하기 전에 대체 리소스를 만들도록 Terraform에 지시해요.
이는 opt-in 동작이에요. 많은 원격 객체 유형이 고유 이름 요구사항이나 새 객체와 이전 객체가 동시에 존재하기 위해 수용해야 하는 다른 제약을 가지기 때문이에요. 일부 리소스 유형은 충돌을 피하기 위해 각 객체 이름에 임의 접미사를 추가하는 특수 옵션을 제공해요. Terraform CLI는 이런 기능을 자동으로 활성화할 수 없으므로, create_before_destroy를 사용하기 전에 각 리소스 유형의 제약을 이해해야 해요.
create_before_destroy와 리소스 의존성
Terraform은 create_before_destroy 동작을 모든 리소스 의존성에 전파·적용해요. 예를 들어:
create_before_destroy가 리소스A에는 활성화되어 있지만 리소스B에는 없어요.- 리소스
A가 리소스B에 의존하므로, Terraform이 명시적으로 기본값으로 리소스B에 대해create_before_destroy를 활성화하고 state 파일에 저장해요.
결과적으로 그래프의 의존성 순환을 암시하게 되므로 리소스 B에서 create_before_destroy를 false로 재정의할 수 없어요. 리소스가 destroy 연산 중 실행되는 프로비저너를 포함하면 create_before_destroy를 true로 설정하면 프로비저너가 실행되지 않게 해요. create_before_destroy는 resource 블록에서 사용할 수 있어요.
prevent_destroy
prevent_destroy가 true로 설정되면 Terraform은 리소스와 연결된 인프라 객체를 파괴할 plan을 거부하고 오류를 반환해요. 이 인자는 구성에 반드시 존재해야 해요. 이 규칙은 구성을 제거하면 Terraform이 리소스를 파괴하는 것을 막지 못해요. 실제 리소스를 파괴하지 않고 state에서 리소스를 제거하는 방법은 state에서 리소스 제거를 참고해요.
이 규칙을 데이터베이스 인스턴스, 스토리지, 기타 stateful 리소스처럼 재현하는 데 비용이 많이 들 수 있는 객체를 우발적으로 교체하는 것에 대한 보호로 사용해요. 하지만 prevent_destroy를 활성화하면 일부 구성 변경을 적용할 수 없게 되고, 일단 이런 객체가 생성되면 terraform destroy 명령이 작동하지 못하게 돼요. prevent_destroy를 아껴서 사용해요. resource 블록에서 사용할 수 있어요.
ignore_changes
기본적으로 Terraform은 실제 인프라 객체의 현재 설정의 차이를 감지하고 원격 객체를 구성과 일치하도록 업데이트할 plan을 세워요. ignore_changes 인자는 리소스가 미래에 변경될 수 있지만 생성 후에는 리소스에 영향을 주지 말아야 하는 데이터에 대한 참조로 생성될 때 사용해요.
드물게 원격 객체의 설정이 Terraform 밖의 프로세스에 의해 수정되어 Terraform이 다음 실행에서 이를 해결하려 시도하는 경우가 있어요. 단일 객체의 관리 책임을 별도 프로세스와 공유하도록, ignore_changes 메타-인자는 Terraform이 관련 원격 객체에 대한 업데이트를 계획할 때 무시해야 하는 리소스 속성을 지정해요.
Terraform은 create 연산을 계획할 때 주어진 속성 이름에 해당하는 인자를 고려하지만, update 연산을 계획할 때는 무시해요. 인자는 리소스의 속성의 상대 주소예요. tags["Name"]과 list[0]처럼 인덱스 표기법으로 맵·목록 요소를 참조할 수 있어요.
다음 예시에서 Terraform은 tags에 대한 변경을 무시해, 관리 에이전트가 다른 곳에서 관리되는 규칙 집합에 기반해 이를 업데이트할 수 있게 해요:
resource "aws_instance" "example" {
# ...
lifecycle {
ignore_changes = [
tags
]
}
}
항목 목록 대신 all 키워드를 사용해 Terraform이 모든 속성을 무시하도록 지시할 수 있어요. 그러면 Terraform은 원격 객체를 생성·파괴할 수 있지만 업데이트는 절대 제안하지 않아요. Terraform은 리소스 유형이 정의한 속성만 무시해요. ignore_changes를 그 자체나 다른 메타-인자에 적용할 수 없어요. resource 블록에서 사용할 수 있어요.
replace_triggered_by
Terraform은 참조된 리소스나 지정된 속성 중 하나라도 변경되면 리소스를 교체해요. 관리 리소스, 인스턴스, 인스턴스 속성을 참조하는 표현식 목록을 제공해요.
count 또는 for_each를 사용하는 리소스에서 count.index나 each.key를 표현식에 사용해 같은 count 또는 컬렉션으로 구성된 다른 리소스의 특정 인스턴스를 참조할 수 있어요. 참조는 다음 조건에서 교체를 트리거해요:
- 참조가 여러 인스턴스가 있는 리소스라면, 어떤 인스턴스든 업데이트하거나 교체할 plan이 교체를 트리거해요.
- 참조가 단일 리소스 인스턴스라면, 그 인스턴스를 업데이트·교체할 plan이 교체를 트리거해요.
- 참조가 리소스 인스턴스의 단일 속성이라면, 속성 값의 어떤 변경이든 교체를 트리거해요.
replace_triggered_by 표현식에는 관리 리소스만 참조할 수 있어요. 이렇게 하면 교체를 강제하지 않고 표현식을 수정할 수 있어요. 다음 예시에서 Terraform은 이 aws_ecs_service 인스턴스가 교체될 때마다 aws_appautoscaling_target을 교체해요:
resource "aws_appautoscaling_target" "ecs_target" {
# ...
lifecycle {
replace_triggered_by = [
aws_ecs_service.svc.id
]
}
}
replace_triggered_by는 주어진 모든 리소스의 계획된 동작에 기반해 결정되므로 리소스 주소만 허용해요. 로컬 값이나 입력 변수 같은 순수 값은 자체 계획된 동작이 없지만, terraform_data 리소스와 함께 사용해 리소스 같은 라이프사이클로 취급할 수 있어요. resource 블록에서 사용할 수 있어요.
precondition
리소스를 생성하기 전에 Terraform이 평가하는 조건을 지정해요. precondition 블록에서 다음 인자가 필요해요:
| 인자 | 설명 | 데이터 타입 |
|---|---|---|
condition |
Terraform이 연산을 진행하기 위해 true를 반환해야 하는 표현식. 참조가 순환 의존성을 만들지 않는 한 같은 구성 범위의 다른 어떤 객체도 참조할 수 있어요. |
참조, 문자열, 연산자를 포함할 수 있는 표현식 |
error_message |
condition이 false를 반환하면 Terraform이 콘솔에 출력하는 메시지. |
String |
Terraform은 precondition 블록을 리소스의 구성 인자를 평가하기 전에 평가해요. precondition은 인자 평가 오류보다 우선할 수 있어요. Terraform은 count와 for_each 메타-인자를 평가한 후에 precondition 블록을 평가해요. 따라서 Terraform이 각 인스턴스에 대해 precondition을 별도로 평가할 수 있고 조건에 each.key와 count.index 객체를 사용할 수 있어요.
같은 리소스에 precondition과 postcondition 블록을 포함할 수 있어요. 같은 구성을 나타내는 resource 블록과 data 블록 양쪽에 precondition 블록을 추가하지 마세요. 그러면 resource 블록의 변경에서 비롯된 data 블록의 변경을 Terraform이 무시할 수 있어요. data, ephemeral, resource 블록에서 사용할 수 있어요.
postcondition
리소스를 생성한 후 Terraform이 평가하는 조건을 지정해요. postcondition 블록에서 다음 인자가 필요해요:
| 인자 | 설명 | 데이터 타입 |
|---|---|---|
condition |
Terraform이 다운스트림 리소스에 연산을 수행하기 위해 true를 반환해야 하는 표현식. 참조가 순환 의존성을 만들지 않는 한 같은 구성 범위의 다른 어떤 객체도 참조할 수 있어요. |
참조, 문자열, 연산자를 포함할 수 있는 표현식 |
error_message |
condition이 false를 반환하면 Terraform이 콘솔에 출력하는 메시지. |
String |
Terraform은 데이터 소스에 plan·apply 변경을 적용한 후 postcondition 블록을 평가해요. postcondition 실패는 실패한 리소스에 의존하는 다른 리소스의 변경을 방지해요. 같은 리소스에 postcondition과 precondition 블록을 포함할 수 있어요. data, ephemeral, resource 블록에서 사용할 수 있어요.
destroy
false로 설정하면 실제 인프라 리소스를 파괴하지 않고 state에서 리소스를 제거해요.
지원되는 구성 (Supported constructs)
각 lifecycle 규칙의 지원 여부는 Terraform 구성 블록에 따라 달라져요. 구성에 추가하는 Terraform 블록에 대한 레퍼런스 문서를 참고해요.
예시 사용 사례 (Example use cases)
속성 변경 무시 (Ignore attribute changes)
다음 예시에서 Terraform은 리소스의 태그에 대한 변경을 무시해요:
resource "aws_instance" "example" {
# ...
lifecycle {
ignore_changes = [tags]
}
}
리소스를 교체하는 트리거 지정 (Specify triggers that replace resources)
다음 예시에서 Terraform은 이 aws_ecs_service 인스턴스가 교체될 때마다 aws_appautoscaling_target을 교체해요:
resource "aws_appautoscaling_target" "ecs_target" {
# ...
lifecycle {
replace_triggered_by = [
aws_ecs_service.svc.id
]
}
}
인스턴스 생성용 AMI 검증 (Validate the AMI for creating instances)
다음 예시에서 precondition 블록은 data 블록에서 가져온 AMI ID의 architecture 속성이 x86_64인지 보장해요. postcondition 블록은 EC2 인스턴스에 공용 DNS 호스트 이름이 할당되어 있어야 한다고 지정해요. 조건 중 하나라도 충족되지 않으면 Terraform은 실패한 조건의 error_message를 반환해요:
data "aws_ami" "example" {
most_recent = true
owners = ["amazon"]
filter {
name = "image-id"
values = ["ami-*"]
}
}
resource "aws_instance" "example" {
instance_type = "t3.micro"
ami = data.aws_ami.example.id
lifecycle {
precondition {
condition = data.aws_ami.example.architecture == "x86_64"
error_message = "The selected AMI must be for the x86_64 architecture."
}
postcondition {
condition = self.public_dns != ""
error_message = "EC2 instance must be in a VPC that has public DNS hostnames enabled."
}
}
}
루트 스토리지 볼륨이 암호화되었는지 검증 (Validate that a root storage volume is encrypted)
다음 예시에서 data 블록은 volume_id 속성을 사용해 aws_instance.example EC2 인스턴스에 연결된 루트 스토리지 볼륨을 가져와요. data 리소스가 같은 구성에 선언된 관리 리소스의 결과를 검증할 때는 리소스의 postcondition 블록에 검사를 정의해야 Terraform이 관리 리소스 변경이 완료될 때까지 기다린 다음 데이터 리소스를 읽어요:
data "aws_ebs_volume" "example" {
filter {
name = "volume-id"
values = [aws_instance.example.root_block_device[0].volume_id]
}
lifecycle {
# EC2 인스턴스는 암호화된 루트 볼륨을 가질 것임.
postcondition {
condition = self.encrypted
error_message = "The server's root volume is not encrypted."
}
}
}
data "aws_ami" "example" {
most_recent = true
owners = ["amazon"]
filter {
name = "image-id"
values = ["ami-*"]
}
}
resource "aws_instance" "example" {
instance_type = "t3.micro"
ami = data.aws_ami.example.id
}
output "api_base_url" {
value = "https://${aws_instance.example.private_dns}:8433/"
}
이 예시에서는 검증이 실패하므로 Terraform이 콘솔에 다음 메시지를 출력해요:
│ Error: Resource postcondition failed
│
│ on main.tf line 31, in data "aws_ebs_volume" "example":
│ 31: condition = self.encrypted
│ ├────────────────
│ │ self.encrypted is false
│
│ The server's root volume is not encrypted.
ephemeral 리소스 검증 (Validate ephemeral resources)
다음 예시에서 aws_ssm_parameter ephemeral 리소스는 프로덕션 시크릿을 보호하기 위해 규정 준수 모드가 활성화되었는지 확인하는 precondition과, 생성된 비밀번호가 비밀번호 요구사항을 충족하는지 확인하는 postcondition을 가져요:
variable "environment" {
description = "Deployment environment"
type = string
}
variable "compliance_mode" {
description = "Enable compliance requirements for production"
type = bool
default = false
}
ephemeral "aws_ssm_parameter" "database_password" {
name = "/secrets/${var.environment}/database/password"
lifecycle {
precondition {
condition = var.environment != "prod" || var.compliance_mode == true
error_message = "Enable compliance mode to assess production secrets."
}
postcondition {
condition = can(regex("^[A-Za-z0-9!@#$%^&*()_+=-]{16,}$", self.value))
error_message = "Password from external source must meet security requirements."
}
}
}