CGOA — GitOps Certified Associate
We deployed tag v1, and v2 showed up
한국어 원문으로 표시합니다.
목표
브랜치·태그·커밋 SHA 를 각각 추적하는 Argo CD Application 을 세워, 어떤 이름이 움직이고 어떤 이름이 움직이지 않는지 실제 동기화 기록으로 확인합니다. 그리고 GitOps 에서 되돌리기가 왜 새 커밋이어야 하는지 봅니다.
왜 중요한가
OpenGitOps 두 번째 원칙은 원하는 상태가 "버전이 매겨지고 바뀌지 않게(versioned and immutable)" 저장되어야 한다고 말합니다.
Git 커밋은 내용의 해시라 바뀌지 않지만, 브랜치와 태그는 커밋을 가리키는 이름표일 뿐이라 누구든 옮길 수 있습니다.
targetRevision: v1 은 "v1 이라는 이름이 지금 가리키는 것" 이지 "처음에 검토한 그 커밋" 이 아닙니다.
이 차이를 모르면 검토를 통과한 릴리스 이름 아래로 검토하지 않은 내용이 배포되고, 감사 기록에는 여전히 v1 이라고 남습니다.
되돌리기도 같은 원칙에서 나옵니다. 클러스터만 옛 상태로 돌리면 Git 과 클러스터가 갈라지고,
Git 의 이력을 지우면 무엇이 언제 배포됐는지의 증거가 사라집니다.
단계
- 베어 저장소
/srv/bare/rev.git을 만들고/root/cgoa-rev/repo로 복제하세요.app/release.yaml에 ConfigMaprelease(namespace 는 쓰지 않음, dataversion: v1)를 커밋해main으로 push 하고, 그 커밋에 태그v1을 달아 태그도 push 합니다. 그 커밋의 40자리 SHA 를/root/cgoa-rev/v1.sha에 한 줄로 적으세요. /root/cgoa-rev/apps.yaml에 Application 세 개를 작성해 적용하세요. 이름·대상 네임스페이스는rev-branch·rev-tag·rev-sha이고, source 는git://gitd.gitsrv.svc.cluster.local:9418/rev.git의app경로, targetRevision 은 차례로main,v1,v1.sha에 적은 40자리 SHA 입니다. 셋 다 projectdefault, 자동 동기화(prune·selfHeal)와CreateNamespace=true를 켜고, 세 앱이 모두 Synced 가 되는 것을 확인하세요./root/cgoa-rev/repo에서app/release.yaml의 version 을v2로 바꿔 커밋하고main으로 push 하세요(태그는 건드리지 않습니다). 세 앱을 hard refresh 한 뒤, 각 앱의status.sync.revision을/root/cgoa-rev/after-commit.json에{"branch": ..., "tag": ..., "sha": ...}형태로 적으세요. branch 는 새 커밋으로 가고 tag·sha 는 v1 커밋에 남아야 합니다.- 태그
v1을 main 의 최신 커밋(v2)으로 강제로 옮겨git push -f하고rev-tag를 hard refresh 하세요.rev-tag의status.history[].revision을 순서대로/root/cgoa-rev/tag-history.json에{"target": "v1", "revisions": [...]}로 적습니다. 같은 이름v1아래에 서로 다른 커밋 두 개가 기록되고,rev-tag네임스페이스의 ConfigMap 이 v2 로 바뀌며,rev-sha는 그대로 v1 이어야 합니다. - 먼저 태그
v1을v1.sha의 원래 커밋으로 되돌려 push 하고rev-tag가 다시 그 커밋으로 동기화되게 하세요. 그다음/srv/bare/rev.git/hooks/pre-receive에 실행 가능한 훅을 두어, 이미 있는refs/tags/*를 바꾸거나 지우는 push 는 거절하고 새 태그 생성과 브랜치 갱신은 허용하게 합니다. 끝으로v1을 다시 옮기는 push 를 시도해 거절된 출력 전체를/root/cgoa-rev/guard.txt에 저장하세요. - main 은 지금 v2 입니다. 이력을 지우지 말고
git revert로 v2 커밋을 되돌리는 새 커밋을 만들어 push 하세요.rev-branch가 그 새 커밋으로 동기화되어 ConfigMap 이 v1 로 돌아와야 합니다./root/cgoa-rev/rollback.json에revert_sha(새 main SHA),deployed(rev-branch 네임스페이스 ConfigMap 의 version 값),equals_v1_sha(새 SHA 가 v1.sha 와 같은지, 불리언)를 적으세요. - 자동 동기화를 켠 그대로
argocd app rollback rev-branch 0 --core를 실행해 거절 메시지를 포함한 출력 전체를/root/cgoa-rev/rollback-refused.txt에 저장하세요. core 모드는 kubeconfig 의 현재 네임스페이스에서 Argo CD 설정을 찾으므로,/root/cgoa-rev/kubeconfig로 k3s kubeconfig 를 복사해 현재 네임스페이스를argocd로 바꾸고KUBECONFIG로 지정해 실행합니다. rollback 을 성공시키려고 자동 동기화를 끄지 마세요. 끝났을 때rev-branch는 여전히 main 의 최신 커밋에 Synced 여야 합니다. /root/cgoa-rev/report.json에mutable_refs(움직인 참조 종류 두 개를branch·tag로, 배열),immutable_ref(commit-sha),pinned_revision(지금rev-sha의 status.sync.revision),tag_guard(pre-receive),rollback(git-revert)을 적으세요. 채점기는 파일 값과 함께 세 앱의 현재 상태를 다시 확인합니다.
참고
- VM 안에 k3s, Argo CD v3.5.2, 클러스터 안 git 데몬(
gitd.gitsrv)이 떠 있습니다./srv/bare아래 베어 저장소는 모두git://gitd.gitsrv.svc.cluster.local:9418/<이름>.git으로 보입니다. - 새 커밋을 기다리지 않으려면
kubectl -n argocd annotate app <이름> argocd.argoproj.io/refresh=hard --overwrite를 씁니다. - 상태 읽기:
kubectl -n argocd get app -o custom-columns=N:.metadata.name,S:.status.sync.status,R:.status.sync.revision - 흔한 실수: 태그를 로컬에서만 옮기고
git push -f origin v1을 빠뜨리는 것. 원격 태그가 그대로라 아무 일도 일어나지 않습니다. - 흔한 실수: 5단계에서 훅을 먼저 두고 태그를 되돌리려는 것. 훅이 그 push 도 거절합니다.
- OpenGitOps 원칙 · Argo CD 추적 전략 · argocd app rollback · githooks
v1 커밋에 태그를 달고 SHA 를 적는다
베어 저장소 /srv/bare/rev.git 을 만들고 /root/cgoa-rev/repo 로 복제하세요. app/release.yaml 에 ConfigMap release(namespace 는 쓰지 않음, data version: v1)를 커밋해 main 으로 push 하고, 그 커밋에 태그 v1 을 달아 태그도 push 합니다. 그 커밋의 40자리 SHA 를 /root/cgoa-rev/v1.sha 에 한 줄로 적으세요.
빈 베어 저장소는 git init --bare 로 만듭니다. git 데몬이 /srv/bare 를 그대로 내보내므로 따로 등록할 것은 없습니다. 태그는 브랜치와 따로 push 해야 원격에 생깁니다. SHA 는 git rev-parse 로 얻습니다.
브랜치·태그·SHA 를 추적하는 앱 셋
/root/cgoa-rev/apps.yaml 에 Application 세 개를 작성해 적용하세요. 이름·대상 네임스페이스는 rev-branch·rev-tag·rev-sha 이고, source 는 git://gitd.gitsrv.svc.cluster.local:9418/rev.git 의 app 경로, targetRevision 은 차례로 main, v1, v1.sha 에 적은 40자리 SHA 입니다. 셋 다 project default, 자동 동기화(prune·selfHeal)와 CreateNamespace=true 를 켜고, 세 앱이 모두 Synced 가 되는 것을 확인하세요.
세 앱은 targetRevision 한 줄만 다릅니다. 지금은 세 이름이 모두 같은 커밋을 가리키므로 status.sync.revision 도 같아야 합니다.
main 에 v2 를 올리면 누가 따라가나
/root/cgoa-rev/repo 에서 app/release.yaml 의 version 을 v2 로 바꿔 커밋하고 main 으로 push 하세요(태그는 건드리지 않습니다). 세 앱을 hard refresh 한 뒤, 각 앱의 status.sync.revision 을 /root/cgoa-rev/after-commit.json 에 {"branch": ..., "tag": ..., "sha": ...} 형태로 적으세요. branch 는 새 커밋으로 가고 tag·sha 는 v1 커밋에 남아야 합니다.
refresh 를 요청하는 방법은 annotation argocd.argoproj.io/refresh=hard 입니다. 파일에 적을 값은 추측하지 말고 Application 상태에서 읽습니다.
태그 v1 을 배포했는데 v2 가 떴다
태그 v1 을 main 의 최신 커밋(v2)으로 강제로 옮겨 git push -f 하고 rev-tag 를 hard refresh 하세요. rev-tag 의 status.history[].revision 을 순서대로 /root/cgoa-rev/tag-history.json 에 {"target": "v1", "revisions": [...]} 로 적습니다. 같은 이름 v1 아래에 서로 다른 커밋 두 개가 기록되고, rev-tag 네임스페이스의 ConfigMap 이 v2 로 바뀌며, rev-sha 는 그대로 v1 이어야 합니다.
태그는 이름일 뿐 커밋이 아닙니다. history 의 각 항목에는 그때의 source.targetRevision 과 실제 revision 이 함께 남습니다.
이미 있는 태그는 못 옮기게 막는다
먼저 태그 v1 을 v1.sha 의 원래 커밋으로 되돌려 push 하고 rev-tag 가 다시 그 커밋으로 동기화되게 하세요. 그다음 /srv/bare/rev.git/hooks/pre-receive 에 실행 가능한 훅을 두어, 이미 있는 refs/tags/* 를 바꾸거나 지우는 push 는 거절하고 새 태그 생성과 브랜치 갱신은 허용하게 합니다. 끝으로 v1 을 다시 옮기는 push 를 시도해 거절된 출력 전체를 /root/cgoa-rev/guard.txt 에 저장하세요.
pre-receive 는 표준 입력으로 옛SHA 새SHA 참조이름 줄을 받습니다. 새로 만드는 참조의 옛 SHA 는 0 이 40개이고, 지우는 참조의 새 SHA 도 그렇습니다. 훅이 0 이 아닌 값으로 끝나면 push 전체가 거절됩니다. 되돌리는 push 는 훅을 두기 전에 합니다.
되돌리기는 새 커밋이다
main 은 지금 v2 입니다. 이력을 지우지 말고 git revert 로 v2 커밋을 되돌리는 새 커밋을 만들어 push 하세요. rev-branch 가 그 새 커밋으로 동기화되어 ConfigMap 이 v1 로 돌아와야 합니다. /root/cgoa-rev/rollback.json 에 revert_sha(새 main SHA), deployed(rev-branch 네임스페이스 ConfigMap 의 version 값), equals_v1_sha(새 SHA 가 v1.sha 와 같은지, 불리언)를 적으세요.
reset 후 강제 push 로도 내용은 v1 이 되지만 v2 커밋이 main 이력에서 사라집니다. 누가 언제 무엇을 되돌렸는지가 남는 쪽을 고르세요.
자동 동기화 앱의 rollback 이 거절되는 이유
자동 동기화를 켠 그대로 argocd app rollback rev-branch 0 --core 를 실행해 거절 메시지를 포함한 출력 전체를 /root/cgoa-rev/rollback-refused.txt 에 저장하세요. core 모드는 kubeconfig 의 현재 네임스페이스에서 Argo CD 설정을 찾으므로, /root/cgoa-rev/kubeconfig 로 k3s kubeconfig 를 복사해 현재 네임스페이스를 argocd 로 바꾸고 KUBECONFIG 로 지정해 실행합니다. rollback 을 성공시키려고 자동 동기화를 끄지 마세요. 끝났을 때 rev-branch 는 여전히 main 의 최신 커밋에 Synced 여야 합니다.
Argo CD 는 자동 동기화가 켜진 앱을 옛 revision 으로 돌리지 않습니다. 돌려도 다음 조정에서 Git 으로 되돌아가기 때문입니다. kubectl config set-context --current --namespace=... 는 지정한 kubeconfig 파일만 바꿉니다.
바뀌는 이름과 바뀌지 않는 이름 보고
/root/cgoa-rev/report.json 에 mutable_refs(움직인 참조 종류 두 개를 branch·tag 로, 배열), immutable_ref(commit-sha), pinned_revision(지금 rev-sha 의 status.sync.revision), tag_guard(pre-receive), rollback(git-revert)을 적으세요. 채점기는 파일 값과 함께 세 앱의 현재 상태를 다시 확인합니다.
앞 단계에서 실제로 움직인 것이 무엇이었는지, 끝까지 움직이지 않은 앱이 무엇이었는지 떠올리세요. pinned_revision 은 상태에서 읽습니다.