1. 목적
이 글은 Kubernetes 기반 시스템에서 Istio를 이용하여 Blue/Green 배포를 구성하는 방법을 설명하고 있다.
구성 환경은 GCP GKE 1.18.20-gke.501 버전과 Istio 1.10.0 버전으로 Kubernetes Service mesh 환경을 구현하였다.
CI/CD 툴은 Gitlab, Gitlab runner를 사용한다.
2. Blue/Green Deployment
Blue/Green Deployment는 현재 운영중인 버전(Blue)과 새로운 버전(Green)의 서버들을 동시에 나란히 구성하고 배포 시점이 되면 트래픽을 일제히 전환시키는 배포 방식이다.
운영 환경에 영향을 주지 않고 실제 서비스 환경으로 새 버전 테스트가 가능하고, 빠른 롤백이 가능하다는 것이 장점이다.
3. Kubernetes 구성
3.1. Istio 구성
Istio는 IstioOperator 메니페스트를 istioctl install 명령어의 -f 옵션의 매개변수로 넘겨서 생성하였다.
apiVersion: install.istio.io/v1alpha1 kind: IstioOperator spec: meshConfig: enableTracing: true #addon tracing 활성화 components: ingressGateways: - enabled: true # istio-ingressgateway 활성화 여부 name: istio-ingressgateway k8s: # istio-ingressgateway Service에 외부 LoadBalancer를 연결 overlays: - kind: Service name: istio-ingressgateway patches: - path: spec.externalTrafficPolicy value: "Local" # k8s service, Local을 권장. hpaSpec: # istio-ingressgatway hpa maxReplicas: 5 # default 5 minReplicas: 3 affinity: # 3개의 istio-ingressgateway를 각 worker node에 분산시키기 위해 affinity 선언 podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: "service.istio.io/canonical-name" operator: In values: - "istio-ingressgateway" topologyKey: "kubernetes.io/hostname" nodeSelector: # 특정 nodepool에 생성하기 위해 node selector 선언 cloud.google.com/gke-nodepool: nodepool-1 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 1024m memory: 512Mi |
아래의 명령어를 실행하면 GKE 클러스터에 istio core, istiod, ingress gateway가 설치된다.
istioctl install -f istio_manifest.yaml |
3.2. Gateway, Virtual service 생성
Blue/Green 배포의 Blue 환경과 Green 환경 접근 설정은 virtual service에서 선언한다.
Gateway는 초기 구성 때에만 생성하고, virtual service는 blue/green 배포시마다 파이프라인에서 configure한다.
Gateway의 spec.servers.hosts 값에는 외부에서 GKE로 질의하는 hostname을 선언한다.
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: bluegreen-gateway namespace: app spec: selector: istio: ingressgateway servers: - port: number: 80 name: http-istio-gateway protocol: HTTP hosts: - "bluegreen.domain.com" |
모듈별 gateway와 virtual service를 따로 생성하였고, Virtual service에서의 spec.host는 *로 선언하여 host에 대한 관리는 모듈별 gateway로 일임한다.
spec.http.route.destination.host는 (모듈의 Service name).(namespace).svc.cluster.local 로 입력한다.
이는 초기에 선언하긴 하지만, 배포될 때마다 새로운 service name으로 변경되기 때문에 현재의 virtual service는 초기 세팅 때에만 선언한다.
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: bluegreen-virtualservice namespace: app spec: gateways: - bluegreen-gateway hosts: - "*" http: - route: - destination: host: bluegreen-front.app.svc.cluster.local port: number: ${PORT_NO} |
3.3. Configmap 생성
Blue/Green으로 CI/CD 파이프라인을 구성할 때 배포는 Helm을 사용하지 않고 kubectl 명령어로 service와 deployment를 직접 배포하였다.
그 이유는 helm 배포는 하나의 app을 다른 두 개의 list로 install(혹은 upgrade)을 하지 않는 이상 두 버전의 환경이 같이 동시에 배포되도록 구성하는 것이 불가하다고 판단하였기 때문이다.
Configmap을 생성하여 configmap 안에 운영 중인 버전(Green)의 이름을 저장하는 용도로 사용한다.
apiVersion: v1 kind: ConfigMap metadata: name: app-configmap namespace: app data: active-deployment-name: bluegreen-front |
3.4. Pipeline 구성
CI/CD 파이프라인은 yarn build - docker build 및 GCR로 이미지 push - Green버전 service/deployment 배포 및 Green 버전 접근 설정 - Green 버전 정상 배포 check - Green 버전 배포 진행 or Blue 버전 rollback 으로 구성된다.
이 중에 앞의 두 stage에 대한 설명은 제외하고 Green 버전 service/deployment 배포 및 green 버전 접근 설정부터 설명한다.
3.4.1. Green 버전 service/deployment 배포 및 Green버전 접근 설정
CI/CD 파이프라인 job의 script에서 Green 버전의 service와 deployment를 생성한다.
service와 deployment를 선언할 때 모듈의 이름 뒤에 CI/CD 파이프라인이 실행될 때마다 정의되는 랜덤 문자열인 ${CI_COMMIT_SHORT_SHA}를 붙여서 이름을 선언한다.
예를 들면, 모듈의 이름이 bluegreen이면 service명과 deployment명을 bluegreen-${CI_COMMIT_SHORT_SHA} 로 생성한다.
${변수이름}은 Gitlab runner에서 선언한 변수이다.
script: - | cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: ${APP_NAME}-${CI_COMMIT_SHORT_SHA} namespace: ${NAMESPACE} spec: type: ClusterIP ports: - port: ${PORT_NO} targetPort: ${PORT_NO} protocol: TCP name: http selector: app.kubernetes.io/name: ${APP_NAME}-${CI_COMMIT_SHORT_SHA} EOF - | cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: ${APP_NAME}-${CI_COMMIT_SHORT_SHA} namespace: ${NAMESPACE} spec: replicas: ${REPLICAS} selector: matchLabels: app.kubernetes.io/name: ${APP_NAME}-${CI_COMMIT_SHORT_SHA} template: metadata: labels: app.kubernetes.io/name: ${APP_NAME}-${CI_COMMIT_SHORT_SHA} spec: containers: - name: ${APP_NAME} image: "${GCR_URL}/${APP_NAME}:${CI_COMMIT_SHORT_SHA}" ports: - name: http containerPort: ${PORT_NO} protocol: TCP resources: requests: memory: ${REQUEST_MEMORY} cpu: ${REQUEST_CPU} limits: memory: ${LIMIT_MEMORY} cpu: ${LIMIT_CPU} livenessProbe: httpGet: path: /healthcheck.html port: ${PORT_NO} initialDelaySeconds: ${HEALTH_CHECK_SECONDS} readinessProbe: httpGet: path: /healthcheck.html port: ${PORT_NO} initialDelaySeconds: ${HEALTH_CHECK_SECONDS} nodeSelector: cloud.google.com/gke-nodepool: ${NODEPOOL_NAME} EOF |
Green 버전 service와 deployment가 생성이 되고 나면, green 버전으로 접근할 수 있도록 virtual service를 수정해 준다.
Blue/Green 환경에 대한 접근은 gateway에 선언한 domain으로 입력하면 blue환경으로, 도메인의 뒤에 /test로 입력하면 green환경으로 접근이 가능하다.
같은 방식으로 script 내에서 manifest를 선언한다.
script: - | cat <<EOF | kubectl apply -f - apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ${CI_PROJECT_NAME} namespace: app spec: gateways: - bluegreen-gateway hosts: - "*" http: - match: - uri: prefix: /test rewrite: uri: / route: - destination: host: ${NEW_DEPLOYMENT_NAME}.${NAMESPACE}.svc.cluster.local port: number: ${PORT_NO} - route: - destination: host: ${ACTIVE_DEPLOYMENT_NAME}.${NAMESPACE}.svc.cluster.local port: number: ${PORT_NO} EOF |
3.4.2. Green 버전 정상 배포 check
이 stage는 Gitlab 콘솔에서 개발자들이 정상적으로 배포가 되었는지 확인할 수 있게 하기 위해서 추가한 단계이다.
아래의 script를 실행하여 확인한다.
이렇게 선언하면 Blue 버전에는 영향을 주지 않고 새로 배포된 Green 버전의 테스트가 가능하다.
script: - kubectl rollout status deployment ${NEW_DEPLOYMENT_NAME} -n ${NAMESPACE} |
3.4.3. Green 버전 배포 진행 or Blue 버전 rollback
테스트를 진행한 후 Green 버전으로 현재 운영을 대체할 지, 혹은 테스트 중 문제가 발생하여 기존 version으로 rollback할지를 선택한다.
Gitlab runner에서는 기본적으로 green 버전 배포를 바로 실행 설정해놓되, delay 시간을 설정하여 테스트할 수 있는 시간을 확보한다.
본 gitlab-ci.yml 파일에서 다른 gitlab-ci.yml 파일을 trigger로 실행한다.
trigger-proceed-deployment-stg: stage: blue-green-control when: on_success trigger: include: - project: developerV2/bluegreen-project ref: master file: .gitlab-ci-child-proceed-deployment.yml strategy: depend variables: # https://docs.gitlab.com/ee/ci/yaml/README.html#artifact-downloads-to-child-pipelines PARENT_PIPELINE_ID: ${CI_PIPELINE_ID} PARENT_JOB_NAME: ${PARENT_JOB_NAME} needs: [deployment-check-stg] only: refs: - stage |
위의 job에서 trigger된 .gitlab-ci-child-proceed-deployment.yml 에서 delay 설정을 하는데, Gitlab - CI/CD - Variables에서 변수(AUTO_BLUE_GREEN_CONTROL)를 설정하면 변수 값에 따라서 delay를 줄지 바로 실행을 할 지 설정할 수 있다.
그리고 script 안에서 virtual service를 update하여 운영 환경을 Green 버전(NEW_DEPLOYMENT_NAME) 으로 변경한다.
그 이후 configmap의 현재 운영중인 service, deployment 명을 kubectl patch configmap 명령어를 통해 변경한다.
마지막으로 기존의 blue 버전을 kubectl delete 명령어로 삭제한 후 마무리한다.
kubectl delete 명령어를 감싸고 있는 if문은 만약 별도의 배포 없이 CI/CD 파이프라인만 실행시켰을 때에는 CI_COMMIT_SHORT_SHA 값이 같기 때문에, Blue 버전과 Green 버전의 이름이 같다면 삭제하지 않도록 하기 위함이다.
reroute-traffic: stage: reroute-traffic # rules cannot use together with only/except rules: - if: '$AUTO_BLUE_GREEN_CONTROL == "true"' when: on_success allow_failure: true - if: '$AUTO_BLUE_GREEN_CONTROL == null || $AUTO_BLUE_GREEN_CONTROL == "false"' when: delayed start_in: 25 minutes environment: name: ${CI_COMMIT_REF_NAME} needs: - pipeline: ${PARENT_PIPELINE_ID} job: ${PARENT_JOB_NAME} before_script: - !reference [.template-get-gcr-authentication, script] - !reference [.template-connect-gke-cluster, script] script: - echo AUTO_BLUE_GREEN_CONTROL=${AUTO_BLUE_GREEN_CONTROL} #old service로 접속 가능한 virtual service 삭제 - | cat <<EOF | kubectl apply -f - apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ${CI_PROJECT_NAME} namespace: ${NAMESPACE} spec: gateways: - ${GATEWAY_NAME} hosts: - "*" http: - route: - destination: host: ${NEW_DEPLOYMENT_NAME}.${NAMESPACE}.svc.cluster.local port: number: ${PORT_NO} EOF #Configmap에 active-deployment-name 을 new deployment name으로 변경 - | echo "Configmap is patched to new deployment name." kubectl patch configmap -n ${NAMESPACE} ${CI_PROJECT_NAME} -p $'data:\n active-deployment-name: '${NEW_DEPLOYMENT_NAME} # 프로젝트 소스에 변경이 없이 파이프라인만 run하는 경우 CI_COMMIT_SHORT_SHA가 같다. # 이 변수가 같으면 기존의 service, deployment 이름이 새로 배포한 것과 이름이 같다. # 그러므로 active와 new 변수가 다를 때에만 삭제 작업을 하도록 안전장치를 추가한다. - if [ "${ACTIVE_DEPLOYMENT_NAME}" != "${NEW_DEPLOYMENT_NAME}" ]; then kubectl delete svc -n ${NAMESPACE} ${ACTIVE_DEPLOYMENT_NAME}; kubectl delete deployment -n ${NAMESPACE} ${ACTIVE_DEPLOYMENT_NAME}; fi |
rollback의 경우에는 virtual service를 기존의 Blue 버전으로만 서비스되도록 수정한 후, kubectl delete 명령어로 green 버전의 service와 deployment를 삭제한다.
rollback-deployment: stage: stop-deployment when: on_success needs: - pipeline: ${PARENT_PIPELINE_ID} job: ${PARENT_JOB_NAME} before_script: - !reference [.template-get-gcr-authentication, script] - !reference [.template-connect-gke-cluster, script] script: #new service로 접속 가능한 virtual service 삭제 - | cat <<EOF | kubectl apply -f - apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ${CI_PROJECT_NAME} namespace: ${NAMESPACE} spec: gateways: - gw-${CI_PROJECT_NAME} hosts: - "*" http: - route: - destination: host: ${ACTIVE_DEPLOYMENT_NAME}.${NAMESPACE}.svc.cluster.local port: number: ${PORT_NO} EOF #배포를 위해 생성한 svc, deployment 삭제 # 프로젝트 소스에 변경이 없이 파이프라인만 run하는 경우 CI_COMMIT_SHORT_SHA가 같다. # 이 변수가 같으면 기존의 service, deployment 이름이 새로 배포한 것과 이름이 같다. # 그러므로 active와 new 변수가 다를 때에만 삭제 작업을 하도록 안전장치를 추가한다. - if [ "${NEW_DEPLOYMENT_NAME}" != "${ACTIVE_DEPLOYMENT_NAME}" ]; then kubectl delete svc -n ${NAMESPACE} ${NEW_DEPLOYMENT_NAME}; kubectl delete deployment -n ${NAMESPACE} ${NEW_DEPLOYMENT_NAME}; fi |
'Kubernetes' 카테고리의 다른 글
Istio Envoy Proxy의 TCP Dump 수집 (0) | 2021.12.24 |
---|---|
Pod Affinity를 적용하여 Pod 분산 배포하기 (0) | 2021.09.01 |
Vertical Pod Autoscaling 적용하기 (0) | 2021.09.01 |
Horizontal Pod Autoscaling 적용하기 (0) | 2021.08.31 |
EKS에 EFS Mount하기 (0) | 2021.08.31 |