티스토리 뷰

728x170

Kubernetes와 같은 클라우드 환경으로 접어 들면서 기존 3Tier 환경과는 다르게 배포 환경이 복잡하고 많아졌습니다.
특히 마이크로서비스로 접어 들면서 수많은 서비스들을 관리하고 배포하는 것은 굉장히 복잡하고 어려운 일이 되었습니다.
이에 자동 빌드 및 배포환경을 구성하고 자동화 환경을 구성하는 것은 필수요소가 되었습니다.
이번 포스팅에서는 Jenkins pipeline job을 이용한 CI/CD 구성방안에 대해 살펴보겠습니다.

Jenkins 관련 포스팅은 아래를 참고하시기 바랍니다.
[Jenkins] Jenkins 설치 가이드
[Jenkins] Jenkins를 활용한 WildFly CI/CD 환경 구성
[Jenkins] 사용자 관리 및 권한 관리 방안
[Jenkins] Pipeline Syntax (젠킨스 파이프라인 문법)


Jenkins Credential 구성

Jenkins Credential 정보를 등록합니다. Jenkins를 배포 도구로 활용하며 앞에는 GitLab, 뒤에는 Kubernetes가 구성되어 있는 구조로 각각 연결을 위한 Credential 정보를 등록해 주어야 합니다.
1) GitLab Credential

- Jenkins → Credentials → System에서 위와 같이 GitLab 관련 Credential 정보를 입력합니다. Username / Password를 입력하고 ID 정보는 실제 Pipeline에서 사용하는 정보로 Description에 동일하게 입력하여 확인하기 쉽도록 저장합니다.
2) Kubernetes Credential

- Jenkins → Credentials → System에서 위와 같이 Kubernetes 관련 Credential 정보를 입력합니다. Kubernetes Credential을 등록하기 위해서는 Kubernetes Continuous Deploy라는 Plug-In이 설치되어 있어야 하며, 설치가 되어 있지 않을 경우에는 GitLab → Jenkins 관리 → 플러그인 관리에서 플러그인을 설치합니다.

- Kubernetes의 Credential을 등록하는 방법은 총 3가지로 첫번째 Enter directly 방식은 Mater Node에 등록되어 있는 Kubernetes Credential (.kube/config) 내용을 그대로 넣어주는 방식입니다. 그 밖에도 Jenkins Home에 kubeconfig를 넣어주거나 직접 Kubernetes Master Node로 부터 입력받는 방법들을 사용할 수 있습니다.
- 마찬가지로 ID 정보가 Pipeline에서 사용되는 정보이므로 반드시 기억하도록 합니다.
# 참고
Jenkins Version이 올라가며, EKS와 연동 시 플러그인 버전 차이로 인한 배포 실패가 발생할 수 있습니다.
이때, Manually하게
- jackson2-api-2.10.3.hpi
- kubernetes-cd-2.3.0.hpi
- snakeyaml-api.hpi
- azure-common-1.0.5.hpi
를 설치하여 원 버전을 다운그레이드 한 후 배포 처리하면 정상처리 됨을 확인할 수 있습니다.


Jenkins Pipeline 구성

1) Jenkins pipeline 생성 (새로운 Item → Pipeline → OK 버튼)

위와 같이 Kubepipe name의 Pipeline을 생성합니다.
2) Pipeline Script 작성

하단의 Pipeline Script에 Pipeline을 정의합니다.
pipeline에 정의된 정보들의 서버는 다음과 같이 구성되어 있습니다.
- 192.168.56.100 : Jenkinx, Nexus3(Docker Registry)
- 192.168.56.101 : GitLab
- 192.168.56.102 : Kubernetes Mater Node
- 192.168.56.103 : Kubernetes Worker Node

pipeline { agent any parameters { string(name: 'tag', defaultValue: 'latest', description: 'tag {YYYYMMDD}{HHMMSS}') string(name: 'buildtag', defaultValue: 'v1', description: 'tag {YYYYMMDD}{HHMMSS}') string(name: 'GIT_URL', defaultValue: 'http://192.168.56.101/root/springboot.git', description: 'GIT_URL') booleanParam(name: 'VERBOSE', defaultValue: false, description: '') } environment { GIT_BUSINESS_CD = 'master' GITLAB_CREDENTIAL_ID = 'superuser' IMAGE_REGISTRY = '192.168.56.100:5000' SYSTEM_CODE = 'middleware' SVC_CODE = 'wildfly/wildfly_custom_image' IMAGE_REPO = "${IMAGE_REGISTRY}/${SYSTEM_CODE}/${SVC_CODE}" DOCKER_USERNAME = 'admin' DOCKER_PASSWORD = 'admin123' VERBOSE_FLAG = '-q' } stages{ stage('Preparation') { // for display purposes steps{ script{ env.ymd = sh (returnStdout: true, script: ''' echo `date '+%Y%m%d-%H%M%S'` ''') } echo("params : ${env.ymd} " + params.tag) } } stage('Checkout') { steps{ git(branch: "${env.GIT_BUSINESS_CD}", credentialsId: "${env.GITLAB_CREDENTIAL_ID}", url: params.GIT_URL, changelog: false, poll: false) } } stage('SonarQube analysis') { steps{ withSonarQubeEnv('SonarQube-Server'){ sh "mvn sonar:sonar -Dsonar.projectKey=demo -Dsonar.host.url=http://192.168.123.141:9000 -Dsonar.login=03a3d935387d5a8bb8894ff0a0f282055f39466a" } } } stage('SonarQube Quality Gate'){ steps{ timeout(time: 1, unit: 'MINUTES') { script{ echo "Start~~~~" def qg = waitForQualityGate() echo "Status: ${qg.status}" if(qg.status != 'OK') { echo "NOT OK Status: ${qg.status}" updateGitlabCommitStatus(name: "SonarQube Quality Gate", state: "failed") error "Pipeline aborted due to quality gate failure: ${qg.status}" } else{ echo "OK Status: ${qg.status}" updateGitlabCommitStatus(name: "SonarQube Quality Gate", state: "success") } echo "End~~~~" } } } } stage('Build') { steps{ sh 'mvn clean package' } } stage('Docker build') { steps{ script { env.IMAGE_TAG = "${params.tag}" env.IMAGE_LOC = env.IMAGE_REPO + ':' + env.IMAGE_TAG } sh "rm -rf docker ; mkdir docker" sh "cp target/template.war docker/springboot.war" sh "cp wboot.sh docker/wboot.sh" sh "cp Dockerfile docker/" sh "chmod -R 775 docker" dir('docker') { sh "docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD} ${IMAGE_REGISTRY}" echo "docker build to ${IMAGE_LOC}" sh "docker build -t ${IMAGE_LOC} ${env.VERBOSE_FLAG} --rm --force-rm --pull --network host ." echo "docker push" sh "docker push ${IMAGE_LOC}" } } } stage('Kubernetes deploy') { steps { kubernetesDeploy configs: "deployment.yaml", kubeconfigId: 'springboot' sh "kubectl --kubeconfig=/root/.jenkins/.kube/config rollout restart deployment/wildfly-deployment" } } stage('Clear') { steps { sh "docker rmi \$(docker images -f 'dangling=true' -q)" } } } }


1) parameters : tag와 git url, verbose 옵션을 포함합니다.Pipeline으로 자동화 하는 단계는 다음과 같습니다.

2) environment : 상세 변수등을 사전에 정의합니다. image 관련 정보 및 user, password 등 환경에 따라 자주 변경되는 데이터를 정의합니다. 이때 GITLAB_CREDENTIAL_ID = 'superuser'의 superuser가 앞서 추가한 GitLab Credential의 ID와 동일해야 함을 유의합니다.
3) stages
- Preparation : 준비 단계에서는 해당 빌드 과정에 대한 간단한 Description을 담고 있습니다.
- Checkout : Checkout 단계에서는 Gitlab 인증을 거쳐 소스 파일을 Checkout 받습니다.
- Build : 빌드 단계에서는 Checkout 받은 소스를 컴파일합니다.
- Docker build : 실질적인 가장 중요한 stage라고 볼 수 있습니다. Dockerfile을 이용하여 docker 이미지를 생성하기 위한 Step을 정의합니다. 생성이 완료된 이미지는 docker private registry에 push 되도록 구성하였습니다.
- Kubernetes deploy : Kubernetes에 배포하기 위해 Kubernetes plugin을 설치하고 deployment.yaml 파일을 정의하여 배포하였습니다. 이때 kubeconfigId: 'springboot'의 springboot 정보가 앞서 추가한 kubernetes credential ID와 맞아야 합니다.
- Clear : 로컬 Repository에 구성되어 있는 도커 이미지를 정리합니다.

사실상 Kubernete 배포를 위한 Jenkins의 CI 환경을 잘 구성하느냐는 바로 이 Pipeline을 얼마나 유연하게 작성할 수 있느냐에 달려있다고 볼 수 있습니다.
Pipeline 정의까지 완료되면 다음으로 GitLab에 구성된 Application과 Dockerfile 등의 파일을 다시한번 검증합니다.
현재 Pipeline의 Docker build stage에서는 다음과 같은 작업이 수행되도록 작성하였습니다.

sh "rm -rf docker ; mkdir docker" → 도커 빌드를 위한 docker 디렉토리 생성 sh "cp target/template.war docker/springboot.war" → springboot application war 파일을 docker 디렉토리로 복사 sh "cp wboot.sh docker/wboot.sh" → wboot.sh이라는 wildfly 기동 스크립트를 docker 디렉토리로 복사 sh "cp Dockerfile docker/" → Dockerfile을 docker 디렉토리로 복사 sh "chmod -R 775 docker" → docker 디렉토리 권한 부여 dir('docker') { sh "docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD} ${IMAGE_REGISTRY}" → 도커 로그인 echo "docker build to ${IMAGE_LOC}" sh "docker build -t ${IMAGE_LOC} ${env.VERBOSE_FLAG} --rm --force-rm --pull --network host ." → 도커 태그 생성 echo "docker push" sh "docker push ${IMAGE_LOC}" → 도커 이미지 push }

위 과정과 같이 Docker 이미지를 생성하기 위해서는 template.war, wboot.sh, Dockerfile이 GitLab Repository에 정의되어 있어야 합니다.
GitLab에 구성되어 있는 정보와 이를 기반으로 Pipeline을 실행하는 과정은 다음 포스팅에서 이어서 테스트해 보도록 하겠습니다.

#참고
폐쇄망 환경의 경우 mvn을 실행하기 위해 maven repository 경로 설정이 필요하다.
maven repository로 많이 활용되는 nexus를 구성할 경우 다음의 절차를 따른다.
- $USER_HOME/.m2/settings.xml 파일을 복사하고 servers 항목에 id, username, password를 추가한다.
- password가 특수문자를 포함 할 경우 mvn --encrypt-password [PASSWORD] 구문으로 생성되는 {ENCRYPT_PASSWORD}를 settings.xml의 server/password에 그대로 기입한다.

그리드형
댓글
  • 프로필사진 noah 질문이 있습니다.. Kubernetes Continuous Deploy은 젠킨스에서 쿠버 클러스터에 접근할 때 사용되는 거고 거기까지는 잘 됬습니다. 하지만 거기서 Deployment를 실행하려고 하는데 serviceAccount 설정이 필요하지 않나요 ? 제가 알기로는 젠킨스 같은 시스템으로 접근할 때는 anonymous로 접근이 되서 serviceAccount 설정을 해줘야 한다고 들었거든요 .. 2020.11.26 17:18
  • 프로필사진 GodNR deployment가 가능한 managed credential을 추가해두었고 그 kubeconfig를 이용하여 배포합니다. 2020.12.04 19:46 신고
  • 프로필사진 ZunoXI 파이프라인 구축하는데 큰 도움이 되고있습니다. 혹시 kubernetes deploy stage에서 "kubectl --kubeconfig=/root/.jenkins/.kube/config rollout restart deployment/wildfly-deployment" 는 어디서 확인할 수 있는경로일까요? 모든서버가 동일한지 궁금합니다. 저는 바로 위에줄의 영향으로 쿠버네티스에 yaml 파일이 정상적으로 적용은되는데 'sh'에서 kubectl을 찾을 수 없다고 rollout 사용이 안되네요...ㅜ 2021.06.17 17:27 신고
댓글쓰기 폼