Skip to main content

Command Palette

Search for a command to run...

Docker로 다중 컨테이너 애플리케이션 구축하기

Updated
3 min read
Docker로 다중 컨테이너 애플리케이션 구축하기

Docker로 다중 컨테이너 애플리케이션 구축하기

여기서는 프론트<->백<->DB 컨테이너의 상호작용과 configuration에 대해 설명한다. 실무와는 괴리가 있겠지만

세 컨테이너를 구축할 것인데 이들이 갖춰야 할 조건은 다음과 같다:

DB:

  • 데이터가 유지되어야 하고

  • Access제한을 걸어놔야한다. 백엔드:

  • 데이터가 유지되어야 하고

  • 실시간 코드 업데이트가 반영되도록 할 것이다. 프론트:

  • 실시간 코드 업데이트가 반영되도록 한다.

따라서 리액트 프론트, 노드 백엔드, 몽고DB가 있는 상황에서 이들을 도커화하여 연결해보는 작업에 대한 내용을 작성할 것이다.


백엔드, 프론트, DB 컨테이너 통신

Mongo DB 도커화

현재 백엔드가 도커화되지 않은 상황에서

mongoose.connect(
  'mongodb://localhost:27017/course-goals',
  {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  },

DB는 컨테이너로 돌리고자 할 때 위 코드를 유지하면서도 돌아가게 하려면 포트 27017을 노출해야 한다.

이전에 도커허브에 몽고DB 이미지가 존재한다고 하였다. 따라서 몽고DB이미지를 가지고 와서

docker run --name mongodb --rm -d -p 27017:27017 mongo                                                                                         ─╯
1ba11b5201cac261ecc24481913d650f67168fe4c1c228ea021f07f9557fe891

로 DB가 로컬에서 돌고 있지 않아도 로컬 백엔드와 통신할 수 있다.

Node 백엔드 도커화

아까 컨테이너의 몽고DB와 호스트 머신의 백엔드는 통신 가능했는데, 제대로 도커파일을 작성해도 노드 코드 그 상태로는 연결에 실패한다.

노드도 컨테이너에서 돌고 있는데 몽고DB는 호스트 머신의 백엔드와 통신하고 있기 때문이다.

이를

mongoose.connect(
  'mongodb://host.docker.internal:27017/course-goals',
  {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  },

로 백엔드와 몽고DB가 다시 통신이 가능해진다.

이미지를 다시 빌드하고 프론트와 통신하는 포트를 노출하여 돌려주면

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

프론트와 정상적으로 통신 가능해진다.

프론트 도커화

프론트도 마찬가지로 도커파일을 작성하여 이미지를 빌드하고 컨테이너를 돌리면 되는데,

다만 리액트 컨테이너를 실행할 때 주의점이 있는데

docker run-t 옵션을 붙이지 않는 경우 실행 후 바로 프로그램이 죽어버릴 수 있다.

❯ docker run --name goals-frontend --rm -p 3000:3000  goals-react                                                                                ─╯

> frontend@0.1.0 start
> react-scripts start

ℹ 「wds」: Project is running at http://172.17.0.3/
ℹ 「wds」: webpack output is served from
ℹ 「wds」: Content not from webpack is served from /app/public
ℹ 「wds」: 404s will fallback to /
Starting the development server...

기본적으로 터미널 환경을 가정하고 돌아가서, 표준입력이 닫혔다고 간주하고 그대로 프로세스가 죽는 것으로 추측할 수 있다.

따라서 이런식으로

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

-t 옵션을 붙여주면

Compiled successfully!

You can now view frontend in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://172.17.0.3:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

정상적으로 작동함이 확인 가능하다.


도커 네트워크로 효율적인 통신 추가하기

이렇게 하나의 서비스를 각각 DB, 프론트, 벡엔드의 개별 컨테이너로 생성하였는데, 위에서는 호스트 머신의 로컬을 이용하여 세 컨테이너가 통신하고 있지만 다른 방법이 있다.

이전에 도커 네트워크를 쓰면 다중 컨테이너끼리 통신할 수 있도록 설정 가능하다고 했었다. 따라서 이러한 경우 네트워크를 활용해볼 수 있다.

네트워크를 사용하면 더 이상 포트를 노출해줄 필요가 없다.

우선 몽고DB를 네트워크 아래에서 돌리고

 docker run --name mongodb --rm -d --network goals-net mongo                                                                                    ─╯
1626781cb852a12610a229756544cd61e6fba623e64ac8a42629b3ed845cbe8d

다음으로 백엔드와 프론트엔드를 돌릴 차례인데

백엔드를 도커화하면서 호스트 머신의 로컬에 접근 가능하도록 host.docker.internal을 작성해주었지만 DB가 네트워크로 통신하므로 해당 부분을 DB 컨테이너의 이름으로 바꿔줘야한다.

mongoose.connect(
  'mongodb://mongodb:27017/course-goals',
  {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  },

와 같이 될 수 있겠다.

프론트엔드의 경우에도 코드를 수정해줄 필요가 있는데 이제 네트워크 아래에서 백엔드 컨테이너로 통신하면 되므로 localhost 대신 백엔드 컨테이너의 이름을 적어주게 되면

...

정상적으로 작동하지 않을 것이다.

프론트엔드 코드는 컨테이너만으로 작동하는 게 아니라 브라우저 위에서 돌게 되는데 프론트엔드 코드를 실행할 브라우저는 백엔드 컨테이너에 대한 정보가 없기 때문이다. 따라서 localhost는 그대로 놔두어야 하고, 네트워크에서 실행할 필요도 없다.

그러나 백엔드와도 통신해야하기 때문에 백엔드 컨테이너는

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

프론트와의 통신을 위한 80번 포트를 열어놔야한다.


DB에 데이터 지속성 추가하기

이제 DB, 백엔드, 프론트엔드 컨테이너들이 통신할 수 있게 되었으나 처음에 말한 부분들은 아직 반영되지 않았다. 데이터 지속성과 실시간 코드 업데이트에 대한 부분이다.

우선 데이터 지속성은 볼륨을 사용함으로써 가능하다.

그런데 여기서 좀 더 나아가 인증을 추가해볼 수 있다.

docker run --name mongodb -v data:/datadb --rm --network goals-net -e MONGO_INITDB_ROOT_USERNAME=max -e MONGO_INITDB_ROOT_PASSWORD=secret mongo

이런식으로 루트 유저네임과 패스워드를 추가해서 실행한다면 기존의 백엔드 코드로는 접근이 되지 않는다.


mongoose.connect(
  'mongodb://max:secret@mongodb:27017/course-goals?authSource=admin',
  {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  },

유저네임과 패스워드, authSource=admin을 추가해주게 되면 다시 DB에 연결 가능해진다.

4 views

More from this blog

락프리 데이터 구조와 알고리즘

여기서는 락프리 데이터 구조를 설명한다. 락프리(lock-free) 란 배타락을 이용하지 않고 처리를 수행하는 데이터 구조 및 그에 대한 조작 알고리즘을 총칭한다. 왜 락프리인가? 전통적인 동시성 제어 방법인 뮤텍스나 세마포어는 여러 문제점을 가지고 있다: 성능 저하: 락 경합(lock contention)으로 인한 대기 시간 데드락: 여러 스레드가 서로의 락을 기다리는 상황 우선순위 역전: 낮은 우선순위 스레드가 높은 우선순위 스레드를 ...

Jul 27, 20257 min read126

소프트웨어 트랜잭셔널 메모리

소프트웨어 트랜잭셔널 메모리 동시성 프로그래밍에서 공유 자원에 대한 안전한 접근은 항상 중요한 과제다. 전통적으로 뮤텍스 락과 같은 비관적 락(Negative Lock) 방식을 사용해왔다. 이 방식은 크리티컬 섹션에 진입하기 전에 반드시 락을 획득해야 하며, 락을 얻지 못하면 코드 실행 자체가 블록된다. 하지만 이와는 다른 접근 방식이 있다. 바로 낙관적 락(Optimistic Lock) 방식인데, 이는 "일단 실행하고 나중에 검증하자"는 철학...

Jul 20, 202517 min read263

공평한 배타 제어

공평한 배타 제어 여기서는 공평한 배타 제어에 대해 설명한다. 먼저 컨텐션(contention) 이라는 개념을 이해할 필요가 있다. 컨텐션이란 여러 스레드가 동시에 같은 락을 획득하려고 경쟁하는 상황을 말한다. 컨텐션이 높을수록 스레드들이 락을 기다리는 시간이 길어지고 성능이 저하된다. 이러한 컨텐션 상황은 시스템 아키텍처에 따라 더욱 복잡해질 수 있다. 특히 비균일 메모리 접근(Non-Uniform Memory Access, NUMA) 와 같...

Jul 13, 20259 min read21

KernelSnitch[논문 리뷰]

Paper 1. Intro 이 글은 NDSS 2025에서 발표된 KernelSnitch 논문을 소개이다. 이 연구는 커널의 평범한 데이터 구조체들이 가진 본질적인 특성이 어떻게 심각한 보안 취약점이 되는지를 보여준다. 핵심은 이러하다: "데이터 구조체의 크기에 따른 접근 시간 차이를 이용해 커널의 비밀 정보를 유출할 수 있다" 여기서는 커널 힙 포인터 유출에 집중해서 설명한다. 이 공격이 성공하면 KASLR을 우회하고 더 심각한 커널 익스플로...

Jul 11, 20257 min read131

멀티태스크와 액터 모델

멀티태스크 협조적/비협조적 멀티태스크 선점: 프로세스와의 협조 없이 수행하는 컨택스트 스위칭이라고는 하나, 결국 뺏어오는 게 가능하냐의 문제다. 협조적 멀티태스크(비선점형, cooperative): 각각의 프로세스가 자발적으로 컨택스트 스위칭을 수행하는 멀티태스크 방식. 장점: 멀티태스크 매커니즘을 구현하기 쉽다. 단점: 프로세스가 자발적으로 컨텍스트 스위칭을 해야하는데, 만약 버그가 발생하여 프로세스가 무한 루프에 빠지거나 정지하게 되면 그 ...

Jul 6, 20252 min read25
M

MaxLog

35 posts