[CI/CD]Jenkins + GitHub + Docker 연동

송영호·2025년 3월 11일

Spring Boot

목록 보기
6/8
post-thumbnail

1️⃣ GitHub Repository 생성

✅GitHub Dashboard 이동 -> New 선택

  • Repository 명, README 파일 추가 여부, Public 공개 여부 선택 후 Repository 생성

2️⃣ Repository Token 발급

✅Repository -> Settings -> Developer Settings -> Tokens (classic)

  • Generate new token 클릭
  • scope 설정
    - repo, admin:org, admin:repo_hook 선택

    repo ➡️ Repository에 대한 전체 액세스 권한(코드, 커밋, 이슈, PR 등) 접근 가능
    admin:org ➡️ GitHub 조직을 관리할 수 있는 관리자 권한(멤버 추가,제거, 조직 리포지토리 관리 등)
    admin:repo_hook ➡️ 리포지토리 웹훅 권한(웹훅 생성 / 변경 / 삭제)

  • 토큰은 다시 볼 수 없기 때문에, 저장 필요
  • 토큰 만료기간 default 30일(custom✅ -> 만료기간 설정 가능)

3️⃣ 플러그인 설치

✅Jenkins DashBoard -> Jenkins 관리 -> Plugins -> Available plugins

Docker Plugin
Generic Webhook Trigger Plugin
SSH server
GitHub API Plugin
GitHub Integration Plugin


4️⃣ GitHub 인증 Credentials 및 ec2-ssh-key 생성

✅Jenkins DashBoard -> Jenkins 관리 -> Credentials 이동

GitHub Credential 생성

  • GitHub Push trigger가 발생했을 때, Jenkins 파이프라인이 동작하기 위해선, 두 시스템이 연동 되어야 한다. ➡️ GitHub Repository에 접근할 수 있는 인증 정보(Credential)가 필요
  • global 클릭
  • kind : Username with password (GitHub ID & GitHub PASSWORD)
  • scope : Global 선택
    • Global : Jenkins 내 모든 Job 및 Pipeline에서 접근 가능한 scope (여러 Job 또는 Pipeline에서 공유해야 할 경우)
    • System : Jenkins 내부에서만 사용 가능(Jenkins 관리자 기능에서만 활용)
  • ID : credential ID

Jenkins에서 EC2 접근을 위한 ssh-key 생성

  • scope : Global 선택
  • kind : SSH Username with private key (ssh key(pem key) 인증)
  • Username : EC2 login username
  • Enter directly 선택 후, pem키 복사

5️⃣ Jenkins Pipeline 생성

✅Jenkins DashBoard -> New Item -> Freestyle Project

  • Triggers -> GitHub hook trigger for GITScm polling 체크
    • GitHub에서 변경 사항이 발생할 때마다 GitHub에서 Jenkins로 HTTP 요청(웹훅)을 보내 빌드를 트리거
  • Definition -> Pipeline script from SCM 선택
    • Pipeline script from SCM : 프로젝트에 Jenkinsfile을 포함하고, Jenkins에서 해당 파일을 실행하는 방식
    • Pipeline script : Pipeline 스크립트를 직접 작성하는 방식 (Git에서 버전 관리 불가)
  • Repository URL : GitHub Repository의 .git 경로(사진)
  • Credentials : 4️⃣에서 추가한 GitHub Credential
  • Branch : Push할 때, Pipeline 빌드 트리거를 발생시킨 브렌치
  • Script Path : Jenkinsfile의 경로 (프로젝트 루트)


6️⃣ GitHub Webhook 설정

✅ GitHub Repository -> 좌측 상단 Settings -> Webhooks

  • Payload URL : Jenkins URL + /github-webook/
  • Content type : application/json 선택
  • 아래 사진과 같이 ✅ 표시가 나타나면, 설정한 branch push 이벤트 발생 시, 웹훅이 동작한다. 추가로, Recent Deliveries 탭에 들어가면 최근 웹훅 요청에 대한 header와 payload, HTTP 상태코드를 볼 수 있다.


7️⃣ Dockerfile 및 docker-compose.yml 작성

Dockerfile📜

# EC2에 전달받은 jar를 배포 경로에 배포한다.
FROM openjdk:21

WORKDIR /app

COPY app.jar /app/app.jar

CMD ["java", "-jar", "app.jar"]

docker-compose.yml📜

version: '3.8'

services:
  db:
    image: mysql:8.0 # pull image
    container_name: beauty-care-db-container
    environment:
      MYSQL_ROOT_PASSWORD: {root 패스워드}
      MYSQL_DATABASE: {database_schema}
    volumes:
      - db_data:/var/lib/mysql # 데이터 유지를 위해 볼륨 설정
    networks:
      - beauty_care_network # Spring Boot container와 네트워크 설정
    ports:
      - 3306:3306 # host 3306 -> container 3306
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      retries: 5
      timeout: 5s

  app:
    build:
      context: . # Dockerfile 경로
      dockerfile: Dockerfile # 정의된 Docker file을 빌드하여, 이미지를 생성한다.
    image: beauty-care-app # Docker image명
    container_name: beauty-care-app-container
    environment:
      - SPRING_PROFILES_ACTIVE=prod # profile 설정
    depends_on:
      db:
        condition: service_healthy # db container가 healthy일때 까지 대기
    networks:
      - beauty_care_network # db <-> backend container
    ports:
      - 8080:8080 # host 8080 -> container 8080

networks:
  beauty_care_network:
    external: true
    name: beauty_care_network
    driver: bridge

volumes:
  db_data:

8️⃣ Jenkinsfile(Pipeline script) 작성

✅ 최상위 프로젝트 -> Jenkinsfile

Pipeline script📜

pipeline {
    agent any

    environment {
        EC2_USER = 'ubuntu'
        EC2_HOST = {EC2 Public IP}
        SSH_KEY_ID = {Jenkins ssh key}
        DEPLOY_DIR = {배포할 경로}
        GIT_REPO = {.git 경로}
        GIT_CREDENTIALS_ID = {Jenkins에 등록한 Credential ID}
        DOCKER_COMPOSE_FILE = 'docker-compose.yml'
        DOCKER_HOST = 'unix:///var/run/docker.sock'
    }

    stages {
        stage('메인 브렌치 체크아웃') {
            steps {
                withCredentials([usernamePassword(credentialsId: GIT_CREDENTIALS_ID, passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
                    sh """
                        rm -rf beauty-care  # 기존 디렉토리 삭제
                        git clone https://${GIT_USERNAME}:${GIT_PASSWORD}@{.git 경로} beauty-care
                        cd beauty-care
                        git checkout main
                    """
                }
            }
        }

        stage('Gradle Build') {
            steps {
                script {
                    sh """
                        cd beauty-care
                        chmod +x ./gradlew
                        ./gradlew clean build
                    """
                }
            }
        }

        stage('JAR 배포') {
            steps {
                sshagent([SSH_KEY_ID]) {
                # 배포 경로로 JAR 전달
                    sh """
                        scp -o StrictHostKeyChecking=no beauty-care/build/libs/beauty-care-0.0.1-SNAPSHOT.jar ${EC2_USER}@${EC2_HOST}:${DEPLOY_DIR}/app.jar
                    """
                }
            }
        }

        stage('EC2에서 컨테이너 실행') {
            steps {
                sshagent([SSH_KEY_ID]) {
                    script {
                        # docker-compose.yml 및 Dockerfile 전달
                        sh "scp -o StrictHostKeyChecking=no beauty-care/docker-compose.yml ${EC2_USER}@${EC2_HOST}:${DEPLOY_DIR}/"
                        sh "scp -o StrictHostKeyChecking=no beauty-care/Dockerfile ${EC2_USER}@${EC2_HOST}:${DEPLOY_DIR}/"
						
                        // docker-compose up
                        def dockerDeployScript = """#!/bin/bash
                                                    set -e  # 실패 시 즉시 종료
                                                    docker-compose -f ${DEPLOY_DIR}/docker-compose.yml down
                                                    cd ${DEPLOY_DIR}
                                                    docker-compose up -d --build
                                                    exit 0
                                                """

                        sh "echo \"${dockerDeployScript}\" | ssh -o StrictHostKeyChecking=no ${EC2_USER}@${EC2_HOST}"
                    }
                }
            }
        }
    }

    post {
        success {
            echo '🎉 배포 성공'
        }
        failure {
            echo '🚨 배포 실패'
        }
    }
}
  • docker-compose -f ${DEPLOY_DIR}/docker-compose.yml down
    • ️-f ➡️ docker-compose.yml 파일의 경로를 지정
    • down ➡️ 기존 컨테이너 및 네트워크 종료
  • docker-compose up -d --build
    • -d ➡️ 백그라운드에서 실행
    • --build ➡️ 컨테이너 실행 전에 이미지를 다시 빌드 (호스트에 이미지가 존재해도, 빌드한다.)
profile
BACKEND 개발자

0개의 댓글