Jenkins, Docker๋ฅผ ์ด์ฉํ ๋ฌด์ค๋จ ๋ฐฐํฌ ์์ํ๊ธฐ
์์ํ๊ธฐ
์์ ์๋ ๋ฐฐํฌํ๋ ๋ ์ด๋ฉด ์ ์ํ๋ ์ฌ์ฉ์๊ฐ ์ ์ ์๋ฒฝ์๊ฐ์ ํ๋ค๊ณ ๋ณธ์ ์ด ์์ต๋๋ค. ๋ฐฐํฌ๋ฅผ ํ ๋์๋ ์๋ก์ด ๋ฒ์ ์ jar ํ์ผ์ ๋ฐฐํฌํ ์๋ฒ๋ก ๋ณต์ฌ์ํค๊ณ , ์ง์ SSH๋ก ์ ์ํ์ฌ jar๋ฅผ ๋ณ๊ฒฝํ์ฌ ์คํํ๋ ๊ฒ์ด์์ต๋๋ค.
์ด๋ ๊ฒ ๋ฐฐํฌ๋ฅผ ์งํํ๋ฉด, ๋ฐฐํฌ๋ฅผ ํ ๋๋ง๋ค ๊ฐ๋ฐ์์ ๋ง์ ๋ฆฌ์์ค๊ฐ ๋ค์ด๊ฐ๋ค๋ ๋จ์ ๊ณผ jar๋ฅผ ๋ฐ๊ฟ๋ผ๋ ๊ทธ ์ฐฐ๋์ ์๋น์ค๊ฐ ๋๊ธด๋ค๋ ์น๋ช ์ ์ธ ๋จ์ ์ด ์กด์ฌํฉ๋๋ค.
๊ทธ๋์ ์ด๋ฒ ์๊ฐ์๋ ์ด๋ป๊ฒ ํ๋ฉด ๋ฐฐํฌ๋ฅผ ์๋ํํ๊ณ , ๋ ๊ธฐ์กด ์๋น์ค๋ฅผ ์ ์ง์ํค์ง ์๊ณ ๋ฐฐํฌํ ์ ์๋์ง ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์์์ ๋งํ ์ด ๋ฌด์ค๋จ ๋ฐฐํฌ์๋ ์ฌ๋ฌ ๋ฐฉ๋ฒ๋ค์ด ์์ต๋๋ค.
- Nginx๋ก ๋ฌด์ค๋จ ๋ฐฐํฌ
- AWS์์ Blue-Green ๋ฌด์ค๋จ ๋ฐฐํฌ
- ๋์ปค๋ฅผ ์ด์ฉํ ๋ฌด์ค๋จ ๋ฐฐํฌ
์ด๋ฒ ์๊ฐ์๋ ํ๋์ ์๋ฒ๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๊ณ , ๋ด๋ถ์ Nginx์ Docker๋ฅผ ๊ตฌ์ฑํ์ฌ ๋ฌด์ค๋จ ๋ฐฐํฌ๋ฅผ ํด๋ณด๊ฒ ์ต๋๋ค.
0. ์ ์ฒด ๊ตฌ์ฑ๋
ํ๋ก์ธ์ค๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Gitlab์ ํน์ ๋ธ๋์น๋ก ํธ์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด, Jenkins๋ก Trigger๋ฅผ ๋ ๋ฆฌ๊ฒ ๋ฉ๋๋ค.
- ์ ํจ์ค์์๋ GitLab์์ ์ต์ ์ฝ๋๋ฅผ ๋ฐ์์์ ๋น๋ํ๊ณ ํ ์คํธ๋ฅผ ์ํํ์ฌ ๋์ปค ์ด๋ฏธ์ง๋ฅผ ์์ฑํ์ฌ Docker ์ ์ฅ์๋ก Push ํฉ๋๋ค.
- ์ ํจ์ค์์ ๋ฐฐํฌ ์๋ฒ๋ก ์ปค๋งจ๋๋ฅผ ํตํด ์๋ก์ด ๋ฒ์ ์ ์๋ฒ๋ฅผ ๋ฐฐํฌํฉ๋๋ค.
์ฌ์ ์ค์
- Jenkins ์ค์
- GitLab ์ค์
1. ํ๋ฌ๊ทธ์ธ ๋ค์ด๋ก๋
๋จผ์ ๋ฐฐํฌ๋ฅผ ์ํ ์๋ ํ๋ฌ๊ทธ์ธ๋ค์ ๋ค์ด๋ฐ์์ฃผ๋๋ก ํฉ๋๋ค. ๊น๋ฉ, ๋์ปค, SSH ๋ฅผ ์ํ ํ๋ฌ๊ทธ์ธ๋ค ์ ๋๋ค.
ํ๋ฌ๊ทธ์ธ ์ค์
[Jenkins Gitlab ์ฐ๋ - ์ถํ ํฌ์คํ ]
[Jenkins SSH ์ค์ ํ๊ธฐ- ์ถํ ํฌ์คํ ]
[JDK ์ค์ ํ๊ธฐ- ์ถํ ํฌ์คํ ]
[Docker ์ค์ ํ๊ธฐ- ์ถํ ํฌ์คํ ]
Global Properties ์ค์
Dashboard → Jenkins ๊ด๋ฆฌ → Configure System → Global properties → Environment variables ์ถ๊ฐ
- ๋ฐฐํฌ ์๋ฒ URL
- ๋์ปค ์ ์ฅ์ URL
- …
Credentials ์ถ๊ฐํ๊ธฐ
Dashboard → Jenkins ๊ด๋ฆฌ → Credentials → System → Global credentials (unrestricted) → +Add Credentials
ID๋ Jenkins์์ ์ฌ์ฉํ ๋ณ์ ์ด๋ฆ์ ๋๋ค.
- ๊น๋ฉ
id/pw
- docker
id/pw
- ์ถ๊ฐ
2. Pipeline ์์ฑ
New Item → Pipeline ์์ฑ
- Build Triggers ์ค์ ํ๊ธฐ (์ถํ ํฌ์คํ ์์ )
Pipeline ์คํฌ๋ฆฝํธ ์์ฑ
ํ์ดํ๋ผ์ธ์๋ ์ฌ๋ฌ ์คํ ์ด์ง๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ์คํ ์ด์ง๋ ์๋์ ๊ฐ์ด ๊ตฌ์ฑํ๊ฒ ์ต๋๋ค.
- GitLab ์ต์ ์ฝ๋ Clone
- Gradle ํ๋ก์ ํธ ๋น๋ (ํ ์คํธ ๋ฐ JAR ์์ฑ)
- Docker Image Build
- Docker Image Push
- ๋ฐฐํฌ ์๋ฒ๋ก ๋ช ๋ น์ด๋ฅผ ํตํด ์๋ก์ด ๋ฒ์ ๋ฐฐํฌ
GitLab ์ต์ ์ฝ๋ Clone
stage ('git clone') {
steps {
git branch: 'main', credentialsId: 'gitlab-credentials', url: 'https://git.example.com/myapp.git'
}
post {
failure {
echo 'Repository clone failure !'
}
success {
echo 'Repository clone success !'
}
}
}
์ด๋ฒ Stage์์๋ ์ด์ ์ ์ค์ ํด๋ gitlab credentials ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ URL๋ก Clone์ ๋ฐ๋ ์ฝ๋์ ๋๋ค. Cloneํ Branch๋ช ๊ณผ crendentials, URL์ด ์ ์ค์ ๋์ด์๋์ง ํ์ธํ์๊ธธ ๋ฐ๋๋๋ค.
Gradle ํ๋ก์ ํธ ๋น๋ (ํ ์คํธ ๋ฐ JAR ์์ฑ)
stage('gradle build') {
steps {
sh 'chmod +x gradlew'
sh './gradlew build'
}
post {
failure {
echo 'Gradle build failure!'
}
success {
echo 'Gradle build success!'
}
}
}
์ด๋ฒ Stage์์๋ ํ๋ก์ ํธ ๋ฃจํธ์ ์๋ gradlew ํ์ผ์ ๋ํ ์คํ ๊ถํ์ ๋ถ์ฌํ๊ณ , ๋น๋๋ฅผ ์คํํฉ๋๋ค. ๋ง์ฝ ํ ์คํธ๊ฐ ์คํจํ๋ค๋ฉด ์ดํ Stage๋ค์ ๋ชจ๋ ์คํ๋์ง ์์ต๋๋ค.
Docker Image Build
environment {
dockerHubRegistry = 'https://example.dockerhub.com'
dockerRepository = 'example.dockerhub.com/myapp'
}
stage('Docker Image Build') {
steps {
sh "docker build -t ${dockerRepository}:${currentBuild.number} ."
sh "docker build -t ${dockerRepository}:latest ."
sh "docker rmi ${dockerRepository}:${currentBuild.number}"
}
post {
failure {
echo 'Docker image build failure!'
}
success {
echo 'Docker image build success!'
}
}
}
์ด๋ฒ์๋ environment๋ฅผ ์ด์ฉํด dockerHubRegistry์ dockerRepository๋ฅผ ํ๊ฒฝ๋ณ์๋ก ๋ฑ๋กํ์ฌ ์ฌ์ฉํ์ต๋๋ค. ๋์ปค ๋น๋ ๋ช ๋ น์ด๋ฅผ ์ด์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๋ ์ฝ๋์ ๋๋ค.
Docker Image Build
stage('Docker Image Push') {
steps {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'dockerhub-credentials',
usernameVariable: 'DOCKER_USER_ID',
passwordVariable: 'DOCKER_USER_PASSWORD'
]]){
sh 'echo ${DOCKER_USER_PASSWORD} | docker login -u ${DOCKER_USER_ID} --password-stdin ${dockerHubRegistry}'
sh "docker push ${dockerRepository}:latest"
}
}
}
์ด๋ฒ Stage์์๋ ์ ํจ์ค ๋ด๋ถ์ ๋ด์ฅ๋ UsernamePasswordMultiBinding์ ์ด์ฉํ์ฌ ์ด์ ์ ๋ฑ๋กํด๋์๋ Jenkins ๊ด๋ จ ๋ณ์๋ค์ ๋ถ๋ฌ์ค๋๋ก ํฉ๋๋ค. ๋์ปค ๋ก๊ทธ์ธ์ ํด์ฃผ๊ณ , ์์ฑํ๋ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅ์๋ก ํธ์ํ๋ ์์ ์ ์ํํฉ๋๋ค.
๋ฐฐํฌ ๋จ๊ณ
stage('Server Docker Run') {
steps {
sshagent (credentials: ['jenkins-rsa']) {
sh '''
#!/bin/bash
if curl -s "${blue_url}" > /dev/null
then
deployment_container_name=green
deployment_target_ip=$green_url
deployment_port=$green_port
old_container_name=blue
else
deployment_container_name=blue
deployment_target_ip=$blue_url
deployment_port=$blue_port
old_container_name=green
fi
ssh root@${deploy_ip} "nohup docker run --name ${deployment_container_name} -p ${deployment_port}:8080 ${dockerRepository}:latest > /dev/null &" &
for retry_count in \$(seq 10)
do
if curl -s "${deployment_target_ip}" > /dev/null
then
echo "์๋ฒ Health Check์ ์ฑ๊ณตํ์ต๋๋ค."
break
fi
if [ $retry_count -eq 10 ]
then
echo "์๋ฒ Health Check์ ์คํจํ์ต๋๋ค."
exit 1
fi
echo "์๋ฒ Health Check๋ฅผ 10์ด ์ดํ์ ์ฌ์๋ํฉ๋๋ค..."
sleep 10
done
ssh root@${deploy_ip} "echo 'set \\\$service_url ${deployment_target_ip};' > /root/docker-compose/nginx/nginx/conf.d/service-url.inc"
ssh root@${deploy_ip} "docker exec -i nginx service nginx reload"
echo "Nginx Reverse Proxy ๋ณ๊ฒฝ: ${deployment_target_ip}"
echo "๊ธฐ์กด ๋์ปค ์๋ฒ ์ข
๋ฃ"
ssh root@${deploy_ip} "docker rm -f ${old_container_name}"
'''
}
}
}
์ด์ ์ ์ค์นํ๋ SSH Agent๋ฅผ ์ด์ฉํ์ฌ ๋ฐฐํฌ ์๋ฒ์ ๋ฑ๋ก์ํจ RSA ๊ฐ์ธํค๋ฅผ ๋ถ๋ฌ์์ ๋ฐฐํฌ ์๋ฒ๋ก ๋ช ๋ น์ด๋ฅผ ์ ์ก์ํต๋๋ค. ์๋์์ ์์ธํ๊ฒ ์ฝ๋๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. (์คํฌ๋ฆฝํธ ๋ด๋ถ์์ ์ฌ์ฉ๋ ๋ณ์๋ค์ Jenkins Global Variable๋ก ์ ์ฅ์์ผฐ์ต๋๋ค.)
if curl -s "${blue_url}" > /dev/null
then
deployment_container_name=green
deployment_target_ip=$green_url
deployment_port=$green_port
old_container_name=blue
else
deployment_container_name=blue
deployment_target_ip=$blue_url
deployment_port=$blue_port
old_container_name=green
fi
๋จผ์ ๋ฐฐํฌ ์๋ฒ์ Blue IP์ ์์ฒญ์ ๋ ๋ ค๋ณด๊ณ ํด๋น ์๋ฒ์ ์๋ต์ด ์์ผ๋ฉด Blue ์๋ฒ๊ฐ Old๊ฐ ๋๋ ๊ฒ์ ๋๋ค. ๋ง์ฝ ์๋ฒ์ ์๋ต์ด ์๋ค๋ฉด ํด๋น Blue๊ฐ New๊ฐ ๋๋ ๊ฒ์ ๋๋ค.
ssh ${deploy_username}@${deploy_ip} "nohup docker run --name ${deployment_container_name} -p ${deployment_port}:8080 ${dockerRepository}:latest > /dev/null &" &
์ด์ ๋ฐฐํฌ ์๋ฒ๋ก ์ ํฌ๊ฐ ๋ง๋ ์ต์ ๋ฒ์ ์ ๋์ปค ์ด๋ฏธ์ง๋ฅผ ์คํํ๋๋ก ๋ช ๋ น์ด๋ฅผ ์คํํฉ๋๋ค. ๋ง์ง๋ง์ > /dev/null & ๋ ์ถ๋ ฅ์ ๋ฒ๋ฆฌ๊ฒ ๋ค๋ ์๋ฏธ์ ๋๋ค.
์ด์ ์๋ก ๋ฐฐํฌํ ์๋ฒ๊ฐ ์ค์ ๋ก ์ ๋ด๋์ง ํ์ธํด๋ด์ผ๊ฒ ์ฃ ?
for retry_count in \$(seq 10)
do
if curl -s "${deployment_target_ip}" > /dev/null
then
echo "์๋ฒ Health Check์ ์ฑ๊ณตํ์ต๋๋ค."
break
fi
if [ $retry_count -eq 10 ]
then
echo "์๋ฒ Health Check์ ์คํจํ์ต๋๋ค."
exit 1
fi
echo "์๋ฒ Health Check๋ฅผ 10์ด ์ดํ์ ์ฌ์๋ํฉ๋๋ค..."
sleep 10
done
๋ฐฐํฌํ ์๋ฒ์ Curl ์์ฒญ์ ๋ ๋ ค ์ ์คํ๋์๋์ง ์ฒดํฌํฉ๋๋ค. ๋ง์ฝ 10๋ฒ๋์ Health Check์ ์คํจํ๋ฉด ํด๋น Stage๋ ์คํจํ๊ฒ ๋ฉ๋๋ค.
ssh ${deploy_username}@${deploy_ip} "echo 'set \\\$service_url ${deployment_target_ip};' > /etc/nginx/conf.d/service-url.inc"
ssh ${deploy_username}@${deploy_ip} "docker exec -i nginx service nginx reload"
echo "Nginx Reverse Proxy ๋ณ๊ฒฝ: ${deployment_target_ip}"
echo "๊ธฐ์กด ๋์ปค ์๋ฒ ์ข
๋ฃ"
ssh ${deploy_username}@${deploy_ip} "docker rm -f ${old_container_name}"
๋ฐฐํฌ๋ ์๋ฒ์ Health Check์ ์ฑ๊ณตํ์๋ค๋ฉด, ์ด์ Nginx์ ํ๋ก์ ๋ฐฉํฅ์ ์๋ก์ด ์๋ฒ๋ก ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค. ๋ฐฐํฌํ ์๋ฒ IP ์ ๋ณด๋ฅผ ์ ๋ฐ์ดํธํ๊ณ Nginx๋ฅผ reload ์ํต๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ธฐ์กด์ ์ด์์๋ ๊ตฌ๋ฒ์ ๋์ปค ์ปจํ ์ด๋๋ฅผ ์ข ๋ฃ ๋ฐ ์ญ์ ์ํค๋๋ก ํฉ๋๋ค.
์ ์ฒด ์คํฌ๋ฆฝํธ
pipeline {
agent any
tools {
jdk "openjdk-11"
}
environment {
dockerHubRegistry = 'https://example.dockerhub.com'
dockerRepository = 'example.dockerhub.com/myapp'
}
stages {
stage ('git clone') {
steps {
git branch: 'main', credentialsId: 'gitlab-credentials', url: 'https://git.example.com/myapp.git'
}
post {
failure {
echo 'Repository clone failure !'
}
success {
echo 'Repository clone success !'
}
}
}
stage('gradle build') {
steps {
sh 'chmod +x gradlew'
sh './gradlew build'
}
post {
failure {
echo 'Gradle build failure!'
}
success {
echo 'Gradle build success!'
}
}
}
stage('Docker Image Build') {
steps {
sh "docker build -t ${dockerRepository}:${currentBuild.number} ."
sh "docker build -t ${dockerRepository}:latest ."
sh "docker rmi ${dockerRepository}:${currentBuild.number}"
}
post {
failure {
echo 'Docker image build failure!'
}
success {
echo 'Docker image build success!'
}
}
}
stage('Docker Image Push') {
steps {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'dockerhub-credentials',
usernameVariable: 'DOCKER_USER_ID',
passwordVariable: 'DOCKER_USER_PASSWORD'
]]){
sh 'echo ${DOCKER_USER_PASSWORD} | docker login -u ${DOCKER_USER_ID} --password-stdin ${dockerHubRegistry}'
sh "docker push ${dockerRepository}:latest"
}
}
}
stage('Server Docker Run') {
steps {
sshagent (credentials: ['jenkins-rsa']) {
sh '''
#!/bin/bash
if curl -s "${blue_url}" > /dev/null
then
deployment_container_name=green
deployment_target_ip=$green_url
deployment_port=$green_port
old_container_name=blue
else
deployment_container_name=blue
deployment_target_ip=$blue_url
deployment_port=$blue_port
old_container_name=green
fi
ssh ${deploy_username}@${deploy_ip} "nohup docker run --name ${deployment_container_name} -p ${deployment_port}:8080 ${dockerRepository}:latest > /dev/null &" &
for retry_count in \$(seq 10)
do
if curl -s "${deployment_target_ip}" > /dev/null
then
echo "์๋ฒ Health Check์ ์ฑ๊ณตํ์ต๋๋ค."
break
fi
if [ $retry_count -eq 10 ]
then
echo "์๋ฒ Health Check์ ์คํจํ์ต๋๋ค."
exit 1
fi
echo "์๋ฒ Health Check๋ฅผ 10์ด ์ดํ์ ์ฌ์๋ํฉ๋๋ค..."
sleep 10
done
ssh ${deploy_username}@${deploy_ip} "echo 'set \\\$service_url ${deployment_target_ip};' > /root/docker-compose/nginx/nginx/conf.d/service-url.inc"
ssh ${deploy_username}@${deploy_ip} "docker exec -i nginx service nginx reload"
echo "Nginx Reverse Proxy ๋ณ๊ฒฝ: ${deployment_target_ip}"
echo "๊ธฐ์กด ๋์ปค ์๋ฒ ์ข
๋ฃ"
ssh root@${deploy_ip} "docker rm -f ${old_container_name}"
'''
}
}
}
}
}
์ด๋ ๊ฒ GitLab, Jenkins, Docker, Nginx๋ฅผ ์ด์ฉํ์ฌ ๋ฌด์ค๋จ ๋ฐฐํฌ๋ฅผ ํด๋ณด์์ต๋๋ค. Gitlab์์ ๊ธฐ๋ณธ์ ์ผ๋ก CI/CD์ ๋ํ ๊ธฐ๋ฅ์ ์ง์ํด์ฃผ๊ธฐ๋ ํ์ง๋ง Jenkins๋ฅผ ์ฌ์ฉํด๋ณด๊ณ ์ถ์ด์ ์ค์ต ํด๋ณด์์ต๋๋ค.
๊ฐ์ฌํฉ๋๋ค.
Trouble Shooting
- the input device is not a TTYํด๊ฒฐ ๋ฐฉ๋ฒ
- ๊ฐํน๊ฐ๋ค bash shell script๋ ssh, docker์์ -t ์ต์ ์ ์ค ๋ค์ <<<์ ํตํด input๊ฐ์ ๋๊ฒจ์ฃผ๋ ๊ฒฝ์ฐ pseudo-TTY์๋ฌ๊ฐ ์์ฃผ ๋จ๋ ๊ฒ์ ์ ์ ์๋ค. ๊ทธ ์ด์ ๋ -i์ต์ ์ผ๋ก input์ -t์ต์ ์ผ๋ก ์ธํด interface driver๊ฐ tty(stdin/stdout์ ์์)๋ก ์คํ๋์ด์ผํ๋๋ฐ input pipe๊ฐ ๋ค์ด์ค๋, ์ด๋ด๋๋ t์ต์ ์ ์ฃผ์ง ์์ผ๋ฉด ๋๋ค.
$ ssh root@192.168.0.100 "docker exec -it nginx service nginx reload" the input device is not a TTY
- linux redirection > permission denied
- ssh๋ก ๋ช ๋ น์ด๋ฅผ ์คํ์ํฌ ๋, ๋ช ๋ น์ด๋ฅผ ๊ผญ “”๋ก ๊ฐ์ธ์ค์ผ ํ๋ค. ์๊ทธ๋ฌ๋ฉด ๋ด๋ถ์์ ์คํํ๊ธฐ ๋๋ฌธ์ ์ํ๋ ์คํ ๊ฒฐ๊ณผ๋ฅผ ์์ํ ์ ์๋ค.
- ERROR: JAVA HOME is set to an invalid directory
REFERENCES