TheKoguryo's 기술 블로그

 Version 2024.04.01

1.7.1.2 DevOps 서비스의 Blue/Green 배포 전략으로 배포하기

DevOps 서비스를 통한 CI/CD 배포 자동화 하기

사전 준비 사항

DevOps 프로젝트 만들기

이전에 생성한 프로젝트를 그대로 사용하거나, 새롭게 프로젝트를 만듭니다.

애플리케이션 배포 자동화 하기

Code Repository를 사용하여 애플리케이션 코드 관리하기

샘플로 사전에 만든 Nginx 기반 웹앱을 사용하여 만들어 테스트하겠습니다.

  1. 코드 저장소 생성을 위해 왼쪽 메뉴에서 Code Repositories를 클릭합니다.

  2. Create repository를 클릭하여 저장소를 만듭니다.

    • Repository name: 예) oci-devops-oke-webpage
  3. 실제 개발 작업은 git 명령을 통해 개발 PC에서 진행하면 됩니다. 저장소 상세정보 위에 있는 Clone 버튼을 하면 Clone 명령어가 아래 그림처럼 뜨게 됩니다. 여기서는 Clone with HTTPS를 사용하겠습니다.

  4. 개발 PC에 복사한 주소를 사용해 git clone 명령어를 통해 복제합니다.

    git clone <YourClonewithHTTPS URL>
    
  5. 이때 사용자 인증이 필요합니다. HTTPS기반 사용자 인증시 아래 유저명 형식과 AuthToken을 사용합니다.

  6. 이미 개발된 소스를 가져와 Clone한 저장소로 옮깁니다.

    wget https://github.com/TheKoguryo/oci-devops-oke-webpage/archive/refs/tags/v2022.04.tar.gz
    tar -xvzf v2022.04.tar.gz --strip-components=1 -C oci-devops-oke-webpage/
    
  7. 코드를 Code Repository에 Push 합니다.

    cd oci-devops-oke-webpage
    git add .
    git commit -m "init"
    git push
    
  8. 코드 작성 및 반영 완료

    • Dockerfile, default.conf.template, html 폴더: nginx 기반 웹 앱의 이미지를 빌드 소스 파일입니다.
    • build_spec.yaml: DevOps 서비의 빌드 스테이지에서 사용하는 빌드 정의서입니다.
    • oci-oke-deployment.yaml: OKE에 배포하기 위해 정의된 Kubernetes Manifest 파일입니다. 이후 다시 설명하겠습니다.

    image-20220420164127962

Build Pipeline 만들기

CI/CD 중에 코드를 빌드하여 배포 산출물을 만드는 CI 과정에 해당되는 부분을 Build Pipeline을 통해 구성이 가능합니다.

  1. 프로젝트 페이지로 이동하여 왼쪽 메뉴의 Build Pipelines로 이동합니다.

  2. Create build pipeline을 클릭하여 파이프라인을 생성합니다.

    • Name: 예) webpage-build-pipeline
  3. 생성된 파이프라인을 클릭합니다.

  4. 그림과 같이 Stage를 추가하여 파이프라인 흐름을 구성할 수 있습니다. Add Stage를 클릭합니다.

Build Stage 만들기

  1. 빌드를 위해 먼저 Managed Build Stage를 추가합니다.

  2. Managed Build Stage 설정

    • Stage name: 예) build-stage

    • Build Spec File Path: 빌드 스크립트 경로를 지정합니다. 기본적으로 소스 루트에 있는 build_spec.yaml을 파일을 사용합니다.

    • Primary Code Repository: 빌드할 메인 소스가 있는 코드 저장소를 지정합니다.

      • 대상 소스 코드가 있는 저장소를 지정합니다.

        image-20220420165218490

  3. 설정된 Stage를 Add를 클릭하여 추가합니다.

  4. 테스트처럼 소스 코드상의 Build Spec의 정의가 필요합니다.

    • 개발한 oci-devops-oke-webpage 소스 코드의 root 경로에 build_spec.yaml을 이미 정의해 두었습니다. 정의한 내용을 확인합니다.

    • build_spec.yaml

      • outputArtifacts.output-image: 빌드 산출물인 컨테이너 이미지를 지칭합니다.
      • outputArtifacts.output-oci-oke-deployment: 빌드 산출물로 소스코드상에 있는 OKE 배포용 Manifest 파일입니다.
      • exportedVariables
        • IMAGE_PATH: 빌드되는 환경정보와, APP_NAME을 기반으로 OCIR 리파지토리를 계산합니다.
        • TAG: 편의를 위해 코드 Commit ID 또는 BUILD_ID 해쉬값으로 지정합니다.
      version: 0.1
      component: build
      timeoutInSeconds: 6000
      runAs: root
      shell: bash
      env:
        # these are local variables to the build config
        variables:
          defaultAppName: "webpage"
          defaultContextPath: "/webpage"
      
        # the value of a vaultVariable is the secret-id (in OCI ID format) stored in the OCI Vault service
        # you can then access the value of that secret in your build_spec.yaml commands
        vaultVariables:
      
        # exportedVariables are made available to use as parameters in sucessor Build Pipeline stages
        # For this Build to run, the Build Pipeline needs to have a BUILDRUN_HASH parameter set
        exportedVariables:
          - BUILDRUN_HASH
          - APP_NAME
          - IMAGE_PATH
          - TAG
          - CONTEXT_PATH
      
      # Its a native way to fetch artifacts from external or artifact repo or a file path to use before a stage.
      # More about buildspec formats - https://docs.oracle.com/en-us/iaas/Content/devops/using/build_specs.htm
      
      inputArtifacts: 
      
      steps:
        - type: Command
          name: "Init exportedVariables"
          timeoutInSeconds: 30
          command: |
            APP_NAME=$defaultAppName
            CONTEXT_PATH=$defaultContextPath
            echo $APP_NAME
            echo $CONTEXT_PATH      
      
        - type: Command
          name: "Build Source"
          timeoutInSeconds: 4000
          command: |
            echo no action      
      
        - type: Command
          name: "Define Image Tag - Commit ID"
          timeoutInSeconds: 30
          command: |
            COMMIT_ID=`echo ${OCI_TRIGGER_COMMIT_HASH} | cut -c 1-7`
            BUILDRUN_HASH=`echo ${OCI_BUILD_RUN_ID} | rev | cut -c 1-7`
            [ -z "$COMMIT_ID" ] && TAG=$BUILDRUN_HASH || TAG=$COMMIT_ID      
      
        - type: Command
          name: "Define Image Path"
          timeoutInSeconds: 30
          command: |
            TENANCY_NAMESPACE=`oci os ns get --query data --raw-output`
            REPO_NAME=$APP_NAME
            IMAGE_PATH=$OCI_RESOURCE_PRINCIPAL_REGION.ocir.io/$TENANCY_NAMESPACE/$REPO_NAME      
      
        - type: Command
          timeoutInSeconds: 1200
          name: "Build Container Image"
          command: |
            cd ${OCI_PRIMARY_SOURCE_DIR}
            docker build --pull --rm -t new-generated-image . --build-arg CONTEXT_PATH=$CONTEXT_PATH      
      
        - type: Command
          name: "Check exportedVariables"
          timeoutInSeconds: 30
          command: |  
            [ -z "$APP_NAME" ] && APP_NAME=unknown          
            [ -z "$IMAGE_PATH" ] && IMAGE_PATH=unknown    
            [ -z "$TAG" ] && TAG=unknown
            echo "APP_NAME: " $APP_NAME      
            echo "IMAGE_PATH: " $IMAGE_PATH
            echo "TAG: " $TAG
      
      outputArtifacts:
        - name: output-image
          type: DOCKER_IMAGE
          # this location tag doesn't effect the tag used to deliver the container image
          # to the Container Registry
          location: new-generated-image
      
        - name: output-oci-oke-deployment
          type: BINARY
          location: ${OCI_PRIMARY_SOURCE_DIR}/oci-oke-deployment.yaml      
      
  5. 빌드 스테이지가 완성되었습니다.

빌드 산출물을 저장할 Artifact 저장소 만들기

  1. 좌측 상단 햄버거 메뉴에서 Developer Services > Containers & Artifacts > Artifact Registry로 이동합니다.
  2. Create repository를 클릭하여 저장소를 추가합니다.
    • Name: 예) oci-hol-artifact-repository
    • Immutable artifacts: 테스트용으로 체크 해제

빌드 산출물을 OCIR 및 Artifact 저장소에 저장하기

  1. Build Pipeline 탭으로 이동합니다.

  2. 플러스 버튼을 클릭하여 build-stage 다음에 stage를 추가합니다.

  3. Delivery Artifact Stage를 선택합니다.

  4. stage 이름을 입력하고 Create Artifact를 선택합니다.

    image-20220420171453459

  5. Container image 유형으로 Artifact 추가합니다.

    • 이미지 경로: docker tag를 달때 사용하는 이미지 경로입니다. 직접 입력해도 되지만 여기서는 build-stage에서 넘어온 exportedVariable을 사용하여 아래와 같이 입력합니다.

      • Name: generated_image_with_tag
      • Image Path: ${IMAGE_PATH}:${TAG}

      image-20220420171635825

  6. 같은 방식으로 하나 더 추가 합니다.

    • Name: generated_image_with_latest
    • Image Path: ${IMAGE_PATH}:latest
  7. Kubernetes Manifest 파일을 앞서 생성한 Artifact Repository에 추가합니다.

    • Name: 예) oci-oke-deployment.yaml

    • Artifact path: oci-devops-oke-webpage/oci-oke-deployment.yaml

    • Version: ${BUILDRUN_HASH}

      image-20220420184107716

  8. Artifact 매핑

    • Associate Artifact에서 방금 추가한 3개의 Artifact에 실제 컨테이너 이미지 파일을 매핑해 줍니다. 앞서 build-stage에서 build_spec.yaml에서 정의한 outputArtifacts의 이름을 입력합니다.

      outputArtifacts:
        - name: output-image
          type: DOCKER_IMAGE
          # this location tag doesn't effect the tag used to deliver the container image
          # to the Container Registry
          location: new-generated-image
      
        - name: output-oci-oke-deployment
          type: BINARY
          location: ${OCI_PRIMARY_SOURCE_DIR}/oci-oke-deployment.yaml  
      

      image-20220421102046463

  9. 이제 delivery stage까지 추가하였습니다.

Blue/Green Deployment Pipeline 만들기

CI/CD 중에 빌드된 산출물을 가지고 실제 서버에 배포하는 CD 과정에 해당되는 부분을 Deployment Pipeline을 통해 구성이 가능합니다.

Blue/Green 전략으로 쿠버네티스에 배포하기 위해서는 배포 환경, 배포할 Kubernetes Manifest 파일, 배포에 사용될 Kubernetes Namespace 두 개가 필요합니다.

Kubernetes Environment 등록하기

  1. 앞서 등록한 OKE 환경이 없는 경우, 프로젝트 페이지로 이동하여 왼쪽 메뉴의 Environments로 이동하여 배포할 OKE 환경을 등록합니다.

Kubernetes에 배포할 manifest 파일 준비

  1. Kubernetes에 배포할 Stage 유형을 사용하기 위해서는 사전에 배포할 manifest yaml 파일을 준비해야 합니다.

  2. 앞서 실습과 같이 inline 형식으로 하는 방법도 있고, 이번에서 앞서 가져온 소스 프로젝트 파일에 Kubernetes Manifest 파일을 관리하고 사용할 수도 있습니다.

    • 사용할 샘플 Manifest 파일

      • Deployment, Service: 빌드된 컨테이너 앱을 배포할 Deployment와 Cluster IP기반 Service
      • Ingress: Blue/Green 또는 Canary 배포를 위해 필요합니다. 둘다 Nginx Ingress Controller를 기반으로 라우팅 규칙을 조절하여 해당 기능을 제공하고 있습니다.
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: ${APP_NAME}-deployment
      spec:
        selector:
          matchLabels:
            app: ${APP_NAME}
        replicas: 3
        template:
          metadata:
            labels:
              app: ${APP_NAME}
          spec:
            containers:
              - name: ${APP_NAME}
                # enter the path to your image, be sure to include the correct region prefix
                image: ${IMAGE_PATH}:${TAG}
                imagePullPolicy: Always
                ports:
                  - containerPort: 80
                    protocol: TCP
                readinessProbe:
                  exec:
                    command:
                    - cat
                  # For demo, see the start status easily about rolling updates.
                  initialDelaySeconds: 5
                  periodSeconds: 5
                livenessProbe:
                  exec:
                    command:
                    - cat
                  initialDelaySeconds: 5
                  periodSeconds: 5
                env:
                  - name: POD_NAMESPACE
                    valueFrom:
                      fieldRef:
                        fieldPath: metadata.namespace
      
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: ${APP_NAME}-service
      spec:
        type: ClusterIP
        ports:
          - port: 8080
            protocol: TCP
            targetPort: 80
        selector:
          app: ${APP_NAME}
      ---
      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: ${APP_NAME}-ingress
        annotations:
          kubernetes.io/ingress.class: "nginx"
          nginx.ingress.kubernetes.io/rewrite-target: /$2
      spec:
        rules:
        - http:
            paths:
              - path: ${CONTEXT_PATH}(/|$)(.*)
                pathType: Prefix
                backend:
                  service:
                    name: ${APP_NAME}-service
                    port:
                      number: 8080
      

Kubernetes에 배포할 네임스페이스 만들기

블루 앱이 배포될 네임스페이스, 그린 앱이 배포될 네임스페이스 필요합니다. 각 네임스페이스에 동일한 앱이 현재 버전, 신규 버전이 각각 배포되게 되고, 라우팅 설정을 통해 트래픽을 조절합니다.

  1. Cloud Shell을 통해 kubectl 명령이 실행 가능한 환경으로 접속합니다.

  2. 두 개의 네임스페이스를 생성합니다.

    • 예) ns-blue, ns-green

      kubectl create ns ns-blue
      kubectl create ns ns-green
      
  3. OCIR에서 이미지 가져오기

    OCIR 리파지토지(예시, webpage)가 사전에 없는 경우 Root Compartment에 Private 형태로 만들어집니다. OKE에서 가져오기 위해서는 각각 네임스페이스에 imagepullsecret을 사전에 생성하거나, 리파지토리를 미리 Public으로 생성합니다.

Blue/Green 전략으로 Kubernetes 배포 Stage 만들기

  1. 프로젝트 페이지로 이동하여 왼쪽 메뉴의 Deployment Pipelines로 이동합니다.

  2. Create pipeline을 클릭하여 파이프라인을 생성합니다.

    • Name: 예) webpage-bluegreen-deployment-pipeline
  3. 생성된 파이프라인을 클릭하여 Blue/Green Strategy Stage를 추가합니다.

    image-20220420185718429

  4. 배포 유형을 OKE로 선택하고, 배포환경 및 manifest 파일을 선택합니다.

  5. Blue/Green 배포를 위한 추가 설정을 합니다.

    • Namespace set: 앞서 만들 배포용 네임스페이스 2개를 입력합니다. 이전 버전이 있는 곳에 배포하고, 현재 버전으로 향한 트래픽을 새 버전이 있는 곳으로 변경하는 방식으로 2개의 순서는 크게 중요하지는 않습니다.

      • 예) ns-blue, ns-green
    • NGINX ingress namespace: 배포 manifest 파일에서 정의한 배포 앱의 ingress 자원 이름을 입력합니다.

    image-20220421094407123

  6. 옵션 설정: Approval controls을 활성화합니다. 배포까지만 자동으로 진행하고, 승인후 트래픽을 전환하기 위해 추가합니다. 트래픽 전환 스위치 정도라고 생각하면 될 것 같습니다.

    • Approval controls:

      • Name: 예) approval-stage

      image-20220421094756223

  7. 파이프라인 완성

    image-20220421094951551

Build Pipeline에서 Deployment Pipeline 호출하기

앞서 만든 Build Pipeline이 끝나고, 배포가 될수 있도록 Deployment Pipeline 호출을 추가합니다.

  1. 앞서 만든 Build Pipelines으로 이동합니다.

  2. 파이프라인 마지막에 Stage를 추가합니다.

  3. Trigger Deployment 유형을 선택합니다.

  4. 앞서 만든 Blue/Green 배포용 Deployment Pipeline을 지정합니다.

  5. 빌드후 배포하는 전체 흐름이 완료되었습니다.

Trigger 설정하기

개발자가 코드를 코드 저장소에 반영이 될 때 자동으로 빌드, 배포 파이프라인이 동작할 필요가 있습니다. Trigger는 코드 저장소에 발생한 이벤트를 통해 빌드 파이프라인을 시작하게 하는 역할을 하게 됩니다.

  1. 프로젝트 페이지로 이동하여 왼쪽 메뉴의 Trigger로 이동합니다.

  2. Create trigger을 클릭합니다.

  3. Trigger를 설정합니다.

    • Name: 예) webpage-build-pipeline-trigger
    • Source Code Repository: 앞서 만든 OCI Code Repository상의 oci-devops-oke-webpage를 선택합니다.
    • Actions: 트리거링 되었을 때 호출하는 액션으로 작성한 빌드 파이프라인인 webpage-pipeline을 선택합니다.
  4. 설정이 완료되었습니다.

테스트 - Blue 배포

  1. Trigger에서 지정한 oci-devops-oke-webpage 소스 코드에 임의의 변경사항을 발생시키고 Code Repository에 반영합니다.

  2. 작업중인 코드가 있는 Cloud Shell로 이동합니다.

  3. html/index.html을 변경해도 되지만, 편의상 환경변수로 해놓은 Dockerfile을 변경합니다.

  4. 현재 설정 기준으로 변경사항을 발생시키고 코드를 Commit 후 Push 합니다.

    FROM nginx:alpine
    
    COPY default.conf.template /etc/nginx/templates/
    COPY html/ /usr/share/nginx/html/
    
    ENV POD_NAMESPACE="default"
    
    ENV VERSION="1.0"
    ENV MESSAGE="Hello OCI DevOps"
    ENV BACKGROUND="blue"
    
    ARG CONTEXT_PATH="/webpage"
    ENV CONTEXT_PATH=$CONTEXT_PATH
    
  5. Trigger가 되고 빌드가 시작됩니다.

    image-20220421102411179

  6. 빌드가 성공하고, 배포 파이프라인이 실행하다, approval-stage에서 대기합니다.

    image-20220421102841459

  7. 쿠버네티스 배포 상태를 확인합니다. ns-blue에만 배포된 것을 알 수 있습니다.

    [opc@jumpbox-945115 ~]$ kubectl get all,ingress -n ns-blue
    NAME                                      READY   STATUS    RESTARTS   AGE
    pod/webpage-deployment-6fbfd769c7-gqprs   1/1     Running   0          9m28s
    pod/webpage-deployment-6fbfd769c7-m4lk4   1/1     Running   0          9m28s
    pod/webpage-deployment-6fbfd769c7-z924h   1/1     Running   0          9m28s
    
    NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
    service/webpage-service   ClusterIP   10.96.166.161   <none>        8080/TCP   9m28s
    
    NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/webpage-deployment   3/3     3            3           9m28s
    
    NAME                                            DESIRED   CURRENT   READY   AGE
    replicaset.apps/webpage-deployment-6fbfd769c7   3         3         3       9m28s
    
    NAME                                        CLASS    HOSTS   ADDRESS          PORTS   AGE
    ingress.networking.k8s.io/webpage-ingress   <none>   *       146.56.118.147   80      9m28s
    [opc@jumpbox-945115 ~]$ kubectl get all,ingress -n ns-green
    No resources found in ns-green namespace.
    
  8. 실행중인 배포 파이프라인으로 돌아가 approval-stage에서 승인합니다.

    image-20220421103948397

    image-20220421104100911

  9. 승인이 되면 Traffic Shift도 진행되어 ns-green에도 ingress가 배포된 것을 볼수 있습니다. 그러나 지금은 ns-blue가 Active이므로 ns-green에 있는 webpage-ingress의 annotations에서 보면 canary-weight:0“이 되어는 실제는 ns-green으로는 요청이 가지 않습니다.

    [opc@jumpbox-945115 ~]$ kubectl get all,ingress -n ns-green
    NAME                                        CLASS    HOSTS   ADDRESS          PORTS   AGE
    ingress.networking.k8s.io/webpage-ingress   <none>   *       146.56.118.147   80      4m
    [opc@jumpbox-945115 ~]$ kubectl get ingress webpage-ingress -n ns-green -o yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        kubernetes.io/ingress.class: nginx
        nginx.ingress.kubernetes.io/canary: "true"
        nginx.ingress.kubernetes.io/canary-by-header: redirect-to-namespaceB
        nginx.ingress.kubernetes.io/canary-weight: "0"
        nginx.ingress.kubernetes.io/rewrite-target: /$2
    ...
    
  10. Ingress 주소를 통해 웹페이지에 접속하면, 현재 Blue 앱이 잘 동작하는 것을 볼수 있습니다.

    • 주소 예) http://146.56.118.147/webpage

    image-20220421105020566

테스트 - Green 배포

  1. 소스로 돌아가서 코드를 변경(예시, Dockerfile의 환경변수 변경)하고 Commit & Push 하여 코드를 반영합니다

    FROM nginx:alpine
    
    COPY default.conf.template /etc/nginx/templates/
    COPY html/ /usr/share/nginx/html/
    
    ENV POD_NAMESPACE="default"
    
    ENV VERSION="1.1"
    ENV MESSAGE="Hello OCI DevOps"
    ENV BACKGROUND="green"
    
    ARG CONTEXT_PATH="/webpage"
    ENV CONTEXT_PATH=$CONTEXT_PATH
    
  2. 다시 빌드, 배포 파이프라인이 실행되고 approval-stage에서 멈출때까지 기다립니다.

  3. 쿠버네티스 배포 상태를 확인합니다. ns-green에 앱이 배포된 것을 볼 수 있습니다.

    [opc@jumpbox-945115 ~]$ kubectl get all,ingress -n ns-blue
    NAME                                      READY   STATUS    RESTARTS   AGE
    pod/webpage-deployment-6fbfd769c7-gqprs   1/1     Running   0          34m
    pod/webpage-deployment-6fbfd769c7-m4lk4   1/1     Running   0          34m
    pod/webpage-deployment-6fbfd769c7-z924h   1/1     Running   0          34m
    
    NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
    service/webpage-service   ClusterIP   10.96.166.161   <none>        8080/TCP   34m
    
    NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/webpage-deployment   3/3     3            3           34m
    
    NAME                                            DESIRED   CURRENT   READY   AGE
    replicaset.apps/webpage-deployment-6fbfd769c7   3         3         3       34m
    
    NAME                                        CLASS    HOSTS   ADDRESS          PORTS   AGE
    ingress.networking.k8s.io/webpage-ingress   <none>   *       146.56.118.147   80      34m
    [opc@jumpbox-945115 ~]$ kubectl get all,ingress -n ns-green
    NAME                                      READY   STATUS    RESTARTS   AGE
    pod/webpage-deployment-75c8bb6ffc-5b497   1/1     Running   0          98s
    pod/webpage-deployment-75c8bb6ffc-m8dl4   1/1     Running   0          98s
    pod/webpage-deployment-75c8bb6ffc-xctr4   1/1     Running   0          98s
    
    NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
    service/webpage-service   ClusterIP   10.96.153.34   <none>        8080/TCP   98s
    
    NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/webpage-deployment   3/3     3            3           98s
    
    NAME                                            DESIRED   CURRENT   READY   AGE
    replicaset.apps/webpage-deployment-75c8bb6ffc   3         3         3       98s
    
    NAME                                        CLASS    HOSTS   ADDRESS          PORTS   AGE
    ingress.networking.k8s.io/webpage-ingress   <none>   *       146.56.118.147   80      18m
    
  4. 웹페이지에 접속하면, 여전히 Blue 앱으로 접속됩니다.

  5. 실행중인 배포 파이프라인으로 돌아가 approval-stage에서 승인합니다.

  6. 승인이 되면, Traffic Shift가 됩니다. 이제 ns-green이 Active이므로 ns-green내 webpage-ingress의 annotations에서 보면 canary-weight:100“으로 변경된 걸 볼 수 있습니다. 이제 모든 요청은 ns-green으로만 전달됩니다.

    [opc@jumpbox-945115 ~]$ kubectl get ingress webpage-ingress -n ns-green -o yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        kubernetes.io/ingress.class: nginx
        nginx.ingress.kubernetes.io/canary: "true"
        nginx.ingress.kubernetes.io/canary-by-header: redirect-to-namespaceB
        nginx.ingress.kubernetes.io/canary-weight: "100"
        nginx.ingress.kubernetes.io/rewrite-target: /$2
    ...
    
  7. 웹페이지에 접속하면, Green 앱으로 접속됩니다. 여러번 반복해서 요청해도 Green 앱으로 접속됩니다.

    image-20220421110959032

테스트 - 추가적인 변경 사항 배포 - Red 배포

  1. 소스로 돌아가서 코드를 변경(예시, Dockerfile의 환경변수 변경)하고 Commit & Push 하여 코드를 반영합니다

    ...
    ENV VERSION="1.2"
    ENV MESSAGE="Hello OCI DevOps"
    ENV BACKGROUND="red"
    ...
    
  2. 다시 빌드, 배포 파이프라인이 실행되고 approval-stage에서 멈출때까지 기다립니다.

  3. 쿠버네티스 배포 상태를 확인합니다. POD의 AGE를 보면 신규 배포가 ns-blue로 된 것을 알 수 있습니다.

    [opc@jumpbox-945115 ~]$ kubectl get all,ingress -n ns-blue
    NAME                                      READY   STATUS    RESTARTS   AGE
    pod/webpage-deployment-7958d6b7b7-pkzmm   1/1     Running   0          115s
    pod/webpage-deployment-7958d6b7b7-vwx22   1/1     Running   0          2m4s
    pod/webpage-deployment-7958d6b7b7-wkbjd   1/1     Running   0          107s
    
    NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
    service/webpage-service   ClusterIP   10.96.166.161   <none>        8080/TCP   53m
    
    NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/webpage-deployment   3/3     3            3           53m
    
    NAME                                            DESIRED   CURRENT   READY   AGE
    replicaset.apps/webpage-deployment-6fbfd769c7   0         0         0       53m
    replicaset.apps/webpage-deployment-7958d6b7b7   3         3         3       2m4s
    
    NAME                                        CLASS    HOSTS   ADDRESS          PORTS   AGE
    ingress.networking.k8s.io/webpage-ingress   <none>   *       146.56.118.147   80      53m
    [opc@jumpbox-945115 ~]$ kubectl get all,ingress -n ns-green
    NAME                                      READY   STATUS    RESTARTS   AGE
    pod/webpage-deployment-75c8bb6ffc-5b497   1/1     Running   0          20m
    pod/webpage-deployment-75c8bb6ffc-m8dl4   1/1     Running   0          20m
    pod/webpage-deployment-75c8bb6ffc-xctr4   1/1     Running   0          20m
    
    NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
    service/webpage-service   ClusterIP   10.96.153.34   <none>        8080/TCP   20m
    
    NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/webpage-deployment   3/3     3            3           21m
    
    NAME                                            DESIRED   CURRENT   READY   AGE
    replicaset.apps/webpage-deployment-75c8bb6ffc   3         3         3       21m
    
    NAME                                        CLASS    HOSTS   ADDRESS          PORTS   AGE
    ingress.networking.k8s.io/webpage-ingress   <none>   *       146.56.118.147   80      37m
    
  4. 웹페이지에 접속하면, 여전히 Green 앱으로 접속됩니다.

  5. 실행중인 배포 파이프라인으로 돌아가 approval-stage에서 승인합니다.

  6. 승인이 되면, Traffic Shift가 됩니다. 이제 ns-blue가 Active입니다. ns-green내 webpage-ingress의 annotations에서 보면 canary-weight:0“로 다시 변경된 걸 볼 수 있습니다. 이제 모든 요청은 ns-blue으로만 전달됩니다.

    [opc@jumpbox-945115 ~]$ kubectl get ingress webpage-ingress -n ns-green -o yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        kubernetes.io/ingress.class: nginx
        nginx.ingress.kubernetes.io/canary: "true"
        nginx.ingress.kubernetes.io/canary-by-header: redirect-to-namespaceB
        nginx.ingress.kubernetes.io/canary-weight: "0"
    ...
    
  7. 웹페이지에 접속하면, ns-blue 네임스페이스에 새롭게 배포한 Red 앱으로 접속됩니다. 여러번 반복해서 요청해도 Red 앱으로 접속됩니다.

    image-20220421112509154

  8. 이렇게 Blue/Green 전략으로 앱을 배포하면, 지정한 2개의 네임스페이스를 이용하여, 현재 서비스 되지 않는 네임스페이스로 신규 버전을 배포하고, 승인이 되면, 신규 버전으로 트래픽을 변경하는 방법으로 지속적인 신규 버전에 대한 배포를 지원합니다.

테스트 - 롤백

신규 버전으로 서비스를 하다, 문제가 발생하면 이전 버전으로 롤백이 기능합니다.

  1. 배포 히스토리 중에서 현재 버전을 배포한 배포 파이프라인으로 이동합니다.

  2. Traffic Shift 메뉴 중에서 Revert traffic shift를 클릭합니다.

    image-20220421113416970

  3. 변경내용 확인 후 다시 Revert traffic shift를 클릭합니다.

    image-20220421113848569

  4. 수동 롤백 작업이 완료되었습니다.

    image-20220421114156771

  5. 배포 변경은 없고 Traffic Shift만 변경된 것으로, 쿠버네티스 배포 상태를 확인해 보면, ns-green내 webpage-ingress의 annotations에서 보면 canary-weight:100“로 다시 변경된 걸 볼 수 있습니다.

    [opc@jumpbox-945115 ~]$ kubectl get ingress webpage-ingress -n ns-green -o yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        kubernetes.io/ingress.class: nginx
        nginx.ingress.kubernetes.io/canary: "true"
        nginx.ingress.kubernetes.io/canary-by-header: redirect-to-namespaceB
        nginx.ingress.kubernetes.io/canary-weight: "100"
        nginx.ingress.kubernetes.io/rewrite-target: /$2
    
  6. 웹페이지에 접속하면, 마지막 배포앱인 Red에서 ns-blue 네임스페이스에 있는 기존 앱인 Green 앱으로 복구된 것을 알 수 있습니다.

    image-20220421114559253

  7. 이렇게 DevOps 서비스에서 제공하는 Blue/Green 전략으로 앱을 배포하면, 2개의 네임스페이스, Ingress Controller의 라우팅 규칙 변경을 통해서 Blue/Green 배포를 할 수 있습니다.



이 글은 개인으로서, 개인의 시간을 할애하여 작성된 글입니다. 글의 내용에 오류가 있을 수 있으며, 글 속의 의견은 개인적인 의견입니다.

Last updated on 20 Apr 2022