4. Helm chart 이용 Jenkins CI/CD Pipeline: 무중단 배포
이전 글에서 배포시에는 helm chart로 기존에 배포한 모든 리소스(deployment, service, ingress 등)를 지우고 다시 chart를 설치하였습니다. 실제 운영시에 이렇게 배포하면 잠깐의 서비스 중단이 발생합니다.
https://happycloud-lee.tistory.com/10?category=832245
이번에는 서비스 중단없이 설치해 보도록 하겠습니다.
핵심은 기존에 helm chart로 설치되었는지 먼저 확인하고, 이미 한번 설치되었으면 upgrade를 하고 설치가 안되어 있다면 install을 하는것입니다.
1. Jenkins file 수정
이전 글에서 작성한 Jenkinsfile 파일을 아래와 같이 수정합니다.
Jenkinsfile
def label = "hello-helm-${UUID.randomUUID().toString()}"
/* -------- functions ---------- */
def notifySlack(STATUS, COLOR) {
slackSend (color: COLOR, message: STATUS+" : " + "${env.JOB_NAME} [${env.BUILD_NUMBER}] (${env.BUILD_URL})")
}
def notifyMail(STATUS, RECIPIENTS) {
emailext body: STATUS+" : " + "${env.JOB_NAME} [${env.BUILD_NUMBER}] (${env.BUILD_URL})",
subject: STATUS + " : " + "${env.JOB_NAME} [${env.BUILD_NUMBER}]",
to: RECIPIENTS
}
/* ------------------------------ */
def emailRecipients="hiondal@gmail.com,hiondal@daum.net"
notifySlack("STARTED", "#FFFF00")
notifyMail("STARTED", "${emailRecipients}")
podTemplate(
label: label,
containers: [
//container image는 docker search 명령 이용
containerTemplate(name: "docker", image: "docker:stable", ttyEnabled: true, command: "cat"),
containerTemplate(name: "helm", image: "dtzar/helm-kubectl", ttyEnabled: true, command: "cat")
],
//volume mount
volumes: [
hostPathVolume(hostPath: "/var/run/docker.sock", mountPath: "/var/run/docker.sock")
]
)
{
node(label) {
stage("Get source from gitlab") {
git "http://gitlab.169.56.164.244.nip.io:31836/ondalk8s/hello-helm.git"
}
//-- 환경변수 파일 읽어서 변수값 셋팅
def props = readProperties file:"./deployment/pipeline.properties"
def tag = props["version"]
def dockerRegistry = props["dockerRegistry"]
def credentialRegistry=props["credentialRegistry"]
def image = props["image"]
def baseDeployDir = props["baseDeployDir"]
def helmRepository = props["helmRepository"]
def helmChartname = props["helmChartname"]
def helmChartfile = "${baseDeployDir}/${helmChartname}-${tag}.tgz"
def releaseName = props["releaseName"]
def namespace = props["namespace"]
try {
stage("Buil/push App image") {
container("docker") {
docker.withRegistry("${dockerRegistry}", "${credentialRegistry}") {
sh "docker build -f ${baseDeployDir}/Dockerfile -t ${image}:${tag} ."
sh "docker push ${image}:${tag}"
sh "docker tag ${image}:${tag} ${image}:latest"
sh "docker push ${image}:latest"
}
}
}
//--- 무중단 배포를 위해 clean up 하지 않음
/*
stage( "Clean up current deployments" ) {
container("helm") {
try {
sh "helm delete ${releaseName} --purge"
} catch(e) {
echo "Clean-up Error : " + e.getMessage()
echo "Continue process !"
}
}
}
*/
stage( "Deploy to cluster" ) {
container("helm") {
boolean isExist = false
//====== 이미 설치된 chart 인지 검사 =============
String out = sh script: "helm ls -q --namespace ${namespace}", returnStdout: true
if(out.contains("${releaseName}")) isExist = true
//===========================
if (isExist) {
echo "Already installed. I will upgrade it with chart file."
sh "helm upgrade ${releaseName} ${helmChartfile}"
} else {
echo "Install with chart file !"
sh "helm install ${helmChartfile} --name ${releaseName} --namespace ${namespace}"
}
}
}
notifySlack("${currentBuild.currentResult}", "#00FF00")
notifyMail("${currentBuild.currentResult}", "${emailRecipients}")
} catch(e) {
currentBuild.result = "FAILED"
notifySlack("${currentBuild.currentResult}", "#FF0000")
notifyMail("${currentBuild.currentResult}", "${emailRecipients}")
}
}
}
소스 설명은 아래와 같습니다.
- Stage 'Clean up current deployments'는 remark 또는 삭제를 합니다.
- Stage 'Deploy to cluster'를 아래와 같이 수정합니다.
stage( "Deploy to cluster" ) {
container("helm") {
boolean isExist = false
//====== 이미 설치된 chart 인지 검사 =============
String out = sh script: "helm ls -q --namespace ${namespace}", returnStdout: true
if(out.contains("${releaseName}")) isExist = true
//===========================
if (isExist) {
echo "Already installed. I will upgrade it with chart file."
sh "helm upgrade ${releaseName} ${helmChartfile}"
} else {
echo "Install with chart file !"
sh "helm install ${helmChartfile} --name ${releaseName} --namespace ${namespace}"
}
}
}
- boolean isExist = false
이미 배포되었는지 여부를 담을 boolean 변수를 false로 초기화
- String out = sh script: "helm ls -q --namespace ${namespace}", returnStdout: true
배포될 namespace(여기서는 'helm')에 helm으로 설치된 목록을 변수 'out'으로 리턴 받음
- if(out.contains("${releaseName}")) isExist = true
releaseName(여기서는 'release-hello-helm')이 결과 문자열에 포함되어 있으면 isExist를 true로 셋팅
- if(isExist) { ... sh "helm upgrade ${releaseName} ${helmChartfile}" ..
이미 설치되어 있으면 helm upgrade 수행
- else { ... sh "helm install ${helmChartfile} --name ${releaseName} --namespace ${namespace}" ...
아직 설치되어 있지 않으면 helm install 수행
2. 테스트
- gitlab에 push 합니다.
- Jenkins pipeline에서 다시 build합니다.
- Blue Ocean에서 로그를 보면 helm install을 할 겁니다.
- 다시한번 Jenkins pipeline에서 build합니다.
- 이번에는 이미 설치되어 있으므로, helm upgrade를 할 겁니다.