junglast
Published on

Rolling Update 기반의 웹 애플리케이션 배포에서 발생하는 문제점과 Blue/Green 배포

쿠버네티스를 이용해 리액트로 작성된 프로젝트를 배포하는 환경에서, 초기에는 무중단 배포를 위해 여러 전략 중 Rolling Update를 선택했습니다. 하지만 웹 애플리케이션의 특성으로 인해 Rolling Update를 통해 완전한 무중단 배포를 구현하는 것에는 한계가 있었습니다.

이 글에서는 프론트엔드 배포에 Rolling Update를 사용하는 경우 발생할 수 있는 문제점과 이를 해결하기 위한 시도에 대해 다룹니다.

Rolling Update에서 발생하는 문제

무중단 배포를 위해 흔히 사용되는 전략에는 Rolling Update, Blue/Green 배포, Canary 배포 등이 있습니다. Rolling Update는 쿠버네티스에서도 기본적으로 제공되는 전략 중 하나로, 새 버전을 배포할 때 새로운 버전의 Pod을 하나씩 추가하고 기존 버전의 Pod을 하나씩 제거하는 방식으로 배포를 진행합니다. 하지만, 이 방식에서는 순간적으로 새로운 버전의 Pod과 기존 버전의 Pod이 동시에 존재하는 순간이 발생한다는 단점이 있습니다.

rolling update

Rolling Update 배포 과정

출처: https://medium.com/@kylelzk/kubernetes-practical-lab-rolling-update-project-f69b418b2fe6

초기에는, 운영 중인 서비스를 배포시 쿠버네티스의 Deployment를 이용하여 Rolling Update를 수행하도록 했습니다. 그러나, 위에서 언급한 대로 새로운 버전의 Pod과 기존 버전의 Pod이 동시에 존재하는 순간이 존재한다는 Rolling Update의 단점이 문제가 되었습니다. 일반적인 형태의 웹 애플리케이션은 빌드시 의도치 않은 캐싱을 막기 위해, 빌드 결과물로 나오는 번들의 파일명을 해시 등을 이용하여 매 빌드마다 다르게 설정합니다. 즉, 새로운 버전이 배포되면 이 버전에 필요한 번들의 파일명이 이전 버전이 필요로 하는 번들의 파일명과 다르게 설정된다는 의미입니다.

따라서 배포가 진행되고 있는 도중 유저의 트래픽이 인입되는 경우 웹 페이지 자체에 대한 요청은 기존의 Pod으로 전달되지만, 이 웹 페이지에 필요한 번들을 가져오는 요청은 새로운 버전의 Pod으로 전달될 수 있습니다(그 반대도 마찬가지). 결국 배포를 시작한 후 Rolling Update에 의해 기존 Pod이 모두 제거되기 전까지는 웹 페이지에 필요한 리소스를 받아오지 못해 간헐적으로 웹 페이지를 제대로 표시할 수 없는 순간이 생길 수 있습니다.

Blue/Green 배포를 이용한 해결 시도

이러한 Rolling Update가 가진 문제점을 해결하기 위한 방법으로 Blue/Green 배포를 고려해볼 수 있습니다. Blue/Green는 새 버전을 배포하는 과정에서 새로운 버전의 Pod을 추가하고 이를 테스트한 후, 그제서야 모든 트래픽을 새로운 Pod으로 전환하는 방식으로 배포를 진행합니다. 따라서 새로운 Pod의 추가가 완료되고, 이 Pod들(ReplicaSet)을 실제로 사용하도록 설정하기 전까지는 유저의 트래픽이 기존 Pod으로 전달되므로 Rolling Update에서 발생할 수 있는 문제점을 해결할 수 있습니다.

bluegreen

Blue/Green 배포 과정

출처: https://avikdas.com/2020/06/30/scalability-concepts-zero-downtime-deployments.html

쿠버네티스 환경에서 Blue/Green 배포를 적용하는 방법은 여러가지가 있지만, 진행 중인 프로젝트에서는 ArgoCD를 사용하고 있었으므로 ArgoCD의 Rollouts 플러그인을 이용해서 Blue/Green 배포를 구현했습니다. 이 플러그인을 이용했을 때의 장점은 다음과 같습니다.

  • ArgoCD 대시보드에서 유저 트래픽이 인입되는 흐름을 시각적으로 확인할 수 있습니다.
  • autoPromotionEnabled와 같은 옵션을 활용하면 새로운 Pod이 모두 추가되었을 때, 이 버전을 바로 배포하는 것이 아니라 충분한 테스트 후에 CLI나 대시보드상에서 새로운 버전을 실제로 사용하도록(새 버전으로 트래픽이 인입되도록) 설정할 수 있습니다.
  • 새로운 Pod이 추가 완료된 이후, 혹은 이 새로운 Pod을 실제로 사용하도록(Promote)한 후에도 이전 버전으로의 롤백이 용이합니다.

Blue/Green 배포의 문제점

하지만 이론적으로 완벽해보였던 Blue/Green 배포도 완벽한 무중단 배포를 보장하지는 못했습니다.

다음과 같은 시나리오가 대표적인 예시입니다.

  1. 유저가 웹 사이트에 접속
  2. 1번 이후 새로운 버전이 Blue/Green 전략을 이용해 배포되기 시작
  3. 새 Pod이 모두 추가되고 기존 Pod이 제거된 이후, 새 Pod들이 실제로 트래픽을 받기 시작함
  4. 1번 시점에서 접속한 유저가 웹 페이지 상의 링크를 누르거나 인터랙션을 시도하는 경우 기존 버전의 번들을 찾을 수 없어 오류가 발생함

특히 웹뷰에서 구동되는 페이지의 경우, 유저가 새로고침을 직접적으로 할 수 없는 경우가 많기에 사용성에 큰 영향을 끼칠 수 있습니다.

이는 자바스크립트 번들을 여러 개로 Split하여 구성한 경우에 더욱 문제가 될 수 있습니다. 물론 이는 배포 방식 자체에 존재하는 문제가 아니기에 Rolling Update 방식 등과 비교하면 다운타임이 없다고 할 수 있지만, 완벽한 무중단 배포를 위한 방법이라고 할 수는 없기에 추가적인 개선이 필요하다고 생각했습니다.

이외에도, 새로운 Pod이 모두 추가되기 전까지 기존 Pod이 제거되지 않으므로 순간적으로 원래 설정한 Pod 개수의 2배만큼의 Pod이 존재할 수 있다는 단점도 존재합니다.

추가적인 시도

대부분의 모던 프론트엔드 환경은 초기 1회 페이지를 하드로딩하고, 그 이후의 내비게이션이나 인터렉션은 비동기 요청으로 처리되는 것이 일반적입니다. 따라서 어떤 배포 방법을 사용하더라도, 새로운 배포가 진행되기 이전에 열렸던 웹페이지가 배포 완료 후 유저에 의해서 완전히 새로고침되지 않는 한 위와 같은 문제가 발생하기 쉽습니다.

이 경우에서 실제로 고민했던 방법 중 하나는 아래와 같이 빌드 결과물로 나온 정적인 번들 파일들을 쿠버네티스 클러스터 외부에서 CDN 등을 통해 별도로 배포하는 것입니다. 이 때 클러스터 내의 Pod은 프론트엔드 서버로만 동작하고, 이 서버에서 실행되는 웹페이지는 CDN에 배포된 번들을 불러오도록 설정합니다.

특히, Vite와 같은 도구는 Base와 같은 옵션을 활용하면 빌드시 아래와 같이 JS/CSS 등의 번들 에셋을 로드하는 부분에 Base URL을 임의로 추가할 수 있습니다.

<!-- index.html -->
...
<script src="https://mycdn.com/aabb1122.js" async></script>

이 경우 이전 버전 웹페이지를 이용하고 있는 유저에게도 이전 버전 번들을 문제없이 제공할 수 있어 새로고침을 하지 않아도 기존 웹페이지를 계속 사용할 수 있습니다. 물론 신규 배포가 완료되더라도 바로 직전 배포의 번들들은 CDN에서 삭제하지 않고 유지해야 할 것입니다.

물론 이렇게 별도로 번들을 배포하는 경우, CI/CD 환경에서는 번들을 CDN에 업로드하는 과정을 추가로 구성하는 추가적인 방법이 필요하게 됩니다.

위 과정에 추가로, 더 완벽한 사용자 경험을 제공하기 위해 이전 버전 웹페이지에 머물러 있던 유저에게 신규 버전이 배포되었음을 자연스럽게 알려주는 방법을 고려하기도 했습니다. 예를 들어 클라이언트에서 페이지 내비게이션이 일어나는 경우, 혹은 주기적인 폴링을 통해 새로운 버전이 배포되었는지 확인한 후, 신규 버전이 있다면 유저에게 이를 알려주고 새로고침을 유도하는 방법이 있을 것입니다.