Docker 네트워킹 완벽 가이드
Docker 컨테이너 간 통신, 외부 네트워크 연결, 그리고 다양한 네트워크 드라이버를 활용한 효과적인 네트워킹 구성 방법을 알아봅니다
Part 3: Docker 네트워킹 완벽 가이드
마이크로서비스 아키텍처에서 서비스 간 통신은 핵심입니다. Docker는 강력하고 유연한 네트워킹 기능을 제공하여 컨테이너 간, 그리고 외부 세계와의 통신을 쉽게 구성할 수 있게 해줍니다. 이번 포스트에서는 Docker 네트워킹의 모든 것을 실전 예제와 함께 자세히 알아보겠습니다.
Docker 네트워킹의 기본 이해
Docker 컨테이너는 기본적으로 격리된 네트워크 환경에서 실행됩니다. 각 컨테이너는 자체 네트워크 네임스페이스를 가지며, 이는 다음을 의미합니다:
- 컨테이너의
localhost는 컨테이너 자신을 가리킴 - 컨테이너는 기본적으로 외부 인터넷에 접근 가능
- 컨테이너 간 통신은 설정이 필요
3가지 주요 통신 패턴
Docker에서는 세 가지 주요 통신 패턴을 이해해야 합니다.
1. 컨테이너 → 외부 인터넷 (WWW)
가장 간단한 케이스입니다. Docker는 기본적으로 NAT(Network Address Translation)를 통해 외부 인터넷 접근을 허용합니다.
// app.js - 외부 API 호출 예제
const axios = require('axios');
async function getMovies() {
try {
// 별도 설정 없이 외부 API 호출 가능!
const response = await axios.get('https://swapi.dev/api/films/');
console.log(`총 ${response.data.count}개의 스타워즈 영화가 있습니다.`);
} catch (error) {
console.error('API 호출 실패:', error.message);
}
}
getMovies();
FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "app.js"]
# 실행 - 별도 네트워크 설정 없이 작동!
docker build -t external-api .
docker run external-api
2. 컨테이너 → 호스트 머신
컨테이너에서 호스트에서 실행 중인 서비스(예: 로컬 MongoDB)에 접근해야 할 때가 있습니다. 이때 주의할 점은 컨테이너 내부의 localhost는 호스트가 아닌 컨테이너 자신을 가리킨다는 것입니다.
문제 상황
// ❌ 잘못된 예 - 컨테이너 내부에서
mongoose.connect('mongodb://localhost:27017/mydb');
// Error: ECONNREFUSED 127.0.0.1:27017
해결 방법
1. host.docker.internal 사용 (권장)
// ✅ Docker Desktop에서 작동
mongoose.connect('mongodb://host.docker.internal:27017/mydb');
2. Linux에서의 추가 설정
# Linux에서는 --add-host 플래그 필요
docker run --add-host=host.docker.internal:host-gateway myapp
전체 예제:
// app.js
const mongoose = require('mongoose');
// 환경에 따른 동적 설정
const DB_HOST = process.env.DB_HOST || 'host.docker.internal';
const DB_PORT = process.env.DB_PORT || 27017;
async function connectDB() {
try {
await mongoose.connect(`mongodb://${DB_HOST}:${DB_PORT}/myapp`);
console.log('MongoDB 연결 성공!');
} catch (error) {
console.error('MongoDB 연결 실패:', error);
}
}
connectDB();
3. 컨테이너 ↔ 컨테이너
가장 중요하고 자주 사용되는 패턴입니다. 마이크로서비스 아키텍처의 핵심이죠.
❌ 잘못된 방법: IP 주소 하드코딩
# MongoDB 컨테이너 실행
docker run -d --name mongodb mongo
# IP 주소 확인
docker inspect mongodb | grep IPAddress
# "IPAddress": "172.17.0.2"
// 하드코딩된 IP - 절대 하지 마세요!
mongoose.connect('mongodb://172.17.0.2:27017/mydb');
문제점:
- 컨테이너 재시작 시 IP 변경
- 환경별로 다른 IP
- 유지보수 악몽
✅ 올바른 방법: Docker 네트워크 사용
# 1. 커스텀 네트워크 생성
docker network create myapp-network
# 2. 컨테이너를 네트워크에 연결
docker run -d --name mongodb --network myapp-network mongo
docker run -d --name backend --network myapp-network -p 3000:3000 myapp
// 컨테이너 이름으로 접근!
mongoose.connect('mongodb://mongodb:27017/mydb');
Docker 네트워크 드라이버 심화
Docker는 다양한 네트워크 드라이버를 제공합니다. 각각의 특징과 사용 시나리오를 알아봅시다.
1. Bridge 드라이버 (기본값)
가장 일반적으로 사용되는 드라이버입니다.
# 커스텀 브리지 네트워크 생성
docker network create --driver bridge myapp-net
# 네트워크 상세 정보 확인
docker network inspect myapp-net
특징:
- 단일 호스트 내 컨테이너 통신
- DNS 기반 서비스 디스커버리
- 네트워크 격리 제공
실전 예제: 3-Tier 애플리케이션
# 네트워크 생성
docker network create app-network
# 데이터베이스 계층
docker run -d \
--name postgres \
--network app-network \
-e POSTGRES_PASSWORD=secret \
postgres:13
# 백엔드 API 계층
docker run -d \
--name api \
--network app-network \
-e DB_HOST=postgres \
-p 3000:3000 \
backend-api
# 프론트엔드 계층
docker run -d \
--name frontend \
--network app-network \
-e API_URL=http://api:3000 \
-p 80:80 \
frontend-app
2. Host 드라이버
컨테이너가 호스트의 네트워크를 직접 사용합니다.
docker run --network host nginx
장점:
- 최고 성능 (네트워크 오버헤드 없음)
- 호스트와 동일한 네트워크 인터페이스
단점:
- 포트 충돌 가능성
- 네트워크 격리 없음
- Linux에서만 완전 지원
사용 사례:
# 네트워크 모니터링 도구
docker run --network host --name netdata netdata/netdata
# 고성능이 필요한 경우
docker run --network host --name redis redis:alpine
3. None 드라이버
네트워크를 완전히 비활성화합니다.
docker run --network none busybox
사용 사례:
- 보안이 중요한 배치 작업
- 네트워크가 필요 없는 데이터 처리
- 격리된 테스트 환경
4. Overlay 드라이버
여러 Docker 호스트에 걸친 네트워크를 생성합니다.
# Docker Swarm 모드에서
docker network create --driver overlay --attachable multi-host-network
특징:
- Docker Swarm과 함께 사용
- 멀티호스트 통신
- 자동 로드 밸런싱
실전 예제: 풀스택 애플리케이션 구성
실제 풀스택 애플리케이션의 네트워크 구성을 살펴보겠습니다.
프로젝트 구조
myapp/
├── frontend/ # React 앱
├── backend/ # Node.js API
├── database/ # PostgreSQL 초기화 스크립트
└── docker-compose.yml
Docker Compose로 네트워크 구성
version: '3.8'
services:
# PostgreSQL 데이터베이스
database:
image: postgres:13
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 10s
timeout: 5s
retries: 5
# Node.js 백엔드 API
backend:
build: ./backend
environment:
NODE_ENV: production
DB_HOST: database
DB_PORT: 5432
DB_NAME: myapp
DB_USER: admin
DB_PASSWORD: secret
depends_on:
database:
condition: service_healthy
networks:
- backend-network
- frontend-network
ports:
- "3000:3000"
# React 프론트엔드
frontend:
build: ./frontend
environment:
REACT_APP_API_URL: http://backend:3000
networks:
- frontend-network
ports:
- "80:80"
# Redis 캐시 (선택사항)
redis:
image: redis:alpine
networks:
- backend-network
command: redis-server --appendonly yes
networks:
frontend-network:
driver: bridge
backend-network:
driver: bridge
volumes:
db-data:
네트워크 격리 전략
위 구성에서 주목할 점:
database와redis는backend-network에만 연결backend는 양쪽 네트워크에 연결 (브리지 역할)frontend는frontend-network에만 연결
이렇게 하면 프론트엔드가 데이터베이스에 직접 접근할 수 없어 보안이 강화됩니다.
네트워크 디버깅과 트러블슈팅
유용한 디버깅 명령어
# 네트워크 목록 확인
docker network ls
# 네트워크 상세 정보
docker network inspect myapp-network
# 컨테이너의 네트워크 설정 확인
docker inspect container-name | jq '.[0].NetworkSettings'
# 연결 테스트
docker exec -it container1 ping container2
docker exec -it container1 nc -zv container2 3000
docker exec -it container1 curl http://container2:3000/health
# DNS 확인
docker exec -it container1 nslookup container2
docker exec -it container1 cat /etc/hosts
일반적인 문제와 해결책
1. "Connection refused" 에러
# 문제 진단
docker exec -it app nc -zv database 5432
# 해결책
# 1. 서비스가 실행 중인지 확인
docker ps
# 2. 같은 네트워크에 있는지 확인
docker inspect app | grep NetworkMode
docker inspect database | grep NetworkMode
# 3. 포트가 올바른지 확인
docker port database
2. DNS 해석 실패
// Error: getaddrinfo ENOTFOUND mongodb
// 해결책 1: 컨테이너 이름 확인
docker ps --format "table {{.Names}}"
// 해결책 2: 네트워크 연결 확인
docker network inspect myapp-network
3. 간헐적 연결 실패
// 연결 재시도 로직 구현
const mongoose = require('mongoose');
async function connectWithRetry() {
const maxRetries = 5;
let retries = 0;
while (retries < maxRetries) {
try {
await mongoose.connect('mongodb://mongodb:27017/mydb', {
useNewUrlParser: true,
useUnifiedTopology: true,
serverSelectionTimeoutMS: 5000
});
console.log('MongoDB 연결 성공!');
break;
} catch (err) {
retries++;
console.log(`연결 시도 ${retries}/${maxRetries} 실패, 재시도...`);
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
}
보안 모범 사례
1. 네트워크 분리
# 환경별 네트워크 분리
docker network create dev-network
docker network create staging-network
docker network create prod-network
2. 최소 권한 원칙
# docker-compose.yml
services:
frontend:
networks:
- public-network # 외부 접근 가능
backend:
networks:
- public-network
- internal-network # 내부 통신용
database:
networks:
- internal-network # 내부만 접근 가능
3. 환경 변수로 설정 관리
// config.js
module.exports = {
database: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
name: process.env.DB_NAME || 'myapp',
// 절대 하드코딩하지 마세요!
password: process.env.DB_PASSWORD
},
redis: {
host: process.env.REDIS_HOST || 'redis',
port: process.env.REDIS_PORT || 6379
}
};
성능 최적화 팁
1. 연결 풀링
// PostgreSQL 연결 풀
const { Pool } = require('pg');
const pool = new Pool({
host: 'database',
port: 5432,
database: 'myapp',
user: 'admin',
password: process.env.DB_PASSWORD,
max: 20, // 최대 연결 수
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
2. 헬스 체크 구현
// healthcheck.js
app.get('/health', async (req, res) => {
const checks = {
database: 'healthy',
redis: 'healthy',
memory: process.memoryUsage(),
uptime: process.uptime()
};
try {
// 데이터베이스 체크
await pool.query('SELECT 1');
} catch (err) {
checks.database = 'unhealthy';
}
try {
// Redis 체크
await redisClient.ping();
} catch (err) {
checks.redis = 'unhealthy';
}
const isHealthy = checks.database === 'healthy' && checks.redis === 'healthy';
res.status(isHealthy ? 200 : 503).json(checks);
});
마무리
Docker 네트워킹은 처음에는 복잡해 보일 수 있지만, 기본 개념을 이해하면 매우 강력한 도구가 됩니다. 핵심은:
- 통신 패턴 이해: WWW, Host, Container-to-Container
- 적절한 드라이버 선택: Bridge, Host, None, Overlay
- DNS 기반 서비스 디스커버리 활용: IP 대신 이름 사용
- 네트워크 격리로 보안 강화: 필요한 연결만 허용
- 디버깅 도구 숙달: inspect, exec, logs 명령어
다음 포스트에서는 이러한 네트워킹 지식을 바탕으로 실제 다중 컨테이너 애플리케이션을 구축하는 방법을 알아보겠습니다. Docker Compose를 활용한 효율적인 개발 환경 구성이 기다리고 있습니다!
시리즈 네비게이션
- ← 이전: Part 2: 데이터 관리와 볼륨
- → 다음: Part 4: 다중 컨테이너 애플리케이션