CI-CD

CD를 위한 Jenkins, Argo CD 연계

Operation CWAL 2021. 2. 21. 14:06

 

이전 포스트에서 Jenkins, GitHub, Docker Hub를 파이프라인으로 구성하여, 컨테이너 이미지 빌드를 자동화하였다. 이번 시간엔 Argo CD를 연계하여 배포까지 자동화된 CI/CD Pipeline을 만들어 볼 차례다.

 

우선 기존 구성에서 추가되는 내용은 다음과 같다.

Before 

 

After

1. Jenkins에서 Image 빌드 및 Push 후, 배포 전용 Repository에 새로운 이미지 태그 반영

2. Argo CD는 해당 Repository로부터 Auto Sync 수행

3. AutoSync를 통해 업데이트된 Manifest를 사용하여 리소스 재생성

 

참고로 GitHub와 Argo CD의 연결은 일반적으로 GitHub Webhook 방식을 사용하나, 현재 테스트 환경은 외부에서 Argo CD에 접근할 수 없으므로 Auto Sync를 통해 주기적으로 Polling하도록 설정한다. 실제 운영 환경에선 전자를 선택하는 것이 옳다.

 

가장 먼저 추가해야 할 부분은 k8s 배포를 위한 Manifest 파일이다. Deployment와 Service는 다음과 같이 정의하였다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
  labels:
    app: hello
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
      - name: hello
        image: arm7tdmi/node-hello-world:latest
        ports:
        - containerPort: 8080
apiVersion: v1
kind: Service
metadata:
  name: hello
spec:
  type: NodePort
  selector:
    app: hello
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

매우 간단하지만 작성하다보니 의문점이 든다. 이미지 태그가 'latest'로 고정되어 있는데 이 부분을 어떻게 자동으로 수정할 수 있을까? 그리고 dev, stage, prod 등의 환경에 따라 필드를 추가하거나 값을 수정해야 하는 경우도 당연히 발생할 것이다. Shell 명령어(ex: sed)로 해결할 수 있지만, 빌드 스크립트가 복잡해지고 선언형(declarative) 방식이 아니라는 큰 단점이 존재한다.

많은 사람들이 이 문제에 대해 고민해왔으며, 그 해답으로 이미 다양한 툴이 존재한다. 그 중에서도 Templating 방식의 Helm, ksonnet 그리고 Patch 방식의 Kustomize가 대표적이다. Argo CD는 여기서 언급한 세가지 툴을 모두 지원하나 이번 포스트에선 비교적 진입장벽이 낮은 Kustomize를 사용하여 Manifest 파일을 업데이트하고 배포 환경을 구분해보자.

 

배포용 Repository를 확인해보면 파일 계층구조가 다음과 같이 구성되었음을 알 수 있다.

kustomize 사용법은 별도 페이지에 정리해두었으므로 필요한 경우, 참고하자.

 

kustomize를 활용한 Manifest 관리

Manifest의 재사용성 이 글을 읽고 있는 대부분의 사람은 Kubernetes에서 App 배포를 위해 Manifest 파일을 작성한 경험이 있을 것이다. 예를 들어 Nginx로 구성한 Frontend의 Deployment, Service, Persistent Vo..

cwal.tistory.com

 

다음은 기존 Jenkins 파일에 배포 관련 내용을 추가할 차례다. 참고로 기존 Pod Template의 'git' 컨테이너를 Argo Project에서 제공하는 별도의 빌더 컨테이너인 'argo-cd-ci-builder'로 대체하였다. 해당 컨테이너 안에는 git 외에 kustomize, ksonnet 등의 툴이 같이 설치되어 있기 때문에 상당히 편리하다.

podTemplate(label: 'docker-build',
  containers: [
    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'), 
  ]
) {
    node('docker-build') {
        def dockerHubCred = "dockerhub_cred"
        def appImage
        
        stage('Checkout'){
            container('argo'){
                checkout scm
            }
        }
        
        stage('Build'){
            container('docker'){
                script {
                    appImage = docker.build("arm7tdmi/node-hello-world")
                }
            }
        }
        
        stage('Test'){
            container('docker'){
                script {
                    appImage.inside {
                        sh 'npm install'
                        sh 'npm test'
                    }
                }
            }
        }

        stage('Push'){
            container('docker'){
                script {
                    docker.withRegistry('https://registry.hub.docker.com', dockerHubCred){
                        appImage.push("${env.BUILD_NUMBER}")
                        appImage.push("latest")
                    }
                }
            }
        }

        stage('Deploy'){
            container('argo'){
                checkout([$class: 'GitSCM',
                        branches: [[name: '*/main' ]],
                        extensions: scm.extensions,
                        userRemoteConfigs: [[
                            url: 'git@github.com:cure4itches/docker-hello-world-deployment.git',
                            credentialsId: 'jenkins-ssh-private',
                        ]]
                ])
                sshagent(credentials: ['jenkins-ssh-private']){
                    sh("""
                        #!/usr/bin/env bash
                        set +x
                        export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no"
                        git config --global user.email "cure4itches@gmail.com"
                        git checkout main
                        cd env/dev && kustomize edit set image arm7tdmi/node-hello-world:${BUILD_NUMBER}
                        git commit -a -m "updated the image tag"
                        git push
                    """)
                }
            }
        }
    } 
}

 

Deploy 스테이지는 다소 복잡한 코드에 비해 생각보다 단순한 작업을 수행한다. 배포용 Repository를 체크아웃하여 dev 환경용 Application 배포의 이미지 태그를 현재 빌드번호로 수정 후 다시 Push하는 동작이다. 참고로 'kustomize edit' 명령어를 통해 변경된 내용은 현재 디렉토리의 kustomization.yaml 파일에 반영된다.

 

참고로 GitHub에 변경된 코드를 Push하기 위해서 Jenkins에 SSH Agent 플러그인을 설치해야 한다. 이전에 GitHub와 Jenkins에 각각 SSH Public Key, Private Key를 등록한 적이 있는데 이번 실습에서 본격적으로 SSH를 Credential로 사용할 것이다. HTTPS 인증 방식도 가능하나 ID/Password가 URL에 노출되는 치명적인 보안 이슈가 있으므로 사용을 지양한다.

 

Jenkinsfile가 정상적으로 작성되었는지 테스트해보자. 수정된 내용으로 Jenkins 빌드가 이루어진 뒤 배포용 Repository의 env/dev/kustomization.yaml 파일 내용을 확인하면 된다.

 

우선 Jenkins 빌드 자체는 Checkout부터 Deploy까지 전 과정이 정상적으로 수행되었음을 알 수 있다. 

이제 확인해볼 것은 kustomization 파일이다. 과연 이미지 태그가 빌드번호인 34로 업데이트 되었을까?

이제 이미지 태그까지 자동으로 업데이트하는 과정까지 구현하였다. 마지막으로 남은 것은 Argo CD 설정이다.

 

Argo CD 웹 콘솔에 접속하여 아래와 같이 새로운 Application을 추가한다. 만약 Argo CD가 어떤 툴인지 잘 모르겠다면 필자가 예전에 작성한 ArgoCD: Kubernetes에 GitOps 적용하기 포스트를 참고하길 바란다.

 

아래와 같은 내용으로 Argo CD Application 'hello-world-app'을 정의해 보았다.

Argo CD가 ./env/dev 디렉토리로부터 kustomize를 실행하여 'dev' Namespace에 관련 리소스를 생성하는 방식으로 Application 배포가 이루어진다. 잠시 기다린 후 'hello-world-app'의 상태를 확인해보자.

기대했던 것 이상으로 배포 자동화 과정이 깔끔하게 진행된다. 마지막으로 curl 명령어를 사용하여 해당 nodePort를 확인해보자.

만약 dev 환경이 아닌 prod 환경에서 Application 배포를 하고 싶다면? hello-world-app과 동일한 구성으로 Argo CD Application을 생성하되, kustomize 디렉토리(./env/prod)와 Namespace만 변경하면 바로 가능하다.

 

마치며...

CI/CD 파이프라인 전체를 자동화하는 것은 확실히 쉽지 않은 과정이며, 수많은 시행착오를 겪으면서 진행할 수 밖에 없다. 필자도 이번 CI/CD 파이프라인 자동화 튜토리얼을 작성하는 동안, 다양한 원인들로 인한 빌드 실패때문에 Jenkinsfile만 수십번 수정하였다.

하지만 딱 처음만 고생하고 나면, 그 이후부턴 모든 개발자가 코드에만 집중할 수 있는 환경에서 일할 수 있기 때문에 그만한 가치가 있는 작업이라고 생각한다.

 

참고
Pipeline - Equivalent to Git Publisher


 

'CI-CD' 카테고리의 다른 글

Jenkins Pipeline  (0) 2021.03.01
kustomize를 활용한 Manifest 관리  (0) 2021.02.24
CI를 위한 Jenkins, GitHub, Docker Hub 연계  (0) 2021.02.20
Kubernetes 위에 Jenkins 설치하기  (0) 2021.02.19
ArgoCD: Kubernetes에 GitOps 적용하기  (2) 2021.02.17