무중단 배포와 쿠버네티스에서의 구현 방법

Hoonkii·2022년 1월 30일
0
post-custom-banner

오늘은 무중단 배포 전략 3가지와 각 전략들이 쿠버네티스에서 어떻게 구현될 수 있는지 간단히 다룰 것이다. 마이크로 서비스 아키텍처에 관심이 있었을 때 무중단 배포 전략들의 컨셉들은 이해하였는데Rolling Update 이외에는 k8s문서를 읽어보니 직접적으로 제공하지 않았던 것 같아서, 그 부분을 공부하고 관련 내용을 포스팅하고자 한다.

무중단 배포에는 Rolling Update, Blue-Green, Canary 배포 세 가지 방법이 있다.

  • Rolling Update

Rolling update는 구버전의 pod 집합에서 pod집합을 하나씩 제거하고 새 버전의 pod을 하나씩 추가하는 과정을 반복한다. Deployment 의 배포 전략에 아무것도 설정하지 않으면 기본적으로 Rolling Update로 설정된다.

Rolling Update에는 maxUnavailable, maxSurge라는 두가지 파라미터가 있다.
MaxUnavailable은 롤링 업데이트 프로세스 중에 사용할 수 없는 최대 파드의 수를 지정하는 필드이다. 절대 숫자 혹은 의도한 파드 비율(ex: 10%)로 지정이 가능하다.

위에 설명에서는 구 버전의 pod집합을 하나씩 제거한다고 하였지만 실제로는 maxUnavailable 에 지정된 값만큼 이전 버전의 pod들을 바로 제거하고 새로운 pod들을 생성한다.

MaxSurge는 의도한 파드 수에 대해 생성할 수 있는 최대 파드의 수를 지정하는 필드이다. 이 값 역시 절대 숫자 혹은 의도한 파드 비율(ex: 10%)로 지정이 가능하다.

이 값들은 노드 수와 pod 스케줄링 조건들을 잘 생각해서 계산하여 넣어야 한다. 일례로 노드 2개에 pod 2개가 있는 상황에서 노드 1개에 pod 1개만이 스케줄링 될 수 있는 anti-affinity 조건이 존재하였다. 이 상황에서 Rolling Update의 maxUnavailable, maxSurge를 기본 값으로 지정하면 두 값이 0이 된다. 기존 pod들이 죽지 않으니 새로운 pod이 생성될 수 없어 롤링 업데이트가 정상적으로 수행되지 않았다. (이 때 스테이징이 배포가 안되어서 당황했었다.. ㅠ)

YAML 예시는 다음과 같다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: application-deployment
spec:
  selector:
    matchLabels:
      app: application
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  • Blue-Green

롤링 업데이트는 구 버전과 새 버전이 공존하는 시간이 발생하는 문제가 있다. 여담으로 이 문제를 직접 경험해본 적이 있는데 사내 CI/CD 파이프라인은 롤링 업데이트가 끝나지 않았는데 브라우저 테스트를 진행하도록 되어 있었다. 그러다 보니 간혹 API 변경 점이 있을 때 브라우저 테스트가 실패로 떠서 FE 개발자가 원인파악하는데 애를 먹은 적이 많다. 결론은 구 버전과 새 버전이 공존해서 발생하는 문제였고 CD 파이프라인을 롤링 업데이트가 끝난 이후 브라우저 테스트를 진행하도록 수정하였다.

자 이 문제를 해결하기 위해 사용되는 방법이 블루 - 그린 배포이다. 이는 서버를 새 버전과 구 버전으로 2세트를 마련하고, 이를 한꺼번에 교체하는 방법이다.

yaml 예시를 보자.

apiVersion: apps/v1
kind: Deployment
metadata:
  name : application-deployment-blue
spec:
  replicas: 4
  selector:
    matchLabels:
      app: application
      color: blue
  template:
    metadata:
      labels:
        app: application
        color: blue
    spec:
      containers:
        - name: application
          image: gcr.io/project/application:0.1
          ports:
            - containerPort: 8080
apiVersion: v1
kind: Service
metadata:
  name: app-service
spec:
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: application
    color: blue

application이라는 pod이 존재하며 0.1 버젼이 blue로 배포된 상황이다. 그리고 pod은 deployment로 관리되며 연결된 서비스가 있다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name : application-deployment-green
spec:
  replicas: 4
  selector:
    matchLabels:
      app: application
      color: green
  template:
    metadata:
      labels:
        app: application
        color: green
    spec:
      containers:
        - name: application
          image: gcr.io/project/application:0.2
          ports:
            - containerPort: 8080

이제 위와 같이 0.2 버젼의 deployment를 띄운다. 그리고 아래와 같이 blue-green.yaml 을 작성하고,

spec:
  selector:
    color: green
$ kubectl patch service app-service -p "$(cat blue-green.yaml)"

kubectl patch service 명령어를 통해 app-service가 바라보고 있는 label selector를 바꿔주면 된다. 이제 서비스는 새로운 pod 집합 (green) 을 바라볼 것이다! (갓 k8s..)

아직까지 우리 사내 서비스는 blue-green 배포를 도입하고 있지는 않다. 서비스 특성도 그렇고 아직까지는 사용자들이 어느 때나 “지속적으로” 유입되지 않기 때문에 굳이 도입할 필요는 없다고 생각했다. (Rolling Update로도 충분!)

그러나 서비스 사용자가 많고 두 버전의 공존 없이 한꺼번에 업데이트가 필요하다면 blue-green을 고려해볼만 하다고 느꼈다. 노드 수를 똑 같이 늘리고 blue-green을 사용하면 업데이트 도중 트래픽 처리량이 떨어지지도 않으며 한번에 버전 업데이트를 할 수 있기 때문이다. 또한 문제가 생겼을 때 롤링 업데이트보다 훨씬 롤백이 빠르고 유연하다.

  • Canary


카나리 배포는 특정 서버나 소수의 유저들에게만 새로운 버전을 배포하고 추후 안전하다는 판단이 되면 그 후에 모든 서버들에 새로운 배전을 배포하는 방식이다. 이 기법은 A/B 테스트와 오류율 및 성능 모니터링에 유용하다고 한다.

yaml예시를 보자. Blue-Green 배포와 비슷하게 라벨을 이용한다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: application-deployment
  labels:
    app: application
    version: stable
spec:
  replicas: 4
  selector:
    matchLabels:
      app: application
      version: stable
  template:
    metadata:
      labels:
        app: application
        version: stable
    spec:
      containers:
      - name: application
        image: gcr.io/project/application:0.1
        ports:
        - containerPort: 8080

위와 같이 stable 버전의 애플리케이션이 있다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: application-deployment-canary
  labels:
    app: application
    version: canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: application
      version: canary
  template:
    metadata:
      labels:
        app: application
        version: canary
    spec:
      containers:
      - name: application
        image: gcr.io/project/application:0.2
        ports:
        - containerPort: 8080

이제 카나리 버전의 deployment를 위와 같이 작성한다.

apiVersion: v1
kind: Service
metadata:
  labels:
    app: application
  name: app-service
  namespace: default
spec:
  ports:
  - nodePort: 30880
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: application
  type: NodePort

자 서비스를 위와 같이 작성하면 이제 레플리카 개수 비율로 번갈아 가며 요청이 실행될 것이다. 이게 가능한 이유는 서비스의 selector app: application을 지정하였고, 위 두 deployment는 동일한 app selector를 가지기 때문이다. (굉장히 쉽다.)

무중단 배포 방법과 쿠버네티스로 어떻게 그것들을 구현하는지 알아보았다. 우리가 운영하는 서비스의 특징, 배포 시 요구사항들을 잘 고려해서 어떤 방법을 쓸지 잘 결정해야할 것 같다.

profile
개발 공부 내용 정리
post-custom-banner

0개의 댓글