사전 카나리 배포
패턴

카나리 배포

gabury1

새 버전을 모두에게 한 번에 내보내지 않기로 하는 결정입니다. 먼저 아주 일부에게만 보내고 그 일부가 겪는 것을 지켜봅니다. 지켜본 것을 근거로 나머지까지 넓힐지 정합니다.

상세

카나리 배포는 프로덕션에 새 소프트웨어 버전을 들이는 위험을 줄이는 기법입니다. 전체 인프라에 한 번에 올려 모두가 쓸 수 있게 하는 대신, 작은 사용자 부분집합에 변경을 천천히 굴려 냅니다.

시작은 사용자가 아무도 라우팅되지 않는 인프라 일부에 새 버전을 배포하는 것입니다. 새 버전이 괜찮다고 판단되면 선택한 소수의 사용자를 그쪽으로 라우팅하기 시작합니다. 새 버전에 대한 확신이 쌓이면 더 많은 서버로 풀고 더 많은 사용자를 그쪽으로 보냅니다.

flowchart TD
    A[새 버전을 인프라 일부에 배포] --> B[사용자를 아직 보내지 않음]
    B --> C[선택한 소수 사용자만 라우팅]
    C --> D[확신을 쌓음]
    D --> E[더 많은 서버와 더 많은 사용자로 넓힘]

누가 새 버전을 보게 될지 고르는 전략은 여럿입니다. 단순한 전략은 무작위 표본을 쓰는 것입니다. 세상에 내놓기 전에 사내 사용자와 직원에게 먼저 릴리스하는 회사도 있습니다. 사용자의 프로필과 인구통계로 고르는 더 정교한 방식도 있습니다.

크고 분산된 상황에서는 라우터로 누구를 새 버전으로 돌릴지 정하는 대신 파티셔닝 전략을 쓰는 것도 흔합니다. 사용자가 지리적으로 분산되어 있으면 한 지역이나 특정 위치에 먼저 롤아웃합니다. 브랜드가 여럿이면 한 브랜드에 먼저 롤아웃합니다.

Google SRE(Site Reliability Engineering, 사이트 신뢰성 엔지니어링) 워크북은 이 결정이 왜 필요한지를 배포 파이프라인 쪽에서 설명합니다. 릴리스 파이프라인이 완전히 자동화되어 있어도, 실제 트래픽이 서비스에 닿기 전까지는 릴리스와 관련된 결함을 전부 감지할 수 없습니다. 일부 결함은 프로덕션에 도달합니다. 릴리스가 모든 곳에 즉시 배포되면 결함도 같은 방식으로 배포됩니다. 결함을 빨리 감지하고 해소할 수 있다면 그 상황도 받아들일 만합니다. 더 안전한 대안은 프로덕션 트래픽의 일부만 새 릴리스에 먼저 노출하는 것입니다. 카나리를 쓰면 배포 파이프라인이 결함을 가능한 한 빨리, 서비스에 미치는 영향은 가능한 한 적게 감지하게 됩니다.

이름이 낯설어도 관행 자체는 한동안 채택되어 왔습니다. 단계적 출시나 점진적 롤아웃이라 불리기도 합니다.

대가

한 번에 여러 버전의 소프트웨어를 관리해야 합니다. martinfowler.com 의 bliki 글은 프로덕션에 두 개보다 많은 버전을 동시에 돌리기로 정할 수도 있지만 동시 버전 수는 최소로 유지하는 것이 최선이라고 적습니다.

사용자의 컴퓨터나 모바일 기기에 설치되는 소프트웨어를 배포할 때는 이 결정을 쓰기가 어렵습니다. 업그레이드가 언제 일어나는지에 대한 통제가 적기 때문입니다. 그 소프트웨어가 백엔드와 통신한다면 ParallelChange 로 두 버전을 함께 지원합니다. 그동안 어떤 클라이언트 버전이 쓰이고 있는지 관찰합니다. 사용량이 일정 수준 아래로 떨어지면 그때 백엔드를 새 버전만 지원하도록 좁힙니다.

데이터베이스 변경도 주의를 요구합니다. 같은 글은 여기서도 ParallelChange 를 완화 기법으로 듭니다. 롤아웃 단계 동안 데이터베이스가 애플리케이션의 두 버전을 모두 지원하게 해 준다고 적습니다.

트래픽을 가중치로 가르면 어느 요청이 카나리로 갈지가 요청 단위로 무작위입니다. 같은 사용자가 매번 같은 버전에 닿는다는 보장이 사라집니다. 그래서 세션 어피니티를 따로 챙겨야 합니다. nginx ingress 는 ingress 를 카나리로 표시하면 카나리가 아닌 애너테이션을 메인 ingress 에서 상속해 무시합니다. 세션 어피니티에 관련된 애너테이션은 그 예외로 남습니다. 세션 어피니티가 무시되던 옛 카나리 동작으로 되돌리려면 affinity-canary-behavior 애너테이션을 legacy 값으로 둡니다.

배포 흐름이 카나리 기간에 묶입니다. Google SRE 워크북은 카나리 배포를 동시에 여러 개 굴릴 수는 있다고 적습니다. 다만 그러면 시스템 상태를 좇는 데 상당한 정신적 노력이 듭니다. 카나리가 서로 겹치면 신호 오염의 위험도 커집니다. 그래서 한 번에 하나의 카나리 배포만 굴리기를 강하게 권합니다.

이름의 출처

Danilo Sato 가 2014년 6월 25일 martinfowler.com 의 bliki 에 「Canary Release」를 올렸습니다. 이름과 그 어원이 함께 적힌 글입니다. 다만 이 글이 이름을 처음 붙인 자리인지는 확인하지 못했습니다. 같은 글도 이름이 낯설 뿐 관행은 한동안 채택되어 왔다고 적습니다.

그 글의 각주는 이름의 유래를 광부가 새장에 카나리아를 넣어 탄광 갱도로 데리고 내려가던 관행에서 찾습니다. 유독가스가 갱도로 새어 들면 광부보다 카나리아가 먼저 죽습니다. 카나리 릴리스도 프로덕션 인프라 전체나 사용자 기반 전체에 영향이 가기 전에 비슷한 형태의 조기 경보를 준다고 적습니다.

Google SRE 워크북도 같은 어원을 적습니다. 새는 사람보다 작고 숨을 자주 쉬어서 위험한 가스에 사람 관리자보다 먼저 취하게 된다고 풀어 씁니다.

같은 bliki 글의 더 읽을거리는 이 기법이 Jez Humble 과 Dave Farley 의 책 『Continuous Delivery』에 설명되어 있다고 적습니다.

예시

쿠버네티스 track 라벨

쿠버네티스 공식 문서는 같은 컴포넌트의 서로 다른 릴리스나 설정을 구분하는 데 라벨이 필요한 자리로 카나리 배포를 듭니다. 새 애플리케이션 릴리스의 카나리를 이전 릴리스 옆에 나란히 배포해 두면, 완전히 롤아웃하기 전에 새 릴리스가 실제 프로덕션 트래픽을 받을 수 있습니다. 릴리스를 가르는 라벨로 track 을 씁니다. 기본이 되는 안정 릴리스는 track 값이 stable 입니다.

YAML
name: frontend
replicas: 3
...
labels:
  app: guestbook
  tier: frontend
  track: stable
...
image: gb-frontend:v3

그리고 track 값이 canary 인 새 릴리스를 만듭니다. 두 파드 집합이 겹치지 않게 됩니다.

YAML
name: frontend-canary
replicas: 1
...
labels:
  app: guestbook
  tier: frontend
  track: canary
...
image: gb-frontend:v4

프론트엔드 서비스는 두 집합의 공통 라벨만 골라 둘 다에 걸칩니다. 셀렉터에서 track 라벨을 빼는 것이 그 방법입니다. 트래픽이 두 애플리케이션 모두로 향하게 됩니다.

YAML
selector:
  app: guestbook
  tier: frontend

안정 릴리스와 카나리 릴리스의 레플리카 수를 조절해 각 릴리스가 받을 실제 프로덕션 트래픽의 비율을 정합니다. 위 경우는 3 대 1입니다. 확신이 서면 안정 트랙을 새 애플리케이션 릴리스로 갱신합니다. 그리고 카나리 쪽을 없앱니다.

nginx ingress 카나리 애너테이션

nginx ingress 는 프로덕션 서비스가 아닌 다른 서비스로 소수의 요청을 보내는 일을 애너테이션 하나로 켭니다. 카나리 애너테이션을 붙이면 그 ingress 명세가 규칙에 따라 요청이 향할 대체 서비스 노릇을 합니다.

canary-weight 는 카나리 ingress 가 가리키는 서비스로 보낼 무작위 요청의 퍼센트를 정수로 받습니다. 가중치가 0 이면 이 카나리 규칙으로는 그 서비스에 아무 요청도 가지 않습니다. <weight-total> 만큼이면 모든 요청이 대체 서비스로 갑니다. <weight-total> 의 기본값은 100 이고 nginx.ingress.kubernetes.io/canary-weight-total 로 올릴 수 있습니다.

카나리 규칙은 우선순위 순서로 평가됩니다. canary-by-header 다음이 canary-by-cookie 이고 그다음이 canary-weight 입니다. canary-by-cookie 에 지정한 쿠키 값이 always 면 요청이 카나리로 라우팅되고, never 면 카나리로 라우팅되지 않습니다.

운영

관찰 기간

Google SRE 워크북은 카나리 기간을 고를 때 개발 속도를 함께 셈에 넣으라고 적습니다. 매일 릴리스한다면 카나리 배포를 한 번에 하나만 굴리면서 카나리를 일주일씩 둘 수는 없습니다. 주 단위로 배포한다면 꽤 긴 카나리를 할 시간이 있습니다. 하루 20번처럼 연속적으로 배포한다면 카나리 기간이 상당히 짧아야 합니다.

모집단

기본적인 평가에는 핵심적인 임계 조건을 감지하는 데 아주 큰 카나리 모집단이 필요하지는 않습니다. 다만 대표성 있는 카나리 과정은 여러 차원에 걸친 결정을 요구합니다.

차원 무엇을 정하나
크기와 기간 전체 배포를 대표할 만큼 크고 길어야 합니다. 질의 몇 개만 받고 카나리 배포를 끝내면, 기능이 서로 다른 다양한 질의로 특징지어지는 시스템에서는 쓸모 있는 신호가 나오지 않습니다
트래픽 양 시스템이 대표성 있는 표본을 처리했다고 볼 만큼, 그리고 그 입력에 부정적으로 반응할 기회를 가졌다고 볼 만큼 받아야 합니다
시각 성능 결함은 대개 높은 부하에서만 드러납니다. 한산한 시간대에 배포하면 성능과 관련된 결함을 건드리지 않을 가능성이 높습니다

무엇을 볼지

성공 비율은 카나리 평가에 아주 분명한 지표입니다. 다만 이 지표 하나만으로는 의미 있는 카나리 과정에 충분하지 않습니다. 모든 요청을 10배의 지연으로 처리하거나 그러면서 메모리를 10배 쓴다면 그것도 문제일 수 있습니다.

Google SRE 워크북은 카나리 지표를 SLI(Service Level Indicator, 서비스 수준 지표)에서 시작해 생각하기를 대체로 권합니다. 쓸 만한 SLI 는 서비스 건강도에 대한 귀속이 강한 편이라고 적습니다. 이미 SLO(Service Level Objective, 서비스 수준 목표) 준수를 좇으려고 SLI 를 재고 있다면 그 작업을 재사용할 수 있습니다.

지표마다 받아들일 만한 동작이 무엇인지를 제대로 정의해야 합니다. 그 정의가 지나치게 엄격하면 거짓 양성이 많아집니다. 문제가 없는 카나리 배포를 문제 있다고 보게 됩니다. 반대로 정의가 너무 느슨하면 결함이 있는 카나리 배포를 감지하지 못하고 넘길 가능성이 커집니다.

단계 값

Argo Rollouts 문서는 카나리 배포에 합의된 표준이 없다고 적습니다. 그래서 사용자가 카나리 배포를 어떻게 굴릴지 단계 목록으로 직접 적게 합니다. 각 단계는 새 레플리카셋이 안정 버전으로 승격되고 옛 버전이 완전히 축소되기 전에 평가됩니다.

가장 기본적인 단계가 setWeight 와 pause 입니다. setWeight 는 카나리로 보낼 트래픽의 퍼센트를 정합니다. pause 는 롤아웃을 멈춥니다. pause 안의 duration 이 설정되어 있으면 그 값만큼 기다린 뒤에 다음 단계로 갑니다. 설정되어 있지 않으면 그 일시정지 조건이 제거될 때까지 무기한 기다립니다.

YAML
strategy:
  canary:
    maxSurge: '25%'
    maxUnavailable: 0
    steps:
    - setWeight: 10
    - pause:
        duration: 1h
    - setWeight: 20
    - pause: {}

무기한 일시정지를 풀어 다음 단계로 넘길 때는 kubectl argo rollouts promote <rollout> 을 씁니다.

관련 항목

같은 계보의 다른 결정

블루-그린 배포 · 단계적 출시 · 롤아웃 · 롤백 · 기능 플래그 · A/B 테스트 · 지속적 배포 · ParallelChange · 이뮤터블 서버 · 피닉스 서버

트래픽을 나누는 방법

로드 밸런서 · 라우팅 · 세션 어피니티 · 쿠키

이것을 실제로 구현·채택한 제품

nginx · 쿠버네티스 · Argo Rollouts · setWeight · canary-weight

카나리와 안정판이 나뉘어 도는 쿠버네티스 자원

디플로이먼트 · 레플리카셋 · 파드 · 클러스터 · 프론트엔드

카나리를 승격할지 판단하는 기준

서비스 수준 지표 · 서비스 수준 목표 · 오류율 · 지연 · 성능 · 관측성 · 메트릭 · 거짓 양성

두 버전이 겹치는 동안

스키마 · 하위 호환 · 데이터베이스 · 클라이언트 버전 · 백엔드 · 배포

이것이 비롯된 배경

파이프라인 · 자동화 · 개발

다른 이름: canary release · canary deployment · canary releasing · canarying · 카나리 릴리스 · 카나리아 배포