세 작업 디렉터리에 겹친 리소스 소유권
세 state에 중복으로 올라간 리소스와 이름으로만 이어진 연결
20편에서 한
main.tf가 세 작업 디렉터리로 나뉘었다. 서버, 선점 복구, 백업 정리가 각자 state를
갖고 별도의 apply로 배포됐다. 한 서비스를 바꿀 때 다른 두 서비스의 plan을 함께
읽지 않아도 됐다.
보관된 세 state에는 분리되지 않은 대상도 남았다. 어떤 리소스는 두 state에 함께 들어 있고, 어떤 연결은 사람이 옮겨 적은 문자열로만 이어져 있었다.
세 state에서 관리 대상의 타입과 이름만 읽어 리소스별 관리 주체를 복원했다. 속성값과 실제 식별자는 읽지 않았고, 아래 이름도 자리 표시자다. state는 저장된 시점의 스냅숏이므로 지금 그 리소스가 존재한다는 뜻은 아니다.
세 state가 관리한 것
서버 8개 고정 IP, 방화벽 5, 인스턴스, 백업 버킷
선점 복구 15개 함수, 로그 메트릭, 알림 정책·채널, IAM 3, API 5, topic, 버킷, 객체
백업 정리 15개 Scheduler, 함수, IAM 2, API 8, topic, 버킷, 객체
서버 state에는 API 리소스가 하나도 없다. 서버를 만들 때 필요한 Compute API는 이미 켜져 있었고, 코드로 관리 대상에 넣지 않았다. 나머지 두 디렉터리는 각자 필요한 API를 자기 state에 넣었다.
같은 API를 두 state가 관리했다
선점 복구와 백업 정리가 동시에 관리한 google_project_service는 다섯 개다.
cloudbuild cloudfunctions logging monitoring pubsub
두 디렉터리 모두 Cloud Functions로 배포하고, 빌드를 거치고, Pub/Sub으로 트리거를 받고, 로그와 모니터링을 쓴다. 각자 필요한 API를 자기 코드에 적은 결과가 이 중복이다. 코드만 보면 각 디렉터리가 필요한 API를 스스로 갖춘 구성이다.
API 활성화 상태는 프로젝트당 하나다. 두 state는 같은 API 하나를 각각 자기 관리 대상으로 기록했다.
flowchart TB R["선점 복구 state"] --> API["프로젝트 API 5개<br/>cloudbuild·cloudfunctions<br/>logging·monitoring·pubsub"] C["백업 정리 state"] --> API API --> REAL["프로젝트에 실제로 켜진 스위치 하나"]
한쪽 디렉터리에서 destroy를 실행하면 그 state는 자기가 관리하는 API를 끄려 한다.
다른 state가 그 API를 쓰고 있어도 Terraform은 확인하지 않는다. 반대로 한쪽이 API를
다시 켜면 다른 쪽의 다음 plan에서는 아무 변화도 보이지 않는다. 실제 상태가 이미
원하는 값이기 때문이다.
이 구조에서는 두 디렉터리를 정리하는 순서가 결과를 바꾼다. 어느 쪽을 먼저
destroy해야 하는지는 저장소 어디에도 적혀 있지 않다.
이름으로만 이어진 연결
선점 복구 함수는 서버 VM을 다시 시작한다. 백업 정리 함수는 서버가 만든 백업 버킷을 비운다. 두 디렉터리는 서버 리소스의 이름을 변수 기본값으로 받았다.
variable "instance_name" {
default = "<INSTANCE_NAME>"
}
서버 디렉터리에도 같은 이름이 있다. 두 곳에 같은 문자열을 적어 두고 사람이 맞춰 놓는 방식이다. 백업 버킷 이름도 마찬가지로 정리 디렉터리의 변수에 다시 적혀 있다.
세 루트 어디에도 terraform_remote_state나 module 참조가 없다. 한 루트의 output이
다른 루트에서 소비되는 자리도 없다. 서버 이름을 바꾸는 순간 나머지 두 디렉터리는
존재하지 않는 대상을 가리키게 되고, 그 사실은 함수가 실행될 때가 되어야 드러난다.
기본값이 갈라진 흔적도 남아 있다. 서버 디렉터리와 백업 정리 디렉터리의 기본 리전이 서로 다르다. 백업 정리 함수가 정말 다른 리전에 있었는지, 기본값을 고치지 않은 채 다른 경로로 배포했는지는 코드만으로 알 수 없다. 확인할 수 있는 것은 같은 프로젝트를 다루는 세 디렉터리가 각자의 기본값을 들고 있었고, 그 값들이 같은지 보는 장치는 없었다는 사실이다.
보존 정책을 두 곳이 정했다
백업 보존 설정도 두 곳에 겹쳤다. 백업 버킷은 서버 state가 소유하고, 버킷 자체에 수명 주기 규칙이 붙어 있다.
lifecycle_rule {
action { type = "Delete" }
condition { age = 1 }
}
백업 정리 디렉터리는 Scheduler와 함수로 같은 버킷의 오래된 객체를 하루 한 번 지운다. 버킷을 소유하지 않은 채 그 안의 내용을 지우는 구조다.
flowchart LR S["서버 state"] -->|소유| B["백업 버킷"] S -->|lifecycle 1일| B C["백업 정리 state"] -->|Scheduler + 함수| B
두 설정 모두 하루였기 때문에 실제 보존 기간은 같았다. 다만 한쪽만 늘리면 짧은 쪽이 그대로 적용되고, 백업이 예상보다 빨리 사라졌을 때 볼 곳도 두 군데로 갈린다.
리소스 주소가 겹치는 곳도 있다. google_storage_bucket_object.function_zip이
선점 복구와 백업 정리 state 양쪽에 있다. 서로 다른 버킷의 서로 다른 객체라 충돌은
아니지만, 로그나 계획에서 주소만 보면 어느 디렉터리의 것인지 구분되지 않는다.
배포 단위와 리소스 소유권
디렉터리를 나눠 서버와 복구 함수의 plan을 따로 읽을 수 있게 됐다. 각 리소스를 어느 state가 관리하고 다른 state에 값을 어떻게 전달할지는 정하지 않았다. 그래서 API는 두 곳이 함께 관리하고, 이름은 사람이 옮겨 적고, 보존 정책은 두 곳에서 갈라졌다.
다시 나눈다면 다음 세 기준을 적용한다.
- 리소스마다 관리하는 state를 하나만 둔다. 프로젝트 API처럼 프로젝트당 하나인 대상은 공통 준비 루트 한 곳에서 관리한다.
- 인스턴스와 버킷 이름은 소유 state의 output으로 내보내고, 소비하는 구성에는
명시적인 입력으로 전달한다. 이름이 바뀌면 다음
plan에서 확인할 수 있어야 한다. - 보존 기간은 버킷 수명 주기나 정리 함수 중 한 곳에서만 정한다.
다시 나눈다면 프로젝트 공통 준비, Minecraft 서버, 선점 복구를 세 배포 단위로 둔다. 하루 단위 보존이면 백업 정리 함수는 없애고 버킷 lifecycle 한 곳에서 처리한다.
bootstrap/
프로젝트 API
원격 state 버킷
공통 서비스 계정
server/
VPC와 서브넷
방화벽과 고정 IP
VM
백업 버킷과 lifecycle rule
spot-recovery/
Logging sink
Pub/Sub
복구 함수
이 배치에서는 프로젝트 API 변경이 bootstrap의 plan에만 나타난다. 다른 서비스를
모두 제거한 뒤 bootstrap을 마지막에 정리해야 API를 먼저 끄는 일을 피할 수 있다.
server가 내보낸 프로젝트, zone과 인스턴스 이름은 spot-recovery의 명시적 입력으로
전달한다. 보존 기간을 정하는 곳은 버킷 lifecycle 하나뿐이다.
GCS backend는 이미 존재하는 버킷을
사용한다. bootstrap은
처음에 local state로 state 버킷을 만든 뒤
terraform init -migrate-state로 옮긴다. server와 spot-recovery는 같은 버킷에서
서로 다른 prefix를 쓴다.
구축 트랙의 공개 예제도 이 기준을 일부 적용한다. 04편의 서버 루트가 백업 버킷과 수명 주기를 함께 소유하고, 보존 기간을 정하는 자리는 그 한 곳뿐이다. 09편의 복구 루트는 서버 리소스를 만들지 않고 대상 인스턴스 이름만 입력으로 받는다.
공개 예제에서도 인스턴스 이름은 운영자가 두 곳에 맞춰 넣고, Compute와 Storage API는
두 루트에 겹쳐 있다. 두 루트의 google_project_service는
disable_on_destroy = false라서 한 루트를 삭제해도 공유 API를 끄지 않는다. 이 설정은
API 중단을 막지만 두 state에 같은 프로젝트 서비스가 기록되는 중복까지 없애지는
않는다.
삭제 절차가 복구 루트를 먼저 제거하는 이유는 늦게 도착한 선점 이벤트가 삭제 중인 서버를 다시 시작하지 못하게 하기 위해서다. API 소유권 중복을 해결하는 순서로 해석하면 안 된다.
디렉터리를 나누기 전에 각 리소스를 관리할 state와 다른 루트에 값을 전달할 방법을 함께 정했어야 했다. 당시 구성에는 그 결정이 빠져 있어 API와 보존 정책이 겹쳤다.
참고
Comments
아직 댓글이 없습니다. 첫 댓글을 남겨주세요.
검토 대기 중