Docker Compose: Orkiestracja wielokontenerowa

Docker Compose: Orkiestracja wielokontenerowa

Wskazówka pomocnicza

Jeśli masz trudności z rozwiązaniem zadań z Docker Compose, możesz znaleźć pomocnicze materiały w repozytorium: Docker Lab 02

Czym jest Docker Compose?

Docker Compose to narzędzie do definiowania i uruchamiania wielokontenerowych aplikacji Docker. Za pomocą jednego pliku konfiguracyjnego w formacie YAML oraz prostych komend możesz utworzyć i uruchomić wszystkie usługi swojej aplikacji.

Główne zalety Docker Compose: - Definiowanie całego środowiska aplikacji w jednym pliku - Uruchamianie wszystkich kontenerów jedną komendą - Automatyczne tworzenie sieci między kontenerami - Współdzielenie wolumenów między kontenerami - Łatwe zarządzanie zależnościami między usługami

Podstawy Docker Compose

Plik docker-compose.yml

Centralnym elementem Docker Compose jest plik docker-compose.yml. Definiuje on wszystkie usługi, sieci i wolumeny potrzebne do uruchomienia aplikacji.

Podstawowa struktura pliku docker-compose.yml:

version: '3.8'

services:
  service1:
    image: nginx:alpine
    ports:
      - "8080:80"
  
  service2:
    build: ./app
    volumes:
      - ./app:/code
    depends_on:
      - service1

volumes:
  data-volume:

networks:
  app-network:

Główne sekcje docker-compose.yml

  1. version: Określa wersję składni Docker Compose (obecnie rekomendowana jest ‘3.x’)
  2. services: Definicje wszystkich usług (kontenerów) aplikacji
  3. volumes: Definicje współdzielonych wolumenów
  4. networks: Definicje sieci używanych przez usługi

Konfiguracja usług (services)

Dla każdej usługi można zdefiniować wiele opcji konfiguracyjnych:

Podstawowe komendy Docker Compose

# Uruchomienie wszystkich usług
docker-compose up

# Uruchomienie w tle (detached mode)
docker-compose up -d

# Zatrzymanie usług
docker-compose down

# Zatrzymanie i usunięcie wolumenów
docker-compose down -v

# Przebudowanie obrazów
docker-compose build

# Przebudowanie i uruchomienie
docker-compose up -d --build

# Wyświetlenie statusu kontenerów
docker-compose ps

# Wyświetlenie logów
docker-compose logs

# Wyświetlenie logów w trybie ciągłym
docker-compose logs -f

# Wyświetlenie logów konkretnej usługi
docker-compose logs service_name

# Uruchomienie polecenia w kontenerze
docker-compose exec service_name command

# Uruchomienie powłoki w kontenerze
docker-compose exec service_name sh

# Zatrzymanie pojedynczej usługi
docker-compose stop service_name

# Uruchomienie pojedynczej usługi
docker-compose start service_name

# Restart usługi
docker-compose restart service_name

Przykłady zastosowań Docker Compose

Prosta aplikacja webowa z bazą danych

version: '3.8'

services:
  web:
    build: ./app
    ports:
      - "5000:5000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/mydatabase
    depends_on:
      - db
  
  db:
    image: postgres:13
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydatabase

volumes:
  postgres-data:

Aplikacja MERN stack (MongoDB, Express, React, Node.js)

version: '3.8'

services:
  frontend:
    build: ./frontend
    ports:
      - "3000:80"
    environment:
      - API_URL=http://backend:3000
    depends_on:
      - backend

  backend:
    build: ./backend
    ports:
      - "3001:3000"
    environment:
      - MONGODB_URI=mongodb://db:27017/myapp
      - PORT=3000
      - CORS_ORIGIN=http://localhost:3000
    depends_on:
      - db

  db:
    image: mongo:5
    ports:
      - "27017:27017"
    volumes:
      - mongo-data:/data/db

volumes:
  mongo-data:

Zarządzanie zależnościami między usługami

Definiowanie zależności z depends_on

services:
  web:
    depends_on:
      - db
      - redis

Ważne jest, by zrozumieć, że depends_on tylko określa kolejność uruchamiania, ale nie czeka aż usługa będzie “gotowa”. Na przykład, kontener bazy danych może być uruchomiony, ale sama baza danych może nie być jeszcze gotowa na przyjmowanie połączeń.

Czekanie na gotowość usługi

Dla bardziej zaawansowanych zależności, można użyć skryptów typu wait-for-it lub healthcheck:

services:
  web:
    depends_on:
      db:
        condition: service_healthy
  
  db:
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

Sieci w Docker Compose

Docker Compose automatycznie tworzy sieć dla wszystkich usług, dzięki czemu mogą one komunikować się ze sobą używając nazw usług jako nazw hostów.

Definiowanie własnych sieci

services:
  frontend:
    networks:
      - frontend-network
  
  backend:
    networks:
      - frontend-network
      - backend-network
  
  db:
    networks:
      - backend-network

networks:
  frontend-network:
  backend-network:
    internal: true  # Sieć bez dostępu do Internetu

Wolumeny i trwałość danych

Wolumeny pozwalają na przechowywanie danych nawet po zatrzymaniu kontenerów.

Typy wolumenów

  1. Nazwane wolumeny: Zarządzane przez Docker

    volumes:
      - postgres-data:/var/lib/postgresql/data
  2. Mapowanie ścieżek (bind mounts): Mapuje ścieżki z hosta do kontenera

    volumes:
      - ./app:/code
  3. Wolumeny tymczasowe (tmpfs): Przechowuje dane w pamięci

    volumes:
      - type: tmpfs
        target: /tmp

Deklarowanie nazwanych wolumenów

services:
  db:
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  postgres-data:
    # Opcjonalne konfiguracje wolumenu

Zmienne środowiskowe

Zmienne środowiskowe są kluczem do konfiguracji usług w Docker Compose.

Bezpośrednie definiowanie zmiennych

services:
  web:
    environment:
      - NODE_ENV=production
      - PORT=3000

Używanie plików .env

services:
  web:
    env_file:
      - .env
      - .env.production

Format pliku .env:

NODE_ENV=production
PORT=3000

Używanie zmiennych z środowiska hosta

services:
  web:
    environment:
      - NODE_ENV=${NODE_ENV:-development}

Złożone aplikacje i wzorce użycia

Środowisko deweloperskie z hot reload

version: '3.8'

services:
  frontend:
    build: 
      context: ./frontend
      dockerfile: Dockerfile.dev
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    environment:
      - CHOKIDAR_USEPOLLING=true

Różne konfiguracje dla różnych środowisk

Docker Compose pozwala na używanie wielu plików konfiguracyjnych i nakładanie ich na siebie:

# Uruchomienie ze specyficznym plikiem
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Praktyczne przykłady

Przykład 1: Aplikacja web + API + baza danych

version: '3.8'

services:
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    environment:
      - API_URL=http://backend:3000
    depends_on:
      - backend

  backend:
    build: ./backend
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
      - DB_USER=postgres
      - DB_PASSWORD=password
      - DB_NAME=myapp
    depends_on:
      - db

  db:
    image: postgres:13
    environment:
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=myapp
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  postgres-data:

Przykład 2: Środowisko deweloperskie dla aplikacji Node.js

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
    command: npm run dev

Ćwiczenia praktyczne

Ćwiczenie 1: Podstawowy docker-compose.yml

Utwórz plik docker-compose.yml dla prostej aplikacji składającej się z frontendu (nginx) i backendu (node.js).

version: '3.8'

services:
  frontend:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./frontend:/usr/share/nginx/html
  
  backend:
    image: node:18-alpine
    working_dir: /app
    volumes:
      - ./backend:/app
    command: node app.js
    ports:
      - "3000:3000"

Ćwiczenie 2: Dodanie bazy danych

Rozszerz poprzedni przykład o bazę danych MongoDB.

version: '3.8'

services:
  # frontend i backend jak poprzednio
  
  db:
    image: mongo:5
    ports:
      - "27017:27017"
    volumes:
      - mongo-data:/data/db

volumes:
  mongo-data:

Ćwiczenie 3: Środowisko z hot reload

Zmodyfikuj docker-compose.yml, aby umożliwić hot reload dla aplikacji frontendowej i backendowej.

version: '3.8'

services:
  frontend:
    build: ./frontend
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    environment:
      - CHOKIDAR_USEPOLLING=true
    command: npm start
  
  backend:
    build: ./backend
    volumes:
      - ./backend:/app
      - /app/node_modules
    ports:
      - "3001:3000"
    environment:
      - MONGO_URI=mongodb://db:27017/myapp
    command: npm run dev
    depends_on:
      - db
  
  db:
    image: mongo:5
    volumes:
      - mongo-data:/data/db

volumes:
  mongo-data:

Rozwiązywanie problemów

Problem 1: Kontener nie uruchamia się

Sprawdź logi kontenera:

docker-compose logs service_name

Problem 2: Problemy z zależnościami

Sprawdź, czy wszystkie zależne serwisy są uruchomione:

docker-compose ps

Użyj healthcheck dla krytycznych usług:

services:
  db:
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 5s
      retries: 5

Problem 3: Problemy z wolumenami

Sprawdź, czy wolumeny istnieją i mają poprawne uprawnienia:

docker volume ls

Dobre praktyki

  1. Używaj wersji w plikach docker-compose.yml: version: '3.8'
  2. Definiuj wolumeny dla danych które muszą być trwałe
  3. Używaj zmiennych środowiskowych do konfiguracji
  4. Używaj healthchecków dla krytycznych usług
  5. Nadawaj nazwy sieciom i wolumenom, aby uniknąć konfliktów
  6. Minimalizuj ekspozycję portów na hoście (tylko te które są naprawdę potrzebne)
  7. Używaj restart policy odpowiedniego do potrzeb aplikacji
  8. Organizuj różne konfiguracje środowisk w osobnych plikach

Materiały dodatkowe