[Docker] docker-compose를 이용한 로컬 개발 환경 구축하기

2025. 5. 20. 17:20Docker

이번 포스팅에선 docker Compose를 이용한 로컬 빌드 환경 세팅 방법에 대해서 알아보겠습니다.

 

가상화

가상화 = 하나의 서버 자원을 나눠가지며 분산시키는 것

가상화를 통해 하나의 서버로부터 분산된 서버들은 각기 다른 서비스를 구동시킬 수 있습니다.

 

가상화의 종류

1. 서버 가상화

하나의 물리적 서버 호스트에서 여러개의 서버 운영체제를 게스트로 실행할 수 있게 해주는 소프트웨어 아키텍처입니다.

 

이러한 서버 가상화 기술을 구현할 수 있게 하는 소프트웨어 = 하이퍼바이저 (가상 머신이라는 단위로 구별됩니다.)

 

각 가상머신에 운영체제가 설치되어 여러 운영체제가 구동됩니다.

하이버바이저에 의해 생성되고 관리되는 운영체제를 게스트 운영체제라고 합니다.

각 게스트 운영체제는 완전히 독립된 공간과 시스템 자원을 할당받아 사용됩니다.

 

따라서 하이퍼바이저는 OS들에게 자원을 나누어 주며 조율하는 역할을 수행합니다.

대표적인 하이퍼바이저로 VirtualBox, VMware 등이 있습니다.

 

각종 시스템 자원을 가상화하고 독립된 공간을 생성하는 작업은 무조건 하이퍼바이저를 거치기 때문에

일반 호스트 OS에 비해 성능 손상이 발생합니다. 

또한 가상 머신에는 게스트 운영체제를 사용하기 위한 라이브러리, 커널들을 전부 포함하기 때문에 배포하기 위한 이미지로 만들었을 때 크기가 커집니다.

 

즉, 가상 머신은 독립공간을 만들어 완벽한 운영체제를 생성할 수 있다는 장점이 있지만 성능이 떨어지고 용량상으로 부담이 있습니다.

 

2. 컨테이너 가상화

컨테이너는 가상화된 공간을 생성하기 위해 리눅스 자체 기능인 chroot, 네임스페이스, cgroup을 사용해 프로세스 단위의 격리환경을 만듭니다.

 

따라서 아래 사진에서 볼 수 있듯이 도커 엔진 위에 컨테이너들이 할당된 모습을 볼 수 있습니다.

 

컨테이너 안에는 어플리케이션을 구동하기 위해 필요한 라이브러리 및 실행파일만 존재합니다. 

따라서 이미지로 만들었을 때 이미지의 용량이 가상머신보다 대폭 줄어듭니다.

 

(위 사진에서는 도커엔진이라 표현하였지만 컨테이너 가상화 기술을 제공하는 다른 벤더사들도 있습니다.)

 

도커 컨테이너 

도커는 앞서 본 컨테이너 기술에 여러기능을 추가한 오픈소스 프로젝트라 말할 수 있습니다. 

 

가상화 관점에서 컨테이너란 이미지(컨테이너를 만들기 위한 틀) 목적에 따라 생성되는 프로세스 단위의 격리환경을 말합니다.

즉, 프로세스의 생명주기를 관리하는 것입니다. 

 

SpringBoot 어플레이션 1개, nginx 어플리케이션 1개를 컨테이너에서 실행하고 MySQL은 따로 설치해서 실행한다고 가정해보겠습니다. 

 

 

SpringBoot 프로세스와 Nginx 프로세스가 각각 다른 컨테이너로 격리되어 있는 모습입니다. 

컨테이너는 파일시스템과 격리된 시스템자원 및 네트워크를 사용하는 독립된 공간을 가집니다. 

 

그렇다면 Host OS는 컨테이너를 어떻게 바라볼까요?

앞서 말했듯이 컨테이너는 프로세스의 생명주기를 관리하며 실행하는 하나의 프로세스라 말할 수 있습니다.

즉 SpringBoot 어플리케이션을 직접 실행하나, 컨테이너로 실행하나 HostOS 입장에서는 똑같은 프로세스로 바라보게 됩니다. 

 

 

컨테이너 가상화 기술을 왜 써야할까?

가상머신보다 성능 손상이 적고 이미지의 실행, 배포가 가벼워져 빠르기 때문입니다.

컨테이너는 프로세스를 HostOS와 격리된 환경에서 관리합니다. 이를 통해 컨테이너에 어떠한 설정을 하던 HostOS에 어떠한 영향을 끼치지 않습니다. 

이를 통해 프로세스를 컨테이너 단위로 바라볼 수 있게 되고 프로세스의 관리, 확장이 용이해집니다.

 

 

도커엔진

그렇다면 많은 컨테이너들을 어떻게 관리할까?

바로 도커엔진을 통해 컨테이너를 관리할 수 있습니다.

 

도커엔진이란 유저가 컨테이너를 쉽게 사용할 수 있게 하는 주체입니다. 

도커 엔진은 컨테이너의 라이프 사이클을 관리, 컨테이너를 생성하기 위한 이미지 관리, 컨테이너의 데이터를 저장하기 위한 저장소 역할을 하는 볼륨 관리, 컨테이너의 접속을 관리하기 위한 네트워크 관리등의 기능을 제공합니다.

 

사용자가 도커명령을 실행했을 때 도커엔진에서의 동작 과정을 살펴보겠습니다.

 

 

사용자가 도커 명령으로 도커엔진에게 명령어를 보냅니다. 

이를 전달받은 도커 클라이언트는 var/run/docker.sock에 위치한 유닉스 소켓을 통해 도커 데몬의 API를 호출합니다.

도커 데몬은 명령어에 해당하는 작업을 수행하고 수행 결과를 도커 클라이언트에게 반환하며 사용자에게 결과를 출력합니다. 

 

여기서 dockerd는 컨테이너를 생성하고 실행하며 이미지를 관리하는 주체입니다. 

도커 프로세스가 실행되어 입력을 받을 준비가 된 상태를 도커 데몬이라 부릅니다. 

 

 

분산 서버 환경에서는 컨테이너를 어떻게 관리할까?

하나의 서버에서 너무 많은 컨테이너들이 실행되고 있어서 더 이상 새로운 컨테이너를 실행할 수 없게되면?

그 때는 Scale Up 혹은 Scale Out을 고려합니다.

(더 좋은 스펙의 서버를 사는 Scale Up이 좋지만 시간과 비용 문제를 고려해야해서 Scale Out 전략을 더 많이 고려합니다.)

 

Scale Out 환경에서는 서버를 클러스터로 묶어 병렬적으로 컨테이너를 늘려 구동시킵니다.  

이 때 효율적으로 많은 컨테이너들을 생성하고 실행하기 위해 도커 컴포즈를 이용합니다.

 

 

도커 컴포즈

서비스에 필요한 컨테이너가 1~2개 정도라면 직접 명령어를 쳐서 컨테이너를 실행시키면 되지만 서비스가 많아질 경우에는 비효율적입니다.

이 때 많은 컨테이너를 효율적으로 생성하고 실행하기 위해 도커 컴포즈를 사용합니다.

 

 

도커 컴포즈는 컨테이너 설정이 정의되어있는 docekr-compose.yml 파일을 읽어 도커 엔진을 통해 컨테이너를 생성합니다.

docker-compose.yml 파일을 생성했으면 docker-compose up -d 명령어를 통해 컨테이너를 생성할 수 있습니다.

 

docker-compose의 구조를 살펴보면

하나의 프로젝트에는 여러개의 서비스들로 이루어져 있고 하나의 서비스에는 여러개의 컨테이너가 존재할 수 있습니다.

(하나의 서비스에 여러개의 컨테이너가 구동될 경우 각 컨테이너들은 번호로 구분됩니다.)

 

따라서 도커 컴포즈는 서비스에 정의된 컨테이너들에 대해 docker-compose up [서비스 이름] 명령을 통해 컨테이너를 시작하고 docker-compose down [서비스 이름] 을 통해 컨테이너를 종료할 수 있습니다.

(그냥 docker-compose up, docker-compose down일 경우는 정의된 모든 컨테이너들을 실행, 종료합니다.)

 

도커 컴포즈를 사용한 이유

1. 도입 근거

위와 같이 분산 서버 환경에서 많은 컨테이너를 구동해야 할 때 도커 컴포즈를 이용하기도 하지만 협업 시 개발 로컬 환경을 구성할 때 이용하기도 합니다. 

만약 docker compose를 사용하지 않을 경우, 개발자가 수동으로 프로젝트에 맞게 환경을 구성하거나 설정 충돌을 해결하는 데 드는 시간과 노력이 필요합니다.

 

2. 얻는 이점

docker compose를 사용한다면 해당 프로젝트를 받아 docker compose를 실행하기만 하면 되기 때문에, 통일된 개발 환경에서 빌드가 가능하다는 장점이 존재합니다.

 

3. 비용

docker compose는 오픈소스이므로 별도의 금전적 비용은 들지 않으나, 초기에는 Docker 및 docker compose에 대한 학습이 필요하며, 익숙하지 않은 팀원에게는 약간의 진입 장벽이 될 수 있습니다.

 

docker-compose.yml 파일 구성하기

다음은 컨테이너로 MySQL과 Redis를 띄우는 docker-compose.yml 파일입니다.

services:
  db:
    image: mysql:latest
    container_name: mysql-database
    environment:
      - "MYSQL_RANDOM_ROOT_PASSWORD=1111"
      - "MYSQL_DATABASE=database"
      - "MYSQL_USER=test"
      - "MYSQL_PASSWORD=test"
    ports:
      - "3306:3306"
    healthcheck:
      test: [ "CMD-SHELL", "mysqladmin ping -h localhost -u root -p1111" ]
      interval: 5s
      retries: 10
    restart: on-failure

  redis:
    image: redis:latest
    container_name: redis
    ports:
      - "6379:6379"
    healthcheck:
      test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ]
      interval: 5s
      retries: 10
    restart: on-failure

 

  • services: docker compose에서 실행할 컨테이너를 정의하는 부분입니다.
  • db : MySQL 데이터베이스를 실행할 컨테이너를 정의하는 부분입니다.
  • redis : Redis를 실행할 컨테이너를 정의하는 부분입니다.
  • image : 컨테이너 실행 시 어떤 이미지를 사용할지 정의하는 부분입니다. 해당 이미지가 없을 경우 docker에서 자동으로 다운로드 받습니다.
  • container_name : 실행할 컨테이너 이름을 정의하는 부분입니다.
  • environment : 해당 컨테이너에서 사용될 환경 변수를 정의하는 부분입니다. (MySQL의 경우 DB에 접근할 수 있도록 해당 환경 변수를 설정해주었습니다.)
  • ports : 어떤 포트를 사용할지 정의하는 부분입니다. MySQL의 경우 3306, Redis의 경우 6379가 default 포트 값이므로, 해당 포트 값을 설정해주었습니다.
  • healthCheck : 정상적으로 동작하는지 확인하는 부분입니다.
    • test : 컨테이너의 헬스 체크를 판단할 커맨드를 지정합니다.
      • MySQL의 경우 쉘을 이용하여 "mysqladmin ping -h localhost -u root -p1111"을 보내 ping 명령을 수행합니다.
      • Redis의 경우 redis-cli를 이용해 incr ping 명령을 수행함으로써 쓰기 연산이 제대로 동작하는지 테스트합니다.
    • interval : healthcheck를 얼마나 자주 실행할지 지정합니다.
    • retries : health check가 실패해도 몇 번까지 재시도할지 지정합니다.
  • restart : 컨테이너 재시작을 설정하는 부분입니다.
    • no (기본값) : 절대 재시작하지 않습니다.
    • always : 언제든 종료되면 무조건 재시작합니다. (수동 종료 포함)
    • on-failure : 비정상 종료일 때만 재시작합니다.  
    • unless-stopped : 항상 재시작하되, 사용자가 수동으로 멈추면 다시 시작하지 않습니다.

 

docker-compose 실행하기

먼저 Window의 경우 docker Desktop을 설치합니다.

 

인텔리제이와 같은 IDE를 열고 터미널에

docker-compose up -d

명령어를 실행합니다.

 

그럼 위 사진과 같이 docker-compose.yml파일에 정의된 컨테이너들이 실행되고 있는 모습을 확인할 수 있습니다.

 

실행을 취소하고 싶을 경우

docker-compose down

명령을 실행합니다.

 

따라서 협업하는 개발자 사이에서 컨테이너를 통해 HostOS와 구분되어 동일한 개발환경을 구성하고 정의한 여러가지의 컨테이들을 도커 컴포즈를 이용해서 실행할 수 있습니다.