GitOps 배포를 두려워하지 않기 위한 작은 자동화

안녕하세요! 강남언니 DevOps 팀에서 플랫폼 엔지니어링을 담당하고 있는 에일리와 아린입니다.
GitOps 구조는 어떻게 진화해왔을까 – 멀티리전 배포를 가능하게 만든 선택들에 이어서, 이번 글에서는 GitOps 배포에 안정감을 높이기 위해 개선했던 내용을 소개해볼게요!
왜 작은 변경이 두려웠을까?
초기 GitOps 구성 현황
지난 글에서 이야기했던 것처럼, 처음 GitOps 구조를 구축했을 때는 Helm Chart를 감싼 Kustomize 방식을 사용했어요. 서비스의 배포 파일은 기본적으로 2개(kustomization.yaml과 values.yaml)가 있고, kustomization.yaml은 이렇게 생겼습니다.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
helmGlobals:
chartHome: ../../../../charts # 바로 여기가 문제였어요!
helmCharts:
- name: miniservice
namespace: backend
releaseName: member
valuesFile: values.yaml
version: 0.0.1
helmGlobals.chartHome: ../../../../charts 이 부분 때문에 강남언니의 모든 서비스가 GitOps 저장소 메인 브랜치에 있는 charts/miniservice 디렉토리를 바라보고 있었어요. 처음에는 "어차피 Helm Chart가 자주 바뀌지 않으니까 같은 저장소에서 쉽게 접근하자!" 했었죠.
서비스 성장과 함께 찾아온 도전들
GitOps를 구축한 후 강남언니 서비스가 가파르게 성장하면서, 저희 플랫폼 엔지니어들도 바빠졌어요. 서비스를 더 안정적으로 운영하고 개발자분들이 더 쉽게 사용할 수 있도록 개선해야 할 것들이 계속 생겼거든요. 주요 개선 사항들은 아래와 같았습니다.
- 운영 환경 자동 E2E 테스트: 배포 후 자동으로 테스트 실행하고, 실패하면 알림 + 롤백
- Istio를 활용해 헤더 기반 트래픽 분리 필요
- Datadog 연동 자동 롤백: 배포 후 에러 발생 시 자동 롤백
- Argo Rollout의 AnalysisTemplate로 Datadog APM 지표 연동 필요
- 서비스별 시크릿 관리 단순화: 서비스와 시크릿 1:1 매핑으로 환경변수 자동 주입
- 등등
문제는 이런 개선을 위해서는 항상 Helm Chart를 변경해야 했습니다. VirtualService, DestinationRule 추가하고, Deployment를 Argo Rollout으로 바꾸고, 시크릿 구조 변경하는 등 Helm Chart 에 큰 변경이 수반되었어요.
문제점
그런데 Helm Chart를 변경하면 무슨 일이 생길까요? 다시 한 번 말해보면, 모든 서비스가 GitOps 저장소 메인 브랜치의 Helm Chart를 바라보고 있습니다!
Helm Chart를 수정해서 PR 올리고 메인 브랜치에 머지하는 순간 강남언니의 모든 서비스가 바라보는 Helm Chart가 바뀌어 버려요! 😱 실수가 있었다면 강남언니의 모든 서비스(회원 가입, 병원과 이벤트 검색, 예약 등)가 재배포될 수도 있고, 버그가 있었다면 서비스가 제대로 올라오지 않을 수도 있었죠.
Helm Chart를 디렉토리로 관리하는 방식은 저희에게 2가지 문제를 일으켰습니다.
- 버전 개념 없음: 서비스가 어떤 상태의 차트를 사용하고 있는지, 어떤 변경이 있었는지 추적 어려움
- 배포 타이밍 제어 불가: 메인 브랜치 디렉토리를 보고 있어서 서비스별 배포 타이밍 조절 어려움
물론 플랫폼 엔지니어들이 하위 호환성을 완벽하게 맞춰가며 개선하면 되겠지만, 간단한 변경 하나 할 때마다 엄청난 테스트를 해야 하고, 배포할 때마다 "혹시 운영에서 뭔가 터지지 않을까?" 하며 가슴을 졸여야 했어요.
이건 좋지 않은 신호였습니다.
Helm Chart 레포지토리와 버저닝을 통해 안전하게 배포하기
2가지 문제를 해결하기 위해 Helm Chart 저장소를 구성해서 GitOps 저장소 의존성에서 벗어나고, 릴리즈를 통해 변경사항을 관리하기로 했어요.
Helm Chart 저장소
The Chart Repository Guide를 보니 생각보다 간단했어요. 아주 단순한 HTTP 서버 하나만 만들면 되거든요! 가이드의 예시를 가져와보자면, https://example.com/charts로 접근할 수 있는 HTTP 서버를 만들고, GET 요청을 처리할 수 있게 해주고, 이런 구조로 파일을 두면 끝!
charts/
|- index.yaml
|- alpine-0.1.2.tgz
|- alpine-0.1.2.tgz.prov
index.yaml은 여기 있는 차트들을 알려주는 전화번호부 역할이구요.
apiVersion: v1
entries:
alpine:
- created: 2016-10-06T16:23:20.499814565-06:00
description: Deploy a basic Alpine Linux pod
digest: 99c76e403d752c84ead610644d4b1c2f2b453a74b921f422b9dcb8a7c8b559cd
home: https://helm.sh/helm
name: alpine
sources:
- https://github.com/helm/helm
urls:
- https://technosophos.github.io/tscharts/alpine-0.2.0.tgz
version: 0.2.0
- ..
helm package 명령으로 나오는 <chart name>-<version>.tgz를 올리기만 하면 됩니다!
이렇게 하면 helm repo add 명령어를 실행할 때 GET /index.yaml 이 실행되어 챠트 정보를 가지고 오게 되고 로컬 캐시를 만들어요. helm install 명령어를 실행하면 앞서 가져온 챠트 정보에서 URL 정보를 찾아서 GET 요청을 보내서 챠트 압축 파일을 가지고 옵니다.
Chart Repository Guide 에도 나와있는 것처럼 다양한 방법을 Helm Chart 저장소를 구성할 수 있습니다. 내가 직접 웹서버를 만들어도 되고, 스토리지 서비스인 AWS S3, GCP Cloud Storage 를 사용해서 정적 컨텐츠로 서버를 제공해도 돼요. Cloudsmith 같은 아티팩트를 관리해주는 서비스를 사용해도 되고요.
그 중, 저희는 ChartMuseum을 선택했습니다. 불필요하게 관리할 서비스를 늘리지 않으면서 강남언니 환경에서 사용하기 쉬운걸 골랐어요. ChartMuseum은 쿠버네티스 환경에 손쉽게 설치할 수 있고, AWS S3 를 백엔드 스토리지로 설정할 수 있거든요. 그리고 내부망에서만 접근 가능하도록 구성했어요. 게다가 관리용 API도 제공하고, 스토리지에 파일을 직접 넣어도 인덱스를 주기적으로 업데이트해주기 때문에 CI/CD 구성이 편합니다.
Helm Chart 릴리즈
다음으로 버전과 변경사항을 자동으로 관리하는 기능이 필요했어요. Release Please를 사용해서 GitHub Actions로 자동 릴리즈를 구성했습니다. Release Please는 구글에서 만든 릴리즈 관리 도구로, Conventional Commit 규칙을 기반으로 자동 릴리즈를 해줘요.
주요 기능을 함께 살펴볼까요?
- 커밋 메시지 헤더 기반 Helm Chart 자동 버전 관리
- feat: VirtualService 리소스 설정 추가 → 마이너 버전 업
- fix: Ingress 태그 오타 수정 → 패치 버전 업
- feat!: 시크릿 주입 구조 변경 → 메이저 버전 업 (하위 호환 깨짐)
- 변경사항을 담은 CHANGELOG.md 자동 관리
- 릴리즈되면 커밋 메시지를 기반으로 기능 변경, 수정, 하위 호환 깨지는 변경사항을 자동으로 정리해서 기록
- PR 기반 워크플로우
- 엔지니어는 평소처럼 PR로 Helm Chart 변경
- GitHub Actions가 자동으로 릴리즈 PR 생성하고 변경사항 누적
- 릴리즈 PR 머지하면 자동으로 Git 태그 생성하고 릴리즈 실행
실제로 Release Please가 자동 생성한 릴리즈 PR입니다.

릴리즈 PR 을 병합하면, 아래와 같이 Release Please GitHub Actions 워크플로우가 실행됩니다.

첫 번째 release 작업에서 Git 태그를 추가하고 Chart.yaml 내 버전을 업데이트 하고, 변경 사항을 CHANGELOG.md 에 기록해요. 두 번째 deploy-helm-chart 작업에서는 AWS S3 로 Helm Chart 의 패키지를 업로드해요.
이렇게 Helm Chart 릴리즈까지 완성되었어요.
이제 서비스별로 원하는 버전 사용 가능!
드디어 서비스마다 원하는 Helm Chart 버전을 명시해서 사용할 수 있게 되었어요.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
helmCharts:
- name: miniservice
repo: https://chart-museum.*.*.com
namespace: **
releaseName: **
valuesFile: values.yaml
version: 2.2.2
이제 어떻게 달라졌을까요?
Helm Chart에 버그가 있어서 2.2.3 버전으로 수정했다고 해볼게요. 예전이었다면 모든 서비스가 영향받아 재배포되거나 원하지 않는 동작을 할 수 있었어요. 지금은 A 서비스의 개발 환경에서만 version: 2.2.3으로 수정하면, 다른 곳은 전혀 영향없이 Helm Chart 변경을 테스트할 수 있게 되었답니다!
애플리케이션 배포 CI
새로운 문제 발견
여러분, 혹시 이런 경험 있으신가요? values.yaml을 변경했는데 "이거 잘 바뀐 거 맞을까?" 또는 Helm Chart 새 버전으로 올렸는데 "내가 원하는 대로 변경되는 거 맞을까?" 싶을 때요.
예를 들어 이런 PR을 봐주세요.

이 PR은 게이트웨이 서비스에서 시크릿 주입을 사용하지 않다가 사용하도록 활성화하는 변경이에요. 시크릿 주입 방식은 Helm Chart 안에 숨겨져 있고, 사용자는 values.yaml에서 토글만 하도록 추상화되어 있어요.
PR에서는 단순히 false가 true로 바뀐 것만 보이는데.. 실제로 쿠버네티스에서 뭐가 바뀌는 건지 확인해야 마음이 놓이지 않을까요?

이 PR은 Helm Chart 버전을 올리는 변경입니다. 메이저 버전까지 바뀌었으니 Helm Chart는 하위 호환성이 깨지는 경우죠. 이 서비스가 어떤 기능을 사용하고 있는지에 따라 하위 호환성이 깨질 수도, 아닐 수도 있어요.
이런 PR 을 리뷰할 때, 저희 팀은 아래의 과정을 거쳤어요.
- 로컬에서 해당 브랜치로 체크아웃
- kustomize build . --enable-helm > after.yaml로 변경 후 Manifest 생성
- 커밋 하나 되돌리고 kustomize build . --enable-helm > before.yaml로 변경 전 Manifest 생성
- Diff 툴로 after.yaml과 before.yaml 비교해서 리뷰
너무 귀찮은 과정이죠? 그리고 모든 PR마다 이렇게 리뷰하는 것도 어렵기 때문에, 이런 일을 누군가 대신 해줬으면 좋겠다고 생각했어요.
자동화로 해결하기
그래서 이 과정을 자동화하기로 했습니다! GitHub Actions를 통해 애플리케이션 배포 코드를 수정하는 PR이 올라오면 자동으로 이런 동작을 하게 했답니다.
- 이번 PR에서 변경된 애플리케이션 배포 나열
- 각 애플리케이션별로
- PR 브랜치 코드로 Manifest 생성
- 메인 브랜치 코드로 Manifest 생성
- 쿠버네티스 리소스 맥락을 가진 채로 차이점 비교
- 차이점을 PR 커멘트로 등록
구현할 때는 GitHub Action 워크플로우에서 아래의 툴들을 사용했어요.
- paths-filter: 변경된 파일 위치 기반으로 변경된 애플리케이션 추출
- GitHub Actions Matrix Strategy: 애플리케이션마다 병렬 처리
- Bash 스크립트: Helm Chart와 버전 추출해서 GitHub Release에서 압축 파일 다운로드
- dyff: 쿠버네티스 리소스 맥락을 이해하는 비교 도구
- sticky-pull-request-comment: PR에 비교 결과 커멘트 등록
그 중 dyff 는 정말 유용합니다!
kustomize build나 helm template 결과물은 순서가 다를 수 있잖아요? 현재 버전에서는 Deployment가 Service보다 먼저 나오는데, 이전 버전에서는 Service가 먼저 나올 수 있죠. 일반 Diff 도구는 이걸 차이점으로 인식하지만, dyff는 "순서만 다르네요"라고 알려주고 차이점으로 보지 않아요. 마찬가지로 Deployment 안의 환경변수 순서가 바뀌어도 의미상 차이가 없으니 차이점으로 인식하지 않고요. 그 외에 여러가지로 쿠버네티스 리소스의 의미를 알고 비교해주는 정말 유용한 도구입니다.
결과물: 이제 이렇게 보여줘요
자, 이제 위에 예시로 만든 PR 에서 애플리케이션 CI 가 어떻게 커멘트를 올려줄까요?

이렇게 해줍니다! 그래서 우리는 이제 이 PR에 애플리케이션 CI 가 달아준 Manifest Diff 를 보고 이렇게 생각할 수 있어요.
“시크릿 주입을 활성화하면 ExternalSecret 리소스를 만들어서 AWS 시크릿 매니저에서 자동으로 시크릿을 가져와 쿠버네티스 시크릿으로 만들고, 그걸 Argo Rollout의 환경변수로 주입하네.”

이 PR의 애플리케이션 CI 가 달아준 Manifest Diff 를 보면 이렇게 생각할 수 있어요.
“Helm Chart 버전을 올렸지만 모니터링용 Datadog Admission Controller의 Init Container 버전만 올라갈 뿐, 실질적인 변화가 없으니 하위 호환성에 문제없구나.”
아래는 도입하면서 써보면서 나온 반응들이예요.


왼쪽 PR은 50개 서비스의 Helm Chart를 업그레이드를 하는 내용이였어요. 패치 버전만 올라가서 대부분 호환성에 문제는 없겠지만, 50개 서비스의 챠트를 바꾸려면 겁이 나지 않나요? 그런데 애플리케이션 CI 가 50개 서비스의 Manifest 변경이 없다고 확인해서 위에처럼 “No manifest diff found” 를 50개 달아준 거예요! 이러면 배포해도 아무런 변경이 없겠군 하며 마음 놓고 배포할 수 있겠죠?
오른쪽 PR도 비슷한 상황이예요. 강남언니에 Helm Chart가 여러가지가 있어서 통합하는 과정이었어요. Helm Chart가 바뀔 때도 Manifest의 차이를 분석해서 없다고 알려주면 마음놓고 배포할 수 있었어요.
이후 애플리케이션 CI를 잘 정착시키면서 2가지 변화를 만들어냈어요!
- 배포 PR의 평균 리뷰 시간이 10분에서 1분으로 감소
- Helm Chart 변경으로 인한 운영 환경 배포 실패가 월 1-2회에서 0회로 감소
마무리
엄청나고 거대한 자동화는 아니지만, 이런 작은 자동화로도 생각이 바뀔 수 있었어요.
예전: "이거 바꿔도 될까? 잘 모르겠지만 다른 서비스 코드 보니까 이렇게 하던데..." 지금: "이렇게 바뀌네요. 괜찮을까요?"
플랫폼 엔지니어 입장에서는 하위 호환이 깨져서 서비스가 장애 나지 않을까 하는 걱정이 줄고 안정감이 생겼어요. 개발자 입장에서도 변경 영향을 파악하기 쉽고, 리뷰 요청하는 것도 쉬워졌고요.
모두에게 배포 PR에 대한 신뢰도를 높이고 리뷰와 배포 속도를 개선할 수 있었습니다.
함께 하실 분을 찾습니다
강남언니 DevOps 팀은 언제나 더 나은 개발 경험을 만들기 위해 고민하고 있습니다. 이런 재미있는 문제들을 함께 고민하고 해결해나갈 분을 찾고 있어요!
- "전 세계 유저들에게 더 빠른 서비스를 제공하려면?"
- "수백 개의 서비스를 효율적으로 관리하려면?"
- "개발자들이 더 편하게 배포할 수 있게 하려면?"
이런 고민들이 흥미가 생긴다면 저희와 함께해요! 궁금한 점이 있으시다면 언제든 연락해주세요!!