CGOA — GitOps 인증 어소시에이트 · 진짜 Argo CD 에서 확인하기 · 실습
태그 v1 을 배포했는데 v2 가 떴다
목표
브랜치·태그·커밋 SHA 를 각각 추적하는 Argo CD Application 을 세워, 어떤 이름이 움직이고 어떤 이름이 움직이지 않는지
실제 동기화 기록으로 확인합니다. 그리고 GitOps 에서 되돌리기가 왜 새 커밋이어야 하는지 봅니다.
왜 중요한가
OpenGitOps 두 번째 원칙은 원하는 상태가 "버전이 매겨지고 바뀌지 않게(versioned and immutable)" 저장되어야 한다고 말합니다.
Git 커밋은 내용의 해시라 바뀌지 않지만, 브랜치와 태그는 커밋을 가리키는 이름표일 뿐이라 누구든 옮길 수 있습니다.targetRevision: v1 은 "v1 이라는 이름이 지금 가리키는 것" 이지 "처음에 검토한 그 커밋" 이 아닙니다.
이 차이를 모르면 검토를 통과한 릴리스 이름 아래로 검토하지 않은 내용이 배포되고, 감사 기록에는 여전히 v1 이라고 남습니다.
되돌리기도 같은 원칙에서 나옵니다. 클러스터만 옛 상태로 돌리면 Git 과 클러스터가 갈라지고,
Git 의 이력을 지우면 무엇이 언제 배포됐는지의 증거가 사라집니다.
단계
1. 베어 저장소 /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 에 한 줄로 적으세요.
2. /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 가 되는 것을 확인하세요.
3. /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 커밋에 남아야 합니다.
4. 태그 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 이어야 합니다.
5. 먼저 태그 v1 을 v1.sha 의 원래 커밋으로 되돌려 push 하고 rev-tag 가 다시 그 커밋으로 동기화되게 하세요. 그다음 /srv/bare/rev.git/hooks/pre-receive 에 실행 가능한 훅을 두어, 이미 있는 refs/tags/* 를 바꾸거나 지우는 push 는 거절하고 새 태그 생성과 브랜치 갱신은 허용하게 합니다. 끝으로 v1 을 다시 옮기는 push 를 시도해 거절된 출력 전체를 /root/cgoa-rev/guard.txt 에 저장하세요.
6. 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 와 같은지, 불리언)를 적으세요.
7. 자동 동기화를 켠 그대로 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 여야 합니다.
8. /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 원칙](https://opengitops.dev/) · [Argo CD 추적 전략](https://argo-cd.readthedocs.io/en/stable/user-guide/tracking_strategies/) · [argocd app rollback](https://argo-cd.readthedocs.io/en/stable/user-guide/commands/argocd_app_rollback/) · [githooks](https://git-scm.com/docs/githooks)
단계 8개
- v1 커밋에 태그를 달고 SHA 를 적는다
- 브랜치·태그·SHA 를 추적하는 앱 셋
- main 에 v2 를 올리면 누가 따라가나
- 태그 v1 을 배포했는데 v2 가 떴다
- 이미 있는 태그는 못 옮기게 막는다
- 되돌리기는 새 커밋이다
- 자동 동기화 앱의 rollback 이 거절되는 이유
- 바뀌는 이름과 바뀌지 않는 이름 보고