Table of contents
Open Table of contents
1. 작은 변경인데 왜 이렇게 무거운가
폐쇄망 On-Prem 환경에서 Kubernetes 기반 AI 서비스를 운영하고 있었다. 배포는 Helm으로 하고 있었고, 시스템을 가장 잘 아는 사람이 직접 명령을 치고 상태를 확인하는 방식이었다.
초기에는 이게 가장 빠른 방식이었다. 서비스 수가 적고, 환경이 하나둘이고, 전체를 이해하는 사람이 직접 다루니까 문제가 생겨도 금방 대응할 수 있었다.
그런데 고객이 늘고 환경마다 인프라 구성이 달라지면서, 이상한 일이 생기기 시작했다. 설정 몇 줄을 바꾸는 것뿐인데, 실제로는 클러스터가 어떤 전제 위에 설치되어 있는지, 어떤 차트와 의존성이 얽혀 있는지, 환경별 예외가 무엇인지까지 함께 신경 써야 했다. 작은 변경인데 매번 무겁다.
배포 도구는 그대로였는데, 왜 설정 한 줄 바꾸는 일은 점점 더 무거워졌을까?
2. 설치와 배포가 같은 무게로 묶여 있었다
당시 Helm 기반 운영에서는 설치와 업데이트가 사실상 같은 실행 흐름 안에서 처리되고 있었다.
helm upgrade --install my-app ./chart -f values.yaml
이 한 줄이 처음 설치에도 쓰이고, 이후 업데이트에도 쓰인다. 같은 도구, 같은 프로세스, 같은 수준의 주의가 매번 필요하다.
초기에는 오히려 이런 단순함이 장점이었다. 배울 것이 하나뿐이었고, 설치를 할 줄 알면 업데이트도 같은 흐름으로 다룰 수 있었다.
하지만 이 둘은 빈도가 전혀 다르다.
- 설치: 클러스터를 새로 구성할 때. 드물다.
- 배포: 설정 변경, 버전 업그레이드, 기능 추가. 반복적이고 빈번하다.
Helm 하나로 운영하면 이 둘이 구분되지 않는다. 한 달에 한 번 하는 일과 하루에 여러 번 하는 일이 같은 무게로 처리된다.
물론 helm upgrade를 치면 업데이트 자체는 된다. 도구가 안 되는 게 아니다. 문제는 그때마다 머릿속에 올려야 하는 문맥의 크기가 줄지 않는다는 것이다. 설정 한 줄을 바꾸는 건데도, “지금 이 클러스터에 뭐가 깔려 있지? 의존성은? 환경별 예외는?” 을 매번 떠올려야 한다. 배포 횟수가 늘어도 배포당 주의의 범위는 그대로다.
그리고 이 구조에서는 실수의 영향 범위도 넓었다. 설치와 배포가 같은 흐름에 있으니, 네임스페이스나 릴리즈 이름을 잘못 지정하는 일상적인 실수도 의도하지 않은 리소스를 건드리거나 기존 배포를 꼬이게 만들 수 있었다.
관심의 윈도우를 좁힐 구조가 없었다. 여기서 말하는 관심의 윈도우란, 운영자가 한 번의 변경을 처리할 때 동시에 떠올려야 하는 문맥의 범위다.
3. 문제를 다시 정의한다
여기까지는 왜 무거운지를 봤다. 이제는 뭘 바꿔야 하는지로 넘어간다.
Helm 기반이라고 해서 기록이 없었던 건 아니다. 변경 사항은 Git에 남겼다. 하지만 기록이 있는 것과 변경 경로가 제한되어 있는 것은 다른 문제다. Helm은 자유도가 높다. helm upgrade로 무엇이든 할 수 있고, 어떤 순서로든 할 수 있고, 누구든 아무 시점에 실행할 수 있다. 자유도가 높다는 건 매번 “어떻게 할까”를 판단해야 한다는 뜻이기도 하다.
차이는 SSH로 서버에 직접 들어가 수정하는 방식과, PR 기반 배포 파이프라인을 통과시키는 방식의 차이에 가깝다. 둘 다 결국 변경은 가능하지만, 전자는 실행자 판단이 넓게 남고 후자는 변경 경로가 더 좁고 공유 가능하다. Helm 단일 운영과 GitOps 운영의 차이도 비슷했다. 필요한 건 더 많은 자유가 아니라, 변경 경로를 좁혀서 누가 하든 같은 방식으로 흘러가게 만드는 것이었다.
그리고 또 하나. 설치는 한 번 하면 끝이다. 초반에 고생하더라도, 이후에는 거의 다시 돌아오지 않는다. 반면 배포는 매일 일어난다. 서비스가 살아있는 한 계속된다.
정리하면 이건 “어떤 배포 도구를 쓸 것인가”의 문제가 아니었다. 두 가지가 함께 필요했다.
- 자주 일어나는 일(배포)의 자유도를 줄여서, 누가 하든 같은 경로로 흘러가게 만드는 것
- 빈도가 다른 일(설치와 배포)을 분리해서, 매번 전체를 떠안지 않게 만드는 것
그런데 Helm 단일 레이어에서는 둘 다 구조적으로 불가능했다. 설치든 배포든 같은 도구, 같은 자유도, 같은 흐름 안에 있었으니까. 특히 폐쇄망에서는 외부 CI/CD 서비스를 쓸 수 없어서, 변경 경로를 제한하려면 내부에서 직접 그 구조를 만들어야 했다.
판단은 이거였다. 설치와 배포를 나누고, 자주 일어나는 변경은 더 좁은 경로로만 처리하자.
4. 둘로 나눈다
이 판단의 결과로 GitOps 운영 모델을 도입했고, 그 구현 도구로 ArgoCD를 사용했다.
[이전: 단일 레이어]
설치 ──────────┐
├── helm upgrade --install (매번 같은 무게)
배포(업데이트) ──┘
[이후: 2단계 레이어]
1단계: 부트스트랩
┌─────────────────────────────────┐
│ 클러스터 초기 상태 + ArgoCD 설치 │
│ 스크립트 기반. 1회성. 무겁다. │
└─────────────────────────────────┘
│
▼
2단계: GitOps 운영
┌─────────────────────────────────┐
│ 이후 모든 변경은 Git → ArgoCD │
│ 선언형. 반복적. 가볍다. │
└─────────────────────────────────┘
1단계 부트스트랩은 폐쇄망 클러스터에 ArgoCD를 포함한 최소 구성을 올리는 단계다. 외부 의존성 없이, 스크립트 하나로 초기 상태를 만든다. 무겁지만 한 번만 하면 된다.
2단계 GitOps 운영은 이후의 모든 변경을 다루는 단계다. Git 저장소에 원하는 상태를 선언하면, ArgoCD가 클러스터와 동기화한다. 매일 반복되는 배포가 여기서 일어난다.
Helm을 버린 것이 아니다. Helm은 부트스트랩 단계의 한정된 역할로 남겼고, 반복되는 변경 경로에서는 Git과 ArgoCD가 기본 흐름이 되도록 했다.
5. 관심의 윈도우가 나뉜다
가장 큰 변화는 “지금 뭘 봐야 하는가”가 명확해진 것이다.
[이전]
┌──────────────────────────────────────────────┐
│ 설치 구성 │ 배포 설정 │ 상태 확인 │ ... │ ← 매번 전체를 봐야 한다
└──────────────────────────────────────────────┘
[이후]
┌────────────────┐
│ 부트스트랩 │ ← 새 환경 구성 시에만
└────────────────┘
┌──────────────────────────────────┐
│ GitOps 운영 │ ← 일상적으로 여기만
└──────────────────────────────────┘
일상 운영에서는 GitOps 레이어만 보면 된다. 부트스트랩 레이어는 새 클러스터를 구성할 때만 꺼내본다. 이전에는 하나의 면을 매번 전부 봐야 했다. 이제는 두 면으로 나뉘어서 필요할 때 필요한 쪽만 본다.
예를 들어 이미지 버전을 하나 올리는 일을 생각해 보자. 변경하는 행위 자체는 Helm이든 ArgoCD든 똑같이 한 줄이다. 달라진 건 그 한 줄이 클러스터에 도달하는 경로다. 이전에는 누군가가 터미널에서 helm upgrade를 직접 쳐야 했고, 그 명령은 누가, 언제, 어디서든 실행할 수 있었다. 지금은 Git에 커밋하면 ArgoCD가 변경을 감지하고 sync한다. 변경을 클러스터에 적용하는 주체가 사람에서 컨트롤러로 바뀌었고, 누가 언제 뭘 바꿨는지는 Git 이력에 남는다.
팀 관점에서도 달라졌다. 단일 레이어에서 운영은 전체를 이해하는 사람의 일이었다. 설치와 배포가 구분되지 않으니, 부분만 알아서는 손댈 수 없었다. 2단계로 분리되면서, 일상 배포는 GitOps 레이어만 이해하면 참여할 수 있게 됐다. 숙련자가 아닌 사람도, 다른 직군도, 변경과 상태를 더 좁은 범위 안에서 이해할 수 있게 됐다.
관심의 윈도우를 나눌 수 있다는 것은, 역할을 나눌 수 있다는 뜻이기도 하다.
6. 대신 부트스트랩이 단단해야 한다
이 구조에는 트레이드오프가 있다.
일상 운영을 가볍게 만든 건 좋은데, 부트스트랩이 제대로 안 되면 가벼울 것도 없다. 부트스트랩이 불안정하면 “초반에 한 번만 고생”이 아니라 “새 환경마다 처음부터 다시 원인을 추적해야 한다”가 된다. 폐쇄망에서는 실패 시 디버깅도 어렵다. 외부에서 이미지를 다시 가져올 수도, 문서를 바로 찾아볼 수도 없다.
부트스트랩은 ArgoCD가 동작하기 전 단계이기 때문에, 웹 UI도 대시보드도 없다. 터미널에서 모든 걸 해야 한다. 여기서 뭔가 꼬이면, 폐쇄망이라 외부 문서도 못 보고 이미지도 다시 못 가져오는 상태에서 터미널만으로 원인을 추적해야 한다.
그래서 부트스트랩 스크립트는 최대한 단순하고 예측 가능하게 만들었다. 외부 의존성을 줄이고, 실패 지점을 명확하게 하고, 한 번 돌리면 같은 결과가 나오게 했다. 자주 실행하지 않는 코드일수록, 실행할 때 믿을 수 있어야 한다.
레이어가 둘로 나뉘면 그 경계를 어디에 둘지도 계속 판단해야 한다. 새로운 구성요소가 추가될 때 “이건 부트스트랩에 포함할 것인가, GitOps 관리 대상으로 둘 것인가?”를 매번 정해야 한다. 이 경계가 흐려지면 애초에 둘로 나눈 이유가 사라진다.
7. 돌아보면
이 전환은 Helm을 ArgoCD로 교체한 일이 아니었다.
결국 복잡성을 없앤 것이 아니다. “매번 전체를 봐야 하는 문제”가 사라진 대신, “부트스트랩의 완성도를 유지해야 하는 문제”와 “레이어 경계를 관리해야 하는 문제”가 새로 생겼다. 문제를 없앤 것이 아니라 다른 종류의 문제로 바꾼 것이고, 중요한 건 그 바꾼 문제가 팀이 실제로 다룰 수 있는 종류인가였다. 매번 넓게 떠안아야 했던 문제를 나눠서, 한 번에 하나만 다룰 수 있는 형태로 바꾼 것이다.