핵심 정리
도커 컴포즈(Docker Compose)를 이용해 여러 개의 도커 컨테이너로 구성된 분산 애플리케이션을 어떻게 실행하는지에 대해 다룬다.
도커 컴포즈는 여러 개의 컨테이너로 구성된 애플리케이션을 정의하고 관리하기 위한 도구이다. 각 컨테이너가 어떤 이미지를 기반으로 생성될 것인지, 컨테이너 간의 네트워크 구성은 어떻게 될 것인지, 볼륨은 어떻게 사용될 것인지 등을 설정 파일에 명시적으로 정의하고 이를 바탕으로 애플리케이션을 실행할 수 있게 해준다.
이번 포스트에서는 도커 컴포즈의 기본 개념을 이해하고, 간단한 애플리케이션을 도커 컴포즈를 통해 배포한다. 그 과정에서 컴포즈 파일의 주요 구성 요소와 구문을 익히고, 네트워킹과 볼륨을 사용하여 컨테이너 간의 상호작용을 구성하는 방법에 대해 다룬다.
도커 컴포즈
도커 컴포즈에서는 컨테이너의 집합을 '서비스'로, 서비스의 집합을 '프로젝트'로 관리한다. 이를 통해 여러 컨테이너를 하나의 단위로 취급하여 효율적으로 관리할 수 있다.
❔ 도커 컴포즈
: 여러 도커 컨테이너를 함께 실행하고 관리하기 위한 도구
- 애플리케이션을 구동하는 데 필요한 각 컴포넌트는 개별적인 컨테이너에서 실행되며, 도커는 표준 네트워크 프로토콜을 사용해 이러한 컨테이너를 서로 연결한다.
- 이러한 여러 컨테이너가 함께 작동하도록 애플리케이션을 정의하고 관리하는 것이 도커 컴포즈의 주요 역할이다.
- 개별 컨테이너에 대해 일일이 관리하는 번거로움을 줄여주고, 여러 컨테이너로 구성된 애플리케이션의 관리를 단순화하며, 일관된 환경에서 애플리케이션을 실행할 수 있도록 한다.
분산 애플리케이션을 실행하는 방법
프론트엔드(FE), 백엔드(BE) API, 데이터베이스(DB)와 같이 여러 컴포넌트로 구성된 애플리케이션을 패키징하려면, 각 컴포넌트마다 Dockerfile 스크립트가 필요하다.
이런 컴포넌트를 실행하는 방법은 크게 두 가지이다.
1. 각 컨테이너를 일일이 실행하며 필요한 옵션을 지정하는 방법
- 복잡하고 오류가 발생하기 쉽다.
- 옵션을 조금만 잘못 지정해도 애플리케이션은 정상 작동하지 않을 수 있고, 컨테이너 간의 통신에도 문제가 발생할 수 있다.
2. 도커 컴포즈 파일에 애플리케이션의 구조를 정의하는 방법
컴포즈 파일
컴포즈 파일의 구조와 각 섹션별 작성 방법을 소개합니다. 컴포즈 파일은 YAML 형식으로 작성되며, 여기에는 사용할 이미지, 필요한 환경 변수, 볼륨과 네트워크 설정 등 애플리케이션을 실행하는 데 필요한 모든 정보를 포함합니다.
❔ Docker Compose file
- 애플리케이션의 원하는 상태(모든 컴포넌트가 실행 중일 때의 상태)를 정의하는 파일이다.
- 이 파일은 docker container run 명령을 통해 컨테이너를 실행할 때 지정하는 모든 옵션을 한곳에 모아놓은 것이라고 볼 수 있다.
- 도커 컴포즈 파일을 작성한 후에는 도커 컴포즈 도구를 사용하여 애플리케이션을 실행한다.
- 그러면 도커 컴포즈는 도커 API에 명령을 내려 필요한 모든 도커 객체(컨테이너, 네트워크, 볼륨 등)를 생성한다.
Docker Compose file 작성 방법
- 도커 컴포즈 파일은 JSON으로 변환하기 쉬운 YAML 문법을 이용해 작성한다.
- 세 개의 statement으로 구성된다.
- version
- Docker Compose 파일 형식의 버전을 지정한다.
- Docker Compose의 기능 범위를 결정하며, 사용자가 사용 중인 Docker 엔진 버전과의 호환성을 판단하는 데 도움을 준다.
- services
- 애플리케이션을 구성하는 모든 서비스를 열거한다.
- 컨테이너 대신 서비스라는 단위로 동일한 이미지로부터 여러 컨테이너를 실행할 수 있다.
- 서비스의 이름은 컨테이너의 이름이자 도커 네트워크상에서 다른 컨테이너들이 해당 컨테이너를 식별하기 위한 DNS 네임이다.
- 필드 종류
- image: 실행할 이미지를 지정
- ports: 공개할 포트에 대한 정보
- networks: 컨테이너가 접속할 도커 네트워크를 정의
- 아래 예제에서는
- todo-web라는 이름의 서비스는 diamol/ch06-todo-list 이미지를 사용해 컨테이너를 실행하며,
- 호스트의 8020번 포트와 컨테이너의 80번 포트를 bind한다.
- 또한, app-net이라는 도커 네트워크에 연결한다.
- networks
- 서비스 컨테이너가 연결될 모든 도커 네트워크를 나열한다.
- 컨테이너 간의 통신을 가능하게 하는데 필요한 기능이다.
- 아래 예제에서는
- app-net이라는 이름의 네트워크가 external 키워드를 통해 이미 존재하는 외부 네트워크인 nat에 연결되도록 설정한다.
- version
예시 1 - todo 애플리케이션을 실행하는 도커 컴포즈 파일 스크립트
version: '3.7' # Docker Compose 파일 형식의 버전
services: # 애플리케이션의 모든 '서비스'를 정의
todo-web: # 서비스 이름
image: diamol/ch06-todo-list # 서비스의 컨테이너가 사용할 Docker 이미지를 지정
ports: # 호스트-컨테이너 간에 연결할 포트를 지정
- "8020:80" # 호스트의 8020번 포트와 컨테이너의 80번 포트 연결
networks: # 서비스의 컨테이너가 연결될 네트워크를 지정
- app-net # 해당 서비스의 app-net 네트워크에 연결
networks: # 애플리케이션의 모든 네트워크를 정의
app-net: # 네트워크의 이름
external: # 네트워크가 Docker Compose가 아닌 외부에서 정의
name: nat # 네트워크가 연결될 외부 네트워크의 이름을 지정
위 도커 컴포즈 파일은 아래 명령을 실행한 것과 같다.
docker container run -p 8020:80 --name todo-web --network nat diamol/ch06-todo-list
◾ services 내의 networks vs 상위 수준의 networks
◾ services 내의 networks : 특정 서비스가 연결되어야 할 네트워크를 지정
- 해당 서비스가 어떤 네트워크에 연결돼야 하는지 지정한다.
- 서비스의 컨테이너는 이 목록에 나열된 네트워크에 자동으로 연결된다.
- 여러 개의 네트워크를 목록에 넣으면, 해당 서비스는 그 모든 네트워크에 연결된다.
◾ 상위 수준의 networks : 애플리케이션에서 사용 가능한 네트워크를 정의
- 애플리케이션에서 사용되는 네트워크의 전체 목록을 정의한다.
- Docker Compose에게 이 애플리케이션에서 사용할 수 있는 네트워크의 종류와 설정을 알려준다.
- 만약 'external: true'나 'external: name: <network-name>'와 같은 설정을 사용한다면, 그 네트워크는 이미 존재하고 외부에서 관리되는 것이라는 것을 의미한다.
예시 1에서
- services.todo-web.networks 설정은 todo-web 서비스가 app-net 네트워크에 연결되어야 함을 나타내며,
- 상위 수준의 networks 설정은 app-net이라는 이름의 네트워크가 이 애플리케이션에서 사용할 수 있으며, 이 네트워크가 외부의 nat 네트워크에 연결되어 있음을 나타낸다.
◾ 실행 방법
docker network create nat
- Docker Compose는 여러 개의 컨테이너를 함께 실행하는 도구로, 서로 통신할 필요가 있는 컨테이너 사이에 네트워킹을 설정한다.
- 그러나 모든 애플리케이션에서 Docker 네트워크가 필요한 것은 아니다.
- 단일 컨테이너로만 구성된 애플리케이션이나, 컨테이너 사이에 통신할 필요학 없는 애플리케이션에서는 Docker 네트워크가 필요하지 않을 수 있다.
- Docker는 리눅스와 Windows에서 모두 동작하지만, 두 환경에서의 네트워킹 동작은 약간 다르다.
- 리눅스에서는 Docker Compose가 필요한 네트워크를 자동으로 관리해준다.
- 반면 Windows에서는 Docker 설치시 자동으로 생성되는 기본 네트워크인 nat을 사용해야 한다.
docker-compose up
- docker-compose 명령을 실행하면 먼저 현재 작업 디렉터리에서 docker-compose.yml 파일을 찾아 읽는다.
- docker-compose.yml 파일을 통해 필요한 컨테이너들이 이미 실행 중인지 확인하고, 필요한 컨테이너가 없다면 이를 실행한다.
- 예시 1에서는 todo-web 서비스를 구성하는 컨테이너가 없으므로 도커 컴포즈가 컨테이너를 하나 실행한다.
- Docker Compose는 실행 중인 모든 컨테이너의 로그를 표시해주는 기능을 가지고 있다.
- 로그는 각 컨테이너 별로 정리되며, 개발 및 테스트 단계에서 유용하다.
예시 2 - 여러 개의 컨테이너로 구성된 이미지 갤러리를 기술한 컴포즈 스크립트
version: '3.7' # Docker Compose 파일의 버전 지정
services: # Docker Compose에 의해 관리되는 서비스
accesslog: # 서비스 이름
image: diamol/ch04-access-log # accesslog 서비스에 사용될 Docker 이미지 지정
networks: # accesslog 서비스가 app-net 라는 Docker 네트워크에 연결되도록 지정
- app-net
iotd: # 서비스 이름(REST API)
image: diamol/ch04-image-of-the-day # iotd 서비스에 사용될 Docker 이미지 지정
ports:
- "80" # 80번 포트를 호스트 컴퓨터의 무작위 포트를 통해 공개
networks:
- app-net # app-net 네트워크에 연결
image-gallery: # 서비스 이름
image: diamol/ch04-image-gallery # image-gallery 서비스에 사용될 Docker 이미지 지정
ports:
- "8010:80" # 컨테이너의 80번 포트를 호스트의 8010 포트에 바인딩
depends_on: # 의존성 지정
- accesslog
- iotd
networks:
- app-net # app-net 네트워크에 연결
networks: # Docker Compose 프로젝트에서 사용될 네트워크를 정의
app-net:
external:
name: nat
- 8010번 포트는 웹사이트, 8000번 포트는 API에 사용된다.
- API 서비스는 stateless 서비스다.
- stateless 서비스의 장점 중 하나는 서비스의 확장성이다. 각 요청이 독립적이기 때문에, 동일한 서비스의 여러 인스턴스를 실행하는 것이 가능하고, 따라서 scale-out이 가능하다.
- 즉, 트래픽이 많아질 때 추가적인 컨테이너를 늘려 서비스의 처리 능력을 증가시킬 수 있다.
- 도커는 로드 밸런싱 기능을 제공한다.
- 웹 컨테이너가 API 서비스에 요청을 보내면, 도커는 이 요청을 여러 API 서비스 컨테이너 중 하나로 라우팅한다.
- 따라서 API 서비스의 여러 인스턴스가 트래픽을 공유하고 부하를 분산시키는 것이 가능하다.
◾ 실행 방법
docker-compose up --detach # detached mode로 실행
- 만약 iotd 서비스의 컨테이너 수를 늘리고 싶다면
docker-compose up -d --scale iotd=3
- 로그를 확인하고 싶다면
- --tail=1 파라미터: 각 iotd 컨테이너의 마지막 로그를 출력
docker-compose logs --tail=1 iotd
- 다시 실행한다면
docker-compose stop # 애플리케이션 종료(모든 컨테이너가 중지됨)
docker-compose start # 애플리케이션 시작(기존 컨테이너 재시작)
docker container ls # 실행 중인 컨테이너 목록
Docker Compose vs Docker 엔진
- Docker Compose는 클라이언트 측 도구로서, Compose 파일에 기록된 지시에 따라 Docker API를 이용한다.
- Docker 엔진은 컨테이너를 실행하는 것에만 집중하며, 여러 컨테이너가 하나의 애플리케이션으로 작동하는 방식에 대해선 알지 못한다.
- Docker Compose 파일에서 제공한다.
- Docker Compose 파일과 실제 애플리케이션의 상태가 일치하지 않으면 문제가 발생할 수 있다.
- 이는 Docker Compose 파일을 변경하지 않고 직접 컨테이너를 조정했을 때 문제가 발생할 수 있다.
- Docker Compose를 사용하지 않고 직접 컨테이너를 스케일링한 경우, Docker Compose로 애플리케이션을 재시작하면, Compose 파일에 정의된 설정에 따라 애플리케이션 상태가 복구된다.
docker-compose down # 애플리케이션 중지(컴포즈가 관리하는 리소스 모두 제거)
docker-compose up -d # 리소스 다시 생성 후 애플리케이션 재시작
docker-compose ls # 실행 중인 컨테이너 목록(컨테이너 수가 하나로 실행)
네트워킹과 볼륨
- 분산 애플리케이션의 모든 구성요소는 Docker Compose에 의해 도커 컨테이너로 실행된다.
- 컨테이너 각 통신은 각각의 컨테이너가 도커 엔진으로부터 부여받은 가상 IP 주소를 통해 이루어진다. 모든 컨테이너는 동일한 도커 네트워크에 연결되어 있다.
- 애플리케이션의 생명 주기 동안 컨테이너가 교체될 경우 해당 컨테이너의 IP 주소도 변경된다.
- 이를 해결하기 위해 도커는 내장된 DNS 서비스를 이용해 서비스 디스커버리 기능을 제공한다.
- 컨테이너에서 실행되는 애플리케이션은 다른 구성 요소에 접근하기 위해 내장 DNS 서비스를 사용한다. 컨테이너 이름을 도메인으로 사용하여 조회하면 해당 컨테이너의 IP를 알려준다.
도커 컴포즈의 한계
도커 컴포즈 장점
- 복잡한 분산 애플리케이션의 설정을 간결하게 표현할 수 있다.
- YAML 형식의 컴포즈 파일은 애플리케이션 배포 과정을 단순화시킨다.
도커 컴포즈 기능과 목적
- 애플리케이션을 정의하고 이를 도커 엔진이 실행 중인 컴퓨터에 적용할 수 있다.
- 컴퓨서 상에서 실제로 동작하는 도커 리소스와 컴포즈 파일에 정의된 리소스를 비교하고, 필요한 리소스를 도커 API를 통해 요청하거나 수정할 수 있다.
도커 컴포즈 한계
- 도커 컴포즈는 컨테이너 배포를 위한 도구이지만, 도커 스웜이나 쿠버네티스와 같은 오케스트레이션 플랫폼은 아니다.
- 애플리케이션이 지속적으로 정의된 상태를 유지하도록 하는 기능, 일부 컨테이너에 오류가 발생하거나 중지되었을 때 애플리케이션의 상태를 복구하는 기능, 고가용성, 로드 밸런싱, 이중화 등의 기능이 없다.
도커 컴포즈 적절한 사용 시기
- Dev
- 개발자는 자신의 컴퓨터에서 애플리케이션을 실행하고 end-to-end 테스트를 수행하기 위해 도커 컴포즈를 사용한다.
- CI
- 지속적 통합 프로세스 중에서 빌드 및 자동화된 테스트를 수행하기 위해 컴포즈를 사용한다.
- Test
- 단일 서버에서 테스트를 진행하려면 도커 컴포즈를 사용하면 된다.
- Production
- 실제 운영 환경에서는 보통 도커 스웜이나 쿠버네티스 같은 오커스트레이션 플랫폼이 사용되지만, 애플리케이션의 정의는 여전히 도커 컴포즈 파일 포맷을 사용한다.
- 즉, 도커 컴포즈는 애플리케이션의 개발 및 테스트 단계에서 주로 사용되며, 프로덕션 환경에서는 보다 강력한 오케스트레이션 도구가 필요하다.
'DevOps > Docker' 카테고리의 다른 글
[Docker] 컨테이너 모니터링 (0) | 2023.07.28 |
---|---|
[Docker] 헬스 체크와 디펜던시 체크 (0) | 2023.07.18 |
[Docker] 도커 볼륨과 마운트 (0) | 2023.07.18 |
[Docker] 도커 레지스트리와 도커 허브: 이미지 공유와 관리 (0) | 2023.07.18 |
[Docker] 멀티 스테이지와 애플리케이션 빌드 (0) | 2023.07.09 |