업무 상 Docker와 Nginx를 통해 Blue Green 무중단 배포를 구축하려고 한다.
OS는 Centos 7.x 기준이고 Application은 Spring Boot 기준으로 정리하였다.
현재 서버는 NCP에 Server를 사용하고 있고 Container Registry에 이미지를 저장해 사용하고 있다.
1. Centos에 Nginx 설치
먼저 yum 업데이트한다.
$ sudo yum update
업데이트가 다 되었으면 yum 저장소에는 nginx가 없기 때문에 외부 저장소를 추가해서 설치해야 한다.
$ sudo vi /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1
$ sudo yum install -y nginx
설치가 정상적으로 되었으면 Nginx를 실행시킨다.
$ sudo systemctl start nginx
$ sudo systemctl status nginx
2. Docker 및 Docker compose 설치
yum util 설치 후 docker repository를 시스템에 추가한다.
$ sudo yum install yum-utils
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
정상적으로 등록되었으면 docker community edition을 설치하고 docker를 실행한다.
$ sudo yum install docker-ce
$ sudo systemctl start docker
$ sudo systemctl status docker
나의 경우는 ncloud 계정으로 접속했기 때문에 docker socket에 ncloud 계정의 권한을 줘야 한다.
$ sudo chown ncloud:ncloud /var/run/docker.sock
docker compose도 설치한다.
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose # docker-compose 권한 부여
3. Nginx 설정
/etc/nginx/conf.d 경로에 service-url.inc을 만들고 service url을 환경변수로 등록해준다.
요 파일은 deploy시 셸 스크립트에서 사용해 동적으로 nginx 설정을 바꿔주는 역할을 한다.
$ sudo vim /etc/nginx/conf.d/service-url.inc
set $service_url http://127.0.0.1:8080;
그리고 동일한 경로에 application.conf 파일을 작성한다.
$ sudo vi application.conf
server {
listen 80;
server_name {domain};
include /etc/nginx/conf.d/service-url.inc;
location / {
proxy_pass $service_url;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header x-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
}
위 설정은 80 port로 접속했을 때 service-url.inc 파일에 기입된 url로 바인딩한다.
위에서 작성한 application.conf를 include 하도록 nginx.conf 파일을 수정한다.
$ sudo vi /etc/nginx/nginx.conf
http {
....
# include /etc/nginx/conf.d/*.conf;
include /etc/nginx/conf.d/application.conf;
}
위 설정을 모두 마쳤다면 nginx를 재시작한다.
$ sudo systemctl restart nginx
4. Docker compose 작성
총 2개(blue, green)의 docker compose를 작성해야 한다.
나는 루트 경로에 작성했는데 위치는 원하는 곳에 작성해도 무관하다.
$ cd ~/
$ sudo vi docker-compose.blue.yaml
$ sudo vi docker-compose.green.yaml
docker-compose.blue.yaml
version: '3.1'
services:
api:
image: ${IMAGE_STORAGE}/${IMAGE_NAME}:${BUILD_NUMBER}
container_name: ${IMAGE_NAME}-blue
environment:
- LANG=ko_KR.UTF-8
- UWSGI_PORT=8080
ports:
- '8080:8080'
networks:
default:
external:
name: service-network
docker-compose.green.yaml
version: '3.1'
services:
api:
image: ${IMAGE_STORAGE}/${IMAGE_NAME}:${BUILD_NUMBER}
container_name: ${IMAGE_NAME}-green
environment:
- LANG=ko_KR.UTF-8
- UWSGI_PORT=8081
ports:
- '8081:8080'
networks:
default:
external:
name: service-network
blue 컨테이너는 8080 포트를 그대로 포워딩하였고, green 컨테이너는 8081 포트로 포워딩해주었다.
IMAGE_STORAGE, IMAGE_NAME, BUILD_NUMBER는 Jenkins에서 환경변수로 지정해줄 것이다.
그리고 두 컨테이너에서 사용할 docker network도 생성해주어야 한다.
$ docker network create service-network
5. 배포 스크립트 작성
이제 배포하는 쉘 스크립트를 작성해야 한다.
$ sudo vi deploy.sh
# 1
EXIST_BLUE=$(docker-compose -p ${IMAGE_NAME}-blue -f docker-compose.blue.yaml ps | grep Up)
if [ -z "$EXIST_BLUE" ]; then
docker-compose -p ${IMAGE_NAME}-blue -f ~/docker-compose.blue.yaml up -d
BEFORE_COMPOSE_COLOR="green"
AFTER_COMPOSE_COLOR="blue"
BEFORE_PORT_NUMBER=8081
AFTER_PORT_NUMBER=8080
else
docker-compose -p ${IMAGE_NAME}-green -f ~/docker-compose.green.yaml up -d
BEFORE_COMPOSE_COLOR="blue"
AFTER_COMPOSE_COLOR="green"
BEFORE_PORT_NUMBER=8080
AFTER_PORT_NUMBER=8081
fi
echo "${AFTER_COMPOSE_COLOR} server up(port:${AFTER_PORT_NUMBER})"
# 2
for cnt in {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
# 3
sudo sed -i "s/${BEFORE_PORT_NUMBER}/${AFTER_PORT_NUMBER}/" /etc/nginx/conf.d/service-url.inc
sudo nginx -s reload
echo "Deploy Completed!!"
# 4
echo "$BEFORE_COMPOSE_COLOR server down(port:${BEFORE_PORT_NUMBER})"
docker-compose -p ${IMAGE_NAME}-${BEFORE_COMPOSE_COLOR} -f docker-compose.${BEFORE_COMPOSE_COLOR}.yaml down
- blue container가 실행되고 있는지 확인 후 실행되고 있다면 green container를 생성하고 실행되고 있지 않다면 blue container를 생성한다.
- 생성한 컨테이너 안에 Application이 정상적으로 구동되기까지 10번의 핑을 보내 확인한다.
- 만약 정상적으로 response가 반환되었다면 service-url.inc에 포트를 바꿔주고 Nginx를 reload 시켜 80 port에 새로운 container를 바인딩해준다.
- 마지막으로 전에 구동되고 있던 container는 삭제한다.
그리고 Container Registry에서 이미지를 pull 받으려면 docker에 로그인해야 한다.
$ docker login {Container Registry 주소}
6. 테스트
이제 환경변수에 알맞을 값들을 설정해주고 배포 스크립트와 docker compose 파일에 실행 권한을 준 뒤 배포 스크립트를 실행해보자.
# 환경변수 등록
$ export IMAGE_STORAGE={Container Registry 주소};
$ export IMAGE_NAME={Image Name};
$ export BUILD_NUMBER={Image Tag};
$ cd ~/
# 읽기, 실행 권한 부여
$ sudo chmod 755 deploy.sh
$ sudo chmod 755 docker-compose.blue.yaml
$ sudo chmod 755 docker-compose.green.yaml
# 스크립트 실행
$ ./deploy.sh
정상적으로 배포가 되는 것을 확인할 수 있다!!
7. Jenkins 파이프라인 구축
내가 구축한 무중단 자동화 배포 파이프라인은 간단히 정리해보자면
- 소스 Push
- Jenkins Webhook
- Application Test, Build
- Docker image Build
- Docker image Push
- ssh 배포할 서버에 ssh로 쉘 명령어 실행(./deploy.sh)
요 순서로 파이프라인이 진행된다.
Jenkinsfile의 내용은 아래와 같다.
pipeline {
agent any
tools {
jdk("java-11-amazon-corretto")
}
stages {
stage('Start!') {
steps {
script {
GIT_COMMIT_AUTHOR = sh(script: "git --no-pager show -s --format=%an ${GIT_COMMIT}", returnStdout: true).trim();
GIT_COMMIT_MESSAGE = sh(script: "git --no-pager show -s --format=%B ${GIT_COMMIT}", returnStdout: true).trim();
}
}
}
stage('Clean Build Test') {
steps {
sh "SPRING_PROFILES_ACTIVE=${BRANCH_NAME} ./gradlew clean build test"
}
}
stage('Build Container Image') {
steps {
script {
image = docker.build("${IMAGE_STORAGE}/${IMAGE_NAME}", "--build-arg SPRING_PROFILES_ACTIVE=${BRANCH_NAME} --build-arg CONFIG_SERVER_ENCRYPT_KEY=${CONFIG_SERVER_ENCRYPT_KEY} --build-arg CONFIG_SERVER_USERNAME=${CONFIG_SERVER_USERNAME} --build-arg CONFIG_SERVER_PASSWORD=${CONFIG_SERVER_PASSWORD} .")
}
}
}
stage('Push Container Image') {
steps {
script {
docker.withRegistry("https://${IMAGE_STORAGE}", IMAGE_STORAGE_CREDENTIAL) {
image.push("${BUILD_NUMBER}")
image.push("latest")
}
}
}
}
stage('Server Run') {
steps {
sshagent(credentials: [SSH_CONNECTION_CREDENTIAL]) {
sh "ssh -o StrictHostKeyChecking=no ${SSH_CONNECTION} 'IMAGE_NAME=${IMAGE_NAME} IMAGE_STORAGE=${IMAGE_STORAGE} BUILD_NUMBER=${BUILD_NUMBER} ./deploy.sh'"
}
}
}
}
가독성 때문에 Slack 발송은 제거했다.
더 자세한 내용을 설명하기에는 글이 너무 길어져서 Jenkins 구축 방법은 아래에서 확인이 가능하다.
8. 대망의 배포..!
이제 코드를 Push 하고 Jenkins 로그를 확인해보면 정상적으로 무중단 자동화 배포가 진행되는 것을 볼 수 있다!!!
이렇게 Jenkins와 Docker, Nginx를 이용해 무중단 자동화 배포를 구축해보았다.
정리해보면 별것 아니지만 구축을 하면서 방화벽을 잘못 건드려서 아예 새로 서버를 만들어 다시 작업도 했고, nginx를 정확히 알지 못해서 설정에 애를 먹기도 했다. (한 2일 정도 삽질 후에 구축이 된 것 같다...)
그래도 서버에 대한 지식 기초들을 많이 접해본 기회였던 것 같아서 좋은 경험이었다.
'Docker & Kubernetes' 카테고리의 다른 글
도커 네트워크(Docker Network)의 구조와 기능 (0) | 2022.12.27 |
---|---|
도커 볼륨(Docker Volume) 활용하기 (2) | 2022.12.27 |
Centos Docker 컨테이너 내부에 Docker 설치하기 (0) | 2022.07.22 |
초기화 전용 컨테이너 - initContainers (0) | 2022.02.04 |
파드(Pod)의 동작 확인과 헬스 체크(health check) (0) | 2022.02.04 |