DevOps/Kubernetes

[Kubernetes] Jenkins를 활용한 Blue/Green 배포 구현

연향동큰손 2025. 8. 5. 11:50

 

 

 

 

kubernetes-anotherclass-sprint2/2213 at main · yangwoohyeon/kubernetes-anotherclass-sprint2

[⚓쿠버네티스 어나더 클래스 (지상편) - 📗Sprint2]. Contribute to yangwoohyeon/kubernetes-anotherclass-sprint2 development by creating an account on GitHub.

github.com

 

Jenkins와 쿠버네티스를 이용하여 Blue/Green 배포를 진행해봤다.

 

Blue/Green 배포의 특징

  • 무중단 배포 가능
  • 운영환경에서 테스트 가능
  • 빠른 롤백 지원
  • 새로운 배포본의 문제점 발견시 즉시 안정된 버전으로 복구 가능해 안정적인 서비스 운영 가능

 

<Jenkinsfile>

pipeline {
    agent any

    parameters {
        // GitHub  사용자명 입력
        string(name: 'GITHUB_USERNAME',  defaultValue: '', description: 'GitHub  사용자명을 입력하세요.')
    }

    environment {
        // 전역값을 넣어 두시면 위 parameters 입력이 필요 없어요. (전체 Jenkinfile에서 해당 내용을 모두 수정해 놓으면 좋습니다.)
        // GITHUB_USERNAME = ""

        // 아래 부분 수정(x)
        GITHUB_URL = "https://github.com/${GITHUB_USERNAME}/kubernetes-anotherclass-sprint2.git"
        CLASS_NUM = '2213'
    }

    stages {

        stage('Username 확인') {
            steps {
                script {
                    if (!env.GITHUB_USERNAME?.trim()) {
                        error "[파라미터와 함께 빌드]에 GITHUB_USERNAME를 본인의 username으로 입력해 주세요! 매번 입력이 번거롭다면 Jenkinsfile에서 parameters에 입력 항목을 삭제 하시고 environment에 전역값을 넣은 후 해당 조건문은 삭제해 주세요. "
                    }
                }
            }
        }

        stage('릴리즈파일 체크아웃') {
            steps {
                checkout scmGit(branches: [[name: '*/main']],
                        extensions: [[$class: 'SparseCheckoutPaths',
                                      sparseCheckoutPaths: [[path: "/${CLASS_NUM}"]]]],
                        userRemoteConfigs: [[url: "${GITHUB_URL}"]])
            }
        }

        stage('쿠버네티스 Blue배포') {
            steps {
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/namespace.yaml"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/configmap.yaml"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/secret.yaml"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/service.yaml"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/deployment.yaml"
            }
        }

        stage('배포 시작') {
            steps {
                input message: '수동배포 시작', ok: "Yes"
            }
        }

        stage('쿠버네티스 Green배포') {
            steps {
	        	sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/green/deployment.yaml"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/green/service.yaml"
            }
        }

        stage('전환여부 확인') {
            steps {
                script {
                    returnValue = input message: 'Green 전환?', ok: "Yes", parameters: [booleanParam(defaultValue: true, name: 'IS_SWITCHED')]
                    if (returnValue) {
                        sh "kubectl patch -n anotherclass-221 svc api-tester -p '{\"spec\": {\"selector\": {\"blue-green-no\": \"2\"}}}'"
                    }
                }
            }
        }

        stage('롤백 확인') {
            steps {
                script {
                    returnValue = input message: 'Blue 롤백?', parameters: [choice(choices: ['done', 'rollback'], name: 'IS_ROLLBACk')]
                    if (returnValue == "done") {
                        sh "kubectl delete -f ./${CLASS_NUM}/deploy/k8s/blue/deployment.yaml"
                        sh "kubectl delete -f ./${CLASS_NUM}/deploy/k8s/green/service.yaml"
                        sh "kubectl patch -n anotherclass-221 svc api-tester -p '{\"metadata\": {\"labels\": {\"version\": \"2.0.0\"}}}'"
                        sh "kubectl patch -n anotherclass-221 cm api-tester-properties -p '{\"metadata\": {\"labels\": {\"version\": \"2.0.0\"}}}'"
                        sh "kubectl patch -n anotherclass-221 secret api-tester-postgresql -p '{\"metadata\": {\"labels\": {\"version\": \"2.0.0\"}}}'"
                    }
                    if (returnValue == "rollback") {
                        sh "kubectl patch -n anotherclass-221 svc api-tester -p '{\"spec\": {\"selector\": {\"blue-green-no\": \"1\"}}}'"
                    }
                }
            }
        }
    }

}

 

 

1. 쿠버네티스 Blue 배포

Blue 환경(V1) 관련 네임스페이스, ConfigMap, Secret, Service, Deployment YAML을 순서대로 쿠버네티스에 적용한다.

 

<Blue deployment.yaml>

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: anotherclass-221
  name: api-tester-1
  labels:
    part-of: k8s-anotherclass
    component: backend-server
    name: api-tester
    instance: api-tester
    version: 1.0.0
    managed-by: kubectl
spec:
  selector:
    matchLabels:
      part-of: k8s-anotherclass
      component: backend-server
      name: api-tester
  replicas: 2
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        part-of: k8s-anotherclass
        component: backend-server
        name: api-tester
        instance: api-tester
        version: 1.0.0
        blue-green-no: "1"
    spec:
      nodeSelector:
        kubernetes.io/hostname: k8s-master
      containers:
        - name: api-tester-1
          image: 1pro/api-tester:v1.0.0
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
              name: http
          envFrom:
            - configMapRef:
                name: api-tester-properties
          startupProbe:
            httpGet:
              path: "/startup"
              port: 8080
            periodSeconds: 5
            failureThreshold: 24
          readinessProbe:
            httpGet:
              path: "/readiness"
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: "/liveness"
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          resources:
            requests:
              memory: "100Mi"
              cpu: "100m"
            limits:
              memory: "200Mi"
              cpu: "200m"
          volumeMounts:
            - name: secret-datasource
              mountPath: /usr/src/myapp/datasource
      volumes:
        - name: secret-datasource
          secret:
            secretName: api-tester-postgresql

위 코드는 Blue의 deployment.yaml 파일이다.

 blue-green-no: "1" 레이블을 통해  Blue 환경임을 의미한다.

 

수동배포를 시작하고 Master Node에서 version 조회 시작해보면 v1의 api가 호출되는것을 확인 가능하다.

while true; do curl http://192.168.56.30:32214/version; sleep 1; echo '';  done;

 

 

2. 쿠버네티스 Green 배포

 

수동배포 시작을 하게 되면 Green배포가 되어서 v2 호출이 가능하다.

v1,v2 service 모두 사용 가능한 상태

 

이 상황에서는 v2로 완전히 대체된것이 아니라 v1 service를 사용하고 있지만 v2 service호출이 가능한 상황 이므로, 이 때 v2로 전환하기 전 테스트를 수행해보면서 안전하게 전환할 수 있다.

 

 

 

이제 전환 여부 확인에서 전환을 하게 되면 v1 service는 종료되고 v2 service만 남게된다.

v1 service는 종료되고 v2 service만 남은 상황

 

이 상황에서는 version조회를 하면 v2만 조회되는 것을 확인 가능하다.

 

 

만약 지금까지의 과정에서 오류가 생겼다면 Rollback을 통해 실행 전 상황으로 다시 돌아갈 수 있다.

 

 


자동배포 스크립트 사용

<Jenkinsfile>

pipeline {
    agent any

    parameters {
        // GitHub  사용자명 입력
        string(name: 'GITHUB_USERNAME',  defaultValue: '', description: 'GitHub  사용자명을 입력하세요.')
    }

    environment {
        // 전역값을 넣어 두시면 위 parameters 입력이 필요 없어요. (전체 Jenkinfile에서 해당 내용을 모두 수정해 놓으면 좋습니다.)
        // GITHUB_USERNAME = ""

        // 아래 부분 수정(x)
        GITHUB_URL = "https://github.com/${GITHUB_USERNAME}/kubernetes-anotherclass-sprint2.git"
        CLASS_NUM = '2214'
    }


    stages {

        stage('Username 확인') {
            steps {
                script {
                    if (!env.GITHUB_USERNAME?.trim()) {
                        error "[파라미터와 함께 빌드]에 GITHUB_USERNAME를 본인의 username으로 입력해 주세요! 매번 입력이 번거롭다면 Jenkinsfile에서 parameters에 입력 항목을 삭제 하시고 environment에 전역값을 넣은 후 해당 조건문은 삭제해 주세요. "
                    }
                }
            }
        }

        stage('릴리즈파일 체크아웃') {
            steps {
                checkout scmGit(branches: [[name: '*/main']],
                        extensions: [[$class: 'SparseCheckoutPaths',
                                      sparseCheckoutPaths: [[path: "/${CLASS_NUM}"]]]],
                        userRemoteConfigs: [[url: "${GITHUB_URL}"]])
            }
        }

        stage('쿠버네티스 Blue배포') {
            steps {
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/namespace.yaml"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/configmap.yaml"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/secret.yaml"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/service.yaml"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/blue/deployment.yaml"
            }
        }

        stage('자동배포 시작') {
            steps {
                input message: '자동배포 시작', ok: "Yes"
            }
        }

        stage('쿠버네티스 Green배포') {
            steps {
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/k8s/green/deployment.yaml"
            }
        }

        stage('Green 배포 확인중') {
            steps {
                script {
                    def returnValue
                    while (returnValue != "true true"){
                        returnValue = sh(returnStdout: true, encoding: 'UTF-8', script: "kubectl get -n anotherclass-221 pods -l instance='api-tester-2214',blue-green-no='2' -o jsonpath='{.items[*].status.containerStatuses[*].ready}'")
                        echo "${returnValue}"
                        sleep 5
                    }
                }
            }
        }

        stage('Green 전환 완료') {
            steps {
                sh "kubectl patch -n anotherclass-221 svc api-tester-2214 -p '{\"spec\": {\"selector\": {\"blue-green-no\": \"2\"}}}'"
            }
        }

        stage('Blue 삭제') {
            steps {
                sh "kubectl delete -f ./${CLASS_NUM}/deploy/k8s/blue/deployment.yaml"
                sh "kubectl patch -n anotherclass-221 svc api-tester-2214 -p '{\"metadata\": {\"labels\": {\"version\": \"2.0.0\"}}}'"
                sh "kubectl patch -n anotherclass-221 cm api-tester-2214-properties -p '{\"metadata\": {\"labels\": {\"version\": \"2.0.0\"}}}'"
                sh "kubectl patch -n anotherclass-221 secret api-tester-2214-postgresql -p '{\"metadata\": {\"labels\": {\"version\": \"2.0.0\"}}}'"
            }
        }
    }
}

 

동작 과정

  1. 깃허브 username 확인
  2. 깃허브 리포지토리에서 특정 경로만 불러오기
  3. 쿠버네티스 Blue배포 -> 네임스페이스, configmap, secret, service, deployment 리소스 생성
  4. Green 배포
  5. Green에 배포된 파드들의 레디 상태가 모두 true일 때까지 기다림 (5초 간격으로 체크)
  6. Green 배포가 완료되면 트래픽을 Blue에서 Green으로 변환
  7. 기존 Blue deployment 제거

트래픽이 Blue에서 Green으로 변환되는 모습