무중단 배포를 하기 위해서는 몇가지 configuration 설정이 필요합니다.
1. docker-compose.green.yaml
2. docker-compose.blue.yaml
3. nginx-docker-compose.yaml
4. mysql-docker-compose.yaml
5. deploy.sh
docker-compose.green.yaml
version: "3.7"
services:
pullo:
image: kimminwoo1234/pullo-backend:${BUILD_NUMBER}
restart: always
ports:
- 8080:8080
environment:
SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
networks:
- pullo-network-group
volumes:
- ./log:/var/log
networks:
pullo-network-group: # 기존에 생성한 네트워크 그룹과 동일한 이름으로 지정
external: true
- BUILD_NUMBER를 환경변수 처리함으로 써 버전관리를 할 수 있도록하였습니다.
- external networks를 사용해야만 compose가 내려가도 네트워크는 사라지지않습니다.
docker-compose.blue.yaml
version: "3.7"
services:
pullo:
image: kimminwoo1234/pullo-backend:${BUILD_NUMBER}
restart: always
ports:
- 8081:8080
environment:
SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
networks:
- pullo-network-group
volumes:
- ./log:/var/log
networks:
pullo-network-group: # 기존에 생성한 네트워크 그룹과 동일한 이름으로 지정
external: true
- 호스트포트는 8081 게스트포트는 8080으로 돌아가게함으로 blue와 green컨테이너가 동시에 띄워질 수 있도록 설정하였습니다.
version: "3.7"
services:
nginx:
image: nginx:latest
restart: always
ports:
- 80:80
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
networks:
- pullo-network-group
networks:
pullo-network-group: # 기존에 생성한 네트워크 그룹과 동일한 이름으로 지정
external: true
- configuration파일을 불륨마운트를 시켜 제가 직접 custom한 configuration을 주입시켰습니다.
경로는 pullo/nginx/conf.d
upstream backend {
server green_pullo_1:8080;
}
server {
listen 80;
# HTTP Keepalive Timeout 설정 (기본 75초)
keepalive_requests 200;
# 클라이언트 요청 버퍼 크기 설정
client_body_buffer_size 10K;
client_header_buffer_size 1k;
client_max_body_size 8m;
large_client_header_buffers 2 1k;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
error_page 500 502 503 504 /50x.html;
}
location /api/ {
allow all;
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, PATCH, DELETE';
add_header 'Access-Control-Allow-Headers' 'x-requested-with, authorization, content-type, credential, X-AUTH-TOKEN, X-CSRF-TOKEN';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /swagger-ui/ {
allow all;
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, PATCH, DELETE';
add_header 'Access-Control-Allow-Headers' 'x-requested-with, authorization, content-type, credential, X-AUTH-TOKEN, X-CSRF-TOKEN';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
- 주위깊게 볼 부분은 upstream쪽입니다 저부분이 바뀌고 nginx -s reload를 통해 nginx의프록시를 변경시켜 라우팅시켜줄 것 입니다.
mysql-docker-compose.yml
version: "3.7"
services:
mysql:
image: mysql:5.7
container_name: pullo-mysql
environment:
MYSQL_DATABASE: pullo
MYSQL_USER: user
MYSQL_PASSWORD: user
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
MYSQL_CHARSET: utf8mb4
MYSQL_COLLATION: utf8mb4_general_ci
ports:
- 3306:3306
networks:
- pullo-network-group
volumes:
- ./db:/var/lib/mysql
- ./initdb:/docker-entrypoint-initdb.d # Mount a directory containing SQL initialization scripts
networks:
pullo-network-group: # 기존에 생성한 네트워크 그룹과 동일한 이름으로 지정
external: true
- spring boot가 띄워질라면 docker mysql이 띄워져야하기에 미리 띄워둡니다.
5.deploy.sh
#!/bin/bash
# Blue 를 기준으로 현재 떠있는 컨테이너를 체크한다.
EXIST_BLUE=$(docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yaml ps | grep Up)
# 컨테이너 스위칭
if [ -z "$EXIST_BLUE" ]; then
echo "blue up"
docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yaml up -d
BEFORE_COMPOSE_COLOR="green"
AFTER_COMPOSE_COLOR="blue"
else
echo "green up"
docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yaml up -d
BEFORE_COMPOSE_COLOR="blue"
AFTER_COMPOSE_COLOR="green"
fi
sleep 30
# 새로운 컨테이너가 제대로 떴는지 확인
EXIST_AFTER=$(docker-compose -p ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} -f docker-compose.${AFTER_COMPOSE_COLOR}.yaml ps | grep Up)
if [ -n "$EXIST_AFTER" ]; then
# Nginx 설정 변경 및 재로드
docker exec -it pullo_nginx_1 /bin/bash -c "sed -i 's/${BEFORE_COMPOSE_COLOR}/${AFTER_COMPOSE_COLOR}/g' /etc/nginx/conf.d/nginx.conf && nginx -s reload"
echo "Nginx configuration updated and reloaded"
# 이전 컨테이너 종료
docker-compose -p ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR} -f docker-compose.${BEFORE_COMPOSE_COLOR}.yaml down
echo "$BEFORE_COMPOSE_COLOR down"
fi
- blue를 기준으로 그린이 띄워져있는지 블루가 띄워져있는지 체크를 합니다.
- 새로운 컨테이너 제대로 떳으면 컨테이너에 bash shell로 접속하여 nginx.conf파일에서 upstream부분을 새로운 컨테이너 이미지로
변경시켜줍니다.
- nginx는 새로운 그린포트로 업데이트가 될 것이고, 기존에 있던 blue 컨테이너는 삭제시켜줍니다.
JenkinsFile구성
pipeline {
agent any
options {
timeout(time: 1, unit: 'HOURS') // set timeout 1 hour
}
environment {
TIME_ZONE = 'Asia/Seoul'
//github
TARGET_BRANCH = 'develop'
REPOSITORY_URL= 'https://github.com/minwoo1999/pullo'
//docker-hub
registryCredential = 'docker-hub'
CONTAINER_NAME = 'pullo-backend'
IMAGE_NAME = 'kimminwoo1234/pullo-backend'
}
stages {
stage('init') {
steps {
echo 'init stage'
deleteDir()
}
// post {
// success {
// echo 'success init in pipeline'
// }
// // failure {
// // slackSend (channel: '#backend', color: '#FF0000', message: "${env.CONTAINER_NAME} CI / CD 파이프라인 구동 실패, 젠킨스 확인 해주세요")
// // error 'fail init in pipeline'
// // }
// }
}
stage('Prepare') {
steps {
echo 'Cloning Repository'
git branch: 'develop',
url: 'https://github.com/minwoo1999/pullo.git',
credentialsId: 'gitihub-signin'
}
// post {
// success {
// echo 'Successfully Cloned Repository'
// }
// // failure {
// // slackSend (channel: '#backend', color: '#FF0000', message: "${env.CONTAINER_NAME} CI / CD 파이프라인 구동 실패, 젠킨스 확인 해주세요")
// // error 'This pipeline stops here...'
// // }
// }
}
// 일단은 merge 하기전에 테스트통과함으로 테스트없이 빌드
stage('Build Gradle') {
steps {
echo 'Build Gradle'
dir('.'){
sh '''
pwd
cd /var/jenkins_home/workspace/pullo-backend
chmod +x ./gradlew
./gradlew build
'''
}
}
// post {
// // failure {
// // slackSend (channel: '#backend', color: '#FF0000', message: "${env.CONTAINER_NAME} CI / CD 파이프라인 구동 실패, 젠킨스 확인 해주세요")
// // error 'This pipeline stops here...'
// // }
// }
}
// 도커 이미지를 만든다. build number로 latest 태그 부여한다.
stage('Build Docker') {
steps {
echo 'Build Docker'
sh """
cd /var/jenkins_home/workspace/pullo-backend
docker build -t $IMAGE_NAME:$BUILD_NUMBER .
docker tag $IMAGE_NAME:$BUILD_NUMBER $IMAGE_NAME:latest
"""
}
// post {
// // failure {
// // slackSend (channel: '#backend', color: '#FF0000', message: "${env.CONTAINER_NAME} CI / CD 파이프라인 구동 실패, 젠킨스 확인 해주세요")
// // error 'This pipeline stops here...'
// // }
// }
}
// 빌드넘버 latest
stage('Push Docker') {
steps {
echo 'Push Docker'
script {
docker.withRegistry('', registryCredential) {
docker.image("${IMAGE_NAME}:${BUILD_NUMBER}").push()
docker.image("${IMAGE_NAME}:latest").push()
}
}
}
// post {
// // failure {
// // slackSend (channel: '#backend', color: '#FF0000', message: "${env.CONTAINER_NAME} CI / CD 파이프라인 구동 실패, 젠킨스 확인 해주세요")
// // error 'This pipeline stops here...'
// // }
// }
}
stage('rm container and rm images') {
steps {
echo 'rm container stage'
sh '''
docker rm -f $CONTAINER_NAME
docker image prune -f --filter "label=${IMAGE_NAME}"
'''
}
// post {
// success {
// echo 'success rm container in pipeline'
// }
// // failure {
// // slackSend (channel: '#backend', color: '#FF0000', message: "${env.CONTAINER_NAME} CI / CD 파이프라인 구동 실패, 젠킨스 확인 해주세요")
// // error 'fail rm container in pipeline'
// // }
// }
}
stage('Docker run') {
steps {
echo 'Pull Docker Image & Docker Image Run'
script {
docker.withRegistry('', registryCredential) {
sshagent (credentials: ['ssh']) {
sh "ssh -o StrictHostKeyChecking=no ubuntu@43.200.143.218 'cd pullo && sed -i \"s/^BUILD_NUMBER=.*/BUILD_NUMBER=${BUILD_NUMBER}/\" .env'"
sh "ssh -o StrictHostKeyChecking=no ubuntu@43.200.143.218 'cd pullo && ./deploy.sh'"
}
}
}
}
post {
failure {
echo 'Docker Run failure !'
}
success {
echo 'Docker Run Success !'
}
}
}
stage('Clean Up Docker Images on Jenkins Server') {
steps {
echo 'Cleaning up unused Docker images on Jenkins server'
// Clean up unused Docker images, including those created within the last hour
// sh "docker image prune -f --all --filter \"until=1m\""
sh "docker image prune"
}
}
}
// post {
// success {
// slackSend (channel: '#backend', color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
// }
// failure {
// slackSend (channel: '#backend', color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
// }
// }
}
- 환경변수 부분에서 BUILD_NUMBER를 변경하고
- ./deploy.sh를 실행시켜 blue/green배포를 실시합니다.
- docker image prune 명령어를 통해 사용하지 않는 이미지들은 처리해줍니다.
nginx swagger config
upstream backend {
server blue_pullo_1:8080;
}
server {
listen 80;
# HTTP Keepalive Timeout 설정 (기본 75초)
keepalive_requests 200;
# 클라이언트 요청 버퍼 크기 설정
client_body_buffer_size 10K;
client_header_buffer_size 1k;
client_max_body_size 8m;
large_client_header_buffers 2 1k;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
error_page 500 502 503 504 /50x.html;
}
location /api/ {
allow all;
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, PATCH, DELETE';
add_header 'Access-Control-Allow-Headers' 'x-requested-with, authorization, content-type, credential, X-AUTH-TOKEN, X-CSRF-TOKEN';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /swagger-ui/ {
allow all;
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /v3/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
참고한 블로그:https://jay-ji.tistory.com/99
'nginx' 카테고리의 다른 글
react을 Nginx 로 배포해보자 (0) | 2023.05.22 |
---|