본문 바로가기
개발

[배포] Docker, spring을 통한 무중단 배포 구현

by 주주병 2024. 10. 16.
728x90
반응형

Docker를 통한 배포

이전의 글에서는 단순히 docker 없이 배포하는 방법을 설명했었습니다.

이 글에서는 dockerfile을 작성해서 배포하는 방법을 알려드리겠습니다. 이전의 글이 궁금하신 분은 아래의 링크를 봐주세요.

 

[배포] github actions, EC2, nginx를 통한 무중단 배포

github actions, EC2, nginx를 통한 무중단 배포지금은 익숙한 프로세스이지만 누구나 처음 배포하는 과정은 매우 어려울 것 같습니다. 그렇지만 미래를 상상하면서 지금의 과정을 즐기는 것도 나쁘지

gotobill.tistory.com

 

 

디렉토리 구조

ec2 서버의 디렉토리 구조

ec2 서버의 구조

로컬 프로젝트 디렉토리 구조

로컬 디렉토리의 구조

  • 여기서 jar 파일은 깃허브의 가상 서버에 만들어지는 파일입니다.
728x90

 

설치

패키지 업데이트

sudo apt-get update

 

기본 패키지 설치

sudo apt-get install \
  ca-certificates \
  curl \
  gnupg \
  lsb-release

 

gpg 키 추가

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  • sudo mkdir -p /etc/apt/keyrings: gpg 키를 저장할 디렉토리 생성
  • curl -fsSL https://download.docker.com/linux/ubuntu/gpg: gpg 키 다운로드
  • sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg: gpg 키를 바이너리로 저장

-> 이렇게 다운로드를 받음으로써 패키지를 안전하게 다운 받을 수 있습니다.

 

도커 repository 설정

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

-> Docker의 공식 APT 리포지토리를 Ubuntu 시스템에 추가하여 Docker 패키지를 설치할 수 있도록 설정하는 과정입니다.

 

도커 설치

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

 

도커 시작 및 버전 확인

sudo systemctl start docker
sudo systemctl enable docker
sudo docker --version

 

도커 사용자 그룹에 추가

sudo usermod -aG docker $USER

 

nginx 설정 및 Dockerfile 작성

nginx 설정

upstream backend {
    server app_blue:8081;
    server app_green:8082;
}

server {
    listen 80;

    server_name [공인 ip]; # 서버의 공인 IP 주소를 설정합니다

    location / {
        proxy_pass http://backend;
        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;
    }
}

위 코드에서 이 부분은 로드밸런싱을 위한 코드입니다.

우리가 새로운 버전을 배포할 때 두 개의 서버를 통해서 새로운 버전의 서버가 다 배포됐을 경우,

기존의 서버를 다운시켜서 총 1개의 서버로 유지시키는 기법을 이용할텐데 그때 기존의 이용자가 끊김을 겪지 않게 하기 위해서 nginx가 트래픽을 분산시키기 위한 설정입니다.

 

nginx Dockerfile

# Nginx 이미지를 기반으로 설정합니다
FROM nginx:alpine

# Nginx 설정 파일을 컨테이너로 복사합니다
COPY nginx.conf /etc/nginx/conf.d/default.conf

 

etc/nginx/conf.d/ 경로에 copy를 왜 하는가?

  • conf.d/ 디렉토리에 있는 모든 .conf 파일은 Nginx가 자동으로 로드합니다.
  • Nginx의 디폴트 설정에서는 include /etc/nginx/conf.d/*.conf; 명령어가 일반적으로 포함되어 있어, conf.d 디렉토리에 있는 모든 설정 파일을 자동으로 불러옵니다.

이는 새로운 설정을 추가할 때마다 nginx.conf를 직접 수정할 필요 없이 파일을 추가하기만 하면 되게 하기에 일종의 관례입니다.

 

docker-compose.yml 작성

services:
  app_blue:
    build:
      context: .
      dockerfile: Dockerfile
    image: 도커의_유저네임/app_blue:latest
    container_name: roomescape-app-blue
    environment:
      - SERVER_PORT=8081
    ports:
      - "8081:8081"
    restart: always

  app_green:
    build:
      context: .
      dockerfile: Dockerfile
    image: 도커의_유저네임/app_green:latest
    container_name: roomescape-app-green
    environment:
      - SERVER_PORT=8082
    ports:
      - "8082:8082"
    restart: always

  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    image: 도커의_유저네임/nginx:latest
    ports:
      - "80:80"
    depends_on:
      - app_blue
      - app_green
    container_name: roomescape-nginx
    restart: always

 

알아야 할 점

  • docker의 build할 때 도커 파일은 context의 폴더 설정 기준으로 Dockerfile이라고 적혀있는 파일을 씁니다.
  • SERVER_PORT=8081 -> 애플리케이션이 8081 포트를 사용하게 합니다.
  • ports
    - "8082:8082"
    -> 호스트의 포트 8082와 컨테이너의 포트 8082를 연결한다는 의미.
  • restart: always
    -> 컨테이너가 중지되거나 시스템이 재부팅되었을 때 항상 자동으로 다시 시작하도록 설정

app_green과 app_blue의 Dockerfile 설정

# OpenJDK 17을 기반으로 하는 이미지를 사용합니다
FROM openjdk:17-jdk-slim

# 컨테이너 내의 작업 디렉터리를 설정
WORKDIR /app

# 호스트의 JAR 파일을 컨테이너로 복사합니다
# ec2 서버 내부로 app.jar 파일을 github actions를 통해 가져와야함.
COPY ./app.jar app.jar

# 애플리케이션이 사용할 포트를 노출합니다
EXPOSE 8080

# JAR 파일을 실행하는 명령을 지정합니다
CMD ["java", "-jar", "app.jar"]
  • WORKDIR /app
    -> WORKDIR 명령어를 사용하여 작업 디렉토리를 설정하면 이후의 모든 COPY, ADD, RUN, CMD 명령어는 이 디렉토리를 기준으로 실행됩니다. 따라서 경로를 명확하게 설정할 수 있습니다.

 

build.sh 작성

#!/bin/bash

# 현재 구동 중인 애플리케이션 확인
CURRENT_APP=$(docker-compose ps | grep "app_blue" | grep "Up" || echo "")

# 새 버전을 배포할 대상 결정
if [[ -n "$CURRENT_APP" ]]; then
  TARGET="app_green"
else
  TARGET="app_blue"
fi

echo "배포할 대상: $TARGET"

# Docker 이미지 빌드
docker-compose build $TARGET

# 새로운 버전 실행
docker-compose up -d $TARGET

# 새로운 버전이 구동될 때까지 대기
echo "새로운 버전의 애플리케이션이 구동될 때까지 대기 중..."
sleep 15

# 오래된 버전 중지
if [[ "$TARGET" == "app_green" ]]; then
  OLD_APP="app_blue"
else
  OLD_APP="app_green"
fi

echo "오래된 버전 중지: $OLD_APP"
docker-compose stop $OLD_APP

 

github actions 설정

name: CI/CD Pipeline

on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          distribution: 'adopt'
          java-version: '17'

      - name: Install Docker Compose
        run: |
          sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
          sudo chmod +x /usr/local/bin/docker-compose
        # 깃허브의 가상 서버에 docker-compose 설치

      - name: Build with Gradle
        run: ./gradlew clean -x test build

      - name: Copy JAR for Docker
        run: cp build/libs/프로젝트_이름-0.0.1-SNAPSHOT.jar app.jar

      - name: Upload app.jar as artifact
        uses: actions/upload-artifact@v3
        with:
          name: app-jar
          path: ./app.jar
       # 최신의 버전을 ec2로 옮기기 위한 과정

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

      - name: Build and Push Docker Image
        run: docker-compose build && docker-compose push
        # 도커 허브에 이미지 삽입

  deploy:
    if: github.event_name == 'push'
    needs: build
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Download app.jar artifact
        uses: actions/download-artifact@v3
        with:
          name: app-jar
          path: .

      - name: Deploy with Docker Compose
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
        run: |
          echo "$SSH_PRIVATE_KEY" > private_key.pem
          chmod 600 private_key.pem
          scp -i private_key.pem -o StrictHostKeyChecking=no \
            docker-compose.yml \
            build.sh \
            app.jar \
            Dockerfile \
            ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}:/home/${{ secrets.EC2_USERNAME }}/
            # 나는 나의 로컬에서 nginx의 폴더를 제외한 설정 파일을 만들어서 ec2로 보내는 과정을 거쳤다.
            # 하지만 그렇게 하지 않고 ec2에 미리 만들어놔도 된다.
           	
          
          ssh -i private_key.pem -o StrictHostKeyChecking=no ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }} "chmod +x build.sh && ./build.sh"
          rm private_key.pem
728x90
반응형