
이 Winection 프로젝트는 협업으로 진행되기 때문에 젠킨스를 잘 모르는 팀원들도 빌드 결과를 쉽게 확인할 수 있도록 하면 좋겠다는 생각이 들었다.
그래서 깃에 푸시하거나 머지하는 것만으로도 젠킨스가 자동으로 빌드를 실행하고, 디스코드 봇이 실시간으로 결과를 알림으로 보내주는 시스템을 만들어 보기로 했다.
이번 글에서는 젠킨스와 디스코드 봇을 연동하여 CI/CD 빌드 알림을 자동화하는 과정을 다룬다.
디스코드 봇을 생성하고 젠킨스와 연결하는 방법, 그리고 실제로 Jenkinsfile을 수정하여 빌드 시작, 성공, 실패 시 알림을 전송하는 과정을 정리해보겠다.
https://discord.com/developers/applications
위의 사이트로 들어가면 디스코드 봇을 만들 수 있는 사이트가 있다.
이 사이트에 들어가면

좌측 상단에 New Application 버튼을 누른다.

봇 Name을 정하고 약관에 동의하고 Create버튼을 누른다.

좌측 탭의 Installation을 눌러 Install Link를 None으로 바꿔준다.(private로 설정할 예정이기 때문이다.)

좌측의 Bot으로 이동한다.

Public Bot 체크설정을 풀어준다.(서버에 있는 사람들만 사용할 거기 때문에)

MESSAGE CONTENT INTENT 설정은 무조건 체크해줘야한다. -> 디스코드에서 메시지를 입력했을 때 백엔드에 제대로 넘어올 수 있다.
좌측 탭의 OAuth2를 클릭해 이동한다.


bot를 체크한다.

Send Messages를 체크한다.
이 과정을 마치면 아래 GENERATED URL를 Copy해준다.
이제 팀원들과 있는 디스코드 서버로 이동한다
해당 서버의 채팅방에 복사한 링크를 붙여넣고 이 링크를 클릭한다.


다음 뜨는 항목들에 다음, 클릭 등 눌러주면 정상적으로 봇이 초대가 완료된다.

만든 디스코드 봇이 있는 서버의 아이콘을 오른쪽 클릭하고 서버 설정 -> 연동으로 들어간다.


웹후크 만들기를 눌러 웹후크 설정창에 들어가고 새 웹후크 버튼을 누른다.

봇이 메세지를 보낼 채널을 선택하고 이름을 설정해준다.
웹후크 URL 복사 버튼을 클릭한다.
이제 젠킨스 사이트로 다시 이동해보자.
디스코드에서 만든 웹훅을 Credentials에 추가하면 된다.

Jenkins 관리 -> Credentals -> Stores scoped to Jenkins에 있는 (global) -> +Add Credentials
Kind를 Secret text로 바꿔준다.

Secret에 우리가 복사한 토큰을 입력해주고, ID는 파이프라인에서 사용할거라서 기억해놔야한다.

여기서
discode_webhook이라 되어있는데 나중에 오타 발견해서discord_webhook으로 변경하였다.
모든 설정이 완료되면 Create 버튼을 눌러준다.
디스코드와 젠킨스 간에 연결을 위해 젠킨스에서 Jenkins 관리 -> Plugins -> Available plugins에 가보면 Discode Notifier가 있다는데 나는 이게 아무리 찾아봐도 안보였다.
https://plugins.jenkins.io/discord-notifier/releases/
여기 사이트로 가서 최근 버전의 discode notifier를 설치하는 방법을 알아냈다.
우선 기존에 젠킨스 도커 컨테이너에 접근한다.
docker exec -it jenkins /bin/bash
그 후 직접 플러그인을 설치한다.
jenkins-plugin-cli --plugins discord-notifier:260.v8f28622b_a_6b_7
exit로 컨테이너를 빠져나오고 docker restart jenkins를 통해 컨테이너를 재시작한다.
다시 젠킨스 사이트로 가보면

해당 플러그인이 설치되었다.
이제 젠킨스파일에서 바로 웹훅을 보낼 수 있도록 수정해보자
나는 빌드 시작할 때 메시지 보내고, 배포 완료 혹은 실패 시에 Bot이 메시지를 보낼 수 있게 했다.
프론트엔드 Jenkinsfile
pipeline {
agent any
environment {
VITE_SERVER_URL = credentials('vite_server_url')
DISCORD = credentials('discord_webhook')
}
stages {
stage('Start Notification') {
steps {
script {
discordSend description: "젠킨스 배포를 시작합니다!",
link: env.BUILD_URL,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 시작",
webhookURL: env.DISCORD
}
}
}
stage('Checkout') {
steps {
git branch: 'main', credentialsId: 'github_token', url: 'https://github.com/DongUgaUga/winection-web.git'
}
}
stage('Setup') {
steps {
script {
sh '''
echo "VITE_SERVER_URL=$VITE_SERVER_URL" > .env
chmod 600 .env
'''
}
}
}
stage('Deploy') {
steps {
script {
sh "docker-compose down"
sh "docker-compose up -d --build web"
}
}
}
}
post {
success {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
footer: "빌드 성공!",
link: env.BUILD_URL, result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: env.DISCORD
}
failure {
script {
def logs = currentBuild.rawBuild.join("\n")
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
footer: "⚠️ 빌드 실패 로그 ⚠️\n```\n${logs}\n```",
link: env.BUILD_URL, result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: env.DISCORD
}
}
}
}
백엔드 Jenkinsfile
pipeline {
agent any
environment {
DEEPSEEK_API_KEY = credentials('deepseek_api_key')
GOOGLE_CLOUD_API_KEY = credentials('google_cloud_api_key')
PROJECT_ID = credentials('project_id')
DISCORD = credentials('discord_webhook')
}
stages {
stage('Start Notification') {
steps {
script {
discordSend description: "젠킨스 배포를 시작합니다!",
link: env.BUILD_URL,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 시작",
webhookURL: env.DISCORD
}
}
}
stage('Checkout') {
steps {
git branch: 'main', credentialsId: 'github_token', url: 'https://github.com/DongUgaUga/winection-api.git'
}
}
stage('Setup') {
steps {
script {
sh '''
echo "DEEPSEEK_API_KEY=$DEEPSEEK_API_KEY" > .env
echo "GOOGLE_CLOUD_API_KEY=$GOOGLE_CLOUD_API_KEY" >> .env
echo "PROJECT_ID=$PROJECT_ID" >> .env
chmod 600 .env
'''
}
}
}
stage('Deploy') {
steps {
script {
sh "docker-compose down"
sh "docker-compose up -d --build api"
}
}
}
}
post {
success {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
footer: "빌드 성공!",
link: env.BUILD_URL, result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: env.DISCORD
}
failure {
script {
def logs = currentBuild.rawBuild.join("\n")
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
footer: "⚠️ 빌드 실패 로그 ⚠️\n```\n${logs}\n```",
link: env.BUILD_URL, result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: env.DISCORD
}
}
}
}
여담으로 원래 빌드 시작할 때 알리는 알림은 파란색으로 하고 싶었는데 색 바꾸는 법을 도전해봤지만 계속 실패해서 일단 빨간색으로 출력된다...

이제 push, merge 등 main 브랜치가 바뀌면 이렇게 Jenkins가 결과를 디스코드 메세지로 띄워준다.
이번 작업을 통해 젠킨스와 디스코드 봇을 연동하여 빌드 상태를 실시간으로 공유하는 환경을 구축할 수 있었다.
처음에는 단순히 CI/CD 자동화만 생각했지만 이렇게 알림 시스템까지 추가하니 협업 효율이 더 좋아질 것 같다는 생각이 들었다.
물론, 색상을 바꾸는 기능을 완벽하게 구현하지 못한 점은 아쉽지만 이 또한 추후 개선할 도전 과제로 남겨두기로 했다.
앞으로도 필요에 따라 새로운 기능을 추가하면서 CI/CD 환경을 발전시켜 나가야겠다.
이제, 깃에 푸시하거나 머지만 하면 자동으로 배포되고, 그 결과를 디스코드에서 바로 확인할 수 있는 구조가 완성되었다.