본문으로 건너뛰기

Docker 네트워킹 완벽 가이드

Docker 컨테이너 간 통신, 외부 네트워크 연결, 그리고 다양한 네트워크 드라이버를 활용한 효과적인 네트워킹 구성 방법을 알아봅니다

2025년 8월 3일13 min read
광고 영역 (AdSense 미설정)

Part 3: Docker 네트워킹 완벽 가이드

마이크로서비스 아키텍처에서 서비스 간 통신은 핵심입니다. Docker는 강력하고 유연한 네트워킹 기능을 제공하여 컨테이너 간, 그리고 외부 세계와의 통신을 쉽게 구성할 수 있게 해줍니다. 이번 포스트에서는 Docker 네트워킹의 모든 것을 실전 예제와 함께 자세히 알아보겠습니다.

Docker 네트워킹의 기본 이해

Docker 컨테이너는 기본적으로 격리된 네트워크 환경에서 실행됩니다. 각 컨테이너는 자체 네트워크 네임스페이스를 가지며, 이는 다음을 의미합니다:

  • 컨테이너의 localhost는 컨테이너 자신을 가리킴
  • 컨테이너는 기본적으로 외부 인터넷에 접근 가능
  • 컨테이너 간 통신은 설정이 필요

3가지 주요 통신 패턴

Docker에서는 세 가지 주요 통신 패턴을 이해해야 합니다.

1. 컨테이너 → 외부 인터넷 (WWW)

가장 간단한 케이스입니다. Docker는 기본적으로 NAT(Network Address Translation)를 통해 외부 인터넷 접근을 허용합니다.

javascript
// 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();
dockerfile
FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "app.js"]
bash
# 실행 - 별도 네트워크 설정 없이 작동!
docker build -t external-api .
docker run external-api

2. 컨테이너 → 호스트 머신

컨테이너에서 호스트에서 실행 중인 서비스(예: 로컬 MongoDB)에 접근해야 할 때가 있습니다. 이때 주의할 점은 컨테이너 내부의 localhost는 호스트가 아닌 컨테이너 자신을 가리킨다는 것입니다.

문제 상황

javascript
// ❌ 잘못된 예 - 컨테이너 내부에서
mongoose.connect('mongodb://localhost:27017/mydb');
// Error: ECONNREFUSED 127.0.0.1:27017

해결 방법

1. host.docker.internal 사용 (권장)

javascript
// ✅ Docker Desktop에서 작동
mongoose.connect('mongodb://host.docker.internal:27017/mydb');

2. Linux에서의 추가 설정

bash
# Linux에서는 --add-host 플래그 필요
docker run --add-host=host.docker.internal:host-gateway myapp

전체 예제:

javascript
// 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 주소 하드코딩

bash
# MongoDB 컨테이너 실행
docker run -d --name mongodb mongo

# IP 주소 확인
docker inspect mongodb | grep IPAddress
# "IPAddress": "172.17.0.2"
javascript
// 하드코딩된 IP - 절대 하지 마세요!
mongoose.connect('mongodb://172.17.0.2:27017/mydb');

문제점:

  • 컨테이너 재시작 시 IP 변경
  • 환경별로 다른 IP
  • 유지보수 악몽

✅ 올바른 방법: Docker 네트워크 사용

bash
# 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
javascript
// 컨테이너 이름으로 접근!
mongoose.connect('mongodb://mongodb:27017/mydb');

Docker 네트워크 드라이버 심화

Docker는 다양한 네트워크 드라이버를 제공합니다. 각각의 특징과 사용 시나리오를 알아봅시다.

1. Bridge 드라이버 (기본값)

가장 일반적으로 사용되는 드라이버입니다.

bash
# 커스텀 브리지 네트워크 생성
docker network create --driver bridge myapp-net

# 네트워크 상세 정보 확인
docker network inspect myapp-net

특징:

  • 단일 호스트 내 컨테이너 통신
  • DNS 기반 서비스 디스커버리
  • 네트워크 격리 제공

실전 예제: 3-Tier 애플리케이션

bash
# 네트워크 생성
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 드라이버

컨테이너가 호스트의 네트워크를 직접 사용합니다.

bash
docker run --network host nginx

장점:

  • 최고 성능 (네트워크 오버헤드 없음)
  • 호스트와 동일한 네트워크 인터페이스

단점:

  • 포트 충돌 가능성
  • 네트워크 격리 없음
  • Linux에서만 완전 지원

사용 사례:

bash
# 네트워크 모니터링 도구
docker run --network host --name netdata netdata/netdata

# 고성능이 필요한 경우
docker run --network host --name redis redis:alpine

3. None 드라이버

네트워크를 완전히 비활성화합니다.

bash
docker run --network none busybox

사용 사례:

  • 보안이 중요한 배치 작업
  • 네트워크가 필요 없는 데이터 처리
  • 격리된 테스트 환경

4. Overlay 드라이버

여러 Docker 호스트에 걸친 네트워크를 생성합니다.

bash
# Docker Swarm 모드에서
docker network create --driver overlay --attachable multi-host-network

특징:

  • Docker Swarm과 함께 사용
  • 멀티호스트 통신
  • 자동 로드 밸런싱

실전 예제: 풀스택 애플리케이션 구성

실제 풀스택 애플리케이션의 네트워크 구성을 살펴보겠습니다.

프로젝트 구조

plaintext
myapp/
├── frontend/     # React 앱
├── backend/      # Node.js API
├── database/     # PostgreSQL 초기화 스크립트
└── docker-compose.yml

Docker Compose로 네트워크 구성

yaml
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:

네트워크 격리 전략

위 구성에서 주목할 점:

  • databaseredisbackend-network에만 연결
  • backend는 양쪽 네트워크에 연결 (브리지 역할)
  • frontendfrontend-network에만 연결

이렇게 하면 프론트엔드가 데이터베이스에 직접 접근할 수 없어 보안이 강화됩니다.

네트워크 디버깅과 트러블슈팅

유용한 디버깅 명령어

bash
# 네트워크 목록 확인
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" 에러

bash
# 문제 진단
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 해석 실패

javascript
// Error: getaddrinfo ENOTFOUND mongodb

// 해결책 1: 컨테이너 이름 확인
docker ps --format "table {{.Names}}"

// 해결책 2: 네트워크 연결 확인
docker network inspect myapp-network

3. 간헐적 연결 실패

javascript
// 연결 재시도 로직 구현
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. 네트워크 분리

bash
# 환경별 네트워크 분리
docker network create dev-network
docker network create staging-network
docker network create prod-network

2. 최소 권한 원칙

yaml
# docker-compose.yml
services:
  frontend:
    networks:
      - public-network  # 외부 접근 가능
  
  backend:
    networks:
      - public-network
      - internal-network  # 내부 통신용
  
  database:
    networks:
      - internal-network  # 내부만 접근 가능

3. 환경 변수로 설정 관리

javascript
// 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. 연결 풀링

javascript
// 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. 헬스 체크 구현

javascript
// 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 네트워킹은 처음에는 복잡해 보일 수 있지만, 기본 개념을 이해하면 매우 강력한 도구가 됩니다. 핵심은:

  1. 통신 패턴 이해: WWW, Host, Container-to-Container
  2. 적절한 드라이버 선택: Bridge, Host, None, Overlay
  3. DNS 기반 서비스 디스커버리 활용: IP 대신 이름 사용
  4. 네트워크 격리로 보안 강화: 필요한 연결만 허용
  5. 디버깅 도구 숙달: inspect, exec, logs 명령어

다음 포스트에서는 이러한 네트워킹 지식을 바탕으로 실제 다중 컨테이너 애플리케이션을 구축하는 방법을 알아보겠습니다. Docker Compose를 활용한 효율적인 개발 환경 구성이 기다리고 있습니다!


시리즈 네비게이션

광고 영역 (AdSense 미설정)

댓글