새소식

기술스택 및 알쓸신잡

[Docker] 도커 컴포즈? 그냥 컨테이너 동시에 올리는거 아니야?

  • -

TODO List

  • 도커 컴포즈 필요성
  • 도커 컴포즈 파일 작성법 (docker_compose.yml)
  • 도커 컴포즈 관련 명령어

 

이전 포스트에서 도커 이미지를 빌드하고 실행(컨테이너화)하는 방법에 대해 알아보았다.
이번에 설명할 도커 기능은 도커 컴포즈이다.
이전과 마찬가지로 도커 컴포즈에 대해 공부하기 전에, 먼저 왜 필요한지를 이해해보자.

 


1. Docker Compose 필요성

Docker compose의 필요성을 설명하기 위해서는 이전 포스팅에 언급했던
"VM 활용 가상화와 컨테이너 활용 가상화의 차이"를 다시 되돌아볼 필요가 있다.

VM vs Container

VM은 각각의 하이퍼바이저 (OS빌드를 위한 기초)위에 각각의 OS를 올리고, 그 위에 앱을 실행하고
컨테이너는 동일한 호스트 머신의 OS를 공유하면서 여러개의 컨테이너를 각각의 목적에 맞춰 독립적으로 실행할 수 있다는 것이었다.

이때 이 장점을 활용하는 방법이 docker compose이다.
하나의 명령어 파일로 여러개의, 여러 종류의 컨테이너를 한번에 빌드하고 종료하는 기능이라고 생각하면 편하다. 실제로 그게 전부이기도 하고.

 

근데 이렇게만 설명하면 그냥 docker run 명령어 여러개를 하나의 sh 쉘로 만들어서 관리하는거랑 뭔차이가 있는가 의문이 들 수 있다.
예를 들면

#!/bin/bash docker run -d image 1 docker run -d image 2

이런식으로 두개의 이미지를 순차적으로 실행하도록 만드는 것과 큰차이가 없다고 생각들 수 있다.

하지만 두개의 이미지간의 선행 관계, 의존성이 존재하는 경우 docker compose의 필요성이 극대화된다.
필자는 이전 스마트워치앱 개발 프로젝트에서 Django 웹서버를 Nginx 이미지와 같이 올리는 과정에서 docker compose의 필요성을 느낄 수 있었다.

2. Docker Compose 파일 작성법

자, 이제 도커 컴포즈가 무엇인지와 필요한지 알았으니, 실제로 어떻게 사용하는지 알아보자.

도커 컴포즈는 docker-compose.yml이라는 YAML 형식의 파일을 통해 다중 컨테이너 애플리케이션을 정의하고 실행하는 도구다. 이 파일에는 애플리케이션을 구성하는 서비스, 네트워크, 볼륨 등을 선언적으로 정의한다.

기본적인 docker-compose.yml 파일의 구조는 다음과 같다

version: "3" # 도커 컴포즈 파일 버전 services: # 실행할 컨테이너들 정의 service1: # 첫 번째 서비스 (컨테이너) 이름 image: image_name:tag # 사용할 이미지 # 기타 설정... service2: # 두 번째 서비스 (컨테이너) 이름 build: ./path/to/dockerfile # Dockerfile로부터 빌드 # 기타 설정... volumes: # 필요한 볼륨 정의 (선택사항) volume1: # 볼륨 설정... networks: # 필요한 네트워크 정의 (선택사항) network1: # 네트워크 설정...

이 개념을 좀 더 명확하게 이해하기 위해 필자가 진행했던 스마트워치 앱 프로젝트(ItsMe)에서 사용한 실제 예시를 살펴보자:

version: "3" services: web: image: 9unu/itsme_web:latest container_name: itsme_web restart: always environment: - DJANGO_SETTINGS_MODULE=itsme.settings expose: - "8000" nginx: image: 9unu/itsme_nginx:latest container_name: itsme_nginx ports: - "80:80" - "443:443" volumes: - /etc/letsencrypt:/etc/letsencrypt - /var/lib/letsencrypt:/var/lib/letsencrypt depends_on: - web

이 구성은 두 가지 컨테이너로 구성된다:

  1. web: Django 웹 애플리케이션
  2. nginx: 웹 서버 및 리버스 프록시

여기서 주목할 부분은 depends_on 항목이다. 이는 서비스 간의 의존성을 정의하는 것으로, nginx 서비스가 web 서비스에 의존한다는 것을 명시한다. 이렇게 하면 도커 컴포즈는 다음 순서로 컨테이너를 시작한다:

  1. 먼저 웹 애플리케이션(web)이 시작
  2. 그 다음 Nginx(nginx)가 시작하여 웹 애플리케이션에 요청을 프록시

이것이 바로 앞서 말한 셸 스크립트로 여러 docker run 명령어를 실행하는 것과의 차이점이다. 셸 스크립트로는 이런 의존성 관계를 쉽게 표현하기 어렵다. 각 컨테이너가 제대로 시작되었는지 확인하는 로직을 직접 작성해야 할 것이다. 그것도 비동기 작업이라 생각보다 구현하기 까다롭다...

docker-compose.yml 파일에서 사용할 수 있는 주요 설정 옵션들을 살펴보자:

  1. image: 사용할 도커 이미지 지정
  2. build: 이미지를 직접 빌드할 경우 Dockerfile의 위치 지정
  3. ports: 호스트와 컨테이너 간의 포트 매핑 (HOST:CONTAINER)
  4. expose: 다른 컨테이너에는 노출하지만 호스트에는 노출하지 않을 포트
  5. volumes: 파일 시스템 마운트 지정
  6. environment: 환경 변수 설정
  7. depends_on: 서비스 간 의존성 설정
  8. restart: 컨테이너 재시작 정책 (no, always, on-failure, unless-stopped)
  9. networks: 컨테이너가 연결될 네트워크 지정

이 외에도 condition 등의 옵션들이 있지만, 이건 나중에 airflow docker compose 배포 포스팅에서 설명할 예정이다ㅎㅎ

(어차피 다 외울수도 없음)

3. Docker Compose 관련 명령어

이제 Docker Compose 파일을 작성했으니, 이를 실행하고 관리하는 명령어들을 알아보자.

docker-compose up

이 명령어는 docker-compose.yml 파일에 정의된 모든 서비스를 시작한다. 터미널에 모든 컨테이너의 로그가 출력된다.

백그라운드에서 실행하려면 -d 옵션을 사용한다:

docker-compose up -d
docker-compose down

이 명령어는 모든 서비스를 중지하고 컨테이너를 제거한다. 네트워크도 기본적으로 제거되지만, 볼륨은 유지된다.

볼륨까지 함께 제거하려면:

docker-compose down -v
docker-compose ps

이 명령어는 현재 실행 중인 컨테이너의 상태를 보여준다.

docker-compose logs

모든 서비스의 로그를 확인한다. 특정 서비스의 로그만 보려면:

docker-compose logs service_name

실시간 로그를 보려면 -f 옵션을 추가한다:

docker-compose logs -f
docker-compose up service_name

docker-compose.yml에 정의된 여러 서비스 중 특정 서비스만 실행한다. 이 때 depends_on에 정의된 의존성이 있는 서비스도 함께 시작된다.

docker-compose restart service_name

특정 서비스만 재시작한다.

소스 코드가 변경되어 이미지를 다시 빌드해야 할 경우:

docker-compose up --build

또는 빌드만 하려면:

docker-compose build

실행 중인 컨테이너에서 명령을 실행하려면:

docker-compose exec service_name command

예를 들어, 웹 서비스의 bash 셸에 접속하려면:

docker-compose exec web bash

Docker Compose를 사용하면 서비스를 쉽게 스케일링할 수 있다:

docker-compose up -d --scale service_name=3

이 명령어는 지정한 서비스의 인스턴스를 3개 실행한다. 물론 이를 위해서는 포트 충돌을 피하도록 docker-compose.yml 파일을 적절히 구성해야 한다.

4. Docker Compose vs Docker Swarm

Docker Compose는 단일 호스트에서 다중 컨테이너 애플리케이션을 관리하는 데 최적화되어 있다. 따라서 다음과 같은 상황에서 특히 유용하다:

  • 로컬 개발 환경 구성
  • 자동화된 테스트 환경 구성
  • 작은 규모의 서비스 배포

필자도 개인 프로젝트에서는 대부분 Docker Compose를 사용한다. 간단한 설정으로 빠르게 환경을 구성할 수 있기 때문이다. 실제로 위에서 언급한 ItsMe 프로젝트도 Docker Compose로 배포했다.

단일 호스트를 넘어 여러 서버에 분산 배포해야 한다면 Docker Swarm을 고려할 수 있다.

Docker Swarm은 Docker의 기본 제공 클러스터링 및 오케스트레이션 도구로, 여러 Docker 호스트를 하나의 가상 Docker 호스트로 묶는다. Compose와 비교했을 때 주요 차이점은 다음과 같다:

  • 다중 호스트 지원: 여러 서버에 걸쳐 컨테이너를 분산 배포할 수 있다
  • 고가용성: 서비스 복제를 통한 장애 대비
  • 로드 밸런싱: 자동으로 트래픽을 분산
  • 롤링 업데이트: 중단 없이 서비스 업데이트 가능

Docker Compose에서 Docker Swarm으로의 전환이 비교적 쉬운 편이다. Docker Compose 파일을 약간만 수정하면 Docker Swarm에서 사용할 수 있다:

(Docker Swarm은 이후에 Spark 클러스터 구현 및 연동에 대한 포스팅에서 설명할 예정이다!)
(도커만 쓸 줄 알아도, 많은 기술 스택에 쉽게 접근할 수 있으니 화이팅해보자!)

5. 주의사항과 팁

민감한 정보(비밀번호, API 키 등)는 docker-compose.yml 파일에 직접 입력하지 않는 것이 좋다. 대신, .env 파일을 사용하거나 환경변수로 제공하는 것이 좋다:

services: db: environment: - POSTGRES_PASSWORD=${DB_PASSWORD}

그리고 .env 파일:

DB_PASSWORD=very_secret_password

이렇게 하면 민감한 정보를 버전 관리 시스템에 포함시키지 않고도 관리할 수 있다.

6. 정리: Docker Compose의 핵심 개념

지금까지 살펴본 Docker Compose의 핵심 개념을 정리해보자:

  1. 선언적 구성: YAML 파일을 통해 전체 애플리케이션 스택을 선언적으로 정의
  2. 서비스 의존성 관리: depends_on을 통한 서비스 간 의존성 자동 처리
  3. 볼륨과 네트워크 관리: 데이터 지속성과 서비스 간 통신을 위한 볼륨/네트워크 설정
  4. 다양한 환경 지원: 개발, 테스트, 프로덕션 환경에서 일관된 설정 관리
더보기

살짝 빠져서, docker compose로 서비스 의존성을 관리할 수 있다는 것이, 무조건 A 서비스가 작동하고 있어야 B 서비스를 실행시킨다는 의미는 아니다.

 

그저 A컨테이너가 올라간 뒤에, B컨테이너를 올린다는 의미일 뿐. A 서비스가 정상 작동하고 있어야만 B 서비스를 올린다는 의미는 아니다.

 

따라서 이를 해결하고 싶다면, 쿠버네티스 pod로 서비스 상태를 기반으로 의존성을 관리하거나 앞에서 잠깐 언급한 condition 옵션을 활용해야한다.

 

Docker Compose는 소규모 프로젝트부터 중간 규모의 프로젝트까지 컨테이너 관리를 크게 단순화해준다. 특히 개발 환경에서는 "개발 환경을 코드로 관리"하는 이상적인 방식을 제공한다.

다음 포스팅에서는 더 복잡한 실전 예시로 에어플로우 배포 관련 내용을 다루도록 하겠다. 도커 컴포즈를 실제 프로젝트에 어떻게 활용하는지 더 자세히 살펴볼 수 있을 것이다. (여러개의 서비스 컴포넌트가 합쳐져야 정상 작동한다는 것의 대표적인 예시랄까...)

 

도커 컴포즈를 사용해보면 "이전에는 어떻게 개발했지?"라는 생각이 절로 들 것이다. 특히 여러 서비스가 함께 동작해야 하는 현대적인 마이크로서비스 아키텍처에서는 더욱 그렇다.

 

아직 Docker Compose를 사용해보지 않았다면, 지금 바로 시작해보자. 초기 학습 비용보다 얻는 이점이 훨씬 크다!

 

ㄱㅊㅁ_ㅇㅈ

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.