GitOps 와 ArgoCD · 필드의 주인은 누구인가 · 이론
이 필드는 누구 것인가
한 줄 요약
서버측 적용은 객체의 필드마다 주인을 기록해 두는 장치이고, 그 기록(managedFields)을 읽을 줄 알아야
"왜 내 값이 자꾸 되돌아가는지" 와 Argo CD 의 managedFieldsManagers 무시 규칙을 설명할 수 있다.
왜 이 기록이 필요했나
예전 kubectl apply 는 클라이언트에서 계산했다. "내가 지난번에 보낸 매니페스트" 를kubectl.kubernetes.io/last-applied-configuration 주석에 통째로 넣어 두고, 새 매니페스트와 견줘 무엇을
지울지 정했다. 이 방식에는 주인이라는 개념이 없다. 그래서 두 도구가 같은 객체를 다루면 서로의 필드를
조용히 지운다 — Argo CD 가 적용한 다음 순간 오토스케일러가 replicas 를 바꾸고, 다음 적용에서 그 값이
사라지고, 오토스케일러가 다시 쓰는 식으로 끝없이 오간다. 누가 잘못했는지도 알 수 없다.
서버측 적용은 이 계산을 API 서버로 옮기고, 필드마다 어떤 관리자가 소유하는지 기록한다.kubectl apply --server-side --field-manager=gitops 의 gitops 가 그 이름이다.
어떻게 동작하나
기록은 metadata.managedFields 에 들어간다. 여기서 먼저 걸리는 함정이 하나 있다 —kubectl get ... -o json 은 이 필드를 기본으로 숨긴다. --show-managed-fields 를 붙이지 않으면
언제나 null 이라 jq 가 죽는다.
kubectl -n demo get deploy web -o json --show-managed-fields | jq -r '.metadata.managedFields[] | .manager + " | " + .operation'gitops | Applykubectl | Update내가 소유하지 않은 필드에 값을 쓰려 하면 서버가 거절하고, 어떤 필드가 누구 것인지 목록으로 알려 준다.--force-conflicts 를 주면 통과하는데, 이때 일어나는 일을 정확히 알아야 한다 — **충돌이 사라지는 게 아니라
소유권이 옮겨 간다.** 옛 주인은 그 필드에서 빠지고, 다음번에 옛 주인이 자기 값을 다시 적용하면 이번에는
그쪽이 막힌다. 자동화 둘이 강제를 습관처럼 쓰면 끝나지 않는 싸움이 된다.
반대 방향도 직관과 다르다. 매니페스트에서 필드를 빼면 그 관리자는 소유를 놓는다. 그런데 값은 **옛
주인의 값으로 돌아가지 않는다.** 아무도 소유하지 않는 필드는 API 기본값이 된다. replicas 를 5 로
빼앗아 갔던 관리자가 그 줄을 지우면 2 가 아니라 1 이 되는 것이다.
하위 자원도 따로 기록된다. kubectl scale 은 객체 전체가 아니라 scale 하위 자원에 쓰므로 소유 기록에
관리자 kubectl, 하위 자원 scale 이라는 항목이 따로 생기고, 충돌 메시지도 그 사실을 알려 준다.
오토스케일러가 만드는 충돌이 정확히 이 모양이다.
Argo CD 와 어떻게 이어지나
Argo CD 는 동기화 옵션 ServerSideApply=true 로 이 방식을 쓸 수 있다. 그리고 무시 규칙의managedFieldsManagers 는 필드 경로가 아니라 이 소유 기록으로 비교 대상을 고른다.
resource.customizations.ignoreDifferences.apps_Deployment: | managedFieldsManagers: - kubectl jsonPointers: - /spec/replicas"오토스케일러가 쓴 필드는 비교하지 않는다" 를 경로를 일일이 적지 않고 표현할 수 있다는 점이 장점이다.
대신 한계가 있다 — 미리보기 명령은 경로 규칙만 렌더한다. 관리자 이름 규칙이 실제로 무엇을 거를지는
소유 기록을 사람이 읽어 판단해야 한다.
현장에서 만나는 모습
가장 흔한 사고는 "HPA 와 Argo CD 가 싸운다" 는 것이다. 저장소에 replicas 가 적혀 있고 HPA 도 그 값을
쓴다. 소유 기록을 보면 답이 바로 나온다 — 두 관리자가 같은 필드를 쥐고 있다. 해법은 강제가 아니라
저장소에서 replicas 줄을 빼는 것이다. 다만 위에서 본 대로, 빼는 순간 값이 API 기본값으로 떨어지므로
HPA 가 다시 계산할 때까지 잠깐 파드가 줄어든다. 이 사실을 모르고 운영 시간에 하면 사고가 된다.
두 번째는 이전 중의 혼선이다. 클라이언트측으로 관리하던 객체를 서버측으로 옮기면 last-applied-configuration
주석이 남아 있고, 그 주석이 가리키는 필드와 새 소유 기록이 어긋나 예상치 못한 삭제가 일어날 수 있다.
그래서 이전은 객체 단위로, 소유 기록을 확인하며 한다.
이 실습 환경의 한계
실습 파드에는 Argo CD 컨트롤러가 없다. 그래서 ServerSideApply=true 동기화가 실제로 도는 것은 볼 수
없다. 대신 kwok 이 띄우는 것은 진짜 kube-apiserver 라 소유 기록·충돌·강제·놓아 주기는 운영과 똑같이
일어나고, Argo CD 쪽 연결고리는 무시 규칙 렌더로 확인한다.
다음 실습에서 할 것
관리자 gitops 로 적용하고 소유 기록을 읽는다. 두 번째 관리자 hotfix 로 같은 필드를 써 충돌을 만들고,
강제로 빼앗은 뒤 다시 놓아 줘서 값이 어디로 가는지 본다. 클라이언트측 적용이 남기는 주석을 견주고,kubectl scale 이 만드는 하위 자원 충돌을 일으킨다. 마지막으로 소유 기록이 담긴 실물 객체에managedFieldsManagers 규칙을 걸어 렌더해 본다.