쿠버네티스 시작하기(9) - CI/CD 파이프라인 만들기(3/3) - Gitlab webhook 설정 및 Slack 연동
이전 글에서 쿠버네티스 클러스터에 올라간 Jenkins와 Gitlab간의 연동을 시킨 후 컨테이너 배포까지 수행해 보았다.
이번 글에서는 Gitlab으로 로컬PC의 소스 Push/Merge 시 자동으로 Jenkins에서 빌드/배포가 수행되는 프로세스를 만들어 볼 것이고, 빌드/배포가 완료된 후에는 Slack을 통해 알람 메시지를 전송할 것이다.
1. 시작하기 전에
현재까지 완성된 빌드/배포 프로세스를 보면 아래와 같다.
- 개발/운영자가 로컬PC에서 소스 변경 후 git push/merge를 통해 Git Repository(gitlab/github ...)로 소스 업로드 -> 완료
- 소스가 업로드되면 Git Repository에서 Jenkins로 Webhook 전송 ->미완료
- jenkins에서 Webhook을 통해 소스 변경 사실을 인지한 후, 빌드툴(maven/gradle/npm ...)을 활용하여 소스 빌드 수행 -> 미완료
- 소스 빌드 완료 후 Docker Image로 빌드 -> 완료
- 빌드된 Docker Image를 Docker Registry(Docker hub, Habor ...)로 전송 -> 완료
- kubectl create/apply를 통해 업로드된 image를 down 후 pod container로 생성/배포 -> 완료
- 배포 성공/실패 후 Slack을 통한 알람 전송 -> 미완료
그럼 우선 Git Repository(Gitlab)에서 Jenkins로 Webhook을 전송하는 것에 대해서 공부해보도록 하자.
2. Gitlab Webhook 등록
Gitlab Webhook은 Gitlab Repository의 소스 변경 시 해당 작업이 수행되었다는 것을 HTTP POST로 알리는 작업을 말한다. Gitlab Webhook을 등록하는 방법은 아래와 같다.
2-1. Jenkins에서 Gitlab Hook Plugin 설치
해당 작업은 쿠버네티스 클러스터에 Jenkins 설치 시 진행을 했었다. 때문에 이전 장에서 진행한대로 수행했다면 생략해도 된다(https://twofootdog.tistory.com/11).
2-2. Jenkins Wehook 설정
Jenkins 웹페이지로 이동하여 설정을 진행할 Jenkins pipeline 선택 -> 구성 -> Build Triggers로 가서 Build when a change is pushed to GitLab를 선택한다.
Allowed branches의 include에 원하는 git branch 값을 입력하고(필자는 master branch입력), Secret token 우측에 Generate 버튼을 클릭해서 token을 발급받는다. 해당 Token은 Gitlab에 webhook 등록 시 사용할 것이다.
2-3. Gitlab Webhook 설정
이제 Gitlab webhook 설정을 해보자. gitlab 사이트(https://gitlab.com/)로 가서 로그인 한 후 Jenkins와 연동된 프로젝트를 선택한다.
그리고 좌측에 Settings -> Integration 을 선택한다.
URL에는 2-2 Jenkins 설정 에서 Build when a change is pushed to GitLab 체크 시 옆에 있던 URL을 입력하고 Secret Token은 Jenkins 설정에서 생성한 Secret Token 값을 입력한다.
다음에 밑에 있는 Add webhook 버튼을 눌러 Webhook을 등록하고 Test-> Push events를 클릭한다.
그러면 Jenkins에서 해당 Push event를 받고 정상적으로 배포가 진행되는 것을 확인할 수 있다.
3. Slack 알람 등록
이제 Webhook으로 Jenkins 자동 빌드/배포 프로세스를 완성했으니, 빌드/배포가 수행된 후 Slack을 통해 알람메시지가 전송되는 프로세스를 만들어보자.
3-1. Slack 신규 Workspace 생성
Slack(https://slack.com/)에 가서 GET STARTED를 선택한다.
본인의 이메일을 입력하고 Continue 버튼을 클릭한다.
이메일로 가서 Confirm Email Address 버튼을 클릭한다.
Get Started 버튼을 클릭해서 새로운 Workspace를 생성해준다.
팀 이름 & 프로젝트 명 & 팀원의 이메일을 입력한다.
생성한 Workspace로 들어가면 해당 Workspace의 URL를 확인할 수 있다. 해당 URL은 추후 Workspace 로그인 할 때 사용하게 되며, 바로 아래 단계에서 Slack Token을 생성 할 때에도 활용하게 된다.
3-2. Slack Token 생성
이제 Slack Token을 생성할 차례다. Workspace 생성 시 제공받은 URL/apps로 들어가서 Jenkins CI를 검색한다(이 포스트에서는 http://testteam-49b6088.slack.com/apps 이다).
좌측 상단의 Install 혹은 Add to Slack 버튼을 클릭한다.
채널을 선택한다. 필자는 채널명을 auth라고 지었으므로 # auth라고 입력하고 Add Jenkins CI integration 버튼을 클릭한다. 해당 채널명은 조금 전 Slack Workspace를 만들 때 입력했던 프로젝트명과 동일하다(만약 해당 Workspace에 여러개의 채널이 존재한다면, 그 중 한개의 채널명만 입력하고 Jenkinsfile에서 여러개의 채널 중 알람 전송할 채널명을 작성하면 해당 채널로 알람 전송이 가능하다).
그러면 아래와 같은 Setup Instructions가 나오는데, 설명대로 진행을 하면 된다. Post Channel과 Token은 Jenkins에서 입력을 해야 하므로 기억해두도록 하자. 그리고 Save Settings 를 클릭한다.
우선 STEP2에 나온대로 Jenkins 웹페이지로 접속 -> Jenkins관리 -> Plugin 관리에서 Slack Notification을 설치해준다.
다음 STEP3에 나온대로 Jenkins 관리 -> 시스템설정 -> Slack 으로 가서 아래와 같이 입력해준다. Credential 에는 Secret Text를 선택한 후 위 Slack 설정 시 얻은 Token 값을 입력하면 된다. Default channel/member id에는 Slack 알람을 날릴 채널명을 입력하면 된다. Slack 설정 시 Post to Channel에서 입력했던 채널명을 입력하자. 셋팅이 완료되었으면 Test Connection 버튼을 눌러준다. Success 메시지가 뜨면서 Slack으로도 알람이 온다면 설정이 완료된 것이다.
4. Jenkinsfile 변경
위 설정을 통해 Slack 환경설정까지 완료되었다. 그러면 로컬PC 소스에서 git push 를 하게 되면 Slack을 통해 알람이 올까? 그렇지 않다. 왜냐하면 Jenkinsfile을 수정해주지 않았기 때문이다. 위와 같이 Slack 환경설정을 한 후 변경/배포 프로젝트의 소스 내 Jenkinsfile에 Slack 함수를 호출해서 어느 시점에 Slack알람이 올지를 지정해줘야 한다.
때문에 아래와 같이 Jenkinsfile을 수정해보자.
Jenkinsfile :
/* pipeline 변수 설정 */
def DOCKER_IMAGE_NAME = "twofootdog/project-repo" // 생성하는 Docker image 이름
def DOCKER_IMAGE_TAGS = "batch-visualizer-auth" // 생성하는 Docker image 태그
def DOCKER_CONTAINER_NAME = "auth-container" // 생성하는 Docker Container 이름
def NAMESPACE = "ns-project"
def SLACK_CHANNEL = "auth"
def DATE = new Date();
/* Slack 시작 알람 함수 */
def notifyStarted(slack_channel) {
slackSend (channel: "${slack_channel}", color: '#FFFF00', message: "STARTED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}
/* Slack 성공 알람 함수 */
def notifySuccessful(slack_channel) {
slackSend (channel: "${slack_channel}", color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}
/* Slack 실패 알람 함수 */
def notifyFailed(slack_channel) {
slackSend (channel: "${slack_channel}", color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}
podTemplate(label: 'builder',
containers: [
containerTemplate(name: 'gradle', image: 'gradle:5.6-jdk8', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'kubectl', image: 'lachlanevenson/k8s-kubectl:v1.15.3', command: 'cat', ttyEnabled: true)
],
volumes: [
hostPathVolume(mountPath: '/home/gradle/.gradle', hostPath: '/home/admin/k8s/jenkins/.gradle'),
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
//hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker')
]) {
node('builder') {
try {
stage('Start') {
/* Slack 메시지 전송 */
notifyStarted(SLACK_CHANNEL)
}
stage('Checkout') {
checkout scm // gitlab으로부터 소스 다운
}
stage('Build') {
container('gradle') {
/* 도커 이미지를 활용하여 gradle 빌드를 수행하여 ./build/libs에 jar파일 생성 */
sh "gradle -x test build"
}
}
stage('Docker build') {
container('docker') {
withCredentials([usernamePassword(
credentialsId: 'docker_hub_auth',
usernameVariable: 'USERNAME',
passwordVariable: 'PASSWORD')]) {
/* ./build/libs 생성된 jar파일을 도커파일을 활용하여 도커 빌드를 수행한다 */
sh "docker build -t ${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_TAGS} ."
sh "docker login -u ${USERNAME} -p ${PASSWORD}"
sh "docker push ${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_TAGS}"
}
}
}
stage('Run kubectl') {
container('kubectl') {
withCredentials([usernamePassword(
credentialsId: 'docker_hub_auth',
usernameVariable: 'USERNAME',
passwordVariable: 'PASSWORD')]) {
/* namespace 존재여부 확인. 미존재시 namespace 생성 */
sh "kubectl get ns ${NAMESPACE}|| kubectl create ns ${NAMESPACE}"
/* secret 존재여부 확인. 미존재시 secret 생성성 */
sh """
kubectl get secret my-secret -n ${NAMESPACE} || \
kubectl create secret docker-registry my-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username=${USERNAME} \
--docker-password=${PASSWORD} \
--docker-email=ekfrl2815@gmail.com \
-n ${NAMESPACE}
"""
/* k8s-deployment.yaml 의 env값을 수정해준다(DATE로). 배포시 수정을 해주지 않으면 변경된 내용이 정상 배포되지 않는다. */
sh "echo ${DATE}"
sh "sed -i.bak 's#DATE_STRING#${DATE}#' ./k8s/k8s-deployment.yaml"
/* yaml파일로 배포를 수행한다 */
sh "kubectl apply -f ./k8s/k8s-deployment.yaml -n ${NAMESPACE}"
sh "kubectl apply -f ./k8s/k8s-service.yaml -n ${NAMESPACE}"
}
}
}
notifySuccessful(SLACK_CHANNEL)
} catch(e) {
/* 배포 실패 시 */
currentBuild.result = "FAILED"
notifyFailed(SLACK_CHANNEL)
}
}
}
위 Jenkinsfile을 보면 Slack 시작을 알리는 함수(notifyStarted), 배포 성공을 알리는 함수(notifySuccessful), 배포 실패를 알리는 함수(notifyFailed)를 각각 선언하고, 필요한 시점에 해당 함수를 호출하였다.
5. 변경/배포 테스트
자 이제 변경/배포 파이프라인이 완성되었다. 그러면 로컬PC에서 소스를 변경 한 후 git push를 해보자.
Jenkins log를 확인해보면 아래와 같이 배포가 정상적으로 완료되는 것을 확인할 수 있다.
또한 Slack을 확인해보면 알람까지 온 것을 확인할 수 있다. 성공이다!!!!!!!!!!
마치며
지금까지 3개의 포스트에 걸쳐서 쿠버네티스 클러스터 & Jenkins & Gitlab & Slack을 활용하여 CI/CD 파이프라인을 구축해보았다. 사실 이번 파이프라인에서는 Git Branch도 나누지 않았고(오직 master로 push), 개발기와 운영기를 분리해서 배포하지도 않았고, CI/CD가 완벽하게 구현된 것은 아니다(JUnit 테스트도 수행되지 않았다). 하지만 쿠버네티스 클러스터 환경에 대한 이해 및 CI/CD 파이프라인 구축의 기본기는 다질 수 있을 거라 확신하며, On-Premise 환경에서의 CI/CD 파이프라인을 구축하는데 조금은 도움이 될 거라 확신한다(필자도 이 파이프라인을 구축해보면서 정말 많은 공부가 되었다).
다음장부터는 이 파이프라인에 살을 붙이는 내용을 포스팅할 것이다.
- 우선 Sonarqube & Jacoco & JUnit TEST를 붙여서 CI/CD 파이프라인에 TEST Step을 넣는 방법에 대해 포스팅할 것이다.
- 그 다음으로 NodeExporter & Prometheus & AlertManager를 붙여서 쿠버네티스 클러스터 & 서버CPU/Memory/저장소 & SpringBoot API 모니터링 프로세스를 구축하는 내용도 포스팅할 것이다.
- 그 다음엔 Grafana를 붙여서 해당 모니터링 프로세스를 시각화 하는 내용도 포스팅할 것이다.
수고하셨습니다.