1. Docker-compose

  • 다수의 Container로 소프트웨어가 구성되는 경우 사용할 수 있는 툴 + 환경 설정 파일
    • 여기서 다양한 테스트 등도 수행 가능
    • 다양한 버전을 만드는 것도 일반적 (dev, test, prod 등)
  • 개별 컨테이너를 따로 관리하는 것보다 훨씬 생산성이 높음
  • 환경 설정 파일: docker-compose.yml / docker-compose.yaml

  • 복잡도가 올라가지만 이점이 더 큼
  • 명령어 -> 소프트웨어를 구성하는 모든 컨테이너에 적용됨
    • build, up (run), pull, ps, down (컨테이너 삭제까지), start, stop, rm


  • Docker Desktop의 일부로 설치가 됨
  • Docker Engine을 실행하고 먼저 터미널에서 버전 확인
  • Docker Compose v.1.27부터 v2와 v3가 합쳐짐
docker-compose --version
# Docker Compose version 2.23.3

docker-compose.yml

  • services: 프로그램을 구성하는 서비스들을 지정
    • 각 서비스는 별개의 Docker Image 지정과 Docker Container 실행으로 구성됨
    • 즉, 각 서비스는 자신의 Dockerfile을 갖고 있거나, docker hub 등에서 이미지를 다운로드 받아야 함
    • 서비스 별로 포트번호, 환경변수, 디스크 볼륨 등을 지정해야 함
    • 서비스 이름은 아무거나 가능
  • volumes: 앞서(services에서) 사용된 docker volume들을 지정
  • networks: 앞서 사용된 network들을 지정
    • 모든 서비스에 하나의 네트워크를 사용할 것이라면 따로 지정하지 않아도 됨


  • volumes, networks에서 지정한 볼륨, 네트워크 이름을 services 안에서 사용한다는 것이 더 정확!

기타

1) docker-compose vs. docker compose

  • v1: docker-compose
  • v2: docker compose
  • docker compose를 쓰는 것이 더 좋음, 하지만 아직 대부분의 문서가 docker-compose 중심으로 만들어져 있음


2) docker-compose.yml vs. docker-compose.yaml

  • docker compose 명령 -> 위 둘 중 하나 파일을 찾음
    • 둘 다 존재하면 에러 발생
  • 만약 다른 이름의 파일을 사용하고 싶다면 -f 옵션 사용
    • docker-compose -f docker-compose.dev.yml up

docker-compose.yml 예시

services:
    frontend:  
        build: ./frontend
        ports:
            - 3000:3000
    backend:
        build: ./backend
        ports:
            - 3001:3001
        environment:
            DB_URL: mongodb://database/vidly
    database:
        image: mongo:4.0-xenial
        ports:
            - 27017:27017
        volumes:
            - vidly: /data/db
volumes:
    vidly:


  • service 이름: 각각의 호스트 이름
    • 서로 간의 연결을 보장해줌
    • 컨테이너를 각각 실행시킬 때와 달리 네트워크를 따로 설정하지 않아도 이름을 가지고 바로 연결이 가능함
  • mongodb://database/vidly
    • database
      • 데이터베이스 서비스
      • mongodb host를 가리킴
    • 데이터베이스 이름을 vidly라고 설정

이미지 생성과 관리

  • docker-compose build
    • build 키로 지정된 것들 대상
  • docker-compose pull
    • docker hub에서 이미지들을 읽어오려고 함
  • docker images
    • 로컬 시스템에 있는 모든 이미지 리스트
    • 각 개별 이미지 앞에 폴더 이름을 prefix로 붙임 (docker hub에서 읽어온 것 제외)

스크린샷 2023-12-26 오후 5 07 58


  • docker-compose images
    • docker-compose로 실행된 컨테이너에 의해 실행되고 있는 이미지들만 보여줌 스크린샷 2023-12-26 오후 5 09 35
  • docker-compose push
    • docker hub로 이미지들을 푸시하려고 함
    • 권한이 있는 것들만 푸시됨 (official image 등은 X)

소프트웨어 시작과 중단

  • docker-compose up
    • build -> create -> start
    • docker-compose create
    • docker-compose start
  • docker-compose down
    • stop -> rm
  • docker-compose stop
  • docker-compose rm


  • docker-compose ls
    • docker-compose를 그룹별로 보여줌 스크린샷 2023-12-26 오후 5 14 03
  • docker-compose ps
    • docker-compose로 실행중인 컨테이너들을 개별로 보여줌 스크린샷 2023-12-26 오후 5 14 26

네트워킹

  • docker끼리 네트워크 연결이 필요한 경우
    • services에 준 이름으로 호스트 이름이 생성됨
    • 내부에 DNS 서버가 하나 생성되어 이름을 내부 IP로 변환해줌
  • 별도로 네트워크를 구성하고 싶담면
    • networks에 네트워크를 나열하고, 네트워크를 적절하게 서비스에 지정해주어야 함
  • docker network ls 스크린샷 2023-12-26 오후 5 15 57

2. voting application을 docker-compose로 실행해보기

앞서 일일히 실행했을 때의 문제점

  • Postgres를 실행하는 부분이 제대로 동작하지 않았음
    • docker run -d --name=db -e POSTGRES_PASSworD=password --network mynetwork postgres
  • worker/Program.cs
    • worker가 redis에 기록된 투표 결과를 postgres에 저장하는 부분
var pgsql = OpenDbConnection("Server=db;Username=postgres;Password=postgres;");
var redisConn = OpenRedisConnection("redis");


  • 이를 해결하려면 Container를 실행할 때 아래 2개의 환경 변수를 넘겨주어야 함
    • POSTGRES_USER: “postgres”
    • POSTGRES_PASSWORD: “postgres”
    • 이것을 docker-compose 환경 설정 파일을 통해 넘기면서 해결!

docker-compse.yml

services:
    redis:
        image: redis:alpine
    db:
        image: postgres:15-alpine
        environments:
            POSTGRES_USER: "postgres"
            POSTGRES_PASSWORD: "postgres"
    vote:
        build: ./vote
        # use python rather than gunicorn for local dev
        # cmd override
        command: python app.py   
        ports:
            - 5001:80
    result:
        build: ./result
        # entrypoint override
        entrypoint: nodemon server.js
        ports:
            - 5002:80
    worker:
        - build: ./worker

실행

git clone https://github.com/learndataeng/example-voting-app.git
cd example-voting-app 

# 청소
docker container rm -f $(docker container ls -aq)
docker image rm -f $(docker image ls -q)

# 확인
docker ps -a
docker images


docker compose -f docker-compose.mac.yml build
docker compose -f docker-compose.mac.yml pull   # official images

docker compose -f docker-compose.mac.yml up 


스크린샷 2023-12-26 오후 5 59 17


  • redis, postgres가 제대로 동작해야 함 (dependecies)
  • postgres -> volume을 걸어야 지속적으로 운영 가능
  • backend, frontend에 network를 각각 걸어주는 것이 좋음 -> 보안 강화

postgres 접속

  • 위 docker-compose가 실행중인 상태에서
docker exec -it --user=postgres example-voting-app-db-1 sh


  • postgres 쉘 접속
psql     #postgres shell 실행
\c     
# You are now connected to database "postgres" as user "postgres".
# postgres db에 postgres user로 연결

\dt
# postgres db 밑의 테이블 보여줌

스크린샷 2023-12-26 오후 6 32 01

스크린샷 2023-12-26 오후 6 32 44

3. docker-compose.yml 개선

networks 정의

스크린샷 2023-12-26 오후 7 24 10

networks:
    back-tier:
        # redis, worker, db, vote, result
    front-tier:
        # vote, result
        # frontend의 애플리케이션은 backend에서도 접근할 수 있어야 함


  • services들에 네트워크 지정
services:
    redis:
        image: redis:alpine
        networks:
            - back-tier

    db:
        image: postgres:15-alpine
        environments:
            POSTGRES_USER: "postgres"
            POSTGRES_PASSWORD: "postgres"
        networks:
            - back-tier
    vote:
        build: ./vote
        # use python rather than gunicorn for local dev
        # cmd override
        command: python app.py   
        ports:
            - 5001:80
        networks:
            - back-tier
            - front-tier
    result:
        build: ./result
        # entrypoint override
        entrypoint: nodemon server.js
        ports:
            - 5002:80
        networks:
            - back-tier
            - front-tier
    worker:
        build: ./worker
        networks:
            - back-tier

networks:
    back-tier:
    front-tier:

volumes 정의

스크린샷 2023-12-26 오후 7 29 06

  • PostgreSQL 서비스의 데이터가 계속해서 저장되어야 함 (persistent)
volumes:
    db-data:


services:
    redis:
        image: redis:alpine
        networks:
            - back-tier

    db:
        image: postgres:15-alpine
        environments:
            POSTGRES_USER: "postgres"
            POSTGRES_PASSWORD: "postgres"
        networks:
            - back-tier
        volumes:
            - db-data:/var/lib/postgresql/data
    vote:
        build: ./vote
        # use python rather than gunicorn for local dev
        # cmd override
        command: python app.py   
        ports:
            - 5001:80
        networks:
            - back-tier
            - front-tier
    result:
        build: ./result
        # entrypoint override
        entrypoint: nodemon server.js
        ports:
            - 5002:80
        networks:
            - back-tier
            - front-tier
    worker:
        build: ./worker
        networks:
            - back-tier

networks:
    back-tier:
    front-tier:

volumes:
    db-data:

기타

  • command: 이미지 Dockerfile의 CMD를 덮어쓰는데 사용
  • entrypoint: 이미지 Dockerfile의 ENTRYPOINT를 덮어쓰는데 사용


  • healthcheck
    • Dockerfile에서 기술가능한 기능
    • docker-compose에서 덮어쓰기 가능
    • 0이 성공, 1 (0이 아닌 값)이 실패


  • depends_on
    • 해당 서비스가 실행되기 위해서 먼저 실행되어야 하는 서비스들을 기술
    • 하나 이상 기술 가능
    • 옵션으로 condition을 추가할 수 있음
      • condition: service_healty : 서비스에 지정된 healthcheck이 건강한가
      • condition: service_started : 시작
      • condition: service_completed_successfully : dependency가 있는 모듈이 성공적으로 성공적으로 끝난 다음에 실행

최종 개선

vote

  • 기존
vote:
    build: ./vote
    ports:
        - 5001:80
    networks:
        - back-tier
        - front-tier


  • 개선
vote:
    build: ./vote

    # cmd override
    command: python app.py

    # 서비스들 간의 의존성이 있을 경우 먼저 실행되어야 하는 서비스들을 여기 기술해야 함
    depends_on:
        redis:
            condition: service_healthy   # option

    # 해당 서비스의 건강을 나타내주는 체크를 기술 (override)
    healthcheck:
        test: ["CMD", "curl", "-f", "http://localhost"]
        interval: 15s
        timeout: 5s
        retries: 3
        start_period: 10s

    volumes:
        - ./vote:/app
    ports:
        - 5001:80
    networks:
        - back-tier
        - front-tier

db

  • 기존
db:
    image: postgres:15-alpine
    networks:
        - back-tier


  • 개선
db:
    image: postgres:15-alpine
    envirionment:
        POSTGRES_USER: "postgres"
        POSTGRES_PASSWORD: "postgres"

    # host volume과 named volume을 사용하고 있음
    volumes:
        - "db-data:/var/lib/postgresql/data"
        - "./healthchecks:/healthchecks"
    healthcheck:
        test: /healthchecks/postgres.sh
        interval: "5s"
    networks:
        - back-tier

volumes:
    db-data:

redis

  • 기존
redis:
    image: redis:alpine
    networks:
        - back-tier


  • 개선
redis:
    image: redis:alpine
    volumes:
        - "./healthchecks:/healthchecks"
    healthcheck:
        test: /healthchecks/redis.sh
        interval: "5s"
    networks:
        - back-tier

worker

  • 기존
worker:
    build: ./worker
    networks:
        - back-tier


  • 개선
worker:
    build: 
        context: ./worker
    depends_on:
        redis:
            condition: service_healthy
        db:
            condition: service_healthy
    networks:
        - back-tier
  • 위 빌드 방식을 쓰는 경우 더 많은 빌드 관련 정보를 넘겨줄 수 있음
build:
    context: ./myapp
    dockerfile: Dockerfile.dev
    args:
        ENVIRONMENT: development

result

  • 기존
result:
    build: ./result
    ports:
        - 5002:80
    networks:
        - front-tier
        - back-tier


  • 개선
result:
    build: ./result
    entrypoint: nodemon server.js
    depends_on:
        db:
            condition: service_healthy
    volumes:
        - ./result:/app
    ports:
        - 5002:80
        - 5858:5858
    networks:
        - front-tier
        - back-tier

4. Airflow Docker-compose

docker-compose.yaml

  • version
  • x-airflow-common
    • airflow-common 이라는 별칭 정의
    • 여러 서비스에서 공유하는 공통 구성을 정의한 것
    • 보통 yaml에서 anchor라고 부르며, YML 파일 블록을 나중에 계승이라는 형태로 재사용 가능하게 해줌
    • version, services, volumes, networks를 제외한 최상위 레벨 키워드는 모두 anchor
  • services
    • postgres
    • redis
    • airflow-webserver
    • airflow-scheduler
    • airflow-worker
    • airflow-triggerer
    • airflow-init
  • volumes
    • postgres-db-volume

x-airflow-common

x-airflow-common:
  &airflow-common    
  image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.5.1}
  environment:
    &airflow-common-env
    AIRFLOW__CORE__EXECUTOR: CeleryExecutor
    AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    # For backward compatibility, with Airflow <2.3
    AIRFLOW__CORE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
    AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow
    AIRFLOW__CELERY__BROKER_URL: redis://:@redis:6379/0
    AIRFLOW__CORE__FERNET_KEY: ''
    AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true'
    AIRFLOW__CORE__LOAD_EXAMPLES: 'true'
    AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth,airflow.api.auth.backend.session'
    _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:- yfinance}
  volumes:
    - ${AIRFLOW_PROJ_DIR:-.}/dags:/opt/airflow/dags
    - ${AIRFLOW_PROJ_DIR:-.}/logs:/opt/airflow/logs
    - ${AIRFLOW_PROJ_DIR:-.}/plugins:/opt/airflow/plugins
  user: "${AIRFLOW_UID:-50000}:0"
  depends_on:
    &airflow-common-depends-on
    redis:
      condition: service_healthy
    postgres:
      condition: service_healthy
  • &airflow-common
    • 별칭
    • &airflow-common 아래에 있는 모든 내용을 이제 뒤에서부터 airflow-common이라고 부르겠다는 뜻
  • image:
    • 같은 airflow 이미지가 모든 서비스 기본 이미지로 사용됨
  • volumes:
    • 모두 세 개의 volume을 공유함 (host volumes)
  • depends_on:
    • 모두 redis와 postgres가 정상동작할 때까지 대기

airflow-scheduler 서비스

airflow-scheduler:
    <<: *airflow-common
    command: scheduler
    healthcheck:
      test: ["CMD-SHELL", 'airflow jobs check --job-type SchedulerJob --hostname "$${HOSTNAME}"']
      interval: 10s
      timeout: 10s
      retries: 5
    restart: always
    depends_on:
      <<: *airflow-common-depends-on
      airflow-init:
        condition: service_completed_successfully
  • <<: *으로 anchor 사용