최종프로젝트 트러블슈팅 - AWS EC2에서 Spring boot 배포시 문제

노재원·2024년 9월 12일
0

내일배움캠프

목록 보기
90/90

Spring에 서버 배포하며 삽질하기

최종프로젝트 당시에 Route53에서 도메인을 구입하고 VPC, ELB등의 설정은 다른 팀원분이 다 해주셨고 이제 EC2 인스턴스 내에서 서버를 실행하는 일만 남았었다.

그 과정에 대해서 정말 무작정 일정에 맞춰 되는지부터 체크하는 데에만 급급했기 때문에 Spring을 EC2에 배포하려면 다른 방식이 있었는지, 차이점이 있는지 등에 대해 정리해보고자 한다.

외부 리소스의 경로 문제

이 때는 아직 CI/CD가 설정되어있지 않아 단순히 인스턴스를 시작하고 Git을 설치해서 Clone 받은 다음 Ignore 처리되어 있던 환경변수 파일들도 직접 손수 넣어주는 불편한 방식으로 일단 배포된 도메인으로 API 서버 접속이 되는가 부터 체크하려고 했다.

Gradle build를 통해 jar 파일을 생성한 다음 Nginx도 사용하지 않고 nohup java -jar ~~~.jar & 명령어를 통해 해당 인스턴스의 백그라운드에 스프링 서버를 실행시키는 것이 목표였다.

하지만 나는 BeanInstantiationException 을 마주해야만 했다.

내가 이해하기론 Gradle build를 진행하면 환경변수도 문제 없이 빌드에 포함이 되는데 왜 문제가 발생했을까를 곰곰히 고민하다가 내가 와인 추천 로직에 실컷 써먹은 json 파일입출력을 통한 재사용에 이슈가 있음을 깨달았다.

/resources 하위에서 Classpath에 같이 포함되어 빌드되는 YAML 파일들과는 달리 그냥 /data 에서 존재하던 내 json 파일은 빌드에 포함되지 않았고 인스턴스 실행에서 내 json 파일을 읽던 Component는 파일을 찾지 못해 에러가 발생한 것이다.

빌드에 포함되게 /resources 에 포함해서 빌드하는 방법을 가장 먼저 떠올렸지만 이렇게 하면 단순히 io.File 을 사용하던 내 Component의 구조가 InputStream, OutputStream 을 사용하거나 ResourceLoader를 사용하게 전부 변경해야해서 생각보다 리팩토링할 게 많았었다. File 객체로 읽기 / 쓰기를 전부 처리했기 때문에 Resource를 읽으면 이후 파일의 입출력이 의도한 대로 수행되지 않았다.

결국 근본적인 해결책은 써먹지 못하고 ./gradlew bootRun 을 통해 빌드된 파일이 아닌 프로젝트 자체를 실행하거나 빌드된 jar 파일이 찾는 상대 경로에 맞게 json 파일을 위치시켜주면 nohup java -jar 명령어가 문제 없이 성공했었다.

리소스를 리소스처럼 다루지 않고 파일입출력을 이용한 결과였고 안드로이드 하던 시절에는 패키징에 문제 없게 항상 resources로 잘 챙겨놓고 이번에 무지성으로 /data 에 집어넣고 개발을 진행했던 것이 패착이었다.

그리고 json 파일을 통한 파일입출력은 스케일아웃 환경에서 확장성이 좋지 않아 애초부터 중앙 저장소를 따로 두는게 좋았을 것으로 보인다. 여러모로 로컬에서만 고려된 기능이었다.

Nginx의 장점

위에서 nohup java -jar 명령어로 빌드된 jar을 바로 실행했다고 했는데, 사실 강의에서는 Nginx를 거쳐가는 방식을 설명했기 때문에 이 차이점에 대해서도 아무래도 짚고 넘어가기로 했다.

처음엔 같이 진행해주신 팀원분이 블로그 레퍼런스를 읽고 최대한 빠르게 실행시키려고 하셔서 별도 설정이 필요 없는 방식을 취하셨다고 한다.

Reverse Proxy

우선 Nginx의 가장 큰 이유인 Reverse Proxy 가 있다.

한줄로 설명하면 클라이언트의 요청을 중간에서 다시 전달해주는 것을 의미하는데, Spring boot 톰캣의 기본 설정인 8080 포트를 가장 보편적으로 사용하는 HTTP 포트인 80으로 바꿔서 실행하기 보다는

Nginx를 80 포트로 열고 톰캣을 그대로 8080으로 둬서 전달하는 것이 내 Spring boot 서버가 영향을 가장 덜 받기 때문이라고 생각한다.

그래서 Nginx를 실행하고 proxy_passhttp://localhost:8080 로 설정하면 내 로컬에서 테스트하듯 인스턴스에 로컬로 실행된 서버로 전달해준다고 생각하면 된다.

server {
    listen 80;
    server_name api.sober-wachu.com;

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

실제로 우리 EC2는 일단 ELB 설정이 되어있어 ELB는 HTTPS에 대해서만 요청을 열어두고 EC2는 ELB에게 HTTP만 열어뒀기 때문에 외부 요청은 HTTPS로 안전하게 처리하고 내부 요청은 HTTP로 빠르게 주고 받는 방식을 취했다.

그런데 nohup java -jar로 실행한 서버와 API 통신이 원활했던 것으로 보면 아마 EC2 보안 설정에서 8080 포트에 대해서도 열려있던 것 같다. (현재는 요금때문에 모든 설정을 삭제해서 조회가 안된다.)

정적 컨텐츠 제공

두 번째 장점은 정적 콘텐츠 제공인데, 이 쪽은 사실 써보진 않았지만 쓸 일이 있을 수 있다.

api.sober-wachu.com 처럼 api 서브 도메인이었는데 이 서브 도메인에서 제공하는 이미지, HTML같은 정적 파일이 따로 존재했더라면 이걸 내 Spring boot 서버가 그대로 제공할 경우 기본적인 API의 통신 속도도 부하로 인해 저하될 수 있기 때문에 Nginx가 별도로 처리를 맡아준다고 생각할 수 있다.

보안

세 번째 장점은 보안이라는데 이쪽에 관련해선 레퍼런스를 읽어보기론 통신이 다시 한번 Nginx를 거쳐야 하기 때문에 물리적으로 분리되어 Spring boot 서버의 노출도가 되지 않아 혹시 모를 공격에 대비할 수 있다고 한다. 특히 특정 IP 차단, DDos 방어까지도 설정이 가능하다고 한다.

추가로 SSL에 대한 설정은 Route53과 ELB를 통해 처리하고 있어 Nginx에 설정할 필요 없었지만 만약 바로 서버를 인스턴스에서 외부에 바로 열어야 하는 상황이었다면 Nginx에서 SSL 처리를 담당해 바로 HTTPS 통신을 받을 수도 있을 것이다.

0개의 댓글