kimyu0218
  • [docker] Docker Compose로 멀티 컨테이너 어플리케이션 만들기 (yaml/volumes)
    2024년 01월 16일 22시 16분 12초에 업로드 된 글입니다.
    작성자: @kimyu0218

    이번엔 도커 컴포즈를 이용하여 멀티 컨테이너 어플리케이션을 만들 것이다. 멀티 컨테이너를 구축하면 서비스별로 컨테이너를 만들기 때문에 각 컨테이너가 한 가지 일만 수행하고, 서로의 리소스에 영향을 미치지 않는다는 장점이 있다.

     

    Docker Compose를 사용하는 이유

    docker CLI의 단점

    결론부터 말하면 도커 CLI는 단일 서비스에 최적화 되어있기 때문에 복잡한 멀티 컨테이너를 다루기엔 적합하지 않다. 도커 컨테이너는 격리된 환경을 가지므로 다른 컨테이너와 통신하기 위해서는 동일한 네트워크에 배치해줘야 한다. 이처럼 멀티 컨테이너를 위한 설정으로 인해  CLI 명령어가 길어지고 복잡해진다.

    docker CLI로 멀티 컨테이너를 띄울 때의 단점
    • 복잡성 및 가독성 : 네트워크, 볼륨 등 멀티 컨테이너 설정이 복잡한 경우, CLI 명령이 길어지고 복잡해진다.
    • 종속성 및 실행 순서 : 여러 컨테이너 간에 실행 순서나 종속성이 있을 경우, CLI에서 실행 순서를 명시적으로 정의해줘야 한다.

     

    Docker Compose란?

    도커 컴포즈는 여러 컨테이너로 이루어진 어플리케이션을 정의하고 실행하기 위한 도구다. 도커 컴포즈는 YAML 파일을 사용하여 어플리케이션 서비스를 설정한다. 그런 다음, 하나의 명령으로 어플리케이션을 실행할 수 있다.

    Docker Compose의 장점
    • 단일 호스트에 여러 개의 격리된 환경 구축
    • 컨테이너 생성 시 볼륨 데이터 보존 : `docker-compose up` 시 이전 컨테이너가 실행 중이면, 이전 컨테이너의 볼륨을 새 컨테이너에 복사하므로 데이터가 손실되지 않는다.
    • 변경된 컨테이너만 다시 생성 : 변경되지 않은 서비스를 재시작하면 기존 컨테이너를 재사용한다.

    docker-compose.yml 작성하기

    이제 멀티 컨테이너 어플리케이션의 설정파일을 작성해볼 것이다. 도커 컴포즈는 명령어를 실행할 때 해당 파일을 참조하여 컨테이너를 실행하고 관리한다.

    지금부터 nginx, was, signal 서비스를 별도의 컨테이너로 띄울 것이다. nginx가 url 정보를 바탕으로 was와 signal 컨테이너로 프록시하는 형태다.

    version: "3.3"
    
    services:
      was:
        container_name: "was"
        build:
          context: .
          dockerfile: Dockerfile.was
        env_file: .env
        environment:
          - PORT=3000
        expose:
          - "3000"
        volumes:
          - /var/log/was:/app/was/logs
          - /var/log/ormlogs.log:/app/was/ormlogs.log
    
      signal:
        container_name: "signal"
        build:
          context: .
          dockerfile: Dockerfile.signal
        environment:
          - PORT=3001
        expose:
          - "3001"
    
      nginx:
        container_name: "nginx"
        build:
          context: .
          dockerfile: Dockerfile.nginx
        ports:
          - "80:80"
          - "443:443"
        depends_on:
          - was
          - signal
        volumes:
          - /var/log/nginx:/var/log/nginx

    services 섹션은 여러 서비스를 정의하는 부분이다. 각 서비스는 특정 기능이나 역할을 수행하도록 구성되며, 독립적인 컨테이너로 실행된다. nginx, was, signal을 별도의 컨테이너에 띄우기 때문에 서비스로 정의했다.

     

    was 서비스 정의하기

    was 서비스를 작성하기에 앞서, 서비스를 정의할 때 자주 사용되는 중요한 설정들을 살펴보자.

    • image : 서비스에 사용할 도커 이미지를 지정한다.
    • build : 이미지를 빌드할 때 사용할 Dockerfile 경로를 지정한다.
    • ports : 호스트 포트와 컨테이너 포트를 매핑하여 호스트와 컨테이너 간의 네트워크 통신을 허용한다.
    • environments : 환경변수를 설정하고 컨테이너에 전달한다.
    • depends_on : 의존성을 설정하여 특정 서비스가 시작되기 전에 다른 서비스가 먼저 시작되도록 한다.
    was:
      container_name: "was"
      build:
        context: .
        dockerfile: Dockerfile.was
      env_file: .env
      environment:
        - PORT=3000
      expose:
        - "3000"
      volumes:
        - /var/log/was:/app/was/logs
        - /var/log/ormlogs.log:/app/was/ormlogs.log

    container_name: "was"

    container_name은 말 그대로 컨테이너 이름을 지정하는 부분이다. 해당 설정을 통해 기본적으로 설정되는 이름 대신 was라는 이름의 컨테이너가 생성된다.

     

    build

    build 섹션은 이미지를 빌드하는 데 사용된다. build 섹션을 통해 로컬에 있는 Dockerfile을 기반으로 새로운 컨테이너를 빌드할 수 있다.

    build:
      context: .
      dockerfile: Dockerfile.was
    *context : Dockerfile이 있는 디렉토리 경로로, 명시적으로 설정하지 않으면 `.`을 사용한다.
    *dockerfile : 사용할 Dockerfile의 이름을 지정하며 생략 context에 있는 기본 Dockerfile을 사용한다. 만약 dockerfile이 설정된 경우, dockerfile_inline 속성을 사용할 수 없다.
    🚨 build 섹션과 image 섹션*을 동시에 사용하면 image 섹션을 무시한다.
    *image 섹션은 이미 빌드된 이미지를 사용할 때 사용한다.

     

    expose 3000

    expose는 컨테이너에서 어떤 포트를 노출할지 지정한다. 노출된 포트는 컨테이너 외부에서 내부로 접근할 때 사용된다. `expose 3000`을 통해 컨테이너에 접속하기 위해 3000번 포트를 이용해야 한다는 걸 알 수 있다.

     

    volumes

    volumes는 컨테이너와 호스트 간에 파일 시스템을 공유하는 방법 중 하나다. 볼륨을 통해 컨테이너의 파일 시스템 변경이 호스트 파일 시스템에 실시간으로 반영되고, 그 반대도 성립한다. 컨테이너가 종료되거나 삭제되더라도 호스트 파일 시스템의 내용이 남아있으므로 데이터를 보존할 수 있다.

    volumes:
      - /var/log/was:/app/was/logs
      - /var/log/ormlogs.log:/app/was/ormlogs.log

    해당 구문을 통해 호스트의 `/var/log/was` 디렉토리를 컨테이너의 `/app/was/logs` 디렉토리와 공유하게 된다.

     

    nginx 서비스 정의하기

    signal 서비스는 nginx와 비슷하니 생략하고, nginx 서비스를 작성해보자. nginx는 was와 signal 서비스로 연결해주는 역할을 수행하기 때문에 was와 signal 서비스가 생성된 후에 만들어져야 한다.

    nginx:
      container_name: "nginx"
      build:
        context: .
        dockerfile: Dockerfile.nginx
      ports:
        - "80:80"
        - "443:443"
      depends_on:
        - was
        - signal
      volumes:
        - /var/log/nginx:/var/log/nginx

    depends_on

    depends_on은 서비스 간의 시작과 종료 의존성을 나타낸다. short syntax와 long syntax가 존재하는데 short syntax의 경우, 의존하는 서비스의 이름만 나열한다.

    depends_on:
      - was
      - signal

    해당 구문은 was와 signal이 nginx가 생성되기 전에 먼저 생성됨을 의미한다. nginx가 실행될 때 이미 was와 signal이 존재하므로 서비스를 찾지 못하는 에러가 발생하지 않는다.


    Compose CLI로 이미지 빌드하고 실행하기

    이제 YAML 파일을 바탕으로 멀티 컨테이너를 한 번에 빌드하고 실행해보자. 도커 CLI는 Dockerfile을 실행하는 명령이므로 컴포즈 CLI를 이용해야 한다.

     

    이미지 빌드하기

    docker-compose -f docker-compose.yml build

    `-f` 플래그는 컴포즈 설정파일의 이름과 경로를 지정한다. 해당 플래그를 통해 플래그 뒤의 설정파일을 찾아 이미지를 빌드한다. (`-f` 플래그를 사용하지 않으면 기본적으로 현재 디렉토리의 `docker-compose.yml`을 찾는다!) 

     

    컨테이너 실행하기

    docker-compose -f docker-compose.yml up -d

    빌드한 이미지를 바탕으로 컨테이너를 시작한다. 도커 CLI처럼 `-d` 플래그를 이용하여 백그라운드에서 실행할 수 있다.

    💡 up 명령을 통해 빌드와 실행을 동시에 할 수도 있다.


    서비스 개수만큼 도커 CLI를 실행하지 않고, 간편하게 멀티 컨테이너 띄우기 성공 😎  


    참고자료

    댓글