LabHub
学习 学习路径 课程

CGOA — GitOps 认证助理

Git 接收了提交,Argo 却无法应用

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

깨진 설정이 상태 저장소에 들어갔을 때 조정기가 겪는 일을 실제로 보고, CI 의 검증을 저장소 입구로 옮겨 같은 실수가 main 에 닿지 못하게 합니다. CI 는 Git 에만 쓰고 클러스터에는 조정기만 쓰는 분담을 RBAC 판정으로 확인합니다.

왜 중요한가

GitOps 는 CI/CD 를 없애지 않고 경계를 옮깁니다. CI 는 원하는 상태를 만들고 검증해 Git 에 기록하고, 배포는 클러스터 안의 조정기가 Git 을 당겨 와서 합니다. 그래서 CI 에게 운영 클러스터의 쓰기 자격 증명을 줄 이유가 사라집니다. 대신 Git 에 들어간 것은 곧 배포 명령이 됩니다. Git 은 YAML 의 의미를 모르므로 replicas: two 같은 커밋도 받아 주고, 조정기는 그것을 적용하려다 계속 실패합니다. 설정을 코드로 다룬다(Configuration as Code)는 것은 코드처럼 검증을 거친 뒤에만 원본에 들어오게 한다는 뜻이고, 이 실습은 그 검증을 검증 전용 신원과 서버 측 dry-run 으로 세웁니다.

단계

  1. 베어 저장소 /srv/bare/ci.git 을 만들고 /root/cgoa-ci/repo 로 복제해 deploy/web.yaml 에 Deployment web(namespace 없음, replicas 2, 라벨 app: web, 컨테이너 web, 이미지 nginx:1.27-alpine)을 커밋하고 main 으로 push 하세요. /root/cgoa-ci/app.yaml 에 Application ci-web(argocd 네임스페이스, project default, 저장소 git://gitd.gitsrv.svc.cluster.local:9418/ci.gitmain·deploy 경로, 대상 네임스페이스 ci-web, 자동 동기화 prune·selfHeal, CreateNamespace=true)을 작성해 적용하고 Synced·Healthy 를 확인합니다.
  2. deploy/web.yaml 의 replicas 를 문자열 two 로 바꿔 커밋·push 하고 ci-web 을 hard refresh 하세요. 20초쯤 뒤 Application 상태를 읽어 /root/cgoa-ci/broken.jsoncommit(깨진 커밋 SHA), sync_status(status.sync.status), operation_message(status.operationState.message), live_replicas(ci-web 네임스페이스 Deployment 의 실제 spec.replicas, 숫자)를 적습니다. 관찰이 끝나면 git revert 로 깨진 커밋을 되돌려 push 하세요. 되돌린 뒤에도 status.operationState 가 깨진 커밋으로 Running(재시도 중)이면 /root/cgoa-ci/kubeconfig(k3s kubeconfig 사본, 현재 네임스페이스 argocd)로 argocd app terminate-op ci-web --core 를 실행해 그 작업을 끝냅니다. ci-web 이 새 main 에 Synced·Healthy 이고 깨진 커밋을 재시도하는 작업이 남아 있지 않아야 합니다.
  3. 네임스페이스 cici-dryrun 을 만들고, ci 에 ServiceAccount validator 를 만드세요. ci-dryrun 에 Role dryrun-deployments(apps 그룹 deployments 에 get·create·patch 만)와 그것을 ci:validator 에 묶는 RoleBinding validator-dryrun 을 둡니다. 그 계정의 토큰(8시간)으로 kubeconfig /root/cgoa-ci/ci-kubeconfig 를 만드세요(서버 주소는 k3s kubeconfig 와 같게, 현재 네임스페이스 ci-dryrun). 이 계정은 ci-dryrun 에서만 Deployment 를 만들 수 있고 ci-web 에는 아무것도 쓸 수 없어야 합니다.
  4. /root/cgoa-ci/validate.sh <디렉터리> 를 실행 가능한 스크립트로 만드세요. 그 디렉터리의 매니페스트를 /root/cgoa-ci/ci-kubeconfigci-dryrun 네임스페이스에 서버 측 dry-run 으로 적용해 보고, 하나라도 거절되면 0 이 아닌 값으로 끝나야 합니다. 환경 변수 KUBECONFIG 에 기대지 말고 스크립트 안에서 그 kubeconfig 를 지정합니다. 실제 객체를 만들면 안 됩니다.
  5. /srv/bare/ci.git/hooks/pre-receive 를 실행 가능한 훅으로 만드세요. refs/heads/main 을 갱신하는 push 마다 새 커밋의 deploy 디렉터리를 임시 디렉터리에 꺼내 /root/cgoa-ci/validate.sh 로 검증하고, 실패하면 push 를 거절합니다. main 삭제는 거절하고 다른 참조는 통과시킵니다.
  6. /root/cgoa-ci/repo 에서 deploy/web.yamlreplicas 키를 오타 replica 로 바꾼 커밋을 만들어 push 를 시도하고, 거절된 출력 전체를 /root/cgoa-ci/blocked.txt 에 저장하세요. 그다음 로컬 main 을 원격 main 으로 되돌립니다(git reset --hard origin/main). 원격 main 은 그대로이고 ci-web 은 계속 Synced·Healthy 여야 합니다.
  7. /root/cgoa-ci/bump.sh <태그> 를 실행 가능한 스크립트로 만드세요. /root/cgoa-ci/repo 를 원격 main 으로 맞춘 뒤 deploy/web.yaml 의 이미지를 nginx:<태그> 로 바꾸고 메시지 ci: web nginx:<태그> 로 커밋해 push 합니다. kubectl 은 쓰지 않습니다. bump.sh 1.28-alpine 을 실행하고 hard refresh 뒤 ci-web 이 그 커밋에 Synced·Healthy 가 되어 Deployment 이미지가 nginx:1.28-alpine 으로 바뀐 것을 확인하세요.
  8. /root/cgoa-ci/report.jsonci_writes(git), cd_writes(cluster), ci_can_write_prod(ci:validatorci-web 에 deployments 를 patch 할 수 있는지, 불리언), gate(pre-receive), deployed_revision(지금 ci-web 의 status.sync.revision), broken_reached_git(2단계 깨진 커밋이 main 이력에 남아 있는지, 불리언)를 적으세요.

참고

Git 에서 배포되는 기준선

베어 저장소 /srv/bare/ci.git 을 만들고 /root/cgoa-ci/repo 로 복제해 deploy/web.yaml 에 Deployment web(namespace 없음, replicas 2, 라벨 app: web, 컨테이너 web, 이미지 nginx:1.27-alpine)을 커밋하고 main 으로 push 하세요. /root/cgoa-ci/app.yaml 에 Application ci-web(argocd 네임스페이스, project default, 저장소 git://gitd.gitsrv.svc.cluster.local:9418/ci.gitmain·deploy 경로, 대상 네임스페이스 ci-web, 자동 동기화 prune·selfHeal, CreateNamespace=true)을 작성해 적용하고 Synced·Healthy 를 확인합니다.

gitd 서비스가 /srv/bare 아래 베어 저장소를 git 프로토콜로 내보냅니다. 첫 동기화 뒤 파드 두 개가 Ready 가 되어야 Healthy 입니다.

저장소는 받았는데 Argo 가 적용에 실패했다

deploy/web.yaml 의 replicas 를 문자열 two 로 바꿔 커밋·push 하고 ci-web 을 hard refresh 하세요. 20초쯤 뒤 Application 상태를 읽어 /root/cgoa-ci/broken.jsoncommit(깨진 커밋 SHA), sync_status(status.sync.status), operation_message(status.operationState.message), live_replicas(ci-web 네임스페이스 Deployment 의 실제 spec.replicas, 숫자)를 적습니다. 관찰이 끝나면 git revert 로 깨진 커밋을 되돌려 push 하세요. 되돌린 뒤에도 status.operationState 가 깨진 커밋으로 Running(재시도 중)이면 /root/cgoa-ci/kubeconfig(k3s kubeconfig 사본, 현재 네임스페이스 argocd)로 argocd app terminate-op ci-web --core 를 실행해 그 작업을 끝냅니다. ci-web 이 새 main 에 Synced·Healthy 이고 깨진 커밋을 재시도하는 작업이 남아 있지 않아야 합니다.

Git 은 문자열인지 숫자인지 모릅니다. 조정기가 적용하려는 순간에야 API 서버가 거절합니다. 그동안 클러스터에 남는 것이 무엇인지 보세요. 자동 동기화 작업은 실패하면 정해진 횟수만큼 같은 revision 으로 재시도하고, 그동안 새 커밋의 동기화는 시작되지 않습니다. status.operationState.operation.sync.revision 을 확인하세요.

운영에는 못 쓰는 CI 신원

네임스페이스 cici-dryrun 을 만들고, ci 에 ServiceAccount validator 를 만드세요. ci-dryrun 에 Role dryrun-deployments(apps 그룹 deployments 에 get·create·patch 만)와 그것을 ci:validator 에 묶는 RoleBinding validator-dryrun 을 둡니다. 그 계정의 토큰(8시간)으로 kubeconfig /root/cgoa-ci/ci-kubeconfig 를 만드세요(서버 주소는 k3s kubeconfig 와 같게, 현재 네임스페이스 ci-dryrun). 이 계정은 ci-dryrun 에서만 Deployment 를 만들 수 있고 ci-web 에는 아무것도 쓸 수 없어야 합니다.

kubectl create token 으로 토큰을 받고 kubectl config --kubeconfig <파일> set-cluster/set-credentials/set-context 로 새 파일을 조립합니다. CA 는 k3s kubeconfig 의 certificate-authority-data 를 그대로 옮기면 됩니다. kubectl auth can-i --as system:serviceaccount:<ns>:<이름> 으로 판정을 확인하세요.

서버가 거절할 매니페스트를 CI 에서 먼저 거절

/root/cgoa-ci/validate.sh <디렉터리> 를 실행 가능한 스크립트로 만드세요. 그 디렉터리의 매니페스트를 /root/cgoa-ci/ci-kubeconfigci-dryrun 네임스페이스에 서버 측 dry-run 으로 적용해 보고, 하나라도 거절되면 0 이 아닌 값으로 끝나야 합니다. 환경 변수 KUBECONFIG 에 기대지 말고 스크립트 안에서 그 kubeconfig 를 지정합니다. 실제 객체를 만들면 안 됩니다.

클라이언트 쪽 dry-run 은 문자열 replicas 도, 오타 난 필드도 통과시킵니다. API 서버의 스키마 검증을 거치되 저장하지 않는 모드를 고르세요. set -e 만으로 파이프 중간의 실패가 드러나지 않을 수 있습니다.

검증을 저장소 입구에 세운다

/srv/bare/ci.git/hooks/pre-receive 를 실행 가능한 훅으로 만드세요. refs/heads/main 을 갱신하는 push 마다 새 커밋의 deploy 디렉터리를 임시 디렉터리에 꺼내 /root/cgoa-ci/validate.sh 로 검증하고, 실패하면 push 를 거절합니다. main 삭제는 거절하고 다른 참조는 통과시킵니다.

훅은 베어 저장소에서 돌아서 작업 트리가 없습니다. 특정 커밋의 한 디렉터리는 git archive <커밋> deploy | tar -x -C <임시> 로 꺼낼 수 있습니다. 새 SHA 가 0 이 40개면 삭제입니다.

같은 실수는 이제 main 에 닿지 않는다

/root/cgoa-ci/repo 에서 deploy/web.yamlreplicas 키를 오타 replica 로 바꾼 커밋을 만들어 push 를 시도하고, 거절된 출력 전체를 /root/cgoa-ci/blocked.txt 에 저장하세요. 그다음 로컬 main 을 원격 main 으로 되돌립니다(git reset --hard origin/main). 원격 main 은 그대로이고 ci-web 은 계속 Synced·Healthy 여야 합니다.

거절된 커밋은 원격에 없으므로 로컬만 되돌리면 됩니다. 이번에는 조정기가 실패를 겪을 기회조차 없습니다.

CI 는 Git 에 쓰고 Argo 가 배포한다

/root/cgoa-ci/bump.sh <태그> 를 실행 가능한 스크립트로 만드세요. /root/cgoa-ci/repo 를 원격 main 으로 맞춘 뒤 deploy/web.yaml 의 이미지를 nginx:<태그> 로 바꾸고 메시지 ci: web nginx:<태그> 로 커밋해 push 합니다. kubectl 은 쓰지 않습니다. bump.sh 1.28-alpine 을 실행하고 hard refresh 뒤 ci-web 이 그 커밋에 Synced·Healthy 가 되어 Deployment 이미지가 nginx:1.28-alpine 으로 바뀐 것을 확인하세요.

이 커밋도 5단계의 훅을 통과해야 main 에 들어갑니다. 스크립트는 Git 의 원하는 상태만 바꾸고, 적용은 조정기에 맡깁니다.

누가 어디에 쓰는지 보고

/root/cgoa-ci/report.jsonci_writes(git), cd_writes(cluster), ci_can_write_prod(ci:validatorci-web 에 deployments 를 patch 할 수 있는지, 불리언), gate(pre-receive), deployed_revision(지금 ci-web 의 status.sync.revision), broken_reached_git(2단계 깨진 커밋이 main 이력에 남아 있는지, 불리언)를 적으세요.

불리언 두 개는 추측하지 말고 kubectl auth can-igit merge-base --is-ancestor 로 확인한 결과를 적습니다.