Docker 101 - 마지막 장: Docker Compose는 무슨 역할을 할까요 ?

okkkkkky·2023년 8월 12일
0
post-thumbnail

Docker Compose가 어떤 역할을 하는지 간단하게 살펴보는 글입니다.

Docker Compose란 ?

앞서 Docker CLI를 통해 Docker 클라이언트를 통해 이미지 혹은 컨테이너를 제어할 수 있다고 알아보았는데요,
이미지를 다운받고, 환경변수를 설정하고, 호스트 포트와 컨테이너 포트는 어떻게 설정하고 등등 여러가지 제어 내용을 CLI 여러줄로 매번 실행시켜야 한다면 어떨까요 ? CLI와 관련된 내용을 복사해서 붙여넣기를 계속해서 하거나, 그 모든 내용들을 필요할 때마다 매번 실행시켜야 하는 번거로움이 있을 것입니다.

이를 위해 있는 것이 Docker Compose입니다. Docker Compose는 도커 컨테이너를 일괄적으로 제어하는 도구로, yml 파일로 작성합니다. 마치 github action 스크립트와 유사하게 yml 파일 내부에 설정 내용을 작성해주면 이를 토대로 Docker CLI로 번역하는 역할을 해줍니다.

아래의 Docker Compose yaml파일 예시를 한번 살펴볼까요?

version: '3.0'

services:
  mariadb10:
    image: mariadb:10
    ports:
     - "3310:3306/tcp"
    environment:
      - MYSQL_ROOT_PASSWORD=my_db_passward
      - MYSQL_USER=docker_pro
      - MYSQL_PASSWORD=docker_pro_pass
      - MYSQL_DATABASE=docker_pro
  redis:
    image: redis
    command: redis-server --port 6379
    restart: always
    ports:
      - 6379:6379
  rabbitmq:
    image: rabbitmq:3-management-alpine
    container_name: 'rabbitmq'
    ports:
        - 5672:5672
        - 15672:15672
    volumes:
        - ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
        - ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
    networks:
        - rabbitmq_go_net

networks:
  rabbitmq_go_net:
    driver: bridge

일단 이렇게 생긴 것은 인지했으니, 본격적으로 각각 어떤 내용을 담고 있고, 어떤 명령을 내리는지 확인해보도록 하겠습니다. 파일의 구성이 어떻게 되어있는지 확인해보고 다시 돌아와서 위의 예시가 어떤 의미를 담고 있는지 확인해보시죠 !

Docker 컴포즈 파일 구성

Docker 컴포즈 파일명은 기본적으로 compose.yaml 혹은 compose.yaml로 작성합니다. (물론 working directory 내부에 있어야 하겠죠) 예전에 사용했던 docker-compose라는 파일명도 호환되기는 하지만, 두 개의 파일이 존재하는 경우 Docker에서는 compose.yaml을 우선으로 합니다. 컴포즈 파일은 여러개를 동시에 사용할 수 있고, 다른 컴포즈 파일을 재사용하여 작성할 수도 있습니다.
Docker 컴포즈 파일은 아래의 내용을 담고 있습니다.

Version

컴포즈의 버전을 명시하기 위해 작성하는 부분입니다. Docker 엔진이 새로 배포될 때마다, 그에 맞는 컴포즈의 파일 버전도 변경될 수 있습니다. 현재는 version 3.x까지 나온 상태이고, version 1은 deprecate되어 사용하지 못합니다. 버전에 따라 컴포즈 파일에서 사용할 수 있는 구성 요소들이 달라지기 때문에 잘 체크하여 사용해주어야 합니다.

Services

실행하려는 컨테이너들을 정의하는 역할을 합니다. 이름, 이미지, 포트 매핑, 환경 변수, 볼륨 정보 등을 명시할 수 있고, 이 정보들을 기반으로 컨테이너를 생성하고 관리할 수 있습니다. services에서 사용할 수 있는 하위 속성들은 Docker 공식문서에서 확인할 수 있는데 너~무 많아서 우선 제일 메인으로 자주 사용하는 부분들에 대해 살펴보겠습니다 !

image

컨테이너를 생성할 때 참고하는 이미지를 의미합니다. 이미지를 정의할 때는 무조건 아래의 형식에 따라 명시해주어야 합니다.

image: [<registry>/][<project>/]<image>[:<tag>|@<digest>]

# Example
image: redis
image: redis:5
image: redis@sha256:0ed5d5928d4737458944eb604cc8509e245c3e19d02ad83935398bc4b991aac7
image: library/redis
image: docker.io/library/redis
image: my_private.registry:5000/redis

build

정의된 도커 파일에서 이미지를 빌드해서 서비스의 컨테이너를 생성하도록 설정합니다.

environment

컨테이너의 환경변수를 설정하는 역할을 합니다. docker run --env / -e 옵션과 동일하게 작동합니다. 환경변수를 설정할 때는 Map형식을 따르거나 Array형식을 따를 수 있습니다. 여기에서 주의할 점은 boolean 값들은 따옴표로 정의되어야 yaml 파일이 읽어들일 때 값 변환이 되지 않게끔 할 수 있습니다. 추가로 환경변수를 설정할 수 있는 env_file도 있는데 environment와 동시에 설정되는 경우, environment 속성에 선언된 값에 기반하여 설정됩니다.

# Map 형식
environment:
  RACK_ENV: development
  SHOW: "true"
  USER_INPUT:
 
# Array 형식
 environment:
  - RACK_ENV=development
  - SHOW=true
  - USER_INPUT

command

컨테이너가 실행될 때 수행할 명령어를 실행하는 역할을 합니다. docker run 뒤에 붙는 커맨드와 동일한 역할을 합니다. 아래의 두 명령어는 동일한 역할을 수행합니다.

# Docker Compose 내부 command
command: bundle exec thin -p 3000

# Dockerfile 내부 command
command: ["bundle", "exec", "thin", "-p", "3000"]

depends_on

꽤나 직관적인 속성이름이네요 ! 이 부분은 컨테이너간의 의존성을 주입하는 역할을 합니다. 명시된 컨테이너가 "먼저" 생성되고 실행됩니다. 예를 들어 아래의 Compose 명령어들을 봅시다.

services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

이 depends_on 부분은 short_syntax 방법으로 작성되었는데요, depends_on에 명시된 db와 redis를 web이 먼저 빌드되기 전에 생성합니다. 참고로 redis는 redis라는 이미지에서, db는 postgres라는 이미지로 만듭니다. 제거를 할 때는 오히려 반대입니다. web을 제거하고 난 후 db와 redis를 제거합니다.
Compose는 위와 같이 종속된 서비스를 시작하기 전에 "준비"가 되었는지, 시작되었는지 확인을 하고 진행합니다.

ports

이 부분은 앞서 작성한 게시글에서도 자주 언급된 것과 같이 개방할 포트를 지정하는 역할을 합니다. docker run -p와 동일한 역할을 하겠죠 ? 아래와 같은 방식으로 작성할 수 있습니다.
다만 여기서 주의할 점은 네트워크 설정을 host로 설정하면 런타임에러가 난다는 점입니다.

# Short syntax
ports:
  - "127.0.0.1:8000:80"
  
# Long syntax
ports:
  - target: 80
    host_ip: 127.0.0.1
    published: "8080"
    protocol: tcp
    mode: host

volumes

컨테이너에 볼륨을 마운트(연결)합니다. 즉, 호스트 경로 혹은 이름을 가지고 있는 볼륨을 연결하는 역할을 합니다. 연결하는 곳이 호스트 주소이고, 단일 서비스에서만 사용되는 경우에는 이렇게 서비스 하위 속성으로 사용할 수 있지만, 여러 서비스에서 볼륨을 재사용해야 하는 경우가 있다면, services와 동일하게 최상단에 volumes를 선언하여 사용해주어야 합니다. services의 하위에서 선언하는 경우 아래의 예시처럼 사용할 수 있습니다.

services:
  backend:
    image: awesome/backend
    volumes:
      - type: volume
        source: db-data
        target: /data
        volume:
          nocopy: true
      - type: bind
        source: /var/run/postgres/postgres.sock
        target: /var/run/postgres/postgres.sock

restart

컨테이너가 종료될 때, 재시작에 대한 설정을 해주는 부분입니다.

  • no : 재시작하지 않음
  • always: 외부의 영향에 의해 종료되었을 때 항상 재시작 (수동으로 끄기 전까지 항상)
  • on-failure: 오류가 있을 때에만 재시작
  • unless-stopped: 서비스가 종료되거나 정지되는 경우를 제외하고는 항상 재시작을 합니다.

Networks

이 부분은 services와 동일한 최상위 요소입니다. 이 부분에서는 여러 서비스에서 재사용할 수 있는 네임드 네트워크를 구성할 수 있습니다. 아래의 예시를 살펴볼까요 ?

services:
  frontend:
    image: awesome/webapp
    networks:
      - front-tier
      - back-tier

networks:
  front-tier:
  back-tier:

front-tierback-tier라는 네트워크가 있고, frontend라는 서비스는 front-tier, backend-tier 네트워크에 모두 연결되었다는 뜻입니다.

또다른 예시를 확인해볼까요 ?

services:
  proxy:
    build: ./proxy
    networks:
      - frontend
  app:
    build: ./app
    networks:
      - frontend
      - backend
  db:
    image: postgres
    networks:
      - backend

networks:
  frontend:
    # Use a custom driver
    driver: custom-driver-1
  backend:
    # Use a custom driver which takes special options
    driver: custom-driver-2
    driver_opts:
      foo: "1"
      bar: "2"

네트워크는 frontend, backend 두 개가 있습니다. proxy라는 서비스는 frontend 네트워크, app이라는 서비스는 frontend, backend 네트워크 모두, db는 backend 네트워크에만 연결되었다는 것을 알 수 있습니다. proxydb 서비스는 일반적으로 같은 네트워크를 사용하지 않고, app만이 둘다를 사용할 수 있다는 것을 의미합니다.

Docker Compose file 분석해보기

지금까지 각각의 항목이 어떤 것들을 의미하는지 알아보았습니다. 그렇다면, 처음에 예시로 들었던 파일을 다시 한번 살펴볼까요 ? 각각의 내용들은 위에서 살펴봤던 내용들을 토대로 이렇게 해석할 수 있을 것입니다.

version: '3.0'

services:
  mariadb10:
    image: mariadb:10
    ports:
     - "3310:3306/tcp"
    environment:
      - MYSQL_ROOT_PASSWORD=my_db_passward
      - MYSQL_USER=docker_pro
      - MYSQL_PASSWORD=docker_pro_pass
      - MYSQL_DATABASE=docker_pro
  redis:
    image: redis
    command: redis-server --port 6379
    restart: always
    ports:
      - 6379:6379
  rabbitmq:
    image: rabbitmq:3-management-alpine
    container_name: 'rabbitmq'
    ports:
        - 5672:5672
        - 15672:15672
    volumes:
        - ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
        - ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
    networks:
        - rabbitmq_go_net

networks:
  rabbitmq_go_net:
    driver: bridge
  1. Docker Compose 파일 버전은 3.0을 쓴다
  2. 서비스에는 총 3개로 구성한다
    2-1) mariadb10
    • mariadb:10이라는 이미지에서 컨테이너를 만든다
    • 호스트 포트는 3310, 컨테이너 포트는 3306으로 설정한다
    • 환경변수는 mySql과 관련된 4개의 변수를 설정한다
      2-2) redis
    • redis 이미지에서 컨테이너를 만든다
    • docker run redis-server --port 6379라는 명령어를 실행한다
    • 컨테이너는 항상 재시작을 한다
    • 호스트 포트, 컨테이너 포트 모두 6379으로 설정한다
      2-3) rabbitmq
    • rabbitmq:3-management-alpine라는 이미지에서 컨테이너를 만든다
    • 컨테이너 이름은 "rabbitmq"로 설정한다
    • 호스트 포트는 각각 5672(AMQP 프로토콜), 15672(RabbitMQ 관리 UI)로 설정하고 컨테이너 포트도 동일하게 설정한다
    • 2개의 볼륨을 정의하여 마운트 path를 지정해준다
    • 이 서비스는 rabbitmq_go_net이라는 네트워크에 연결된다.
  3. rabbitmq_go_net라는 네트워크가 정의되어있는데 이는 브릿지 네트워크로서 작동한다

Docker Compose 실행시키기

자 이렇게 Docker Compose 파일을 만들어봤다면 실행시켜봐야겠죠 ? 아래의 CLI를 통해 Docker Compose 파일을 실행시킬 수 있습니다.

docker-comopse -f local-infra.yml up -d 
  • up: compose 파일로 컨테이너를 생성한다
  • -f: 컴포즈 파일 지정하기
  • -d: 백그라운드에서 계속 실행하기

여기까지 Docker의 기본적인 개념들에 대해서 알아보았습니다. 정확하게 Docker가 무엇인지, 그 내부에 있는 구성들은 어떻게 작동하며, Docker network는 어떻게 구성되어있고, 마지막으로 Docker Compose 파일까지 거쳐왔는데요, 이 과정들을 토대로 조금이나마 Docker를 알아갈 수 있는 시간이 되었으면 좋겠습니다.

참고
https://blog.kubesimplify.com/everything-you-need-to-know-about-docker-compose
https://docs.docker.com/compose/compose-file/03-compose-file/
https://docs.docker.com/compose/compose-file/05-services
https://docs.docker.com/compose/compose-file/06-networks/
원티드 백엔드 챌린지 2023년 8월 강의 내용

0개의 댓글