SpringBoot Docker 배포

Jemin·2023년 12월 13일
0

AWS

목록 보기
4/4
post-thumbnail

서론

React 프로젝트를 AWS Amplify로 배포하고 SpringBoot 서버를 로컬에서만 구동하니 다른 팀원들 PC에서는 로그인이 되지 않아 기능이 있는 페이지들을 확인하지 못해서 SpringBoot 프로젝트도 배포하기로 했다.

기존에 MongoDB가 AWS EC2 인스턴스에 도커 컨테이너로 구동 중이어서 같은 인스턴스안의 도커 컨테이너로 배포하기로 했다.

환경변수 문제

SpringBoot에서 사용 중인 데이터베이스에 대한 정보나 엑세스 토큰에 필요한 Secret key를 외부에 노출하고 싶지 않아서 인텔리제이 환경 변수로 설정해뒀는데, IDE에 설정한 경우 로컬에서는 정상동작이 가능하지만 배포를 위해서는 apllication.properties 같은 설정 파일에 직접 작성해주어야 한다.

배포용 설정파일을 추가로 작성하려던 찰나에 팀원분께서 어차피 도커로 배포할 예정이니 도커파일에 환경 변수를 설정하는 것이 가능하다고 말씀해주셨다.

일반적인 Spring Boot 배포 방법은 프로젝트를 빌드하고 JAR 파일을 만들고 배포해서 실행하는 것인데, 도커 컨테이너에 배포하는 경우 JAR 파일을 도커 이미지로 한번 더 빌드 해주어야 한다.

도커 이미지로 빌드하기 위해 Spring Boot 프로젝트 내부에 Dockerfile을 생성해주는데 여기서 환경 변수외에도 여러 설정이 가능하다.

Dockerfile

팀원분께서 현재 프로젝트에 사용중인 도커 파일을 참고용으로 넘겨주셨고 해당 파일을 한줄한줄 이해한 뒤 필요한 설정을 찾아서 작성해주었다.

FROM amazoncorretto:17.0.6
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 9000
ENV AUTHENTICATION_DATABASE={...}\
    DATABASE_NAME={...}\
    DB_HOST={...}\
    DB_PORT={...}\
    DB_USER_NAME={...}\
    DB_USER_PASSWORD={...}\
    EXPIRATION={...}\
    SECRET_KEY={...}
ENTRYPOINT ["java",\
            "-jar",\
            "/app.jar",\
            "-Djava.net.preferIPv4Stack=true",\
            "-XX:+UseContainerSupport",\
            "-Dserver.port=9000",\
            "-Dfile.encoding=UTF-8",\
            "-XX:MinRAMPercentage=85.0",\
            "-XX:MaxRAMPercentage=85.0"\
           ]

위에서 부터 한줄 한줄 이해해보자면

  • FROM

    • Base 이미지 지정한다는 뜻인데, Docker Hub나 Docker 레지스트리에서 기본 이미지를 가져온다. Amazon Corretto JDK 17 버전을 이미지로 사용하겠다고 설정했다.
  • ARG

    • Docker 빌드 시에 전달되는 인수를 정의한다. JAR_FILE이라는 변수로 빌드된 이미지를 해당 경로에 jar파일로 생성하겠다는 뜻이다.
  • COPY

    • 위에서 빌드된 jar파일을 복사해서 이미지 내부의 app.jar라는 파일로 생성한다.
  • EXPOSE

    • 도커 이미지가 사용할 포트를 설정해준다. 실제 컨테이너에 포트를 열어주거나 어떤 영향을 주는 것은 아니고 그냥 다른 사람이 이 컨테이너가 어떤 포트를 사용할 것인지 예상할 수 있도록 만들어주는 것이다.
  • ENV

    • 도커 이미지 내부의 환경 변수를 설정한다. 여기에 인텔리제이에 설정한 환경 변수를 넣어주었다.
  • ENTRYPOINT

    • 도커 이미지가 실행될 때 실행되는 명령어나 스크립트를 지정한다. React에서 package.json의 스크립트를 설정해주는 것과 동일하다.
    • JVM 실행 => JAR 파일 실행 => 실행할 JAR 파일 경로 => JVM 시스템 프로퍼티 IPv4로 설정 => JVM에게 컨테이너 환경을 사용하도록 지시 => 서버 포트 설정 => JVM 인코딩 UTF-8 설정 => 최소 힙메모리 비율 설정 85% => 최대 힙메모리 비율 설정 85% 라고 설정해뒀다.

이제 Spring Boot 프로젝트를 도커 이미지로 빌드하면 된다.

현재 프로젝트는 Gradle이기 때문에 인텔리제이에서 Gradle을 사용해 build => .jar 파일까지 생성해준다. 도커 이미지로 빌드하기 위해서 아래 명령어를 작성해준다.

docker build -t {이미지명} .

뒤에 "."를 적어주는 이유는 도커 이미지를 빌드할 때 현재 디렉토리의 context를 도커 빌드 context로 사용하겠다는 의미다.

Docker Container 구동하기

이제 로컬에서 빌드된 이미지를 허브에 push하고 EC2 인스턴스에 접속해서 허브에 저장된 이미지를 pull 받아서 실행하면 된다.

docker push {이미지명}

docker pull {이미지명}

허브에 저장할 때 주의할 점은 이미지명 앞에 허브 계정과 동일한 이름을 붙여줘야 허브에 정상적으로 접근할 수 있다. kkamjange/image 와 같이 작성해주어야 한다. 본인도 처음에 자꾸 허브에서 access denied 오류가 발생해서 고생 조금 했다.

EC2 인스턴스 내부에 ssh로 접속해서 본인 허브에 로그인하고 pull을 받아서 실행하면 되는데 이때 컨테이너 접근에 대한 포트를 설정해줄 수 있다.

docker run -d -p {외부접근포트}:{컨테이너포트} --name {컨테이너명} {이미지명}

외부에서 즉, EC2에 접근하는 포트와 컨테이너의 포트를 매핑해줄 수 있다. 이렇게 설정해두면 EC2에서 외부접근포트에 연결하면 컨테이너의 포트로 연결할 수 있다.

--name은 실행할 컨테이너의 이름이고 -d는 백그라운드 실행을 뜻한다. 이름은 붙이지 않으면 랜덤으로 컨테이너 이름이 설정된다.

Mixed Content Error

이제 프론트에서 접근하면 되는데 문제가 발생한다. 분명 백엔드는 배포했고 정상적으로 동작하는데, 프론트에서 API를 요청하면 콘솔에 오류가 출력된다.

프론트는 AWS Amplify로 배포했기 때문에 자동으로 SSL 인증이 되어 https가 되는데 백엔드는 아무런 설정도 없기에 http라서 서로 다른 프로토콜에서 주고받기 때문에 Mixed Content 오류가 발생하는 것이다.

이를 해결하기 위해 프론트에서 프록시 서버를 설정해줄까, 백엔드에 도메인을 사용해 호스팅을 해줘야하나 고민하던 차에 동료분께서 본인의 방법을 알려주셨다. AWS의 로드 밸런서를 사용하면 로드 밸런서가 HTTPS 요청을 HTTP로 변경해서 백엔드에 요청을 넘겨줄 수 있다고 하셨다.

AWS 로드 밸런서

로드 밸런서는 보통 많은 요청에 대해서 분산처리용으로 사용해서 조금 과할 수 있는데, 평소 AWS에 대한 욕심이 많아져서 흔쾌히 사용해보기로 했다.

처음에 개념을 이해하는게 쉽지 않았다. 지금도 누군가 자신에게 로드 밸런서에 대해서 설명해보라 하면 정확하게 대답할 수는 없겠지만 어느 정도 기본 틀은 이해했다.

  1. 프론트엔드에서 백엔드에 요청을 보낸다. (프론트엔드 뿐만 아니라 다른 곳에서도 요청을 보낼 수 있다)

  2. 로드 밸런서가 해당 요청을 받아서 분산 처리를 해준다. (이때 받은 요청이 https라면 로드 밸런서에도 SSL 인증이 필요하다)

  3. 로드 밸런서가 여러 서비스를 합친 대상 그룹에 리스너 규칙에 맞춰 요청을 넘겨준다.

현재 사용하는 프로젝트에 맞춘다면 위와 같은 순서로 진행된다. 물론 서버는 하나라 분산처리할 필요는 없다만.. 아래 참고한 링크들로 들어가면 로드 밸런서 사용에 대한 더 자세한 설명을 확인할 수 있다.

대상 그룹

먼저 대상 그룹을 만들어준다. 대상 그룹은 로드 밸런서에서 요청을 넘겨받을 대상들을 모아서 그룹화한 것이다. 대상 그룹을 생성하면서 요청을 받을 포트번호를 지정해 줄 수 있고 프로토콜 버전도 설정해줄 수 있다. 본인은 EC2 인스턴스를 그룹화해야 해서 유형을 인스턴스로 선택했는데, 그 외에도 IP 주소나 Lambda 함수 등을 유형으로 선택할 수 있다.

IP 주소 유형은 EC2 인스턴스에 맞춰서 IPv4를 선택했고, 포트는 도커 컨테이너로 구동 중인 백엔드 포트와 동일하게 설정해주었다.

대상 그룹을 만들면서 설정 중에 상태 검사(Health Checks)가 있는데 이는 로드 밸런서에서 주기적으로 상태 확인을 위해 등록한 주소로 요청을 보낸다. 간단하게 컨트롤러에서 /health 라는 get 요청으로 문자열을 반환해주어도 되는데 로드 밸런싱이 필요하지 않다면 아무것도 안해주고 넘어가도 된다.

만약 로드 밸런싱을 사용하면 상태 검사 설정을 해주어야 로드 밸런서가 서버의 상태를 확인하고 서버가 비정상적이라면 다른 서버로 요청을 넘겨주거나 할 수 있다.

로드 밸런서

이제 로드 밸런서를 만들어주는데 본인은 프로토콜 변환이랑 라우팅만 있으면 돼서 Application Load Balancer로 생성해주었다.

아래 참고 글들을 보고 따라하면서 만들면 되는데 주의할 점은 네트워크 매핑에서 매핑 설정으로 가용 영역을 최소 2개 선택해주는데 본인의 EC2 인스턴스에 설정된 가용 영역을 포함해서 2개 선택해주어야 한다. EC2 인스턴스의 네트워킹 탭에서 가용 영역을 확인할 수 있다.

보안 그룹은 아직 잘 이해가 안가는데 로드 밸런서의 보안 그룹을 일단 인스턴스랑 동일하게 하면 정상적으로 동작한다. 아마 로드 밸런서가 받는 요청에 대한 보안 그룹 설정인 것 같다.

리스너 및 라우팅에서 위에서 만들어준 대상 그룹을 선택하는데 이 부분이 잘 이해가 안간다. 여기서 설정한 포트와 프로토콜로 요청을 받으면 설정한 대상 그룹으로 넘겨주는 것 같다. 프론트에서 HTTPS로 요청하기에 HTTPS:443 으로 설정해주었다.

이때 로드 밸런서에서 HTTPS로 요청을 받으려면 SSL 인증서가 필요한데 본인은 프론트를 호스팅 중인 도메인을 사용해 SSL 인증서를 발급받고 해당 인증서로 로드 밸런서를 HTTPS로 설정해주었다. 메인 도메인으로 로드 밸런서를 라우팅한다면 그냥 인증서를 받으면 되지만 서브 도메인으로 라우팅하면 와일드 카드를 사용해서 SSL 인증서를 발급받아야 한다. (와일드 카드 ex: *.abc.com)

로드 밸런서까지 설정이 끝나면 대상 그룹으로 가서 세부 정보 탭에서 연결이 됐는지 안됐는지 확인할 수 있다. "사용되지 않음"으로 나온다면 연결이 안된 것이고, "정상"이나 "비정상"으로 나와야 한다.

본인은 지금 상태 검사(Health Check)에 대한 설정을 하지 않았기 때문에 상태 검사에서 비정상이라고 나온다. 어쨋든 연결은 된 것이다.

레코드 생성

이제 EC2 인스턴스의 IP 주소로 요청하는 것이 아닌 로드 밸런서로 요청을 해주어야 하는데, 이때 로드 밸런서에 라우팅해줄 도메인이 필요하다. Route53에서 도메인을 선택하고 레코드를 생성한다. 이미 AWS Aplify를 호스팅 중이기 때문에 서브 도메인으로 앞에 api 같은 것을 추가해 주었다.

별칭을 클릭하면 트래픽 라우팅 대상을 선택할 수 있는데, ALB(Application Load Balancer)를 선택하고 리전(서울)을 선택하면 밑에 로드 밸런서를 선택할 수 있는 Select Box가 나온다. 거기서 로드 밸런스를 선택해서 라우팅해줄 수 있다.

이제 프론트엔드에서 생성한 레코드 주소로 요청을 보내면 로드 밸런서를 거쳐서 백엔드까지 정상적으로 접근하는 것을 확인할 수 있을 것이다.

마무리

이 글은 기억이 휘발되기 전에 기록해두기 위한 글이고 다른 사람들이 봐도 딱히 도움이 되지는 않을 것 같다. 아마 아래의 참고 글들을 따라가는 것이 더 좋다.

프론트엔드 개발자여서 처음 Spring Boot를 배포하는 것이 많이 어려웠는데 옆에서 많은 도움을 주신 동료, 팀원들에게 감사하다.

참고
AWS Application Load Balancer 쉽게 이해하기 #2
ELB(Elastic Load Balancer) 구성 & 사용법 가이드
출처: https://inpa.tistory.com/entry/AWS-📚-ELB-Elastic-Load-Balancer-개념-원리-구축-세팅-CLB-ALB-NLB-GLB#albapplicationload_balancer생성하기 [Inpa Dev 👨‍💻:티스토리]

profile
경험은 일어난 무엇이 아니라, 그 일어난 일로 무엇을 하느냐이다.

0개의 댓글