Overview
사이드 프로젝트에서 서버를 제대로 관리하기 위해 Docker를 적용하면서 무중단으로 배포한 과정을 공유하려고 합니다.😃
전에는 Node.js 프로세스 관리 모듈로 Pm2를 사용하면서 reload 기능으로 무중단 배포를 했었는데 Docker와 pm2를 같이 사용하는 것은 권장되지 않기때문에 pm2를 사용하지 않고 nginx의 http 로드 밸런싱 기능을 이용해 무중단 배포를 진행하였습니다.
왜 Docker와 pm2를 함께 사용하는 것이 권장되지 않을까?
Docker 내부에서 pm2를 실행시키는 것은 의미가 없습니다.
Docker와 pm2 모두 프로세스 프로세스 관리자이며 둘 다 로그 전달, 자동 다시 시작을 수행할 수 있습니다.
(pm2 자동 재시작 - Docker의 restart 옵션으로 처리, pm2 멀티 프로세싱 - 다수의 컨테이너로 처리)
컨테이너 내부에서 여러 프로세스를 실행하면 모니터링이 어려워집니다. 문제가 생겼을때 pm2가 자동으로 restart를 해버리면 docker의 모니터링 시스템에서는 알 수 없습니다.
무중단 배포 아키텍처
무중단 배포의 핵심은 로드밸런서를 통해 두개 이상의 인스턴스에 트래픽을 제어해 배포하는 것 입니다.
nginx의 http 로드밸런싱 기능을 이용해 적용하였습니다.
무중단 배포는 크게 롤링 방식과 블루/그린 방식으로 나뉩니다.
롤링 방식
인스턴스 하나를 로드밸런서에서 라우팅하지 않게 한뒤 새 버전을 적용 후 다시 라우팅하는 방식
블루/그린 방식
현재 인스턴스의 수만큼 새 버전의 인스턴스를 준비해 로드밸런서가 스위칭해주는 방식
관련하여 잘 정리된 글 링크를 첨부합니다.
Nginx HTTP 로드밸런싱이란
Nginx는 매우 효율적인 HTTP 로드 밸런서입니다.
HTTP 로드 밸런싱을 적용하려면 upstream 그룹을 설정해주고 server 설정 부분에 proxy_pass를 upstream 그룹으로 설정해주면 됩니다.
http {
upstream backend {
server backend1.example.com;
server backend2.example.com;
}
server {
location / {
proxy_pass http://backend;
}
}
}
로드 밸런싱 방식은 여러가지가 있는데, 기본적으로 서버 전체에 고르게 분산되는 라운드 로빈 방식으로 처리됩니다.
- 기본(라운드 로빈) : 서버 가중치를 고려하여 서버 전체에 고르게 분산
- least_conn(최소 연결) : 서버 가중치를 다시 고려하여 활성 연결 수가 가장 적은 서버로 요청 전송
- ip_hash(IP 해시) : 클라이언트의 IP 주소에 따라 요청할 서버를 결정. 동일한 주소의 요청이 동일한 서버에 도달하는 것을 보장함
일시적으로 서버를 제거해야 하는 경우 down 변수로 요청을 중지할 수 있습니다.
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
}
특정 서버에 가중치를 주거나 요청이 실패했을때의 경우도 처리할 수 있습니다.
upstream backend {
server backend1.example.com max_fails=3 fail_timeout=30s;
server backend2.example.com max_fails=3 fail_timeout=30s;
}
- weight=number : 요청을 보낼때 이 서버는 number 만큼의 가중치를 가진다. (기본값 = 1)
- max_fails=number : 이 서버가 number 만큼 요청이 실패하면 중단된 것으로 간주한다. (기본값 = 1)
- fail_timeout=time (ex > 3s) : 이 서버가 중단된 경우 time 만큼 요청을 보내지 않는다. (기본값 = 10s)
Nginx 로드 밸런싱 적용하기
아래 사진과 같이 80포트로 들어온 요청을 두개의 포트로 분산되도록 로드밸런싱을 설정해줍니다.
nginx 설정(다른 부분 생략)
upstream backend {
server host.docker.internal:5001 max_fails=3 fail_timeout=30s;
server host.docker.internal:5002 max_fails=3 fail_timeout=30s;
}
server {
location / {
proxy_pass http://backend;
}
}
host.docker.internal는 HOST의 IP주소를 의미합니다.
서버의 IP 주소를 직접 입력해도 되겠지만 확장성이 떨어지게 됩니다.
127.0.0.1로 upstream을 설정할 경우 서버의 127.0.0.1이 아닌 컨테이너 내부를 가리키게 되므로 아래 에러가 발생하게 됩니다.
"no live upstreams while connecting to upstream" error in nginx-proxy container"
로컬 환경에서는 문제없이 잘 돌아갔지만 리눅스 환경에서는 아래 에러가 발생할 수 있습니다.
"host not found in upstream "host.docker.internal:5001" in /etc/nginx/nginx.conf:14"
도커 최신 버전부터는 리눅스도 host.docker.internal를 지원을 해주지만 자동으로 적용되는 것이 아니기 때문에 수동으로 -add-host=host.docker.internal:host-gateway 옵션을 붙여줘야합니다.
docker-compose의 경우 extra_host를 옵션으로 주면 됩니다.
nginx:
extra_hosts:
- "host.docker.internal:host-gateway"
배포 sciprt 작성하기
shell script는 이 글을 참고하여 작성하였습니다.
docker 원격 배포(context) 설정은 이전에 작성한 docker 호스트에 원격으로 배포하기 글을 참고하시면 됩니다.
기존에 작성해준 docker-compose.yml 파일을 docker-compose.blue.yml, docker-compose.green.yml 파일로 분리하여 로드밸런싱 설정에서 입력한 포트와 매칭해주시면 됩니다.
docker-compose.blue가 실행중이라면 docker-compose.green을 최신 이미지로 내려받은 후 실행합니다.
DOCKER_APP_NAME=backend
docker context use backend-ec2
EXIST_BLUE=$(docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml ps | grep Up)
if [ -z "$EXIST_BLUE" ]; then
docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml pull
docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d
sleep 10
docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml down
else
docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml pull
docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml up -d
sleep 10
docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml down
fi
docker context use default
Conclusion
현재 무중단 배포 방법은 확실하게 동작하지만 PORT가 항상 고정이고, 로드밸런싱 과정에서 해당 포트가 살아있는지 계속해서 확인하는 과정을 거쳐야 합니다.
이러한 문제점을 어떻게 해결할 수 있을지 고민해봐야겠습니다.
더 좋은 방법이 있거나 잘못된 부분이 있다면 댓글로 알려주시기 바랍니다. 🙏🏻
참조글
'기타' 카테고리의 다른 글
[Docker] Docker 환경 Nginx에 SSL 인증서 적용하기(Let’s Encrypt) (1) | 2022.01.17 |
---|---|
[Macbook] 맥북 외장모니터 연결 문제 임시 해결(모니터 깜빡임) (2) | 2022.01.09 |
[Docker] Docker 호스트에 원격으로 배포하기(docker compose 이용) (1) | 2021.12.18 |
[MacBook]맥북에게 5000번 포트를 빼앗겼을때 - error: bind EADDRINUSE null:5000 에러 해결 (8) | 2021.12.18 |
SVN checkout시 SSL 에러 해결방법(CentOS) (0) | 2021.11.05 |