이번 포스트에서는 도커 컴포즈에 대해 정리하겠습니다.
도커 컴포즈란?
도커 컴포즈
는 단일 서버에서 여러개의 컨테이너를 하나의 서비스로 정의해 컨테이너의 묶음으로 관리할 수 있는 작업 환경을 제공하는 관리 도구입니다.
도커 컴포즈를 사용하는 이유
여러 개의 컨테이너가 하나의 어플리케이션으로 동작할 때 도커 컴포즈
를 사용하지 않는다면, 이를 테스트하려면 각 컨테이너를 하나씩 생성해야 합니다. 예를 들면, 웹 어플리케이션을 테스트하려면 웹 서버 컨테이너, 데이터베이스 컨테이너 두 개의 컨테이너를 각각 생성해야 합니다.
즉, 아래와 같이 두개의 run 명령어를 입력해야합니다.
$ docker run --name wordpress_db -d mysql:8
$ docker run -d -p 8080:80 \
--link wordpress_db:mysql --name seunghwan_wordpress \
wordpress:latest
위의 예제를 실행하면 wordpress와 mysql 컨테이너를 생성합니다. 이처럼 여러 개의 컨테이너로 구성된 어플리케이션을 구축하기 위해 run 명령어를 여러 번 사용할 수 있지만 각 컨테이너가 제대로 동작하는지 확인하는 테스트 단계에서는 이런식으로 일일히 여러개의 컨테이너를 실행하기는 번거롭습니다. 매번 run 명령어에 옵션을 설정해 CLI로 컨테이너를 생성하기보다는 여러 개의 컨테이너를 하나의 서비스로 정리해 컨테이너 묶음으로 관리할 수 있다면 좀 더 편리할 것입니다. 이를 위해 도커 컴포즈는 컨테이너를 이용한 서비스의 개발과 CI를 위해 여러 개의 컨테이너를 하나의 프로젝트로서 다룰 수 있는 작업 환경을 제공합니다.
도커 컴포즈
는 여러 개의 컨테이너의 옵션과 환경을 정의한 파일을 읽어 컨테이너를 순차적으로 생성하는 방식으로 동작합니다. 도커 컴포즈의 설정 파일은 도커 엔진의 run 명령어의 옵션을 그대로 사용할 수 있으며, 각 컨테이너의 의존성, 네트워크, 볼륨 등을 함께 정의할 수 있습니다. 또한 스웜모드의 서비스와 유사하게 설정 파일에 정의된 서비스의 컨테이너 수를 유동적으로 조절할 수 있으며 컨테이너의 서비스 디스커버리도 자동으로 이뤄집니다. 그래서 컨테이너의 수가 많아지고 정의해야 할 옵션이 많아지고 정의해야 할 옵션이 많아진다면 도커 컴포즈를 사용하는 것이 좋습니다.
도커 컴포즈 설치
리눅스에서는 아래 명령어로 도커 컴포즈의 깃허브 저장소에서 현재 날짜 기준으로 최신 버전인 2.16버전을 설치할 수 있습니다.
다른 OS에서의 설치방법은 도커 컴포즈 도큐먼트에서 확인하실 수 있습니다.
$ curl -SL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
설치가 완료되었는지 확인하기 위해 아래 명령어로 도커 컴포즈의 버전을 확인해줍니다.
$ docker-compose -v
docker-compose.yml 작성 및 활용
도커 컴포즈의 사용법을 알아보기 위해 아래의 도커 run 명령어를 docker-compose.yml
파일로 변환하고 컨테이너를 생성해보겠습니다.
먼저, 아래의 명령어는 CLI에서 docker run 명령어를 이용해서 mysql을 만들고, wordpress를 생성해서 mysql을 연결하는 명령어입니다.
docker run 명령어로 컨테이너 생성
$ docker run -d --name wordpress_db \
--network seunghwan_network \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=seosh817 \
-e MYSQL_DATABASE=seosh817 \
-e MYSQL_USER=seosh817 \
-e MYSQL_PASSWORD=seosh817 \
-v mysql:/var/lib/mysql \
--restart unless-stopped \
mysql:8
$ docker run -d --name seunghwan_wordpress \
--network seunghwan_network \
-p 8080:80 \
--link wordpress_db:mysql \
-e WORDPRESS_DB_HOST=db:3306 \
-e WORDPRESS_DB_USER=seosh817 \
-e WORDPRESS_DB_PASSWORD=seosh817 \
-e WORDPRESS_DB_NAME=seosh817 \
--restart unless-stopped \
wordpress:latest
실행결과
docker-compose.yml로 도커 컴포즈 프로젝트 실행
위에서 docker run 명령어로 컨테이너를 실행하던 것을 docker compose.yml로 변경해 보겠습니다.
version: '3.9'
services:
db:
image: mysql:8
volumes:
- db:/var/lib/mysql
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=seosh817
- MYSQL_DATABASE=seosh817
- MYSQL_USER=seosh817
- MYSQL_PASSWORD=seosh817
networks:
- wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: unless-stopped
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: seosh817
WORDPRESS_DB_PASSWORD: seosh817
WORDPRESS_DB_NAME: seosh817
networks:
- wordpress
volumes:
db: {}
networks:
wordpress: {}
* yaml 파일에서 들여쓰기를 할 때 탭(Tab)은 도커 컴포즈가 인식하지 못하므로 2개의 공백(Space)을 사용해서 하위 항목을 구분해야 합니다.
도커 컴포즈를 사용하려면 컨테이너 설정을 저장해 놓은 yaml 파일이 필요합니다. 그러므로 기존에 사용하던 run 명령어를 yaml 파일로 변환하는 것이 도커 컴포즈 사용법의 대부분입니다.
위의 docker-compose.yml 파일을 보면 서비스로 db, wordpress 두개가 존재합니다.
그리고 볼륨으로 db, 네트워크로 wordpress를 생성하고 서비스에서 해당 볼륨과 네트워크를 사용하는 구조로 되어있습니다.
어떠한 설정도 하지 않으면 도커 컴포즈는 현재 디렉토리의 docker-compose.yml 파일을 읽어 로컬의 도커 엔진에게 컨테이너 생성을 요청합니다. docker compse up 명령어로 도커 컴포즈 프로젝트를 실행할 수 있습니다. docker-compose.yml을 읽어 컨테이너를 생성해 보겠습니다.
docker ps 명령어로 mysql과 wordpress 컨테이너가 docker-compose.yml에 명시된대로 생성된 것을 확인할 수 있습니다.
또한, 도커 docker-compose ps 명령어로 컴포즈 프로젝트를 확인할 수 있습니다.
도커 컴포즈 yaml 파일의 옵션
도커 컴포즈 yaml 파일의 옵션들을 알아보겠습니다.
버전 정의
version
-> yaml 파일 포맷의 버전을 나타냅니다. 도커 컴포즈 버전마다 사용하는 yaml 포맷 버전이 있으므로 도커 컴포즈 버전은 도커 엔진 버전에 의존성이 있으므로 가능하면 최신 버전을 사용하는 것이 좋습니다.
서비스 정의
서비스는 도커 컴포즈로 생성할 컨테이너 옵션을 정의합니다. 이 항목에 쓰인 각 서비스는 컨테이너로 구현되며, 하나의 프로젝트로ㅠ서 도커 컴포즈에 의해 관리됩니다. 서비스의 이름은 services의 하위 항목으로 정의하고, 컨테이너의 옵션은 서비스 이름의 하위 항목에 정의합니다.
services:
container_1:
image: ...
container_2:
image: ...
services
-> 생성될 컨테이너들을 묶어놓은 단위입니다. 서비스 항목 아래에는 각 컨테이너에 적용될 생성 옵션을 지정합니다.
image
-> 서비스의 컨테이너를 생성할 때 쓰일 이미지의 이름을 설정합니다. 이미지 이름 포맷은 docker run과 같으며 만일 이미지가 도커에 존재하지 않으면 도커 허브에서 자동으로 내려받습니다.
services:
container_1:
image: seosh817/composetest:mysql
links
-> docker run 명령어의 --link와 같으며, 다른 서비스에 서비스명만으로 접근할 수 있도록 설정합니다. [SERVICE:ALIAS] 형식을 사용하면 서비스에 별칭으로도 접근할 수 있습니다.
services:
web:
links:
- db
- db:database
- redis
environment
-> docker run 명령어의 --env, -e 옵션과 동일합니다. 서비스의 컨테이너 내부에서 사용할 환경변수를 지정하며, 딕셔너리(Dictionary)나 배열 형태로 사용할 수 있습니다.
services:
web:
environment:
- MYSQL_ROOT_PASSWORD=mypassword
- MYSQL_DATABASE_NAME=mydb
or
environment:
- MYSQL_ROOT_PASSOWRD:mypassword
- MYSQL_DATABASE_NAME:mydb
command
-> 컨테이너가 실행될 때 수행할 명령어를 설정하며, docker run 명령어의 마지막에 붙는 커맨드와 같습니다. Dockerfile의 RUN과 같은 배열 형태로도 사용할 수 있습니다.
services:
web:
image: seosh817/composetest:web
command: apachectl -DFOREGROUND
or
web:
image: seosh817/composetest:web
command: [apachectl, -DFOREGROUND]
depends_on
-> 특정 컨테이너에 대한 의존 관계를 나타내며, 이 항목에 명시된 컨테이너가 먼저 생성되고 실행됩니다. 다음 예제에서는 web 컨테이너보다 mysql 컨테이너가 먼저 생성됩니다.
* links도 depends_on과 같이 컨테이너의 생성 순서와 실행 순서를 정의하지만 depends_on은 서비스 이름으로만 접근할 수 있다는 점이 다릅니다.
services:
web:
image: seosh817/composetest:web
depends_on
- mysql
mysql:
image: seosh817/composetest:mysql
특정 서비스의 컨테이너만 생성하되 의존성이 없는 컨테이너를 생성하려면 --no-deps 옵션을 사용합니다.
$ docker-compose up --no-deps web
* 추가) links와 depends_on 사용시 주의할 점
links, depends_on 모두 실행 순서만 설정할 뿐 컨테이너 내부의 어플리케이션이 준비된 상태인지에 대해서는 확인하지 않습니다. 예를 들어, 데이터베이스 컨테이너와 웹 서버 컨테이너가 정해진 순서대로 실행됐더라도 데이터베이스가 초기화 중이라면 웹 서버 컨테이너가 정상적으로 동작하지 않을 수도 있습니다. 이를 해결하는 방법으로 컨테이너에 셸 스크립트를 entrypoint로 지정하는 방법이 있습니다. yaml 파일의 entrypoint에 다음과 같이 지정합니다.
services:
web:
...
entrypoint: ./sync_script.sh mysql:3306
entrypoint에 지정된 sync_script.s는 다음과 같은 형식을 가집니다. until 구문의 조건 안에 다른 컨테이너의 어플리케이션이 준비되었는지 확인하는 명령어를 입력합니다. 예를 들어, curl mysql:3306을 조건으로 넣는다면 mysql 데몬이 준비될 때까지 기다릴 것입니다.
until (상태를 확인할 수 있는 명령어); do
echo "depend container is not available yet"
sleep 1
done
echo "depends_on container is ready"
ports
-> docker run 명령어의 -p와 같으며 서비스의 컨테이너를 개방할 포트를 설정합니다. 그러나 단일 호스트 환경에서 80:80과 같이 호스트의 특정 포트를 서비스의 컨테이너에 연결하면 docker-compose scale 명령어로 서비스의 컨테이너의 수를 늘릴 수 없습니다.
services:
web:
image: seosh817/composetest:web
ports:
- "8080"
- "8081-8100"
- "80:80"
...
build
-> build 항목에 정의된 도커파일에서 이미지를 빌드해 서비스의 컨테이너를 생성하도록 설정합니다. 다음 예제는 ./composetest 디렉토리에 저장된 도커파일로 이미지를 빌드해 컨테이너를 생성합니다. 새롭게 빌드될 이미지의 이름은 image 항목에 정의된 이름인 seosh817/composetest:web이 됩니다.
services:
web:
build: ./composetest
image: seosh817/composetest:web
또한 build 항목에서는 도커파일에 사용될 컨텍스트나 도커파일의 이름, 도커파일에서 사용될 인자 값을 설정할 수 있습니다. 다음과 같이 image 항목을 설정하지 않으면 이미지의 이름은 [프로젝트 이름]:[서비스 이름]이 됩니다.
services:
web:
build: ./composetest
context: ./composetest
dockerfile: myDockerfile
args:
HOST_NAME: web
HOST_CONFIG: self_config
extends
-> 다른 yaml 파일이나 현재 yaml 파일에서 서비스 속성을 상속 받도록 설정합니다. 아래의 docker-compose.yml의 web 서비스는 extend_compose.yml의 extend_web 서비스의 옵션을 그대로 갖게 됩니다. 즉, web 서비스의 컨테이너는 ubuntu:focal 이미지의 80:80 포트로 설정됩니다. file 항목을 설정하지 않으면 현재 yaml 파일에서 extends할 서비스를 찾습니다.
docker-compose.yml 파일
version: '3.9'
services:
web:
extends:
file: extend_compose.yml
service: extend_web
extend_compose.yml 파일
version: '3.9'
services:
web:
extend_web:
image: ubuntu:focal
ports:
- "80:80"
네트워크 정의
driver
-> 도커 컴포즈는 생성된 컨테이너를 위해 기본적으로 브리지 타입의 네트워크를 생성합니다. 그러나 yaml 파일에서 driver 항목을 정의해 서비스의 컨테이너가 서비스의 컨테이너가 브리지 네트워크가 아닌 다른 네트워크를 사용하도록 설정할 수 있습니다. 특정 드라이버에 필요한 옵션은 하위 항목인 driver_ops로 전달할 수 있습니다.
아래 예제는 docker-compose up -d 명령어로 컨테이너를 생성할 때 mynetwork라는 overlay 타입의 네트워크도 함께 생성하고, myservice 서비스의 네트워크가 mynetwork 네트워크를 사용하도록 설정합니다. 단, overlay 타입의 네트워크는 스웜 모드나 주키퍼를 사용하는 환경이어야만 생성할 수 있습니다.
version: '3.9'
services:
myservice:
image: nginx
networks:
- mynetwork
networks:
mynetwork
driver: overlay
driver_opts:
subnet: "255.255.255.0"
IPAddress: "10.0.0.2"
ipam
-> IPAM(IP Address Manager)를 위해 사용할 수 있는 옵션으로서 subnet, ip 범위 등을 설정할 수 있습니다. driver 항목에서는 IPAM을 지원하는 드라이버의 이름을 입력합니다.
services:
...
networks:
ipam:
driver: mydriver
config:
subnet: 172.20.0.0/16
ip_range: 172.20.5.0/24
gateway: 172.20.5.1
external
-> yaml 파일을 통해 프로젝트를 생성할 때마다 네트워크를 생성하는 것이 아닌, 기존의 네트워크를 사용하도록 설정합니다. 이를 설정하려면 사용하려는 외부 네트워크의 이름을 하위 항목으로 입력한 뒤 external의 값을 true로 설정합니다. external 옵션을 준비된 네트워크를 사용하므로 driver, driver_ops, ipam 옵션과 함께 사용할 수 없습니다.
아래는 서비스의 컨테이너가 기존의 seosh817_network라는 이름의 네트워크를 사용하도록 설정합니다.
services:
web:
image: seosh817/composetest:web
networks:
- seosh817_network
networks:
seosh817_network:
extenral: true
볼륨 정의
driver
-> 볼륨을 생성할 때 사용될 드라이버를 설정합니다. 어떠한 설정도 하지 않으면 local로 설정되며 사용하는 드라이버에 따라 변경해야 합니다. 드라이버를 사용하기 위한 추가 옵션은 하위 항목인 driver_opts를 통해 인자로 설정할 수 있습니다.
version: '3.9'
services:
...
volumes:
driver: flocker
driver_opts:
opts: "1"
opt2: 2
external
-> 도커 컴포즈는 yaml 파일에서 volume, volume-from 옵션 등을 사용하면 프로젝트마다 볼륨을 생성합니다. 이때 external 옵션을 설정하면 볼륨을 프로젝트를 생성할 때마다 매번 생성하지 않고 기존 볼륨을 사용하도록 설정합니다. 다음 예제에서 myvolume이라는 이름의 외부 볼륨을 web 서비스의 컨테이너에 마운트합니다.
services:
web:
image: seosh817/composetest:web
volumes:
- myvolume:/var/www/html
volumes:
myvolume:
external: true
도커 컴포즈 네트워크
yaml 파일에 네트워크 항목을 정의하지 않으면 도커 컴포즈는 프로젝트 별로 브리지 타입의 네트워크를 생성합니다. 생성된 네트워크의 이름은 [프로젝트 이름]_default로 설정되며, docker-compose up 명령어로 생성되고 docker-compose down 명령어로 삭제됩니다.
docker-compose up 명령어뿐만 아니라 docker-compose scale 명령어로 생성되는 컨테이너 전부가 이 브리지 타입의 네트워크를 사용합니다. 서비스 내의 컨테이너는 --net-alias 서비스의 이름을 갖도록 자동으로 설정되므로 이 네트워크에 속한 컨테이너는 서비스의 이름으로 서비스 내의 컨테이너에 접근할 수 있습니다.
예를 들어, web 서비스와 mysql 서비스가 각기 존재할 때 web 서비스의 컨테이너가 mysql이라는 호스트 이름으로 접근하면 mysql 서비스의 컨테이너 중 하나의 IP로 변환(resolve)되며, 컨테이너가 여러개 존재할 경우 라운드 로빈으로 연결을 분산합니다.
따라서 docker-compose scale 명령어로 컨테이너의 수를 늘려도 해당 서비스의 이름으로 서비스의 모든 컨테이너에 접근할 수 있습니다. 서비스의 이름으로 컨테이너에 접근하는 방식은 스웜 모드와 유사하지만 작동 원리는 다릅니다.
도커 컴포즈 명령어
도커 컴포즈 명령어들을 정리하겠습니다.
도커 컴포즈 프로젝트 실행
도커 컴포즈 프로젝트는 docker-compose up
명령어로 실행할 수 있습니다.
# Foreground로 도커 컴포즈 프로젝트 실행
$ docker-compose up
# Background로 도커 컴포즈 프로젝트 실행
$ docker-compose up -d
# 프로젝트 이름 my-project로 변경하여 도커 컴포즈 프로젝트 실행
$ docker-compose -p my-project up -d
# docker-compose scale 명령어로 각 서비스에 여러개의 컨테이너를 생성
# 단, scale 시 주의할 점은 포트를 여러개 지정하면 충돌이나므로 호스트 포트는 하나만 지정해주어야 함.
$ docker-compose scale [서비스명]=[컨테이너 갯수]
-d 옵션
-> 도커 컴포즈 프로젝트를 백그라운드에서 실행합니다.
-p 옵션
-> 프로젝트 이름을 변경하여 도커 컴포즈 프로젝트를 실행합니다. 만약, -p 옵션으로 명시하지 않으면 컨테이너들은 현재 디렉터리의 이름으로 시작하는 이름을 가지게 됩니다.
도커 컴포즈 프로젝트 종료
도커 컴포즈 프로젝트는 docker-compose down
명령어로 종료할 수 있습니다.
# 프로젝트 내 컨테이너 및 네트워크 종료 및 제거
$ docker-compose down
# 프로젝트 내 컨테이너, 네트워크 및 볼륨 종료 및 제거
$ docker-compose down -v
-v 옵션
-> 도커 컴포즈 프로젝트가 종료할 때 네트워크와 볼륨을 제거 후 종료합니다.
도커 컴포즈 프로젝트 목록 확인
도커 컴포즈 프로젝트 목록은 docker-compose ps
명령어로 확인할 수 있습니다.
# 도커 컴포즈 프로젝트 목록 확인
$ docker-compose ps
References
용찬호, "시작하세요 도커!", WikiBook (2017)