GitOps 와 ArgoCD · GitOps 원칙 · 실습
매니페스트 저장소 만들고 드리프트 잡기
목표
매니페스트를 담은 git 저장소를 만들어 클러스터에 적용하고, 손으로 만든 변경(드리프트)을 감지해 저장소 기준으로 되돌릴 수 있게 됩니다.
왜 중요한가
GitOps 의 규칙은 한 문장입니다 — 저장소가 옳고 클러스터가 따라온다. 이 문장을 지키는 순간 "지금 프로덕션에 뭐가 떠 있나"라는 질문이 git log 로 답할 수 있는 질문이 되고, 롤백은 git revert 라는 평범한 작업이 됩니다. 반대로 클러스터를 직접 고치는 습관이 하나라도 남아 있으면 저장소는 현실을 설명하지 못하는 문서가 되고, 그 순간부터 재현이 불가능해집니다. 이 실습에서 kubectl diff 를 반복해서 쓰는 이유가 여기 있습니다 — diff 는 "선언과 실제가 얼마나 벌어졌나"를 종료 코드로 답해 주는 드리프트 계측기이고, ArgoCD 가 화면에 OutOfSync 라고 띄우는 것과 정확히 같은 판단을 사람 손으로 하는 것입니다. 이 환경에는 ArgoCD 컨트롤러가 돌지 않으므로, 그 컨트롤러가 대신 해 주는 일을 마지막 단계에서 직접 스크립트로 작성해 봅니다.
단계
1. /root/gitops/repo 디렉터리를 만들어 git init 하고, 그 저장소에 user.name 과 user.email 을 설정하세요. 그다음 /root/gitops/repo/README.md 를 만들어 첫 커밋을 남기세요 (커밋이 최소 하나 있어야 합니다).
2. /opt/lab/fixtures/gitops/seed/deployment.yaml 과 service.yaml 을 /root/gitops/repo/apps/web/ 로 복사하세요. Deployment 는 metadata.labels 에 app.kubernetes.io/managed-by: gitops 를 갖고, spec.selector.matchLabels 의 app.kubernetes.io/name 은 web 이어야 하며, spec.replicas 는 기본값에 맡기지 말고 명시해야 합니다(여기서는 2 로 시작합니다). 컨테이너 이미지는 nginx:1.27 처럼 태그가 고정된 값이어야 합니다 (:latest 는 실패 처리됩니다). README.md 도 그대로 있어야 합니다.
3. apps/web/deployment.yaml, apps/web/service.yaml, README.md 세 파일을 모두 git add 해 추적시키고, 10자 이상의 의미 있는 메시지로 커밋하세요. 마치면 git status --porcelain 출력이 비어 있어야 합니다.
4. kubectl apply -n gitops-lab -f /root/gitops/repo/apps/web/ 로 적용하고, 그 출력 전체를 /root/gitops/out/apply.txt 에 저장하세요. 적용 후 gitops-lab 네임스페이스에 Deployment web 과 Service web 이 있어야 하고, Deployment 에 app.kubernetes.io/managed-by=gitops 라벨이 붙어 있어야 합니다.
5. 저장소의 apps/web/deployment.yaml 에서 spec.replicas 를 3 으로 고치고, 커밋 메시지에 replicas 라는 단어를 넣어 커밋한 뒤 클러스터에 다시 적용하세요. 끝나면 커밋이 2개 이상이고, 저장소와 클러스터의 replicas 가 둘 다 3 이며, 작업 트리가 깨끗해야 합니다.
6. 이번에는 저장소를 건드리지 말고 kubectl scale deploy web -n gitops-lab --replicas=5 로 드리프트를 만드세요. 그 상태에서 kubectl diff -n gitops-lab -f /root/gitops/repo/apps/web/ 의 출력을 /root/gitops/out/drift-diff.txt 로, 그 명령의 종료 코드를 /root/gitops/out/drift-exit.txt 로 저장하세요. 그리고 /root/gitops/out/drift-note.txt 에 손으로 한 변경이 다음 적용에서 되돌려져 사라진다는 내용을 한국어로 두세 줄 적으세요.
7. 저장소 기준으로 다시 적용해 드리프트를 없애세요. 그다음 kubectl diff -n gitops-lab -f /root/gitops/repo/apps/web/ 를 한 번 더 실행해 그때의 종료 코드를 /root/gitops/out/clean-exit.txt 에 저장하세요. 손으로 만든 5 를 저장소에 반영하면 안 됩니다 — 저장소의 replicas 는 3 이고 작업 트리는 깨끗해야 합니다.
8. /root/gitops/sync.sh 를 만들고 실행 권한을 주세요. 이 스크립트는 (a) kubectl diff 로 적용 전 차이를 확인하고, (b) kubectl apply 로 적용하고, (c) git rev-parse HEAD 로 어떤 커밋을 적용했는지 기록해야 합니다. 실행 결과로 /root/gitops/out/sync-report.json 에 repo_commit(현재 HEAD 의 전체 해시), drift(불리언 false), applied(적용한 오브젝트 수, 2 이상) 세 키를 담으세요.
참고
- 같은 커밋이 언제 적용되든 같은 결과를 내야 GitOps 입니다. 그래서 이미지 태그를 고정하고, replicas 처럼 기본값이 존재하는 필드도 굳이 명시합니다. 명시하지 않은 값은 적용 시점의 클러스터 기본값이 정하게 되고, 그 순간 저장소는 상태를 정의하지 못합니다.
kubectl diff는 차이가 있으면 종료 코드 1, 없으면 0 을 냅니다.kubectl apply --dry-run=server와 달리 실제 서버 병합 결과와 라이브 상태를 비교해 줍니다.- 종료 코드는 명령 바로 다음에
echo $?로 읽어야 합니다.&&로 이으면 실패했을 때 뒷부분이 아예 실행되지 않으니명령 > 파일; echo $? > 코드파일처럼;로 잇는 편이 안전합니다. - 현재 HEAD 해시는
git -C /root/gitops/repo rev-parse HEAD로 얻습니다. 짧은 해시가 아니라 전체 해시여야 합니다. - 흔한 실수 1: 6번에서 손으로 만든
5를 저장소에도 반영하는 것. 그러면 드리프트를 해결한 게 아니라 사고를 코드로 승격시킨 것입니다. 그 값이 옳다면 별도 커밋으로 정식 반영해야 하고, 이 실습에서는 되돌리는 쪽이 정답입니다. - 흔한 실수 2:
sync.sh나out/을 저장소 안(/root/gitops/repo)에 두는 것. 커밋하지 않으면 작업 트리가 더러워지고, 커밋하면 보고서에 적은 HEAD 해시가 곧바로 어긋납니다. 산출물은/root/gitops/아래 저장소 바깥에 둡니다.
단계 8개
- 선언을 담을 저장소 초기화하기
- 앱별 매니페스트 디렉터리 만들기
- 선언을 커밋으로 못 박기
- 저장소의 선언을 클러스터에 적용하기
- 변경을 커밋을 거쳐 반영하기
- 손으로 바꿔 드리프트 만들어 보기
- 저장소 기준으로 되돌리기
- 동기화 스크립트와 보고서 만들기