Search

도커를 활용한 무중단 배포 - (1) Rolling Updates

Date
2021/02/25
Tags
tags

무중단 배포와 도커

무중단 배포란 서버를 실제로 서비스할 때 서비스적 장애와 배포에 있어서 부담감을 최소화할 수 있게끔 서비스가 중단되지 않고도 코드를 배포할 수 있는 기술입니다.
서버 환경을 통해서 무중단 배포를 구현하면 좋겠지만 여기서는 무중단 배포를 경험하는 정도로 도커를 활용하여 구현하겠습니다.

준비사항

1.
본인의 환경에 맞추어서 docker-compose 명령어를 설치합니다.
2.
프로젝트 폴더를 생성하고 아래와 같이 docker-compose.ymlnginx.conf 파일을 생성합니다.
docker-non-stop-deploy ├── docker-compose.yml └── proxy └── nginx.conf
Plain Text
복사
1.
docker-compose.yml 파일을 작성합니다.
nginx, app1, app2 총 3개의 서비스로 이루어져 있습니다.
nginx만 클라이언트에서 접근할 수 있게 포트가 노출되어 있고 app1, app2는 nginx을 통해서 로드밸런싱된 트래픽을 수신합니다.
app1, app2 이미지로 bithavoc/hello-world-env 를 사용합니다. 실제로는 flask, express, spring-boot 가 사용되겠지만 예시를 단순화하기 위해서 사용하였습니다. GET /으로 요청을 보낼 경우 Hi there, I love ! ${MESSAGE}와 같이 환경변수 MESSAGE가 응답본문에 나타납니다. 환경변수를 변경하는 작업으로 버전 업그레이드를 표현하겠습니다.
version: '3.8' services: # nginx nginx: image: nginx:latest container_name: nginx ports: - "80:80" volumes: - ./proxy/nginx.conf:/etc/nginx/nginx.conf # nginx을 통해서 로드밸런싱되는 app1, app2 app1: image: bithavoc/hello-world-env environment: - 'MESSAGE=app1,v1' expose: - "3000" app2: image: bithavoc/hello-world-env environment: - 'MESSAGE=app2,v1' expose: - "3000"
YAML
복사
1.
nginx의 설정 파일인 nginx.conf를 작성합니다.
설정 수정 후 service nginx reload 명령어를 실행하면 nginx을 중단하지 않고 설정을 반영 할 수 있습니다.
listen 80location /을 통해서 요청을 받고 proxy_pass http://apps 설정을 통해서 upstream apps 부분으로 이동하여 app1:3000과 app2:3000으로 로드밸런싱을 진행합니다.
user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # load balancing upstream apps { server app1:3000; server app2:3000; } server { listen 80; server_name localhost; location / { # proxy pass proxy_pass http://apps; proxy_http_version 1.1; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } 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; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; }
Plain Text
복사
1.
nginx, app1, app2를 실행하고 요청을 보냅니다.
# docker-non-stop-deploy 폴더 내에서 > docker-compose up -d Creating network "docker-non-stop-deploy_default" with the default driver Creating docker-non-stop-deploy_app2_1 ... done Creating docker-non-stop-deploy_app1_1 ... done Creating nginx ... done
Bash
복사
nginx, app1, app2가 실행되었는지 확인합니다.
# docker-non-stop-deploy 폴더 내에서 > docker-compose ps Name Command State Ports ------------------------------------------------------------------------------------------- docker-non-stop-deploy_app1_1 /root/app Up 3000/tcp docker-non-stop-deploy_app2_1 /root/app Up 3000/tcp nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:80->80/tcp
Bash
복사
GET / 요청을 날려서 응답를 확인합니다. 로드밸런싱이 되어서 app1,app2로 요청이 나누어서 가는 것을 알 수 있습니다.
> curl localhost:80 Hi there, I love ! app1,v1 > curl localhost:80 Hi there, I love ! app2,v1 > curl localhost:80 Hi there, I love ! app1,v1 > curl localhost:80 Hi there, I love ! app2,v1
Bash
복사

Rolling Updates

Rolling Update는 다음의 순서로 진행됩니다.
step1 - 배포 전 상태입니다. app1, app2 모두 로드밸런서(nginx)에 연결되어 있습니다.
step2 - app2를 로드밸런서에서 제거하고 app2에 배포를 진행합니다.
step3 - app2에 배포가 완료되면 app2를 다시 로드밸런서에 연결합니다. app1을 로드밸런서에 제외하고, app1에 배포를 진행합니다.
step4 - app1에 배포가 완료되면 app2를 다시 로드밸런서에 연결합니다.

Step 1

배포가 진행되는 동안 무중단으로 배포되는지 확인을 위해서 터미널 하나에서 주기적으로 nginx에 요청을 보냅니다. app1, app2으로부터 v1이 포함된 응답을 받을 수 있습니다.
# 1초 간격으로 무한으로 요청을 보냅니다. > while true; do curl localhost:80; echo ""; sleep 1; done Hi there, I love ! app1,v1 Hi there, I love ! app2,v1 Hi there, I love ! app1,v1 Hi there, I love ! app2,v1 ...
Bash
복사

Step 2

app2를 로드밸런서에서 제거합니다. proxy/nginx.conf 내에서 upstream 부분을 다음과 같이 down을 추가합니다.
upstream apps { server app1:3000; server app2:3000 down; }
Bash
복사
nginx 컨테이너에 다음의 명령어를 보내서 무중단으로 설정을 리로드합니다.
> docker-compose exec nginx service nginx reload [ ok ] Reloading nginx: nginx.
Bash
복사
app1에서만 응답이 오는 것을 확인할 수 있습니다.
Hi there, I love ! app1,v1 Hi there, I love ! app1,v1 Hi there, I love ! app1,v1 Hi there, I love ! app1,v1 Hi there, I love ! app1,v1 Hi there, I love ! app1,v1 Hi there, I love ! app1,v1 Hi there, I love ! app1,v1
Bash
복사
app2에 대해서 배포를 진행합니다. docker-compose.yml 내 app2 부분을 다음과 같이 수정합니다.
app2: image: bithavoc/hello-world-env environment: - 'MESSAGE=app2,v2'
YAML
복사
app2만 단독으로 배포를 진행합니다.
> docker-compose up -d app2 docker-non-stop-deploy_app2_1 is up-to-date
Bash
복사

Step 3

app2에 배포가 완료되면 app2를 다시 로드밸런서에 연결합니다. nginx.conf 중 upstream 부분을 다음과 같이 수정하고 nginx의 설정을 리로드합니다.
upstream apps { server app1:3000; server app2:3000; }
Plain Text
복사
> docker-compose exec nginx service nginx reload [ ok ] Reloading nginx: nginx.
Bash
복사
app1에서는 v1, app2에서는 v2 응답이 오는 것을 확인할 수 있습니다.
Hi there, I love ! app1,v1 Hi there, I love ! app2,v2
Bash
복사
app1을 로드밸런서에 제거하기 위해서 nginx.conf를 다음과 같이 수정하고 nginx를 리로드합니다.
upstream apps { server app1:3000 down; server app2:3000; }
Plain Text
복사
> docker-compose exec nginx service nginx reload [ ok ] Reloading nginx: nginx.
Bash
복사
app2에서 v2 응답만 오는 것을 확인할 수 있습니다.
Hi there, I love ! app2,v2 Hi there, I love ! app2,v2
Bash
복사
app1이 로드밸런서에서 끊어졌으므로 배포를 진행합니다. docker-compose.yml 내 app1 부분을 다음과 같이 수정합니다.
app1: image: bithavoc/hello-world-env environment: - 'MESSAGE=app1,v2'
YAML
복사
app1만 단독으로 배포를 진행합니다.
> docker-compose up -d app1 docker-non-stop-deploy_app1_1 is up-to-date
Bash
복사

Step 4

app1에 배포가 완료되었으므로 app1을 nginx에 연결하기 위해서 nginx.conf를 수정합니다.
upstream apps { server app1:3000; server app2:3000; }
Plain Text
복사
nginx 설정을 리로드합니다.
> docker-compose exec nginx service nginx reload [ ok ] Reloading nginx: nginx.
Bash
복사
이제 app1, app2 모두 v2 응답이 오는 것을 확인할 수 있습니다.
Hi there, I love ! app1,v2 Hi there, I love ! app2,v2
Bash
복사
조금은 복잡한 것 같지만 Rolling Update를 docker-compose로 구현해보았습니다.
무중단 배포의 전제조건은 이중화, 다시 말해서 로드밸런서가 전제되어야 합니다.
다음 글에서는 도커를 활용하여 Blue/Green 배포를 구현해보도록 하겠습니다.

참고