二つのリポジトリが同じ設定ファイルを互いに戻し合っていた
한국어 원문으로 표시합니다.
목표
두 상태가 한 자원을 두고 싸우는 모습을 재현해 removed 블록으로 소유를 정리하고, 이미 있는 값을 import 블록·설정 생성·CLI import 로 지우지 않고 데려와 계획이 빈 상태로 관리를 시작합니다.
왜 중요한가
IaC 를 도입할 때는 백지에서 시작하지 않습니다. 손으로 만든 자원이 이미 있고, 그것을 코드로 옮기는 동안 모양만 따라 적으면 도구는 새로 만들어 기존 값을 쓰던 곳을 깨뜨립니다. 그래서 import 로 상태에 먼저 등록하고, 계획이 빌 때까지 코드를 다듬어야 끝난 것입니다. 반대로 한 자원을 두 코드베이스가 관리하면 오류 하나 없이 서로를 되돌리므로, 소유를 넘길 때는 리소스 블록을 지우는(=삭제) 대신 상태에서만 빼는 방법을 써야 합니다.
단계
/root/iac-own/team-a/main.tf와/root/iac-own/team-b/main.tf에 각각local_file.conf를 두세요. 둘 다/root/iac-own/shared/app.conf를 관리하고, 내용은owner=team-a,owner=team-b한 줄입니다. 각 디렉터리를 init 한 뒤 team-a, team-b, team-a, team-b 순서로 apply 하고, apply 할 때마다 파일 내용을/root/iac-own/flip.txt에 한 줄씩 덧붙이세요.- team-b 가 소유를 넘기기로 했습니다. team-b 의 리소스 블록을 지우고
removed { from = local_file.conf }블록을 두세요. team-b 에서tofu plan출력을/root/iac-own/forget.txt에 저장한 뒤 apply 하고, 이어서 team-a 를 apply 하세요. 파일은 남아 있어야 하고(team-a 의 내용), team-b 상태에는 local_file 이 없어야 하며, 두 디렉터리의 계획은 모두 깨끗해야 합니다. /root/iac-own/legacy.env에 손으로 만든 자원의 기록을 적으세요:DB_PASSWORD=Lb7qK2mZx9Wc4Rt8Yp3N,PORT=8123,PORT_RANGE=8000,8999,INSTANCE_ID=6f1c2a4e-3b7d-4c9a-8e2f-1a2b3c4d5e6f,TOKEN=abcDEF123(한 줄에 하나). 그다음/root/iac-own/naive/main.tf에 import 없이random_password.db(길이 20),random_integer.port(8000 부터 8999),random_uuid.instance를 적고tofu plan -out=/root/iac-own/naive/naive.tfplan으로 저장만 하세요(적용하지 않음)./root/iac-own/adopt/main.tf에 random 프로바이더 요구와 import 블록 셋만 두세요: random_password.db 는 legacy.env 의 비밀번호, random_integer.port 는<값>,<최소>,<최대>형식, random_uuid.instance 는 UUID 가 식별자입니다. 리소스 블록은 쓰지 않고tofu plan -generate-config-out=generated.tf로 설정을 뽑으세요. 계획이3 to import, 0 to add, 0 to change, 0 to destroy여야 합니다(적용하지 않음).generated.tf의 세 리소스를adopt/main.tf로 옮기되 값이null인 줄은 빼고, 기본값과 같은 인자도 빼서 짧게 다듬으세요(비밀번호 길이 20, 포트 범위 8000·8999 는 남깁니다). generated.tf 는 지웁니다. 계획이3 to import, 0 to add, 0 to change, 0 to destroy인지 확인한 뒤 apply 하세요. 채점기는 상태의 값이 legacy.env 와 같고 그 뒤 계획이 깨끗한지 봅니다.- legacy.env 의 TOKEN 을
random_string.token으로 가져오세요. 이번에는 import 블록을 쓰지 말고/root/iac-own/adopt/token.tf에 리소스 블록(길이 9)을 먼저 둔 뒤tofu import명령으로 상태에 등록합니다. 그 뒤 계획이 깨끗해야 합니다. /root/iac-own/owners.sh <작업디렉터리>...를 만드세요. 각 디렉터리의 상태에서 local_file·local_sensitive_file 이 관리하는 파일 경로를 절대 경로로 모아, 같은 경로가 둘 이상의 디렉터리에 있으면CONFLICT <경로> <디렉터리들(절대 경로, 정렬, 공백 구분)>을 경로마다 한 줄씩 출력하고 3, 없으면OK와 0 으로 끝냅니다. 상대 경로 filename 은 그 작업 디렉터리 기준으로 풉니다. 채점기는 team-a·team-b(이제 OK)와 스스로 만든 충돌 디렉터리로 확인합니다.
참고
- 파드에는 OpenTofu 1.9.0 과 local·random 프로바이더 미러가 있어 인터넷 없이 돕니다.
- import 식별자 형식은 리소스마다 다릅니다: random_password 는 비밀번호 값, random_integer 는
<값>,<최소>,<최대>, random_uuid 는 UUID, random_string 은 문자열 값(프로바이더 문서의 Import 절). - 이 파드의 1.9.0 에서 removed 블록은 from 하나만 받고 늘 '상태에서만 뺀다' 로 동작합니다. 최신 OpenTofu 문서는 같은 뜻을
lifecycle { destroy = false }로 명시하라고 권하는데, 1.9.0 은 그 블록을 거절합니다(실측). - 흔한 실수: 소유를 넘기면서 리소스 블록만 지워 실물 삭제가 계획되는 것. 흔한 실수: 설정 생성 뒤 generated.tf 와 main.tf 에 같은 리소스를 두 번 두는 것.
- Import · Generating Configuration · tofu import · Removing Resources · random_id (Import 형식 예)
두 팀이 같은 파일을 번갈아 되돌린다
/root/iac-own/team-a/main.tf 와 /root/iac-own/team-b/main.tf 에 각각 local_file.conf 를 두세요. 둘 다 /root/iac-own/shared/app.conf 를 관리하고, 내용은 owner=team-a, owner=team-b 한 줄입니다. 각 디렉터리를 init 한 뒤 team-a, team-b, team-a, team-b 순서로 apply 하고, apply 할 때마다 파일 내용을 /root/iac-own/flip.txt 에 한 줄씩 덧붙이세요.
두 상태는 서로의 존재를 모릅니다. 한쪽이 쓰면 다른 쪽의 다음 계획에는 그 파일이 '밖에서 바뀐 것'(local_file 에서는 '삭제됨')으로 보이고, 적용하면 자기 내용으로 되돌립니다. 마지막을 team-b 로 끝내는 이유는 다음 단계에서 team-b 가 자기 기록이 살아 있는 상태에서 소유를 내려놓게 하기 위해서입니다. 이런 싸움은 오류 없이 조용히 계속됩니다.
한쪽이 소유를 내려놓는다 — 지우지 않고 상태에서만 뺀다
team-b 가 소유를 넘기기로 했습니다. team-b 의 리소스 블록을 지우고 removed { from = local_file.conf } 블록을 두세요. team-b 에서 tofu plan 출력을 /root/iac-own/forget.txt 에 저장한 뒤 apply 하고, 이어서 team-a 를 apply 하세요. 파일은 남아 있어야 하고(team-a 의 내용), team-b 상태에는 local_file 이 없어야 하며, 두 디렉터리의 계획은 모두 깨끗해야 합니다.
리소스 블록만 지우면 도구는 그것을 삭제로 계획합니다 — 그러면 team-a 가 쓰는 파일을 team-b 가 지웁니다. removed 블록은 '관리만 그만둔다' 는 뜻이라 실물은 그대로 둡니다. 계획 출력의 문장을 읽어 차이를 확인하세요.
이미 있는 값을 모양만 따라 적으면 새로 만든다
/root/iac-own/legacy.env 에 손으로 만든 자원의 기록을 적으세요: DB_PASSWORD=Lb7qK2mZx9Wc4Rt8Yp3N, PORT=8123, PORT_RANGE=8000,8999, INSTANCE_ID=6f1c2a4e-3b7d-4c9a-8e2f-1a2b3c4d5e6f, TOKEN=abcDEF123(한 줄에 하나). 그다음 /root/iac-own/naive/main.tf 에 import 없이 random_password.db(길이 20), random_integer.port(8000 부터 8999), random_uuid.instance 를 적고 tofu plan -out=/root/iac-own/naive/naive.tfplan 으로 저장만 하세요(적용하지 않음).
설정의 모양이 같아도 상태에 기록이 없으면 도구에게는 '아직 없는 것' 입니다. 적용하면 기존 비밀번호와 다른 새 값이 생기고, 그 값을 쓰던 곳은 모두 깨집니다. 계획의 Plan 줄에서 import 가 0 인 것을 보세요.
import 블록을 쓰고 설정은 도구에게 뽑게 한다
/root/iac-own/adopt/main.tf 에 random 프로바이더 요구와 import 블록 셋만 두세요: random_password.db 는 legacy.env 의 비밀번호, random_integer.port 는 <값>,<최소>,<최대> 형식, random_uuid.instance 는 UUID 가 식별자입니다. 리소스 블록은 쓰지 않고 tofu plan -generate-config-out=generated.tf 로 설정을 뽑으세요. 계획이 3 to import, 0 to add, 0 to change, 0 to destroy 여야 합니다(적용하지 않음).
리소스마다 import 식별자의 형식이 다릅니다 — 프로바이더 문서의 Import 절을 보세요. 설정 생성은 실험 기능이고, 파일이 이미 있으면 덮어쓰지 않고 오류를 냅니다.
뽑은 설정을 다듬어 계획이 빈 채로 가져온다
generated.tf 의 세 리소스를 adopt/main.tf 로 옮기되 값이 null 인 줄은 빼고, 기본값과 같은 인자도 빼서 짧게 다듬으세요(비밀번호 길이 20, 포트 범위 8000·8999 는 남깁니다). generated.tf 는 지웁니다. 계획이 3 to import, 0 to add, 0 to change, 0 to destroy 인지 확인한 뒤 apply 하세요. 채점기는 상태의 값이 legacy.env 와 같고 그 뒤 계획이 깨끗한지 봅니다.
다듬다가 교체를 강제하는 인자(예: 범위의 최대값)를 바꾸면 계획이 '가져온 뒤 교체' 로 바뀝니다 — 그대로 적용하면 가져온 값이 곧바로 사라집니다. Plan 줄의 add·destroy 가 0 인지가 판단 기준입니다.
블록 대신 명령으로 하나 더 가져온다
legacy.env 의 TOKEN 을 random_string.token 으로 가져오세요. 이번에는 import 블록을 쓰지 말고 /root/iac-own/adopt/token.tf 에 리소스 블록(길이 9)을 먼저 둔 뒤 tofu import 명령으로 상태에 등록합니다. 그 뒤 계획이 깨끗해야 합니다.
명령형 import 는 계획 없이 곧바로 상태를 바꿉니다. 그래서 먼저 설정이 있어야 하고, 리뷰할 계획이 남지 않습니다 — import 블록이 코드 리뷰와 계획을 거치게 만든 이유입니다.
소유자가 둘인 파일을 찾아내는 점검
/root/iac-own/owners.sh <작업디렉터리>... 를 만드세요. 각 디렉터리의 상태에서 local_file·local_sensitive_file 이 관리하는 파일 경로를 절대 경로로 모아, 같은 경로가 둘 이상의 디렉터리에 있으면 CONFLICT <경로> <디렉터리들(절대 경로, 정렬, 공백 구분)> 을 경로마다 한 줄씩 출력하고 3, 없으면 OK 와 0 으로 끝냅니다. 상대 경로 filename 은 그 작업 디렉터리 기준으로 풉니다. 채점기는 team-a·team-b(이제 OK)와 스스로 만든 충돌 디렉터리로 확인합니다.
tofu show -json 의 values 에서 filename 을 얻습니다. 같은 이름의 상대 경로라도 디렉터리가 다르면 다른 파일입니다. 중복만 고르는 데는 sort | uniq -d 가 편합니다.