💫한화시스템 BEYOND SW캠프 4차 프로젝트💫
OMOS : One Minute One Second
🍀 관리자에게 학생 정보를 보내줄 수 있습니다.
🍀 캘린더를 통해 일정을 확인하고 조율할 수 있습니다.
교육 일정 및 휴가 일정을 캘린더를 통해 손쉽게 확인하고 관리할 수 있는 서비스입니다. 매니저가 캘린더를 통해 일정을 한눈에 파악하고 교육, 강의, 프로젝트 일정을 확인할 수 있으며, 개인 휴가 계획 또한 간편한 통합 관리가 가능하도록 도와줍니다.
일정 관리의 비효율성을 해결하고, 교육과 휴가, 프로젝트 일정을 통합 관리할 수 있는 도구에 대한 필요성 때문입니다. 많은 교육 기관과 기업에서는 다양한 일정을 관리할 때 개별적으로 시스템을 사용하거나 수작업으로 관리하는 경우가 많습니다. 이러한 방식은 일정 중복, 혼란, 휴가 계획 누락 등의 문제를 야기할 수 있습니다.
특히 매니저는 팀원의 교육, 프로젝트, 휴가 일정을 한눈에 파악하기 어려워 조율에 많은 시간을 할애해야 하고, 구성원들 역시 본인의 교육과 휴가 계획을 쉽게 확인하거나 조정하기 어렵습니다. 이를 해결하기 위해 캘린더를 중심으로 한 통합 일정 관리 시스템을 구축하여, 사용자들이 더 효율적으로 일정을 관리하고, 계획을 원활하게 진행할 수 있도록 돕고자 이 프로젝트를 시작하게 되었습니다.
auth-service-jenkinsfile
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
metadata:
name: jenkins-agent-${env.BUILD_NUMBER}
spec:
containers:
- name: gradle
image: gradle:7.6.2-jdk17
command:
- cat
tty: true
- name: docker
image: docker:latest
command:
- cat
tty: true
volumeMounts:
- mountPath: "/var/run/docker.sock"
name: docker-socket
- name: kubectl
image: gcr.io/cloud-builders/kubectl
command:
- cat
tty: true
volumes:
- name: docker-socket
hostPath:
path: "/var/run/docker.sock"
'''
}
}
environment {
DOCKER_CREDENTIALS_ID = 'dockerhub_access'
DOCKER_IMAGE_NAME = 'jjjwww8802/auth-service'
DOCKERHUB_URL = 'https://index.docker.io/v1/'
GITHUB_URL = 'git@github.com:beyond-sw-camp/be08-4th-DQ-OMOS.git'
GITHUB_CREDENTIALS_ID = 'omos_access_ssh'
}
stages {
stage('Checkout') {
steps {
git branch: 'main', credentialsId: "${GITHUB_CREDENTIALS_ID}", url: "${GITHUB_URL}"
}
}
stage('Gradle Build') {
steps {
dir('Backend/auth-service') {
container('gradle') {
sh 'chmod +x ./gradlew'
sh './gradlew clean bootJar'
}
}
}
}
stage('Docker Image Build & Push') {
steps {
dir('Backend/auth-service') {
container('docker') {
script {
sh 'docker logout'
withCredentials([usernamePassword(credentialsId: "${DOCKER_CREDENTIALS_ID}", usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
}
sh "docker build --no-cache -t ${DOCKER_IMAGE_NAME}:latest ."
sh "docker push ${DOCKER_IMAGE_NAME}:latest"
sh 'docker logout'
}
}
}
}
}
stage('Kubernetes Deployment') {
steps {
container('kubectl') {
script {
sh "kubectl set image deploy auth-deploy auth=${DOCKER_IMAGE_NAME}:latest -n default"
sh "kubectl rollout restart deploy auth-deploy -n default"
}
}
}
}
}
post {
success {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: "${DISCORD}"
}
}
failure {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: "${DISCORD}"
}
}
}
}
calendar-service-jenkinsfile
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
metadata:
name: jenkins-agent-${env.BUILD_NUMBER}
spec:
containers:
- name: gradle
image: gradle:7.6.2-jdk17
command:
- cat
tty: true
- name: docker
image: docker:latest
command:
- cat
tty: true
volumeMounts:
- mountPath: "/var/run/docker.sock"
name: docker-socket
- name: kubectl
image: gcr.io/cloud-builders/kubectl
command:
- cat
tty: true
volumes:
- name: docker-socket
hostPath:
path: "/var/run/docker.sock"
'''
}
}
environment {
DOCKER_CREDENTIALS_ID = 'dockerhub_access'
DOCKER_IMAGE_NAME = 'jjjwww8802/calendar-service'
DOCKERHUB_URL = 'https://index.docker.io/v1/'
GITHUB_URL = 'git@github.com:beyond-sw-camp/be08-4th-DQ-OMOS.git'
GITHUB_CREDENTIALS_ID = 'omos_access_ssh'
}
stages {
stage('Checkout') {
steps {
git branch: 'main', credentialsId: "${GITHUB_CREDENTIALS_ID}", url: "${GITHUB_URL}"
}
}
stage('Gradle Build') {
steps {
dir('Backend/calendar-service') {
container('gradle') {
sh 'chmod +x ./gradlew'
sh './gradlew clean bootJar'
}
}
}
}
stage('Docker Image Build & Push') {
steps {
dir('Backend/calendar-service') {
container('docker') {
script {
sh 'docker logout'
withCredentials([usernamePassword(credentialsId: "${DOCKER_CREDENTIALS_ID}", usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
}
sh "docker build --no-cache -t ${DOCKER_IMAGE_NAME}:latest ."
sh "docker push ${DOCKER_IMAGE_NAME}:latest"
sh 'docker logout'
}
}
}
}
}
stage('Kubernetes Deployment') {
steps {
container('kubectl') {
script {
sh "kubectl set image deploy calendar-deploy calendar=${DOCKER_IMAGE_NAME}:latest -n default"
sh "kubectl rollout restart deploy calendar-deploy -n default"
}
}
}
}
}
post {
success {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: "${DISCORD}"
}
}
failure {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: "${DISCORD}"
}
}
}
}
notice-service-jenkinsfile
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
metadata:
name: jenkins-agent-${env.BUILD_NUMBER}
spec:
containers:
- name: gradle
image: gradle:7.6.2-jdk17
command:
- cat
tty: true
- name: docker
image: docker:latest
command:
- cat
tty: true
volumeMounts:
- mountPath: "/var/run/docker.sock"
name: docker-socket
- name: kubectl
image: gcr.io/cloud-builders/kubectl
command:
- cat
tty: true
volumes:
- name: docker-socket
hostPath:
path: "/var/run/docker.sock"
'''
}
}
environment {
DOCKER_CREDENTIALS_ID = 'dockerhub_access'
DOCKER_IMAGE_NAME = 'jjjwww8802/notice-service'
DOCKERHUB_URL = 'https://index.docker.io/v1/'
GITHUB_URL = 'git@github.com:beyond-sw-camp/be08-4th-DQ-OMOS.git'
GITHUB_CREDENTIALS_ID = 'omos_access_ssh'
}
stages {
stage('Checkout') {
steps {
git branch: 'main', credentialsId: "${GITHUB_CREDENTIALS_ID}", url: "${GITHUB_URL}"
}
}
stage('Gradle Build') {
steps {
dir('Backend/notice-service') {
container('gradle') {
sh 'chmod +x ./gradlew'
sh './gradlew clean bootJar'
}
}
}
}
stage('Docker Image Build & Push') {
steps {
dir('Backend/notice-service') {
container('docker') {
script {
sh 'docker logout'
withCredentials([usernamePassword(credentialsId: "${DOCKER_CREDENTIALS_ID}", usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
}
sh "docker build --no-cache -t ${DOCKER_IMAGE_NAME}:latest ."
sh "docker push ${DOCKER_IMAGE_NAME}:latest"
sh 'docker logout'
}
}
}
}
}
stage('Kubernetes Deployment') {
steps {
container('kubectl') {
script {
sh "kubectl set image deploy notice-deploy notice=${DOCKER_IMAGE_NAME}:latest -n default"
sh "kubectl rollout restart deploy notice-deploy -n default"
}
}
}
}
}
post {
success {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: "${DISCORD}"
}
}
failure {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: "${DISCORD}"
}
}
}
}
notification-service-jenkinsfile
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
metadata:
name: jenkins-agent-${env.BUILD_NUMBER}
spec:
containers:
- name: gradle
image: gradle:7.6.2-jdk17
command:
- cat
tty: true
- name: docker
image: docker:latest
command:
- cat
tty: true
volumeMounts:
- mountPath: "/var/run/docker.sock"
name: docker-socket
- name: kubectl
image: gcr.io/cloud-builders/kubectl
command:
- cat
tty: true
volumes:
- name: docker-socket
hostPath:
path: "/var/run/docker.sock"
'''
}
}
environment {
DOCKER_CREDENTIALS_ID = 'dockerhub_access'
DOCKER_IMAGE_NAME = 'jjjwww8802/notification-service'
DOCKERHUB_URL = 'https://index.docker.io/v1/'
GITHUB_URL = 'git@github.com:beyond-sw-camp/be08-4th-DQ-OMOS.git'
GITHUB_CREDENTIALS_ID = 'omos_access_ssh'
}
stages {
stage('Checkout') {
steps {
git branch: 'main', credentialsId: "${GITHUB_CREDENTIALS_ID}", url: "${GITHUB_URL}"
}
}
stage('Gradle Build') {
steps {
dir('Backend/notification-service') {
container('gradle') {
sh 'chmod +x ./gradlew'
sh './gradlew clean bootJar'
}
}
}
}
stage('Docker Image Build & Push') {
steps {
dir('Backend/notification-service') {
container('docker') {
script {
sh 'docker logout'
withCredentials([usernamePassword(credentialsId: "${DOCKER_CREDENTIALS_ID}", usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
}
sh "docker build --no-cache -t ${DOCKER_IMAGE_NAME}:latest ."
sh "docker push ${DOCKER_IMAGE_NAME}:latest"
sh 'docker logout'
}
}
}
}
}
stage('Kubernetes Deployment') {
steps {
container('kubectl') {
script {
sh "kubectl set image deploy notification-deploy notification=${DOCKER_IMAGE_NAME}:latest -n default"
sh "kubectl rollout restart deploy notification-deploy -n default"
}
}
}
}
}
post {
success {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: "${DISCORD}"
}
}
failure {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: "${DISCORD}"
}
}
}
}
student-service-jenkinsfile
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
metadata:
name: jenkins-agent-${env.BUILD_NUMBER}
spec:
containers:
- name: gradle
image: gradle:7.6.2-jdk17
command:
- cat
tty: true
- name: docker
image: docker:latest
command:
- cat
tty: true
volumeMounts:
- mountPath: "/var/run/docker.sock"
name: docker-socket
- name: kubectl
image: gcr.io/cloud-builders/kubectl
command:
- cat
tty: true
volumes:
- name: docker-socket
hostPath:
path: "/var/run/docker.sock"
'''
}
}
environment {
DOCKER_CREDENTIALS_ID = 'dockerhub_access'
DOCKER_IMAGE_NAME = 'jjjwww8802/student-service'
DOCKERHUB_URL = 'https://index.docker.io/v1/'
GITHUB_URL = 'git@github.com:beyond-sw-camp/be08-4th-DQ-OMOS.git'
GITHUB_CREDENTIALS_ID = 'omos_access_ssh'
}
stages {
stage('Checkout') {
steps {
git branch: 'main', credentialsId: "${GITHUB_CREDENTIALS_ID}", url: "${GITHUB_URL}"
}
}
stage('Gradle Build') {
steps {
dir('Backend/student-service') {
container('gradle') {
sh 'chmod +x ./gradlew'
sh './gradlew clean bootJar'
}
}
}
}
stage('Docker Image Build & Push') {
steps {
dir('Backend/student-service') {
container('docker') {
script {
sh 'docker logout'
withCredentials([usernamePassword(credentialsId: "${DOCKER_CREDENTIALS_ID}", usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
}
sh "docker build --no-cache -t ${DOCKER_IMAGE_NAME}:latest ."
sh "docker push ${DOCKER_IMAGE_NAME}:latest"
sh 'docker logout'
}
}
}
}
}
stage('Kubernetes Deployment') {
steps {
container('kubectl') {
script {
sh "kubectl set image deploy student-deploy student=${DOCKER_IMAGE_NAME}:latest -n default"
sh "kubectl rollout restart deploy student-deploy -n default"
}
}
}
}
}
post {
success {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 성공",
webhookURL: "${DISCORD}"
}
}
failure {
withCredentials([string(credentialsId: 'discord-webhook', variable: 'DISCORD')]) {
discordSend description: """
제목 : ${currentBuild.displayName}
결과 : ${currentBuild.result}
실행 시간 : ${currentBuild.duration / 1000}s
""",
result: currentBuild.currentResult,
title: "${env.JOB_NAME} : ${currentBuild.displayName} 실패",
webhookURL: "${DISCORD}"
}
}
}
}
팀 원 | 회고 |
---|---|
👑김종원 | Devops 프로젝트에서 Spring Boot과 Vuejs 애플리케이션의 CI/CD 파이프라인을 구축하며, Jenkins와 Kubernetes 설정 과정에서 Helm을 활용해 효율적으로 환경을 구축해보고, Ngrok을 이용해 깃허브와 웹훅을 연결해 자동화를 하였습니다. Gradle을 통한 빌드 후 Docker 이미지를 생성하고 Docker Hub에 푸시하여 다양한 환경에서 배포 작업을 해보며 잘 몰랐던 CI/CD를 이해할 수 있었습니다. 프로젝트 초기에 기존의 개발방식과 다르게 MSA 방식으로 개발해보고자 제안을 했고 매일 남아서 공부하며 열심히 해준 팀원들에게 고맙습니다!! |
🍙김태영 | 처음으로 해보는 CI/CD 프로젝트라서 도커, 쿠버네티스, 젠킨스의 동작 원리를 알 수 있는 소중한 기회였던 것 같습니다. |
🐶이은서 | 이번 프로젝트를 프론트엔드부터 백엔드 그리고 CI/CD 파이프라인을 만들어서 배포까지 경험 할 수 있어서 좋았습니다. 배포하는 과정이 어려워 해결하지 못한 부분들을 팀원들 덕분에 배포까지 마무리 할 수 있었던 것 같습니다!! |
🧁조은희 | 이번 프로젝트는 프론트엔드부터 배포까지 CI/CD 파이프라인을 직접 경험할 수 있었던 좋은 기회였습니다. 특히 배포 과정에서 노트북 성능이 낮아 빌드와 테스트 속도가 느려지는 문제가 발생하였고, 효율적인 빌드 프로세스와 환경 최적화의 중요성을 깨닫게 되었습니다. |
🐧홍석민 | 이번 데브옵스 프로젝트를 진행하면서 기존 모놀리딕 방식이 아닌 msa 방식으로 진행을 해보았는데 중간중간 설계를 바꾸지 않으면 해결하지 못하는 문제들을 마주하면서 설계가 정말 중요하고 신중하게 생각해야 하는 걸 다시 한번 느꼈습니다. 데브옵스도 결코 만만치 않았고 왜 데브옵스 개발자가 따로 있는지 알 수 있는 시간이었습니다. |