- jenkins,Docker,nginx 를 이용한 Blue/Green 방식 무중단 배포
# deploy.sh
# 0
sh /home/ubuntu/deploy/copyLogs.sh
EXIST_BLUE=$(docker ps --filter name=${IMAGE_NAME}-blue --filter status=running -q)
# 1
if [ -z "$EXIST_BLUE" ]; then
echo "Blue Up!"
docker-compose -p ${IMAGE_NAME}-blue --env-file ./spring.env -f /home/ubuntu/deploy/docker-compose.blue.yaml up -d
BEFORE_COMPOSE_COLOR="green"
AFTER_COMPOSE_COLOR="blue"
BEFORE_PORT_NUMBER=8086
AFTER_PORT_NUMBER=8085
else
echo "Green Up!"
docker-compose -p ${IMAGE_NAME}-green --env-file ./spring.env -f /home/ubuntu/deploy/docker-compose.green.yaml up -d
BEFORE_COMPOSE_COLOR="blue"
AFTER_COMPOSE_COLOR="green"
BEFORE_PORT_NUMBER=8085
AFTER_PORT_NUMBER=8086
fi
echo "${AFTER_COMPOSE_COLOR} server up(port:${AFTER_PORT_NUMBER})"
# 2
for cnt in $(seq 1 10)
do
echo "서버 응답 확인중..(${cnt}/10)";
UP=$(curl -s http://localhost:${AFTER_PORT_NUMBER}/actuator/health | grep 'UP')
if [ -z "${UP}" ]
then
sleep 10
continue
else
break
fi
done
if [ $cnt -eq 10 ]
then
echo "서버가 정상적으로 구동되지 않았습니다."
exit 1
fi
CONTAINER=$(docker ps -q -f "name=nginx")
# 3
sudo sed -i "s/${BEFORE_PORT_NUMBER}/${AFTER_PORT_NUMBER}/" /home/ubuntu/deploy/service-url.inc
docker cp /home/ubuntu/deploy/service-url.inc $CONTAINER:/etc/nginx/conf.d/
echo "Copy url"
docker exec $CONTAINER service nginx reload
echo "reload"
echo "Deploy Completed!!"
# 4
docker stop ${IMAGE_NAME}-${BEFORE_COMPOSE_COLOR}
docker rm -f ${IMAGE_NAME}-${BEFORE_COMPOSE_COLOR}
# docker-compose -p ${IMAGE_NAME}-${BEFORE_COMPOSE_COLOR} --env-file ./spring.env -f /home/ubuntu/deploy/docker-compose.${BEFORE_COMPOSE_COLOR}.yaml down
echo "$BEFORE_COMPOSE_COLOR server down(port:${BEFORE_PORT_NUMBER})"
yes | docker image prune
curl -X GET -s "https://dev.hprobot.ai/kakao/server-state?state=true&dashboardName=seungyeon"
# jenkins pipeline
pipeline {
agent any
tools{
gradle 'gradle-7.6.1'
jdk 'java-17'
}
environment{
repositiory = "seungyeonnnnnni/hprobot:latest"
DOCKERHUB_CREDENTIALS = credentials('jeongsy-dockerhub')
dockerImage=''
}
stages {
stage('Github') {
steps {
git branch: 'main', credentialsId: 'jeongsy-github', url: 'https://github.com/seungyeonn-i/helper_monitoring_system.git'
}
}
stage('Clean') {
steps {
sh 'rm -rf build/libs/*.jar' // 기존 JAR 파일 삭제
}
}
stage('Build') {
steps {
sh 'chmod +x ./gradlew'
sh "./gradlew bootJar"
sh 'docker build -t $repositiory .'
}
}
stage('Login') {
steps {
sh 'echo $DOCKERHUB_CREDENTIALS_PSW | docker login -u $DOCKERHUB_CREDENTIALS_USR --password-stdin'
}
}
stage('Deploy') {
steps {
script {
sh 'docker push $repositiory'
}
dir('build/libs') {
sshagent(credentials: ['jeongsy-ssh']) {
sh 'ssh -o StrictHostKeyChecking=no ubuntu@43.201.147.248 "sudo docker pull $repositiory"'
sh 'ssh -o StrictHostKeyChecking=no ubuntu@43.201.147.248 "sudo IMAGE_NAME=hprobot IMAGE_STORAGE=seungyeonnnnnni BUILD_NUMBER=latest sh deploy/deploy.sh"'
}
}
}
}
}
}
EC2 내에 아래와 같은 파일이 존재해야한다.
.
└── ubuntu
└── deploy
├── application-activate.yml
├── application.yml.
├── client_secret.json
├── copyLogs.sh // 로그 복사 스크립트
├── deploy.sh // 실행 파일
├── docker-compose.blue.yaml
├── docker-compose.green.yaml
├── logback-spring.xml // 로그 저장 설정
├── profile-application.yml
├── **service-url.inc // nginx에서 라우팅 할 url**
└── spring.env // docker-compose 에서 사용할 환경변수 저장
Docker Compose 파일에서
volumes
섹션은 컨테이너와 호스트 시스템 간에 파일 시스템을 공유할 때 사용됩니다. 이를 통해 데이터의 지속성과 데이터 공유가 가능하며, 설정 파일이나 코드 등을 컨테이너에 쉽게 제공할 수 있습니다.
# docker-compose.blue.yaml 일부
services:
volumes:
- ./application.yml:/app/config/application.yml
- ./profile-application.yml:/app/config/profile-application.yml
- ./logback-spring.xml:/app/config/logback-spring.xml
- ./client_secret.json:/app/config/client_secret.json
pipeline {
agent any
tools{
gradle 'gradle-7.6.1'
jdk 'java-17'
}
environment{
repositiory = "seungyeonnnnnni/hprobot:latest"
DOCKERHUB_CREDENTIALS = credentials('dockerhub')
dockerImage=''
}
stages {
stage('Github') {
steps {
git branch: 'dev', url: 'https://github.com/@@@/@@@.git', credentialsId: '@@@-jenkins-token'
}
}
stage('Clean') {
steps {
sh 'rm -rf build/libs/*.jar' // 기존 JAR 파일 삭제
}
}
stage('Build') {
steps {
sh 'chmod +x ./gradlew'
sh "./gradlew bootJar"
sh 'docker build -t $repositiory .'
}
}
stage('Login') {
steps {
sh 'echo $DOCKERHUB_CREDENTIALS_PSW | docker login -u $DOCKERHUB_CREDENTIALS_USR --password-stdin'
}
}
stage('Deploy') {
steps {
script {
sh 'docker push $repositiory'
}
dir('build/libs') {
sshagent(credentials: ['dev-spring-boot-ssh-key']) {
sh 'ssh -o StrictHostKeyChecking=no ubuntu@!!!.!!.!!.!!! "sudo docker pull $repositiory"'
sh 'ssh -o StrictHostKeyChecking=no ubuntu@!!!.!!.!!.!!! "IMAGE_NAME=spring-boot IMAGE_STORAGE=hprobot BUILD_NUMBER=api sudo sh /home/ubuntu/deploy/deploy.sh"'
}
}
}
}
}
post {
failure {
script {
def buildNumber = env.BUILD_NUMBER
sh "curl -X GET -s \"https://dev.hprobot.ai/kakao/server-state?state=false&buildNumber=${buildNumber}&dashboardName=dev\""
}
}
}
}
```bash
/etc/nginx
|-- conf.d
| |-- **application.conf**
| |-- default.conf
| `-- **service-url.inc**
|-- fastcgi_params
|-- mime.types
|-- modules -> /usr/lib/nginx/modules
|-- **nginx.conf**
|-- scgi_params
`-- uwsgi_params
```
cat application.conf
server {
listen 80;
include /etc/nginx/conf.d/service-url.inc;
location / {
proxy_pass $service_url;
}
}
cat service-url.inc
set $service_url http://{ip address}}:8086; // 현재 8086 포트인 blue 가 띄워져있어서 그럼
/etc/nginx/conf.d 의 모든 conf 파일을 include 하고 있음
cat nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
**include /etc/nginx/conf.d/*.conf;**
}
spring / redis / nginx 모두 컨테이너 간 통신이 존재한다. 따라서 같은 network안에 위치해야한다. 특히 redis 는 dev(test) 에서는 local 로 돌아가고 있고 main 에서는 도커로 띄워서 profile 에 따라 다르게(?) 연결중이다.
docker network
도커 네트워크는 컨네이너-컨테이너, 컨테이너-호스트 간의 통신을 위해 필요하다.
docker network create service-network
docker network connect {network name} {container name}
# docker-compose-@@.yaml 일부
networks:
default:
external:
name: service-network
host가 저렇게 설정 되어있다. 같은 네트워크 내에 있는 hprobot-redis 라는 이름을 가진 것을 사용
# application.yml 일부
config:
import: profile-application.yml
# profile-application.yml 일부
---
spring:
redis:
host: **hprobot-redis**
port: 6379
config:
activate:
on-profile: blue
server:
port: 8085
logging:
config: /app/config/logback-spring.xml
---
spring:
redis:
host: **hprobot-redis**
port: 6379
config:
activate:
on-profile: green
server:
port: 8086
logging:
config: /app/config/logback-spring.xml
---
spring:
redis:
host: localhost
port: 6379
config:
activate:
on-profile: dev
server:
port: 8080
https://devbksheen.tistory.com/entry/Jenkins-Docker-Nginx-무중단-배포하기
docker pull seungyeonnnnnni/hprobot
Using default tag: latest
latest: Pulling from seungyeonnnnnni/hprobot
38a980f2cc8a: Extracting [=================================> ] 28.11MB/42.11MB
de849f1cfbe6: Download complete
a7203ca35e75: Downloading [==================================================>] 187.5MB/187.5MB
c9d3047a913b: Download complete
write /var/lib/docker/tmp/GetImageBlob527575243: no space left on device
몇번 pull 하고 나면 docker image 용량이 꽉차서 pull 이 안됐다. 그래서 매번 사용하지 않는 이미지, 컨테이너 지울 수 있도록 명령어 추가
파이프라인 실행중, IMAGE_NAME,IMAGE_STORAGE 환경변수를 sh 실행전에 넣어주는데 자꾸 환경변수 인식이 안된다는 에러가 떴다.
IMAGE_NAME=hprobot IMAGE_STORAGE=seungyeonnnnnni BUILD_NUMBER=latest sh deploy/deploy.sh
그래서 터미널에서 export 로 선언해두었다.
- 서버 재부팅하면 환경변수 사라질 수 있다고 판단하여 영구적으로 설정하였다.
```bash
sudo vi /etc/profile
export IMAGE_NAME=hprobot
export IMAGE_STORAGE=seungyeonnnnnni
export BUILD_NUMBER=latest
source /etc/profile // 프로파일 파일 실행해 영구적으로 설정
```
deploy.sh 파일에서 기존에 존재하는 컨테이너를 확인하고 존재하지 않는 컨테이너를 띄운다.
근데 기존 docker-compose 로 존재 여부 판단하는 명령이 안먹혔고
EXIST_BLUE=$(docker-compose -p ${IMAGE_NAME}-blue -f ~/deploy/docker-compose.blue.yaml ps | grep Up)
if [ -z "$EXIST_BLUE" ]; then // EXIST_BLUE 가 null이거나 빈 문자열이라면,
echo "Blue Up!"
docker 명령어를 사용해 존재 여부 판단하는 것으로 바꿨다. 왜 안됐을까
EXIST_BLUE=$(docker ps --filter name=${IMAGE_NAME}-blue --filter status=running -q)
docker ps
명령은 Docker 엔진에 직접적으로 요청하여 모든 실행 중인 컨테이너를 확인하고, --filter name=${IMAGE_NAME}-blue
옵션을 통해 특정 이름을 가진 컨테이너를 필터링합니다. 이 접근법은 Docker Compose의 프로젝트 구조나 docker-compose.yaml
파일의 위치, 환경변수 설정에 의존하지 않으므로 더 간단하고 직접적인 정보를 제공합니다. 따라서 docker ps
를 사용한 방식이 더 일관된 결과를 제공할 가능성이 높습니다.docker ps
를 사용한 방식으로 변경한 것은 더 직접적이고 신뢰할 수 있는 접근 방법을 선택한 것입니다.
ERROR: invalid reference format
에러가 났다. → Docker 명령어가 잘못된 참조 형식을 갖고 있을 때 발생