[CI/CD] (8) Blue-Green 무중단 배포 적용하기
💡 CICD 과정은 LINK의 개요에 따라 작성되었습니다.
순서를 따르면 순조롭게 진행할 수 있습니다.
blue-green 방식으로 무중단 배포하기
기존 배포 방식은 작동하던 Docker Container를 내리고 새로운 이미지를 받아와 새로운 Docker Container를 띄우는 방식이었다. 문제 없이 동작하는 듯 하지만 사용자가 서비스를 이용하다가 배포가 일어나면 해당 서비스 전체가 다운된다.
따라서 blue-green
방식을 이용해 서비스의 중단이 일어나지 않게 해보자.
blue-green
이라는 이름만 들으면 굉장히 거창할 것 같지만 사실은 단순하다. 방식은 다음과 같다.
- 기존 컨테이너를 종료하지 않고 새로운 컨테이너를 띄운다.
- 새로운 컨테이너의 정상 작동이 확인되면 리버스 프록시에 새로운 컨테이너를 연결한다.
- 기존 컨테이너를 종료한다.
nginx의 reload
는 굉장히 빠른 시간 내에 일어나기 때문에 사용자가 인지하기 어렵다. 따라서 서비스의 중단 없는 배포가 가능해진다.
이제 실제로 무중단 배포가 이루어 지기 위한 설정을 해보자.
server에 nginx 관련 설정하기
blue-green
방식을 이용하기 위해 nginx를 Docker Container로 띄워 이용할 예정이다. 새로운 컨테이너를 띄우고 nginx의 설정파일을 reload
하는 방식으로 진행된다.
따라서 nginx Docker Container상에 있는 설정 디렉토리에 접근해야한다.
우리는 nginx의 설정파일이 존재하는 디렉토리를 호스트로 끌어와 쉽게 수정하고 싶다. Docker Container를 실행할 때, nginx의 설정 파일이 존재하는 /etc/nginx
디렉토리와 호스트의 임의의 디렉토리를 -v
옵션으로 실행하면 될 것 같지만, 실제로 실행해보면 호스트의 디렉토리는 비어있게 되고, 컨테이너의 /etc/nginx
도 호스트의 디렉토리를 따라 빈 디렉토리로 남기 때문에 nginx container는 /etc/nginx
디렉토리가 비었다는 로그와 함께 Exited (1)
로 종료되게 된다.
Docker의 volume mapping은 두 가지 방법이 존재한다. volume
과 bind mount
인데 위의 방법이 bind mount
이다. 공식 문서에 따르면 bind mount
방법을 사용할 시 컨테이너의 디렉토리에 존재하는 모든것이 가려지게 된다.
따라서 우리는 volume
을 사용하여야 한다. volume
을 사용하면 호스트 디렉토리를 덮어쓰는 것이 아니라 동기화 한다. 옵션을 추가해 ~/nginx-config
디렉토리를 device로 설정한다.
먼저 volume
을 만들고 -v
옵션에 절대경로 대신 volume
의 이름을 입력하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
$ cd ~
$ mkdir nginx-config
# volume 생성
$ docker volume create --driver local \
--opt type=none \
--opt device=~/nginx-config \
--opt o=bind \
nginx-config-volume
# volume 정보 확인
$ docker volume inspect nginx-config-volume
위의 명령어로 volume을 생성하고 정보를 확인하자.
위처럼 volume
이 잘 생성된 것을 확인할 수 있다.
1
$ docker run -d --name nginx -p 80:80 -v nginx-config-volume:/etc/nginx nginx:latest
위의 명령어로 docker container를 띄운다.
~/nginx-config
를 확인해보면 컨테이너 내부에 /etc/nginx
디렉터리가 동기화 되는 것을 확인할 수 있다.
마지막으로 우리가 관심 있는 파일은 conf.d/default.conf
이다. 이 파일을 교체해 Nginx의 설정을 변경할 예정이다. 해당 파일의 권한은 rw-r--r--
로 설정되어 있기 때문에 jenkins에서 교체를 시도하면 권한오류가 뜰 것이다. 이를 변경해주자.
1
$ sudo chmod 777 ~/nginx-config/conf.d/default.conf
위의 명령어를 입력하면 권한이 변경된다.
기존 컨테이너 종료
이제 blue-green 방식의 배포를 사용하기 위해 기존에 작동중이던 컨테이너의 작동을 중지시키자.
1
$ docker rm -f springboot
위의 명령어로 기존의 컨테이너를 종료시킨다.
nginx.conf 작성
이제 Nginx의 설정을 할 수 있는 nginx.conf
파일을 작성해 보자. 프로젝트 루트 디렉토리에 nginx.conf
파일을 만들고 다음과 같이 작성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
upstream api {
// server 172.17.0.1:${EXTERNAL_PORT};
}
access_log /var/log/nginx/access.log;
server {
listen 80;
location / {
proxy_pass http://api;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Nginx 설정법은 다른 포스트에서 소개하도록 하고 간단하게 내용만 알아보자.
2번째 줄은 deploy.sh
의 명령에 따라 server 172.17.0.2:${EXTERNAL_PORT}
로 변경될 예정이다. 80포트를 어느 포트에 연결해 줄것인지 결정한다. blue가 켜질 경우 blue에 맞는 외부포트와 연결되고, green일 경우 green에 맞는 외부포트와 연결되고 reload를 통해 새로운 컨테이너를 nginx가 바라보게 한다.
나머지는 log관련 설정과 nginx를 통과하기 전 client 정보를 헤더에 넣어 전달해 주는 역할을 한다.
deploy.sh 작성
Jenkins에서 ssh에 접속해 deploy.sh
파일과 nginx.conf
파일을 넘기고, deploy.sh
를 실행해 배포를 진행되게 할 예정이다.
이제 blue-green 배포를 수행하는 deploy.sh
파일을 작성해보자. 프로젝트 루트 디렉토리에 deploy.sh
파일을 생성하고 작성하자.
작성 전, 간단하게 deploy.sh
의 내부 동작을 알아보자.
- nginx 컨테이너가 정상 작동하는지 확인하고 동작하고 있지 않다면 nginx 컨테이너를 실행시킨다.
- 기존에 떠 있던 springboot의 포트가 A포트로 작동되고 있는지, B포트로 작동되고 있는지 확인한다.
- 기존 포트의 반대 포트로 springboot container를 작동시킨다.
- 새로 띄운 springboot container의 health check를 진행한다.
- health check가 통과되었다면, 미리 작성한
nginx.conf
파일의 포트 번호를 변경하고 교체한다. - nginx를 reload시켜 변경된 포트로 연결되게 한다.
- 기존 포트로 작동되고 있는 springboot container를 종료한다.
위의 과정을 거치면 컨테이너를 교체하는 과정에서 공백이 없기 때문에 중단없는 배포가 가능해진다.
하나하나 구현이 필요한 순서대로 deploy.sh
를 작성해보자.
변수 선언
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
NGINX_CONTAINER_CONF_DIR=/etc/nginx
NGINX_CONF_VOLUME=nginx-config-volume
HOST_NGINX_CONF_DIR=~/nginx-config
NGINX_CONF_FILE_DIR=~
DOCKER_CONTAINER_NAME_PREFIX=springboot
# 받아올 변수
# INTERNAL_PORT
# EXTERNAL_PORT_GREEN
# EXTERNAL_PORT_BLUE
# DOCKER_IMAGE_NAME
# OPERATION_ENV
가장 먼저 필요한 변수들을 정리해 둔다.
-
NGINX_CONTAINER_CONF_DIR
: nginx 컨테이너 내부 설정파일의 위치이다. nginx가 정상동작하고 있지 않는 경우 docker 명령어로 nginx를 켤때 볼륨 매핑을 위해 사용한다. -
NGINX_CONF_DIR
: 호스트에서 만들어둔 volume의 이름이다. 마찬가지로 nginx가 정상동작 하고 있지 않을 때, docker 명령어로 volume 매핑을 위해 사용한다. -
HOST_NGINX_CONF_DIR
: 호스트에서 만든 볼륨의 실제 위치이며 jenkins에서 전송한 nginx설정파일이 이동할 위치이다. -
NGINX_CONF_FILE_DIR
: jenkins에서 전송할 nginx설정파일이 위치할 경로이다. 이 경로의 설정파일을 호스트의 볼륨으로 이동시킨다. -
DOCKER_CONTAINER_NAME_PREFIX
: 컨테이너 교체 시, 두개의 컨테이너를 동시에 켜놓고 새로운 컨테이너가 정상작동이 판단되면 교체를 진행한다. 이 때, 컨테이너의 이름으로 어떤 컨테이너를 nginx에 연결할지 판단한다.springboot-blue
와springboot-green
으로 두 컨테이너의 이름을 정할 예정이다. 따라서 앞에 붙을prefix
를 정해 변수화 시킨다. -
INTERNAL_PORT
: 컨테이너가 작동할 내부 포트이다. 일반적으로 springboot 프로젝트의application.yml
에 작동할 포트 번호를 명시한다.deploy.sh
에서 다시 포트번호를 명시하면 포트번호를 관리할 곳이 두곳이나 생긴다. 따라서 Jenkins에서 빌드 전application.yml
에서 포트번호를 파싱해서 실제 서버에 전달할 예정이다. Jenkins에서 조금의 추가작업이 필요하고deploy.sh
를 작성한 후 해당 파싱 작업을Jenkins
파이프라인에 작성해보자. -
EXTERNAL_PORT_GREEN
,EXTERNAL_PORT_BLUE
: 컨테이너가 작동할 외부 포트이다. 예를 들어 8080과 8081이라면nginx.conf
파일을 새로 띄울 컨테이너의 외부 포트에 맞게 변경해주고 reload해야한다. 포트번호는 보안과 관계있기 때문에Jenkins
에서 credential로 관리하고 서버에 변수로 주입해주자. -
DOCKER_IMAGE_NAME
: 도커 허브에서 내려받을 이미지의 이름이다. 해당 이름은Jenkinsfile
에서push
를 위해 필요하고 또한deploy.sh
파일에서도pull
을 위해 필요하다. 두 곳에서 산재하면 관리가 어려우므로Jenkins
에서 변수를 넘겨 작업하는 것이 깔끔할 것이다. -
OPERATION_ENV
: 어떤 branch에서 시작된 배포 과정인지 전달한다.prod
와dev
가 있으며 docker container를 실행할 때 어떤 프로파일로 작동할지SPRING_PROFILES_ACTIVE
에 값을 넣어준다.
Nginx 작동 확인 및 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
# nginx 컨테이너가 정상 작동하는지 확인
IS_NGINX_RUNNING=$(docker inspect -f '{{.State.Status}}' nginx | grep running)
if [ -z "$IS_NGINX_RUNNING" ]; then
# 정상 작동하지 않을 시 nginx를 완전히 내린 후 다시 구동
echo "nginx container is not running. run nginx container"
docker rm -f nginx
docker run -d --name nginx \
-v ${NGINX_CONF_VOLUME}:${NGINX_CONTAINER_CONF_DIR} \
-p 80:80 nginx:latest
sleep 3
else
echo "nginx is already running"
fi
nginx가 정상적으로 작동하는지 확인하고 작동하지 않을 시 nginx를 완전히 내린 후 다시 구동한다. 이 과정에서 변수로 선언 해 놓았던 volume의 이름과 container 내부의 경로가 필요하다.
실행 명령어를 내리고 3초 sleep을 주어 구동시간을 확보해준다.
반대 컨테이너 구동
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
IS_BLUE_RUNNING=$(docker inspect -f '{{.State.Status}}' ${DOCKER_CONTAINER_NAME_PREFIX}-blue | grep running)
if [ -n "$IS_BLUE_RUNNING" ]; then
echo "green up"
docker rm -f ${DOCKER_CONTAINER_NAME_PREFIX}-green
docker run -d --name ${DOCKER_CONTAINER_NAME_PREFIX}-green \
-p ${EXTERNAL_PORT_GREEN}:${INTERNAL_PORT} \
-e "SPRING_PROFILES_ACTIVE=${OPERATION_ENV}" \
${DOCKER_IMAGE_NAME}:latest
BEFORE_COLOR=blue
AFTER_COLOR=green
EXTERNAL_PORT=${EXTERNAL_PORT_GREEN}
else
echo "blue up"
docker rm -f ${DOCKER_CONTAINER_NAME_PREFIX}-blue
docker run -d --name ${DOCKER_CONTAINER_NAME_PREFIX}-blue \
-p ${EXTERNAL_PORT_BLUE}:${INTERNAL_PORT} \
-e "SPRING_PROFILES_ACTIVE=${OPERATION_ENV}" \
${DOCKER_IMAGE_NAME}:latest
BEFORE_COLOR=green
AFTER_COLOR=blue
EXTERNAL_PORT=${EXTERNAL_PORT_BLUE}
fi
sleep 3
springboot-blue
가 구동중인지 확인하고, 정상적으로 구동이 판단되면 springboot-green
을 구동시킨다.
springboot-blue
가 구동중이 아니라면 springboot-green
이 작동중이라고 판단하고 springboot-blue
를 구동시킨다.
그리고 그 결과를 BEFORE_COLOR
, AFTER_COLOR
, EXTERNAL_PORT
에 담는다. 여기서 EXTERNAL_PORT
에 값을 담으면 nginx를 reload
할 때 nginx.conf
에서 EXTERNAL_PORT
의 값을 참조해 포트가 변경되게 된다.
새로운 컨테이너 Health Check
새로 띄워진 컨테이너가 정상 작동하는지 판단하고, 정상 작동한다면 default.conf
를 변경 후 nginx가 새로운 컨테이너를 바라보도록 reload한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
echo "Health Check Start!"
for i in {1..10}
do
sleep 3
RESPONSE=$(curl -I http://localhost:${EXTERNAL_PORT} | grep HTTP)
if [ -n "${RESPONSE}" ]; then
echo "> Health check 성공"
sed -i "2s/.*/server 172.17.0.1:${EXTERNAL_PORT};/g" ${NGINX_CONF_FILE_DIR}/nginx.conf
yes | cp -rf ${NGINX_CONF_FILE_DIR}/nginx.conf ${HOST_NGINX_CONF_DIR}/conf.d/default.conf
docker exec nginx nginx -s reload
sleep 3
docker rm -f ${DOCKER_CONTAINER_NAME_PREFIX}-${BEFORE_COLOR}
echo ${BEFORE_COLOR} down
echo "배포를 성공적으로 종료합니다"
exit 0
fi
done
echo "Health Check 실패"
docker rm -f ${DOCKER_CONTAINER_NAME_PREFIX}-${AFTER_COLOR}
echo "이전 컨테이너가 유지됩니다."
exit 1
새로운 컨테이너의 EXTERNAL_PORT
로 http://localhost
에 3초마다 요청을 보내 요청 결과에 HTTP
라는 단어가 있는지 판단한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 정상 작동 시
$ curl -I http://localhost:[PORT]/
HTTP/1.1 404
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 12 Feb 2023 08:39:45 GMT
# 비정상 작동 시
$ curl -I http://localhost:[PORT]/
curl: (7) Failed to connect to localhost port [PORT]: Connection refused
위 처럼 정상적으로 컨테이너가 동작하고 있다면 HTTP
라는 단어가 결과에 포함되어 있음을 할 수 있다. 따라서 HTTP
라는 문자열을 grep
해 사용한다.
sed
라는 리눅스 기본 유틸리티를 이용해 nginx.conf
의 두번째 줄을 server 172.17.9.1:${EXTERNAL_POORT};
로 교체한다. 이렇게 하고 nginx를 reload
하면 새로운 컨테이너를 바라보게 된다.
sed
라는 유틸리티를 사용한 이유는 nginx설정파일에서 공식적으로 환경변수를 지원하지 않고, envsubst
라는 유틸리티를 통해 우회해야하기 때문에 기본 유틸리티인 sed
를 사용하는 방법을 선택했다.
이제 nginx.conf
volume으로 복사하고 Nginx를 reload
한다. reload
후에는 기존에 동작하던 컨테이너를 내리고 배포를 종료한다.
최종 deploy.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/bin/bash
NGINX_CONTAINER_CONF_DIR=/etc/nginx
NGINX_CONF_VOLUME=nginx-config-volume
HOST_NGINX_CONF_DIR=~/nginx-config
NGINX_CONF_FILE_DIR=~
DOCKER_CONTAINER_NAME_PREFIX=springboot
# 받아올 변수
# INTERNAL_PORT
# EXTERNAL_PORT_GREEN
# EXTERNAL_PORT_BLUE
# DOCKER_IMAGE_NAME
# OPERATION_ENV
# nginx 컨테이너가 정상 작동하는지 확인
IS_NGINX_RUNNING=$(docker inspect -f '{{.State.Status}}' nginx | grep running)
if [ -z "$IS_NGINX_RUNNING" ]; then
# 정상 작동하지 않을 시 nginx를 완전히 내린 후 다시 구동
echo "nginx container is not running. run nginx container"
docker rm -f nginx
docker run -d --name nginx \
-v ${NGINX_CONF_VOLUME}:${NGINX_CONTAINER_CONF_DIR} \
-p 80:80 nginx:latest
sleep 3
else
echo "nginx is already running"
fi
IS_BLUE_RUNNING=$(docker inspect -f '{{.State.Status}}' ${DOCKER_CONTAINER_NAME_PREFIX}-blue | grep running)
if [ -n "$IS_BLUE_RUNNING" ]; then
echo "green up"
docker rm -f ${DOCKER_CONTAINER_NAME_PREFIX}-green
docker run -d --name ${DOCKER_CONTAINER_NAME_PREFIX}-green \
-p ${EXTERNAL_PORT_GREEN}:${INTERNAL_PORT} \
-e "SPRING_PROFILES_ACTIVE=${OPERATION_ENV}" \
${DOCKER_IMAGE_NAME}:latest
BEFORE_COLOR=blue
AFTER_COLOR=green
EXTERNAL_PORT=${EXTERNAL_PORT_GREEN}
else
echo "blue up"
docker rm -f ${DOCKER_CONTAINER_NAME_PREFIX}-blue
docker run -d --name ${DOCKER_CONTAINER_NAME_PREFIX}-blue \
-p ${EXTERNAL_PORT_BLUE}:${INTERNAL_PORT} \
-e "SPRING_PROFILES_ACTIVE=${OPERATION_ENV}" \
${DOCKER_IMAGE_NAME}:latest
BEFORE_COLOR=green
AFTER_COLOR=blue
EXTERNAL_PORT=${EXTERNAL_PORT_BLUE}
fi
sleep 3
echo "Health Check Start!"
for i in {1..10}
do
sleep 3
RESPONSE=$(curl -I http://localhost:${EXTERNAL_PORT} | grep HTTP)
if [ -n "${RESPONSE}" ]; then
echo "> Health check 성공"
sed -i "2s/.*/server 172.17.0.1:${EXTERNAL_PORT};/g" ${NGINX_CONF_FILE_DIR}/nginx.conf
yes | cp -rf ${NGINX_CONF_FILE_DIR}/nginx.conf ${HOST_NGINX_CONF_DIR}/conf.d/default.conf
docker exec nginx nginx -s reload
sleep 3
docker rm -f ${DOCKER_CONTAINER_NAME_PREFIX}-${BEFORE_COLOR}
echo ${BEFORE_COLOR} down
echo "배포를 성공적으로 종료합니다"
exit 0
fi
done
echo "Health Check 실패"
docker rm -f ${DOCKER_CONTAINER_NAME_PREFIX}-${AFTER_COLOR}
echo "이전 컨테이너가 유지됩니다."
exit 1
위의 deploy.sh
파일을 프로젝트 최상단에 위치시킨다.
Jenkinsfile 작성
기존에 Deploy to Server
Stage에서는 직접 ssh로 접속해 docker image를 pull받고 기존에 떠있는 docker container을 내린 후 다시 이미지를 띄웠다.
이 과정을 이제 다음과 같이 변경해 주어야 한다.
nginx.conf
,deploy.sh
를 전송한다.- 필요한 환경변수를 설정해준다.
deploy.sh
를 실행시킨다.
가장 먼저 전달해야하는 변수는 다음과 같다.
INTERNAL_PORT
EXTERNAL_PORT_GREEN
EXTERNAL_PORT_BLUE
DOCKER_IMAGE_NAME
OPERATION_ENV
INTERNAL_PORT
는 이전에 application.yml
에서 yq
를 통해 변수화 시켜놓아서 그냥 전달하면 된다. 또한 OPERATION_ENV
도 담아두어서 그냥 전달하기만 하면 된다. EXTERNAL_PORT_GREEN
과 EXTERNAL_PORT_BLUE
는 도커가 작동하는 외부포트이다. 이 변수는 소스코드내에서 관리되는 것이 아니라서 Jenkins에 Credential에 등록하고 전달한다. 여기서 DOCKER_IMAGE_NAME
은 도커허브아이디/도커이미지이름
의 형태이다. 따라서 해당 형태로 만들어 전달한다.
가장 먼저 EXTERNAL_PORT_GREEN
과 EXTERNAL_PORT_BLUE
를 Credential에 등록해 보자. cicd-test라는 item에 국한된 변수이므로 cicd-test 도메인에 Credential을 생성한다.
위와 같이 credential을 생성해준다. GREEN
이 8080이라면 BLUE
가 8081인 식으로 서로 다르게 등록하자.
가장 먼저 EXTERNAL_PORT_BLUE
와 EXTERNAL_PORT_GREEN
을 Credentials에서 꺼내와 변수에 넣어놓자.
environment
에서 포트 관련한 정보를 변수에 저장한다.
1
2
3
4
5
6
7
environment {
GPG_SECRET_KEY = credentials('GPG_SECRET_KEY')
// PORT
EXTERNAL_PORT_BLUE = credentials('EXTERNAL_PORT_BLUE')
EXTERNAL_PORT_GREEN = credentials('EXTERNAL_PORT_GREEN')
}
위와 같이 선언하면 다른 스테이지에서도 "${EXTERNAL_PORT_BLUE}"
와 같이 포트 정보에 접근이 가능하다.
INTERNAL_PORT
는 이미 Parse Internal Port
Stage에서 yq
를 이용해 내부 포트를 변수에 넣어주었다.
1
2
3
4
5
6
7
stage('Parse Interal Port') {
steps {
script {
INTERNAL_PORT = sh(script: "yq e '.server.port' ./src/main/resources/application-${OPERATION_ENV}.yml", returnStdout: true).trim();
}
}
}
returnStdout
은 sh메서드가 yq
의 작동 결과를 리턴하게 한다. returnStdout
이 false
라면 성공 시 0, 실패 시 1을 리턴하여 포트정보는 소실되게 된다.
이제 환경변수가 될 정보를 변수에 담았으니 실제로 배포 관련 파일들을 옮기고 실행해주기만 하면 된다. Deploy to Server
스테이지를 다음과 같이 변경한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
stage('Deploy to Server') {
steps {
echo 'Deploy to Server'
withCredentials([
usernamePassword(credentialsId: DOCKER_HUB_CREDENTIAL_ID,
usernameVariable: 'DOCKER_HUB_ID',
passwordVariable: 'DOCKER_HUB_PW'),
sshUserPrivateKey(credentialsId: SSH_CREDENTIAL_ID,
keyFileVariable: 'KEY_FILE',
passphraseVariable: 'PW',
usernameVariable: 'USERNAME'),
string(credentialsId: SSH_HOST_CREDENTIAL_ID, variable: 'HOST'),
string(credentialsId: SSH_PORT_CREDENTIAL_ID, variable: 'PORT')]) {
script {
def remote = [:]
remote.name = OPERATION_ENV
remote.host = HOST
remote.user = USERNAME
remote.password = PW
// remote.identity = KEY_FILE
remote.port = PORT as Integer
remote.allowAnyHosts = true
sshCommand remote: remote, command:
'docker pull ' + DOCKER_HUB_ID + '/' + DOCKER_IMAGE_NAME + ":latest"
sshPut remote: remote, from: './deploy.sh', into: '.'
sshPut remote: remote, from: './nginx.conf', into: '.'
sshCommand remote: remote, command:
('export OPERATION_ENV=' + OPERATION_ENV + ' && '
+ 'export INTERNAL_PORT=' + INTERNAL_PORT + ' && '
+ 'export EXTERNAL_PORT_GREEN=' + EXTERNAL_PORT_GREEN + ' && '
+ 'export EXTERNAL_PORT_BLUE=' + EXTERNAL_PORT_BLUE + ' && '
+ 'export DOCKER_IMAGE_NAME=' + DOCKER_HUB_ID + '/'
+ DOCKER_IMAGE_NAME + ' && '
+ 'chmod +x deploy.sh && '
+ './deploy.sh')
}
}
}
}
기존 Docker Container를 내리고 새로운 Container를 띄우는 과정을 삭제하고 다음과 같은 내용을 추가했다.
가장 먼저 docker 이미지를 pull해준다. 그리고 다음 명령으로 deploy.sh
와 nginx.conf
를 전송한다. 그리고 환경변수를 전달해 export하게 되면 해당 환경변수에 이전에 담아놨던 값들이 담기게 된다. 마지막으로 deploy.sh
를 실행 가능한 권한으로 변경하고 deploy.sh
를 실행한다.
이제 blue-green
무중단 배포를 시행해보자. 변경 내용을 커밋하고 push
하자.
1
2
3
$ git add .
$ git commit -m "chore: Blue Green 무중단 배포 적용"
$ git push
Jenkins를 확인해보자.
배포가 정상적으로 진행되었다. /hello
URL에 접속해보면 정상작동을 확인할 수 있다.
빌드 번호를 누르고 Replay
버튼을 눌러 다시 빌드해본다. 빌드가 진행되는 와중에 /hello
URL에서 새로고침을 계속 눌러봐도 서비스가 끊기는 구간이 없다. 정상적으로 무중단배포가 진행된 것이다.
또한 실제로 서버에 들어가서 컨테이너를 확인해 보면
Replay
전과 후로 blue
에서 green
으로 컨테이너가 변경된 것을 확인할 수 있다.
이로써 성공적으로 blue-green
방식의 무중단 배포를 마쳤다. 다음 포스트에서는 Jenkinsfile
에 간단한 메서드 작성으로 빌드 stage
마다의 성공 또는 실패 결과를 실시간으로 받아보자.
댓글남기기