๋ฌด์ค๋จ ๋ฐฐํฌ
์๋น์ค๋ฅผ ์ด์ํ ๋ ์๋ก์ด ๋ฒ์ ์ ๋ฐฐํฌํ๋ ๋์ ์ด์์ค์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ข ๋ฃ๋๋ ๋ฌธ์ ๊ฐ ์๋ค.
์ด๋ฐ ๋ฌธ์ ๋ ์ฌ์ฉ์์๊ฒ ์ ์ข์ ๊ฒฝํ์ ์ ๊ณตํ๊ธฐ ๋๋ฌธ์ ์๋น์ค๊ฐ ์ ์ง๋์ง ์๊ณ ๋ฐฐํฌํ ์ ์๋ ํ๊ฒฝ์ ๊ตฌ์ฑํ๊ธฐ ์ํ ์ฌ๋ฌ ์ ๋ต์ด ์กด์ฌํ๋ค.
๋ฌด์ค๋จ ๋ฐฐํฌ ์ ๋ต
- Rolling Update
- Blue-Green Deployment
- Canary Release
Rolling Update
๋กค๋ง ์ ๋ต์ ์ฌ์ฉ์ค์ธ ์ธ์คํด์ค ๋ด์์ ์ ๋ฒ์ ์ ๊ต์ฒดํ๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์ ๋ต์ด๋ค. ์๋น์ค์ค์ธ ์ธ์คํด์ค ํ๋๋ฅผ ๋ก๋๋ฐธ๋ฐ์์์ ๋ผ์ฐํ ํ์ง ์๋๋ก ํ ๋ค ์ ๋ฒ์ ์ ์ ์ฉํ์ฌ ๋ค์ ๋ผ์ฐํ ํ๋ ์ ๋ต์ผ๋ก ๋ชจ๋ ์ธ์คํด์ค๋ฅผ ์์ฐจ์ ์ผ๋ก ์๋ก์ด ๋ฒ์ ์ผ๋ก ๊ต์ฒดํ๋ ์ ๋ต์ด๋ค.
์ฅ์
- ๋กค๋ง ์ ๋ต์ ๊ตฌ์ฑ๋ ์์์ ๊ทธ๋๋ก ์ ์งํ ์ฑ๋ก ๋ฌด์ค๋จ ๋ฐฐํฌ๊ฐ ๊ฐ๋ฅํ๊ธฐ์ ๊ด๋ฆฌ๊ฐ ํธํ๋ค.
- ์ธ์คํด์ค๋ง๋ค ์ฐจ๋ก๋ก ๋ฐฐํฌ๋ฅผ ์งํํ๊ธฐ ๋๋ฌธ์ ์ํฉ์ ๋ฐ๋ผ ์์ฝ๊ฒ ๋กค๋ฐฑ์ด ๊ฐ๋ฅํ๋ค.
๋จ์
- ๋กค๋ง ์ ๋ต์ ์ธ์คํด์ค๊ฐ ์ ํ์ ์ผ ๊ฒฝ์ฐ์ ์ฌ์ฉ๋๋ฉฐ ์ ๋ฒ์ ์ ๋ฐฐํฌํ ๋ ๊ธฐ์กด ํธ๋ํฝ์ ๊ฐ๋นํ๋ ์ธ์คํด์ค ์๊ฐ ๊ฐ์ํ๊ธฐ ๋๋ฌธ์ ์๋น์ค ์ฒ๋ฆฌ ์ฉ๋์ ๊ณ ๋ คํด์ผ ํ๋ค.
- ๋ฐฐํฌ๊ฐ ์งํ๋๋ ๋์ ๊ตฌ๋ฒ์ ๊ณผ ์ ๋ฒ์ ์ด ๊ณต์กดํ๊ธฐ ๋๋ฌธ์ ํธํ์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
Blue-Green Deployment
์ด์ ํ๊ฒฝ์์ ๊ตฌ ๋ฒ์ ๊ณผ ๋์ผํ๊ฒ ์ ๋ฒ์ ์ ๋ฐฐํฌํ๊ณ ์ผ์ ํ ์ ํํ์ฌ ๋ชจ๋ ํธ๋ํฝ์ ์ ๋ฒ์ ์ผ๋ก ์ ํํ๋ ์ ๋ต์ด๋ค. ๋ธ๋ฃจ ๊ทธ๋ฆฐ ์ ๋ต์ ๋ฌผ๋ฆฌ์ ์ธ ์๋ฒ๋ฅผ ๋์์ผ๋ก ์ฌ์ฉํ๊ธฐ์๋ ๋น์ฉ์ ๋ฒ๊ฒ๋ค. ๊ทธ๋์ ํด๋ผ์ฐ๋ ํ๊ฒฝ์์ ์ฝ๊ฒ ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ฑฐ๋ ์์จ ์ ์๋ AWS ๋ Docker ์ ๊ฐ์ ๊ฐ์ ํ๊ฒฝ์์ ์ฌ์ฉํ๋ ๊ฒ์ด ํจ๊ณผ์ ์ด๋ค.
์ฅ์
- ๋กค๋ง ์ ๋ต๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๋น ๋ฅธ ๋กค๋ฐฑ์ด ๊ฐ๋ฅํ๋ค.
- ๊ตฌ๋ฒ์ ๊ณผ ๋์ผํ ํ๊ฒฝ์์ ์ ๋ฒ์ ์ธ์คํด์ค๋ฅผ ๊ตฌ์ฑํ๊ธฐ ๋๋ฌธ์ ์ค์ ์๋น์ค ํ๊ฒฝ์์ ์ ๋ฒ์ ์ ๋ฏธ๋ฆฌ ํ ์คํธํ ์ ์๋ค.
- ๋ฐฐํฌ๊ฐ ์๋ฃ๋ ํ ๋จ์์๋ ๊ตฌ๋ฒ์ ํ๊ฒฝ์ ๋ค์ ๋ฐฐํฌ์ ์ฌ์ฌ์ฉ ํ ์ ์๋ค.
- ์ ๋ฒ์ ๋ฐฐํฌ๊ฐ ์งํ๋๋ ๋์ ์๋ฒ ๊ณผ๋ถํ๊ฐ ์ผ์ด๋ ํ๋ฅ ์ด ์ ๋ค.
Canary Release
์นด๋๋ฆฌ์
๋ผ๋ ์๋ ์ ๋
๊ฐ์ค์ ๊ต์ฅํ ๋ฏผ๊ฐํ ๋๋ฌผ์ด๋ค. ๊ณผ๊ฑฐ ๊ด๋ถ๋ค์ ์ ๋
๊ฐ์ค์ ๋ฏผ๊ฐํ ์นด๋๋ฆฌ์ ์๋ฅผ ์ ๋
๊ฐ์ค ๋์ถ์ ์ํ์ ๊ฐ์งํ๋ ์ฉ๋๋ก ์ฌ์ฉํ์๋ค.
์นด๋๋ฆฌ ์ ๋ต์ ์ํ์ ๋น ๋ฅด๊ฒ ๊ฐ์งํ ์ ์๋ ๋ฐฐํฌ ์ ๋ต์ด๋ค. ์ ๋ฒ์ ์ ์ ๊ณต ๋ฒ์๋ฅผ ๋๋ ค๊ฐ๋ฉด์ ๋ชจ๋ํฐ๋ง ๋ฐ ํผ๋๋ฐฑ ๊ณผ์ ์ ๊ฑฐ์น ์ ์๋ค. ๋ก๋๋ฐธ๋ฐ์๋ฅผ ํตํด ์ ๋ฒ์ ์ ์ ํ์ ๊ฒฝํํ๋ ์ฌ์ฉ์๋ฅผ ์กฐ์ ํ ์ ์๋ ๊ฒ์ด ํน์ง์ผ๋ก ์ ๋ฒ์ ์ ํน์ ์ฌ์ฉ์ ํน์ ๋จ์ ๋น์จ์ ๋ฐ๋ผ ๊ตฌ๋ถํด ์ ๊ณตํ ์ ์๋ค. ์ด๋ ๊ฒ ์๋ฒ์ ํธ๋ํฝ ์ผ๋ถ๋ฅผ ์ ๋ฒ์ ์ผ๋ก ๋ถ์ฐํ์ฌ ์ค๋ฅ ์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์๋ค.
์ฅ์
- ๋ธ๋ฃจ ๊ทธ๋ฆฐ ์ ๋ต๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์ ๋ฐฐํฌ ์ ์ ์ค์ ์ด์ ํ๊ฒฝ์์ ๋ฏธ๋ฆฌ ํ ์คํธํ ์ ์๋ค.
- ์นด๋๋ฆฌ ๋ฐฐํฌ๋ ๋จ๊ณ์ ์ธ ์ ํ ๋ฐฉ์์ ํตํด ๋ถ์ ์ ์ํฅ์ ์ต์ํํ๊ณ ์ํฉ์ ๋ฐ๋ผ ํธ๋ํฝ ์์ ๋๋ฆฌ๊ฑฐ๋ ๋กค๋ฐฑํ ์ ์๋ค.
๋จ์
- ๋กค๋ง ์ ๋ต๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ตฌ๋ฒ์ ๊ณผ ์ ๋ฒ์ ์ด ๋ชจ๋ ์ด์๋๊ธฐ ๋๋ฌธ์ ๋ฒ์ ๊ด๋ฆฌ๊ฐ ํ์ํ๋ค.
์ฌ์ ์ค๋น
์ ๋ต ๊ตฌ์ฑ
๋ฌด์ค๋จ ๋ฐฐํฌ๋ฅผ ๊ตฌํํ๊ธฐ ์ํด ์ ์ ๋ต ์ค ํ๋๋ฅผ ์ ํํด์ผ ํ๊ณ ๊ณต์ฑ ํ๋ก์ ํธ์์ ๋ธ๋ฃจ ๊ทธ๋ฆฐ ์ ๋ต์ ์ ํํ๋๋ฐ ๊ทธ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ๋ค.
- EC2 ์ธ์คํด์ค๋ฅผ ์ฌ์ ๋กญ๊ฒ ์ฌ์ฉํ ์ ์๋ ํ๊ฒฝ์์ ๋ธ๋ฃจ ๊ทธ๋ฆฐ ์ ๋ต์ด ์ ํฉํ ๊ฒ์ด๋ผ ํ๋จ
- ์ฌ์ฉ์ค์ธ EC2 ์ธ์คํด์ค๊ฐ ๋ ๊ฐ์ ์คํ๋ง ํ๋ก์ ํธ๋ฅผ ๋์์ ๊ตฌ๋ํ๊ธฐ์ ์ค๋ฒํค๋๊ฐ ํด ๊ฒ์ด๋ผ ํ๋จ
๋ธ๋ฃจ ๊ทธ๋ฆฐ ์ ๋ต ์ค ํ๋์ EC2 ์ธ์คํด์ค ๋ด๋ถ์์ ๋ ๊ฐ์ ์คํ๋ง ํ๋ก์ ํธ๋ฅผ ๊ตฌ๋ํ์ฌ ํฌํธ ์ค์์นญ์ ํตํด ๋ธ๋ฃจ ๊ทธ๋ฆฐ ์ ๋ต์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ด ์๋๋ฐ 2๋ฒ ์ด์ ๋๋ฌธ์ EC2 ์ธ์คํด์ค๋ฅผ ๋๋ ค IP ์ค์์นญ์ ํตํ ๋ธ๋ฃจ ๊ทธ๋ฆฐ ์ ๋ต์ ๊ตฌํํ๊ธฐ๋ก ํ๋ค.
๋ธ๋ฃจ ๊ทธ๋ฆฐ ์ ๋ต ์๋๋ฆฌ์ค
- ์ธ์คํด์ค๋ค์
group_a
์group_b
๋ก ๊ตฌ์ฑ - ๊ตฌ๋์ค์ธ
group
์ ํ์ธ group_a
๊ฐ ๊ตฌ๋์ค์ด๋ผ๋ฉดgroup_a
๋blue
๋กgroup_b
๋green
์ผ๋ก ์ค์ green
๊ทธ๋ฃน์ ์ ๋ฒ์ ๋ฐฐํฌ ํ ์คํgreen
๊ทธ๋ฃน์ ๋ํ Health Check ์งํNginx
Reverse Proxy ์ค์ ์green
๊ทธ๋ฃน์ผ๋ก ์ ํblue
๊ทธ๋ฃน ์๋ฒ๋ฅผ ๋ชจ๋ ์ข ๋ฃ
Global properties ์ค์
group_a
์ group_b
๋ก ๊ตฌ๋ถํ WAS ์ IP ๋ฅผ ๋ช
์ํด์ฃผ๊ธฐ ์ํด Global properties ๋ฅผ ์ค์ ํด์ฃผ์ด์ผ ํ๋ค.
Manage Jenkins โ Configure System โ Global properties ์์ ์ค์ ํ ์ ์๋ค.
๋ช ์๋ IP ๋ค๊ณผ SSH ํต์ ์ด ์ด๋ฃจ์ด์ ธ์ผํ๊ธฐ ๋๋ฌธ์ Publish over SSH ํญ๋ชฉ์ ๋ชจ๋ ๋ฑ๋ก๋์ด ์์ด์ผ ํ๋ค.
Pipeline ์ฝ๋
pipeline {
agent any
stages {
stage('git clone') {
steps {
checkout([$class: 'GitSCM', branches: [[name: '*/dev']], extensions: [[$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true, recursiveSubmodules: false, reference: '', trackingSubmodules: true]], userRemoteConfigs: [[credentialsId: 'github-webhook-access-token', url: 'https://github.com/woowacourse-teams/2022-gong-check']]])
}
}
stage('build') {
steps {
dir('backend') {
sh '''
echo '=== start application bootJar ==='
./gradlew clean bootJar
'''
}
}
}
stage('publish ssh to server') {
environment {
GROUP_GREEN = sh (returnStdout: true, script: '''#!/bin/bash
group_a_array=($group_a)
alive_was_count=0
for i in "${group_a_array[@]}"
do
if curl -I -X OPTIONS "http://${i}:8080"; then
alive_was_count=$((alive_was_count+1))
fi
done
if [ ${alive_was_count} -eq 0 ];then
echo 'group_a'
elif [ ${alive_was_count} -eq ${#group_a_array[@]} ];then
echo 'group_b'
else
echo 'currently runnig was counts are not correct'
exit 100
fi''').trim()
}
steps {
dir('backend') {
script {
def green_ips;
def blue_ips;
def envVar = env.getEnvironment();
if ("group_a" == GROUP_GREEN) {
green_ips = envVar.group_a.split(' ');
blue_ips = envVar.group_b.split(' ');
} else {
green_ips = envVar.group_b.split(' ');
blue_ips = envVar.group_a.split(' ');
}
def green_ips_inline = green_ips.join(' ');
def map = parseSSHServerConfiguration();
for (item in green_ips) {
sshPublisher(publishers: [sshPublisherDesc(configName: map.get(item), transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: 'sh /home/ubuntu/script/server_start.sh', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/deploy', remoteDirectorySDF: false, removePrefix: 'build/libs', sourceFiles: 'build/libs/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
}
sh '''#!/bin/bash
array=(''' +green_ips_inline+ ''')
for ip in "${array[@]}"
do
url=${array[0]}
attempts=10
timeout=10
online=false
echo "Checking status of $url."
for (( i=1; i<=$attempts; i++ ))
do
code=$(curl -sL --connect-timeout 20 --max-time 30 -w "%{http_code}\\n" "$url:8080" -o /dev/null)
if [ "$code" = "200" ]; then
online=true
echo "Connection successful."
break
else
sleep $timeout
echo "Connection failed."
fi
done
if $online; then
echo "Monitor finished, website is online."
exit 0 # Build Success
else
echo "Monitor failed, website seems to be down."
exit 1 # Build Failed
fi
done
'''
sshPublisher(publishers: [sshPublisherDesc(configName: 'gongcheck-reverse-proxy-dev', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''#!/bin/bash
function parse_was_address() {
array=(''' +green_ips_inline+ ''')
str=""
for i in "${array[@]}"
do
str+='set $service_url '
str+="http://${i}:8080;\n"
done
echo -e "$str"
}
echo -e "$(parse_was_address)" | sudo tee /etc/nginx/conf.d/service-url.inc
sudo service nginx restart
''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
for (item in blue_ips) {
sshPublisher(publishers: [sshPublisherDesc(configName: map.get(item), transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: 'sh /home/ubuntu/script/kill.sh', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/deploy', remoteDirectorySDF: false, removePrefix: 'build/libs', sourceFiles: 'build/libs/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
}
}
}
}
}
stage('clean up workspace') {
steps {
cleanWs deleteDirs: true
}
}
}
post {
success {
slackSend (channel: 'jenkins', color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}
failure {
slackSend (channel: 'jenkins', color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}
}
}
@NonCPS
def parseSSHServerConfiguration() {
def xml = new XmlSlurper().parse("${JENKINS_HOME}/jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml");
def server_nick_names = xml.'**'.findAll{it.name() == 'name'};
def server_ip_address = xml.'**'.findAll{it.name() == 'hostname'};
def map = new HashMap();
for (i = 0; i < server_nick_names.size(); i++) {
def server_ip_str = (String) server_ip_address.get(i);
def server_nick_name_str = (String) server_nick_names.get(i);
map.put(server_ip_str, server_nick_name_str);
}
println(map);
return map;
}
๋ธ๋ฃจ ๊ทธ๋ฆฐ ๊ทธ๋ฃน ๊ตฌ๋ถ
environment {
GROUP_GREEN = sh (returnStdout: true, script: '''#!/bin/bash
group_a_array=($group_a)
alive_was_count=0
for i in "${group_a_array[@]}"
do
if curl -I -X OPTIONS "http://${i}:8080"; then
alive_was_count=$((alive_was_count+1))
fi
done
if [ ${alive_was_count} -eq 0 ];then
echo 'group_a'
elif [ ${alive_was_count} -eq ${#group_a_array[@]} ];then
echo 'group_b'
else
echo 'currently runnig was counts are not correct'
exit 100
fi''').trim()
}
Global properties ์ค์ ์ ํตํด group_a
group_b
๋ณ์๋ฅผ ์ฌ์ฉํ ์ ์๋ค. group_a
๋ฐฐ์ด์ ์ํํ๋ฉด์ OPTIONS
์์ฒญ์ ๋ณด๋ด ํด๋น ๊ทธ๋ฃน์ ์ํ ์๋ฒ๋ค์ด ๊ตฌ๋์ค์ธ์ง ํ์ธํ ์ ์๋ค.
environment
๋ธ๋ญ์ ํตํด ์ ์คํฌ๋ฆฝํธ์์ ๋ฐํํ ๊ฐ์ ํ๊ฒฝ ๋ณ์์ ๋ด์์ ์ธ๋ถ Groovy ์คํฌ๋ฆฝํธ์์ ์ฌ์ฉํ ์ ์๋ค.
group_a
์ ์ํ ์๋ฒ๋ค์ด ๊ตฌ๋์ค์ด๋ผ๋ฉด GROUP_GREEN
์๋ group_a
๋ผ๋ ๋ฌธ์์ด์ด ์ ์ฅ๋๋ค.
๊ทธ๋ฃน๋ณ IP ๋ฐฐ์ด ์์ฑ
def green_ips;
def blue_ips;
def envVar = env.getEnvironment();
if ("group_a" == GROUP_GREEN) {
green_ips = envVar.group_a.split(' ');
blue_ips = envVar.group_b.split(' ');
} else {
green_ips = envVar.group_b.split(' ');
blue_ips = envVar.group_a.split(' ');
}
env.getEnvironment()
๋ฉ์๋๋ฅผ ํตํด Global properties ์์ ์ ์ธํ ํ๊ฒฝ ๋ณ์๋ฅผ Groovy ์คํฌ๋ฆฝํธ์์ ์ฌ์ฉํ ์ ์๋ค.
environment
๋ธ๋ญ์์ ์ ์ธํ GROUP_GREEN
์ ํตํด green_ips
์ blue_ips
๋ฅผ ๊ตฌ๋ถํ๋ค.
group_a
๊ฐ green
์ด๋ผ๋ฉด green_ips
์๋ 192.168.1.199
๊ฐ ํ ๋น๋์์ ๊ฒ์ด๋ค.
IP ์ ํด๋นํ๋ SSH Server Name Map ์์ฑ
@NonCPS
def parseSSHServerConfiguration() {
def xml = new XmlSlurper().parse("${JENKINS_HOME}/jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml");
def server_nick_names = xml.'**'.findAll{it.name() == 'name'};
def server_ip_address = xml.'**'.findAll{it.name() == 'hostname'};
def map = new HashMap();
for (i = 0; i < server_nick_names.size(); i++) {
def server_ip_str = (String) server_ip_address.get(i);
def server_nick_name_str = (String) server_nick_names.get(i);
map.put(server_ip_str, server_nick_name_str);
}
println(map);
return map;
}
Jenkins ์ sshPublisher
๋ IP ๊ฐ ์๋ SSH Server Name ์ผ๋ก ์์
์ ์ํํ๊ธฐ ๋๋ฌธ์ IP ์ ํด๋นํ๋ SSH Server Name ์ ์์๋ด์ผ ํ๋ค.
Jenkins ๋ Publish over SSH ํญ๋ชฉ์์ ์ค์ ํ๋ SSH Server Name ๊ณผ IP ๋ฑ์ ์ ๋ณด๋ฅผ ${JENKINS_HOME}/jenkins.plugins.publish_over_ssh.BapSshPublisherPlugin.xml
์ ์ ์ฅํ๋ค.
์ ํ์ผ์ ํ์ฑํ์ฌ IP ์ SSH Server Name ์ด key-value
ํํ๋ก ์ด๋ฃจ์ด์ง Map ์ ์์ฑํด ์คํฌ๋ฆฝํธ์์ ํ์ฉํ ํ์๊ฐ ์๋ค.
Jenkins ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์์
์ค์ง ๋ฐ ์ฌ๊ฐ ๋ฑ์ ์ํด ์คํฌ๋ฆฝํธ์ step
๋ณ ์ํ๋ฅผ ์ง๋ ฌํํ์ฌ ์ ์ฅํ๋๋ฐ, XML ํ์ฑ ๊ด๋ จ ๊ฐ์ฒด๋ค์ ์ง๋ ฌํ๊ฐ ๋ถ๊ฐ๋ฅํ์ฌ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
๋๋ฌธ์ XML ํ์ฑ ๊ด๋ จ ๋ก์ง์ ๋ณ๋์ ๋ฉ์๋๋ก ๋ถ๋ฆฌํ์ฌ @NonCPS
์ ๋ํ
์ด์
์ ํตํด ์ง๋ ฌํ ๋ฐ ์ ์ฅ ๋์์์ ์ ์ธ์ํจ๋ค.
{
192.168.1.218=gongcheck-frontend-dev,
192.168.1.247=gongcheck-frontend-prod,
192.168.1.220=gongcheck-image-prod,
192.168.1.199=gongcheck-backend-dev-a,
192.168.1.234=gongcheck-backend-dev-b,
192.168.1.201=gongcheck-backend-prod,
192.168.1.212=gongcheck-image-dev,
192.168.1.240=gongcheck-reverse-proxy-dev
}
ํ์ฑ๋ Map ์ ํ์ธํด๋ณด๋ฉด ์์ ๊ฐ์ด IP ์ ํด๋นํ๋ SSH Server Name ์ ๊ฐ์ ธ์ฌ ์ ์๋๋ก ์์ฑ๋์ด ์๋ค.
Green ๊ทธ๋ฃน ๋ฐฐํฌ ๋ฐ WAS ์คํ
for (item in green_ips) {
sshPublisher(publishers: [sshPublisherDesc(configName: map.get(item), transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: 'sh /home/ubuntu/script/server_start.sh', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/deploy', remoteDirectorySDF: false, removePrefix: 'build/libs', sourceFiles: 'build/libs/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
}
green_ips
๋ฐฐ์ด์ ์ํํ๋ฉด์ map.get(item)
์ ํตํด IP ์ ํด๋นํ๋ SSH Server Name ์ ํตํด sshPublisher
๋ฅผ ์คํํ ์ ์๋ค.
green_ips
์ 192.168.1.199
๊ฐ ๋ค์ด์๋ค๋ฉด, map.get(item)
์ ํตํด gongcheck-backend-dev-a
์ ํด๋นํ๋ WAS ๋ก sshPublisher
๋ฅผ ์คํํ ๊ฒ์ด๋ค.
echo "> ํ์ฌ ์งํ์ค์ธ application pid ์กฐํ"
CURRENT_PID=$(ps -ef | grep java | grep jar | grep -v nohup | grep gong-check | awk '{print $2}')
echo "> ํ์ฌ ์งํ์ค์ธ application pid : $CURRENT_PID"
if [ -z ${CURRENT_PID} ]; then
echo "> ํ์ฌ ๊ตฌ๋์ค์ธ ์ดํ๋ฆฌ์ผ์ด์
์ด ์์ผ๋ฏ๋ก ์ข
๋ฃํ์ง ์์ต๋๋ค."
else
echo "> sudo kill -9 $CURRENT_PID"
sudo kill -9 ${CURRENT_PID}
sleep 10
sudo lsof -i:8080
echo "> ์ดํ๋ฆฌ์ผ์ด์
์ ์ ์ข
๋ฃ ์๋ฃ"
fi
echo "> ์ดํ๋ฆฌ์ผ์ด์
๋ฐฐํฌ ์์"
JAR_PATH=$(ls -t /home/ubuntu/deploy/*.jar | head -1)
BUILD_ID=dontKillMe sudo nohup java -jar ${JAR_PATH} --spring.profiles.active=dev 2>> /dev/null >> /dev/null &
echo "> ์ดํ๋ฆฌ์ผ์ด์
๋ฐฐํฌ ์ข
๋ฃ"
sshPublisher
๋ ์ธ์คํด์ค ๋ด๋ถ์ ์๋ server_start.sh
๋ฅผ ์คํ์ํค๋๋ฐ, ์ด๋ ์์ ๊ฐ๋ค.
Green ๊ทธ๋ฃน Health Check
sh '''#!/bin/bash
array=(''' +green_ips_inline+ ''')
for ip in "${array[@]}"
do
url=${array[0]}
attempts=10
timeout=10
online=false
echo "Checking status of $url."
for (( i=1; i<=$attempts; i++ ))
do
code=$(curl -sL --connect-timeout 20 --max-time 30 -w "%{http_code}\\n" "$url:8080" -o /dev/null)
if [ "$code" = "200" ]; then
online=true
echo "Connection successful."
break
else
sleep $timeout
echo "Connection failed."
fi
done
if $online; then
echo "Monitor finished, website is online."
exit 0 # Build Success
else
echo "Monitor failed, website seems to be down."
exit 1 # Build Failed
fi
done
'''
์ ์คํฌ๋ฆฝํธ๋ฅผ ํตํด green
๊ทธ๋ฃน์ ๋ํ Health Check ๋ฅผ ์งํํ๋ค. curl
์์ฒญ์ ํตํด ํด๋น IP ์ 8080๋ฒ ํฌํธ๊ฐ ์ด๋ ค์๋์ง ํ์ธํ๋ค. 10๋ฒ์ ์๋ ๊ฐ ์๋ฒ๊ฐ ์๋ตํ์ง ๋ชปํ๋ค๋ฉด ์๋ฒ ๋ฐฐํฌ์ ์คํจํ๋ค๊ณ ํ๋จํ๊ณ ์คํฌ๋ฆฝํธ๋ฅผ ์ข
๋ฃํ๋ค.
Nginx ์ค์ ๋ณ๊ฒฝ
green
์ ํด๋นํ๋ WAS ๊ฐ ์ค๋น๊ฐ ๋์ด ์์ผ๋ Nginx
์์ ์์ฒญ์ green
์ผ๋ก ๋ณด๋ผ ์ ์๋๋ก ์ค์ ์ ๋ณ๊ฒฝํด์ฃผ์ด์ผ ํ๋ค.
server {
server_name dev.gongcheck.shop;
include /etc/nginx/conf.d/service-url.inc;
location / {
proxy_pass $service_url/index.html;
}
# ...
}
ํ์ฌ Nginx
์์ proxy_pass
์ service_url
์ด๋ผ๋ ๋ณ์๋ฅผ ํ ๋นํ๊ณ ์๋ค.
sshPublisher(publishers: [sshPublisherDesc(configName: 'gongcheck-reverse-proxy-dev', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''#!/bin/bash
function parse_was_address() {
array=(''' +green_ips_inline+ ''')
str=""
for i in "${array[@]}"
do
str+='set $service_url '
str+="http://${i}:8080;\n"
done
echo -e "$str"
}
echo -e "$(parse_was_address)" | sudo tee /etc/nginx/conf.d/service-url.inc
sudo service nginx restart
''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
ํด๋น ๋ณ์๋ service-url.inc
๋ฅผ ํตํด ์ ์ธ๋๊ณ ์๊ธฐ์ ์ด๋ฅผ ์์ ํ๋ ์ ์คํฌ๋ฆฝํธ๋ฅผ sshPublisher
๋ฅผ ํตํด ์ํํ๋ค. tee
๋ช
๋ น์ด๋ฅผ ํตํด ํด๋น ํ์ผ์ ๋ฎ์ด์์ฐ๊ฑฐ๋ ์๋ก ์์ฑํ ์ ์๋ค.
set $service_url http://192.168.1.199:8080;
green_ips_inline
์ 192.168.1.199
๋ง ์กด์ฌํ๋ค๋ฉด, service-url.inc
ํ์ผ์๋ ์์ ๊ฐ์ ์คํฌ๋ฆฝํธ๊ฐ ์์ฑ๋์ด ์์ ๊ฒ์ด๋ค.
Blue ๊ทธ๋ฃน WAS ์ข ๋ฃ
for (item in blue_ips) {
sshPublisher(publishers: [sshPublisherDesc(configName: map.get(item), transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: 'sh /home/ubuntu/script/kill.sh', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/deploy', remoteDirectorySDF: false, removePrefix: 'build/libs', sourceFiles: 'build/libs/*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
}
blue_ips
๋ฐฐ์ด์ ์ํํ๋ฉด์ map.get(item)
์ ํตํด IP ์ ํด๋นํ๋ SSH Server Name ์ ํตํด sshPublisher
๋ฅผ ์คํํ ์ ์๋ค.
blue_ips
์ 192.168.1.234
๊ฐ ๋ค์ด์๋ค๋ฉด, map.get(item)
์ ํตํด gongcheck-backend-dev-b
์ ํด๋นํ๋ WAS ๋ก sshPublisher
๋ฅผ ์คํํ ๊ฒ์ด๋ค.
echo "> ํ์ฌ ์งํ์ค์ธ application pid ์กฐํ"
CURRENT_PID=$(ps -ef | grep java | grep jar | grep -v nohup | grep gong-check | awk '{print $2}')
echo "> ํ์ฌ ์งํ์ค์ธ application pid : $CURRENT_PID"
if [ -z ${CURRENT_PID} ]; then
echo "> ํ์ฌ ๊ตฌ๋์ค์ธ ์ดํ๋ฆฌ์ผ์ด์
์ด ์์ผ๋ฏ๋ก ์ข
๋ฃํ์ง ์์ต๋๋ค."
else
echo "> sudo kill -9 $CURRENT_PID"
sudo kill -9 ${CURRENT_PID}
sleep 10
sudo lsof -i:8080
echo "> ์ดํ๋ฆฌ์ผ์ด์
์ ์ ์ข
๋ฃ ์๋ฃ"
fi
sshPublisher
๋ ์ธ์คํด์ค ๋ด๋ถ์ ์๋ kill.sh
๋ฅผ ์คํ์ํค๋๋ฐ, ์ด๋ ์์ ๊ฐ๋ค.
๊ฐ์ ์ฌํญ
์ถํ WAS ๋ฅผ ๋ค์คํํ๋ค๋ฉด ๋ก๋ ๋ฐธ๋ฐ์ฑ์ ์ ์ฉํด์ผํ๊ธฐ ๋๋ฌธ์ Nginx
์ค์ ์ ๋ณ๊ฒฝํ๋ ์คํฌ๋ฆฝํธ๋ฅผ ์์ ํด์ฃผ์ด์ผ ํ๋ค. ๋ก๋ ๋ฐธ๋ฐ์ฑ ์ค์ ์ ์ฉ์ ํฌ๊ฒ ์ด๋ ต์ง ์๊ธฐ ๋๋ฌธ์ ์ถํ์ ์๋น์ค๊ฐ WAS ๋ค์คํ๋ฅผ ์๊ตฌํ๋ค๋ฉด ๊ทธ๋ ์์ ํด์ฃผ์ด๋ ๋ฌด๋ฐฉํ๋ค.
์ถ๊ฐ๋ก blue
๊ทธ๋ฃน์ ํด๋นํ๋ EC2 ์ธ์คํด์ค๊ฐ idle
์ํ๋ก ๋๊ธฐํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๋ถํ์ํ ๋น์ฉ์ด ๋ฐ์ํ๊ณ ์๋ ๊ฒ์ ์ ์ ์๋ค. ๋๋ฌธ์ ๋ฐฐํฌ๊ฐ ์ด๋ฃจ์ด์ง ๋ ๋ง๋ค EC2 ์ธ์คํด์ค๋ฅผ ์๋์ผ๋ก ์์ฑ ๋ฐ ์ ๊ฑฐํด์ฃผ๋ ๋ฐฉ์์ ๊ณ ๋ คํด ๋ณผ ํ์๊ฐ ์๋ค.