본문 바로가기

Infra & Devops

[Kubernetes] ArgoCD 정리 (2) - Jenkins 빌드 구성과 배포 설정

 

 이전 포스트에서는 ArgoCD 의 대략적인 개요와 GitOps, 그리고 Kustomize 구성 등을 알아보았다. 이번 포스트에서는 Jenkins 를 기반으로 docker 컨테이너 이미지를 빌드 후 ECR 레포지토리에 Push 하고, Kustomize 를 사용해 Image Tag 를 갱신하는 방법을 정리한다. 이러한 작업은 Jenkins Pipeline 스크립트를 통해 수행된다. 내가 속한 팀에서 개발중인 각종 마이크로서비스 중, 'user-api' 서비스를 예로 들어 설명하겠다.

 

Prerequisite

일단 Jenkins 는 설치되어 있다고 가정한다. 그리고 K8S 관련 작업 및 docker 컨테이너 빌드를 위한 각종 플러그인은 아래와 같다.

 

Kustomize 를 사용하여 선언적으로 Deployment 설정을 변경(Image Tag 갱신)해야 하기 때문에 해당 툴도 'JENKINS_HOME' 디렉토리에 설치 되어 있어야 한다.

 

Jenkins Credentials 생성

 빌드된 docker 이미지는 ECR(Elastic Conatiner Registry)에 Push 된다. 따라서 해당 ECR 레포지토리에 접근 가능한 Credentials를 아래와 같이 생성한다.

 

 

Access Key ID와 Secret Access Key는 IAM 계정의 그것을 입력하면 된다.

 

당연히 필요하게 될 Git Credentials 생성은 생략한다.

 

Build Pipeline

import java.text.SimpleDateFormat

node {

	parameters {
		string(name: 'BRANCH', defaultValue: 'develop', description: '')
	}

	stage('Src Checkout') {
		git branch: '${BRANCH}',
			credentialsId: '<git credentials id>',
			url: '<git repo url>'
	}

  	stage('Build') {
  		sh './gradlew bootJar'
  	}

  	stage('Docker build') {
	  	image = docker.build('dev-user-api:${BUILD_NUMBER}')
  	}

  	stage('ECR push') {
		docker.withRegistry('http://<iam account id>.dkr.ecr.ap-northeast-2.amazonaws.com/dev-user-api', 'ecr:ap-northeast-2:ecr-credential') {
			image.push()
		}
  	}
}

 

 Application Repository의 코드를 빌드하는 Pipeline 스크립트이다. 일반적인 Scripted Pipeline 문법을 사용해 작성했다. 각 `stage` 는 이름 그대로의 작업을 수행하며, 마지막 단계에서 ECR 레포지토리에  Push 하게 된다. 이때 앞서 생성한 credentials ID를 입력해줘야 한다.

 

Image Tag 갱신

 이제 우리가 해야할 일은 앞선 Jenkins 빌드에서 `${BUILD_NUMBER}` 로 태깅되어 Push 된 이미지를 'GitOps Repo' 에 있는 Deployment Manifest 에 적용시키는 일이다. 일단 아래의 `kustomize.yaml` 을 다시한번 살펴보자.

 

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patchesStrategicMerge:
- deployment.yaml
resources:
- ../base
images:
- name: user-api-image
  newName: <iam account number>.dkr.ecr.ap-northeast-2.amazonaws.com/dev-user-api
  newTag: "<new tag>"

 

아래의 Jenkins Pipeline 스크립트를 수행하면, 'new tag' 부분에 이전 빌드에서 Push 된 이미지 번호가 업데이트 된다. 여기서는 GitOps Repository의 파일들을 Checkout 한다.

 

import java.text.SimpleDateFormat

node {

	stage('Src Checkout') {
		git branch: 'main',
			credentialsId: '<git crendentials>',
			url: '<gitops repo url>'
	}
	
    def imageTag = getLatestBuildNumber()
    
  	stage('Update image tag') {
  	    
        withCredentials([usernamePassword(credentialsId: '<git credentials id>', 
            usernameVariable: 'GIT_USER', passwordVariable: 'GIT_PWD')]) {
                
            def encodedPassword = URLEncoder.encode("$GIT_PWD",'UTF-8')
            def gitUrl = "https://$GIT_USER:$encodedPassword@<gitops repo url>"
            def registry = "<iam account id>.dkr.ecr.ap-northeast-2.amazonaws.com"
            
            dir("user-api") {
              sh "git pull $gitUrl"
              sh """
                    cd ./dev && ~/kustomize edit set image \
                    user-api=$registry/dev-user-api:${imageTag} \
                """
              sh "git add -A"
              sh "git commit -m '[DEV][USER-API] updated tag: $imageTag'"
              sh "git push $gitUrl || echo 'no changes'"
            }
        }
    }
}

@NonCPS
def getLatestBuildNumber() {
    def jobname = "dev-user-api-build"
    def job = Jenkins.instance.getItemByFullName(jobname)
    job.getLastBuild().getNumber()
}

 

간단히 설명하자면, 지난 포스트에서 언급한 'GitOps Repo' 의 kustomize 를 적용한 구조에서, 'dev' overlay 디렉토리로 들어가 `kustomize` 명령어를 통해 `kustomize.yaml` 의 `newTag` 섹션을 새롭게 태깅된 Image Tag(최근 빌드한 Jeknins 빌드번호)로 갱신한다. 만약 최신 빌드 번호가 '100' 이라면, 위 스크립트를 수행한 후에 GitOps 설정 파일들이 있는 디렉토리에서 `kustomize build ./user-api/dev` 를 실행하면 아래와 같은 Deployment 설정이 완성된다. (이전 포스트에서 이미 설명하긴 했다)

 

$ kustomize build ./user-api/dev
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-api
spec:
  .
  .
  <생략>
  .
  .
  template:
    metadata:
      labels:
        app: user-api
      name: user-api
    spec:
      containers:
      - command:
        - /bin/sh
        - -c
        - java -jar -Duser.timezone=Asia/Seoul -Dspring.profiles.active=dev user-api.jar
        image: <iam account number>.dkr.ecr.ap-northeast-2.amazonaws.com/dev-user-api:100
        name: user-api
   .
   .
   .
   <생략>
   .

 

 새로운 컨테이너 이미지를 빌드하여 Push 했고, 해당 이미지의 주소와 새로운 Tag 도 갱신 시켰다. 이로써 GitOps Repo 에서 해야할 일은 끝났고, 이제 ArgoCD 설정을 통해 GitOps Repo 에 설정된 Deployment 를 클러스터에 반영(배포) 해보도록 한다.

 

ArgoCD 배포 설정 및 배포 수행

 

1. App 생성

 

 

2. 배포 설정

 

 

'SYNC POLICY' 는 두가지가 있는데 'Manual' 과 'Automatic' 이 그것이다. 직관적으로 알 수 있듯이 전자는 수동으로 배포하는 것이고, 후자는 ArgoCD 가 GitOps 레파지토리를 Polling 하면서 변경사항이 감지(주로 Image Tag 갱신)되면 배포를 수행하는 방식을 말한다.

 

 

'Repository URL' 에는 manifest 파일들을 관리하는 GitOps Repo 의 주소를 입력한다. 해당 주소가 인식되면, 'Path' 에는 자동적으로 각 서비스 별로 설정한 overlays (각 환경별) 디렉토리 목록이 노출되고, 현재 배포하고자 하는 환경에 맞는 디렉토리를 선택하면 된다.

 

 

해당 ArgoCD는 배포 대상 K8S 클러스터에 설치되어 있다. 따라서 'Cluster URL' 은 내부 도메인 이름을 입력한다. (자동으로 인식된다)

또 'Namespace' 에는 서비스별 Pod가 배포될 특별한 Namespace를 사용하지 않는다면 'default' 로 입력한다.

 

모든 설정을 완료했다면 좌측 상단의 'CREATE' 버튼을 눌러 생성을 마친다.

 

3. 배포 수행

 

위 설정까지 정상적으로 진행했다면, 이제 실제 실무에서 진행했던 배포 프로세스를 예시로 소개하겠다.

 

1) 생성한 'dev-user-api' 로 진입해서 'REFRESH' 버튼 클릭

 

 

'CURRENT SYNC' 가 'OutOfSync' 로 변경된다. 'APP DIFF' 버튼을 누르면 현재 배포되어 있는 Manifest 와 변경된 (Image Tag 가 갱신된) Manifest 를 비교할 수 있다.

 

 

Image Tag를 갱신하는 Jenkins Job에 의해 기존 '144' 에서 '145' 로 변경된 모습을 볼 수 있다.

 

2) 'SYNC' 를 눌러 배포를 수행

 

편의상 '배포'라고 이야기하지만

 

 

사실 이것은 GitOps 리파지토리의 최신 Manifest를 동기화(Sync) 하는 것을 말한다.

 

동기화가 시작되면 새로운 ReplicaSet이 생성되고, 기존 Pod와 새 Pod가 RollingUpdate 방식으로 배포되는 것을 시각적으로 확인 할 수 있다. 또 아래처럼 각 Pod에서 구동되는 컨테이너의 'stdout' 으로 출력되는 로그 또한 확인할 수 있다.

 

 

3) 배포후 Sync 상태 확인

 

 

마치며

 처음 K8S(EKS) 배포환경을 구축할때는 단순히 Jenkins 의 Pipeline 스크립트에서 `kubectl` 을 사용하여 Deployment yaml 을 `apply` 했었다. 이 방식은 배포 현황을 파악하려면 일일이 로컬의 `kubectl` 툴을 통해 Deployment Rollout Status 와 Pod 상태를 확인해야함으로 꽤나 번거로웠다.

 

하지만 이번 ArgoCD 도입으로 인해서 더 시각적으로 풍부한 정보가 제공되는 세련된 방식으로 배포가 가능 할 수 있었다. 'Tekton' 이나 'JenkinsX' 에 비해 구축이 쉽고, 다양하지만 꼭 필요한 정보만 제공 되기 때문에 여러모로 가성비가 좋은 툴이라는 생각이든다.