์ธํ”„๋ผ

[AWS, Docker, Jenkins] ๋„์ปค + ์  ํ‚จ์Šค + EC2 ๋กœ CICD ๊ตฌ์ถ• ํ›„ ๋ฐฐํฌํ•˜๊ธฐ

HHRR 2024. 6. 26. 10:44

 

๋ฐฐํฌ ๊ฐœ์š”

๋„์ปค์™€ ์  ํ‚จ์Šค๋กœ CICD๋ฅผ ๊ตฌ์ถ•ํ•˜๊ณ , EC2๋กœ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์™€ ํ”„๋ก ํŠธ ์„œ๋ฒ„๋ฅผ ๋ฐฐํฌํ•ด๋ณด์ž.

์ดˆ๊ธฐ์—๋Š” ๋ฐฑ์—”๋“œ, ํ”„๋ก ํŠธ, ์  ํ‚จ์Šค ๊ฐ๊ฐ์˜ EC2๋ฅผ ์ƒ์„ฑํ•ด์„œ ๋ฐฐํฌ๋ฅผ ์ง„ํ–‰ํ•˜์˜€๋Š”๋ฐ, ๋น„์šฉ ๋ฌธ์ œ๋กœ ํ”„๋ก ํŠธ ์„œ๋ฒ„๋Š” ์ถ”ํ›„์— S3๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค.

 

์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜

- ๋ฐฑ์—”๋“œ ๋ฐฐํฌ : ๋„์ปค ํŒŒ์ผ์„ ์  ํ‚จ์Šค๊ฐ€ ๊ฐ์ง€ํ•ด์„œ jar ํŒŒ์ผ ์ƒ์„ฑํ•˜์—ฌ EC2๋กœ ๋ฐฐํฌํ•œ๋‹ค.

- ํ”„๋ก ํŠธ ๋ฐฐํฌ : ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ง„ํ–‰ํ•œ๋‹ค.


๊ตฌ์ถ• ๊ณผ์ •

1. ์  ํ‚จ์Šค ์„œ๋ฒ„ EC2 ์„ค์ •

  1. jenkins-server EC2 ์ƒ์„ฑ
    • AMI : Ubuntu Server 22.04 LTS (HVM), SSD Volume Type
    • ์•„ํ‚คํ…์ฒ˜ : 64๋น„ํŠธ x86
    • Quick start : ubuntu
    • ์ธ์Šคํ„ด์Šค ์œ ํ˜• : t2.micro
    • ํ‚คํŽ˜์–ด ์ƒ์„ฑ ํ›„ ์ €์žฅ (jenkins-key-pair)
    • ๋„คํŠธ์›Œํฌ ์„ค์ • : ๋ณด์•ˆ๊ทธ๋ฃน ์ƒ์„ฑ, SSH ํŠธ๋ž˜ํ”ฝ ํ—ˆ์šฉ
    • ์Šคํ† ๋ฆฌ์ง€ ๊ตฌ์„ฑ : 1x30, gp3
  2. ์ธ๋ฐ”์šด๋“œ ๊ทœ์น™
    • 8080 ์—ด์–ด์ฃผ๊ธฐ
  3. EC2 ๋‚ด๋ถ€์— ๋„์ปค ์„ค์น˜ํ•˜๊ธฐ
sudo apt update
sudo apt upgrade
sudo apt install build-essential

 

$ sudo apt update
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common

#์ž๋™ ์„ค์น˜ ์Šคํฌ๋ฆฝํŠธ ํ™œ์šฉ
$ sudo wget -qO- https://get.docker.com/ | sh

#Docker ์„œ๋น„์Šค ์‹คํ–‰ํ•˜๊ธฐ ๋ฐ ๋ถ€ํŒ… ์‹œ ์ž๋™ ์‹คํ–‰ ์„ค์ •
$ sudo systemctl start docker
$ sudo systemctl enable docker

#Docker ๊ทธ๋ฃน์— ํ˜„์žฌ ๊ณ„์ • ์ถ”๊ฐ€
$ sudo usermod -aG docker ${USER}
$ sudo systemctl restart docker

#Docker ์„ค์น˜ ํ™•์ธ
$ docker -v

2. ๋„์ปค๋กœ ์  ํ‚จ์Šค ์„ค์น˜

  1. ํ”„๋ฆฌํ‹ฐ์–ด ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด SWAP์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹นํ•ด์ค๋‹ˆ๋‹ค.
  2. Jenkins ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ๋‚ด๋ ค๋ฐ›์•„์„œ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹คํ–‰ํ•ด์ค๋‹ˆ๋‹ค.
// ๋ฉ”๋ชจ๋ฆฌ ์ƒํƒœ ํ™•์ธ
$ free -h

// swap ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์ค€๋‹ค. (๋ฉ”๋ชจ๋ฆฌ ์ƒํƒœ ํ™•์ธ ์‹œ swap์ด ์žˆ์—ˆ์ง€๋งŒ ๋””๋ ‰ํ† ๋ฆฌ ํŒŒ์ผ์€ ๋งŒ๋“ค์–ด์ค˜์•ผํ•œ๋‹ค.)
$ sudo mkdir /var/spool/swap
$ sudo touch /var/spool/swap/swapfile
$ sudo dd if=/dev/zero of=/var/spool/swap/swapfile count=2048000 bs=1024

// swap ํŒŒ์ผ์„ ์„ค์ •ํ•œ๋‹ค.
$ sudo chmod 600 /var/spool/swap/swapfile
$ sudo mkswap /var/spool/swap/swapfile
$ sudo swapon /var/spool/swap/swapfile

// swap ํŒŒ์ผ์„ ๋“ฑ๋กํ•œ๋‹ค.
$ sudo vim /etc/fstab
ํŒŒ์ผ์ด ์—ด๋ฆฌ๋ฉด ํ•ด๋‹น ํŒŒ์ผ ์•„๋ž˜์ชฝ์— ํ•˜๋‹จ ๋‚ด์šฉ ์ž…๋ ฅ ํ›„ ์ €์žฅ
/var/spool/swap/swapfile    none    swap    defaults    0 0

// ๋ฉ”๋ชจ๋ฆฌ ์ƒํƒœ ํ™•์ธ
$ free -h
# 1 ๋ช…๋ น์–ด ์•ˆ๋˜๋ฉด 2 ๋ช…๋ น์–ด ์‹คํ–‰
# 1
sudo docker run -d -p 8080:8080 -p 50000:50000 -v /jenkins:/var/jenkins -v /home/ubuntu/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -u root jenkins/jenkins:lts
# 2
sudo docker run --name jenkins -p 8080:8080 -p 50000:50000 -d jenkins/jenkins:lts

#Jenkins ์ด๋ฏธ์ง€ ํŒŒ์ผ ๋‚ด๋ ค๋ฐ›๊ธฐ(lts ๋ฒ„์ „)
$ sudo docker pull jenkins/jenkins:lts

#๋‚ด๋ ค๋ฐ›์•„์ง„ ์ด๋ฏธ์ง€ ํ™•์ธ
$ sudo docker images

#Jenkins ์ด๋ฏธ์ง€๋ฅผ Container๋กœ ์‹คํ–‰
sudo docker start jenkins

#๋Œ์•„๊ฐ€๊ณ  ์žˆ๋Š” Container ํ™•์ธ
sudo docker ps

3. ์  ํ‚จ์Šค ์ ‘์†

  1. { ์  ํ‚จ์Šค EC2 ํผ๋ธ”๋ฆญ IP }:8080 ์œผ๋กœ ์ ‘์†ํ•˜๋ฉด ์  ํ‚จ์Šค ๋กœ๊ทธ์ธ ํ™”๋ฉด์ด ๋œน๋‹ˆ๋‹ค.
  2. `  $docker logs jenkins ` ๋ช…๋ น์–ด๋กœ ์ดˆ๊ธฐ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. Install suggested plugins๋กœ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์น˜ํ•˜๊ณ  ์  ํ‚จ์Šค ๊ณ„์ • ์„ค์ •์„ ํ•ด์ค๋‹ˆ๋‹ค.

4. ์  ํ‚จ์Šค ๊นƒํ—ˆ๋ธŒ ์—ฐ๋™

  1. ssh ํ‚ค ์ƒ์„ฑ
  2. Github Deploy Key ๋“ฑ๋ก
    • Github Repository > Settings > Deploy Keys > Add deploy key๋กœ ์ ‘์†
    • Title์€ Jenkins๋กœ ์ง€์–ด ์ฃผ๊ณ (๋งˆ์Œ๋Œ€๋กœ ํ•ด๋„ ๋จ), Key ๋ถ€๋ถ„์— id_rsa.pub์— ๋“ค์–ด์žˆ๋Š” public key ๊ฐ’์„ ๋„ฃ์–ด์ค€๋‹ค.
  3. ๋งŒ์•ฝ ๊นƒํ—ˆ๋ธŒ ๋ ˆํฌ๊ฐ€ ํ”„๋ผ์ด๋น—์ด๋ผ๋ฉด ์ž์‹ ์˜ ๊นƒ ํ† ํฐ์„ ํฌ๋ฆฌ๋ด์…œ๋กœ ์ฃผ์ž…ํ•ด์ฃผ๊ณ  ํŒŒ์ดํ”„๋ผ์ธ์— ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
#ssh ํ‚ค ์ƒ์„ฑ(/home/ubuntu/.ssh)
ssh-keygen

#Jenkins ์ปจํ…Œ์ด๋„ˆ์— SSH ํ‚ค ๋ณต์‚ฌ
sudo docker cp /home/ubuntu/.ssh/id_rsa.pub jenkins:/root/.ssh/

# ํ‚ค ๊ฐ’ ํ™•์ธ
$ cd /home/ubuntu/.ssh
$ cat id_rsa.pub
ssh-rsa AAAAB...cHovq0= ubuntu@ip-172-31-38-163

 

5. ์  ํ‚จ์Šค ๋„์ปคํ—ˆ๋ธŒ ์—ฐ๊ฒฐ

  1. Docker Plugin ์„ค์น˜
    • Jenkins ๋Œ€์‹œ๋ณด๋“œ > Jenkins ๊ด€๋ฆฌ > ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ด€๋ฆฌ > ์„ค์น˜ ๊ฐ€๋Šฅ > Docker ๊ฒ€์ƒ‰ > Docker, Docker Pipeline ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜ ๋ฐ ์žฌ์‹คํ–‰
  2. Docker Hub Credentials ๋“ฑ๋ก
    • Jenkins ๋Œ€์‹œ๋ณด๋“œ > Jenkins ๊ด€๋ฆฌ > Manage Credentials > Credentials์— ์ ‘์†
    • Store Jenkins์— Domain์ด (global)์ธ ํ™”์‚ดํ‘œ๋ฅผ ๋ˆŒ๋Ÿฌ Global credentials (unrestricted)๋กœ ์ด๋™
    • credentials๋ฅผ ์ถ”๊ฐ€
      • Kind : Username with password
      • Username : ๋ณธ์ธ์˜ Docker Hub ID
      • Password : ๋ณธ์ธ์˜ Docker Hub Password
      • ID : docker-hub ← Pipeline Script ์ž‘์„ฑ ์‹œ credentialsId๋กœ ์‚ฌ์šฉ๋จ
  3. Jenkins Container ๋‚ด๋ถ€์— Docker ์„ค์น˜
    • Jenkins Pipeline์—์„œ Docker ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก Jenkins Container ๋‚ด๋ถ€์— Docker๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•จ
#Jenkins Container์— ์ ‘์†
sudo docker exec -it jenkins bash
sudo docker exec -u root -it jenkins bash #๋ฃจํŠธ๊ถŒํ•œ์œผ๋กœ ์ ‘์†

# sudo, vi, wget ์„ค์น˜
apt update
apt install -y sudo vim wget

# ๋„์ปค ์„ค์น˜ "2. ๋„์ปค๋กœ ์  ํ‚จ์Šค ์„ค์น˜" ์ฐธ๊ณ ํ•ด์„œ ์„ค์น˜

 

6. ์  ํ‚จ์Šค + ํ”„/๋ฐฑ ์„œ๋ฒ„ SSH ์—ฐ๊ฒฐ

ํŽธ์˜๋ฅผ ์œ„ํ•ด ํ”„๋ก ํŠธ ์„œ๋ฒ„๋กœ ๊ฐ€์ •ํ›„ ์„ค๋ช… (๋ฐฑ๋„ ๋™์ผํ•˜๊ฒŒ ์ง„ํ–‰)

  1. SSH Agent Plugin ์„ค์น˜
    • Jenkins ๋Œ€์‹œ๋ณด๋“œ > Jenkins ๊ด€๋ฆฌ > ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ด€๋ฆฌ > ์„ค์น˜ ๊ฐ€๋Šฅ > SSH Agent ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜
  2. front-server EC2 ์ƒ์„ฑ ํ›„ ์ ‘์†
    • EC2 ์•ˆ์— ๋„์ปค ๊น”์•„์ฃผ๊ธฐ
  3. front-server์˜ .ssh/authorized_keys ํŒŒ์ผ์— Jenkins Server์˜ public key ์ถ”๊ฐ€
    • โ€ผ๏ธ ํผ๋ธ”๋ฆญ ํ‚ค ์ถ”๊ฐ€ํ•  ๋•Œ ๊ธฐ์กด ํผ๋ธ”๋ฆญํ‚ค์— ์ค„๋ฐ”๊ฟˆ ํ•˜์—ฌ ์  ํ‚จ์Šค ์„œ๋ฒ„ ํผ๋ธ”๋ฆญํ‚ค ์ถ”๊ฐ€ํ•˜๊ธฐ. ๋นˆ ์ค„ ์—†์–ด์•ผํ•จ!!
  4. Jenkins Credentials ๋“ฑ๋ก
    • Jenkins ๋Œ€์‹œ๋ณด๋“œ > Jenkins ๊ด€๋ฆฌ > Manage Credentials > Credentials์— ์ ‘์†
    • ์™ผ์ชฝ ๋ฉ”๋‰ด์˜ Add credentials๋ฅผ ๋ˆŒ๋Ÿฌ credentials๋ฅผ ์ถ”๊ฐ€
      • Kind : SSH Username with private key
      • ID : ssh ← Pipeline Script ์ž‘์„ฑ ์‹œ credentialsId๋กœ ์‚ฌ์šฉ๋จ
      • Username : ubuntu โ€ผ๏ธ๋‚ด ec2์˜ username ํ™•์ธํ›„ ์ž…๋ ฅ
      • Private KeyEnter : directly ์ฒดํฌ -> private key ์ž…๋ ฅ
        • ์—ฌ๊ธฐ์„œ private key๋Š” Jenkins Server์—์„œ ์ƒ์„ฑํ•œ id_rsa
    • Store Jenkins์— Domain์ด (global)์ธ ํ™”์‚ดํ‘œ๋ฅผ ๋ˆŒ๋Ÿฌ Global credentials (unrestricted)๋กœ ์ด๋™
      • โ€ผ๏ธ BEGIN, END ์ค„๋„ ๊ฐ™์ด ๋„ฃ์–ด๋†”์•ผํ•จ
      • `  -----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY----- `
# jenkins-server์—์„œ public key ํ™•์ธ
$ cat /home/ubuntu/.ssh/id_rsa.pub
ssh-rsa AAA...fx6Lc= ubuntu@ip-...
ssh-rsa AAAA...CcHovq0= ubuntu@ip-...

# front-server์— Jenkins Server public key ์ถ”๊ฐ€
$ vi /home/ubuntu/.ssh/authorized_keys
ssh-rsa AAAAB3N....vq0= ubuntu@ip-...

7. ์  ํ‚จ์Šค + ๊นƒํ—ˆ๋ธŒ Web Hook ์—ฐ๊ฒฐ

  1. ์ƒˆ๋กœ์šด Item ๋งŒ๋“ค๊ธฐ
    • pipeline ์„ ํƒ
  2. Github Integration Plugin ์„ค์น˜
    • Jenkins ๋Œ€์‹œ๋ณด๋“œ > Jenkins ๊ด€๋ฆฌ > ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ด€๋ฆฌ > ์„ค์น˜ ๊ฐ€๋Šฅ > Github Integration ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜
  3. Jenkins Pipeline ์„ค์ •
    • Github project ์„ค์ •
      • Pipeline ๊ตฌ์„ฑ ํ™”๋ฉด > General ์˜์—ญ์—์„œ Github project
      • Project url์— Github Repository Url์„ ์ž…๋ ฅ (HTTPS Url)
    • Build Triggers ์„ค์ •
      • Pipeline ๊ตฌ์„ฑ ํ™”๋ฉด > Build Triggers ์˜์—ญ์—์„œ GitHub hook trigger for GITScm polling์„ ์„ ํƒ
  4. Github Webhook ์ถ”๊ฐ€
    • Github Repository์—์„œ Settings > Webhooks > Add Webhook ์„ ๋ˆŒ๋Ÿฌ Webhook์„ ์ถ”๊ฐ€
    • Payload URL : [Jenkins Server URL]:[Jenkins Server ํฌํŠธ]/github-webhook/
    • Content type : application/x-www-form-urlencoded
    • ๋‚˜๋จธ์ง€๋Š” default

8. ์  ํ‚จ์Šค ํŒŒ์ดํ”„๋ผ์ธ, ๋„์ปคํŒŒ์ผ, ๋นŒ๋“œ

ํ”„๋ก ํŠธ ํŒŒ์ดํ”„๋ผ์ธ

pipeline {
    agent any

    environment {
        // ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
        DOCKER_IMAGE = '{๋„์ปค ์•„์ด๋””/๋„์ปค ์ด๋ฏธ์ง€ ์ด๋ฆ„}'
        DOCKER_TAG = 'latest'
        CONTAINER_NAME = '{์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„}'
        EC2_HOSTNAME = '{ํ”„๋ก ํŠธ EC2 IP}'
    }

    stages {
        stage('Checkout') {
            steps {
                // GitHub ๋ฉ”์ธ ๋ธŒ๋žœ์น˜์—์„œ ์†Œ์Šค ์ฝ”๋“œ ์ฒดํฌ์•„์›ƒ
                git branch: 'main', url: '{.git์œผ๋กœ ๋๋‚˜๋Š” ๊นƒํ—ˆ๋ธŒ url}'
            }
        }

        stage('Build & Push Docker Images') {
            steps {
                script {
                    // Docker Hub์— ๋กœ๊ทธ์ธ
                    withCredentials([usernamePassword(credentialsId: 'docker-hub', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
                        sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
                    }
                    // Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ ๋ฐ ํ‘ธ์‹œ
                    sh 'docker build -t $DOCKER_IMAGE:$DOCKER_TAG ./'
                    sh 'docker push $DOCKER_IMAGE:$DOCKER_TAG'
                }
            }
        }

        stage('Deploy to EC2') {
            steps {
                // Docker ์ด๋ฏธ์ง€๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด SSH ์—์ด์ „ํŠธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
                sshagent(credentials: ['ssh']) {
                    // SSH๋ฅผ ํ†ตํ•ด EC2 ์ธ์Šคํ„ด์Šค์— ์ ‘์†ํ•˜์—ฌ Docker ์ด๋ฏธ์ง€ ์‹คํ–‰
                    sh """
                        ssh -o StrictHostKeyChecking=no ubuntu@$EC2_HOSTNAME '
                            sudo docker pull $DOCKER_IMAGE:$DOCKER_TAG
                            sudo docker stop $CONTAINER_NAME || true
                            sudo docker rm $CONTAINER_NAME || true
                            sudo docker run -d --name $CONTAINER_NAME -p 80:80 $DOCKER_IMAGE:$DOCKER_TAG
                        '
                    """
                }
            }
        }
    }

	// ๋””์Šค์ฝ”๋“œ ์›น ํ›… ์—ฐ๊ฒฐ
    post {
        success {
            withCredentials([string(credentialsId: 'Discord-Webhook', variable: 'DISCORD')]) {
                        discordSend description: """
                        ์ œ๋ชฉ : ${currentBuild.displayName}
                        ๊ฒฐ๊ณผ : ${currentBuild.result}
                        ์‹คํ–‰ ์‹œ๊ฐ„ : ${currentBuild.duration / 1000}s
                        """,
                        link: env.BUILD_URL, 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
                        """,
                        link: env.BUILD_URL, result: currentBuild.currentResult, 
                        title: "${env.JOB_NAME} : ${currentBuild.displayName} ์‹คํŒจ", 
                        webhookURL: "$DISCORD"
            }
        }
    }
}

 

๋ฐฑ์—”๋“œ ํŒŒ์ดํ”„๋ผ์ธ

- ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฃผ์ž…์„ ํ•˜๋“œ์ฝ”๋”ฉ๋œ yml ํŒŒ์ผ์„ ๋ณต์‚ฌํ•ด์„œ ์ฃผ์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค.

pipeline {
    agent any

    environment {
        // Docker ๋ฐ ์ด๋ฏธ์ง€ ๊ด€๋ จ ํ™˜๊ฒฝ ๋ณ€์ˆ˜
        DOCKER_IMAGE = '{๋„์ปค ์•„์ด๋””/๋„์ปค ์ด๋ฏธ์ง€ ๋ช…}'
        DOCKER_TAG = 'latest'
        CONTAINER_NAME = '{๋„์ปค ์ปจํ…Œ์ด๋„ˆ ๋ช…}'
        EC2_HOSTNAME = '{๋ฐฑ์—”๋“œ ์„œ๋ฒ„ EC2 IP}' // ๋ฐฑ์—”๋“œ ์„œ๋ฒ„
    }

    stages {
        stage('Checkout') {
            steps {
                // GitHub ๋ฉ”์ธ ๋ธŒ๋žœ์น˜์—์„œ ์†Œ์Šค ์ฝ”๋“œ ์ฒดํฌ์•„์›ƒ
                git branch: 'main', url: '{.git์œผ๋กœ ๋๋‚˜๋Š” ๊นƒํ—ˆ๋ธŒ url}'
            }
        }
        
        stage('Add Env') {
				    steps {
				        dir('.') {
				            withCredentials([file(credentialsId: 'application', variable: 'application')]) {
				               sh 'cp ${application}  src/main/resources/application.yml'
				            }
				        }
				    }
				}

        stage('Build Gradle') {
            steps {
                echo 'Building Gradle'
                dir('.') {
                    sh './gradlew clean build'
                }
            }
            post {
                failure {
                    error 'Gradle build failed. Stopping the pipeline...'
                }
            }
        }

        stage('Build & Push Docker Images') {
            steps {
                script {
                    // Docker Hub์— ๋กœ๊ทธ์ธ
                    withCredentials([usernamePassword(credentialsId: 'docker-hub', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
                        sh 'echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin'
                    }
                    // Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ ๋ฐ ํ‘ธ์‹œ
                    sh 'docker build -t $DOCKER_IMAGE:$DOCKER_TAG ./'
                    sh 'docker push $DOCKER_IMAGE:$DOCKER_TAG'
                }
            }
        }

        stage('Deploy to EC2') {
            steps {
                // Docker ์ด๋ฏธ์ง€๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด SSH ์—์ด์ „ํŠธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
                sshagent(credentials: ['ssh']) {
                    // SSH๋ฅผ ํ†ตํ•ด EC2 ์ธ์Šคํ„ด์Šค์— ์ ‘์†ํ•˜์—ฌ Docker ์ด๋ฏธ์ง€ ์‹คํ–‰
                    sh """
                    ssh -o StrictHostKeyChecking=no ubuntu@$EC2_HOSTNAME '
                        sudo docker pull $DOCKER_IMAGE:$DOCKER_TAG
                        sudo docker stop $CONTAINER_NAME || true
                        sudo docker rm $CONTAINER_NAME || true
                        sudo docker run -d --name $CONTAINER_NAME -p 8080:8080 $DOCKER_IMAGE:$DOCKER_TAG
                    '
                    """
                }
            }
        }
    }

    post {
        success {
            withCredentials([string(credentialsId: 'Discord-Webhook', variable: 'DISCORD')]) {
                discordSend description: """
                ์ œ๋ชฉ : ${currentBuild.displayName}
                ๊ฒฐ๊ณผ : ${currentBuild.result}
                ์‹คํ–‰ ์‹œ๊ฐ„ : ${currentBuild.duration / 1000}s
                """,
                link: env.BUILD_URL, 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
                """,
                link: env.BUILD_URL, result: currentBuild.currentResult, 
                title: "${env.JOB_NAME} : ${currentBuild.displayName} ๋ฐฑ์—”๋“œ ์‹คํŒจ", 
                webhookURL: "$DISCORD"
            }
        }
    }
}