카테고리 없음

OTP 기반 2단계 인증 시스템 직접 구현해보기 - 5-2 Docker를 구현해보자.

dandev 2025. 6. 2. 14:06
반응형

이 포스팅은 이전 포스팅과 이어지므로 이전 포스팅을 먼저 보기를 권장한다.

이번 포스팅에서는 현재 프로젝트의 구조와 MSA로 전환하기 위해 어떤 과정이 필요한지 알아보자.

 

✨ 시리즈 구성

1. OTP 기반 2단계 인증 시스템 직접 구현해보기 - 1 시스템 아키텍처 및 3단계 인증 흐름 

2. OTP 기반 2단계 인증 시스템 직접 구현해보기 - 2 인증 서버를 구현해보자.

3. OTP 기반 2단계 인증 시스템 직접 구현해보기 - 3 비즈니스 논리 서버를 구현해보자.

4. OTP 기반 2단계 인증 시스템 직접 구현해보기 - 4 인증 서버와 비즈니스 논리 서버가 잘 동작하는지 확인해보자.

5. OTP 기반 2단계 인증 시스템 직접 구현해보기 - 5 멀티 모듈 구조를 MSA 구조로 리팩토링해보자.

    5-1. 현재 프로젝트의 구조와 MSA로 전환하기 위해 어떤 과정이 필요한지 알아보자.

    5-2. Docker를 구현해보자.

 


 

 

이번 포스팅에서는 Docker 기반 MSA 환경을 구축해보자.

 

🐳 먼저 Docker란?

컨테이너 기반 가상화 기술로, OS 가상화가 아닌 애플리케이션 수준의 경량 가상화 기술이다.

 

컨테이너란 무엇일까?

- 실행에 필요한 모든 파일을 포함한 runtime환경에서 애플리케이션을 패키징하고 격리할 수 있는 기술로

쉽게 말해 '프로그램과 그 프로그램이 잘 돌아가는 데 필요한 모든 것들(코드, 라이브러리, 설정 파일)'을 함께 가지고 있는 패키지 같은 것이다.

어디서든 똑같이 잘 실행되도록 필요한 모든 걸 담은 파일을 의미하고, 조금 더 기술적으로 표현하면 컨테이너 이미지라는 파일 안에 애플리케이션과 그 실행에 필요한 모든 환경이 포함돼 있고, 이 이미지를 실행하면 컨테이너가 만들어진다.

 

컨테이너 이미지를 실행하면 컨테이너가 생성된다.

 

 

🤷🏼‍♀️ 그럼 왜 Docker를 사용할까?

위에서 말했다 싶이 실행에 필요한 환경(라이브러리, 설정 등)을 하나의 이미지로 묶어 제공하기 때문에 

"내 컴퓨터에서는 되는데,," 라는 문제를 해결해준다.

특히 MSA 서비스는 독립된 컨테이너로 실행되어야 하므로 Docker가 꼭 필요하다.

 


 

이제 Docker에 대해 알아봤으니 실습해보자.

 

각 서비스에 Dockerfile을 만들자.

 

FROM openjdk:17-jdk-slim

WORKDIR /app

COPY build/libs/auth-0.0.1-SNAPSHOT.jar app.jar

COPY wait-for-it.sh /wait-for-it.sh
RUN chmod +x /wait-for-it.sh

CMD ["/wait-for-it.sh", "mysql:3306", "--", "java", "-jar", "app.jar"]

 

 

  • openjdk:17-jdk-slim 이미지를 베이스로 사용한다는 의미이고
  • 컨테이너 내부 작업 디렉토리를 /app으로 설정하고
  • 빌드된 Java 실행 파일(즉, Spring Boot fat JAR)을 컨테이너 /app 폴더에 app.jar 이름으로 복사한다.
    • build/libs/auth-0.0.1-SNAPSHOT.jar 경로는 로컬 빌드 경로이고, 이 JAR가 컨테이너에서 실제로 실행될 애플리케이션이다.
  • wait-for-it.sh 스크립트를 컨테이너 루트(/)로 복사한다.
    • 이 스크립트는 컨테이너 시작 시 특정 서비스(보통 DB)가 준비될 때까지 기다리게 하는 스크립트이다.
    • 네트워크 서비스가 준비될 때까지 기다리는 유명한 오픈소스 스크립트이다. 
    • (p.s) 이걸 설정하지 않고 실행했을 때 DB Connection Refused 에러가 발생해 추가하였다.
      • DB 또는 의존 서비스가 준비될 때까지 앱 실행을 지연시켜서 의존성 문제를 해결하기 위해 많이 사용한다.
      • 이 외에도 Docker 환경에서 서비스가 준비될 때까지 대기하는 방법이 있는데 대표적으로 wait-for-it.sh, dockerize, HEALTHCHECK 방법이 있다. (추후에 각 방법의 장/단점, 용도 및 상황에 대해 포스팅하겠다.)
  • RUN chmod +x 명령어로 스크립트 실행 권한을 준다.
    • 컨테이너가 실행될 때 기본적으로 수행하는 명령어이다.
      • wait-for-it.sh를 실행해서 mysql 호스트의 3306 포트가 열릴 때까지 기다린다.
        • (즉, MySQL DB가 준비 완료될 때까지 대기)
    • 준비가 되면 -- 뒤 명령어인 java -jar app.jar 를 실행해 Spring Boot 애플리케이션을 구동한다.

 

자 이제 Dockerfile로 애플리케이션을 컨테이너화했다.

여기서 문제가 발생한다.

이전 포스팅에서 우리는 이 애플리케이션을 localhost에 띄운 MySQL 서버에 접속하도록 설정했었다.

하지만 Docker 컨테이너 내붕에서는 호스트 컴퓨터의 localhost(MySQL 서버)를 인식하지 못한다.

왜냐하면, localhost는 컨테이너 내부에서 자신의 네트워크를 의미하기 때문에,

컨테이너에서 localhost를 호출하면 컨테이너 내부를 가리키지 호스트 컴퓨터의 MySQL 서버를 가리키지 않는다.

 

따라서 MySQL도 Docker 컨테이너로 띄우기로 결정했다.

이렇게 하면 같은 Docker 네트워크 내에서 컨테이너들이 서로를 이름으로 인식하고 통신할 수 있게 된다.

 

즉, 

  • 애플리케이션 컨테이너와 MySQL 컨테이너를 Docker 네트워크에 연결시키고, MySQL 서버 접속 주소를 localhost가 아닌 MySQL 컨테이너 이름(mysql 같은 서비스명)으로 지정하여 통신하도록 구현했다.

 

요약

  1. Docker 컨테이너는 호스트의 localhost를 인식하지 못함
  2. 따라서, 호스트에 띄운 MySQL에 직접 연결 불가
  3. MySQL도 컨테이너로 띄우면 네트워크 내에서 서비스 이름으로 접근 가능
  4. 이렇게 하면 Docker 컨테이너 간 통신이 원활해지고, 환경 구성도 통일 가능

 

MySQL을 컨테이너로 띄우기 위해 Docker compose에 대해 알아보자.

 


 

Docker Compose란?

  • Docker Compose는 여러 개의 Docker 컨테이너(서비스)를 하나의 파일로 정의하고 한 번에 실행할 수 있도록 도와주는 도구이다.
  • docker-compose.yml이라는 설정 파일에 여러 컨테이너의 이미지, 네트워크, 볼륨, 환경 변수, 의존성 등을 선언할 수 있다.
  • 한 번의 명령으로 여러 컨테이너를 함께 올리고 내리고, 전체 환경을 쉽게 관리할 수 있다.

 

언제, 왜 사용하는가?

  • 멀티 컨테이너 환경일 때 (예: 앱 서버 + DB 서버 + 캐시 서버 등)
    여러 컨테이너를 각각 개별적으로 실행하기 번거로울 때
  • 개발 환경 구성 자동화
    팀원 모두 동일한 환경에서 개발할 수 있도록 쉽게 셋업 가능
  • 서비스 간 의존성 관리
    특정 컨테이너가 먼저 실행되어야 할 때 (depends_on 설정 가능)
  • 환경 변수 관리, 네트워크 구성 등 컨테이너 간 통신 설정을 편리하게 하기 위해

 

version: '3.8'

services:
  mysql:
    image: mysql:8.0
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - backend

  business:
    build:
      context: ./business
      dockerfile: Dockerfile
    image: springsecurity/business:1.0
    container_name: business
    ports:
      - "9090:9090"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - auth.server.base-url=http://auth:8080
    networks:
      - backend

  auth:
    build:
      context: ./auth
      dockerfile: Dockerfile
    image: springsecurity/auth:1.0
    container_name: auth
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_URL}
      - SPRING_DATASOURCE_USERNAME=${SPRING_DATASOURCE_USERNAME}
      - SPRING_DATASOURCE_PASSWORD=${SPRING_DATASOURCE_PASSWORD}

    depends_on:
      - mysql
    networks:
      - backend

volumes:
  mysql_data:

networks:
  backend:
    driver: bridge

 

 

1. version: '3.8'

  • Docker Compose 파일 포맷 버전

2. services:

총 3개 컨테이너가 실행됨.

 

  • mysql
    • 로컬 3306 포트를 컨테이너 3306 포트에 바인딩 (MySQL 기본 포트)
    • volumes 설정으로 MySQL 데이터를 영구 보존 (컨테이너 재시작해도 데이터 유지)
    • networks - backend 네트워크에 연결됨.
  • business
    • ./business 디렉토리 안의 Dockerfile을 기반으로 이미지 빌드
    • 로컬의 9090 포트를 컨테이너의 9090에 연결
    • prod 프로파일 활성화
      • 인증 서버 주소를 auth 컨테이너로 지정 (Docker 내부 네트워크 이름 사용)
  • auth 
    • depends_on : mysql 서비스가 먼저 실행되어야 함.

 

이미지를 빌드 하여 실행해보자.

docker-compose build  # 이미지 새로 빌드
docker-compose up --build # 컨테이너 실행 (빌드 포함)

# 참고
docker-compose up -d # 백그라운드 실행

 

로그를 보니 애플리케이션이 잘 실행된 것을 확인할 수 있다.

 

 

오늘은 이렇게 현재 프로젝트의 구조와 MSA로 전환하기 위해 어떤 과정이 필요한지 알아봤고 docker를 적용해보았다.

다음 포스팅에서는 이 프로젝트에 k8s를 적용해보자.

반응형