티스토리 뷰

 

기존의 로드밸런서만을 사용하던 프로젝트에서 docker를 이용한 무중단 배포를 적용을 해야했습니다.
이때, 추가적으로 Nginx를 도입하면서 했던 생각들과 여기서 어떤 방식이 제일 효율적이며 안정적인가 고민을 하면서 도입을 했는데 이번에 이를 정리해보려 합니다.

 

기존 프로젝트에선 로드밸런서에 ACM과 Route53을 통해 https를 적용하고 뒷단의 서버는 ci-cd로 중단 배포가 적용되었습니다. 이를 그림으로 간단하게 보면 다음과 같습니다.

 

AWS ELB가 앞단에 존재하고, 요청을 받게 되면 뒤에 있는 EC2로 요청을 전달하는 과정으로 통신이 진행됩니다.
이때, ELB에는 Route53을 통해 기존에 저희가 사두었던 도메인이 적용되어 있으며, ACM을 통해 SSL 인증서가 적용되어 앞단의 ELB는 443포트(HTTPS)로의 요청을 받을 수 있었습니다.
또한 ci-cd는 깃허브액션으로 ssh를 기반으로 배포하는 방식으로 이때 서버가 20~40초 정도 중단되는 문제가 있었습니다.

 

 

 

 

 

무중단 배포 도입 방식 논의

 

저희는 배포시에 서버가 다운되는 기존의 구조에서 사용성을 개선시키기 위해 무중단 배포를 도입을 해야했습니다. 이때 저희는 무중단 배포를 도입하기위해 생각한 방식은 총 두가지였는데 다음과 같습니다.

 

  1. CodeDeploy를 활용해 EC2 서버 기반 블루/그린 무중단 배포
  2. Nginx를 활용한 도커 기반 블루/그린 무중단 배포

첫번째 방법은 배포시에 새로운 EC2를 띄우고 ELB를 기준으로 블루/그린 배포를 하는 방식입니다. 따라서, 이 방식을 적용하게 되면 CodeDeploy와 2개의 EC2가 필수적으로 필요했습니다. 하지만 저희는 이 서버를 사비로 운영하고 있었고, 두개의 EC2를 사용하게 될 경우 프리티어를 사용하지 못하여 비용이 추가적으로 부과된다는 점에서 부담을 느꼈습니다.
이러한 이유 때문에 첫번째 방법은 적용하지 않았습니다.

 

두번째 방법은 한개의 EC2 내부에서 두개의 port를 통해 Nginx를 기준으로 블루/그린 배포를 하는 방식입니다. 따라서, 이방식을 적용하게 되면 한개의 EC2 내부에 Nginx 및 두개의 서버를 띄울 수 있어, 프리티어로 충분히 유지할 수 있다는 장점이 있었습니다. 따라서 저희는 비용부담이 적은 두번째 방법을 선택을 하게됐고, 이번에 Nginx를 추가적으로 도입하여 무중단 배포를 구현하기로 하였습니다.

 

 

 

 

 

 

Nginx 도입 과정

 

앞전에 두번째 방법으로 무중단배포를 구현하려고 했기에 Nginx를 도입해야 했습니다. 근데 Nginx를 도입하기위해 아키텍처를 수정하던 도중 다음과 같은 의문점이 생겼습니다.

 

  • Nginx에 SSL 인증서를 적용하면 ELB는 필요가 없는 것 아닌가?

현재 저희 서버에 트래픽이 그렇게 많이 들어오지 않아 아직까진 프리티어 단일 서버로 운영되고 있습니다. 따라서 저희는 이렇게 ELB를 사용하기 보다 ELB를 없애고 Nginx에 SSL 인증서를 적용시키면 HTTPS를 그대로 사용하면서 비용 부담은 줄어들지 않을까 생각을 하였습니다.

 

저희가 Nginx에 SSL을 적용시키기위해 두가지 방식을 생각했는데, 다음과 같습니다.

 

  • Cerbot으로 무료 SSL 인증서 발급
  • ACM에 존재하는 SSL 인증서를 Nginx에 적용

첫번째 방식은 Cerbot을 통해 Let's Encrypt 인증서를 무료로 발급받는 방법이였습니다. 이방식이 굉장히 쉽고 간편한 방식입니다. 하지만 90이라는 짧은 기간 동안만 인증되며, 그 이후에 다시 수동으로 발급하여야 한다는 단점이 존재합니다.
물론 스케줄러를 활용해 자동으로 인증 갱신을 하는 방법도 존재하지만, 아직 저희가 기존에 구매한 ACM SSL 인증서의 보증기간이 1년 남짓 남았기 때문에 Cerbot을 이용하는건 보류하자는 결론이 나왔습니다.

 

두번째 방식은 현재 AWS에서 발급받은 SSL 인증서의 정보를 가져와 Nginx에 적용하는 방식입니다. 사실 ACM의 SSL인증서는 AWS에서 제공하는 ELB나 다른 서비스에 사용할 수 있습니다. 즉 AWS에서 콘솔창으로는 ACM 인증서의 정보가 나와있지않기때문에 Nginx에 적용할 수 없습니다.
하지만, 저희가 구글링을 해본 결과 AWS CLI를 통해 ACM인증서의 정보를 가져올 수 있는 방법을 알게되었습니다. 따라서 저희는 AWS CLI에 로그인을 한후, acm get-certificate 명령을 통해 인증서와 인증서 체인의 정보를 얻을 수 있었습니다.
이렇게 인증서의 정보를 Nginx에 적용을 하고 재시작을 눌렀는데 Private Key 오류가 났습니다. 이 오류에 대해 찾아본 결과 SSL 인증서를 적용해 HTTPS를 구현을 하기 위해선 인증서 + 인증서 체인 + 개인키 이렇게 3개가 필요한데, 우리의 경우 개인키파일이 없기 때문에 일어난 오류였습니다. 정말 당연한 오류였는데, AWS ACM에선 보안상의 이유로 개인키를 직접 사용자에게 제공하지 않고 있었습니다. 따라서 저희는 Nginx에 SSL 인증서를 적용하지 못한다 라고 판단을 하였습니다.


결국, Nginx에 HTTPS를 적용하지않고 로드밸런서를 남겨두기로 결정을 하였습니다. 일단 기존에 구매해둔 SSL 인증서의 인증기간이 남아 있기때문에 Cerbot을 활용하는 방식으로 바꾸기엔 아직 이르다고 생각을 하였고, 정말 혹시 모를 트래픽 증가 때, Scale-Out을 빠르게 적용하기 위해 비용이 조금 들더라도 로드밸런서를 남겨두는게 맞다라는 판단을 하였습니다.

따라서, 위의 그림처럼 앞단에 ELB는 그대로 두고 EC2 안에 Nginx를 추가하여 요청이 들어오면 뒷단의 Spring 서버에 전달을 할 수 있게 되었습니다.

 

 

 

 

 

Docker를 활용한 무중단 배포

 

이제 Nginx를 도입을 하였으니, 무중단 배포를 구현을 할 차례입니다. 무중단 배포는 Nginx를 기반으로 내부 Port를 바꿔가면서 배포를 적용하여 사용자 입장에서 최소한의 중단 시간으로 사용성을 높일 수 있는 기술입니다.

무중단 배포를 도입할 수 있는 방법은 다양하겠지만 가장 많이 쓰이는 방법은 다음과 같습니다.

 

  • Spring 내부 profile을 이용해 서로 다른 port를 설정하는 방식
  • Docker를 이용해 서로 다른 port를 설정하는 방식

전자의 방식은 Spring 내부의 application 파일에 서로 다른 profile을 설정하여 블루/그린의 서로 다른 port(ex. 8080, 8081)를 사용하여 배포할때마다 서로 다른 port로 배포를 하여 무중단 배포를 하는 방식입니다. 이 방식도 굉장히 많이 쓰이는 방식이지만 저희의 경우 application 파일을 깃-서브모듈을 이용해 관리를 하고 있기도하고, Spring을 docker없이 그냥 무중단 배포를 도입한다 했을때, 환경 의존성이 너무 높아진다고 판단하여 docker를 사용하기로 판단을 하였습니다.

docker를 사용하여 무중단 배포를 한다고 가정했을때 다음과 같습니다. 

 

기존에 Blue 컨테이너가 존재할때 배포를 진행했다고 가정 하면, 새로운 Green 컨테이너를 배포합니다. 이때 Nginx는 바뀌는 거 없이 계속해서 기존의 Blue 컨테이너(8080)를 가리키고 있습니다. 이때 Nginx에서 Green 컨테이너에 헬스체크를 하여 Green 컨테이너 내부에서 Spring이 정상적으로 실행이 되고 있는지를 확인합니다. 이후 Green 컨테이너에서 헬스체크가 정상으로 떴다면 Nginx가 가리키는 포트를 Green 컨테이너(8081)로 바꿉니다.


이 과정을 통해 배포시에 서버가 중단되는 시간을 최소화하여 배포를 진행 할 수 있습니다. 이때, Nginx 포트 바꾸는 과정에서 Restart를 해야하기에 0.1~0.5초의 중단 시간은 있지만 이정도면 아주 미세한 시간이기에 무중단 배포라고 불립니다.

 

 

 

 

무중단 배포 도입 과정

 

자 이제 Nginx를 어떻게 설정하였고, CI-CD때 무중단 배포 스크립트를 어떻게 작성했는지 알아보도록 하겠습니다.

Nginx의 경우 /etc/nginx 파일 안에 모든 설정파일들이 존재합니다. 여기서 저희가 봐야하는  디렉토리는 다음과 같습니다.

 

  • /etc/nginx/nginx.conf 파일
    - nginx의 전체설정을 할 수 있는 파일
  • /etc/nginx/sites-available 디렉토리
    - 사용가능한 모든 설정파일을 관리하는 디렉토리
  • /etc/nginx/sites-enabled 디렉토리
    - 실제 nginx에 적용하려는 설정파일 관리하는 디렉토리

 

여기서 저희는 sites-available 안에 있는 설정파일을 수정해서 저희에게 맞게 블루그린으로 바꿀려고 합니다.

/etc/nginx/sites-available 디렉토리 안에 default 파일이 있습니다. 이를 삭제하고 저희는 blue, green 파일로 바꾸면됩니다.

 

 

 

파일은 총 두개가 있으며, blue와 green 파일 내용은 다음과 같습니다.

 

 

 

 

server {
    listen 80;

    location / {
        proxy_pass http://127.0.0.1:8080; #blue
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

 

 

server {
    listen 80;

    location / {
        proxy_pass http://127.0.0.1:8081; #green
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

 

 

 

 

이제 이렇게 sites-available 디렉토리에 blue, green 파일을 만들었습니다. 여기에 파일이 있다고해서 바로 적용되는 것은아닙니다.

/etc/nginx/sites-available 디렉토리는 그냥 사용가능한 모든 설정 파일을 관리하는  디렉토리이고, 실제로 설정 파일을 활성화 시키기 위해선 sites-enabled 디렉토리에 해당 파일이 존재해야합니다.

이에 대한 이유는 nginx.conf 파일 내부 코드에 있습니다.

 

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;


events {
        worker_connections 768;
        # multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;

 

 

 

위의 코드에서 빨간 부분과 같이 nginx.conf 파일에서 sites-enabled 디렉토리 안에 있는 파일들을 가져와 nginx 설정을 하기 때문입니다.

 

따라서, nginx에서 blue, green을 적용하기 위해선 sites-available 디렉토리가 아닌 sites-enabled 디렉토리에 있어야합니다.

이렇게 파일을 계속해서 연결하기 위해서 심볼릭 링크를 사용했으며, 심볼릭 링크를 통해 현재 환경조건에 따라서 sites-available에 있는 파일을 sites-enabled에 포인터 링크를 걸어 관리를 할 수 있게 하였습니다.

즉, blue 컨테이너 일땐  sites-enabled 디렉토리에 blue 환경설정파일이 존재해야하며, green 일땐 green 환경설정파일이 존재해야합니다.

 

심볼릭 링크를 걸어 다음과 같이 환경설정 정보가 sites-enabled에 존재할 수 있도록 하였습니다.

 

 

 

 

이렇게 환경설정을 한 후, 무중단 배포 CI-CD 배포 스크립트를 작성했는데 스크립트는 다음과 같습니다.

 

name: deploy on prod server

on:
  push:
    branches: [main]

jobs:
  deploy:

    runs-on: ubuntu-latest

    steps:
      - name: 체크아웃
        uses: actions/checkout@v4
        with:
          submodules: true
          token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}

      - name: 서브모듈 업데이트
        run: |
          git submodule update --remote

      - name: JDK 11 설치
        uses: actions/setup-java@v4
        with:
          distribution: 'corretto'
          java-version: '11'
          cache: 'gradle'

      - name: Gradle에 실행 권한 부여
        run: chmod +x gradlew

      - name: 빌드
        run: ./gradlew build -x test

      - name: DockerHub 로그인
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Docker 이미지 빌드 & DockerHub에 Push
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./docker/Dockerfile-prod
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/qtudy-server:prod

      - name: EC2 서버에 배포
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.PROD_SERVER_HOST }}
          username: ${{ secrets.PROD_SERVER_USERNAME }}
          key: ${{ secrets.PROD_SERVER_PEM_KEY }}
          envs: GITHUB_SHA, DOCKERHUB_USERNAME
          script: |
            # 현재 활성화된 Nginx 설정 파일 확인
            CURRENT_CONF=$(sudo readlink /etc/nginx/sites-enabled/current)
            echo "Current Nginx configuration: $CURRENT_CONF"
            if [[ "$CURRENT_CONF" == *"blue" ]]; then
              NEW_CONF="/etc/nginx/sites-available/green"
              OLD_CONTAINER="blue-container"
              NEW_CONTAINER="green-container"
              NEW_PORT=8081
            else
              NEW_CONF="/etc/nginx/sites-available/blue"
              OLD_CONTAINER="green-container"
              NEW_CONTAINER="blue-container"
              NEW_PORT=8080
            fi

            echo "New Nginx configuration: $NEW_CONF"
            echo "Old container: $OLD_CONTAINER"
            echo "New container: $NEW_CONTAINER"
            echo "New port: $NEW_PORT"

            # 새 Docker 이미지 가져오기
            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/qtudy-server:prod

            # 새로운 컨테이너 실행
            sudo docker run -d -p $NEW_PORT:8080 --name $NEW_CONTAINER ${{ secrets.DOCKERHUB_USERNAME }}/qtudy-server:prod

            # 새로운 컨테이너가 준비될 때까지 대기
            echo "Waiting for the new container to be ready..."
            sleep 10

            # 헬스 체크를 통해 새 컨테이너가 정상적으로 실행 중인지 확인
            until [[ "$(curl -s -o /dev/null -w ''%{http_code}'' http://localhost:$NEW_PORT/actuator/health)" == "200" ]]; do
              echo "Waiting for the new container to respond with HTTP 200..."
              sleep 2
            done

            echo "New container is ready. Switching Nginx configuration."

            # Nginx 설정 파일 교체 및 재시작
            sudo ln -sf $NEW_CONF /etc/nginx/sites-enabled/current
            sudo systemctl reload nginx

            # 이전 컨테이너 중지 및 제거
            sudo docker stop $OLD_CONTAINER
            sudo docker rm $OLD_CONTAINER

            # 실행 중인 Docker 컨테이너 확인
            sudo docker ps

            # 사용하지 않는 Docker 이미지 정리
            sudo docker image prune -f

 

 

위의 배포 스크립트를 적용한 결과 nginx를 활용해 docker 기반 무중단 배포를 손쉽게 적용시킬 수 있었습니다.
docker를 사용해 환경에 제약받지않고 무중단 배포를 적용할 수 있었고, 또한 컨테이너를 기반으로 blue, green을 나눴기때문에 nginx 환경설정도 그렇게 많은 시간을 들이지 않고 개발이 가능했었습니다.

이렇게 다양한 방식을 고려하고 무중단 배포를 도입해보며 다양한 지식을 얻을 수 있었고 평소에 깊게 파보지못했던 nginx를 깊게 공부할 수 있는 정말 좋은 기회였습니다. 

'네트워크' 카테고리의 다른 글

SSH 원격접속 (Feat.터널링)  (0) 2023.11.16
공개키 암호화 방식  (2) 2023.10.03
해시함수란?  (0) 2023.09.24
대칭키(Symmetric key)  (0) 2023.09.17
포트포워딩이란?  (0) 2023.08.21
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
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
글 보관함