(one-shot init 컨테이너 패턴 + docker host / container 관점 설명)
1. 배경
MongoDB Replica Set을 Docker Compose로 구성하다 보면 보통 이런 흐름을 겪는다.
- 컨테이너는 잘 올라가는데
- rs.initiate()는 직접 실행해야 하고
- 재기동할 때마다 “이미 초기화된 건지” 사람이 판단해야 한다
이 상태는 실습 단계에서는 괜찮지만,
운영 관점에서는 명백한 문제다.
분산 시스템에서 “선출(election)”이 수동으로 남아 있다는 건
자동화가 덜 끝났다는 뜻이기 때문이다.
이번 글에서는:
- docker compose up -d 한 번으로
- MongoDB 3노드 Replica Set 기동
- 레플리카셋 초기화 + PRIMARY 선출까지 자동으로 완료되는 구조
를 one-shot init 컨테이너 패턴으로 정리한다.
2. Docker Host vs Container 관점부터 정리
Replica Set을 이해하기 전에, 주소를 누가 해석하는지부터 명확히 해야 한다.
Docker Host (로컬 PC) 관점
- 우리가 터미널에서 쓰는:
-
localhost:27017
- 이건 호스트 OS 기준 주소
- Docker 컨테이너 내부 DNS (mongo1, mongo2)는 모른다
그래서:
- 호스트 → 컨테이너 접근은 포트 포워딩으로 한다
localhost:27017 → mongo1 컨테이너의 27017
Docker Container (MongoDB) 관점
- 컨테이너 안에서의 localhost는 자기 자신
- MongoDB 컨테이너끼리 통신하려면:
- localhost ❌
- Docker 네트워크 DNS 이름 (mongo1, mongo2) ✅
즉:
- MongoDB 서버끼리 쓰는 주소 = Docker DNS
- 클라이언트가 최초 접속하는 주소 = host가 해석 가능한 주소
이 관점이 섞이면 Replica Set이 바로 꼬인다.
3. 전체 구조 개요
구성은 다음과 같다.
mongo1 ─┐ mongo2 ─┼─ Replica Set (rs0) mongo3 ─┘ mongo-init (one-shot) └─ rs.initiate() 1회 실행 후 종료
역할 분리는 명확하다.
- mongo1/2/3
→ DB 프로세스만 실행 (장기 실행 서비스) - mongo-init
→ 클러스터 초기화 작업만 수행 (Job)
4. docker-compose.yml
version: "3.8" services: mongo1: image: mongo:7.0 container_name: mongo1 hostname: mongo1 ports: - "27017:27017" command: ["mongod", "--replSet", "rs0", "--bind_ip_all"] volumes: - mongo1-data:/data/db healthcheck: test: ["CMD-SHELL", "mongosh --quiet --eval 'db.adminCommand({ ping: 1 }).ok' | grep 1 >/dev/null"] interval: 2s timeout: 2s retries: 30 mongo2: image: mongo:7.0 container_name: mongo2 hostname: mongo2 ports: - "27018:27017" command: ["mongod", "--replSet", "rs0", "--bind_ip_all"] volumes: - mongo2-data:/data/db healthcheck: test: ["CMD-SHELL", "mongosh --quiet --eval 'db.adminCommand({ ping: 1 }).ok' | grep 1 >/dev/null"] interval: 2s timeout: 2s retries: 30 mongo3: image: mongo:7.0 container_name: mongo3 hostname: mongo3 ports: - "27019:27017" command: ["mongod", "--replSet", "rs0", "--bind_ip_all"] volumes: - mongo3-data:/data/db healthcheck: test: ["CMD-SHELL", "mongosh --quiet --eval 'db.adminCommand({ ping: 1 }).ok' | grep 1 >/dev/null"] interval: 2s timeout: 2s retries: 30 mongo-init: image: mongo:7.0 container_name: mongo-init depends_on: mongo1: condition: service_healthy mongo2: condition: service_healthy mongo3: condition: service_healthy restart: "no" entrypoint: ["bash", "-lc"] command: > ' set -euo pipefail; echo "[mongo-init] checking replica set status..."; if mongosh --host mongo1:27017 --quiet --eval "try { rs.status().ok } catch(e) { 0 }" | grep -q "^1$"; then echo "[mongo-init] already initialized"; exit 0; fi echo "[mongo-init] initiating replica set..."; mongosh --host mongo1:27017 --quiet --eval " rs.initiate({ _id: \"rs0\", members: [ { _id: 0, host: \"mongo1:27017\" }, { _id: 1, host: \"mongo2:27017\" }, { _id: 2, host: \"mongo3:27017\" } ] }) "; echo "[mongo-init] waiting for PRIMARY..."; for i in {1..60}; do if mongosh --host mongo1:27017 --quiet --eval "rs.isMaster().ismaster" | grep -q "^true$"; then echo "[mongo-init] PRIMARY elected"; exit 0; fi sleep 1; done echo "[mongo-init] PRIMARY election timeout"; exit 1; ' volumes: mongo1-data: mongo2-data: mongo3-data:
5. 핵심 포인트 설명
1) healthcheck + depends_on
- depends_on: service_healthy는
- “컨테이너가 떴다”가 아니라
- MongoDB가 요청을 받을 준비가 됐다는 의미
- 너무 이른 rs.initiate() 호출을 방지한다
2) mongo-init은 왜 따로 두나?
mongo-init은 one-shot Job이다.
- 할 일:
- Replica Set 초기화
- PRIMARY 선출 확인
- 할 일 끝나면:
- exit 0
- 컨테이너 종료
이게 정상 동작이다.
init 컨테이너가 계속 살아있다면
오히려 설계가 잘못된 것이다.
3) set -euo pipefail의 의미
set -euo pipefail
이건 “컨테이너를 죽이기 위한 옵션”이 아니다.
- 실패를 숨기지 말고
- 하나라도 실패하면
- 즉시 실패로 처리하라는 안전장치
자동화에서는 조용한 실패가 제일 위험하다.
6. 실행 및 확인
docker compose up -d
docker logs -f mongo-init
docker exec -it mongo1 mongosh --eval \ 'rs.status().members.map(m=>({name:m.name,state:m.stateStr,health:m.health}))'
결과 예시:
mongo1 PRIMARY mongo2 SECONDARY mongo3 SECONDARY
7. 정리
이 구성의 핵심은 MongoDB가 아니다.
- DB 실행
- 클러스터 초기화
- 선출 확인
- 성공/실패 기준 명확화
이 패턴은 그대로:
- Kafka topic-init
- DB migration Job
- Kubernetes Job / InitContainer
로 확장된다.
오늘 MongoDB를 배운 게 아니라,
“운영 자동화 패턴 하나”를 만든 것이다.
'DevOps > Docker' 카테고리의 다른 글
| Docker - Healthcheck, Liveness, Readiness (0) | 2026.02.01 |
|---|---|
| Docker - Kafka를 Docker로 띄우면 왜 로컬에서는 붙었다가 실패할까? (0) | 2026.02.01 |
| Docker - Docker와 로컬 환경 통신 (0) | 2026.01.31 |
댓글