LabHub
배우기 러닝패스 코스

Terraform/OpenTofu Fundamentals

Variable Precedence, Outputs, and the Illusion of Sensitive Values

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

같은 코드가 dev·stage·prod 를 감당하려면 값이 코드 밖으로 나가야 한다. 문제는 값을 넣을 수 있는 자리가 여럿이고, 그 사이에 정해진 강약이 있다는 것이다.

Concept map: 정해진 강약 · validation 의 진짜 값어치는 실패 시점을 앞당기는 것 · 이름 붙인 식 · 출력을 화면에서 가릴 뿐

왜 이게 필요했나

환경이 셋인데 다른 것은 인스턴스 크기와 복제 수뿐이라고 하자. 가장 쉬운 해법은 디렉터리를 세 벌 복사하는 것이다. 그리고 반년 뒤, 세 디렉터리는 서로 다른 생물이 되어 있다. prod 에만 들어간 보안 설정이 stage 에는 없고, dev 에서 고친 버그가 prod 에는 반영되지 않았다. 사고는 언제나 그 틈에서 난다.

변수는 이 복사를 막는 장치다. 코드는 하나로 두고 값만 갈아 끼우면, 환경 간 차이가 "파일 세 벌의 diff" 에서 "값 몇 개"로 줄어든다. 그러면 사람이 차이를 눈으로 확인할 수 있게 된다.

어떻게 동작하나

값을 넣는 자리는 여럿이고, 나중에 오는 것이 이긴다.

강도 자리 성격
가장 약함 변수 선언의 default "아무도 안 주면 이 값"
약함 TF_VAR_이름 환경변수 CI 러너나 셸에서 주입
중간 terraform.tfvars 이 디렉터리의 표준 값
조금 셈 *.auto.tfvars (파일명 사전순) 자동으로 함께 읽히는 값
-var-file=파일 이번 실행에 고른 값 묶음
가장 셈 -var 이름=값 이번 한 번의 예외

변수 선언에는 값 말고도 붙일 것이 세 가지 있다. type 은 잘못된 형태를 초기에 막고, description 은 이 값이 무엇인지 알려 주며, validation 은 허용 범위를 코드로 못 박는다. validation 의 진짜 값어치는 실패 시점을 앞당기는 것이다. 검증이 없으면 잘못된 값이 적용 중반에 프로바이더 오류로 터지고, 그때는 이미 절반이 만들어진 상태다.

locals 는 변수처럼 보이지만 성격이 다르다. 밖에서 주입할 수 없는 이름 붙인 식이다. 여러 변수를 조합한 이름 접두어처럼, 한 번 정의해 여러 곳에서 쓰고 싶은 값에 쓴다.

output 은 이 설정의 공개 인터페이스다. 모듈로 쓰일 때는 다른 코드가 참조하는 유일한 창구이므로, 모든 출력에 description 을 다는 것이 좋다. 그리고 -json 옵션을 주면 기계가 읽는 형식으로 나와 파이프라인에서 쓰기 좋다.

마지막으로 sensitive = true 다. 이것은 출력을 화면에서 가릴 뿐이다. 상태 파일에는 값이 평문으로 남고, output -json 에도 값이 그대로 들어 있다. 이 사실을 모르면 "민감 표시를 했으니 안전하다"는 착각으로 상태 파일을 아무 데나 두게 된다.

현장에서 만나는 모습

첫째, -var 로 끄고 잊은 불. 장애 대응 중 -var replicas=10 으로 서비스를 살렸다면 그 값은 반드시 저장소에 커밋돼야 한다. 그러지 않으면 다음 정상 배포가 조용히 예전 값으로 되돌린다. 명령줄 값이 가장 세다는 성질은 응급 상황에서 유용한 만큼, 기록이 남지 않는다는 대가를 함께 지불한다.

둘째, 기본값의 함정. "일단 돌아가게" 하려고 모든 변수에 기본값을 넣으면, 값을 주지 않았을 때 조용히 개발용 설정으로 프로덕션이 만들어진다. 환경마다 반드시 달라야 하는 값에는 기본값을 주지 않는 편이 안전하다. 도구는 값이 없으면 물어보거나 실패하는데, 그 실패가 사고보다 훨씬 싸다.

셋째, 계획 로그에 남는 비밀. plan 출력에는 리소스 속성이 그대로 찍힌다. CI 로그를 사내 누구나 볼 수 있는 곳에 쌓아 두면 그것이 곧 유출 경로다. 민감 표시와 별개로 로그 보관 정책을 함께 봐야 하는 이유다.

값이 어디서 오는지 확실히 하기

변수 하나에 값을 줄 수 있는 통로가 여섯이다. 우선순위를 모르면 "분명히 바꿨는데 안 바뀐다" 를 겪는다. 아래로 갈수록 세다.

  1. 기본값(default)
  2. 환경 변수 TF_VAR_이름
  3. terraform.tfvars
  4. *.auto.tfvars(파일 이름 순서)
  5. -var-file= 로 준 파일
  6. -var= 로 직접 준 값

같은 파일 안에서 두 번 적으면 오류지만, 다른 통로면 조용히 덮인다. CI 가 TF_VAR_ 를 심어 두었는데 로컬에서 terraform.tfvars 를 고치고 있다면 셋째가 둘째를 이기므로 로컬에서는 되고 CI 에서는 안 된다. 값이 어디서 왔는지 확인하려면 계획을 파일로 받아 읽는다.

terraform plan -out=tfplan
terraform show -json tfplan | jq '.variables'

검증은 변수 선언에 붙인다. 잘못된 값을 apply 중간에 만나면 이미 절반은 만들어진 뒤다.

variable "env" {
  type = string
  validation {
    condition     = contains(["dev", "stg", "prod"], var.env)
    error_message = "env 는 dev, stg, prod 중 하나여야 합니다."
  }
}

sensitive = true 는 화면만 가린다. 상태 파일과 계획 파일에는 값이 그대로 들어간다. 비밀은 변수로 넘기지 말고 비밀 저장소에서 데이터 소스로 읽는 편이 낫고, 그마저도 상태에는 남는다는 것을 전제로 저장소를 다룬다.

출력은 다음 사람에게 남기는 인터페이스다. 모듈의 출력은 그 모듈을 쓰는 쪽이 의존하게 되므로, 한 번 정하면 바꾸기 어렵다. 안에서 쓰는 자원 이름을 그대로 내보내는 대신 의미로 이름을 짓는다: instance_id 보다 app_server_id 가, ip 보다 private_endpoint 가 오래 간다.

terraform output -json | jq -r '.app_endpoint.value'

다음 실습에서 할 것

/root/tf/vars 에서 같은 변수에 기본값·tfvars·명령줄·환경변수 네 가지 방법으로 값을 넣어 무엇이 이기는지 파일로 확인한다. validation 으로 잘못된 값을 거부시키고 그 메시지를 저장한다. locals 로 이름 접두어를 조합하고, 설명이 붙은 출력을 네 개 이상 만든다. 마지막에는 민감 변수를 선언해 사람이 보는 출력에서는 가려지지만 JSON 에는 값이 남는다는 사실을 눈으로 확인한다.