도커 컴포즈

image

도커 컴포즈는 단일 서버에서 여러 컨테이너를 프로젝트 단위로 묶어서 관리하기 위해 사용된다. 단일 서버에서 여려 컨테이터를 띄우기 위해서는 커맨드 라인을 통해서 하나씩 도커 컨테이너를 실행했다. 하지만 도커 컴포즈에서는 docker-compose.yml 이라는 YAML 파일을 통해 컨테이너들을 명시적으로 관리하는 것이 가능하다. 또한, 프로젝트 단위로 묶어서 관리하기 때문에 프로젝트 별로 조금더 격리된 환경을 사용하게 만들수 있다.

  • 프로젝트 단위로 도커 네트워크와 볼륨 관리 가능
    • 도커 컴포즈 내에 볼륨도 있고 네트워크도 있는 구조
  • 프로젝트 내 서비스 간 의존성 정의 가능
    • 프로젝트 내에서 서비스2을 띄우기 위해서 서비스1을 먼저 띄워야 한다와 같은 의존성을 관리
  • 프로젝트 내 서비스 디스커버리 자동화
    • 의존성이 있는 서비스를 서로 호출할 때 내부 도메인 명으로 호출 가능
    • 도커 엔진에서 컨테이너를 관리할 때는 브릿지 네트워크 모드에서 netalias를 통해 특정 이름으로 서비스를 호출햇지만, 도커 컴포즈 내에서는 각각의 서비스 명으로 해당 서비스를 네트워크 상에서 호출 가능
    • 이렇게 각각의 서비스 명으로 호출하는 것을 서비스 디스커버리라고 함
  • 손 쉬운 컨테이너 수평 확장
    • 서비스 하나가 여러 컨테이너를 다루게 되는데 동일한 역할을 수행하는 여러 컨테이너를 수평 확장가능

프로젝트 / 서비스 / 컨테이너

방금 말했던거 처럼 도커 컴포즈는 컨테이너를 프로젝트 단위로 관리하게 해준다. 이 프로젝트는 하나의 워크스페이스라고 생각하면 된다.

image

  • 프로젝트 (Project)
    • 도커 컴포즈에서 다루는 워크스페이스 단위
    • 함께 관리하는 서비스 컨테이너의 묶음
    • docker-composer.yaml은 하나의 프로젝트를 명세함
    • 프로젝트 단위로 기본 도커 네트워크가 생성 됨
  • 서비스 (Service)
    • 도커 컴포즈에서 컨테이너를 관리하기 위한 단위
    • scale을 통해 서비스 컨테이너의 수 확장 가능
  • 컨테이너 (Container)
    • 도커에서 프로세스가 실행되는 기본단위
    • 서비스를 통해 컨테이너 관리

docker-compose.yml 작성법

이제 docker-compose.yml 파일의 구조에 대해 알아보자. docker-compose.yml은 version, services, networks, volumes 총 4개의 최상위 옵션을 가진다.

version: '3'

services:
  subkey1:
    ...
  subkey2:
    ...
    
networks:
  ...

volumes: 
  ...
  • version
    • 2021년 11월 기준 버전 3.9가 최신
    • 가능한 최신 버전 사용 권장
    • 도커 엔진 및 도커 컴포즈 버전에 따라 yaml의 구조가 다르기 때문에 호환성 매트릭스 참조할 것 (호환성 매트릭스는 https://docs.docker.com/compose/compose-file/compose-file-v3/ 참조)
    • 버전 3부터 Docker Swarm과 호환 -> Swarm 서비스를 docker-compose.yml로 정의 가능
  • services
    • 프로젝트 내에 구성되는 여러 서비스들을 subkey들을 활용해 관리함
  • networks
    • 프로젝트마다 독립적으로 구성됨
    • 프로젝트에서 사용할 네트워크 목록 명시
    • 명시하지 않아도 브릿지 네트워크가 <프로젝트명>_default라는 이름으로 생성됨 (프로젝트명도 명시하지 않았다면 폴더이름을 프로젝트명으로 사용함)
  • volumes
    • 프로젝트마다 독립적으로 구성됨
    • 프로젝트에서 사용할 볼륨 목록 명시
version: "3.9"
services:
  web:
    # container_name: 'web'
    build: .
    ports:
    - "5000"
  redis:
    image: "redis:alpine"

예시로 위의 docker-compose.yml 파일을 해석해보면, 도커 컴포즈 3.9 파일 포맷을 사용하고 있고, 서비스로는 web 서비스와 redis 서비스 두 가지가 있는데, redis 서비스는 redis:alpine 이미지를 기반으로 만들어지고, web 서비스는 이미지를 명시하지 않고 build 옵션으로 현재 디렉토리에서 Dockerfile을 찾아서 빌드하고 5000번 포트를 사용하라는 의미이다.

여기서 주의할 점은 포트바인딩을 할때 5000:5000과 같이 호스트 포트를 지정하면 나중에 scaling을 할 때 같은 포트를 두개 이상의 컨테이너가 사용하려고 충돌을 일으킨다. 즉, 호스트 포트를 지정하면 스케일링 불가능해진다. container_name도 마찬가지 이유로 주의를 해야한다.

version: '3.9'

services:
  db:
    image: mysql:5.7
    volumes:
    - db:/var/lib/mysql
    restart: always
    environment:
    - MYSQL_ROOT_PASSWORD=wordpress
    - MYSQL_DATABASE=wordpress
    - MYSQL_USER=wordpress
    - MYSQL_PASSWORD=wordpress
    networks:
    - wordpress

  wordpress:
    depends_on:
    - db
    image: wordpress:latest
    ports:
    - "8000:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
    networks:
    - wordpress

volumes:
  db: {}

networks:
  wordpress: {}

조금 더 복잡한 yml 파일을 해석해보자. 서비스로 db 서비스와 wordpress 서비스 두가지가 있고, db 라는 이름의 볼륨을 만들었고 db 서비스의 db:/var/lib/mysql에 마운트 되었다. wordpress라는 이름의 네트워크를 만들어 각 서비스에 workdpress 네트워크를 연결시켰다. 볼륨과 네트워크는 지정하지 않으면 자동으로 만들어주기는 한다. 다른 드라이버를 사용하거나 드라이버 옵션을 지정하고 싶으면 {} 안에 옵션을 지정해주면 된다.

services에 있는 것들은 대부분 docker run에서 제공하는 옵션들이다. image 옵션으로 실행시킬 컨테이너의 이미지를 지정한다. volumes 옵션에서는 db 볼륨을 컨테이너의 /var/lib/mysql 경로에 마운트 한다. restart 키는 컨테이너의 재시작 전략을 관리하며, always 옵션을 통해 컨테이너가 꺼지더라고 항상 재시작한다. environment 옵션으로 환경변수를 전달한는데, key=value 형태 또는 key: column:value의 오브젝트 형태로 지정할 수 있다. networks 옵션은 우리가 만든 네트워크에 해당 서비스를 연결한다. 여러개의 네트워크에도 연결이 가능하다. depends_on으로 컨테이너가 실행되는 순서를 정할 수 있는데, 위에서 wordpress 서비스는 db 서비스가 띄워지고 난 다음에 실행되어야 한다라고 정의했다. 실제로 docker-compose up을 하면 db 서비스부터 실행된다. 주의할 것은 해당 컨테이너가 실행되었음을 보장하지만 준비되었음을 보장하지는 않는다. ports 옵션은 docker run 의 -p 옵션과 동일하다.

  • 도커 스왐 (Docker Swarm)
    • 여러 서버를 기반으로 스왐 클러스터를 형성하여 컨테이너를 관리하는 컨테이너 오케스트레이션 시스템
    • 쿠버네티스와 동일 목적으로 만들어졌지만 인기를 끌지 못함

docker-compose 명령어

아래 명령어들은 docker-compose v2 이상에서만 사용이 가능하다.

# 실행중인 프로젝트 목록 확인
$ docker-compose ls

# 전체 프로젝트 목록 확인
$ docker-compose ls -a

# Foreground로 도커 컴포즈 프로젝트 실행 (docker run 과 유사)
# 이미지를 받아오고 빌드하고 컨테이너를 실행
$ docker-compose up

# Background로 도커 컴포즈 프로젝트 실행
$ docker-compose up -d

# 프로젝트 이름 my-project로 변경하여 도커 컴포즈 프로젝트 실행
$ docker-compose -p my-project up -d

# 프로젝트 내 컨테이너 및 네트워크 종료 및 제거
$ docker-compose down

# 프로젝트 내 컨테이너, 네트워크 및 볼륨 종료 및 제거
$ docker-compose down -v

# web 서비스를 3개로 확장
$ docker-compose up --scale web=3

# 프로젝트 내 서비스 로그 확인
$ docker-compose logs
$ docker-composer logs -f # 실시간

# 프로젝트 내 컨테이너 이벤트 확인
$ docke-compose events

# 프로젝트 내 이미지 목록
$ docker-compose images
$ docker-compose -p <프로젝트명> images

# 프로젝트 내 컨테이너 목록
$ docker-compose ps
$ docker-compose -p <프로젝트명> ps 

# 프로젝트 내 실행중인 프로세스 목록
$ docker-compose top
$ docker-compose -p <프로젝트명> top

이제 아래 파일들을 가지고 간단한 docker-compose를 만들어보자.

# app.py
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install flask, redis
EXPOSE 5000
COPY . .
CMD ["flask", "run"]
# Dockerfile
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]
# docker-compose.yml
version: "3.9"
services:
  web:
    build: .
    ports:
    - "5000"
  redis:
    image: "redis:alpine"

web 서비스 만드는 과정

image

redis 서비스 만드는 과정. Creating build_web_1 에서 build는 프로젝트명, web은 서비스명, 1은 컨테이너 인덱스이다.

image

도커 컴포즈를 이제 백그라운드 모드로 실행하고 목록을 보자. 참고로 -p 옵션으로 프로젝트명을 바꿧다.

image

목록을 보자.

image

이런식으로 다른 명령어도 사용하면 된다.


주요 사용 목적

도커 컴포즈는 주로 아래와 같은 3가지 이유 때문에 사용된다.

  • 로컬 개발 환경 구성
    • 특정 프로젝트의 로컬 개발 환경 구성 목적으로 사용
    • 각 사람마다 프로젝트의 의존성(Redis, MySQL, Kafka 등)을 쉽게 띄울 수 있음
  • 자동화된 테스트 환경 구성
    • CI/CD 파이프라인 중 쉽게 격리된 테스트 환경을 구성하여 테스트 수행 가능
  • 단일 호스트 내 컨테이너를 선언적 관리
    • 단일 서버에서 컨테이너를 관리할 때 YAML 파일을 통해 선언적으로 관리 가능
    • 파일을 통해서 변경사항 추적 가능