Tech

카나리 배포 전략으로 숨고 서비스 안정적으로 배포하기

숨고에서 카나리 배포를 적용한 이유와 그 과정을 소개합니다.

2023-05-26 Ted Jeong

안녕하세요.

숨고 DevOps Engineer Ted 입니다.

이번 포스팅에서는 숨고에서 카나리 배포를 적용한 이유와 그 과정을 소개하려고 합니다.

배포의 종류

DevOps 문화가 잘 알려진 이후 CI/CD에 대한 이해도가 높아짐에 따라 다양한 배포 방법이 제안되었습니다. 그 중 롤링 업데이트, 블루-그린 배포, 카나리 배포의 세 가지의 배포 전략이 가장 잘 알려져 있으며, 이 외에도 A/B 테스트, 섀도우 배포, 재생성 배포 등 다양한 방법이 존재합니다. 이 중 가장 널리 알려진 세 가지 배포 전략에 대한 간략한 내용은 다음과 같습니다.

  • 롤링 업데이트 : 이전 버전과 신규 버전을 하나씩 교체하면서 점진적으로 배포하는 방법
  • 블루-그린 배포 : 이전 버전과 신규 버전을 모두 배포한 뒤 신규 버전으로 일제히 트래픽을 변경하는 방법
  • 카나리 배포 : 이전 버전과 신규 버전을 모두 배포하고 이전 버전과 신규 버전의 트래픽을 비율로 조정한 후 신규 버전으로 트래픽을 100% 전환하는 방법

카나리 배포 전략을 적용한 이유

위에서 설명한 배포 방식 중 숨고에서는 공통적으로 블루-그린 배포 전략을 사용하고 있습니다. 숨고 서비스는 마이크로서비스로 이루어져 있고, 수많은 서비스들이 배포되고 있기 때문에 블루-그린 배포 전략을 통해서 무중단 배포를 통해 서비스의 안정성을 유지하고 있습니다. 여기에 더해 최적의 생산성과 안정성을 위해서 레거시 서비스를 제거하는 미션인 테라포밍 프로젝트를 계획, 실행하고 있습니다. 원활한 프로젝트의 진행을 위해 기존 숨고의 배포 방식인 블루-그린 배포 방법을 기본적으로 유지하면서 테라포밍 프로젝트를 대상으로 카나리 배포 전략을 추가로 채택하게 되었습니다.

물론 현재 운영중인 서비스에서 배포 전략을 다르게 가져간다는 것이 쉬운 결정은 아니었습니다. 하지만 숨고는 코드 리뷰와 원활한 커뮤니케이션, 회의와 회고를 통해서 서비스를 견고하게 배포하고 있고, GitOps를 채택하여 사용하고 있기 때문에 안정적인 배포와 빠른 롤백이 가능한 환경이 갖추어져 있습니다. 거기에 더해 숨고에는 신기술들을 공부하고 적용해보는 개발 문화가 잘 정착되어 있기 때문에 보다 도전적으로 새로운 배포 전략을 도입해볼 수 있었습니다.

카나리 배포 적용 - 프록시 서버로 Nginx 를 선택한 이유

Soomgo Deploy Canary Diagram

숨고의 퍼블릭 엔드포인트는 대표적으로 2개가 열려있습니다. 각각 AWS Application Load Balancer(이하 ALB)와 AWS Network Load Balancer(이하 NLB)로 서비스하고 있고, 요청 경로별로 카나리 배포 전략을 이용할 수 있도록 하였습니다.

더 나아가 NLB 에서 추가로 카나리 배포 전략을 적용하여 이전 버전에서 신규 버전으로의 전환을 더욱 유연하고 안정적으로 할 수 있도록 아키텍처를 구성하였습니다.

ALB 에서 NLB (Proxy-01)

  • ALB는 Ingress의 설정 기반으로 생성되고 있는데, Ingress의 routing rule은 service만 타겟으로 지정이 가능합니다. service를 파드로 연결할 수 있고, 우리가 가장 잘 컨트롤 할 수 있는 파드의 구현체로 Nginx로 단순 프록시를 구현하였습니다.

NLB 에서 MSA (Proxy-02)

  • NLB 에서 MSA 로 카나리 배포를 구현할 수 있는 방법은 Istio, Kong, Nginx 등 다양한 방법이 있지만 러닝 커브와 비용을 고려하였을 때 숨고에는 Nginx가 가장 적합하다고 판단하였습니다.
  • 숨고의 모든 API의 호출은 API Gateway를 통해서 이루어지고 있습니다. 숨고에서 사용하는 API Gateway 로 Kong Gateway를 사용하고 있고, Kong Gateway에 구현된 플러그인을 반드시 통과하는 작업이 필요합니다.

공통적으로 두 프록시 서버에 Nginx를 사용하게 된 이유 중 하나는 바로 리소스 대비 트래픽 처리의 안정성이이었습니다. 실제로 CPU 50m 이하의 적은 리소스를 사용하였음에도 카나리 배포를 적용하는데 무리가 없는 수준의 퍼포먼스를 보여주었습니다.

nginx의 일일 CPU 사용률
nginx의 일일 CPU 사용률

적용 코드 예시

숨고에서는 AWS Load Balancer Controller 와 Ingress 를 이용해서 ALB 생성을 자동화하고 있고, 이를 잘 활용하면 카나리 배포 전략 적용을 유용하게 할 수 있습니다. AWS Load Balancer Controller - Traffic Routing 공식 문서를 보면 다양한 방법으로 라우팅 적용을 할 수 있는데, alb.ingress.kubernetes.io/actions, alb.ingress.kubernetes.io/conditions를 사용하여 카나리 버전에 가해지는 부하의 비율 조정이 가능합니다.

숨고에서 적용한 코드 예시를 보여드리겠습니다.

# mypath-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/actions.canary-service: |
      {
        "type":"forward",
        "forwardConfig":{
          "targetGroups":[
            {
              "serviceName":"legacy-svc",
              "servicePort":"80",
              "weight":99 # 80, 50, 20, 1, 0
            },
            {
              "serviceName":"proxy-01-svc",
              "servicePort":"80",
              "weight":1 # 20, 50, 80, 99, 100
            }
          ]
        }
      }
    alb.ingress.kubernetes.io/conditions.canary-service: |
      [
        {
          "field":"http-request-method",
          "httpRequestMethodConfig":{
            "Values":["GET", "POST"] # PUT, PATCH, DELETE, HEAD
          }
        },
        {
          "field":"path-pattern",
          "pathPatternConfig":{
            "values":["/mypath/"]
          }
        }
      ]
spec:
  rules:
  - host: soomgo.com
    http:
      paths:
      - path: /mypath
        pathType: ImplementationSpecific
        backend:
          service:
            name: canary-service
            port:
               name: use-annotation
      - path: /*
        pathType: ImplementationSpecific
        backend:
          service:
            name: legacy-svc
            port:
              number: 80
  • Ingress 는 위에서 언급드린 것처럼 metadata.annotations 에 actions, conditions 세트로 비율을 지정하거나 대상 경로 및 대상 경로의 메소드를 지정할 수 있습니다.

  • 경로별로 세트를 지정하거나 비율별로 세트를 지정한 후 실제 spec.rules.http.paths 에서 가져다 쓰는 방식도 좋은 전략이 됩니다.

Proxy-01 서비스와 연결된 파드의 Nginx 설정

server {
    listen *:80;
    set $proxy_pass_url https://api.soomgo.com;
    location / {
        proxy_pass $proxy_pass_url;
    }
}

Proxy-02 서비스와 연결된 파드의 Nginx 설정

# new-01-canary-1
upstream new-01-canary-1_GET {
    server new-01-svc weight=99;
    server api-svc;
}
... # 5, 10, 20, 50, 80
upstream new-01-canary-1_POST {
    server new-01-svc weight=99;
    server api-svc;
}
# new-01-canary-100
upstream new-01-canary-100_GET {
    server new-01-svc;
}
upstream new-01-canary-100_POST {
    server new-01-svc;
}
# new-02-canary-1
upstream new-02-canary-1_GET {
    server new-02-svc weight=99;
    server api-svc;
}
upstream new-02-canary-1_POST {
    server new-02-svc weight=99;
    server api-svc;
}
... # 5, 10, 20, 50, 80
# new-02-canary-100
upstream new-02-canary-100_GET {
    server new-02-svc;
}
upstream new-02-canary-100_POST {
    server new-02-svc;
}
... # new-03-canary, new-04-canary, ...
server {
    listen *:80;

    location ~ ^/mypath/.*/new/01 {
        if ( $request_method ~ "GET|POST" ) {
            proxy_pass http://new-01-canary-100_$request_method;
        }

        proxy_pass http://api-svc;
    }
    location ~ ^/mypath/.*/new/02 {
        if ( $request_method ~ "GET|POST" ) {
            proxy_pass http://new-02-canary-100_$request_method;
         }

        proxy_pass http://api-svc;
    }
    ... # new-03-canary, new-04-canary, ...
    location / {
        proxy_pass http://api-svc;
    }
}
  • Nginx 는 upstream 디렉티브를 이용해서 weight 키워드로 비율을 지정할 수 있습니다.
  • upstream 을 비율과 메소드 조합의 세트로 각각 정의하고 location 에서 사용합니다.
  • 로드 밸런싱 방식을 명시하지 않아서 기본 설정인 Round Robin 방식으로 트래픽을 골고루 처리합니다. 더 많은 로드 밸런싱 설정은 Nginx Load Balancer 공식 문서에서 찾아볼 수 있습니다.
  • 숨고에서는 트래픽 흐름의 비율을 0%, 1%, 5%, 10%, 20%, 50%, 80%, 100% 를 정의하고 사용 하였습니다.

공통적으로 Ingress, Nginx 설정 모두 weight 를 조정하면서 트래픽이 흘러가는 비율을 조정하여 카나리 배포 전략을 적용할 수 있습니다.

적용 과정

Soomgo Deploy Canary Process

카나리 배포를 위한 서비스 개발도 중요하지만 배포 이후 비율을 조정하고 모니터링 하는 과정 또한 중요합니다. 신규 버전으로 트래픽이 흘러갈 수 있도록 신규 버전으로의 트래픽 비율을 높이면서 진행하는데, 이 때 신규 버전에서의 처리량과 응답 상태 등 메트릭이 정상인지 면밀하게 살펴보아야 합니다.

  1. 카나리 배포 대상 선정 및 서비스 개발
  2. Pull Request → Approve → Merge 순서로 코드 리뷰 후 배포 준비
  3. 카나리 배포 진행
  4. 카나리 대상 서비스 상세 모니터링
  5. 신규 버전 서비스의 상태 판단 후 비율 조정하여 2~4의 과정을 반복

또한, 배포 이후에는 이전 포스팅에서 다루었던 New Relic을 통해 카나리 배포 대상을 경로별로 모니터링하고 있고, 비정상으로 판단되는 경우 즉시 이전 버전으로의 트래픽 비율을 100%로 설정한 후 다시 배포하여 서비스 장애를 최소화하고 있습니다.

현재 그리고 앞으로

카나리 배포 전략 적용의 최초 트리거는 앞서 말씀드렸듯 테라포밍 프로젝트였습니다. 카나리 배포 전략을 적용함으로써 우리는 보다 안정적으로 테라포밍 프로젝트를 시작할 수 있었고, 지금도 큰 이슈 없이 프로젝트가 무사히 진행되고 있습니다.

이번 글에서 소개한 방법 외에 카나리 배포 전략을 적용하는 방법은 다양합니다. Istio VirtualService나 ArgoCD Rollouts Canary Strategy, Kong Canary Release 등 여러 방법을 통해 숨고에 적용한 방식보다 더 편리하고 강력한 효과를 거둘 수도 있습니다.

하지만 이러한 기술들에 대해서 충분히 성숙되지 않은 상태로 섣불리 서비스에 적용한다면 카나리 배포의 장점을 온전히 발휘할 수 없을 것이라고 판단했습니다. 비록 이러한 기술들이 트렌드하고 더 좋은 퍼포먼스를 보여준다고 할지라도 현 시점의 우리에게 맞는 방법을 선택하는 것이 가장 좋은 방법이라고 생각합니다. 이 글을 읽는 분들께서도 자신의 환경에 맞춰 카나리 배포 전략을 적용하는 다양한 방법을 선택하여 효율적으로 서비스의 안정성을 높이실 수 있길 바랍니다.

긴 글 읽어주셔서 감사합니다.

참고 자료

Ted Jeong Soomgo DevOps Engineer
숨고의 안정적인 인프라를 위해 열심히 고민하며 노력하고 있습니다.