Infra, DevOps/Kubernetes, Docker

[k8s] 쿠버로 무중단 배포 3종 모두 해보자 (argo없는 Blue Green)

philo0407 2024. 1. 30. 17:59

GPT가 그린 그림이다!

이 글은 아주 짧습니다 :)
실습 스크립트 및 코드는 이곳에 있습니다.

 

현재 블루 그린의 경우 무중단 배포가 되지 않습니다!

 

편의상 쿠버네티스를 k8s 혹은 쿠버라고 표시하겠습니다 :)

 

 

눈물 젖은 젠킨스와의 추억

젠킨스.. 이자식 ㅠㅠ

 

 

젠킨스에 셸 스크립트 작성하며 눈물 흘리며 지새웠던 지난 밤들의 추억...

 

쿠버를 사용하면 그 고통의 추억들을 보상받을 수 있다

 

아래는 내가 작성했던 젠킨스의 파이프라인이다

 

 

스케줄링 기능이 본 WAS 2대 중 한 대로 잡혀있는 구조라서 저렇게 다소 복잡하게 짜여진 것도 한 몫을 한다

 

일반적인 WAS 구조를 가정해서 조금 더 간소화하면 아래와 같다

 

 

하지만 쿠버네티스의 경우 아래와 같이 한 단계만이 존재한다

 

정확히는 yml파일에 배포할 container image를 넣고 아래와 같은 명령어를 기술하면 된다 (!!!)

kubectl apply -f deply-was.yml

 

 

많은 내용은 없으나 아래와 같이 간단하게 3가지 배포의 경우에 대한 설정 파일을 소개하고자 한다

 

롤링 배포

아래와 같이 롤링 배포가 있는데,

파란색이 기존에 운영되던 서비스고, 초록색이 배포할 서비스이다

그림은 일프로님의 수업자료를 차용했다

 

저렇게 새로운 버전을 배포 후 기존 서비스를 내리는 방식이 롤링 배포이다

 

- script -

더보기
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-was
spec:
  selector:
    matchLabels:
      pod-type: was
  replicas: 1
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        pod-type: was
    spec:
      containers:
      - name: container
        image: progress0407/app-ver1
        resources:
          requests:
            memory: 20Mi
          limits:
            memory: 300Mi

 

그러나 단순히 위처럼 구성 후 배포하면 아래와 같이 요청이 끊기는 구간이 생긴다

 

이유는 새로운 버전을 배포 후에 pod가 Running으로 표시되더라도

스프링 부트 앱이 실제로 트래픽을 받을 준비는 되지 않았기 때문이다

 

 

그래서 언제 트래픽을 받을 준비가 되었는지를 체크한 후 이후에 신규 배포된 pod로 트래픽을 옮길 수 있도록 해야 한다

쿠버에선 이것을 readiness probe가 담당한다

 

즉 아래 그림처럼 되어야 한다

 

아래는 바뀐 script이다

더보기
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-was
spec:
  selector:
    matchLabels:
      pod-type: was
  replicas: 1
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        pod-type: was
    spec:
      containers:
        - name: container
          image: progress0407/app-ver1
          resources:
            requests:
              memory: 20Mi
            limits:
              memory: 300Mi
          readinessProbe:
            httpGet:
              path: /actuator/health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 2
            periodSeconds: 2
            successThreshold: 2
            timeoutSeconds: 60

 

하단의 readinessProbe 영역을 추가했다

액츄에이터 헬스 체크를 60초 동안 2초 간격으로 확인하는데, 

첫 2초를 건너 뛰고 2회 성공하면 성공으로 간주하여 트래픽 연결을 한다

(정확히는 서비스의 셀렉터와 pod의 라벨과 연결을 시켜준다)

 

그러면 이제 아래처럼 안정적으로 무중단 배포가 된다

 

Blue Green 배포

Blue Green의 경우 무중단 배포가 되지 않아요 ! ㅠ
(애당초 kubectl만으로 블루그린을 만들지 않음!! )

일반적인 상황에서, argo cd를 사용합니다!

 

아래 그림처럼 파란 Pod군이 기존 배포된 Blue군이고

초록색이 Green군이다

Green군을 배포 할 때 Blue군을 유지하고 완료되면 종료시키는 방식이다

 

k8s에서 공식적으로 블루그린을 지원하지 않아서 어느 정도 직접 구현을 해야 한다

아래는 내가 구현한 블루 그린 스크립트이다

 

- script -

더보기
#!/bin/bash

k="minikube kubectl -- "

# deploy green
$k apply -f deploy-was-green.yml

# wait while deploying green
while true; do

    status=$($k get pods | grep deploy-was-green | awk '{print $3}')

    if [[ $status == "Running" ]]; then
        echo "Blue is Running!"
        break;
    fi;
    sleep 1;
done;

# delete blue traffic
$k delete svc svc-nodeport-blue

# allow green traffic
$k apply -f svc-nodeport-green.yml

# now, close blue traffic
$k delete deploy deploy-was-blue

# display blue's status
curl $(minikube ip):30000/actuator/health
echo ""
curl $(minikube ip):30000/version

 

다만 이 경우 레드니스 프로브 체크 로직을 넣기가 쉽지 않았는데,

덕분에 중단 배포가 된다... ㅎㅎ

 

 

 

 

카나리 배포

쿠버의 Ingress weight 기능을 사용하면 카나리 배포를 할 수 있다

아래 스크립트를 확인해보자

 

- script -

더보기

Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: canary-v2
  namespace: ns-deploy
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  ingressClassName: nginx
  rules:
  - host: www.app.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-v1
            port:
              number: 8080

 

 

App version 1

apiVersion: v1
kind: Pod
metadata:
  name: pod-v1
  labels:
    app: v1
  namespace: ns-deploy
spec:
  containers:
  - name: container
    image: progress0407/app-ver1
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v1
  namespace: ns-deploy
spec:
  selector:
    app: v1
  ports:
  - port: 8080

 

 

App version 2

apiVersion: v1
kind: Pod
metadata:
  name: pod-v2
  labels:
    app: v2
  namespace: ns-deploy
spec:
  containers:
  - name: container
    image: progress0407/app-ver2
---
apiVersion: v1
kind: Service
metadata:
  name: svc-v2
  namespace: ns-deploy
spec:
  selector:
    app: v2
  ports:
  - port: 8080

새 버전의 pod와 service를 배포한 후

canary-weight 속성을 통해서 이전 버전과 새 버전의 서비스의 트래픽을 분배할 수 있다

예를 들어 canary-weight가 10일 경우 새로운 버전의 트래픽은 전체 트래픽의 10%만 유입이 된다

 

그리고 ingress의 카나리 기능에 오해한게 있었다.

자동으로 weight값이 올라가지 않는다.

즉, ingress의 기능만으로 온전히 카나리 배포가 되지 않는다

 

그래서 나의 경우 아래처럼 shell로 한 번 더 카나리 배포를 구현했다

 

format_weight_json_str() {
    local ratio=$1
    echo "{\"metadata\":{\"annotations\":{\"nginx.ingress.kubernetes.io/canary-weight\":\"$ratio\"}}}"
}

ratio_list=(10 25 50 100)

for ratio in "${ratio_list[@]}"
do
   echo "current deploy's ratio is $ratio"
   $k patch ingress canary-deploy -p "$(format_weight_json_str $ratio)"
   sleep 5
done

 

10, 25, 50, 100의 비율로 신규 버전으로 배포되는 형태이다

그러면 아래처럼 로그가 찍힌다