문제 상황
docker를 사용하는 가장 큰 이유는 무엇일까요?
아마 대부분 외부 환경에 영향 받지 않고 서버를 정상적으로 운영하기 위해서 docker를 사용할 것입니다.
docker를 사용하면 독립적인 container를 띄워서 외부 호스트에서 별도의 환경을 구축할 필요 없이 바로 원하는 서버를 띄울 수 있습니다.
docker를 활용하여 서버를 구축할 때 docker image만 가지고 있으면 환경이 바뀌더라도 항상 동일한 서버 운영을 보장할 때 가장 이상적일 것입니다.
그에 비해 저희 서버는 docker를 제대로 활용하지 못 한다는 느낌을 받았습니다.
아래는 기존 저희 서버의 구조입니다.
중복 패키지를 방지하기 위해 docker volume을 사용하여 backend, websocket 컨테이너에 연결했습니다.
이 방식의 가장 큰 문제점은 container를 실행할 때마다 매번 패키지를 설치하는 과정이 필요하다는 점이었습니다.
volume 방식을 사용하면 volume이 컨테이너 실행 시점에 연결되기 때문에 컨테이너 실행 이후에 node 패키지를 설치해야 합니다.
그리고 패키지 수정을 감지하고 반영하기 위해 컨테이너를 실행할 때마다 yarn install 명령어를 수행해야 합니다.
자연스럽게 docker의 격리된 환경을 제대로 활용하지 못 한다는 느낌을 많이 받았습니다.
이 방식의 단점은 다음과 같습니다.
- 컨테이너 실행마다 매번 yarn install을 진행하기 때문에 패키지의 변경 사항이 있을 경우 배포에 시간이 많이 소요됩니다.
- docker container에서 외부 네트워크에 연결할 수 있어야 합니다.
- container가 실행만 하면 문제가 없을 것이라고 보장할 수 없습니다.
그렇다면 volume 방식이 아닌 bind mount 방식을 사용하면 어떨까요?
원격 서버에서 패키지를 직접 설치하기 때문에 container를 실행할 때마다 패키지를 설치할 필요도 없고 docker container가 네트워크에 접속하지 않아도 됩니다.
하지만 이 방식 또한 다음과 같은 단점이 있습니다.
- 원격 서버에서 패키지를 쉽게 변경할 수 있기 때문에 container 실행 도중 패키지 관련 이슈가 발생할 수 있습니다.
- 로컬 환경과 원격 환경의 패키지 매니저 버전이 다르다면 패키지 설치 문제가 발생할 수 있습니다.
즉 원격 서버 환경의 영향을 많이 받게 됩니다.
해결 과정
그래서 저희는 다음과 같은 목표를 세웠습니다.
- volume, bind mount를 최소화한다.
- 이미지만 가지고 있어도 서버를 구축할 수 있는 환경을 목표로 한다.
우선 서버 이미지는 이전 포스팅에서 docker volume image를 최적화 하면서 volume을 없애고 패키지만 가지고 있는 이미지를 base로 backend, websocket 이미지를 만들면서 1, 2번 목표를 달성했습니다.
https://growth-coder.tistory.com/320
다음은 프론트 정적 파일을 제공하는 nginx 구조입니다.
기존 환경은 원격 서버에 직접 프론트 패키지를 설치한 뒤 빌드하여 정적 파일을 bind mount 방식을 사용하여 nginx에게 제공해주고 있었습니다.
이 방식도 마찬가지로 호스트에서 쉽게 정적 파일에 접근할 수 있어서 container가 항상 정상적으로 동작한다는 것을 보장할 수 없습니다.
그래서 nginx 이미지를 빌드할 때 리액트 정적 파일을 직접 넣어주는 방식을 적용하여 bind mount를 제거하고 container가 외부 환경에 영향을 받을 일이 거의 없어졌습니다.
이렇게 최대한 mount를 제거했지만 모두 제거할 수 있었던 것은 아니었습니다.
서버 환경 변수 파일의 경우 bind mount 방식을 유지하기로 결정했습니다.
우선 프론트 환경 변수의 경우 빌드할 때 포함해야 하기 때문에 bind mount를 사용할 필요가 없었지만 서버는 달랐습니다.
nest의 빌드 과정은 react와 달리 패키지와 환경 변수도 함께 빌드하지 않습니다.
그래서 빌드된 nest 서버를 실행할 때 node_modules와 환경 변수가 있어야 동적으로 패키지와 환경 변수를 사용할 수 있습니다.
물론 환경 변수 자체를 이미지를 빌드할 때 포함할 수 있겠지만 만약에 이미지가 유출될 경우 환경 변수도 함께 유출되기 때문에 bind mount 방식을 유지하기로 결정했습니다.
결과
가장 먼저 배포 환경을 구축할 때 고려해야 할 사항들이 많이 줄어들었습니다.
기존 구조에서는 배포 환경에 다음과 같은 준비물이 필요했습니다.
- docker와 docker-compose 설치
- nodejs와 yarn 설치하고 로컬 환경과 버전 맞추기
- github에서 프로젝트를 클론한 뒤 프론트 패키지 설치 후 정적 파일 빌드
- .env 파일
- docker-compose의 yml 파일
container 실행에 성공한다고 안심할 수 있는 것도 아니었습니다.
서버 container의 패키지 설치 과정에 이상이 없는지… 실수로 container에 mount된 파일을 수정했는지…
신경써야 할 것들이 너무 많았습니다.
하지만 새로 바뀐 환경에서는 다음과 같은 준비물이 필요합니다.
- docker와 docker-compose 설치
- .env 파일
- docker-compose의 yml 파일
원격 환경에서 준비해야 할 것들이 많이 줄어들었습니다.
원격 환경의 준비물이 줄어들었기 때문에 자동 배포를 구축하기도 쉬웠습니다.
각 이미지를 빌드한 뒤 docker hub에 업로드하고 원격 환경에서는 이미지를 받아 docker-compose로 container를 띄우기만 하면 됩니다.
운영 환경도 크게 개선 되었습니다.
container 실행에 성공하기만 하면 .env 파일이 변경되지 않는 이상 외부 환경이 container 실행에 영향을 줄 상황 자체가 거의 발생하지 않았습니다.
즉 배포 환경 구축과 운영 환경 측면에서 크게 개선되었다는 것을 느낄 수 있었습니다.
댓글