[GitOps] Jenkins 연동 및 컨테이너 이미지 버전 태그 자동 생성

2023. 1. 23. 02:13[ DevOps ]/[ CI-CD ]

이전 포스팅에서는 ArgoCD와 Argo Rollout 컨트롤러를 통한 CD 환경을 구축했다. 이번 포스팅에서는 GitOps 상에서 CI 툴인 Jenkins가 Rollout Manifest 파일을 변경시킴으로써 CD 과정이 자동으로 트리거 되는 시나리오를 구축한다.

 

 

최종 GitOps 아키텍처

 

1) 사용자가 Git에 소스코드를 커밋/푸시

2) Jenkins가 커밋 감지 및 Jenkins Pipeline 실행

    2-1) gradle 등의 빌드 툴로 빌드

    2-2) 컨테이너 이미지 빌드

    2-3) 컨테이너 레지스트리 푸시 (신규 태그 생성)

    2-4) Git의 Manifest 레포지토리에 Rollout Manifest 수정(보통 컨테이너 이미지 tag 수정)

3) ArgoCD가 Git의 Rollout Manifest 레포지토리 변경 감지

4) ArgoCD가 컨테이너 이미지 레지스트리로부터 pull

5) Argo Rollout 컨트롤러가 Pod 업데이트 

 

 

 

CI 서버에 Jenkins 설치

해당 과정은 Jenkins 설치 과정에 대한 포스팅에서 자세히 다루고 있으므로 생략한다.

 

더보기
sudo docker run  \
	--name jenkins-container \
	-d \
	-p 31313:8080 \
	-v ~/jenkins:/var/jenkins_home \
	-v /var/run/docker.sock:/var/run/docker.sock \
	-u root \
	--restart=always \
	jenkins/jenkins:latest-jdk11

 

cf. AWS 보안그룹 인바운드 규칙 추가

 

cf. admin password 확인

sudo docker exec -it <컨테이너> bash -c "cat /var/jenkins_home/secrets/initialAdminPassword"

 

 

- 플러그인 설치, SSH 등록 등의 과정은 생략

 

 

CI Ppeline 생성

 

 

 

 

 

 

 

GitHub WebHook 설정

'Public IP:Port/web-hook/'을 입력하고 jenkins에서는 item수정 -> Build Triggers -> GitHub hook trigger for GITScm polling을 선택해주면 된다.

 

 

 

 

 


 

Pipeline 작성

- Jenkinsfile

def maindir = "."

pipeline {
    agent any

    stages {
        stage('Pull Source Code') {
            steps {
                checkout scm
            }
        }
        stage('Build Source Code') {
            steps {
                sh '''
                id
                java -version
                ./gradlew clean build -x test -x jacocoTestReport -x createDocument -x displaceDocument -x sonarqube
                '''
            }
        }
        stage('Docker image build & push') {
            steps {
                script {
                    withCredentials([usernamePassword(credentialsId: 'dockerHubRegistryCredential', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
                        sh '''
                        NEW_TAG=$(git log -1 --pretty=%h)
                        dockerRepo="goharrm/offer-dev"
                        ./gradlew jib \
                        -Djib.to.auth.username=${USERNAME} \
                        -Djib.to.auth.password=${PASSWORD} \
                        -Djib.to.image=${dockerRepo}:${NEW_TAG} \
                        -Djib.console='plain'
                        sleep 10
                        '''
                    }
                }
            }
        }
        stage('Argo Rollout Manifest Update') {
            steps {
                sshagent(credentials : ["offer-jenkins"]) {
                    sh '''
                        NEW_TAG=$(git log -1 --pretty=%h)
                        MAIN_PATH=`pwd`
                        if [ ! -e ~/offer-rollout/application-manifests ];
                        then
                          mkdir -p ~/offer-rollout
                          cd ~/offer-rollout
                          git clone git@github.com:price-offer/application-manifests.git
                        else
                          cd ~/offer-rollout/application-manifests
                          git pull origin main
                        fi
                        cd ~/offer-rollout/application-manifests
                        sed -i "s/offer-dev:.*\$/offer-dev:${NEW_TAG}/g" ./services/offer-be-rollout/rollout.yaml
                        git config --global user.email "email hear"
                        git config --global user.name "git user name hear"
                        git add ./services/offer-be-rollout/rollout.yaml
                        git commit -m "[FROM Jenkins] Container Image Tag was changed to ${NEW_TAG}"
                        git push
                        cd $MAIN_PATH
                    '''
                }
            }
        }
    }
}

 

 

Jenkinsfile 작성 중 마주했던 이슈

1) 스크립트 파일 실행은 결국 새로운 프로세스 실행이기 때문에, sh로 스크립트 실행 시, Jenkinsfile에서 선언한 변수를 ${변수명}처럼 바로 가져다 쓸 수 없다. 이 경우 스크립트에 입력으로 설정해야 한다. 보통 스크립트 실행 시 입력은 {스크립트 파일명} {입력1} {입력2} 와 같은 형태로 실행 시에 주는데, Jenkinsfile에서는 sh 메소드를 통해 실행하기 때문에 추가적인 방법이 있는지 알아봐야 할 듯하다.

 

2) sed 명령어 작성 시 정규식 문제

sed 명령어 기본 사용법

sed -i "s/old-word/new-word/g" input.txt

input.txt 파일에서 'old-word'를 모두 찾아 'new-word'로 변경한다. i 옵션은 변경된 파일을 input.txt에 그대로 덮어씌운다. 이때 old-word에 ":" 뒷 부분에 나오는 any 문자를 매칭시켜야 하는데 이 부분에서 정규식 작성에 어려움이 있었다. 결론부터 말하자면 ChatGPT를 통해 쉽게 해결할 수 있었다.

 

** The regular expression "my-app:.*$" matches any string that starts with "my-app:" and ends with the end of the line.

 

 

3) Jenkins에서 docker hub 접속

Jenkins를 현재 컨테이너로 실행한 상태이므로 Jenkins 안에서 docker 커맨드를 사용하려면 추가적인 방법이 필요하다. Jenkins 컨테이너 실행 시 "-v /var/run/docker.sock:/var/run/docker.sock" 를 통해 호스트와 같은 docker 소켓을 사용하도록 볼륨 마운트를 추가했지만 해결되지 않았다. 그래서 docker 커맨드를 사용하지 않고 컨테이너 이미지를 생성할 수 있는 jib(애플리케이션 build 과정에 컨테이너 이미지 빌드 과정을 포함시키는 플러그인)을 이용해서 해결했다.

 

cf. Jib: https://jh-labs.tistory.com/509

 

 

4) 컨테이너 이미지 태그 자동화

가장 최신 커밋의 해시값 일부를 사용하도록 함으로써 신규 태그를 자동 생성하고 Rollout Manifest 파일을 수정했다.

 

신규 배포 알림으로 확인 - Slack

위와 같이 Argo Rollout Manifest가 저장된 git 레포지토리 상에서 Argo Rollout Manifest의 컨테이너 이미지 태그가 변경되었으므로(커밋이 발생했으므로) Canray 배포가 진행된다.

 

 

 

[관련 포스팅]

- ArgoCD 구축 및 Application 기반 배포: https://jh-labs.tistory.com/671

- Argo Rollout 컨트롤러 기반 Canary 배포: https://jh-labs.tistory.com/672

 

 

 

Reference

- Jenkins ArgoCD, https://hwannny.tistory.com/113

- Container Image tagging with commit hash, https://axellarsson.com/blog/tag-docker-image-with-git-commit-hash/

- Tagging Docker images the right way, https://blog.container-solutions.com/tagging-docker-images-the-right-way