배포 프로세스 간단하게 살펴보기

푸르둥개·2022년 12월 6일
post-thumbnail

CI/CD ( Jenkins->Harbor->ArgoCD->Kubernetes) 구축기

일부 환경은 Docker(Rancher1.6)에서 운영 중에 있습니다. 통일된 규격의 서비스 운영과 빠른 배포와 확장을 통해 휴먼 에러를 줄이고 개발 시간을 단축하는 효과를 가져왔습니다.

하지만 규모를 자유롭게 확장하고, Global 서비스를 준비하기 위해서는 microservice architecture (MSA) 구조로 변경이 필요 하다는 판단하에, Kubernetes(이하 k8s)에 배포되어 운영하는 자동화된 프로세스를 고민하게 되었고,첫번째 단계로 CI/CD 도구 및 방법론에 대한 TEST를 진행 하였으며 내용을 공유 하고자 합니다.

CI (Continuous Integration)란?

‘지속적인 통합’이란 의미로, 애플리케이션의 새로운 코드 변경 사항이 정기적으로 build 및 test 되어 공유 repository에 통합되는 것.

CD(Continuous Delivery or Continuous Deploy) 란?

‘지속적인 배포’란 의미로, 개발자들이 애플리케이션에 적용한 변경 사항이 bug test를 거쳐 자동으로 배포되는 것.

즉 CI/CD란 개발 단계를 자동화하여 보다 짧은 주기로 배포하는 전략을 말하는데요. CI/CD 방법론을 적용하면 개발의 편의성이 증가하고, 소스코드 변경부터 배포까지의 작업을 자동화할 수 있기 때문에 수작업으로 할 때의 불편함을 줄일 수 있습니다.

Stack Overview


CI/CD pipeline 구축을 위해 다음 기술과 도구를 사용합니다

1.Kubernetes: (Container Orchestration): Container-based deployment 지원, container scheduling, 빠른 배포와 복구 가능, 리소스 절약 등 많은 장점이 있는 Kubernetes를 안 쓸 이유가 없습니다.

2.Rancher: Kubernetes Management Tools로 Rancher 2.x을 같이 사용합니다. WEB UI 지원 합니다.

3.Jenkins (CI): DevOps 관리자 (또는 각 서비스 담당자)에 의해 중앙관리되는 전사 공용 Jenkins를 사용 합니다.

4.ArgoCD (CD): GitOps스타일의 배포를 지원하는 CD 도구입니다. 원하는 설정 사항을 변경하여 Git에 푸시하면, 자동으로 쿠버네티스 클러스터의 상태가 Git에 정의된 상태로 동기화 됩니다

5. Helm3 (YAML Template Engine): 복잡한 k8s application을 관리, 정의할 수 있는 강력한 도구 중 하나입니다. Deployment, Ingress, ServiceAccount 등 개발자가 manifest를 직접 작성하지 않고 Workload를 구성할 수 있도록 공용으로 사용할 수 있는 Helm chart를 구성할 수 있습니다.

6.Harbor (Image and Helm Registry): Harbor를 Kubernetes 위에 설치하여 사용했습니다. 프로젝트별로 권한 관리가 가능하고, 이미지 취약점 스캐닝이 가능합니다.

Pipeline


모든 빌드와 배포는 Jenkins pipeline을 사용하며, 대부분의 경우 Github Source repo 를 Init 하면서 시작됩니다.

기본적인 pipeline은 다음과 같은 단계로 되어있습니다.

1.Trigger: JenkinsFile을 생성합니다. source code repo의 branch와 values.yaml이 존재하는 repo의 branch를 확인 합니다.

2.Build: Pipeline의 첫 번째 stage입니다. Jenkins를 호출하여 build를 시작합니다.

Kubernetes Plugin의 Pod Template 을 활용하여, 원하는 Container를 생성 합니다.

Github repo를 init 하여 image Build 하고, Harbor 에 upload (push) 합니다.

3. Bake Manifest: git에서 values.yaml을 가져와서 Kubernetes YAML manifest를 생성합니다.

image tag 변경시, Jenkins Pipeline 를 통해 values.yaml 파일을 update 해 줍니다.

4. Deploy: 앞서 생성된 manifest를 apply하고, rolling update가 끝날 때까지 상태를 추적합니다.

ArgoCD 에서 DIFF 기능을 활용하여, 변경된 사항을 확인 후 Sync 합니다.

이를 그림으로 요약하면 다음과 같습니다

추가 고려 사항

Deploy 대상은 여러개 Stack ( DEV / STAGE / PROD / QA.. 등)이 존재 할수 있습니다. ( PROD의 경우, 수동 Sync 를 기본으로 합니다.)

Helm 은 공용 chart 형태가 아닌 서비스별 template + values.yaml 구조 입니다.

JenkinsFile 은 공용 template 가 아닌 서비스별로 별도 생성합니다. (Deploy 부분은 공통 )

Github 와의 연동은 SSH key 구조로 되어 입니다. (Jenkins & Harbor )

Pipeline을 사용하기까지


간단해 보이는 pipeline을 사용하기 위해서는 몇 가지 작업이 먼저 수행되어야 합니다.

1.코드 및 Dockerfile 작성: 프로그래밍 언어의 제약은 없으며 대부분 Java, JavaScript (or TypeScript), Laravel Framework과 Go를 주로 사용하고 있습니다 DockerFile은 개발자의 환경에 따라 개별 작성 후 Github 의 pre-release (or Branch) 에 생성해 주시면 됩니다.

2.values.yaml 작성: Helm charts가 아닌 서비스별 Template + values.yaml 구조로 되어 있습니다, Template 폴더엔 Deployment, Service. ConfigMap, Ingress 등 Kubernetes의 Work Load 의 필수 적인 내용으로 구성되어 있고. Values.yaml 은 images tag 등 간단한 내용만 설정되어 있습니다. ( Template 밑의 yaml 파일 수정이 필요 하면, 담당자와 협의 하여, values.yaml 도 같이 수정해 줘여 합니다. )

3.Jenkins Pipeline 생성 : Kubernetes plugin 과 Blue Ocean 을 적용하여 운영중에 있습니다. JenkinsFile은 서비스별 상황에 맞게 pod. container 를 생성하여, 각 stage ( init, Build,Test.Deploy)을 구성 하변 됩니다. 다만 Deploy 는 values.yaml 파일이 있는 git repo를 init 하여 변경된 설정 ( image tag No )을 sed로 치환 하는 구조 인데. 해당 부분은 고정된 설정으로 변경하시면 안됩니다.
1~3단계를 마치면 마침내 Jenkins Pipeline을 사용할 수 있는 상태가 됩니다.

Dockerfile과 values.yaml 리뷰가 통과되고 첫 배포가 성공하게 되면 담당자의 주요 업무는 끝납니다. 업데이트된 코드나 values.yaml이 있으면 개발자가 독립적으로 pipeline을 실행 시켜 배포하고 하시면됩니다. 다만 안정성을 확보하기 위해 production의 values.yaml이 수정된 경우 담당자의 리뷰를 받아 배포하기를 권장합니다.


지금부터는 좀더 구체적인 CI/CD 도구 및 방법론을 설명해 보겠습니다

우리는 어떻게 Docker 환경을 운영하고 있을까?

Docker Management Tools로 Rancher 1.6 버전을 사용하고 있습니다. CI는 개발자의 PC 에서 진행 하여, Build 된 이미지는 범용 Private Registry 로 Push 되며, docker-compose 방식 ( YML 파일생성)으로 Rancher 에 직업 등록 (CD ) 하고 있습니다. Rancher는 이중화 되어 있으며, 패치작업은 개발자가 release 된 image tag (or env 등)를 docker-compose.yml로 다시 만들어 직접 배포해 주고 있습니다. 즉, CI/CD는 개발자가 직업 수동으로 진행 하고 있습니다.

현재 시스템을 운영하는데 문제는 없으나, MSA 로 변환을 위해서는 Docker 가 아닌 Kubernetes 환경으로 변경이 필수 사항입니다. 이에 따른 CI/CD 도구들에 대한 재 구성 작업이 필요 합니다. 현재 운영중인 Tools 이 없기 때문에 가장 범용적이고 안전한 도구들을 기준으로 선택하게 되었습니다.

JENKINS CI


기본적인 CI Pipeline은 아래와 같은 형식을 따릅니다.

Git Checkout > Image Build > Uni Test > image Push > Deploy

Jenkins에서 해당 CI Pipeline을 공용으로 사용하기 위해 필요한 Container와 단계별 실행해야 하는 스크립트가 포함된 groovy 코드를 작성합니다

Jenkinsfile
podTemplate(label: 'docker-build',
  containers: [
    containerTemplate(
      name: 'git',
    ),
  ],
  volumes: [
    hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
  ]
) {
    node('docker-build') {
        def dockerHubCred = "dev-reg"
        def appImage
         
        stage('Checkout'){
        ...
        }      
        stage('Build'){
        ...
        }
        stage('Test'){
        ...
        }     
        stage('Push'){
        ...
        }
        stage('Dev-Deploy'){
        ...
        }       
    }
  }  
}

해당 Jenkins script(groovy)는 Jenkins에 등록된 모든 프로젝트에서 공용으로 사용하기 위해 개발1팀 Github에 등록합니다.

CI Pipeline의 각 stage 단계에서 사용할 containers를 생성해 줍니다 ( 작성 방식은 Kubernetes plugin for Jenkins 를 참고 )

1.등록된 Template 만큼 Kubernetes 의 Jenkins project 내 container가 순차적으로 생성되며, 작업이 stage 내 작업이 완료 되면 자동 삭제 됩니다.

  1. container간 통신을 위해 Host에서 있는 docker.sock을 volumes mount 합니다. (기본 옵션 이니, 수정 하시면 안됩니다. )
Jenkinsfile
podTemplate(label: 'docker-build',
  containers: [
    containerTemplate(
      name: 'git',
      image: 'alpine/git',
      command: 'cat',
      ttyEnabled: true
    ),
    containerTemplate(
      name: 'docker',
      image: 'docker',
      command: 'cat',
      ttyEnabled: true
    ),
     containerTemplate(
      name: 'argo',
      image: 'argoproj/argo-cd-ci-builder:latest',
      command: 'cat',
      ttyEnabled: true
    ),   
  ],
  volumes: [
    hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
  ]
)

개발자는 CI를 적용하기 위해 Git Branch(Pre-release)에 Jenkinsfile만 추가하면 되고, 아래와 같이 Blue Ocean을 통해 실행 환경이 조성됩니다.

특정 Event에만 작동

CI Pipeline을 Jenkins로 구성 시 고민했던 내용은 자동화 측면이었습니다

Git branch 관리 방식이 개인마다 달랐습니다. 특정 Event(ex. master branch에 push)가 발생하였을 때 webhook을 통해 Jenkins job을 triggering 하는 게 일반적인 CI 자동화 방식입니다. 다만 현재는 Git-flow 등 브랜치 관리 방식을 일괄 적용하기는 어렵다고 생각했습니다. 제가 판단한 공통된 운영방식은 아래와 같이 개발을 위한 신규 Branch (개발자 개인마다 네이밍 규칙이 다름 ,보편적으로 pre-release/1.x.x.x 을 사용하기를 권고 함)를 생성 한 후 필요에 따라 CI 작업을 진행 하게 됩니다.

buildingTag

Jenkins CI Pipeline 통해 image build 작업이 진행되면, 정의된 네이밍 규칙 ( 플랫폼명 / 서비스명 ) 에 맞게 변경을 하게 되고, 빌드 된 모든 Docker image는 Harbor에 Push 되며 Image의 tag는 자동으로 {Jeknkins Build Tag No} (pre-release:3) 형태로 기입됩니다. - 현재는 간단한 규칙으로 생성 되지만 차후 tag 규칙은 github 의 pre-release tag 에 맞게 변경 될 예정입니다.( ex. pre-release/1.2.0 )

Jenkinsfile
        stage('Build'){
            container('docker'){
                script {
                    appImage = docker.build("base/node-hello-world")
.
.

        stage('Push'){
            container('docker'){
                script {
                    docker.withRegistry('http://dev-reg.xxx.com:30003', dockerHubCred){
                        appImage.push("${env.BUILD_NUMBER}")
                        appImage.push("latest")

ARGO CD

K8s 운영하면서 가장 까다로운 점은 인프라 변경 사항에 대한 추적입니다. 대부분 개발자가 오너쉽을 갖고 서비스 개발 및 운영까지 전담하는 경우가 많습니다. 우리는 Application code와 Kubernetes manifest code가 repo 단위로 분리하여 운영 할 예정 입니다 (Git history를 통해 manifest 변경 사항에 대해 추적이 가능한 점입니다) 다만 개발자가 서비스 개발에만 집중하기도 벅찬 환경에서 manifest까지 작성하고 수정하는 건 생산성 감소와 직결되며 잦은 휴먼 에러를 발생할 수 있는 요인이라고 판단됩니다. 특히 manifest code는 중복되는 코드가 너무도 많기에 CD Tool 도입 전, 앞서 언급된 문제를 해결이 필요 합니다.

Helm chart

Helm은 복잡한 k8s application을 관리, 정의할 수 있는 강력한 도구 중 하나입니다. Deployment, Ingress, ServiceAccount 등 개발자가 manifest를 직접 작성하지 않고 Workload를 구성할 수 있는 공용 values.yaml 을 작성 할 수 있습니다.

개발자는 values.yaml에 필요한 내용을 기입하여 manifest 작성 없이 k8s resource(workload)를 구성합니다. Helm chart repositiory 관리를 위해 Chartmuseum, Harbor 등을 사용 할 수도 있지만, GitOps를 위해 Github private repository를 만들고 차트 저장소로 사용하기를 권장합니다.

해당 Chart를 통해 중복되는 manifest code를 줄일 수 있고 무엇보다 기능 도입 시 values.yaml에 내용을 업데이트하면 됩니다. ( 참고 : Blue/green, canary deployment는 Argo CD에선 CRD(Custom Resource Definition)로 제공하고 있습니다. 만약 Helm을 사용하지 않은 상황에서 적용해야 한다면 모든 manifest가 저장된 repository에 업데이트를 해야 하지만 공용 Chart만 업데이트하고 개발자들에게 특정 차트 버전으로 업데이트를 부탁드리면 됩니다.)

GitOps

GitOps는 코드로 표현한 환경, 즉 Git 저장소에 저장된 k8s manifest와 같은 파일을 사용하여 선언적으로 기술한다는 개념입니다.

GitOps 구현체 중 하나인 Argo CD를 통해 도식화하였습니다. Git에 저장된 values.yaml에 image tag를 22로 설정 후 Push합니다. Argo CD는 해당 Repository의 변경 사항을 확인하고 k8s Cluster 내 구성된 resource와 비교합니다. Code와 k8s resource 상황이 다르니 상태는 OutOfSync로 표기됩니다. Code에 맞게 상태를 업데이트하기 위해 자동 혹은 수동으로 k8s resource를 변경합니다. 이를 Synchronize 라고 합니다.

이처럼 Code로 개발자가 원하는 배포 상태를 선언하면 실제 배포 환경을 그에 맞게 유지되고 있는지 체크하고 업데이트하는 배포 주체, 즉 GitOps 구현체인 Argo CD를 CD Tool로 선택하였습니다. 해당 도구를 통해 아래와 같은 이점을 가질 수 있었습니다.

Git을 이용한 배포 버전 관리

  • Argo CD를 통해 k8s에 서비스 배포를 위해선 Git repository에 배포에 관한 모든 내용이 선언되어야 합니다. 업데이트를 위해선 Code를 변경하고 commit & push를 해야 하므로 모든 배포 내역이 Git에 기록됩니다. 이를 기반으로 변경 사항에 대한 추적, 롤백(Git revert) 이 용이해졌습니다.
    배포 자동화

  • Git의 변경이 발생하면 배포 주체인 Argo CD가 k8s cluster 내 resource 상태와 비교 후 자동으로 Synchronize를 진행합니다. 배포를 명령하는 것이 아닌 선언적 방식을 취함으로써 휴먼 에러를 줄이고 지속적 배포를 가능하게 합니다. 다만 Production 환경에서 Auto sync가 활성화되면 담당자가 Git에 잘못된 Code를 Push했을 때 바로 Sync되어 이슈가 발생할 여지가 있으므로 상용 환경은 권한이 있는 담당자만 수동으로 Sync 할 수 있도록 하는 것이 권고 사항입니다.

무엇보다 화면처럼 Argo application 별로 관리되는 k8s resource에 대한 시각화입니다. 위에서 언급된 것 처럼, 공용 Helm chart를 사용하여 사용자가 필요한 values.yaml만 지정하여 resource에 대해 원하는 배포 상황을 선언합니다. helm cli도 훌륭한 도구이지만 chart에 구성되는 resource를 한눈에 파악하기 어렵습니다. Argo CD에선 구성되는 Kubernetes resource를 파악하기 용이하며 Ingress - service - pod로 연결되는 selector 등 UI를 통해 확인하기 좋았습니다.

Application

Argo application이 바라봐야 하는 values.yaml을 관리하는 helm-charts Git repository를 생성합니다. 운영 환경(QA,DEV,STAGE,..등)을 구성하고, 서비스별 Directory 를 생성 후 에 values.yaml 작성 해 줍니다. templates 폴더에는 서비스에 맞는 manifast Yaml 파일을 작성해 줍니다. * 차후 helm-subchrats 형태로 재 구성 예정입니다.

배포하기

이제부터 k8s resource는 선언적 배포 방식으로 Argo CD에 의해 관리, 제어됩니다. Git 저장소의 코드 수정은 리소스를 변경하기 위해 사용되지만 초기 세팅 이후 업데이트할 일은 그리 많지 않을 겁니다. 다만 빈번히 발생되는 건 Deployment, Statefulset 등 manifest의 Container image tag 변경 입니다. helm-values에선 각 환경별 values.yaml에 image.tag 값으로 관리됩니다.

이 과정을 자동화하기 위해, Jenkins가 Harbor에 push 한 Docker image tag를 Argo CD가 바라보는 helm-values git repo 내 image.tag 값을 업데이트합니다. dev/qa 환경은 Auto sync가 활성화되어 Argo CD가 변경이 감지되면 자동으로 synchronize를 진행하지만, 상용 환경은 이슈가 발생할 수도 있어 권한을 가진 사용자만 Argo CD app 화면에서 Sync 버튼을 누를 수 있도록 구성됩니다

Jenkins + Argo CI/CD Pipeline 정리

Jenkins CI와 Argo CD가 연동되는 CI/CD Pipeline에서 단계별로 실행되는 내용에 대해 소개합니다.

Checkout

  • Git checkout, argocd-cli init, env 등 CI/CD Pipeline을 실행하기 위해 필요한 내용들을 진행합니다.

Build

  • docker build … 라는 script가 실행되며 docker image build를 진행합니다.

Test

  • Unit 등 단위테스트가 실행됩니다.

Push

  • docker build 단계에서 build 된 docker image를 Harbor에 push 합니다.

Dev-Deploy

  • 선택된 환경의 values.yaml을 업데이트 이후 git push를 진행합니다. 스크립트는 아래와 같습니다.
Jenkinsfile
         stage('Dev-Deploy'){
            container('argo'){
                checkout([$class: 'GitSCM',
                        branches: [[name: '*/main' ]],
                .
                .
                sshagent(credentials: ['xxx-ssh-private']){
                    sh("""
                     .
                     .
                        sed -i "s/tag:.*/tag: ${env.BUILD_NUMBER}/g" values.yaml 
                        git commit -a -m "updated the image tag"
                        git push --set-upstream origin main
                    """)
                }
            }

References


Kubernetes : https://kubernetes.io/
Rancher : https://rancher.com/
Jenkins : https://www.jenkins.io/
ArgoCD : https://argoproj.github.io/argo-cd/
Helm : https://helm.sh/
Harbor: https://goharbor.io/
Kubernetes Plugin for Jenkins: https://plugins.jenkins.io/kubernetes/
Blue Ocean Plugin for Jenkins: https://plugins.jenkins.io/blueocean/
Git-Flow : https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow
Chartmuseum: https://chartmuseum.com/
Helm-subcharts : https://helm.sh/docs/chart_template_guide/subcharts_and_globals/

profile
DevOps업무중, 개발팀과 운영팀이 알아두면 좋은 정보를 공유합니다.

0개의 댓글