CGOA — GitOps 인증 어소시에이트 · 조정 루프와 드리프트 · 실습
push 했는데 아무 일도 일어나지 않았다
목표
Argo CD 가 Git 을 주기적으로 당겨 오는 경로와 push 웹훅으로 즉시 갱신하는 경로를 같은 커밋 흐름에서 잽니다.
폴링을 끄면 무엇이 멈추는지, 웹훅은 무엇을 확인한 뒤에만 받아들이는지, 운영에서 둘을 어떻게 겹쳐 쓰는지 봅니다.
왜 중요한가
GitOps 조정기는 원하는 상태를 스스로 당겨 옵니다. 당겨 오는 계기는 두 가지입니다. 정해진 주기마다 저장소를 다시 읽는 폴링과,
저장소가 "방금 push 가 있었다" 고 알려 주는 이벤트입니다. 폴링만 쓰면 커밋이 반영되기까지 주기만큼 기다리고, 저장소가 많아지면
그 주기가 곧 Git 서버의 부하가 됩니다. 이벤트만 쓰면 빠르지만 알림이 한 번 유실되면 커밋이 조용히 멈춥니다.
그리고 누구나 보낼 수 있는 웹훅은 조정기를 깨우는 공격 표면이 되므로 서명으로 보낸 쪽을 확인해야 합니다.
이벤트는 조정기에게 "지금 읽어라" 고 알릴 뿐 배포 명령이 아니라는 점도 이 실습에서 확인합니다. 무엇을 배포할지는 여전히 Git 이 정합니다.
단계
1. 베어 저장소 /srv/bare/evt.git 을 만들고 /root/cgoa-evt/repo 로 복제해 app/signal.yaml 에 ConfigMap signal(namespace 없음, data rev: r1)을 커밋하고 main 으로 push 하세요. /root/cgoa-evt/app.yaml 에 Application evt(argocd 네임스페이스, project default, repoURL http://githttp.gitsrv.svc.cluster.local/cgi-bin/git/evt.git, targetRevision main, path app, 대상 네임스페이스 evt, 자동 동기화 prune·selfHeal, CreateNamespace=true)를 작성해 적용하고 Synced 를 확인합니다.
2. ConfigMap argocd-cm 에 timeout.reconciliation: 30s 와 timeout.reconciliation.jitter: 0s 를 넣고 argocd-application-controller StatefulSet 과 argocd-repo-server Deployment 를 둘 다 재시작하세요. 새 컨트롤러 파드의 로그에서 appResyncPeriod 가 들어 있는 줄을 찾아 그대로 /root/cgoa-evt/interval.txt 에 저장하고, 재시작이 끝나면 evt 를 한 번 hard refresh 해 둡니다.
3. app/signal.yaml 의 rev 를 r2 로 바꿔 커밋·push 하고, refresh 도 웹훅도 쓰지 않은 채 evt 의 status.sync.revision 이 그 커밋이 될 때까지 기다리세요. /root/cgoa-evt/poll.json 에 commit, pushed_at(push 직후 유닉스 초), synced_at(revision 이 바뀐 것을 본 유닉스 초)을 숫자로 적습니다.
4. argocd-cm 의 timeout.reconciliation 을 0s 로 바꾸고 컨트롤러와 repo-server 를 재시작하세요. 그다음 rev 를 r3 로 바꿔 push 하고 아무 요청 없이 40초 이상 기다린 뒤 /root/cgoa-evt/nopoll.json 에 commit(r3 커밋), pushed_at, checked_at(기다린 뒤 확인한 유닉스 초), revision_at_check(그때 evt 의 status.sync.revision)를 적습니다.
5. argocd-server 서비스의 /api/webhook 에 GitHub push 이벤트를 보내세요. 헤더는 X-GitHub-Event: push 와 Content-Type: application/json, 본문은 ref refs/heads/main, after 에 r3 커밋, repository.html_url 에 http://githttp.gitsrv.svc.cluster.local/cgi-bin/git/evt 를 담은 JSON 입니다. evt 가 r3 커밋으로 바뀔 때까지 기다려 /root/cgoa-evt/webhook.json 에 http_status(숫자), sent_at, synced_at 을 적으세요.
6. openssl rand -hex 16 으로 만든 값을 /root/cgoa-evt/webhook-secret 에 저장하고 Secret argocd-secret 의 webhook.github.secret 키에 넣으세요. rev 를 r4 로 바꿔 push 한 뒤 서명 없는 push 이벤트를 먼저 보내고 15초 기다립니다. 이어서 같은 본문 파일을 그 비밀값으로 HMAC-SHA256 서명해 X-Hub-Signature-256: sha256=<hex> 헤더와 함께 보내고 동기화를 기다리세요. /root/cgoa-evt/secret.json 에 commit(r4), unsigned_status, signed_status(숫자), revision_after_unsigned(서명 없는 요청 15초 뒤 evt 의 revision), signed_synced_at 을 적습니다.
7. 웹훅은 유실될 수 있으므로 폴링을 되살립니다. argocd-cm 의 timeout.reconciliation 을 120s 로 바꾸고 timeout.reconciliation.jitter 키는 지운 뒤 컨트롤러와 repo-server 를 재시작하세요. 새 컨트롤러 로그의 appResyncPeriod 줄을 /root/cgoa-evt/restored.txt 에 저장하고, evt 를 한 번 hard refresh 해 둡니다. 웹훅 비밀값은 그대로 둡니다.
8. /root/cgoa-evt/report.json 에 poll_seconds(3단계에서 잰 synced_at-pushed_at), nopoll_synced(4단계 확인 시점에 r3 가 배포됐는지, 불리언), webhook_seconds(5단계 synced_at-sent_at), unsigned_accepted(서명 없는 이벤트가 배포를 일으켰는지, 불리언), trigger(webhook), safety_net(timeout.reconciliation)을 적으세요.
참고
- VM 안에 k3s, Argo CD v3.5.2, smart HTTP git 서버(
githttp.gitsrv)가 있습니다./srv/bare/<이름>.git이http://githttp.gitsrv.svc.cluster.local/cgi-bin/git/<이름>.git으로 보입니다. - 재시작:
kubectl -n argocd rollout restart statefulset argocd-application-controller와kubectl -n argocd rollout restart deployment argocd-repo-server뒤 각각rollout status로 기다립니다. - 흔한 실수: 컨트롤러만 재시작하는 것. 비교는 30초마다 해도 repo-server 가 브랜치의 옛 커밋을 캐시에서 돌려주어 새 커밋이 몇 분씩 보이지 않습니다.
- 시각 측정은
date +%s, revision 읽기는kubectl -n argocd get app evt -o jsonpath='{.status.sync.revision}'. - 흔한 실수: 3·4단계에서 refresh annotation 을 붙이는 것. 그 순간 측정 대상이 폴링이 아니게 됩니다.
- 흔한 실수: 서명을 계산한 문자열과 실제로 보낸 본문이 한 바이트라도 다른 것. 줄바꿈 하나로도 HMAC 이 달라집니다.
- [Argo CD 웹훅 설정](https://argo-cd.readthedocs.io/en/stable/operator-manual/webhook/) · [argocd-cm 예시(timeout.reconciliation)](https://argo-cd.readthedocs.io/en/stable/operator-manual/argocd-cm-yaml/) · [GitHub 웹훅 서명 검증](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) · [OpenGitOps 원칙](https://opengitops.dev/)
단계 8개
- smart HTTP 저장소를 추적하는 앱
- 당겨 오는 주기를 30초로 줄인다
- 아무것도 누르지 않고 기다린 시간
- 폴링을 끄자 커밋이 멈춰 섰다
- push 이벤트 한 번에 몇 초
- 서명 없는 이벤트는 문 앞에서 돌려보낸다
- 웹훅을 믿되 폴링을 안전망으로 되돌린다
- 당겨 오기와 이벤트의 역할 보고