DevOps/Docker

[Docker] 다중 컨테이너 구성

샤아이인 2022. 9. 28.

 

 

이번에는 MongoDB, NodeJS Server, React를 각각 컨테이너로 만든 후, 이들이 서로 소통하여 진행되도록 만들어 봅시다!

 

1. 컨테이너화 시키기

1 - 1) MongoDB 컨테이너화

docker run --name mongodb --rm -d -p 27017:27017 mongo

"mongodb"라는 이름으로 컨테이너를 생성하며, 컨테이너 종료시 자동 소멸되고, port는 27017로 개발하였다.

 

1 - 2) Node.js 컨테이너화

우선 우리가 만든 간단한 애플리케이션을 Dockerfile을 통해서 이미지로 build 합시다.

FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

EXPOSE 80

CMD ["node", "app.js"]

이후 "docker build -t goals-node . "를 통해 이미지를 빌드합니다.

 

이렇게 생성된 이미지를 기반으로 새로운 컨테이너를 생성해 봅시다!

docker run --name goals-backend --rm -d -p 80:80 goals-node

이때 애플리케이션에서 주의할 점이 있는데, 애플리케이션에서 DB에 접근하는 url로 localhost를 주면 안 됩니다!

 

다음과 같이 host.docker.internal을 사용해야 함을 알 수 있습니다.

'mongodb://host.docker.internal:27017/course-goals'

 

1 - 3) React 컨테이너화

React도 이전과 동일하게 Dockerfile을 만들어 이미지를 우선 만듭시다.

FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

EXPOSE 3000

CMD [ "npm", "start" ]

 

이를 통해 이전과 같이 이미지를 빌드합시다.

docker build -t goals-react .

 

이미지를 기반으로 컨테이너를 실행합시다!

docker run --name goals-frontend --rm -d -p 3000:3000 -it goals-react

위 cmd에서 -it 가 필수적으로 추가되어야 React가 상호작용이 가능합니다.

 

이렇게 실행된 컨테이너의 목록을 출력해보면 다음과 같습니다.

우리의 애플리케이션 또한 성공적으로 작동합니다.

 

다만, 지금 우리는 컨테이너를 실행할 때 port를 전부 명시해주었습니다.

하지만 지난번 글에서 배웠듯, 네트워크 그룹을 사용하면 컨테이너의 이름을 명시하여 통신할 수가 있습니다.

이러한 방식으로 변경해봅시다!

 

2. 네트워크를 통한 컨테이너 간 소통

우선 네트워크부터 하나 만들어줍시다!

docker network create goals-net

goals-net이라는 이름으로 네트워크를 하나 구성하였습니다.

 

이제 이전에 만들었던 이미지를 통해 컨테이너를 생성할 때 해당 컨테이너들은 goals-net 네트워크 안으로 모을 수 있습니다.

또한 더 이상 port를 명시할 필요가 없습니다.

 

2 - 1) MongoDB 컨테이너화

이전과 달리 "--network"를 통해서 만들어둔 네트워크를 명시하고 있습니다.

docker run --name mongodb --rm -d --network goals-net mongo

 

2 - 2) Node.js 컨테이너화

이번에는 이전과 달리 데이터베이스 Url이 조금 달라집니다.

'mongodb://host.docker.internal:27017/course-goals'

우선 기존 위 url의 "host.docker.internal"은 로컬 컴퓨터 내부에서 인식하도록 합니다.

따라서 네트워크 상에 있는 컨테이너가 로컬 컴퓨터 상에 실실 적으로 설치된 것이 아니기 때문에 인식할 수가 없습니다.

 

이를 다음과 같이 변경해줍시다.

'mongodb://mongodb:27017/course-goals'

위와 같이 네트워크 상의 컨테이너 이름으로 변경하였습니다.

이제 다시 build 한 후, 실행해봅시다!

 

docker run --name goals-backend --rm -d --network goals-net goals-node

 

mongoDB 컨테이너와 동일한 네트워크에 연결된 백엔드 컨테이너가 시작됩니다.

 

docker ps를 통해 확인해보면 정상적으로 2개의 컨테이너가 실행 중입니다.

 

2 - 3) React 컨테이너화

react 또한 이전 node와 유사합니다. 우선 코드 일부분을 수정해봅시다!

 

다음과 같이 기존에 "localhost"로 명시돼 있던 부분을 전부 Node 컨테이너의 이름(goals-backend)으로 변경해주어야 합니다.

 

이는 동일한 네트워크에서 컨테이너를 실행하는 경우, 도커에서 Node 백엔드의 컨테이너 이름 부분을 컨테이너의 IP주소로 변환할 수 있기 때문입니다.

 

이제 이전과 마찬가지로 build를 다시 해주면 됩니다.

 

이후 새롭게 빌드된 이미지를 기반으로 컨테이너를 실행해줍시다!

docker run --name goals-frontend --rm --network goals-net -p 3000:3000 -it goals-react

다만 이번에도 port를 명시해줬는데, 이는 웹브라우저를 통해 우리가 눈으로 직접 확인해보기 위함입니다.

port를 열어두지 않으면 우리가 chrome과 같은 브라우저를 통해 확인할수가 없겠죠?

 

하지만, 이후 접속해보면 문제가 발생합니다...

 

이는 사실 React 파일이 브라우져 상에서 실행됐기 때문입니다.

React 애플리케이션은 컨테이너에서 실행되는 것 이 아닙니다, 브라우저에 js 파일이 전달되어 시각적으로 보이는 것입니다.

 

위 경고 로그를 보면, goals-backend/goals를 인식할 수 없다고 나옵니다.

이는 당연한 것이 네트워크에 있는 컨테이너 내부에서 수행된 것이 아니기 때문입니다.

즉, "goals-backend"부분이 도커에 의해서 IP주소로 변환되지 못한 것입니다.

컨테이너 내부가 아닌, 브라우저 상이라 브라우저는 "goals-backend"이 무엇인지 알 수가 없습니다.

 

이를 해결하기 위해 우선 브라우저가 이해할 수 있는 "localhost"로 다시 변경해줍시다!

이후 다시 빌드하고,

 

다시 run을 할 때 네트워크를 지정할 필요가 없습니다.

docker run --name goals-frontend --rm -p 3000:3000 -it goals-react

 

마지막으로 node 서버를 중지한 후, 80 포트를 열면서 재실행하면 됩니다.

우리의 브라우저가 React 애플리케이션을 통해서 컨테이너의 Node 서버와 통신하기 위함입니다.

docker run --name goals-backend --rm -d -p 80:80 --network goals-net goals-node

이전과 다 동일한데, 포트만 명시해주었습니다.

 

최종적으로 3개의 컨테이너 모두 정상 작동하게 되었습니다.

 

3. MongoDB 데이터 지속

이번에는 MongoDB에 저장되는 데이터가 지속되는지 확인해보고, 추가로 권한 설정까지 해봅시다!

 

3 - 1) Volume 설정

이전 MongoDB 컨테이너는 --rm 옵션을 줬기 때문에 stop으로 정지시키면 자동으로 컨테이너가 삭제됩니다.

따라서 저장된 데이터 또한 사라지게 되죠.

 

따라서 저장된 데이터 부분만 분리하여 우리의 Host 머신에 저장되어야 합니다.

따라서 named volume을 사용해봅시다!

docker run --name mongodb -v data:/data/db --rm -d --network goals-net mongo

data라는 이름의 볼륨을 만들며, 컨테이너 내부 "/data/db 경로상에 저장하게 됩니다.

실질적인 저장은 Host 컴퓨터 어딘가의 경로상에 저장되게 됩니다.

 

3 - 2) 인증 설정

mongoDB는 위와 같은 환경변수 2가지를 기본적으로 제공합니다.

각각 username, password에 해당되는데, 이는 컨테이너를 생성할 때 인자로 전달되어 데이터베이스의 기본적인 ID와 password가 됩니다.

 

$ docker run -d --network some-network --name some-mongo \
	-e MONGO_INITDB_ROOT_USERNAME=mongoadmin \
	-e MONGO_INITDB_ROOT_PASSWORD=secret \
	mongo

$ docker run -it --rm --network some-network mongo \
	mongosh --host some-mongo \
		-u mongoadmin \
		-p secret \
		--authenticationDatabase admin \
		some-db
        
> db.getName();
some-db

 

예를 들어 다음과 같이 설정해봅시다.

docker run --name mongodb -v data:/data/db --rm -d --network goals-net -e MONGO_INITDB_ROOT_USERNAME=shine -e MONGO_INITDB_ROOT_PASSWORD=1234 mongo

이렇게 mongoDB를 실행하면 이전의 Node 서버가 DB에 접근할 수가 없습니다.

인증이 없기 때문입니다.

 

따라서 서버에서 DB에 접근하는 url 부분을 다음과 같이 수정해주어야 합니다.

'mongodb://root:1234@mongodb:27017/course-goals?authSource=admin'

성공적으로 수행되는 것을 확인할 수 있습니다.

 

3 - 3) Bind Mount

1. 컨테이너 내부에서 Node 서버가 작성하는 log 파일을 저장하기 => Volume으로 해결

 

우선 바인드 마운트와 볼륨을 둘 다 사용해봅시다.

docker run 
--name goals-backend 
--rm 
-p 80:80 
-v /Users/shine/study/docker-study/multi-01-starting-setup/backend:/app 
-v logs:/app/logs 
-v /app/node_modules 
-d 
--network goals-net 
goals-node

바인드 마운트를 통해 "/Users/shine/study/docker-study/multi-01-starting-setup/backend" 를 컨테이너 내부의 "/app"과 마운트 하였습니다.

따라서 로컬 "/Users/shine/study/docker-study/multi-01-starting-setup/backend" 상에서 소스코드를 수정하면 실시간으로 컨테이너 내부에도 반영됩니다.

 

이후 named volume을 통해 logs라는 이름을 갖는 볼륨을 컨테이너 내부의 "/app/logs"에 생성합니다.

이를 통해 컨테이어 내부의 로그기록을 저장할 수가 있습니다.

 

마지막으로 파일들이 COPY 될 때 /app/node_modules 은 덮어씌워지면 안되기 때문에 익명 볼륨을 통해 보호하였습니다.

잠시 Dockerfile을 살펴봅시다.

FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

EXPOSE 80

CMD ["npm", "start"]

위에서 순서대로 보면 (COPY package.json .) -> (RUM npm install)이 진행됩니다.

이때 모든 종속성이 설치되며, 컨테이너 내부에 node_modules라는 종속성 관련 파일이 생성됩니다.

 

문제는 이후 COPY . . 을 통해 로컬의 모든 파일과 폴더를 컨테이너 내부의 /app으로 복사하기 때문에 문제가 발생합니다.

오래됐을 수 있는 로컬의 node_modules로 방금 새롭게 설치한 컨테이너의 node_modules을 덮어씌워버리는 문제가 발생한 것 이죠!

 

따라서 익명 볼륨을 통해 보호한 것입니다.


2. 실시간으로 소스 코드의 업데이트 사항을 반영하기 => Bind Mount로 해결

우리가 바운드 마운트를 적용한 이유는 로컬상에서 변경한 소스코드를 바로 반영하고 싶기 때문입니다.

하지만 위 코드상으로는 실행 중인 서버의 코드를 변경할 수가 없습니다.

 

따라서 다음과 같이 package.json에 추가적인 의존성을 설정해줍니다.

nodemon은 소스코드가 변경될 경우, 서버를 재시작해주는 역할을 담당합니다.

 

또한 다음과 같이 scripts에 start를 추가해줍시다.

따라서 프로젝트 폴더의 JavaScript 파일에 변경사항이 있을 때마다 노드 서버를 다시 시작합니다. 

 

Dockerfile은 다음과 같이 만듭시다.

FROM node

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

EXPOSE 80

CMD ["npm", "start"]

이를 통해 로컬상에서 코드를 변경해도 바로바로 적용되게 되었습니다!

'DevOps > Docker' 카테고리의 다른 글

[Docker] 유틸리티 컨테이너  (0) 2022.10.01
[Docker] 도커 컴포즈 (Docker Compose)  (0) 2022.09.29
[Docker] 컨테이너 통신  (0) 2022.09.18
[Docker] 도커의 환경변수 설정  (1) 2022.09.15
[Docker] Volumes과 Bind Mounts  (0) 2022.09.14

댓글