What is Docker Compose?

Docker Compose란? 무엇일까요.

Docker Compose는 다중 컨테이너 애플리케이션을 정의 공유할 수 있도록 개발된 도구로
단일 명령을 사용하여 모두 실행 또는 종료할 수 있도록 개발된 도구

위 말을 이해하기 쉽게 풀어서 정리해 보겠습니다.

다중 컨테이너 애플리케이션을 정의

우리는 서비스를 서버 위에 실행시킬 때 가끔 여러 개의 서비스들이 필요할 경우가 있습니다.
예를 들어 새로 만든 application가 사용하는 database는 mysql을 사용한다고 가정한다면
mysql를 설치/실행 한 뒤 우리가 만든 application을 서버 위에 실행을 시켜야 합니다.

그런데 docker-compose는 여러 개의 서비스를 한 번에 정의를 가능하게 합니다.
위에 예시로 들었던 서비스를 실행하려면 아래와 같이 정의만 해주면 됩니다.

services:
  nhn_app:
    image: nhn_app_v2

  nhn_mysq:
    image: mysql

이렇게 하나의 문서에 여러 개의 컨테이너를 정의할 수 있습니다.

단일 명령을 사용하여 모두 실행 또는 종료

docker-compose up이라는 명령어 하나로 문서에 정의한 서비스들이 한꺼번에 컨테이너로 실행되고
반대로 docker-compose down이라는 명령어로 정의한 모든 서비스를 내릴 수도 있습니다.
물론 먼저 실행되어야 하는 순서까지 지정이 가능합니다.
이렇게 단일 명령을 사용하여 편하게 서비스를 실행 또는 종료시킬 수 있는 장점이 있습니다.

Docker Compose의 특징

단일 호스트의 여러 격리된 환경

Compose는 프로젝트 이름을 사용하여 환경을 서로 격리하고 여러 다른 콘텍스트에서 이 프로젝트 이름을 사용하여 접근을 합니다.

예를 들어 application이라는 서비스를 실행시킬 때 my_application, your_application 형식으로 여러 개를 서로 격리하여 서비스가 가능합니다.

프로젝트 이름은 기본으로 실행한 폴더명이 기준이 되며 별도로 지정이 가능합니다.

$ docker-compose -p my up 
$ docker-compose -p your up

위와 같이 -p 옵션으로 프로젝트명을 주어 실행이 가능한데 하나의 애플리케이션을 두 개의 격리된 환경으로 제공해줍니다.

컨테이너 생성 시 볼륨 데이터 보존

컨테이너 생성 시 볼륨 데이터 보존하여 데이터가 휘발되지 않도록 처리해줍니다.
컨테이너 내부에서 생성하여 사용하는 파일 볼륨을 로컬 경로와 공유하여 실수로 컨테이너가 종료되더라도 재실 행시 같은 볼륨을 유지해주어 컨테이너를 내렸다가 다시 실행시키더라도 이전에 사용했던 환경 그대로 사용이 가능한 장점이 있습니다.

변경된 컨테이너만 재생성

컨테이너를 만드는 데 사용되는 구성을 캐시 하여 변경되지 않은 서비스를 다시 시작하면 Compose는 기존 컨테이너를 다시 사용합니다.

변수 및 환경 간 구성 이동

Compose 파일의 변수를 지원하여 다양한 환경 또는 다른 사용자에 맞게 컴포지션 커스텀이 가능합니다.
해당 내용은 아래 Docker Compose Environment 에서 자세히 다루도록 하겠습니다.

서비스 정의

다음은 Dockerfile을 compose로 변환하여 간략하게 다중 컨테이너 애플리케이션을 compose로 정의해보겠습니다.

App Service 정의

$ docker run -dp 3000:3000 \
  -w /app -v ${PWD}:/app \
  --network todo-app \
  -e MYSQL_HOST=mysql \
  -e MYSQL_USER=root \
  -e MYSQL_PASSWORD=secret \
  -e MYSQL_DB=todos \
  node:12-alpine \
  sh -c "yarn install && yarn run dev"

상단의 Docker실행 명령어를 아래처럼 Compose로 작성이 가능합니다.

version: "3.8"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos

compose 장점 중의 하나로는 상대 경로 입력이 가능하며 yaml로 구성하여 설정 파일의 가독성이 좋아집니다.

MySQL 서비스 정의

$ docker run -d \
  --network todo-app --network-alias mysql \
  -v todo-mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=todos \
  mysql:5.7
version: "3.8"

services:
  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment: 
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

volumes:
  todo-mysql-data:

최종본

version: "3.8"

services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos

  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment: 
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

volumes:
  todo-mysql-data:

이렇게 파일 하나에 application, mysql 두 개의 설정을 한꺼번에 정의하여 실행환경을 빠르게 구성할 수 있습니다.
하나의 설정 파일로 관리가 되어 서비스를 올리고 내리는걸 빠르고 쉽게 할 수 있습니다.
여러 개의 서버에 같은 환경을 제공할 때 유용하게 사용이 가능합니다.

Dockerfile을 compose로 변환해주는 사이트가 있습니다.
100% 완벽하게 변환해주진 않지만 굉장히 유용하게 사용할 수 있는 사이트라 추천해드립니다.
https://www.composerize.com/

주요 명령어

이제 가장 중요한 docker의 명령어들을 소개해드리도록 하겠습니다.
docker-compose에서 사용하는 명령어들은 기존 docker에 있는 명령어들과 비슷합니다.
자주 사용되고 유용한 명령어에 대해서 간략하게 정리를 해보았습니다.
테스트로 사용될 docker-compose.yml 파일은 위에서 예제로 정의한 YAML을 그대로 사용하겠습니다.

예제들은 docker-compose 로 실행하였지만 현재 버전의 docker는 compose가 내장되어있어 docker compose명령어로도 실행이 가능합니다.

up

docker-compose.yml 파일의 내용에 따라 이미지를 빌드하고 서비스를 실행합니다.

$ docker-compose -p nhn up -d
Creating network "nhn_default" with the default driver
Creating nhn_mysql_1 ... done
Creating nhn_app_1   ... done

up 명령어로 compose를 실행 시의 단계별 진행사항은 다음과 같습니다.

  1. 서비스를 띄울 네트워크 설정
  2. 필요한 볼륨 생성(혹은 이미 존재하는 볼륨과 연결)
  3. 필요한 이미지 풀(pull)
  4. 필요한 이미지 빌드(build)
  5. 서비스 의존성에 따라 서비스 실행

options

  • -d: 서비스 백그라운드로 실행. (docker run에서의 -d와 같음)
  • --force-recreate: 컨테이너를 지우고 새로 생성.
  • --build: 서비스 시작 전 이미지를 새로 생성
  • -f: 기본으로 제공하는 docker-compose.yml이 아닌 별도의 파일명을 실행할 때 사용
  • docker-compose -f docker-compose.yml -f docker-compose-test.yml up 형태로 두 개의 파일 실행도 가능
  • YAML을 두 개 이상 설정할 경우 앞에 있는 설정보다 뒤에 있는 파일이 우선

down

실행 중인 서비스를 삭제합니다.
컨테이너와 네트워크를 삭제하며, 옵션에 따라 볼륨도 같이 삭제할 수 있습니다.

$ docker-compose down
Stopping nhn_mysql_1 ... done
Removing nhn_app_1   ... done
Removing nhn_mysql_1 ... done
Removing network nhn_default

options

  • -v, --volume: 볼륨까지 같이 삭제
    • DB 데이터 초기화하는데 용이함
    • 모든 설정을 초기화하고 새로 시작하는 데 사용

stop, start

서비스를 멈추거나, 멈춰 있는 서비스를 시작합니다.

$ docker-compose stop
Stopping nhn_mysql_1 ... done

$ docker-compose start
Starting app   ... done
Starting mysql ... done

ps

현재 환경에서 실행 중인 각 서비스의 상태를 표시합니다.

   Name                  Command               State           Ports
---------------------------------------------------------------------------
nhn_app_1     docker-entrypoint.sh sh -c ...   Exit 1
nhn_mysql_1   docker-entrypoint.sh mysqld      Up       3306/tcp, 33060/tcp

exec

실행 중인 컨테이너에서 명령어를 실행합니다.

명령어는 다음과 같은 패턴으로 실행됩니다.
docker-compose exec {정의한 service name} {실행될 명령어}

$ docker-compose exec app ./upload_logs.sh

$ docker-compose exec mysql mysql -uroot -psecret todos

run

docker run과 마찬 가지로 특정 명령어를 일회성으로 실행합니다.
run은 exec와 비슷한 역할을 실행하지만 다른 점은 컨테이너 재실행의 여부입니다.
run은 실행 시에 새로운 컨테이너를 띄우는 반면 exec는 실행되어있는 컨테이너에 접속합니다.
exec는 프로세서를 실행시켜 놓을 때 사용되고 run은 batch성 작업에 특화되어있습니다.

options

  • --rm : 해당 명령어가 종료된 뒤 컨테이너도 종료

logs

output으로 나온 log들을 확인할 때 사용합니다.
docker의 logs와 마찬가지로 --follow(혹은 -f)를 하여 실시간으로 나오는 로그 확인이 가능합니다.

$ docker-compose logs -f
Attaching to docker_app_1, docker_mysql_1
app_1    | yarn install v1.22.5
app_1    | info No lockfile found.
app_1    | [1/4] Resolving packages...
app_1    | [2/4] Fetching packages...
app_1    | [3/4] Linking dependencies...
app_1    | [4/4] Building fresh packages...
app_1    | success Saved lockfile.
app_1    | Done in 0.14s.
app_1    | yarn run v1.22.5
app_1    | error Couldn't find a package.json file in "/app"
app_1    | info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
mysql_1  | 2021-01-05 04:43:03+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.32-1debian10 started.

options

  • -f, --follow: 실시간 로그 출력

config

docker-compose에 최종적으로 적용된 설정을 보여줍니다.
docker-compose -f docker-compose.yml -f docker-compose-test.yml up 형태처럼 여러 개의 YAML파일을 실행시켰을 경우
최종적으로 적용된 설정을 확인하기 좋은 옵션입니다.

$ docker-compose config
services:
  app:
    command: sh -c "yarn install && yarn run dev"
    environment:
      MYSQL_DB: todos
      MYSQL_HOST: mysql
      MYSQL_PASSWORD: secret
      MYSQL_USER: root
    image: node:12-alpine
    ports:
    - published: 3000
      target: 3000
    volumes:
    - /Users/ssabae/project/docker-compose-test/nhn:/app:rw
    working_dir: /app
  mysql:
    environment:
      MYSQL_DATABASE: todos
      MYSQL_ROOT_PASSWORD: secret
    image: mysql:5.7
    volumes:
    - todo-mysql-data:/var/lib/mysql:rw
version: '3.8'
volumes:
  todo-mysql-data: {}

Docker Compose Environment

docker compose의 장점으로는 하나를 정의해놓으면 여러 군데서 동일한 동작을 보장하는 것입니다.
하지만 각 실행 환경에 따라 변경되어야 하는 옵션들이 있는데 이것들 때문에 일일이 파일을 수정하는 건 비효율적입니다.
그래서 각 실행하는 환경마다 환경변수 파일을 정의하여 동일한 compose 파일로 각각의 환경과 동적인 옵션으로 실행이 가능합니다.

. env file

기본적으로 docker-compose up을 한 상태에서 . env 파일을 찾아 파일 내부에 있는 값을 환경 변수로 사용합니다.

$ cat .env     # 환경변수 파일
TAG=v1.5

$ cat docker-compose.yml   # compose 파일
version: '3'
services:
  web:
    image: "webapp:${TAG}"$

$ docker-compose config    # 실제 적용된 옵션

version: '3'
services:
  web:
    image: 'webapp:v1.5'


$ export TAG=v2.0
$ docker-compose config

version: '3'
services:
  web:
    image: 'webapp:v2.0'

여러 파일에서 동일한 환경 변수를 설정할 때 사용할 값을 선택하기 위해 Compose에서 사용하는 우선순위는 다음과 같습니다.

  1. Compose file
  2. Shell environment variables
  3. Environment file
  4. Dockerfile
  5. Variable is not defined

여러 개의 복잡합인 상황에 따라서 의도하지 않은 설정이 들어가지 않게 우선순위를 잘 파악해보는 것이 중요합니다.

상황에 따른. env 적용 예제

dev, prod, local 환경에 따라서
.env.dev, .env.prod, .env.local 형식으로 할당을 줄 수 있는데
--env-file 옵션으로 환경변수 파일 지정이 가능합니다.

$ docker-compose --env-file ./config/.env.dev up

CLI Environment

아래의 명령어들은 각각 그 아래의 compose 파일이 일치하게 맵핑됩니다.

$ docker run -e VARIABLE1

web:
  environment:
    - VARIABLE1
$ docker-compose run -e DEBUG=1 web python console.py

web:
  environment:
    - DEBUG=1

Docker Cli Environment Variable

Compose CLI에서 실행할 때의 참고 변수 키 값 정의가 가능합니다.
https://docs.docker.com/compose/reference/envvars/

Compose Network 설정 옵션

version: "3.8"
services:
  app:
    build: .
    ports:
      - "8000:8000"
  db:
    image: postgres
    ports:
      - "8001:5432"

위 Compose를 실행했을 때의 네트워크가 생성되는 과정은 아래와 같습니다. ( myapp이라는 project name으로 가정 )

  1. myapp_default 네트워크 생성
  2. web 컨테이너가 생성. (web이라는 이름으로 myapp_default에 접속)
  3. db 컨테이너가 생성. (db이라는 이름으로 myapp_default에 접속)

네트워크가 구성된 이후 postgres://db:5432 주소로 접속 가능합니다.
내부 컨테이너끼리는 postgres://db:5432로 접속하고 외부에선 postgres://{DOCKER_IP}:8001로 접속해야 합니다.
컨테이너 업데이트를 위해 다시 시작할 경우에는 컨테이너 이름은 같지만 다른 IP로 네트워크가 생성되어
IP 베이스는 IP를 다시 찾아서 연결하여야 하고(재실행 시 IP 변경) 다른 컨테이너에선 기존에 접속했던 컨테이너 이름으로 재 연결을 시도하여야 합니다.

links

links는 다른 서비스의 컨테이너와 연결시켜주는 역할을 하고 있습니다.
이름만 지정하거나 {name}:{alias}형식으로 지정할 수 있습니다.

version: "3"
services:
  web:
    build: .
    links:
      - "redis"
      - "db:database"
  db:
    image: postgres

links는 레거시 기능으로 공식 페이지에서 권장하지 않는 방법입니다.
depend_on처럼 디펜던시 관계가 맺어지며 네트워크랑 같이 쓰일 때는 하나 이상의 네트워크가 관리되기 때문에 네트워크만 사용하길 권장합니다.

Custom Network

Custom Network를 통해 좀 더 복잡한 네트워크 토폴로지를 만들 수 있습니다.
아래 간단한 예시를 통해서 설명을 드리겠습니다.

version: "3"
services:
  proxy:
    build: ./proxy
    networks:
      - frontend
  app:
    build: ./app
    networks:
      - frontend
      - backend
  db:
    image: postgres
    networks:
      - backend
networks:  # top-level network
  frontend:
    driver: custom-driver-1 # Use a custom driver
  backend:
    driver: custom-driver-2 # Use a custom driver which takes special options
    driver_opts:
      foo: "1"
      bar: "2"

top-level의 network 옵션에서 커스텀하게 지정이 가능합니다.

위와 같이 설정을 하면 app에서만 proxy, db에 접근 가능하고 db와 proxy는 서로 접근이 불가능하게 설정이 됩니다.

IPv4, IPv6

IPv6를 사용하기 위해서는 top-level network에 enable_ipv6 옵션이 반드시 들어가야 합니다.

version: "2.4"

services:
  app:
    image: nginx:alpine
    networks:
      app_net:
        ipv4_address: 172.16.238.10
        ipv6_address: 2001:3984:3989::10

networks:
  app_net:
    enable_ipv6: true
    ipam:
      driver: default
      config:
        - subnet: "172.16.238.0/24"
        - subnet: "2001:3984:3989::/64"

Default Network

default를 이용하여 자체 네트워크를 지정하면서 앱 전체의 기본적인 네트워크 구성이 가능합니다.

version: "3"

services:
  web:
    build: .
    ports:
      - "8000:8000"
  db:
    image: postgres

networks:
  default:
    driver: custom-driver-1    # Use a custom driver

Use Pre Existing Network

기존의 외부의 네트워크를 사용하려면 다음과 같이 external을 사용하여 명시해야 합니다.

networks:
  default:
    external:
      name: my-pre-existing-network

부록 : Compose Version별 특성

다음은 버전별 특성에 대해서 정리된 공식 홈페이지 내용을 요약해서 정리해본 것을 공유드립니다.

https://docs.docker.com/compose/compose-file/compose-versioning/

versioning

docker-compose.yml상단에 작성하는 compose file format의 버전을 명시해주어야 합니다.

version: "3.8"
...

versioning은 버전별로 다음과 같은 특징이 있습니다.

  • Version 1에서는 버저닝을 생략
  • Version 2부터 마이너 버전(2.x)까지 설정해야 함 (생략 시 2.0으로 적용된다.)
  • Version 3은 도커 스웜과 같이 사용되도록 디자인됨

version dependency

다음은 각 docker 버전 별 사용 가능한 docker-compose.yml 버전을 표로 작성해 보았습니다.
Compose version은 3 버전부터는 직접 명시되어있는 게 없어 빈칸으로 두었습니다.
필요하신 분은 릴리즈 노트에서 찾아보시면 쉽게 찾아보실 수 있습니다.

Compose file formatDocker Engine releaseCompose Version

3.8 19.03.0+ -
3.7 18.06.0+ -
3.6 18.02.0+ -
3.5 17.12.0+ -
3.4 17.09.0+ -
3.3 17.06.0+ -
3.2 17.04.0+ -
3.1 1.13.1+ -
3.0 1.13.0+ -
2.4 17.12.0+ 1.21.0+
2.3 17.06.0+ 1.16.0+
2.2 1.13.0+ 1.13.0+
2.1 1.12.0+ 1.9.0+
2.0 1.10.0+ 1.6.0+
1.0 1.9.0.+  

 

설치

 

'Docker' 카테고리의 다른 글

Docker Image 만들기  (0) 2022.02.22
podman Error Message ERRO[0000]  (0) 2022.01.13
Window10 Podman 설치  (0) 2021.12.26
Podman(PodMANger tool)  (0) 2021.12.26
Docker vs Podman  (0) 2021.12.26

+ Recent posts