devops2023-12-05·8 min·333/348

Docker Compose 서비스 의존성 문제 해결

Docker Compose에서 서비스 간 의존성 관리 문제를 해결하는 방법을 알아봅니다.

Docker Compose 서비스 의존성 문제 해결

Docker Compose에서 여러 서비스가 있을 때, 의존성이 있는 서비스가 아직 준비되지 않은 상태에서 다른 서비스가 시작되는 문제가 빈번합니다.

Environment

$ docker-compose --version
Docker Compose version v2.23.0

$ docker --version
Docker version 24.0.7, build afdd53b

Problem: 의존성이 있는 서비스가 준비되기 전에 시작

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - db
      - redis
  
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: mydb
      POSTGRES_PASSWORD: secret
  
  redis:
    image: redis:7-alpine
$ docker-compose up -d
$ docker-compose logs web
# Error: Connection refused to database
# Error: Connection refused to redis

Analysis: 서비스 시작 순서 이해

# Check service status
$ docker-compose ps
NAME      IMAGE         COMMAND                  STATUS
db        postgres:15   "docker-entrypoint.s…"   Up 5 seconds
redis     redis:7       "docker-entrypoint.s…"   Up 5 seconds
web       myapp         "python app.py"          Restarting (1)

# Check logs
$ docker-compose logs --tail 20 web

Solution: 의존성 관리 방법

방법 1: depends_on + condition

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
  
  db:
    image: postgres:15
    environment:
      POSTGRES_DB: mydb
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
  
  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

방법 2: wait-for-it 스크립트

#!/bin/bash
# wait-for-it.sh
host="$1"
port="$2"
timeout="$3"

for i in $(seq 1 $timeout); do
    nc -z $host $port && echo "Service $host:$port is ready" && exit 0
    sleep 1
done

echo "Service $host:$port is not ready after $timeout seconds"
exit 1
version: '3.8'

services:
  web:
    build: .
    command: >
      sh -c "./wait-for-it.sh db 5432 30 &&
             ./wait-for-it.sh redis 6379 30 &&
             python app.py"
    depends_on:
      - db
      - redis

방법 3: Entrypoint 스크립트

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

# Install wait-for-it
RUN apt-get update && apt-get install -y netcat && rm -rf /var/lib/apt/lists/*

COPY wait-for-it.sh /app/wait-for-it.sh
RUN chmod +x /app/wait-for-it.sh

COPY . .

CMD ["./wait-for-it.sh", "db", "5432", "--", "python", "app.py"]

방법 4: Dockerize 사용

version: '3.8'

services:
  web:
    build: .
    command: dockerize -wait tcp://db:5432 -wait tcp://redis:6379 -timeout 30s python app.py
    depends_on:
      - db
      - redis

방법 5: Python에서 재시도 로직

# app.py
import time
import sys
from sqlalchemy import create_engine
from sqlalchemy.exc import OperationalError

def wait_for_database(max_retries=30, delay=1):
    """Wait for database to be ready"""
    engine = create_engine("postgresql://postgres:secret@db:5432/mydb")
    
    for i in range(max_retries):
        try:
            engine.connect()
            print("Database is ready!")
            return True
        except OperationalError:
            print(f"Waiting for database... ({i+1}/{max_retries})")
            time.sleep(delay)
    
    print("Database connection failed after max retries")
    sys.exit(1)

if __name__ == "__main__":
    wait_for_database()
    # Start application
    start_app()

Lessons Learned

  1. depends_on만으로는 부족: depends_on은 컨테이너 시작만 보장할 뿐, 서비스 준비 완료를 보장하지 않습니다.

  2. Healthcheck 필수: condition: service_healthy를 사용하려면 서비스에 healthcheck를 정의해야 합니다.

  3. Application-level 재시도: Infrastructure-level 대기보다 application-level 재시도가 더 견고합니다.

  4. 적절한 타임아웃: 모든 대기 로직에는 합리적인 타임아웃을 설정하여 무한 대기를 방지하세요.

  5. ** 로그 모니터링**: docker-compose logs -f로 서비스 시작 순서와 에러를 모니터링하세요.


This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.