- 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
IMAGE_NAME
, IMAGE_STORAGE
, BUILD_NUMBER
와 같은 변수들을 명령 줄 앞에 배치하면, 이러한 변수들은 해당 명령이 실행되는 현재의 쉘 세션에 대해서만 환경 변수로 설정됩니다. 즉, 이 변수들은 deploy.sh
스크립트가 실행되는 동안에만 환경 변수로 존재하며, deploy.sh
스크립트 내에서 호출되는 다른 프로그램이나 스크립트로는 상속되지 않습니다. 환경 변수가 자식 프로세스에게 상속되게 하려면, 명령을 실행하기 전에 해당 환경 변수들을 쉘에 명시적으로 내보내야 합니다. export
명령을 사용하면 됩니다. 예를 들면:shCopy code
export IMAGE_NAME=hprobot
export IMAGE_STORAGE=seungyeonnnnnni
export BUILD_NUMBER=latest
sh deploy/deploy.sh
이렇게 하면 deploy.sh
스크립트 내에서도, 그리고 그 스크립트에서 호출하는 어떤 자식 프로세스에서도 IMAGE_NAME
, IMAGE_STORAGE
, BUILD_NUMBER
환경 변수를 사용할 수 있게 됩니다. 일시적으로만 변수를 설정하고 싶다면, 명령 줄에서 직접 변수를 설정하는 방식으로 실행하는 것도 가능하지만, 이 경우에는 해당 명령어 내에서만 변수들이 유효하며, 이 명령어로 실행되는 스크립트나 프로그램에만 환경 변수가 전달됩니다. 자식 프로세스에서 이러한 변수를 필요로 할 때는 export
를 사용해야 합니다. https://lh3.googleusercontent.com/a/AGNmyxaOICM05O7g-k2so1l6JcfTKXoexlQ_oEOfdszc=s96-c 즉, 이 변수들은 deploy.sh 스크립트가 실행되는 동안에만 환경 변수로 존재하며, deploy.sh 스크립트 내에서 호출되는 다른 프로그램이나 스크립트로는 상속되지 않습니다. 그럼IMAGE_NAME=hprobot IMAGE_STORAGE=seungyeonnnnnni BUILD_NUMBER=latest sh deploy/deploy.sh
와 같이 실행했을 때, deploy.sh
스크립트 내에서 echo $IMAGE_NAME
을 사용하면 환경 변수 IMAGE_NAME
의 값인 hprobot
이 출력됩니다. 같은 방식으로 IMAGE_STORAGE
와 BUILD_NUMBER
변수에 대해서도 deploy.sh
스크립트 내에서 접근할 수 있습니다. 즉, 이 변수들은 deploy.sh
스크립트가 실행되는 동안에만 환경 변수로 설정되며, 이 스크립트 내에서 사용할 수 있습니다. 하지만, deploy.sh
에서 다른 스크립트나 프로그램을 실행할 때, 그 자식 프로세스들에게는 이 환경 변수들이 자동으로 상속되지 않습니다. 이러한 상속을 원한다면, deploy.sh
스크립트 내에서 해당 환경 변수들을 다시 export 해주거나, 자식 프로세스를 호출할 때 명시적으로 환경 변수를 전달해야 합니다. 제가 이전에 한 말이 정확하지 않았습니다. 명령어를 사용하여 스크립트를 실행할 때 명령어 앞에 환경 변수를 설정하면, 그 스크립트 내에서는 해당 환경 변수들을 사용할 수 있습니다. 오해를 불러일으켜 죄송합니다.그래서 터미널에서 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 명령어가 잘못된 참조 형식을 갖고 있을 때 발생