카테고리 없음

명령어에서 선언으로: Docker Compose로 더 나은 컨테이너 관리하기

sedong 2025. 4. 6. 16:51

명령형 프로그래밍과 선언형 프로그래밍

https://www.copebit.ch/en/how-declarative-and-imperative-styles-differ-in-infrastructure-as-code/

 

명령형(Imperative)과 선언형(Declarative) 프로그래밍 패러다임은 컴퓨터 프로그래밍 및 시스템 관리에서 두 가지 주요 접근 방식입니다.

 

이 두 패러다임은 Docker CLI를 이용한 명령어 실행과 Docker Compose를 사용하는 방식과도 밀접한 관련이 있기 때문에 두 가지 방식을 알아보도록 하겠습니다.


명령형 프로그래밍

명령형 프로그래밍은 시스템의 상태 변경을 위해 필요한 명령을 명시적으로 작성한다.

사용자는 시스템이 어떻게 동작해야 하는지 세부적으로 기술해야 한다.

Docker cli 를 사용하는 방식은 명령형 프로그래밍의 대표적인 예다.

Docker cli 를 통해 사용자는 컨테이너의 라이프사이클을 관리하는 명령을 하나씩 수행한다.

# docker image build
docker image build -t my-web-app:v1 .

# docker container run
docker container run -d -p 80:80 --name my-web my-web-app:v1 

# docker container process 
docker container ps

여기서 각각의 명령어는 시스템의 상태를 변경하기 위해 사용자가 직접 입력해 줘야 한다.

사용자는 시스템 (여기서는 도커와 관련된 오브젝트)의 상태를 변경하기 위해 필요한 명령어를 순차적으로 실행해야 하며, 사용자는 명확히 명령어에 대해서 알고 있어야 정확히 시스템의 상태를 변경 가능하다.


선언형 프로그래밍

선언형 프로그래밍은 시스템의 상태를 정의하고 시스템이 그 상태에 도달하기 위한 방법을 시스템이 결정하도록 한다.

사용자는 어떻게 그 상태를 도달하는지 정의(명령형 프로그래밍)하는 것이 아닌 무엇을 원하는지 상태를 명시(선언형 프로그래밍)한다.

docker compose는 선언형 프로그래밍을 따른다.

사용자는 docker compose 파일에 원하는 상태를 정의한다. 예를 들어 사용하려는 도커파일, 네트워크, 포트 설정, 환경 변수 등등이다.

docker compose는 사용자가 정의한 상태를 달성하기 위해 내부적으로 수행되는 메커니즘을 통해 원하는 상태에 도달한다.

version: '3.9'
services:
  web:
    build: .
    ports:
      - "80:80"
  db:
    image: postgres
    environment:
      POSTGRES_USER: example
      POSTGRES_PASSWORD: example

 

위는 docker compose의 예시 파일이다. 사용자는 위 파일을 정의하고 docker compose up 명령어를 실행하면 docker compose는 선언된 상태를 달성한다.

 

docker compose up -d

 

이러한 접근 방식은 사용자가 시스템의 상태를 정의하고 어떻게 에 해당하는 부분은 docker compose 가 내부적으로 수행하므로 선언적 프로그래밍의 방식을 따른 것이다.


Docker compose

docker compose 는 다중(혹은 단일) 컨테이너 애플리케이션의 실행을 위한 도구이다.

docker compose를 사용하기 위해 도커 컨테이너 기반으로 실행될 애플리케이션의 서비스를 정의하기 위해 yaml 파일을 작성해야 한다.

선언한 yaml 파일을 읽어 다중 컨테이너 애플리케이션을 생성하고 시작할 수 있다.

docker compose는 docker cli와 비교하여 다음과 같은 작업을 용이하게 만든다.

  • 여러 컨테이너로 구성된 애플리케이션 실행 및 중지
  • 복잡한 명령어를 순차적으로 호출하는 것이 아닌 단일 명령으로 컨테이너 환경 시작 및 중지

docker compose를 사용하면 일관된 환경으로 다중 컨테이너를 관리할 수 있다.


docker compose 사용 방식

docker compose는 주로 docker-compose.yaml 파일을 사용하여 애플리케이션의 서비스를 정의한다.

해당 파일엔 컨테이너로 실행될 각 애플리케이션의 이미지, 빌드 설정, 볼륨, 네트워크, 환경변수, 포트, 컨테이너 간 의존성 등 다양한 것을 포함한다.

예를 들어, 간단한 웹 애플리케이션과 데이터베이스 설정을 다음과 같이 정의할 수 있다.

version: '3.9'
services:
  web:
    image: my-web-app
    build: .
    ports:
      - "80:80"
  db:
    image: postgres
    environment:
      POSTGRES_USER: example
      POSTGRES_PASSWORD: example
    depends_on:
		  - web

 

위 파일을 생성한 디렉터리에서 다음 명령어를 사용하여 도커 기반의 컨테이너로 애플리케이션을 실행할 수 있다.

 

docker compose up -d

 

해당 명령어는 docker-compose.yaml 파일에 정의된 모든 서비스를 백그라운드에서 실행한다.

만약 docker cli를 사용하여 동일한 작업을 수행하기 위해선 해당 명령어를 실행해야 한다.

 

# docker web app 이미지 빌드
docker build -t my-web-app .

# web app 이 사용하는 데이터베이스 연결을 위해 db 먼저 실행
docker run -d --name db -e POSTGRES_USER=example -e POSTGRES_PASSWORD=example postgres

# web app 실행
docker run -d -p 80:80 --name web --link db my-web-app

 

여기서 각 명령은 순차적으로 실행되어야 하며, 사용자는 컨테이너의 설정과 연결을 직접 작성하고 관리해야 한다.


docker compose 기본 문법

docker compose에서 사용하는 주요 항목은 다음과 같다.

version: '3.9'
name: 프로젝트 이름
services:
  service_name:
	container_name: 컨테이너명
    image: 이미지명:태그
    build:  # 이미지가 레지스트리에 없는 경우 build 로 바로 이미지 빌드 가능
	  context: .  # 도커 파일을 가져올 컨텍스트
      dockerfile: Dockerfile  # 도커 파일 이름
    ports:  # 포트 설정
      - "host_port:container_port"
    environments:  # 환경 변수 설정
      - ENV_VAR_NAME=value
    volumes:  # 볼륨 설정
      - host_path:container_path
    networks:  # 도커 네트워크 설정
      - network_name
    depends_on:  # 의존관계가 있는 컨테이너 설정
	  - service name
  1. version
    • docker compose 가 docker-compose.yaml 파일을 읽을 때 사용할 docker compose의 버전을 지정한다.
  2. services
    • 애플리케이션을 구성하는 여러 컨테이너에 대한 구성을 정의하는 부분이다. 각 서비스는 고유한 이름을 가져야 하며, 이미지, 빌드 설정, 포트, 환경변수 등을 포함할 수 있다.
  3. service_name
    • 서비스의 고유한 이름을 정의한다. 이는 컨테이너 이름과는 다르며 docker compose 가 인식하는 서비스 이름이다.
  4. image
    • 서비스를 생성하는데 사용할 docker image 를 지정한다. 이미지는 docker hub (이미지 레지스트리) 혹은 로컬에서 사용 가능하다.
  5. build
    • docker image 를 빌드하는데 사용할 디렉토리와 파일을 지정한다.
  6. ports
    • 호스트와 컨테이너간의 포트 매핑 설정을 작성한다.
  7. environments
    • 컨테이너 내부에서 사용할 환경 변수를 설정한다. 리스트 형식 혹은 딕셔너리 형식을 사용 가능하다.
    • 리스트 형식
    environments:
      - POSTGRES_USER=example
      - POSTGRES_PASSWORD=example
    • 딕셔너리 형식
    environments:
      POSTGRES_USER: example
      POSTGRES_PASSWORD: example
  8. volumes
    • 호스트와 컨테이너간 파일 시스템의 공유를 위해 사용되는 볼륨을 설정한다.
  9. networks
    • 서비스 간 네트워크 설정을 정의한다. 사용자가 정의한 네트워크를 지정할 수 있으며 사용자 지정 네트워크 정의를 위해선 하단에 networks 섹션을 추가해야 한다.
    # 위에서 작성한 docker-compose.yaml 하단에 추가
    
    networks:
      my-network:
        driver: bridge
    

 

작성된 docker-compose.yaml 파일은 docker compose 명령어를 사용해서 관리할 수 있다.

# background 로 compose 에 정의된 서비스 시작
docker compose up -d

# 실행중인 서비스 목록 보기
docker compose ps

# 컴포즈에 선언된 모든 컨테이너 중지 및 제거
docker compose down

# 도커 컴포즈에 선언된 서비스 로그 보기
docker compose logs -f

 

docker compose 의 문법과 사용 방법은 공식 문서에서 더 자세한 모든 것을 알 수 있다.

docker compose의 문법과 사용 방식을 이해하면, 여러 컨테이너로 구성된 애플리케이션을 손쉽게 관리하고 배포할 수 있다.

compose 파일을 통해 모든 설정을 명시적으로 정의할 수 있기 때문에, 사용자는 실수에 대한 걱정 없이 모든 환경 간 일관성을 쉽게 유지, 관리할 수 있다.


Docker Compose와 프로그래밍 패러다임

docker compose의 장단점

docker cli와 비교해서 docker compose의 장점과 단점은 다음과 같다.

장점

  • 간단한 설정 관리
    • docker-compose.yaml 파일 하나로 다중 컨테이너 애플리케이션의 설정을 관리할 수 있다.
    • docker cli를 사용해 각 컨테이너를 개별적으로 설정하고 관리하는 것보다 훨씬 간편하다.
  • 일관성 유지
    • 개발, 테스트, 스테이징 환경에서 각기 다른 compose 파일을 정의하고 사용할 수 있어 환경별 일관성을 유지하기 쉽다.
  • 배포 간소화
    • 간단한 명령어로 모든 서비스를 시작하고 중지할 수 있기 때문에 배포 과정이 단순하고 신속하다.
  • 명확한 선언형 구성
    • 선언형 프로그래밍 접근 방식으로 애플리케이션의 최종 상태를 명확하게 정의할 수 있다.
    • 시스템 상태를 명령어의 순서에 의존하지 않기 때문에 실수의 여지가 줄어든다.

단점

  • 학습 곡선
    • compose 파일의 문법과 구성을 이해하는 데 시간이 걸린다.
  • 복잡성 증가
    • 단순한 애플리케이션의 경우 docker cli 만으로도 충분할 수 있다.

 

 

단일 애플리케이션을 배포하는 경우도 docker cli 를 사용하는 것이 더 단순할 수 있다.

하지만 docker compose를 사용하는 것이 더 명확하고 실행하는 사용자의 실수에 대한 여지가 적어진다.

이는 선언형 프로그래밍의 장점으로, 애플리케이션의 구성과 종속성을 명확하게 정의하고 실행에 대한 부분을 시스템에 할당하여 실행 방식을 단순화한다.

docker compose의 학습 곡선과 약간의 복잡성 증가를 고려하더라도, 대부분의 경우 docker compose를 사용하는 것이 컨테이너로 서비스를 실행하는 데 있어 효율성을 높이고 안정성을 높일 수 있다.


명령형 vs 선언형 프로그래밍 비교

Docker Compose의 장단점은 명확히 선언형 프로그래밍 방식에서 기인하며, 이는 명령형 방식과의 차이점과 1:1로 대응된다.

명령형 ↔ 선언형 프로그래밍 패러다임의 철학이 실제 도구 사용에서 어떤 영향을 주는지를 보여주는 사례이다.

 

구분 명령형 프로그래밍(Imperative) 선언형 프로그래밍(Declarative)
사고방식 무엇을 어떻게 할지 직접 명령함 무엇이 되길 원하는지 상태를 정의함
중심 개념 과정(절차)에 집중 결과(상태)에 집중
사용자 역할 모든 단계를 순서대로 작성 원하는 결과만 정의
예시 도구 Docker CLI Docker Compose
작성 예시 컨테이너 생성, 실행, 네트워크 연결 등을 각각 명령어로 수행 서비스 정의를 yaml 파일에 작성 후 up 명령 한 번으로 실행
유지보수 순서나 의존성 실수 가능성 존재 구조적으로 명확해 관리가 쉬움
초기 진입 빠르게 시작 가능 문법 이해 필요
복잡한 구성 명령이 많아지고 실수 가능성 증가 한 파일로 구성 가능, 재사용성 높음