← Posts

DevOps · Deployment · Blue-Green

무중단 배포 - 안 끄고 갈아끼운다

구버전을 끄고 신버전을 켜는 사이에는 공백이 생긴다. 그 공백을 없애는 전략들(롤링·블루그린·카나리)이 각각 무엇을 사고 무엇을 파는지 본다.

목차
  1. 배포하면 왜 잠깐 끊기나
  2. 무중단의 조건: 잠깐 공존해야 한다
  3. 재생성: 공백을 인정하는 기준선
  4. 롤링: 조금씩 갈아끼운다
  5. 블루그린: 옆에 다 세워놓고 스위치를 넘긴다
  6. 카나리: 일부에게만 먼저 보낸다
  7. 관찰이 없으면 카나리가 아니다
  8. 공존이 만드는 대가: 두 버전이 같은 DB를 본다
  9. 되돌리는 속도가 진짜 값이다
  10. 정리

배포는 결국 교체다. 구버전을 신버전으로 바꾸는 일이다. 문제는 그 “바꾸는 순간”에 숨어 있다.

배포하면 왜 잠깐 끊기나

가장 단순하게 배포하면 이런 순서가 된다. 돌고 있는 구버전을 끈다. 그리고 신버전을 켠다.

이 두 동작 사이에 아무것도 안 도는 시간이 있다. 구버전은 이미 죽었고 신버전은 아직 안 떴다. 그 사이에 들어온 요청은 갈 곳이 없다. 화면이 안 뜨거나 에러가 난다.

앱이 뜨는 데 10초가 걸린다면 그 10초가 통째로 공백이다. 배포가 잦아질수록 이 10초짜리 구멍을 자주 낸다. 무중단 배포는 이 공백을 없애는 이야기다.

무중단의 조건: 잠깐 공존해야 한다

공백이 왜 생겼는지 다시 보면 답이 보인다. 끄고 나서 켰기 때문이다. 순서를 뒤집으면 된다. 신버전을 먼저 띄우고, 잘 뜬 걸 확인한 다음, 그때 구버전으로 가던 요청을 신버전으로 돌린다. 마지막에 구버전을 끈다.

이러려면 잠깐이라도 구버전과 신버전이 동시에 살아 있어야 한다. 무중단 배포의 모든 전략은 여기서 갈라진다. 둘을 얼마나 오래 공존시키고, 요청을 얼마나 빨리 넘기느냐의 차이일 뿐이다.

그리고 이게 성립하려면 전제가 있다. 버려도 되는 실행 단위몇 벌이든 똑같이 찍어낼 수 있는 이미지다. 신버전을 옆에 하나 더 띄운다는 건 같은 걸 하나 더 찍는다는 뜻이니까. 컨테이너와 이미지를 먼저 갖춘 이유가 여기서 회수된다. CI/CD 편이 마지막에 이름만 꺼낸 전략들을 지금부터 하나씩 본다.

재생성: 공백을 인정하는 기준선

먼저 무중단이 아닌 방식부터 본다. 앞에서 본 그 단순한 방식, 구버전을 다 끄고 신버전을 다 켜는 것이다. 재생성(recreate)이라고 부른다.

무중단의 반대편에 기준선으로 세워두면 나머지가 무엇을 사는지 또렷해진다. 재생성은 공백을 그냥 받아들인다. 대신 가장 단순하고, 두 버전이 절대 겹치지 않는다.

이게 장점인 경우가 있다. 구버전과 신버전이 같은 데이터를 만지면 안 되는 상황, 예를 들어 데이터 형식을 크게 바꾸는 배포라면, 둘이 겹치는 게 오히려 사고다. 이럴 땐 잠깐 멈추고 통째로 바꾸는 게 맞다.

무엇을 사고 무엇을 파나: 단순함과 안전한 격리를 사고, 다운타임을 판다. 새벽처럼 트래픽이 없는 시간대에 잠깐 멈춰도 되는 서비스라면 이걸로 충분하다. 무중단이 공짜가 아니라는 걸 보여주는 기준선이다.

롤링: 조금씩 갈아끼운다

앱을 한 대가 아니라 여러 대로 돌리고 있다면(스케일 아웃), 방법이 하나 생긴다. 한 번에 다 바꾸지 말고 한 대씩 바꾼다.

세 대가 있으면, 한 대를 내려 신버전으로 올린다. 그동안 나머지 두 대가 요청을 받는다. 그 한 대가 올라오면 다음 한 대를 바꾼다. 이렇게 굴려가며 전부 신버전이 될 때까지 반복한다. 그래서 롤링(rolling)이다.

diagramdiagram

전 과정에서 요청을 받는 대가 항상 남아 있다. 공백이 없다. 추가로 필요한 자원도 적다. 한두 대 분량만 더 있으면 된다.

대신 대가가 둘 있다. 교체가 진행되는 동안 구버전과 신버전이 요청을 섞어 받는다. 어떤 사용자는 v1을, 바로 다음 요청은 v2를 만날 수 있다. 그리고 신버전이 문제가 있어도 이미 절반쯤 바꿔놨다면 되돌리는 데도 절반만큼 시간이 든다. 한 번에 확 끌 수가 없다.

무엇을 사고 무엇을 파나: 공백 없음과 적은 추가 자원을 사고, “두 버전이 섞이는 시간”과 “느린 롤백”을 판다.

블루그린: 옆에 다 세워놓고 스위치를 넘긴다

롤링의 “섞이는 시간”이 싫다면 다른 길이 있다. 신버전을 통째로 한 벌 더 세운다. 구버전 환경 전체(파랑)는 그대로 두고, 그 옆에 신버전 환경 전체(초록)를 똑같이 올린다.

초록이 다 뜨고 정상인 걸 확인할 때까지 사용자는 여전히 파랑만 쓴다. 확인이 끝나면 앞단의 라우터가 가리키는 곳을 파랑에서 초록으로 한 번에 바꾼다. 그 순간부터 모두가 초록을 쓴다.

diagramdiagram

롤링과 갈리는 점이 핵심이다. 새로 들어오는 요청은 한 번에 전부 넘어간다. 스위치 전엔 새 요청이 전부 파랑으로, 넘긴 뒤엔 전부 초록으로 간다. 롤링처럼 한 사용자의 연속 요청이 v1과 v2를 예측 없이 오가는 일이 없다(넘기는 순간 파랑에서 처리 중이던 요청만 파랑에서 마무리된다). 그리고 문제가 생기면 스위치를 도로 파랑으로 넘기면 끝이다. 구버전 환경이 아직 그대로 살아 있으니까. 이게 이 방식의 진짜 값이다 - 롤백이 거의 즉시다.

값이 있으면 대가가 있다. 배포하는 동안 같은 규모의 환경을 두 벌 돌린다. 자원이 잠깐 두 배로 든다.

무엇을 사고 무엇을 파나: 섞임 없는 전환과 즉시 롤백을 사고, 두 배의 자원을 판다.

카나리: 일부에게만 먼저 보낸다

블루그린은 확인이 끝나면 전부를 한 번에 넘긴다. 그런데 “확인”을 어떻게 하나? 아무리 미리 테스트해도, 진짜 사용자의 진짜 트래픽에서만 드러나는 문제가 있다.

그래서 나온 게 카나리(canary)다. 신버전을 띄우되, 전체 트래픽 중 아주 일부만 그쪽으로 흘린다. 5%쯤. 나머지 95%는 여전히 구버전을 쓴다. 그 5%에서 에러율이나 응답 속도를 지켜본다. 멀쩡하면 20%로, 다시 50%로 조금씩 비중을 올린다. 끝까지 문제가 없으면 100%로 올려 배포를 마친다. 이상하면 즉시 0%로 되돌린다.

diagramdiagram

이름이 좋은 비유다. 옛날 광부들이 유독가스를 먼저 감지하려고 카나리아 새를 데리고 들어갔다. 새가 먼저 위험을 알린다. 신버전에 문제가 있으면 전체가 아니라 그 5%만 먼저 겪는다. 사고의 크기를 잘라두는 것이다.

무엇을 사고 무엇을 파나: “폭발 반경”을 잘라내는 것을 사고, 배포가 길어지는 것과 트래픽을 비율로 쪼갤 수 있는 앞단을 판다.

관찰이 없으면 카나리가 아니다

카나리의 알맹이는 “5%만 보낸다”가 아니다. 그 5%를 지켜본다는 데 있다. 지켜보지 않고 그냥 비율만 천천히 올린다면, 그건 느린 롤링일 뿐 카나리가 아니다. 새를 데려가 놓고 새를 안 보는 것과 같다.

그래서 카나리는 앞에 조건이 붙는다. 무엇이 “이상”인지 미리 정해둬야 한다. 에러율이 몇 %를 넘으면, 응답이 몇 배 느려지면 되돌린다 - 이런 기준선이 있어야 한다. 기준이 없으면 사람이 대시보드를 눈으로 보다가 “느낌이 안 좋은데”로 판단하게 되고, 그러면 CI/CD 편에서 본 그 함정, 급할 때 사람이 빠뜨리는 단계가 여기 또 생긴다.

이 기준이 있으면 되돌림을 기계에 맡길 수 있다. 에러율이 선을 넘으면 자동으로 트래픽을 0%로 내린다. 사람이 새벽에 깨서 판단하지 않아도 된다. 쿠버네티스 편에서 본 “원하는 상태와 실제를 계속 비교하는” 그 루프와 같은 태도다. 사람이 감시자가 되지 않게 한다.

공존이 만드는 대가: 두 버전이 같은 DB를 본다

지금까지 앱 서버만 봤다. 그런데 앱은 대개 데이터베이스를 함께 쓰고, DB는 앱처럼 옆에 한 벌 더 복제하기가 쉽지 않다. 롤링이든 블루그린이든 카나리든, 공존하는 동안 구버전과 신버전이 같은 하나의 DB를 함께 본다.

여기서 사고가 난다. 신버전에서 컬럼 이름을 name에서 full_name으로 바꿨다고 하자. 신버전은 full_name을 읽고 쓴다. 그런데 아직 안 죽은 구버전은 여전히 name을 찾는다. 구버전이 깨진다. 컬럼을 지웠다면 더 나쁘다.

그래서 규율이 하나 붙는다. 스키마 변경을 “늘리고, 나중에 줄인다”로 나눈다(expand and contract). 컬럼을 바꾸는 대신,

  1. 먼저 새 컬럼 full_name추가만 한다. 구버전은 옛 name을 그대로 쓰니 안 깨진다.
  2. 신버전을 배포하되, 신버전은 새 full_name을 쓰면서 name도 함께 채운다(두 곳에 쓰기). 그리고 기존 행의 빈 full_name을 한 번 훑어 채워둔다(백필). 이래야 공존하는 동안 어느 버전이 만든 데이터든 서로 보인다 - 안 그러면 신버전이 만든 행을 구버전이 못 본다.
  3. 구버전이 완전히 사라진 뒤, 다음 배포에서 신버전이 옛 name 쓰기를 멈추고 컬럼을 지운다.

핵심은 한 배포에서 추가와 삭제를 같이 하지 않는다는 것이다. 무중단으로 앱을 바꾸려면, 그 앱이 만지는 데이터도 무중단으로 바뀌게 만들어야 한다. 앱만 겹칠 줄 알아서는 부족하다.

되돌리는 속도가 진짜 값이다

전략들을 훑고 나면 공통된 축이 하나 보인다. 잘못됐을 때 얼마나 빨리 되돌리나.

무중단 배포의 값을 “안 끊긴다”로만 보면 절반만 본 것이다. 배포가 위험한 진짜 이유는 신버전이 망가졌을 수 있어서고, 그때 필요한 건 빠른 롤백이다. 각 전략을 이 축으로 다시 세워보면 이렇게 갈린다.

전략되돌리는 법롤백 속도
재생성구버전을 다시 배포느리다 (다시 뜨는 시간 + 공백)
롤링바꾼 만큼 되돌린다중간 (진행된 만큼 걸린다)
블루그린스위치를 도로 파랑으로거의 즉시
카나리비율을 0%로즉시, 게다가 소수만 겪음

블루그린과 카나리가 비싼 이유(두 배 자원, 트래픽 분할 장비)는 결국 이 즉시성을 사는 값이다. 자원을 더 쓰는 대신, 문제를 발견한 순간 되돌릴 상태가 이미 옆에 준비돼 있다.

그래서 무중단 배포는 “새것을 매끄럽게 내보내는 기술”이라기보다 **“틀렸을 때 싸고 빠르게 되돌릴 수 있게 준비해두는 기술”**에 가깝다.

정리

배포는 구버전과 신버전을 잠깐 공존시키는 일이고, 전략들은 그 공존을 어떻게 다루느냐의 선택이다.

전략산다판다
재생성단순함·완전한 격리다운타임
롤링공백 없음·적은 추가 자원버전이 섞이는 시간·느린 롤백
블루그린섞임 없는 전환·즉시 롤백두 배의 자원
카나리잘라낸 폭발 반경긴 배포 시간·트래픽을 쪼갤 앞단

정답인 전략은 없다. 무중단이 필요 없는 서비스에 블루그린을 얹으면 자원만 두 배로 태우는 것이고, 사용자가 몰리는 서비스에 재생성을 쓰면 배포할 때마다 사람들이 에러를 본다.

고를 때 던지는 질문도 결국 이 블로그가 계속 던진 그것이다. 무중단이 나에게 얼마짜리 문제인가, 그리고 틀렸을 때 얼마나 빨리 되돌려야 하나. 그 두 답이 전략을 정한다.

관련 글