给 dev 的改动也出现在了 prod
한국어 원문으로 표시합니다.
목표
진짜 k3s 와 Argo CD 에서 ApplicationSet 하나로 dev·prod 두 환경을 Git 에서 배포합니다. 공통 base 한 줄이 두 환경을 함께 바꾸는 사고를 겪고, prod 를 승격 브랜치에 묶어 환경 사이 승격을 Git 조작으로 바꾼 뒤, 드리프트 복구·승격·되돌리기가 각각 저장소와 클러스터에 어떻게 남는지 확인합니다.
왜 중요한가
GitOps 에서는 Git 이 각 환경의 원하는 상태이고, 클러스터 안의 조정기가 그 상태를 당겨 와 맞춥니다. 그래서 "무엇이 prod 에 있는가" 는 prod 가 추적하는 Git 참조가 가리키는 커밋으로 답해야 하고, 승격은 그 참조를 옮기는 일이 됩니다. 모든 환경이 같은 브랜치를 보면 승격 단계가 사라져 dev 의 실험이 곧바로 prod 가 됩니다. 반대로 환경마다 참조를 나누면 변경이 어디까지 갔는지가 저장소 이력으로 증명되고, 누군가 클러스터를 손으로 고쳐도 조정기가 Git 으로 되돌립니다. 플랫폼 팀이 여러 팀에 전달 경로를 제공할 때 이 구조가 기본 골격입니다.
단계
- 베어 저장소
/srv/bare/shop-envs.git을 만들고/root/cnpa-env/repo로 복제하세요.base/configmap.yaml에 ConfigMapshop-config(namespace 없음, dataFEATURE_CHECKOUT_V2: "off",LOG_LEVEL: "info")와 그것을 담는base/kustomization.yaml을,envs/dev/kustomization.yaml과envs/prod/kustomization.yaml에는../../base를 불러와LOG_LEVEL을 각각debug·warn으로 바꾸는 패치를 두세요. 커밋해main으로 push 합니다. /root/cnpa-env/appset.yaml에 ApplicationSetshop(argocd 네임스페이스)을 작성해 적용하세요. list 생성기의 원소는{env: dev, revision: main}과{env: prod, revision: main}, 템플릿은 이름shop-{{env}}, project default, repoURLgit://gitd.gitsrv.svc.cluster.local:9418/shop-envs.git, targetRevision{{revision}}, pathenvs/{{env}}, 대상 네임스페이스shop-{{env}}, 자동 동기화 prune·selfHeal,CreateNamespace=true입니다. 두 Application 이 Synced 가 될 때까지 기다리세요.- dev 에서 새 결제 화면을 켜려고
base/configmap.yaml의FEATURE_CHECKOUT_V2를"on"으로 바꿔 커밋·push 하세요. 두 앱이 그 커밋으로 동기화될 때까지 기다린 뒤/root/cnpa-env/incident.json에commit(그 커밋 SHA),dev_value,prod_value(각 환경 ConfigMap 의 FEATURE_CHECKOUT_V2 실제 값)를 적으세요. - 사고 직전 커밋(3단계 커밋의 부모)에 브랜치
release/prod를 만들어 push 하고, ApplicationSet 의 prod 원소revision을release/prod로 바꾸세요(dev 는main그대로). shop-prod 가 그 커밋으로 동기화되고 FEATURE_CHECKOUT_V2 가 다시off가 된 것을 확인한 뒤/root/cnpa-env/pin.json에release_prod_initial(브랜치를 만든 커밋 SHA)과prod_value_after_pin을 적으세요. shop-prod네임스페이스의 ConfigMapshop-config에서LOG_LEVEL을kubectl patch로debug로 바꾸고, Argo CD 가 Git 의 값으로 되돌릴 때까지 걸린 초를 재세요./root/cnpa-env/drift.json에uid(그 ConfigMap 의 uid),edited(debug),restored(되돌아온 값),seconds(정수)를 적습니다.- dev 에서 새 결제 화면을 확인했다고 보고 prod 로 승격하세요.
release/prod를 3단계 커밋으로 fast-forward push 하고(강제 push 금지), shop-prod 가 그 커밋으로 동기화되어 FEATURE_CHECKOUT_V2 가on이 될 때까지 기다리세요./root/cnpa-env/promote.json에from(옮기기 전 release/prod SHA),to(옮긴 뒤 SHA)를 적습니다. envs/dev/kustomization.yaml의 resources 에 없는 파일missing.yaml을 더해 커밋·push 하세요. shop-dev 에 비교 오류가 나타나면 그 메시지를, 같은 순간 shop-prod 의status.sync.revision을 읽어 두고,git revert로 되돌려 push 한 뒤 shop-dev 가 다시 Synced 가 되기를 기다리세요./root/cnpa-env/revert.json에bad_commit,revert_commit,dev_error(오류 메시지 일부),prod_revision_during을 적습니다./root/cnpa-env/report.json에dev_tracks,prod_tracks(각 앱의 targetRevision),promoted_commit(6단계 to),drift_seconds(5단계),bad_commit_reached_prod(7단계 bad_commit 이 지금 release/prod 이력에 있는지, 불리언),rollback(revert또는reset중 7단계에서 쓴 방식)을 적으세요.
참고
- VM 안에 k3s, Argo CD v3.5.2, git 데몬(
gitd.gitsrv)이 있습니다./srv/bare/<이름>.git이git://gitd.gitsrv.svc.cluster.local:9418/<이름>.git으로 보입니다. - 즉시 다시 읽기:
kubectl -n argocd annotate app <앱> argocd.argoproj.io/refresh=hard --overwrite. - 렌더 확인:
kubectl kustomize envs/<환경>. - 흔한 실수: ApplicationSet 이 만든 Application 을 직접 고치는 것. 컨트롤러가 템플릿대로 되돌립니다.
- 흔한 실수: 승격을
git push --force로 하는 것. 이력이 바뀌면 무엇이 언제 prod 에 갔는지 증명할 수 없습니다. - ApplicationSet List Generator · Automated Sync Policy(selfHeal) · Kustomize · OpenGitOps 원칙 · CNCF Platforms White Paper
환경 두 개를 한 저장소에
베어 저장소 /srv/bare/shop-envs.git 을 만들고 /root/cnpa-env/repo 로 복제하세요. base/configmap.yaml 에 ConfigMap shop-config(namespace 없음, data FEATURE_CHECKOUT_V2: "off", LOG_LEVEL: "info")와 그것을 담는 base/kustomization.yaml 을, envs/dev/kustomization.yaml 과 envs/prod/kustomization.yaml 에는 ../../base 를 불러와 LOG_LEVEL 을 각각 debug·warn 으로 바꾸는 패치를 두세요. 커밋해 main 으로 push 합니다.
환경별 차이는 overlay 에 차이만 적습니다. kustomization 의 patches 에 ConfigMap 이름을 대상으로 한 작은 패치를 넣으면 됩니다. push 하기 전에 kubectl kustomize envs/prod 로 렌더 결과를 확인하세요. 베어 저장소는 git 데몬 파드가 읽으므로 chmod -R a+rX 가 필요합니다.
ApplicationSet 하나가 환경 둘을 만든다
/root/cnpa-env/appset.yaml 에 ApplicationSet shop(argocd 네임스페이스)을 작성해 적용하세요. list 생성기의 원소는 {env: dev, revision: main} 과 {env: prod, revision: main}, 템플릿은 이름 shop-{{env}}, project default, repoURL git://gitd.gitsrv.svc.cluster.local:9418/shop-envs.git, targetRevision {{revision}}, path envs/{{env}}, 대상 네임스페이스 shop-{{env}}, 자동 동기화 prune·selfHeal, CreateNamespace=true 입니다. 두 Application 이 Synced 가 될 때까지 기다리세요.
ApplicationSet 컨트롤러는 원소마다 템플릿의 {{...}} 를 채워 Application 을 만들고 소유자 참조로 묶습니다. 환경별로 달라야 하는 값은 원소에 키로 둡니다. Argo CD 는 기본 주기마다 Git 을 읽으므로 기다리기 싫으면 Application 에 argocd.argoproj.io/refresh=hard 어노테이션을 붙입니다.
dev 에 넣은 변경이 prod 에도 나타났다
dev 에서 새 결제 화면을 켜려고 base/configmap.yaml 의 FEATURE_CHECKOUT_V2 를 "on" 으로 바꿔 커밋·push 하세요. 두 앱이 그 커밋으로 동기화될 때까지 기다린 뒤 /root/cnpa-env/incident.json 에 commit(그 커밋 SHA), dev_value, prod_value(각 환경 ConfigMap 의 FEATURE_CHECKOUT_V2 실제 값)를 적으세요.
두 환경이 같은 브랜치를 추적하고 같은 base 를 불러오면, base 한 줄이 곧 두 환경의 변경입니다. 환경 사이에 승격 단계가 없다는 뜻입니다. 값은 kubectl -n shop-prod get cm shop-config -o jsonpath=... 로 읽습니다.
prod 를 승격 브랜치에 묶는다
사고 직전 커밋(3단계 커밋의 부모)에 브랜치 release/prod 를 만들어 push 하고, ApplicationSet 의 prod 원소 revision 을 release/prod 로 바꾸세요(dev 는 main 그대로). shop-prod 가 그 커밋으로 동기화되고 FEATURE_CHECKOUT_V2 가 다시 off 가 된 것을 확인한 뒤 /root/cnpa-env/pin.json 에 release_prod_initial(브랜치를 만든 커밋 SHA)과 prod_value_after_pin 을 적으세요.
prod 가 main 이 아니라 따로 움직이는 참조를 추적하면, main 의 변경은 누군가 그 참조를 옮기기 전까지 prod 에 닿지 않습니다. 브랜치는 git push origin <SHA>:refs/heads/release/prod 로 원격에 바로 만들 수 있습니다. Application 을 직접 고치면 ApplicationSet 이 템플릿대로 되돌리므로 생성기의 원소를 고쳐야 합니다.
prod 를 손으로 고치자 몇 초 뒤 되돌아갔다
shop-prod 네임스페이스의 ConfigMap shop-config 에서 LOG_LEVEL 을 kubectl patch 로 debug 로 바꾸고, Argo CD 가 Git 의 값으로 되돌릴 때까지 걸린 초를 재세요. /root/cnpa-env/drift.json 에 uid(그 ConfigMap 의 uid), edited(debug), restored(되돌아온 값), seconds(정수)를 적습니다.
selfHeal 이 켜진 앱은 관리하는 오브젝트가 바뀌면 곧바로 다시 비교하고, Git 과 다르면 Git 쪽으로 맞춥니다. 오브젝트를 지우고 다시 만드는 것이 아니라 고치는 것이라 uid 는 그대로입니다. 0.5..1초 간격으로 값을 읽으며 기다리세요.
승격은 브랜치를 옮기는 커밋 한 번
dev 에서 새 결제 화면을 확인했다고 보고 prod 로 승격하세요. release/prod 를 3단계 커밋으로 fast-forward push 하고(강제 push 금지), shop-prod 가 그 커밋으로 동기화되어 FEATURE_CHECKOUT_V2 가 on 이 될 때까지 기다리세요. /root/cnpa-env/promote.json 에 from(옮기기 전 release/prod SHA), to(옮긴 뒤 SHA)를 적습니다.
승격이 Git 조작이면 누가 언제 무엇을 prod 에 올렸는지가 저장소 이력에 그대로 남고, 되돌리기도 같은 방식으로 합니다. fast-forward 는 옮기기 전 커밋이 옮긴 뒤 커밋의 조상일 때만 됩니다.
dev 를 깨뜨린 커밋은 prod 에 닿지 않았다
envs/dev/kustomization.yaml 의 resources 에 없는 파일 missing.yaml 을 더해 커밋·push 하세요. shop-dev 에 비교 오류가 나타나면 그 메시지를, 같은 순간 shop-prod 의 status.sync.revision 을 읽어 두고, git revert 로 되돌려 push 한 뒤 shop-dev 가 다시 Synced 가 되기를 기다리세요. /root/cnpa-env/revert.json 에 bad_commit, revert_commit, dev_error(오류 메시지 일부), prod_revision_during 을 적습니다.
렌더할 수 없는 커밋은 Argo CD 가 적용하지 않고 앱 조건(status.conditions)에 ComparisonError 로 남깁니다. prod 는 다른 참조를 추적하므로 이 커밋을 볼 일이 없습니다. 되돌리기는 이력을 지우는 reset 이 아니라 반대 변경을 새 커밋으로 쌓는 revert 입니다.
환경과 승격을 값으로 남긴다
/root/cnpa-env/report.json 에 dev_tracks, prod_tracks(각 앱의 targetRevision), promoted_commit(6단계 to), drift_seconds(5단계), bad_commit_reached_prod(7단계 bad_commit 이 지금 release/prod 이력에 있는지, 불리언), rollback(revert 또는 reset 중 7단계에서 쓴 방식)을 적으세요.
앞 단계 JSON 과 git merge-base --is-ancestor 로 계산합니다. 채점기는 같은 파일과 저장소·Application 을 다시 대조합니다.