-
Notifications
You must be signed in to change notification settings - Fork 2
Docker compose를 이용해서 메모리 사용률을 줄여보자
현재 우리 서비스에서 사용하고 있는 인스턴스는 1대이고, 해당 인스턴스에서 Api, Media, Chat, Record 서버 등을 실행시키고 있다. Api 서버와 Chat 서버를 실행 시킬 때는 메모리 사용량에 큰 변화가 없지만, Media, Record 서버를 실행시키면 메모리 사용량이 60퍼센트까지 치솟고 있다. 이렇게 되면 메모리 사용량이 서버의 한계에 가까워져 여러 가지 문제가 발생할 수 있다.
- 메모리가 60% 이상 지속적으로 사용될 경우, 시스템 여유 자원이 부족해지고, 프로세스가 제대로 동작하지 않을 가능성이 높아짐.
- Media 서버와 Record 서버는 실시간 데이터 처리를 요구하며, 메모리와 CPU 사용량이 높음. 메모리 부족 상황에서 응답 속도가 느려지거나 서버가 중단될 위험이 있음
sudo docker run -d --name media-camon \\
-p 3001:3001 \\
-p 30000-31000:30000-31000 \\
sudo docker run -d --name record-camon \\
-p 3003:3003 \\
-p 10000-10300:10000-10300/udp ${{ secrets.NCLOUD_REGISTRY_URL }}/record-camon:latest
위 코드는 우리 서비스의 git action script의 일부로, 각각 미디어 서버와 레코드 서버를 도커 컨테이너로 실행시키는 명령어이다. 코드에서 보이듯이, 각 컨테이들마다 300개에서 1000개까지의 포트를 할당하여 데이터 송수신을 처리하고 있으며, 이게 메모리 사용량이 급증하게 된 원인이었다.
각 컨테이들마다 포트를 매핑시켜주면 Docker Proxy라는 녀석이 포트 별로 생성된다. 때문에 약 1300개 가량의 Docker proxy가 생성되게 되면서, 메모리 사용량이 급증하게 된 것이었다.
- Docker는 -p 옵션으로 컨테이너의 내부 포트를 호스트의 외부 포트로 노출할 때, Docker Proxy를 생성하여 트래픽을 중계한다.
- 예를 들어, -p 3001:3001을 지정하면, 호스트의 3001 포트로 들어온 트래픽은 Docker Proxy를 통해 컨테이너의 3001 포트로 전달된다.
- Docker Proxy는 TCP와 UDP 모두에 대해 동작하며, 각각의 프로토콜별로 별도의 프로세스를 생성하여 요청을 처리한다.
- UDP 프로토콜의 경우, 다량의 포트를 매핑하면 Docker Proxy 프로세스가 폭발적으로 증가하여 메모리와 CPU 사용량이 높아질 수 있다.
- bridge 네트워크 모드에서는 컨테이너 간의 통신이 NAT(Network Address Translation)를 통해 이루어지며, Docker Proxy가 이를 지원한다.
- Docker가 별도의 네트워크를 명시하지 않으면 기본적으로 bridge 모드에서 실행됩니다. 이 모드에서는 Docker Proxy가 생성됩니다.
- -network host를 사용하면 컨테이너가 호스트 네트워크를 직접 사용하므로 Docker Proxy가 필요 없습니다. 이 모드에서는 호스트와 컨테이너 간의 트래픽 중계가 불필요해 메모리와 CPU 사용량이 줄어듭니다.
- 컨테이너에서 수백 개 이상의 포트를 매핑하면, 각 포트마다 별도의 Docker Proxy 프로세스가 생성된다
- 예를 들어, 1000개의 포트를 매핑하면 1000개의 Docker Proxy가 생성되며, 이는 메모리 사용량 증가로 이어진다
- UDP는 상태를 유지하지 않는 통신 방식이지만, 포트별 Docker Proxy는 메모리와 CPU를 추가로 소비합니다. 이로 인해 UDP 포트 매핑이 많을수록 자원 소모가 커진다
Docker Proxy의 과도한 생성으로 인해 발생한 메모리 사용량 증가 문제를 해결하기 위해 다음 두 가지 해결 방향을 설정했습니다.
-
컨테이너 네트워크 통합
서로 다른 네트워크에 속한 컨테이너가 직접 통신하지 못했던 문제를 해결하기 위해, Docker Compose를 사용하여 모든 컨테이너를 동일한 네트워크에 포함시켰습니다.
이를 통해 포트 매핑 없이도 컨테이너 간의 통신이 가능해지고, Docker Proxy 생성이 필요 없게 되었습니다.
-
불필요한 포트 매핑 축소
미디어 서버는 외부 클라이언트와 통신해야 하므로 필수적인 포트 매핑을 유지했습니다.
그러나 레코드 서버는 미디어 서버와의 내부 통신만 필요하므로, 레코드 서버의 외부 포트 매핑을 제거하여 Docker Proxy를 최소화했습니다.
Docker Compose를 활용하여, media
와 record
컨테이너를 하나의 네트워크에 포함시켰습니다.
이를 통해 컨테이너 간에는 서비스 이름만으로 직접 통신할 수 있어 별도의 포트 매핑이 필요 없어졌습니다.
version: "3.9"
services:
media:
container_name: media-camon
image: media-camon
networks:
- camon
ports:
- "3001:3001"
- "30000-31000:30000-31000"
record:
container_name: record-camon
image: record-camon
networks:
- camon:
ports:
- "3003:3003"
networks:
camon:
driver: bridge
- Docker Proxy의 생성 개수가 대폭 감소하여, 메모리 사용량이 안정화되었다.
- Media와 Record 서버 간 통신은 포트 매핑 없이 내부 네트워크를 통해 처리되므로, 메모리 사용량을 약 30% 이상 감소시키는 성과를 얻었다
- Mediasoup 포트 매핑 문제
- swagger 같은 응답 코드에 다양한 응답 보여주기
- Sudo가 계속 비밀번호를 요청함
- Docker 이미지가 너무 크다
- Git action에서 도커 이미지 빌드 시간을 단축시켜보자
- Docker compose를 이용해서 메모리 사용률을 줄여보자
- 방송 녹화 시 CPU 과부하 문제를 해결해보자
- Release 브랜치? 너 필요해?
- 로딩이 너무 짧아…!
- NestJS ORM으로 무엇을 사용해야 할까?
- WebRTC를 이용한 1:N 스트리밍 서비스에서 시그널링 서버가 필요할까?
- 실시간 채팅 구현: 인메모리 방식을 선택한 이유
- MySQL 아키텍처 개선: DB 의존성 분리와 서버 역할 명확화
- 브라우저 창이 최소화되면 비디오 송출이 안된다…!
- Mediasoup 기본 개념
- DLTS와 Signaling
- Tell, Don't Ask (TDA) 원칙이란
- VPC(Virtual Private Cloud) 학습 정리
- 순환참조: A 서비스 ‐ B 서비스 vs. A 서비스 ‐ B 레포지토리
- Dto 메서드 전략
- WebRTC란?
- 자바스크립트 패키지 매니저(npm, yarn, pnpm)
- shadcn/ui을 이용해 UI 개발 생산성 높이기
- React 이벤트 핸들러 네이밍(on vs handle)
- React-router-dom의 createBrowserRouter을 사용해보기
- fetch vs axios