Docker 컨테이너 간 통신, 외부 네트워크 연결, 그리고 다양한 네트워크 드라이버를 활용한 효과적인 네트워킹 구성 방법을 알아봅니다
마이크로서비스 아키텍처에서 서비스 간 통신은 핵심입니다. Docker는 강력하고 유연한 네트워킹 기능을 제공하여 컨테이너 간, 그리고 외부 세계와의 통신을 쉽게 구성할 수 있게 해줍니다. 이번 포스트에서는 Docker 네트워킹의 모든 것을 실전 예제와 함께 자세히 알아보겠습니다.
Docker 컨테이너는 기본적으로 격리된 네트워크 환경에서 실행됩니다. 각 컨테이너는 자체 네트워크 네임스페이스를 가지며, 이는 다음을 의미합니다:
localhost는 컨테이너 자신을 가리킴Docker에서는 세 가지 주요 통신 패턴을 이해해야 합니다.
가장 간단한 케이스입니다. 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
컨테이너에서 호스트에서 실행 중인 서비스(예: 로컬 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();
가장 중요하고 자주 사용되는 패턴입니다. 마이크로서비스 아키텍처의 핵심이죠.
# 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');
문제점:
# 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 network create --driver bridge myapp-net
# 네트워크 상세 정보 확인
docker network inspect myapp-net
특징:
실전 예제: 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
컨테이너가 호스트의 네트워크를 직접 사용합니다.
docker run --network host nginx
장점:
단점:
사용 사례:
# 네트워크 모니터링 도구
docker run --network host --name netdata netdata/netdata
# 고성능이 필요한 경우
docker run --network host --name redis redis:alpine
네트워크를 완전히 비활성화합니다.
docker run --network none busybox
사용 사례:
여러 Docker 호스트에 걸친 네트워크를 생성합니다.
# Docker Swarm 모드에서
docker network create --driver overlay --attachable multi-host-network
특징:
실제 풀스택 애플리케이션의 네트워크 구성을 살펴보겠습니다.
myapp/
├── frontend/ # React 앱
├── backend/ # Node.js API
├── database/ # PostgreSQL 초기화 스크립트
└── docker-compose.yml
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
# 문제 진단
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
// Error: getaddrinfo ENOTFOUND mongodb
// 해결책 1: 컨테이너 이름 확인
docker ps --format "table {{.Names}}"
// 해결책 2: 네트워크 연결 확인
docker network inspect myapp-network
// 연결 재시도 로직 구현
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));
}
}
}
# 환경별 네트워크 분리
docker network create dev-network
docker network create staging-network
docker network create prod-network
# docker-compose.yml
services:
frontend:
networks:
- public-network # 외부 접근 가능
backend:
networks:
- public-network
- internal-network # 내부 통신용
database:
networks:
- internal-network # 내부만 접근 가능
// 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
}
};
// 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,
});
// 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 네트워킹은 처음에는 복잡해 보일 수 있지만, 기본 개념을 이해하면 매우 강력한 도구가 됩니다. 핵심은:
다음 포스트에서는 이러한 네트워킹 지식을 바탕으로 실제 다중 컨테이너 애플리케이션을 구축하는 방법을 알아보겠습니다. Docker Compose를 활용한 효율적인 개발 환경 구성이 기다리고 있습니다!
시리즈 네비게이션
Docker의 핵심 개념인 이미지와 컨테이너를 이해하고, 실무에서 활용하는 방법을 상세히 알아봅니다
로컬에 개발 도구를 설치하지 않고 Docker 컨테이너로 npm, composer, artisan 등을 실행하는 유틸리티 컨테이너 패턴을 알아봅니다
실제 프로덕션 환경과 같은 다중 컨테이너 애플리케이션을 Docker로 구축하는 방법을 React, Node.js, MongoDB를 활용한 실전 예제로 알아봅니다
Nginx, PHP-FPM, MySQL, Redis를 조합한 프로덕션급 Laravel 개발 환경을 Docker로 구축하는 방법을 상세히 알아봅니다