- Test Github Actions + AWS Code Deploy & S3 & EC2 [Done]
- Test Jenkins + AWS Lightsail [Done]
- Declarative Pipeline [Done]
- Blue & Green Zero-Downtime Deployment [Done]
Jenkins์ Item์ค ์ ์ธํ Pipeline์ผ๋ก ๊ตฌ์ถ์ ์๋ฃํด์ ๊ธ์ ์์ฑํฉ๋๋ค.
์ ๋ฒ์ Free Style Project๋ก Jenkins CICD๋ฅผ ๊ตฌํํ์๋๋ฐ, ์๊ตฌ์ฌํญ์ด ๋ณ๊ฒฝํจ์ ๋ฐ๋ผ Declarative Pipeline Script๋ฅผ ์ด์ฉํ Jenkins CICD๋ฅผ ๊ตฌ์ถํด๋ณด์์ต๋๋ค.
์คํฌ๋ฆฝํธ๋ฅผ ๋จผ์ ์์ฑํด๋๊ณ Pipeline์ ๊ตฌ์ถํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค!!
Jenkins Credential, JDK, Gradle, SSH, Token ๋ฑ Jenkins Web์์์ ๋ชจ๋ ์ค์ ์ด ์๋ฃ๋์๋ค๋ ๊ฐ์ ํ์ ๊ธ์ ์ผ์ต๋๋ค.
๋ด์ฉ ์ถ๊ฐ (Jenkins Git ๊ด๋ จ ์ด์ ํด๊ฒฐ)
- Jenkins Credentials์ SSH์ ํ๋ก์ ํธ ๊ตฌ์ฑ์ SSH๊ฐ ์ผ์นํ๋์ง ์ฒดํฌ
- ๋ง์ฝ SSH๋ก Credentials๋ฅผ ๊ตฌ์ฑํ๋ค๋ฉด ํ๋ก์ ํธ ๊ตฌ์ฑ์ ํ๋ก์ ํธ URL๋ ssh URL๋ก ์์ฑํด์ผ ํฉ๋๋ค.
- Jenkins Server์ /var/lib/jenkins ๋๋ ํฐ๋ฆฌ๋ ๊ทธ ํ์์ ํ์ผ๋ค์ ๊ถํ, ์์ ์ ํ์ธ ํ์ ( ์์ ์: jenkins:jenkins ๋ก ํด์ผํจ)
- EC2 jenkins user์ .ssh ํ์ ํ์ผ๋ค์ ๊ถํ, ์์ ์ ํ์ธ
- known_host์ EDSDA ํค ์ถ๊ฐ ๋๋์ง ํ์ธ
# Jenkins ๋๋ ํฐ๋ฆฌ ํ์ ๋ชจ๋ ํ์ผ๋ค์ ์์ ๊ถ ๋ณ๊ฒฝ
chown -R jenkins:jenkins/var/lib/jenkin
# Jenkins User์ ssh ํ์ผ๋ค ์์ ์, ๊ถํ ๋ณ๊ฒฝ
chown -R jenkins:jenkins /var/lib/jenkins/.ssh
chmod 600 /var/lib/jenkins/.ssh/id_rsa
chmod 644 /var/lib/jenkins/.ssh/id_rsa.pub
EC2 m5a.large ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ์ผ๋ฉฐ AMI๋ Ubuntu 20.04 LTS์ ๋๋ค.
root ๊ณ์ ์ผ๋ก ์งํํ์ผ๋ฉฐ ๋ณด์๊ทธ๋ฃน ํฌํธ๋ ์ด๋ฏธ ์ด์ด๋์ ์ํ์์ ์๋ฒ ์ธํ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํด ์๋ฒ๋ฅผ ์ธํ ํ์์ต๋๋ค.
๊ธฐ๋ณธ์ ์ธ ํจํค์ง๋ค๊ณผ ๋ฐฉํ๋ฒฝ ์ค์ , Docker, Jenkins๋ฅผ ์ค์นํ๊ณ ์คํ ์ ์งํํ ๋๋ง๋ค ๋ก๊ทธ ํ์ผ์ ๊ธฐ๋กํฉ๋๋ค.
#!/bin/bash
# APT ์
๋ฐ์ดํธ
apt-get -y update
ate-get -y upgrade
echo ----- APT Update ์ข
๋ฃ ---- | tee settinglogs
# ๊ธฐ๋ณธ ํจํค์ง ์ค์น
apt install -y firewalld mysql-client net-tools curl wget gnupg lsb-release ca-certificates apt-transport-https software-properties-common gnupg-agent openjdk-11-jdk
echo ----- ๊ธฐ๋ณธ ํจํค์ง ์ค์น ์๋ฃ ----- >> settinglogs
# OpenJDK ์ ์ญ๋ณ์ ์ค์
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
echo ----- $JAVA_HOME ----- >> settinglogs
# Firewalld ์์
systemctl start firewalld && systemctl enable firewalld
echo ----- Firewalld ์์ ----- >> settinglogs
# ํฌํธ ์คํ
firewall-cmd --permanent --add-port=22/tcp
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --permanent --add-port=5000/tcp
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --permanent --add-port=18080/tcp
firewall-cmd --permanent --add-port=13306/tcp
# Jenkins < - > Github Webhook์ ์ํ IP ํ์ฉ
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address=192.30.252.0/22 port port="22" protocol="tcp" accept' && firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address=185.199.108.0/22 port port="22" protocol="tcp" accept' && firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address=140.82.112.0/20 port port="22" protocol="tcp" accept' && firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address=143.55.64.0/20 port port="22" protocol="tcp" accept' && firewall-cmd --permanent --add-rich-rule='rule family="ipv6" source address=2a0a:a440::/29 port port="22" protocol="tcp" accept' && firewall-cmd --permanent --add-rich-rule='rule family="ipv6" source address=2606:50c0::/32 port port="22" protocol="tcp" accept'
# Firewall Settings ์ ์ฅ
firewall-cmd --reload
echo ----- Firewalld ์ค์ ์๋ฃ ----- >> settinglogs
# ๋์ปค ์ค์น
## ๋์ปค GPG Key ์ถ๊ฐ
mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
## ๋์ปค ์ ์ฅ์ ์ค์
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
## ๋์ปค ์์ง ์ค์น
apt install -y docker-ce docker-ce-cli containerd.io
echo ----- ๋์ปค ์ค์น ์๋ฃ ----- >> settinglogs
## ๋์ปค ์์
systemctl start docker && systemctl enable docker
echo ----- ๋์ปค ์์ ----- >> settinglogs
# ์ ํจ์ค ์ค์น
wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \
/usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
apt -y update
apt install -y jenkins
echo ----- Jenkins ์ค์น ์๋ฃ ----- >> settinglogs
# ๋์ปค, sudo ๊ถํ ๋ถ์ฌ
usermod -aG docker jenkins
usermod -aG sudo jenkins
chmod 666 /var/run/docker.sock
# ์ ํจ์ค ํฌํธ ๋ณ๊ฒฝ
sed -i 's/8080/18080/g' /usr/lib/systemd/system/jenkins.service
# ์ ํจ์ค ์์
systemctl start jenkins && systemctl enable jenkins
echo ----- Jenkins ์์ ์๋ฃ ----- >> settinglogs
#!/bin/bash
echo -e "\033[31m"=== Firewalld Port Status ==="\033[0m"
firewall-cmd --list-all
echo -e "\033[31m"=== Docker Status ==="\033[0m"
systemctl status docker | grep active
echo -e "\033[31m"=== Jenkins Status ==="\033[0m"
systemctl status jenkins | grep active
Jenkinsfile์ ์กฐ๊ฑด๋ค์ ํต๊ณผํ๊ณ , Build Stage์์ ์ฌ์ฉ๋ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ์์ต๋๋ค.
์์ฑํ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฝํ๋ฉด ์ด๋ ์ต๋๋ค.
- gradlew ํ์ผ์ ์คํ๊ถํ ์ถ๊ฐ
- gradlew ๋น๋
- ์ ์ ๋น๋ํ ์ปจํ ์ด๋ ์ค์ง & ์ญ์
- ๋น๋ํ๋ ์ด๋ฏธ์ง ์ญ์
- ๋์ปค Hub ๋ก๊ทธ์ธ
- ์๋ก์ด ๋น๋์ Dockerfile ๋น๋
- ๊ตฌ์ถํด๋์ Docker Container Registry์ ๋น๋ํ ์ด๋ฏธ์ง Push
- Pushํ ์ด๋ฏธ์ง ์ญ์
- Pushํ๋ ์ด๋ฏธ์ง Pull
- ๊ฐ์ ธ์จ ์ด๋ฏธ์ง๋ฅผ ์ด์ฉํ์ฌ ์ปจํ ์ด๋ ์คํ + ๋ณผ๋ฅจ ๋ง์ดํธ + ํฌํธํฌ์๋ฉ
- ์ค๋ณต๋ ์ด๋ฏธ์ง ์ญ์
#!/bin/bash
# Gradlew ๊ถํ ๋ถ์ฌ
chmod 500 ./gradlew
# ๋น๋
./gradlew build --exclude-task test
# ๊ฐ๋์ค์ธ Spring Boot ์ปจํ
์ด๋ ์ค ์ด๋ฆ์ด cosmic ์ธ ์ปจํ
์ด๋ ์ค์ง & ์ญ์
if docker ps -a --filter "name=cosmic" | grep -q cosmic; then
docker stop cosmic
docker rm cosmic
fi
# ๊ธฐ์กด Spring Boot Image ์ค ์ด๋ฏธ์ง๊ฐ ๊ธฐ์กด๊ณผ ๋๊ฐ์๊ฒ ์์ผ๋ฉด ์ด๋ฏธ์ง ์ญ์
if docker images | awk '{print $1":"$2}' | grep -q "localhost:5000/cosmic:1.0"; then
docker rmi -f localhost:5000/cosmic:1.0
fi
# Docker Hub Login & ํ๋ผ๋ฏธํฐ๋ ์ ํจ์ค์์ ์ค์ ํ ์ ์ญ๋ณ์ ์ฌ์ฉ
echo $PASSWORD | docker login -u $USERNAME --password-stdin
# ๋์ปคํ์ผ ๋น๋
docker build --no-cache -t localhost:5000/cosmic:1.0 -f ./spacepet-deploy/test/cosmic .
# Container Registry์ ์ด๋ฏธ์ง Push
docker push localhost:5000/cosmic:1.0
# Pushํ ์ด๋ฏธ์ง ์ญ์
docker rmi localhost:5000/cosmic:1.0
# Container Registry์์ ์ด๋ฏธ์ง Pull
docker pull localhost:5000/cosmic:1.0
# Docker Container ์์ฑ
docker run -d -v /root/docker_volumn/cosmic:/app --privileged --name cosmic -p 8080:8080 localhost:5000/cosmic:1.0
# ์ฌ์ฉํ์ง ์๋ ๋ถํ์ํ ์ด๋ฏธ์ง ์ญ์ = ๊ฒน์น๋ ์ด๋ฏธ์ง๊ฐ ์กด์ฌํ๋ฉด ์ด๋ฏธ์ง๋ฅผ ์ญ์ ํ๋ค
dangling_images=$(docker images -f "dangling=true" -q)
if [[ -n "$dangling_images" ]]; then
docker rmi -f $dangling_images || true
fi
dangling Image
๋?
๋์ผํ ํ๊ทธ๋ฅผ ๊ฐ์ง Docker Image๊ฐ ๋น๋๋ ๊ฒฝ์ฐ, ๊ธฐ์กด์ ์๋ ์ด๋ฏธ์ง๋ ์ญ์ ๋์ง๋ ์๊ณ ,
tag๊ฐ none์ผ๋ก ๋ณ๊ฒฝ๋ ์ํ๋ก ๋จ๊ฒ ๋ฉ๋๋ค.
์ฆ, ์ฌ ๋น๋์ ์ด์ ์ด๋ฏธ์ง๋ฅผ ์ญ์ ํ๊ณ ์๋ก์ด ์ด๋ฏธ์ง๋ก ๋์ฒดํ๊ฒ ๋ค๋ ๋ป์ ๋๋ค.
๋์ปคํ์ผ์ ํญ์ ๋์ปคํ์ผ์ด ์๋ ์์น๊ฐ ๊ธฐ์ค ๊ฒฝ๋ก์ ๋๋ค.
spacepet-deploy/test/cosmic ์ด๋๊น
COPY ๊ฒฝ๋ก์ ../../ ๋ฅผ ํด์ค์ผํฉ๋๋ค.
์ต๋ ํ์ฌ์ด์ฆ๋ฅผ 6GB๋ก ์ ํํ๋ฉด์ ๋น๋ํฉ๋๋ค.
Workdir์ /app ์ธ๋ฐ Jarํ์ผ์ ์ปจํ ์ด๋ ์ต์๋จ์ ๋๋ ์ด์ ๋
script.sh ์คํฌ๋ฆฝํธ์์ ์ปจํ ์ด๋ ์คํ ์, ๋ณผ๋ฅจ ๋ง์ดํธ๋ฅผ /app์ ํ๋๋ฐ,
๊ทธ๋ /app ํ์์ ์๋ jar๊ฐ ์ฌ๋ผ์ง๋ฏ๋ก Jarํ์ผ์ ์ต์๋จ ๋๋ ํฐ๋ฆฌ๋ก ๋ณต์ฌํ์ต๋๋ค.
FROM openjdk:11
RUN mkdir -p /app
WORKDIR /app
VOLUME /app
EXPOSE 8080 ARG JAR=dangnyang-1.7.08-SNAPSHOT.jar
COPY ../../build/libs/${JAR} /koboot.jar
RUN chmod +x /koboot.jar
ENTRYPOINT ["java","-jar","-Dspring.profiles.active=test","-Xmx6144M","/koboot.jar"]
Jenkinsfile์์ Git repo์ ์ฒดํฌ์์์ ํ๊ณ
์ํ๋ ๋ธ๋์น, ํ๊ทธ ๋ฑ ํํฐ๋ง ์ต์ ๋ค์ ๋ฃ์ด ๋น๋ ์คํ ์ ์กฐ๊ฑด์ ๊ฑธ์ด๋ก๋๋ค.
Stage: Clean Workspace ํญ๋ชฉ ๋น๋๊ฐ ์คํ๋๊ธฐ ์ Workspace๋ฅผ ๋น์๋๋ค.
Stage: Checkout ํญ๋ชฉ ๋ฑ๋กํ Jenkins Credential์ ์ด์ฉํด Git Repo์ Checkout์ ํฉ๋๋ค.
Stage: Build ํญ๋ชฉ ์กฐ๊ฑด์ด ์ผ์นํ๋ฉด ๊ทธ๋ ์์ ์์ฑํ script.sh๋ฅผ ์คํ์ํค๋ฉฐ script.sh ์คํฌ๋ฆฝํธ ๋ด๋ถ์์ Dockerfile์ ๋น๋ํฉ๋๋ค.
pipeline {
agent any
stages {
stage('Clean Workspace') {
steps {
deleteDir()
}
}
stage('Checkout') {
steps {
script {
checkout([$class: 'GitSCM',
branches: [[name: '<branch-name>']],
userRemoteConfigs: [[
url: 'git@github.com:<user-name>/<repo-name>.git',
branch: 'SPACEPET-TEST',
credentialsId: '<jenkins-credentials-id>']]])
}
}
}
stage('Build') {
steps {
script {
def gitTags = sh(returnStdout: true, script: 'git tag --contains HEAD')
if (gitTags.contains('cicd')) {
sh 'chmod 500 spacepet-deploy/test/script.sh'
sh './spacepet-deploy/test/script.sh'
} else {
echo 'No tag containing "cicd" found.'
}
}
}
}
}
}
Github Project ์ ํํ๊ณ Project URL์ ์จ์ค๋๋ค. (.git ํฌํจ)
git@github.com:<user-name>/<repo-name>.git
Github hook trigger for GITScm polling์ ์ ํํด์ค๋๋ค.
Definition ํญ๋ชฉ
Pipeline script from SCM
์ผ๋ก ์ ํํฉ๋๋ค.
SCM ํญ๋ชฉ
Git
์ผ๋ก ์ ํํฉ๋๋ค.
Repository URL ํญ๋ชฉ
git@github.com:<user-name>/<repo-name>.git
์ ํ์์ผ๋ก ์์ฑํด์ค๋๋ค.
Credentials ํญ๋ชฉ
Jenkins Credential์ ๋ฑ๋กํ Jenkins Server์ Jenkins User SSH๋ฅผ ๋ฑ๋กํ๋ Credentials๋ฅผ ์ ํํฉ๋๋ค.
์ด ํ ๋ฐ์ ๊ณ ๊ธ ๋ฒํผ์ ๋๋ฌ์ค๋๋ค.
RefSpec ํญ๋ชฉ
Name์ ์๋ฌด๋ ๊ฒ๋ ์ ๊ณ Refspec์ ์ํ๋ ๋ธ๋์น๋ฅผ ์ ๋ ฅํฉ๋๋ค.
+refs/heads/SPACEPET-TEST:refs/remotes/origin/SPACEPET-TEST
Branch Specifier ํญ๋ชฉ
*/SPACEPET-TEST
<- ์ ๋ SPACEPET-TEST ๋ผ๋ ๋ธ๋์น๋ฅผ ๋น๋ ๋์์ผ๋ก ์ ํ์ต๋๋ค.
Script Path ํญ๋ชฉ
๋น๋ ๋์์ Jenkinsfile์ด ์์นํ ๊ฒฝ๋ก๋ฅผ ์ ์ด์ค๋๋ค. (Pipeline Script ์์น)
spacepet-deploy/test/Jenkinsfile
- Spring Boot์์ ์ค์ ํ ๋ธ๋์น์์ ํ๊ทธ๋ฅผ ์ด์ฉํ Push
- Github Webhook Trigger ๋ฐ๋ -> Jenkins๋ก Webhook ์ ์ก
- Jenkins๊ฐ Webhook์ ๋ฐ๊ณ Jenkinsfile์ ์คํ
- Jenkinsfile์ ๊ฑธ๋ฆฐ ์กฐ๊ฑด ํํฐ๋ง (๋ธ๋์น, ํ๊ทธ)
- ๋ธ๋์น์ ํ๊ทธ๊ฐ ๋ง์ผ๋ฉด Jenkinsfile์ Build Stage์ script.sh ์คํ ์คํฌ๋ฆฝํธ ๋ฐ๋
- script.sh ์คํฌ๋ฆฝํธ ์คํ
100๋ฒ์ ์ฝ์ง๋์ ๊ฒจ์ฐ ์ฑ๊ณต.. Jenkins ๋๋ฌด ๋ถ์น์ ํ๊ฒ ๊ฐ์ต๋๋ค ใ
Jenkins Server์ธ EC2์ docker ps
๋ฅผ ์
๋ ฅํด๋ณด๋ฉด Springboot ์ปจํ
์ด๋๊ฐ ์ ์์ ์ผ๋ก ๋์์ง๊ณ ๋ณผ๋ฅจ ๋ง์ดํธ๋ ์ ๋๊ฑธ ๋ณผ ์ ์์ต๋๋ค.
Dockerfile์ด๋ script.sh๋ฅผ ์์ฑ ์ํ๊ณ Pipeline์ ์ ๋ถ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
์ด ๋ฐฉ๋ฒ์ ์ฅ์ ์ Stage ๋ณ๋ก ๋จ๊ณ๋ฅผ ๋ถ๋ฅํจ์ผ๋ก์จ Jenkins Web์์ ์งํ ์ํฉ์ GUI๋ก ํธ๋ฆฌํ๊ฒ ๋์ผ๋ก ๋ณผ ์ ์์ต๋๋ค.
๊ทผ๋ฐ ์์ ๋ฐฉ๋ฒ์ด ๋ ์ต์ํด์ ์ด๊ฑด ๊ทธ๋ฅ ์ฐ์ต๋ง ํด๋๊ณ ์์ฐ๊ฒ ์ต๋๋ค.
pipeline {
agent any
stages {
stage('Stop & Delete Container') {
when {
allOf {
branch 'SPACEPET-TEST'
tag 'cicd*'
}
}
steps {
sh '''
if docker ps -a --filter "name=cosmic" | grep -q cosmic; then docker stop cosmic docker rm cosmic fi ''' }
}
stage('Remove Image') {
when {
allOf {
branch 'SPACEPET-TEST'
tag 'cicd*'
}
}
steps {
sh '''
if docker images | awk '{print $1":"$2}' | grep -q "localhost:5000/cosmic:1.0"; then docker rmi -f localhost:5000/cosmic:1.0 fi ''' }
}
stage('Login Docker Hub') {
when {
allOf {
branch 'SPACEPET-TEST'
tag 'cicd*'
}
}
steps {
sh 'echo $PASSWORD | docker login -u $USERNAME --password-stdin'
}
}
stage('Git Clone') {
when {
allOf {
branch 'SPACEPET-TEST'
tag 'cicd*'
}
}
steps {
git branch: 'SPACEPET-TEST', url:'https://github.com/CosmicDangNyang/CosmicDangNyang-Server'
}
}
stage('Build') {
when {
allOf {
branch 'SPACEPET-TEST'
tag 'cicd*'
}
}
steps {
sh 'chmod 500 ./gradlew'
sh './gradlew build --exclude-task test'
}
}
stage ('Build Dockerfile') {
when {
allOf {
branch 'SPACEPET-TEST'
tag 'cicd*'
}
}
steps {
sh 'docker build --no-cache -t localhost:5000/cosmic:1.0 -f ./spacepet-deploy/test/cosmic .'
}
}
stage ('Container Registry - Push & Delete & Pull') {
when {
allOf {
branch 'SPACEPET-TEST'
tag 'cicd*'
}
}
steps {
script {
sh 'docker push localhost:5000/cosmic:1.0'
sh 'docker rmi localhost:5000/cosmic:1.0'
sh 'docker pull localhost:5000/cosmic:1.0'
}
}
}
stage ('Run Container') {
when {
allOf {
branch 'SPACEPET-TEST'
tag 'cicd*'
}
}
steps {
sh 'docker run -d -v /root/docker_volumn/cosmic:/ --privileged --name cosmic -p 8080:8080 localhost:5000/cosmic:1.0'
}
}
stage ('Delete Conflict Image') {
when {
allOf {
branch 'SPACEPET-TEST'
tag 'cicd*'
}
}
steps {
sh '''
dangling_images=$(docker images -f "dangling=true" -q) if [[ -n "$dangling_images" ]]; then docker rmi -f $dangling_images || true fi ''' }
}
}
}
์ง๋๋ฒ์ ๊ตฌ์ถํ Jenkins + Github Webhook Trigger + AWS EC2, RDS ELB๋ฅผ ์ฐ๋ํด์ ๊ฐ๋จํ CICD ๋ฐฐํฌ ์ฑ๊ณต์ ํ๊ณ ,
์ฌ๋ด ์๊ตฌ์ฌํญ ๋ณ๊ฒฝ์ผ๋ก ๋ฌด์ค๋จ ๋ฐฐํฌ(zero-downtime)๋ฅผ ๊ตฌ์ถํ ๊ธฐ๋ก์ ์์ฑํฉ๋๋ค.
- ์๋ก์ด ๋ฒ์ ์ด Git์ ๋ณํฉ๋๋ฉด,ย Github Webhook์ ํตํด Jenkins์ ์ ํธ๊ฐ ๋ค์ด์ค๊ณ , ์ ํจ์ค๋ย ์ต์ ๋ฒ์ ์ Jar ํ์ผ์ ๋น๋ํฉ๋๋ค.
- ์ ํจ์ค๋ย Blue์ Health check๋ฅผ ํฉ๋๋ค. Blue๊ฐ ์ด์์๋ค๋ฉด ์ ๋ฒ์ ์ Green์ ๋ฐฐํฌํ๋ฉด ๋๊ณ , ์ด์์์ง ์๋ค๋ฉด Blue์ ๋ฐฐํฌํ๋ฉด ๋ฉ๋๋ค.
- ๊ทธ๋ฆผ์ Blue๊ฐ ์ด์์๋ ๊ฒ์ผ๋ก ํ๋จ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ ํจ์ค๋ Green์ ๋ฐฐํฌ๋ฅผ ํ๊ฒ ์ต๋๋ค.
- ์ ํจ์ค๋ย Green์ ๋งจ ์ฒ์ ๋น๋ํด๋ Jar ํ์ผ์ ์ ์กํ๊ณ , ์๊ฒฉ์ง์์ย ์คํํฉ๋๋ค.
- Green์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๊ตฌ๋๋์๋์งย 10์ด ์ฃผ๊ธฐ๋ก Health Check๋ฅผ ํฉ๋๋ค. Green ์ ํ๋ฆฌ์ผ์ด์ ์ด ๊ธฐ๋๋จ์ ํ์ธํ๋ฉด (6)์ผ๋ก ๋์ด๊ฐ๋๋ค.
- Nginx์ ๋ฆฌ๋ฒ์ค ํ๋ก์ ๋ฐฉํฅ์ Blue์์ Green์ผ๋ก ๋ณ๊ฒฝํฉ๋๋ค. ์ด์ ํด๋ผ์ด์ธํธ์ ๋ชจ๋ ํธ๋ํฝ์ด ์ ๋ฒ์ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ํฅํฉ๋๋ค.
- Blue ์ธ์คํด์ค์ ์ ํ๋ฆฌ์ผ์ด์ ํ๋ก์ธ์ค๋ฅผ ์ฃฝ์ ๋๋ค.
๊ฐ์ฉ์ฑ์ ์ํด Nginx์ ํฌํธ ๋ฆฌ๋ค์ด๋ ํธ๋ฅผ 8080 ์คํจ ์ 8081๋ก ํฌํธ 2๊ฐ๋ฅผ ์ค์ ํ์์ต๋๋ค.
Nginx Logging์ ์ํด ๋ก๊ทธ ์ค์ ๋ ํด์ฃผ์์ต๋๋ค.
nginx.conf
upstream docker-spring {
server localhost:8080 weight=10 max_fails=3 fail_timeout=10s;
server localhost:8081 weight=5 max_fails=3 fail_timeout=10s;
}
server {
listen 80;
server_name localhost;
include /etc/nginx/conf.d/service-url.inc;
location / {
proxy_pass $service_url;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
client_max_body_size 100M;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}
์ ์ค์ ์ ๋ณด๋ฉดย include
ย ๋ผ๋ ์ง์์ด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
์ด๋ ์ธ๋ถ์์ ์ค์ ํ์ผ์ ๋ถ๋ฌ์ฌ ์ ์๋ Nginx ์ ๊ธฐ๋ฅ์ ๋๋ค.
location
์ย proxy_pass
ย ๋ฅผ ๋ณด๋ฉด,ย $service_url
ย ๋ก ๋ฆฌ๋ฒ์ค ํ๋ก์ ์์ฒญ์ ๋ณด๋ด๋ ๊ฒ์ ์ ์ ์๋๋ฐ,
service-url.inc
ย ์์ ์ดย $service_url
ย ๋ณ์ ๊ฐ์ ์ฑ์์ค๋๋ค.
service-url.inc
set $service_url http://XXX.XXX.XXX.XXX:8080;
location
- proxy_pass : ์ฌ์ฉ์ค์ธ ํ๋ก์ URL์ ์ ๋ ฅ ํด์ค๋๋ค.
- proxy_redirect : ํ๋ก์ ๋ฆฌ๋ค์ด๋ ํธ๋ ์ฌ์ฉ์ ํ์ง ์๊ฒ ์ต๋๋ค.
- proxy_http_version : HTTP 1.1์ ์ฌ์ฉํฉ๋๋ค.
ํ๊ฒฝ๋ณ์ ์ค์
- nginx_ip, blue_ip, green_ip 3๊ฐ์ ๋ณ์๋ฅผ ๋ง๋ค์ด ๊ฐ๊ฐ ์ธ์คํด์ค IP ์ฃผ์๋ฅผ ๊ฐ์ผ๋ก ๋ฃ์ด์ค๋๋ค.
๋์ปค ๋คํธ์ํฌ ์์ฑ
์ ๊ฒฝ์ฐ์๋ ๊ฐ๊ฐ์ ์ธ์คํด์ค๊ฐ ์๋ ์ปจํ ์ด๋ ๋ผ๋ฆฌ Blue, Green ๋ฐฐํฌ๋ฅผ ํ ๊ฒ์ด๊ณ Jenkins์ ํ๊ฒฝ๋ณ์์ IP๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
๊ทธ๋์ ๋์ปค ๋คํธ์ํฌ๋ฅผ ๋ง๋ค์ด ์ค์ผ๋ก์จ, Container์ IP๋ฅผ ๊ณ ์ ์์ผ์ ๋ณ๊ฒฝ๋์ง ์๊ฒ ํฉ๋๋ค.
๊ธฐ๋ณธ bridge0์ ํ ๋น๋์ด ์์ผ๋ฉด ์ปจํ ์ด๋๋ ์ฌ์์ ํ ๋๋ง๋ค IP๊ฐ ๋ฐ๋๊ฒ ๋ฉ๋๋ค.
์ฆ, ์๋ก์ด Docker Bridge๋ฅผ ๋ง๋ค๊ณ ์ปจํ ์ด๋๋ค์ ๊ธฐ๋ณธ bridge0์ด ์๋ Custom Bridge์ ํ ๋น์ํต๋๋ค.
docker network create --gateway 172.20.0.1 --subnet 172.20.0.0/16 deploy
์ด ํ, docker run์ ํ ๋ --network deploy ์ต์ ๊ณผ --ip 172.20.0.X ๋ก ์์ดํผ๋ฅผ ํ ๋นํ๋ฉด ๋ฉ๋๋ค.
์คํฌ๋ฆฝํธ ์์
#!/bin/bash
# Blue & Green ํ๊ฒ ์ง์ ๋ณ์
target=2
deployment_target_ip=""
# Gradlew ๊ถํ ๋ถ์ฌ
chmod 500 ./gradlew
# ๋น๋
#./gradlew clean build --exclude-task test
# ํ
์คํธ์ฉ ๋น ๋ฅธ ๋น๋
./gradlew bootJar
# Blue Health Check
if curl -s "http://$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' blue):8080" > /dev/null
then
deployment_target_ip=$green_ip
target=0
else
deployment_target_ip=$blue_ip
target=1
fi
# Target๊ณผ ์ผ์นํ๋ ๊ฐ๋์ค์ธ Spring Boot ์ปจํ
์ด๋ ์ค์ง & ์ญ์
if [ "$target" -eq 1 ]; then
if docker ps -a --filter "name=green" | grep -q green; then
docker stop green
docker rm green
fuser -s -k 8080/tcp
fi
# ๊ธฐ์กด Spring Boot Image ์ค ์ด๋ฏธ์ง๊ฐ ๊ธฐ์กด๊ณผ ๋๊ฐ์๊ฒ ์์ผ๋ฉด ์ด๋ฏธ์ง ์ญ์
if docker images | awk '{print $1":"$2}' | grep -q "localhost:5000/green:1.0"; then
docker rmi -f localhost:5000/green:1.0
fi
elif [ "$target" -eq 2 ]; then
if docker ps -a --filter "name=blue" | grep -q blue; then
docker stop blue
docker rm blue
fuser -s -k 8080/tcp
fi
# ๊ธฐ์กด Spring Boot Image ์ค ์ด๋ฏธ์ง๊ฐ ๊ธฐ์กด๊ณผ ๋๊ฐ์๊ฒ ์์ผ๋ฉด ์ด๋ฏธ์ง ์ญ์
if docker images | awk '{print $1":"$2}' | grep -q "localhost:5000/blue:1.0"; then
docker rmi -f localhost:5000/blue:1.0
fi
else
echo "Invalid target Value"
fi
# Docker Hub Login & ํ๋ผ๋ฏธํฐ๋ ์ ํจ์ค์์ ์ค์ ํ ์ ์ญ๋ณ์ ์ฌ์ฉ
echo $PASSWORD | docker login -u $USERNAME --password-stdin
# ๋์ปคํ์ผ ๋น๋
if [ "${deployment_target_ip}" == "${blue_ip}" ]; then
docker build --no-cache -t localhost:5000/blue:1.0 -f ./spacepet-deploy/test/blue .
# Container Registry์ ์ด๋ฏธ์ง Push
docker push localhost:5000/blue:1.0
# Pushํ ์ด๋ฏธ์ง ์ญ์
docker rmi localhost:5000/blue:1.0
# Container Registry์์ ์ด๋ฏธ์ง Pull
docker pull localhost:5000/blue:1.0
# Docker Container ์์ฑ
docker run -d -v /root/docker_volumn/blue:/app --network deploy --ip 172.20.0.2 --privileged --name blue -p 8080:8080 localhost:5000/blue:1.0
elif [ "${deployment_target_ip}" == "${green_ip}" ]; then
docker build --no-cache -t localhost:5000/green:1.0 -f ./spacepet-deploy/test/green .
# Container Registry์ ์ด๋ฏธ์ง Push
docker push localhost:5000/green:1.0
# Pushํ ์ด๋ฏธ์ง ์ญ์
docker rmi localhost:5000/green:1.0
# Container Registry์์ ์ด๋ฏธ์ง Pull
docker pull localhost:5000/green:1.0
# Docker Container ์์ฑ
docker run -d -v /root/docker_volumn/green:/app --network deploy --ip 172.20.0.3 --privileged --name green -p 8080:8080 localhost:5000/green:1.0
else
echo "Invalid target Value"
fi
# ์ฌ์ฉํ์ง ์๋ ๋ถํ์ํ ์ด๋ฏธ์ง ์ญ์ = ๊ฒน์น๋ ์ด๋ฏธ์ง๊ฐ ์กด์ฌํ๋ฉด ์ด๋ฏธ์ง๋ฅผ ์ญ์ ํ๋ค
dangling_images=$(docker images -f "dangling=true" -q)
if [[ -n "$dangling_images" ]]; then
docker rmi -f $dangling_images || true
fi
# Nginx Reverse Proxy ๋ฐฉํฅ (ํ๊ฒ ์ปจํ
์ด๋) ๋ณ๊ฒฝ
ssh root@${nginx_ip} "echo 'set \\\$service_url http://${deployment_target_ip}:8080;' > /etc/nginx/conf.d/service-url.inc && service nginx reload"
echo "Switch the reverse proxy direction of nginx to ${deployment_target_ip} ๐"
pipeline {
agent any
stages {
stage('Clean Workspace') {
steps {
deleteDir()
}
}
stage('Checkout') {
steps {
script {
checkout([$class: 'GitSCM',
branches: [[name: '<branch-name>']],
userRemoteConfigs: [[
url: 'git@github.com:<user-name>/<repo-name>.git',
branch: 'SPACEPET-TEST',
credentialsId: '<jenkins-credentials-id>']]])
}
}
}
stage('Build') {
steps {
script {
def gitTags = sh(returnStdout: true, script: 'git tag --contains HEAD')
if (gitTags.contains('cicd')) {
sh 'chmod 500 spacepet-deploy/test/script.sh'
sh './spacepet-deploy/test/script.sh'
} else {
echo 'No tag containing "cicd" found.'
}
}
}
}
}
}
FROM openjdk:11
RUN mkdir -p /app
WORKDIR /app
VOLUME /app
EXPOSE 8080
ARG JAR=dangnyang-1.7.08-SNAPSHOT.jar
COPY ../../build/libs/${JAR} /koboot.jar
RUN chmod +x /koboot.jar
ENTRYPOINT ["java","-jar","-Dspring.profiles.active=test","-Xmx6144M","/koboot.jar"]