๋ฐฐํฌ ๊ฐ์
๋์ปค์ ์ ํจ์ค๋ก CICD๋ฅผ ๊ตฌ์ถํ๊ณ , EC2๋ก ๋ฐฑ์๋ ์๋ฒ์ ํ๋ก ํธ ์๋ฒ๋ฅผ ๋ฐฐํฌํด๋ณด์.
์ด๊ธฐ์๋ ๋ฐฑ์๋, ํ๋ก ํธ, ์ ํจ์ค ๊ฐ๊ฐ์ EC2๋ฅผ ์์ฑํด์ ๋ฐฐํฌ๋ฅผ ์งํํ์๋๋ฐ, ๋น์ฉ ๋ฌธ์ ๋ก ํ๋ก ํธ ์๋ฒ๋ ์ถํ์ S3๋ก ๋ณ๊ฒฝํ๋ค.
์๋น์ค ์ํคํ ์ฒ
- ๋ฐฑ์๋ ๋ฐฐํฌ : ๋์ปค ํ์ผ์ ์ ํจ์ค๊ฐ ๊ฐ์งํด์ jar ํ์ผ ์์ฑํ์ฌ EC2๋ก ๋ฐฐํฌํ๋ค.
- ํ๋ก ํธ ๋ฐฐํฌ : ๋ง์ฐฌ๊ฐ์ง๋ก ์งํํ๋ค.
๊ตฌ์ถ ๊ณผ์
1. ์ ํจ์ค ์๋ฒ EC2 ์ค์
- 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
- ์ธ๋ฐ์ด๋ ๊ท์น
- 8080 ์ด์ด์ฃผ๊ธฐ
- 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. ๋์ปค๋ก ์ ํจ์ค ์ค์น
- ํ๋ฆฌํฐ์ด ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ด SWAP์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ํ ๋นํด์ค๋๋ค.
- 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. ์ ํจ์ค ์ ์
- { ์ ํจ์ค EC2 ํผ๋ธ๋ฆญ IP }:8080 ์ผ๋ก ์ ์ํ๋ฉด ์ ํจ์ค ๋ก๊ทธ์ธ ํ๋ฉด์ด ๋น๋๋ค.
- ` $docker logs jenkins ` ๋ช ๋ น์ด๋ก ์ด๊ธฐ ํจ์ค์๋๋ฅผ ์ป์ ์ ์์ต๋๋ค.
- Install suggested plugins๋ก ํ๋ฌ๊ทธ์ธ์ ์ค์นํ๊ณ ์ ํจ์ค ๊ณ์ ์ค์ ์ ํด์ค๋๋ค.
4. ์ ํจ์ค ๊นํ๋ธ ์ฐ๋
- ssh ํค ์์ฑ
- Github Deploy Key ๋ฑ๋ก
- Github Repository > Settings > Deploy Keys > Add deploy key๋ก ์ ์
- Title์ Jenkins๋ก ์ง์ด ์ฃผ๊ณ (๋ง์๋๋ก ํด๋ ๋จ), Key ๋ถ๋ถ์ id_rsa.pub์ ๋ค์ด์๋ public key ๊ฐ์ ๋ฃ์ด์ค๋ค.
- ๋ง์ฝ ๊นํ๋ธ ๋ ํฌ๊ฐ ํ๋ผ์ด๋น์ด๋ผ๋ฉด ์์ ์ ๊น ํ ํฐ์ ํฌ๋ฆฌ๋ด์ ๋ก ์ฃผ์ ํด์ฃผ๊ณ ํ์ดํ๋ผ์ธ์ ์ถ๊ฐํด์ฃผ๋ฉด ๋๋ค.
#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. ์ ํจ์ค ๋์ปคํ๋ธ ์ฐ๊ฒฐ
- Docker Plugin ์ค์น
- Jenkins ๋์๋ณด๋ > Jenkins ๊ด๋ฆฌ > ํ๋ฌ๊ทธ์ธ ๊ด๋ฆฌ > ์ค์น ๊ฐ๋ฅ > Docker ๊ฒ์ > Docker, Docker Pipeline ํ๋ฌ๊ทธ์ธ ์ค์น ๋ฐ ์ฌ์คํ
- 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๋ก ์ฌ์ฉ๋จ
- 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 ์ฐ๊ฒฐ
ํธ์๋ฅผ ์ํด ํ๋ก ํธ ์๋ฒ๋ก ๊ฐ์ ํ ์ค๋ช (๋ฐฑ๋ ๋์ผํ๊ฒ ์งํ)
- SSH Agent Plugin ์ค์น
- Jenkins ๋์๋ณด๋ > Jenkins ๊ด๋ฆฌ > ํ๋ฌ๊ทธ์ธ ๊ด๋ฆฌ > ์ค์น ๊ฐ๋ฅ > SSH Agent ํ๋ฌ๊ทธ์ธ ์ค์น
- front-server EC2 ์์ฑ ํ ์ ์
- EC2 ์์ ๋์ปค ๊น์์ฃผ๊ธฐ
- front-server์ .ssh/authorized_keys ํ์ผ์ Jenkins Server์ public key ์ถ๊ฐ
- โผ๏ธ ํผ๋ธ๋ฆญ ํค ์ถ๊ฐํ ๋ ๊ธฐ์กด ํผ๋ธ๋ฆญํค์ ์ค๋ฐ๊ฟ ํ์ฌ ์ ํจ์ค ์๋ฒ ํผ๋ธ๋ฆญํค ์ถ๊ฐํ๊ธฐ. ๋น ์ค ์์ด์ผํจ!!
- 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 ์ฐ๊ฒฐ
- ์๋ก์ด Item ๋ง๋ค๊ธฐ
- pipeline ์ ํ
- Github Integration Plugin ์ค์น
- Jenkins ๋์๋ณด๋ > Jenkins ๊ด๋ฆฌ > ํ๋ฌ๊ทธ์ธ ๊ด๋ฆฌ > ์ค์น ๊ฐ๋ฅ > Github Integration ํ๋ฌ๊ทธ์ธ ์ค์น
- 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์ ์ ํ
- Github project ์ค์
- 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"
}
}
}
}