[GCP 원데이] 무중단 배포 환경 (로드밸런싱)
1. 무중단 배포 환경
지금까지의 도커 컨테이너의 앱을 실행시키는 과정에서 약 5초라는 시간이 소요되는 것을 로그로 확인할 수 있다. 그리고 약 5초의 시간동안 애플리케이션은 요청을 받지 못한다. 즉, 기존에 동작하고 있던 애플리케이션을 내린 순간부터 사용자는 서비스의 중단을 경험한다.
이전 버전(V1) 애플리케이션을 종료하고 새로운 버전(V2) 애플리케이션을 실행하고 새로운 버전 애플리케이션이 요청을 받을 준비가 될 때까지 서비스가 중단된다. 이러한 시간을 다운타임이라고 한다.
V1과 V2는 동일한 포트를 사용하기 때문에 하나의 서버에서 하나의 포트를 동시에 서로 다른 애플리케이션이 사용하는 것이 불가능하다.
이를 해결하고자 나온 대안으로는 두 개의 서버 중, 하나의 서버에 배포를 하는 동안 다른 서버는 서비스를 운영하는 방법이다. 하지만 이렇게 구성할 경우, 사용자는 두 서버의 IP를 모두 알아야하는 문제와 어떤 서버가 배포 중인 서버인지 알 수 없다는 문제가 발생한다.
이 문제의 해결책으로는 애플리케이션 서버들과 사용자 사이에서 중게를 해주는 기능이 있으면 된다. 이러한 기능을 우리는 리버스 프록시라고 한다.
프록시는 대리자라는 뜻을 가지고 있다. 일반적인 프록시는 클라이언트를 숨겨주는 역할을 수행한다. 서버쪽에서는 클라이언트가 누구인지 식별이 안된다. 이를 반대로 서버쪽이 숨겨지는 구조를 리버시 프록시이다.
그리고 이러한 구조에서는 트래픽도 분산할 수 있을 것으로 보인다. 하나의 서버에서 받아야할 요청을 여러 개의 서버로 분산하는 부하 분산(로드밸런싱)이라고 한다. 그리고 이러한 기능을 도와주는 대표적인 웹 서버로는 NginX와 HAProxy가 있다.
다양한 배포 방식이 존재하지만 대표적으로 롤링 배포, 블루 그린 배포, 카나리 배포가 있다.
- 롤링 배포
일반적인 배포를 의미하며, 단순하게 서버를 구성하여 배포하는 전략이다. 즉, 구 버전에서 신 버전으로 트래픽을 점진적으로 전환하는 배포이다. 관리가 편하지만 배포 중 한쪽 인스턴스의 수가 감소되므로 서버 처리 용량을 미리 고려해야 한다.
- 블루 그린
구 버전을 블루, 신 버전을 그린이라고 하여 붙여진 이름이다. 신 버전을 배포하고 일제히 전환하여 모든 연결을 신 버전을 바라보게 하는 전략이다. 구 버전, 신 버전 서버를 동시에 나란히 구성하여 배포 시점에 트래픽이 일제히 전환된다. 빨느 롤백이 가능하고, 운영환경에 영향을 주지 않고 실제 서비스 환경으로 신 버전 테스트가 가능하다. 단, 이러한 구성은 시스템 자원이 두 배로 필요하여 비용이 많이 발생한다.
- 카나리
카나리는 카나리아라는 새에서 유래되었다. 카나리아는 유독가스에 민감한 동물로 석탄 광산에서 유독가스 누출의 위험을 알리는 용도로 사용되었다. 그리고 카나리 배포는 미리 위험을 빠르게 감지할 수 있는 배포 전략이다. 지정한 서버 또는 특정 사용자에게만 배포했다가 정상적이면 전체를 배포한다. 서버의 트래픽을 일부를 신 버전으로 분산하여 오류 여부를 확인할 수 있다. 이런 전략은 A/B 테스트가 가능하며 성능 모니터링에 유용하다. 트래픽을 분산시킬 때에는 라우팅을 랜덤하게 할 수 있고 사용자로 분류할 수 있다.
- 트래픽이 매우 많아져도 NginX만으로도 충분한가?
충분하지 않다. 스케일 아웃으로 애플리케이션의 서버가 많아져도 트래픽은 NginX를 거쳐가고, NginX가 처리할 수 있는 용량에도 한계가 존재한다. 이를 해결하기 위한 방법은 다음과 같다.
- NginX가 실행되는 서버 스케일업
- 네트워크 장치로 로드 밸런싱 (L4 스위치, L7 스위치)
- DNS 리다이렉션 (하나의 도메인에 여러개의 IP가 있을 때)
- 롤링으로 배포하다 실패할 경우
배포에 실패한 원인은 배포가 진행 중인 과정에서 바로 알기 어려울 때가 많다. 따라서 새로운 버전으로 배포된 서버를 이전 버전으로 롤백시키고 배포가 실패한 원인을 찾아봐야 한다. 대부분 코드에 문제가 있는 경우가 많다. 이를 위해서는 고려해야하는 두 가지가 있다.
배포 성공 여부를 체크할 수 있는 존재가 있어야 한다. 새로 배포된 서비스에 특정 API 요청을 했을 때 올바른 응답이 오는지 확인하는 제 3의 서비스가 있어야 한다.
롤백을 사람에 의해 수동으로 할 것인가 혹은 자동으로 롤백할 것인가를 고려해야한다. 이 경우에는 수동으로 롤백을 하다가 배포가 잦은 조직이라면 자동으로 롤백할 수 있게 변경하는 것에 좋다.
2. NginX 로드밸런싱
1) 인스턴스 생성
머신 이미지(스냅샷)는 기존에 만들었던 인스턴스를 그대로 복제할 수 있는 기능을 제공한다.
생성한 머신 이미지를 이용하여 새로운 인스턴스를 2개 더 만들어 준다.
2) Jenkins 설정
SSH에 접속하여 Jenkins를 재시작해주고, Jenkins에 접속하여 생성한 인스턴스들의 정보를 SSH 서버로 등록해준다. 또한 만들었던 아이템의 구성에 들어가서도 등록을 해준다. 단, 기존에 휴지통으로 버리는 것과 같은 'dev>null' 명령어를 로그를 남길 수 있도록 실행명령어를 수정해준다.
nohup docker run -p 8080:80 ozofweird/spring-boot-cpu-bound > nohup.out 2>&1 &
모든 설정이 완료되면 빌드를 수행한다. 정상적으로 완료가 된 것처럼 보이나, 실제 SSH에 접속하여 nohup.out 파일을 보면 가장 처음에 생성한 인스턴스가 배포되지 않음을 확인할 수 있다.
이는 기존에 8080 포트로 실행 중인 애플리케이션이 있어서 생긴 문제다.
그 외의 2번, 3번 인스턴스를 확인해보면 도커 데몬이 실행 중이지 않다는 내용을 볼 수 있다. 이를 해결하기 위해서 도커를 실행시키는 명령어를 2번과 3번 인스턴스에 적용해준다.
sudo systemctl start docker
sudo chmod
sudo chmod 666 /var/run/docker.sock
3) NginX 로드밸런싱 설정
NginX 인스턴스를 생성해준다.
- 서울 리전에는 4개만 설치가 가능하기 때문에 NginX의 경우 타이완으로 리전 선택
- e2-medium
- CentOS 7
생성해준 인스턴스에 NginX를 설치해준다.
sudo yum install -y nginx
sudo systemctl start nginx
다음은 NginX가 로드밸런싱을 하도록 NginX 문서를 확인하여 설정파일을 수정해준다.
sudo vi /etc/nginx/nginx.conf
upstream cpu-bound-app {
server {instance_1번의_ip}:8080 weight=100 max_fails=3 fail_timeout=3s;
server {instance_2번의_ip}:8080 weight=100 max_fails=3 fail_timeout=3s;
server {instance_3번의_ip}:8080 weight=100 max_fails=3 fail_timeout=3s;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
proxy_pass http://cpu-bound-app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
모든 설정이 되어있다면, NginX를 reload 해주고, 로드밸런싱이 되는지 확인하기 위해 NginX 인스턴스에 접속해본다. 여기서 restart 명령어는 완전히 종료 후 재시작을 하는 것이고, reload는 설정 파일들을 다시 불러오는 명령어이다. 즉, 두 명령어의 차이는 프로세스가 종료되는지의 차이이다. 다만, reload도 짧은 다운타임이 존재하기에 nginx도 이중으로 구성하는 경우도 있다.
sudo systemctl reload nginx
접속해서 에러가 나는 경우, NginX의 에러 로그를 확인하고
sudo tail -f /var/log/nginx/error.log
에러를 해결하기 위한 방법으로는 구글링을 통해 찾을 수 있다.
sudo setsebool -P httpd_can_network_connect on
다시 접속하면 문제가 해결되는 것을 확인할 수 있다.
3. 성능 측정
1) Artillery 로드 밸런싱 테스트 (1)
초당 1회로 요청했을 때 5분 테스트를 진행한다.
config:
target: "http://[NginX IP 설정]"
phases:
- duration: 360
arrivalRate: 1
name: Warm up
scenarios:
# We define one scenario:
- name: "just get hash"
flow:
- get:
url: "/hash/123"
artillery run --output report.json ./cpu-test.yaml
artillery report ./report.json
※ 500 에러는 CPU 바운드 애플리케이션에서 자신의 성능이 더 이상 해당 요청에 대한 응답을 할 수 없는 상태가 되어서 CPU 바운드 애플리케이션 자체에서 떨어진 에러이다.
※ 502에러는 CPU 바운드 애플리케이션이 500에러를 계속 던지다 더 이상 500에러를 응답할 힘조차 없을때에 대한 에러이다. 이 경우 애플리케이션 자체가 종료가 된다. (행 상태)
결과적으로 8개까지는 요청에 대해 정상적으로 서비스가 가능하지만, 16개는 불가능하다는 것을 알 수 있다.
2) Artillery 로드 밸런싱 테스트 (2)
두 개의 인스턴스 컨테이너를 종료하고 하나의 인스턴스에서 스트레스 테스트를 진행하는 경우 절반은 200 절반은 502 에러가 발생하는 것을 확인할 수 있다. 다시 시작을 하려면, Jenkins에 접속하여 새롭게 빌드를 진행해주면 된다.
docker ps | grep spring-boot-cpu-bound
docker container kill -s 15 [컨테이너 ID]
4. 그 외
NginX의 경우 범용적인 기능을 더 많이 제공하기에 사용률이 높지만, 로드 밸런싱의 또 다른 솔루션으로 HAProxy도 있다. NginX와 HAProxy의 차이를 인지하고 적용할 서비스에 알맞는 것을 고려해보아야 한다.
[참고] www.lesstif.com/system-admin/forward-proxy-reverse-proxy-21430345.html
[참고] d2.naver.com/helloworld/284659
[참고] reference-m1.tistory.com/211