LabHub

GitOps 와 ArgoCD · GitOps 원칙 · 이론

GitOps — 상태를 정의할 권한을 저장소에 준다

LabHub 에서 이어서 보기

한 줄 요약

GitOps 는 배포 도구의 이름이 아니라 클러스터의 상태를 정의할 권한은 git 저장소에만 있다는 소유권 선언이다.

왜 이게 필요했나

kubectl apply 로 배포하는 팀에서는 반드시 이 질문이 나온다. "지금 프로덕션에 떠 있는 게 정확히 어느 커밋이죠?" 그리고 아무도 자신 있게 답하지 못한다. 지난주 장애 때 누군가 kubectl scale 로 파드를 늘렸고, 누군가 kubectl edit 로 환경변수를 고쳤고, 그 변경은 어디에도 적히지 않았기 때문이다. 시간이 지나면 클러스터는 저장소 어디에도 없는 상태로 표류한다. 이것이 드리프트다.

드리프트의 진짜 비용은 "설정이 조금 다르다"가 아니라 재현 불가능이다. 클러스터를 다시 만들어야 할 때 저장소를 그대로 적용해도 예전과 같은 시스템이 나오지 않는다. 저자의 홈랩에서도 3노드를 7노드로 늘릴 때 새로 붙인 노드에 예전 클러스터의 잔재(구버전 kubelet, 옛 인증서)가 남아 있었고, 그 차이를 사람이 기억해서 맞추기 시작한 순간부터 실수가 났다.

GitOps 는 이 문제를 규칙 하나로 뒤집는다. 저장소에 없는 것은 클러스터에 있으면 안 되고, 저장소에 있는 것은 클러스터에 반드시 있어야 한다. 그러면 "지금 뭐가 떠 있나"는 git log 로 답할 수 있는 질문이 된다.

어떻게 동작하나

원칙은 네 가지이고, 넷을 다 만족해야 GitOps 다.

| 원칙 | 핵심 질문 | 어겼을 때 증상 |
| --- | --- | --- |
| 선언적 | 원하는 상태가 코드로 적혀 있나 | 재현 불가, 설정 드리프트 |
| 버전 관리 | 모든 변경이 커밋으로 남나 | 누가 언제 왜 바꿨는지 모름 |
| 자동 적용 | 사람 손 없이 적용되나 | 배포 지연, 휴먼 에러 |
| 자가 치유 | 어긋나면 자동으로 되돌리나 | 드리프트 누적, 환경 불일치 |

세 번째가 pull 방식이라는 점이 특히 중요하다. CI 가 클러스터에 직접 밀어 넣는 push 방식은 CI 에게 클러스터 자격 증명을 쥐여 줘야 한다. pull 방식은 클러스터 안의 에이전트가 저장소를 읽어 오므로 자격 증명이 클러스터 밖으로 나가지 않는다. 보안 경계가 통째로 달라지는 선택이다.

실무 규칙 두 가지가 더 붙는다. 첫째, 앱 코드 저장소와 매니페스트 저장소를 분리한다. 한 저장소에 두면 앱 CI 가 돌 때마다 동기화가 트리거되고, 코드 리뷰와 배포 리뷰가 한 PR 안에서 뒤섞인다. 둘째, :latest 태그를 쓰지 않는다. 같은 커밋이 어제와 오늘 다른 이미지를 띄운다면 그 저장소는 더 이상 상태를 정의하지 못한다. 이미지 태그는 커밋 해시나 시맨틱 버전처럼 한 번 정해지면 바뀌지 않는 값이어야 한다.

현장에서 만나는 모습

첫째, 커밋 메시지가 롤백 판단의 유일한 근거다. 장애 한복판에서 사람이 보는 것은 코드가 아니라 git log --oneline 한 화면이다. fix, update 같은 메시지가 열 줄 쌓여 있으면 어느 커밋을 되돌려야 하는지 고를 수 없다. GitOps 에서 롤백은 git revert 이고, revert 할 대상을 고르는 일이 곧 커밋 메시지를 읽는 일이다.

둘째, 급한 손질은 반드시 코드로 돌아와야 한다. kubectl scale 로 서비스를 살렸다면 그 값을 저장소에 커밋하든지, 아니면 저장소 기준으로 되돌리든지 둘 중 하나를 해야 한다. 그대로 두면 다음 동기화가 조용히 그것을 되돌려 같은 장애를 재현한다. Ansible 의 -e 나 Terraform 의 수동 변경과 정확히 같은 구조의 사고다.

셋째, 저장소 구조가 곧 권한 경계다. 저자의 홈랩은 Gitea(10.0.0.200)에 매니페스트를 두고 ArgoCD(10.0.0.201)가 그것을 읽는다. 디렉터리를 앱·환경별로 갈라 두면 나중에 리뷰어와 배포 권한을 그 경계 그대로 나눌 수 있다. 반대로 한 디렉터리에 전부 몰아 두면 권한을 나눌 자리가 없다.

다음 실습에서 할 것

/root/gitops/repo 에 매니페스트 저장소를 만들어 커밋하고, 그 선언을 gitops-lab 네임스페이스에 적용한다. 그다음 일부러 kubectl scale 로 드리프트를 만들어 kubectl diff 가 그것을 어떻게 잡아내는지(차이가 있으면 종료 코드 1) 확인하고, 저장소 기준으로 되돌린다. 마지막에는 diff → apply → 적용한 커밋 기록까지 하는 동기화 스크립트를 직접 써서, ArgoCD 컨트롤러가 대신 해 주는 일이 정확히 무엇인지 손으로 겪는다.