Local dev 환경을 구축하며

hynnch2·2022년 5월 19일
0

현재 진행하고 있는 프로젝트에서 AWS ECR, ELB와 같은 시스템을 이용해서 배포중인데, 특별히 개발이 진행되고 있지 않아도 서버가 계속 켜져 있기 때문에 AWS 비용 부담이 조금 있었습니다.

인프라에 관심이 있고 어느정도 용돈처럼 생각하고 지출하고 있었지만, 아직 개발 단계인데 서버를 계속 켜두는 것은 돈이 아까웠기 때문에, Frontend에서 사용할 수 있는 "Local Backend Server"를 만들어서 전달하기로 했습니다.


현재 백엔드 서버에서 사용하는 자원은 다음과 같았습니다.

  1. Spring Boot
  2. AWS RDS
  3. AWS S3
  4. MongoDB

이 같은 서버를 로컬에서 사용 할 수 있는 독립적인 테스트 공간을 만들기를 원했고, Docker를 통해 컨테이너로 각 프론트엔드에서 독립적으로 사용할 수 있는 서버를 구축하면 되겠다고 생각했습니다.

현재 서버는 AWS ECS에서 돌아가고 있으며, Spring Boot는 Image를 만들어서 push하는 CI/CD가 구성되어 있기 때문에 그대로 가져다 쓰면 되지만, RDS, S3, MongoDB같은 경우는 공식 image를 가져와서 구성해야 했습니다.


Docker-Compose를 사용하자.

먼저 생각이 들었던 방법은 현재 사용하고 있는 모듈들을 각 image로 구성한다음 Docker-Compose를 통해 한번에 제어하면 된다고 생각했습니다. 그럼 각 image마다 생성하고 파괴하는 절차를 가지지 않아도 docker-compose up 명령어를 통해 백엔드에 필요한 모든 모듈을 쉽게 사용할 것이라 생각했습니다.

  1. Spring Boot
  2. AWS RDS (MySQL)
  3. MongoDB
  4. AWS S3

1. Spring Boot

먼저 Spring Boot는 실제 서비스가 실행되는 서버입니다. 현재 Github Action을 통해 자동으로 Build된 image가 ECS에 저장되고 있기 때문에, 이를 가져와서 실행하면 됐습니다. 그러나 실제로 배포 환경에서 돌아가는 서버라 많은 환경변수가 문제가 되었습니다.

예를들면, JPA 설정 정보부터 시작해서 민감한 AWS RDS와 같은 정보를 properties에 저장하고 사용하는 방식이었는데, 이러한 환경 변수들을 Local 환경에 맞게 따로 저장해야 했습니다. 또한, AWS S3과 같은 외부 모듈을 설정하는 과정에서 어려움이 있었는데 이는 뒤에 S3 부분에서 다루겠습니다.

Docker-Compose를 작성하면서 "environment" 항목을 통해 환경변수를 최대한 민감하지 않은 정보들로 구성하여 작성했지만, Github OAuth2 Token과 같은 정보는 어쩔 수 없이 env_file을 통해 받는 식으로 하였고, 이를 Swit(워크 플레이스)에 올려서 해결하였습니다.

2. AWS RDS (MySQL)

현재 서버는 RDS에서 MySQL을 빌려서 사용중입니다. 그렇기 때문에, MySQL 공식 image를 가져와서 실행하면 될 것이라고 생각했습니다.

여기서 의문점이 생겼는데, "Container에 Data를 저장하면 영구적으로 저장되는지" 에 대한 궁금증이 생겼습니다. Volume을 통해 데이터를 영구적으로 저장하면 되지만, 이를 하지 않으면 어떻게 되는지 궁금했습니다.

결론부터 말하면 "docker-compose down"을 하면 데이터가 날라갑니다. 하지만 실행되고 있는 Docker-Compose를 "ctrl+c"를 통해 중지하면 데이터는 유지됩니다.

이러한 결과가 나오는 이유는 다음과 같습니다.

  1. Docker에서 Image를 실행하면 base image를 실행한다.
  2. base image는 각 Layer로 구성되어 있으며, 이를 통해 저장되어 있던 layer가 순서대로 쌓여서 실행된다.
  3. 기본 layer가 다 실행되면 "container layer"가 실행되고, 우리가 작업한 것은 container layer에 저장된다.
  4. container layer를 중단하면, 해당 데이터는 container layer에 저장되고 있다.
  5. 각 container layer는 독립적인 것이라, base image를 통해 새로운 container를 생성하면 기본 layer를 실행하고 다른 container layer를 그 위에서 실행한다.

즉, 우리가 실행하고 저장한 데이터는 각 container마다 가지고 있는 특별한 SHA값이 있는데, 그러한 정보와 같이 저장되어 독립적인 공간을 가지고 있습니다.
그렇기 때문에 단순히 docker-compose를 중지시키는 명령어를 사용하면 container가 삭제되지 않고 중지된 것이기 때문에 데이터는 유지됩니다.

하지만 "docker-compose down" 명령어를 실행하면 실행되고 있던 container는 모두 삭제가 되고, 데이터는 초기화 됩니다.

이러한 작동 방식때문에 volume 속성을 이용해서 데이터를 영구적으로 저장하도록 작성했습니다.

3. MongoDB

MongoDB는 base image를 가져와서 root로 접속 가능한 URL을 받아오고 Spring Boot에서 해당 URL을 환경변수로 등록하여 사용하도록 작성했습니다.

4. AWS S3

AWS S3의 경우 공식 Docker image가 없기 때문에 곤란했습니다.

이를 해결하기 위한 방법으로 2가지 방식을 고려헀습니다.

  1. linux 컨테이너를 실행한다음 AWS S3에서 수행하는 기능을 구현하여 image로 만드는 방법.
  2. 다른 사람이 구현한 AWS S3 기능을 수행하는 image 사용.

처음에는 1번으로 하여 간단한 API를 작성할까 헀지만, 기능 구현까지 해서 만드는 것은 과도한 일이라고 생각했습니다. 다행히 2번의 경우가 없다면 작성해야 했지만, 이미 저같은 고민을 위해 S3Mock image가 있었습니다.
(Github 링크: s3 mock)

해당 image를 통해 s3 mock 객체를 생성하고, 이를 통해 API 요청을 받는 식으로 구성했습니다.


문제 발생.

위의 방식으로 작성하다가 에러가 발생했습니다.

  1. Docker-Compose파일에서 localhost를 통해 URL을 접근함. -> 에러
  2. S3을 사용할 때 필요한 configuration이 실제 endpoint와 mock을 이용할 때 다르다. -> 에러

위와 같은 에러는 다음과 같이 해결했습니다.

1. Docker-compose에서 localhost를 사용하면 에러가 발생한다.

생각해보면 당연한 결과였습니다. Docker는 격리된 환경에서 각 어플리케이션을 독립적으로 수행하는 공간을 만드는데, 이 때 다른 container에 연결하기 위해 localhost를 사용하면, 독립적인 공간에서 돌기 때문에 찾지 못헀습니다. 또한, docker는 내장 ip가 동적으로 변할 수 있기 때문에 정적 ip를 주는 방법은 좋지 못한 방법입니다.

이를 해결하기 위해 Docker에서는 link라는 속성을 통해 해결하지만, docker-compose에서는 더 쉬운 방법으로 "image name"을 사용하면 해당 docker의 ip로 변환되어 사용되기 때문에 쉽게 연결 할 수 있습니다. (링크)

이를 통해 문제를 해결했습니다.

2. S3 configuration이 달라서 에러가 발생.

실제 prod 환경에서는 AccessKey와 SecretKey와 더불어 많은 환경변수가 있지만, mock의 경우 그보다 적은 단순한 환경설정만을 필요로 했습니다.

이를 해결하기 위해 Bean 생성 과정에서 @Profile을 통해 properties에 있는 내용을 분리하는 방법을 채택하고, spring.profile.active 환경변수를 통해 원하는 Bean을 생성하는 방법으로 해당 문제를 해결했습니다.


이렇게 Docker-compose.yml을 작성하고, 해당 서버를 돌려본 결과 정상적으로 Local을 위한 Backend 서버가 동작하는 것을 확인할 수 있었습니다.

- 코드 링크 -


+ 추가 할 내용이나 부족한 부분이 있다면, 댓글 작성 부탁드립니다! :)

profile
more than yesterday

0개의 댓글