링커드 활용 쿠버네티스의 무중단 배포

Ivan (이반) Porta
14 min readOct 6, 2024

애플리케이션 제작을 시작하면 이전 환경에서의 애플리케이션과 자동화의 안정성과 상관없이 초조하기 마련이다. 예상치 못하게 주요 고객을 혼란하게 하는 것– 주요 고객을 떠나가게 할 수 있다는 것 -은 매우 위험하다. 따라서 많은 기업들은 여전히 정규 시간외 수동 배포를 하고 있으며, 모든 수동 단계에 대한 자세한 설명서를 따른다. 이렇게 하면 직접적인 비즈니스 영향을 최소화할 수 있는 반면 인적 오류에 취약하다. 엔지니어는 산만해지거나 주요 단계를 놓칠 수 있고, 작은 실수가 중요한 문제-자동화가 방지하고자 하는 것-를 야기할 수 있다.

반면에 페일페스트(fail-fast) 접근 방법을 적극적으로 수용하는 회사도 있다. 예를 들면, 넷플릭스는 제작 환경에서 시미언 아미(Simian Army)를 실행하여 모든 것이 예상대로 작동하도록 하고, 수작업자는 단계를 나누게 된다. 단, 이러한 신뢰 수준에 도달하려면 조직 성숙도와 시간이 필요하다.

최신 제작 배포 전략은 자동화와 지속적 인도 이외에 블루그린 배포, 카나리 배포 그리고 점진적 롤아웃 등의 배포 기법을 도입한 최신 도구를 사용하여 이러한 문제를 다룬다. 이러한 전략은 중단 시간을 줄이고, 제작 부하를 한층 부드럽고 확실히 이동시켜 준다. 관리자가 이러한 접근 방법을 채택하도록 설득하려면 시간이 걸리지만 개념 증명(POC)과 채택 계획을 통하여 해당 조직이 이러한 목표를 달성하고, 엔지니어와 관리자를 안심시킬 수 있다.

이 논문을 통하여 링커드를 활용하는 쿠버네티스 환경에서 카나리 출시, A/B 시험 그리고 블루그린 배포와 같은 최신 배포 전략을 실행하는 방법을 설명하고 논증한다.

트래픽 관리와 링커드

쿠버네티스는 기본적으로 게이트웨이 API의 HTTPRoute 자원을 통하여 타임아웃, 재시도 그리고 미러링과 같은 트래픽 관리 기능을 지원한다. 이 자원은 규칙과 매칭 조건을 정의하여 인커밍 트래픽을 처리할 백엔드 서비스를 결정한다. weight 필드를 사용하여 특정 백엔드로 송부된 요청의 일부를 명시하면 트래픽이 여러가지 버전이나 환경으로 나뉜다.

버전 2.14 이전에서 링커드 사용자는 httproutes.gateway.networking.k8s.io (구체적으로 말하면 httproutes.policy.linkerd.io) 사용자 지정 리소스 정의 (CRD) 다운스트림을 사용하여 요청을 라우트하는 방법을 링커드 사용자에게 알려야 했다. 링커드는 버전 2.14부터 기본적인 httproutes.gateway.networking.k8s.io로 지원을 확장하였다. 링커드 프록시는 사용 리소스와 상관없이 게이트웨이 API 또는 링커드 정책 HTTPRoute 리소스에 기반한 트래픽을 라우트하게 된다. 이러한 기능성은 gRPC 요청에도 적용된다.

주: 링커드는 설치 시 기본적으로 게이트웨이 API CRD 설치를 시도한다. 단, 해당 CRD가 클러스터에 이미 있으면 링커드 CRD 설치 시 헬름 차트나 CLI에서 enableHttpRoutesfalse로 설정하여 이 단계를 생략하도록 링커드에 지시할 수 있다.

$ kubectl get crds | grep gateway
grpcroutes.gateway.networking.k8s.io 2024-09-25T01:01:18Z
httproutes.gateway.networking.k8s.io 2024-09-25T01:01:18Z

이 논증에서는 NGINX를 인그레스 컨트롤러로 사용한다. NGINX 인그레스 컨트롤러는 기본적으로 인그레스에 명시된 서비스에 대한 종점 리소스를 검색하고, 트래픽을 pod의 IP 주소로 직접 보낸다. 단, 이 행위는 서비스 자체를 통하여 라우트된 트래픽에 적용되는 HTTPRoute 정책과 맞지 않는다. 이 문제를 해결하려면 NGINX 인그레스 콘트롤러를 설정하여 트래픽을 pod 종점으로 직접 송부하지 말고 서비스로 송부해야 한다. 인그레스 리소스에 nginx.ingress.kubernetes.io/service-upstream: "true" 표기를 추가하면 된다.

또한 링커드 프록시가 트래픽을 백엔드 서비스로 리디렉션하므로 그것을 인그레스 콘트롤러 pod로 주입해야 한다.

전반적인 트래픽 흐름은 다음과 같다.

  1. 사용자는 요청을 애플리케이션으로 송부한다.
  2. 인그레스 콘트롤러 pod에서 실행하는 링커드 프록시가 인바운드 트래픽을 가로채어 NGINX 인그레스 컨트롤러로 송부하여 처리한다.
  3. 인그레스 컨트롤러는 nginx.ingress.kubernetes.io/service-upstream: "true" 표기로 인하여 트래픽을/etc/nginx/nginx.conf에 위치한 업스트림 설정에 정의된 서비스로 송부한다.
  4. 발견 결과, 요청 그리고 링커드 목적지 서비스에서 검색된 연결 등의 인 메모리 상태에 기반한 목적지를 평가하는 링커드 프록시가 아웃바운드 트래픽을 가로챈다. 일정 타임아웃 기간 후에 캐쉬된 미사용 항목을 퇴거시킨다.
  5. 프록시는 목표가 결정되면 해당 라우팅 정책에 대한 링커드 정책 서비스를 조회하고 필요 시 해당 정책을 적용한다.
  6. 링커드 프록시는 최종적으로 요청을 정책이 정의한 백엔드로 송부한다(이 경우에는 서비스의 카나리 버전).

막후에서 무슨 일이 일어나는지 알았으므로 여러가지 가용 배포 방식과 추후 예상 상황에 대하여 알아보도록 한다.

카나리 배포

이 배포 전략은 제작 실행 중인 기존 안정적 버전과 함께 서비스(”카나리”라 함)의 신 버전을 배포하는 것을 포함한다. 트래픽의 일부를 카나리로 재전송한다. 개발팀은 이를 통하여 제작 트래픽으로 서비스를 신속하게 시험하고 최소한의 “폭발 반경”(변경으로 인하여 영향을 받는 사용자의 수)으로 문제를 확인할 수 있다. 해당 팀은 이러한 선별 단계에서 서비스로부터 주요 측정 기준도 수집한다. 이 결과에 따라 트래픽을 점진적으로 신 버전(예: 25%, 75%, 100%)으로 늘리거나 출시 중단을 결정할 수 있다.

쿠버네티스 게이트웨이 API를 사용하여 projects-vastaya-svc 서비스를 대상으로 하는 트래픽이 두 서비스로 분리되는 카나리 배포를 실행하는HTTPRoute 설정의 예는 다음과 같다.

  • projects-vastaya-svc: 트래픽의 10%를 수신한다.
  • projects-canary-vastaya-svc: 트래픽의 90%를 수신한다.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: project-vastaya-split
spec:
parentRefs:
- name: projects-vastaya-svc
group: core
kind: Service
namespace: vastaya
port: 80
rules:
- backendRefs:
- name: projects-vastaya-svc
port: 80
weight: 10
- name: projects-canary-vastaya-svc
port: 80
weight: 90

다음의 이미지에서 링커드 프록시가 트래픽을 두 서비스 모두에게 송부하는 것을 볼 수 있다. 서비스에 대한 인바운드 트래픽을 시각화하기 위하여 링커드의 viz 확장을 사용하고, 링커드를 카나리 배포와 안정적 배포 모두에게 주입하였다. 이를 통하여 해당 명령어를 사용하여 트래픽 분포를 관찰할 수 있었다.

linkerd viz top deploy/projects-canary-vastaya-dplmt -n vastaya

주: 트래픽 리디렉션을 하기 위하여 링커드 프록시를 목적지 pod에 주입할 필요가 없으나 서비스 성능에 대한 자세한 측정 기준을 수집하기 위하여 목적지 pod를 주입하였다.

블루그린 배포

블루그린 접근 방법은 카나리 배포와 유사하지만 더 과감한 접근 방법을 취한다. 트래픽의 증분 일부를 점진적으로 송부하는 대신에 구(블루) 버전과 신(그린) 버전을 모두 병행하여 실행한다. 단, 어떤 시점이든지 한 버전만 활성화되고 사용자 접근을 허용한다.

주요한 차이점은 필수 조정을 하는 동안 신 버전(그린)은 비활성화되고 사용자로부터 숨겨져서 안정성과 신뢰성을 확보한다는 것이다. 신 버전의 성능에 대한 확신이 있을 경우 단일 조정 전환으로 모든 트래픽을 교환한다. 이 접근 방법을 취하면 중단 시간을 최소화하고 문제 발견 시 신속한 롤백을 할 수 있다.

블루그린 전략은 사용자가 트래픽이 점진적으로 바뀔 때 두 버전 모두를 활발하게 접근하는 카나리 배포와 대조적으로 신 버전이 제작용으로 완전히 준비될 때까지 신 버전을 사용하지 않는다.

이 경우에는 트래픽 가중치를 0에서 1로 변경하여 블루그린 배포를 실행하고 모든 트래픽을 신 버전으로 송부하게 된다. HTTPRoute 설정의 예는 다음과 같다.

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: project-vastaya-split
spec:
parentRefs:
- name: projects-vastaya-svc
group: core
kind: Service
namespace: vastaya
port: 80
rules:
- backendRefs:
- name: projects-vastaya-svc
port: 80
weight: 0
- name: projects-canary-vastaya-svc
port: 80
weight: 1

A/B 시험

A/B 시험은 동일한 환경의 두 버전을 실행하여 전환율, 성능 그리고 사용자 참여도와 같은 측정 기준을 수집하는 실험 방법이다. 카나리 배포와 유사하게 서비스의 여러가지 버전을 비교할 수 있지만 대상 사용자 그룹으로부터 특정 자료를 수집하는 데 초점을 둔다.

A/B 시험에서 두 번째 버전(“B” 버전)은 위치, 장치 형태, 사용자 행동 또는 기타 요소와 같은 선결 기준이 정의한 한 개 이상의 사용자 그룹을 대상으로 한다. 이 방법은 사용자 경험(UX) 설계 시 널리 쓰인다. 예를 들면 친구에게는 보이지 않지만 넷플릭스에서는 보이는 것이나 애플리케이션 인터페이스의 미묘한 변화를 인지할 수 있을 것이다.

이 경우에 추가 필터를 HTTPRoute 에 추가하면 가능하다. 다음 설정에서 다음 사항을 실행하게 된다.

  • matches 섹션을 사용하여 로켈을 한국어(Accept-Language: ko.*) 로 설정하고 파이어폭스를 웹브라우저(User-Agent: .*Firefox.*)로 사용하는 사용자의 요청을 확인한다.
  • 트래픽은 이 사용자에 대해서 안정적 서비스(projects-vastaya-svc)와 카나리 서비스(projects-canary-vastaya-svc)간에 균등하게 나뉘며, 각각은 트래픽의 50% 를 각각 수신한다.
  • 기타 사용자에 대해서는 트래픽을 모두 안정적 서비스로 송부한다.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: project-vastaya-traffic-split
namespace: vastaya
spec:
parentRefs:
- name: projects-vastaya-svc
group: core
kind: Service
namespace: vastaya
port: 80
rules:
- matches:
- headers:
- name: "User-Agent"
type: RegularExpression
value: ".*Firefox.*"
- name: Accept-Language
type: RegularExpression
value: "en-US.*"
backendRefs:
- name: projects-vastaya-svc
port: 80
weight: 10
- name: projects-canary-vastaya-svc
port: 80
weight: 90
- backendRefs:
- name: projects-vastaya-svc
port: 80

이러한 설정의 실행을 통하여 대상 사용자의 50%를 카나리 버전으로 라우팅하고 나머지 사용자는 안정적 서비스를 계속 사용하게 하여 A/B 시험을 할 수 있다. 이를 통하여 특정 측정 기준을 수집하고, 지정 사용자 세그먼트에 대한 신 버전의 성능을 평가할 수 있다.

그림자 배포(미러드 배포)

미러드 배포로도 알려진 그림자 배포에서는 서비스의 신 버전이 백그라운드에서 실행하고 현실 트래픽의 복사본을 수신한다. 주(안정적) 서비스의 반응만을 고려하므로 사용자는 영향을 받지 않으며, 신 버전의 반응은 무시된다. 이 방법을 통하여 개발팀은 신 서비스를 제작 트래픽에 대하여 시험하여 신 서비스가 사용자에게 영향을 미치지 않고 현실 조건 하에서 어떻게 움직이는지 관찰할 수 있다.

링커드는 현재 이 기능을 완전하게 지원하지 않지만 개발팀은 해당 사항에 대하여 활발히 작업 중이다. GitHub 판을 통하여 상황을 파악할 수 있다(링커드 판 #11027).

이 기능이 사용 가능 시 게이트웨이를 설정하지 않고 다음 설정을 적용할 수 있으며, 링커드 프록시가 뒤처리를 하게 된다. projects-vastaya-svc 서비스로 송부된 트래픽을projects-canary-vastaya-svc 로 미러링 하지만 사용자는projects-vastaya-svc의 반응만을 고려하게 된다.

HTTPRoute설정의 예는 다음과 같다.

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: project-vastaya-traffic-split
namespace: vastaya
spec:
parentRefs:
- name: projects-vastaya-svc
group: core
kind: Service
namespace: vastaya
port: 80
rules:
- backendRefs:
- name: projects-vastaya-svc
port: 80
weight: 0
filters:
- type: RequestMirror
requestMirror:
backendRef:
name: projects-canary-vastaya-svc
port: 80

참고 자료

--

--

Ivan (이반) Porta
Ivan (이반) Porta

Written by Ivan (이반) Porta

Senior DevOps Engineer | Terraform Associate | Certified Argo Project Associate

No responses yet