본문으로 건너뛰기

Docker 네트워킹 완벽 가이드

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

2025년 8월 3일13 min read

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

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

Docker 네트워킹의 기본 이해

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

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

3가지 주요 통신 패턴

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

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

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

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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
1
2
3
4
5
6
FROM node:14
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "app.js"]
bash
1
2
3
# 실행 - 별도 네트워크 설정 없이 작동!
docker build -t external-api .
docker run external-api

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

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

문제 상황

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

해결 방법

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

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

2. Linux에서의 추가 설정

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

전체 예제:

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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
1
2
3
4
5
6
# MongoDB 컨테이너 실행
docker run -d --name mongodb mongo

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

문제점:

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

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

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

Docker 네트워크 드라이버 심화

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

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

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

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

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

특징:

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

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

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 네트워크 생성
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
1
docker run --network host nginx

장점:

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

단점:

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

사용 사례:

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

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

3. None 드라이버

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

bash
1
docker run --network none busybox

사용 사례:

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

4. Overlay 드라이버

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

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

특징:

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

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

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

프로젝트 구조

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

Docker Compose로 네트워크 구성

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 네트워크 목록 확인
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
1
2
3
4
5
6
7
8
9
10
11
12
13
# 문제 진단
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
1
2
3
4
5
6
7
// Error: getaddrinfo ENOTFOUND mongodb

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

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

3. 간헐적 연결 실패

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 연결 재시도 로직 구현
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
1
2
3
4
# 환경별 네트워크 분리
docker network create dev-network
docker network create staging-network
docker network create prod-network

2. 최소 권한 원칙

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# docker-compose.yml
services:
  frontend:
    networks:
      - public-network  # 외부 접근 가능
  
  backend:
    networks:
      - public-network
      - internal-network  # 내부 통신용
  
  database:
    networks:
      - internal-network  # 내부만 접근 가능

3. 환경 변수로 설정 관리

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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
1
2
3
4
5
6
7
8
9
10
11
12
13
// 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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를 활용한 효율적인 개발 환경 구성이 기다리고 있습니다!


시리즈 네비게이션

댓글