규모가 커졌을 때와 점진적 배포
저번 포스팅에서는, Argo CD 의 내부 구조와 커밋 하나가 반영되기까지의 경로를 공부하였습니다. 이번에는 규모가 커졌을 때의 이야기입니다. Application 이 수백 개가 되고 클러스터가 여러 개가 되면 무엇이 문제가 되는지, 그리고 배포를 단순한 교체에서 점진적인 전환으로 어떻게 끌어올리는지를 다루겠습니다.
1. Application 이 많아지면
팀 하나에 서비스 하나라면 Application 도 하나입니다. 그런데 마이크로서비스가 서른 개이고 환경이 개발·스테이징·운영 셋이라면 Application 이 아흔 개가 됩니다.
이것을 손으로 만들면 몇 가지가 무너집니다.
- 새 서비스가 생길 때마다 Application YAML 을 세 벌 만들어야 합니다.
- 공통 설정을 바꾸려면 아흔 개를 전부 고쳐야 합니다.
- 누가 만들었는지, 빠진 것은 없는지 알기 어렵습니다.
Argo CD 자체도 GitOps 로 관리해야 한다는 결론에 자연히 이르게 됩니다.
2. App of Apps
가장 먼저 쓰게 되는 방법입니다. Application 을 만드는 Application 을 하나 두는 것입니다.
root Application
└── manifests/apps/ 안의 Application YAML 들을 배포
├── web Application
├── api Application
└── worker Application
루트 Application 하나만 손으로 등록하면, 그것이 나머지를 만들어냅니다. 새 서비스를 추가하려면 저장소에 Application YAML 을 하나 추가하고 커밋하면 끝입니다. Argo CD 가 배포하는 대상이 다시 Argo CD 의 리소스라는 점이 처음에는 이상하게 느껴집니다. 하지만 Application 도 결국 평범한 쿠버네티스 CRD 이므로, Argo CD 입장에서는 Deployment 를 배포하는 것과 다를 것이 없습니다.
다만 한계가 있습니다. 여전히 파일을 하나씩 만들어야 합니다. 클러스터 열 개에 같은 것을 배포한다면 YAML 열 개를 써야 합니다.
3. ApplicationSet
이 한계를 푸는 것이 ApplicationSet 입니다. 템플릿과 생성기(Generator) 로 Application 을 자동 생성합니다.
3.1. 생성기
무엇을 기준으로 Application 을 찍어낼지 정합니다.
- List : 직접 나열한 값마다 하나씩 만듭니다. 가장 단순합니다.
- Cluster : Argo CD 에 등록된 클러스터마다 하나씩 만듭니다. 클러스터를 추가하면 자동으로 배포 대상이 늘어납니다.
- Git (디렉토리) : 저장소의 디렉토리 구조를 보고 만듭니다.
apps/아래에 폴더를 하나 추가하면 Application 이 하나 생깁니다. - Git (파일) : 저장소의 설정 파일들을 읽어 그 값으로 만듭니다.
- SCM Provider : GitHub 조직의 저장소 목록을 읽어 만듭니다.
- Pull Request : 열려 있는 PR 마다 미리보기 환경을 만듭니다. PR 이 닫히면 함께 정리됩니다.
- Matrix : 두 생성기를 조합합니다. 예를 들어 "서비스 목록 × 클러스터 목록" 으로 전부 만들어냅니다.
- Merge : 여러 생성기의 결과를 키 기준으로 합쳐 값을 덮어씁니다.
Matrix 와 Merge 의 조합이 실무에서 강력합니다. 서비스마다 공통 설정을 두고, 특정 클러스터에서만 값을 다르게 하는 식의 구성이 가능합니다.
3.2. 주의할 점
ApplicationSet 은 Application 을 만들기도 하고 지우기도 합니다. 생성기의 입력이 사라지면 그 Application 도 사라지고, 그러면 배포된 리소스까지 함께 지워질 수 있습니다. 예를 들어 Git 생성기를 쓰는데 디렉토리 이름을 바꾸면, 기존 Application 이 삭제되고 새 이름으로 다시 생성됩니다. 그 사이에 서비스가 내려갑니다.
그래서 preserveResourcesOnDeletion 같은 옵션이나, 먼저 dry-run 으로 무엇이 만들어지고 지워질지 확인하는 습관이 필요합니다.
4. 여러 클러스터 관리하기
4.1. 어떤 구조를 고를 것인가
크게 두 가지 방식이 있습니다.
- 허브 앤 스포크 : Argo CD 를 한 곳에만 두고 여러 클러스터를 등록해 관리합니다. 화면 하나에서 전체가 보이고 운영이 단순합니다. 다만 그 Argo CD 가 모든 클러스터의 자격 증명을 갖게 되고, 그것이 죽으면 전체 배포가 멈춥니다.
- 클러스터마다 설치 : 각 클러스터에 Argo CD 를 두고 자기 것만 관리합니다. 장애 영향 범위가 좁고 자격 증명이 밖으로 나가지 않습니다. 대신 전체를 한눈에 보기 어렵습니다.
규제가 엄격하거나 클러스터가 지역별로 떨어져 있으면 두 번째를, 그렇지 않으면 첫 번째를 고르는 경우가 많습니다.
4.2. 환경별 차이를 어떻게 다룰 것인가
개발과 운영은 복제본 수도, 자원 크기도, 도메인도 다릅니다. 이 차이를 어떻게 표현하느냐가 관리의 질을 좌우합니다.
- Kustomize overlay : 공통 base 를 두고 환경별로 덮어씁니다. 차이가 명시적으로 드러나는 것이 장점입니다.
- Helm values : 차트 하나에 환경별 값 파일을 둡니다. 차이가 값 파일 한 곳에 모입니다.
어느 쪽이든 원칙은 같습니다. 환경 사이에 복사한 YAML 이 생기면 안 됩니다. 복사본이 생기는 순간 한쪽만 고쳐지는 일이 반드시 생깁니다.
4.3. 브랜치로 환경을 나누지 않는 편이 낫다
dev 브랜치는 개발 클러스터에, main 은 운영에 배포하는 방식을 처음에는 자연스럽게 떠올리게 됩니다. 그런데 이 방식은 문제가 있습니다.
- 환경 간 머지 충돌이 계속 생깁니다. 환경별로 다른 값이 브랜치마다 있기 때문입니다.
- 운영에만 있는 변경이 개발로 역류하거나 그 반대가 생깁니다.
- "무엇이 어디에 배포되어 있는가" 를 브랜치 비교로 알아내야 합니다.
그래서 하나의 브랜치에 디렉토리로 환경을 가르는 방식이 권장됩니다. overlays/dev, overlays/prod 처럼 두면 차이가 한 화면에서 비교되고, 승격은 파일 복사가 아니라 이미지 태그를 옮겨 적는 것이 됩니다.
5. 이미지 태그는 누가 바꾸는가
CI 가 새 이미지를 만들었을 때, 설정 저장소의 태그를 누가 어떻게 갱신할지가 남습니다.
- CI 가 커밋한다 : 가장 흔합니다. 빌드 후 설정 저장소의 태그를 바꿔 커밋합니다. 직관적이지만 CI 가 설정 저장소 쓰기 권한을 가져야 하고, 자동 커밋이 이력을 어지럽힙니다.
- Argo CD Image Updater : 레지스트리를 감시하다가 새 태그가 나오면 갱신합니다. CI 와의 결합이 느슨해지지만, Git 을 거치지 않고 클러스터를 바꾸는 모드로 쓰면 GitOps 원칙이 깨집니다. Git 에 다시 쓰는 모드로 설정해야 합니다.
- 사람이 PR 로 올린다 : 운영 환경에서 자주 쓰는 방식입니다. 승격이 리뷰를 거치게 됩니다.
태그를 latest 로 두면 안 됩니다. Git 의 내용이 바뀌지 않으니 Argo CD 는 동기화 상태로 보지만, 실제로 클러스터에 뜬 것은 언제 바뀐 것인지 알 수 없습니다. 어떤 커밋이 지금 돌고 있는지 추적할 수 없게 되는 것이 가장 큰 문제입니다. 가능하면 다이제스트까지 고정합니다.
6. 점진적 배포
쿠버네티스 시리즈 3편에서 롤링 업데이트를 봤습니다. 새 버전을 하나씩 갈아끼우는 방식인데, 여기에는 한계가 있습니다.
- 문제를 알아채는 것이 사람의 몫입니다. 새 버전에 버그가 있어도 롤아웃은 계속 진행됩니다.
- 트래픽 비율을 정밀하게 나눌 수 없습니다. Pod 개수로만 조절되므로 "5% 만 새 버전으로" 같은 것을 표현하기 어렵습니다.
- 되돌리는 것도 수동입니다.
6.1. Argo Rollouts
이 문제를 푸는 별도 프로젝트입니다. Deployment 대신 Rollout 이라는 CRD 를 쓰고, 배포 단계를 선언적으로 적습니다.
strategy:
canary:
steps:
- setWeight: 5
- pause: {duration: 10m}
- setWeight: 25
- pause: {duration: 10m}
- setWeight: 50
- pause: {}
5% 로 열고 10분 지켜보고, 25% 로 늘리고, 마지막에는 사람이 승인할 때까지 멈춥니다. 블루-그린도 지원합니다. 새 버전을 전부 띄워두고 미리보기 주소로 확인한 뒤, 트래픽을 한 번에 옮깁니다.
6.2. 분석과 자동 롤백
Argo Rollouts 의 핵심입니다. AnalysisTemplate 으로 판단 기준을 정의해두면, 단계 사이에서 지표를 확인하고 기준에 못 미치면 자동으로 되돌립니다.
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.99
failureLimit: 3
provider:
prometheus:
address: http://prometheus:9090
query: ...
쿠버네티스 시리즈 8편에서 본 Prometheus 가 여기서 판단 근거로 쓰입니다. 관측이 배포 결정에 직접 연결되는 것이 이 방식의 의미입니다.
6.3. 트래픽은 무엇이 나누는가
Rollouts 자체가 패킷을 나누지는 않습니다. 트래픽 제어는 다른 것이 합니다.
- Ingress 컨트롤러의 가중치 기능
- 서비스 메시 (4편에서 본 Istio 등)
- Gateway API 의 트래픽 분할
이것 없이 Rollouts 만 쓰면, 트래픽 비율이 아니라 Pod 개수 비율로만 조절됩니다. 정밀한 카나리를 하려면 네트워크 계층의 지원이 함께 필요합니다.
7. 운영에서 챙길 것들
7.1. Argo CD 자신의 고가용성
Argo CD 가 죽으면 새 배포가 멈춥니다. 이미 떠 있는 워크로드는 영향이 없지만 배포와 자가 치유가 중단됩니다.
- 각 구성 요소를 복제본 여러 개로 둡니다.
- Redis 를 HA 구성으로 둡니다.
- 관리 대상이 많으면 Application Controller 를 샤딩합니다.
7.2. 권한
2편에서 짚은 대로 Argo CD 는 대상 클러스터에 강한 권한을 갖습니다. 그래서 Argo CD 자체의 접근 통제가 곧 클러스터 보안입니다.
- AppProject 로 팀별 경계를 긋습니다.
- Argo CD RBAC 으로 역할을 나눕니다. 보기만 가능한 역할, 동기화까지 가능한 역할, 프로젝트를 만들 수 있는 역할 등입니다.
- 운영 프로젝트는 자동 동기화를 끄고 사람의 승인을 거치게 하는 구성도 씁니다.
7.3. 재해 복구
Argo CD 의 상태는 전부 쿠버네티스 리소스이므로, 백업 대상은 Application, AppProject, 그리고 클러스터·저장소 접속 정보 Secret 입니다. 다만 GitOps 의 장점이 여기서 드러납니다. 클러스터를 통째로 잃어도 Git 이 남아 있으면 재현할 수 있습니다. 새 클러스터에 Argo CD 를 설치하고 루트 Application 하나만 등록하면, 나머지는 스스로 복구됩니다.
7.4. 알림
동기화 실패나 Degraded 상태를 사람이 화면을 보고 알아채기를 기대할 수는 없습니다. Notifications 로 Slack 등에 연결해둡니다. 주의할 것은 너무 많이 보내면 아무도 보지 않게 된다는 점입니다. 정상 동기화까지 전부 알리면 금세 무시됩니다. 실패와 Degraded 만 보내는 편이 낫습니다.
8. 조금 더 들어가보기 : 5% 는 어디에서 나뉘는가
카나리 배포에서 "트래픽의 5%" 라는 말이 실제로 어디에서 실현되는지를 짚고 넘어가겠습니다. 이 부분을 모르면 Rollouts 를 설정해두고도 원하는 대로 나뉘지 않는 이유를 찾을 수 없습니다.
8.1. Pod 개수로 나누는 것과 트래픽으로 나누는 것
트래픽 제어 장치 없이 Rollouts 만 쓰면, 가중치 5%는 Pod 개수 비율로 근사됩니다. Pod 가 20개일 때 1개를 새 버전으로 두면 5%가 됩니다. 그런데 Pod 가 4개라면 1개를 새 버전으로 두는 순간 25%입니다. 5%를 표현할 방법이 없습니다.
그리고 앞서 정리한 대로 분산은 연결 단위입니다. 커널이 연결이 맺어질 때 목적지를 정하고 기억하므로, 비율은 새로 맺어지는 연결에 대해서만 적용됩니다. 오래 유지되는 연결을 쓰는 클라이언트라면 새 버전으로 넘어가지 않습니다.
"5%로 열었는데 새 버전에 트래픽이 거의 안 온다" 는 상황의 원인이 대개 이것입니다.
8.2. L7 에서 나눠야 정확해진다
정확한 비율 분배는 요청 하나하나를 이해하는 계층에서만 가능합니다.
- Ingress 컨트롤러의 가중치 기능
- 서비스 메시의 사이드카 프록시
- Gateway API 의 트래픽 분할
이들이 하는 일은 같습니다. 요청마다 난수를 뽑아 정해진 비율로 목적지를 고릅니다. 연결 단위가 아니라 요청 단위라서, 하나의 연결로 들어오는 요청들도 나뉩니다. Rollouts 는 이 도구들에게 "지금 가중치는 5" 라고 알려주는 역할을 합니다. 직접 패킷을 나누지 않습니다. 그래서 이 연동이 없으면 앞의 근사 방식으로 떨어집니다.
8.3. 세션 고정과 카나리는 서로 밀어낸다
여기서 자주 놓치는 문제가 있습니다. 같은 사용자가 요청할 때마다 다른 버전으로 가도 되는가입니다. 화면 구성이 바뀌었다면 한 사용자가 새 화면과 옛 화면을 오가게 됩니다. 상태가 세션에 있다면 더 곤란합니다.
그래서 사용자 단위로 고정하는 방식을 씁니다. 사용자 ID 를 해시해 일정 비율을 새 버전으로 보내고, 그 사용자는 계속 그쪽으로 갑니다. 그런데 이렇게 하면 앞서 본 쏠림과 만납니다. 고정하면 분산이 약해집니다. 특정 사용자군이 새 버전에 몰릴 수도 있습니다.
정답은 없고, 무엇이 더 중요한지에 따라 고릅니다. 화면이 바뀌지 않는 API 라면 요청 단위로 나누고, 사용자 경험이 달라지는 변경이라면 고정합니다.
8.4. 자동 롤백의 판단 시점
분석으로 자동 롤백을 건다면, 지표를 언제부터 언제까지 볼 것인지가 중요합니다.
- 너무 빨리 보면 새 Pod 가 아직 예열되지 않은 구간의 지표를 봅니다. JVM 이라면 JIT 이 돌기 전이라 응답이 느리고, 캐시도 비어 있습니다. 정상인데 나쁘게 보입니다.
- 너무 늦게 보면 문제가 있는 버전이 오래 트래픽을 받습니다.
그래서 대개 초기 구간을 건너뛰고 측정을 시작합니다. 그리고 한 번 나빴다고 바로 되돌리지 않고 연속 몇 회 실패를 기준으로 둡니다. 앞서 본 프로브의 실패 임계값과 같은 발상입니다. 또 하나 중요한 것은 비교 대상입니다. 새 버전의 오류율이 1% 라고 할 때, 그것이 나쁜 값인지는 같은 시간대 옛 버전의 오류율과 비교해야 압니다. 전체가 1% 인 상황이라면 새 버전 탓이 아닙니다.
8.5. 대규모에서 부딪히는 것들
- Application 수만큼 Rollout 이 진행되면, 각각이 지표를 조회하므로 Prometheus 에 질의가 몰립니다.
- 카나리 중에는 두 버전이 함께 떠 있으므로 그만큼 자원이 더 필요합니다. 수백 개가 동시에 카나리에 들어가면 클러스터 여유가 사라집니다.
- 데이터베이스 스키마가 얽히면 카나리가 어려워집니다. 두 버전이 같은 테이블을 쓰는데 스키마가 다르면 공존할 수 없습니다. 그래서 스키마 변경은 배포와 분리해 호환되는 단계로 나누는 것이 원칙입니다.
세 번째가 실무에서 가장 큰 제약입니다. 점진적 배포는 두 버전이 공존할 수 있을 때만 가능하고, 그것을 가능하게 만드는 것은 배포 도구가 아니라 애플리케이션과 데이터 설계입니다.
결론
Argo CD 를 세 편에 걸쳐 정리하면서 가장 크게 남은 것은, 배포 도구를 고른 것이 아니라 운영 방식을 고른 것이라는 감각이었습니다. GitOps 를 쓰기로 하면 저장소 구조를 정해야 하고, 환경을 어떻게 가를지 정해야 하고, 이미지 태그를 누가 바꿀지 정해야 합니다. 도구 설치는 몇 분이면 끝나지만 이 결정들이 실제 일이었습니다.
두 번째로 인상 깊었던 것은 ApplicationSet 이 지우기도 한다는 점이었습니다. 자동 생성의 편리함 뒤에 자동 삭제라는 위험이 붙어 있었습니다. 생성기의 입력이 바뀌면 Application 이 사라지고 서비스가 내려갈 수 있다는 것은, 자동화가 늘어날수록 그 입력을 더 조심스럽게 다뤄야 한다는 뜻으로 읽혔습니다.
세 번째는 Argo Rollouts 의 분석 기능입니다. 지금까지 배포와 모니터링은 서로 다른 영역이라고 생각했는데, 지표를 보고 롤백을 자동으로 결정한다는 발상이 그 경계를 지웠습니다. 관측이 대시보드를 보기 위한 것이 아니라 판단의 근거로 쓰이는 순간 의미가 달라진다는 것을 알게 되었습니다.
마지막으로, 쿠버네티스 시리즈에서 본 컨트롤 루프가 여기서 또 한 겹 확장되는 것을 확인했습니다. Pod 를 맞추는 루프 위에, Git 을 향해 맞추는 루프가 있고, 그 위에 지표를 보고 배포를 진행하거나 되돌리는 루프가 있습니다. 같은 패턴이 층층이 쌓여 하나의 체계를 이루고 있었습니다.
공부 참고 자료
📑 1. Argo CD - Cluster Bootstrapping (App of Apps)
📑 3. ApplicationSet Generators
📑 4. Argo CD - RBAC Configuration