각 컴포넌트는 자신만의 경량 컨테이너에서 실행되며 도커가 표준 네트워크 프로토콜을 통해 이들 컨테이너를 엮는다. 도커 컴포즈를 사용하면 이렇게 여러 컨테이너에 걸쳐 실행되는 애플리케이션을 정의하고 관리할 수 있다.
Dockerfile 스크립트가 애플리케이션을 패키징하기 위한 스크립트임을 이제 우리는 안다. 그러나 분산 애플리케이션을 기준으로 보면 한 부분을 패키징하는 수단에 지나지 않는다. 그렇게 되면 각 애플리케이션마다 스크립트가 필요하고 직접 옵션을 지정해 가며 실행해야한다.
이런 방법 대신 도커 컴포즈 파일에 애플리케이션의 구조를 정의하면 된다. 도커 컴포즈 파일은 애플리케이션의 '원하는 상태', 즉 모든 컴포넌트가 실행 중일 때 어떤 상태여야하는지를 기술한 파일이다. 또한 docker container run 명령으로 컨테이너를 실행할 때 지정하는 모든 옵션을 한데 모아둔 파일이다. 도커 컴포즈 파일을 작성하고나면 도커 컴포즈 도구를 사용해 애플리케이션을 실행한다. 그러면 도커 컴포즈가 컨테이너, 네트워크, 볼륨 등 필요한 모든 도커 객체를 만들도록 도커 API에 명령을 내린다.
version: '3.7'
services:
todo-web:
image: diamol/ch06-todo-list
ports:
- "8020:80"
networks:
- app-net
networks:
app-net:
external:
name: nat
도커 컴포즈는 YAML 문법으로 기술된다.(들여쓰기 중요)
다음과 같은 세 개의 최상위 문으로 구성된다.
위의 스크립트에서 todo-web부터 app-net까지는 결국
docker container run -p8020:80 --name todo-web --network nat diamol/ch06-todo-list
와 같은 것이다.
서비스 이름 아래로는 속성이 기술된다.
image는 실행할 이미지를 지정하는 빌드이고
ports는 공개할 포트에 대한 정보
networks는 컨테이너가 접속할 도커 네트워크를 정의하는 필드다.
서비스 이름은 컨테이너의 이름이자 도커 네트워크상에서 다른 컨테이너들이 해당 컨테이너를 식별하기 위한 DNS 네임으로 쓰인다.
여기서 서비스가 구성될 네트워크 이름은 app-net이다. 그러나 nat이라는 이름의 외부 네트워크로 연결되므로 external 필드는 새로 생성하지 말라는 뜻이다.
도커 컴포즈를 사용하려면
docker-compose
를 입력하면 된다. 애플리케이션을 실행하려면 up 명령을 실행하는데 이러면 도커 컴포즈가 컴포즈 파일을 제크하고 정의된 상태로 실행하기 위해서 필요한 요소를 준비하기 시작한다.
docker network create nat
docker-compose up
(윈도 컨테이너를 사용한다면 도커를 설치할 때 자동으로 생성되는 기본 네트워크 nat을 사용해야 한다.)
docker-compose 명령을 실행하면 먼저 현재 작업 디렉터리에서 docker-compose.yml 파일을 찾는다. 해당 파일 발견하면 to-do 애플리케이션의 정의를 읽어 들인다. 현재는 todo-web 서비스를 구성하는 컨테이너가 없으므로 도커 컴포즈가 컨테이너를 하나 실행한다. 이때 출력되는 애플리케이션 로그를 컨테이너별로 정리해서 보여준다. 이 기능은 개발 및 테스트를 수행할 때 매우 도움이 된다.
도커 컴포즈 파일 형식에는 애플리케이션의 모든 설정 사항값과 최상위 레벨 도커 요소가 정의된다. 이 애플리케이션은 단일 서비스로 구성되지만, 이런 경우에도 컴포즈 파일을 통해 설정 사항을 간접적으로 문서화하는 효과를 얻을 수 있다.
4장에 나눠했던 일을 이제 쉽게 해보자.
version: '3.7'
services:
accesslog:
image: diamol/ch04-access-log
networks:
- app-net
iotd:
image: diamol/ch04-image-of-the-day
ports:
- "80"
networks:
- app-net
image-gallery:
image: diamol/ch04-image-gallery
ports:
- "8010:80"
depends_on:
- accesslog
- iotd
networks:
- app-net
networks:
app-net:
external:
name: nat
여기서 중요한 것은 depends_on으로 의존성을 만족하기 위해 컴포즈는 image_gallery 서비스를 실행하기 전에 여기 나열된 두 서비스를 먼저 실행하려 시도하게 된다는 것이다.
이번에는 분리 모드로 애플리케이션을 실행한다. 컴포즈가 우리 대신 컨테이너 로그를 수집하지만, 컨테이너는 백그라운드로 동작하므로 그동안 컴포즈의 기능을 몇 가지 더 사용해 보자.
docker-compose up --detach
를 실행해 보면, image_gallery 서비스를 시작하기 전에 다른 두개의 서비스를 먼저 실행하는 것을 알 수 있다.
4장과 달라진 점은 여러 컨테이너의 설정과 이들이 어떻게 함께 동작하는지 적힌 도커 컴포즈 파일이 생긴 것이다. API 서비스는 상태가 없으므로 컨테이너를 늘리는 방법으로 스케일 아웃 할 수 있다. 웹 컨테이너가 API에 데이터를 요청하면 도커가 여러 개의 API 컨테이너에 이 요청을 고르게 분배해 준다.
docker-compose up -d --scale iotd=3
해당 명령어는 도커 컴포즈를 사용해 iotd 서비스의 컨테이너수를 늘려보는 것이다.
docker-compose logs --tail=1 iotd
명령 실행 후 출력되는 내용을 보면 API 서비스의 컨테이너를 두 개 늘려 애초의 세 배로 만드는 것을 알 수 있다.
도커 컴포즈가 나를 대신해 컨테이너를 관리해 준다. 물론 전체 애플리케이션도 관리 할 수 있다. 이런 작업은 도커 명령행을 통해서도 할 수 있는 작업이다.
docker-compose stop
docker-compose start
docker-compose ls
도커 컴포즈의 다양한 기능을 배우기 이전에 하나 알아야하는 것이 있다.
도커 컴포즈는 클라이언트 측에서 동작하는 도구라는 점이다. 도커 컴포즈 명령을 실행하면 컴포즈 파일의 내용에 따라 도커 API로 지시를 보낸다. 도커 엔진 자체는 컨테이너를 실행할 뿐, 여러 컨테이너가 하나의 애플리케이션으로 동작하는지 여부는 알지 못한다. 이를 아는 것은 컴포즈 뿐이다.
컴포즈 파일을 수정하거나 도커 명령행으로 직접 애플리케이션을 수정하면, 애플리케이션이 컴포즈 파일에 기술된 구조와 불일치하게 만들 수 있다.
docker-compose down
docker-compose up -d
docker container ls
여기서 down은 애플리케이션을 제거하는 명령으로, 애플리케이션이 중지되고 컨테이너를 모두 제거한다. 컴포즈 파일에 포함 됐으나 external 플래그를 붙이지 않았다면 네트워크와 볼륨도 제거 대상이 된다.
up은 시작하는 명령이다. 하지만 지금 실행 중인 컨테이너가 없기에 컴포즈 파일에 정의된 내용대로 모두 다시 생성한다. 대신 컨테이너수가 다시 하나로 돌아간다.
컨테이너가 통신하는 방법에 대해 알아보자. 컨테이너는 도커 엔진으로부터 부여 받은 자신만의 가상 IP 주소를 가지며 모두 같은 도커 네트워크로 연결돼 이 IP 주소를 통해 서로 통신할 수 있다. 그러나 애플리케이션 생애주기 동안에 컨테너가 교체되면 IP 주소도 변경된다. IP 주소가 변경돼도 문제가 없도록 도커에서 DNS를 이용해 서비스 디스커버리 기능을 제공한다.
컨테이너에 실행 중인 애플리케이션도 다른 구성 요소에 접근하기 위해 DNS 서비스를 사용한다. 만약 도메인이 가리키는 대상이 컨테이너가 아니라면, 도커 엔진을 실행 중인 컴퓨터에 요청을 보내 호스트 컴퓨터가 속한 네트워크나 인터넷의 IP 주소를 조회한다. 앞선 예제를 통해 확인 가능하다. 도커의 DNS 서비스로부터 받은 응답에는 서비스를 구성하는 컨테이너의 IP 주소가 있을 것이다. 한 컨테이너로 구성된 서비스라면 IP 주소도 하나이고, 여러 컨테이너로 구성됐다면 이들 모두 IP 주소를 받는다.
docker-compose up -d --scale iotd=3
docker container exec -it image-of-the-day-image-gallery-1 sh
nslookup은 웹 애플리케이션 컨테이너의 기반 이미지에 들어 있는 유틸리티다. 명령의 인자로 도메인을 지정하면 해당 도메인을 DNS 서비스에서 조회하고 그 결과를 출력한다. 현재 내 환경에서 accesslog의 IP주소는 172.18.0.2다
도커 네트워크에 연결된 모든 컨테이너는 이 네트워크의 범위에 포함되는 IP 주소를 부여받는다.
docker container rm -f image-of-the-day-accesslog-1
docker-compose up -d --scale iotd=3 //도커 컴포즈로 애플리케이션을 재실행
docker container exec -it image-of-the-day-image-gallery-1 sh
중간에 보면 강제로 삭제하니 컴포즈 파일에 정의된 것과 달라져 compose를 이용해 대응하는 모습을 볼 수 있다.
6장에서는 데이터베이스 파일을 관리하기 위해 볼륨을 사용하였다. 이번에는 별도의 데이터베이스를 사용할 것이다.
version: "3.7"
services:
todo-db:
image: diamol/postgres:11.5
ports:
- "5433:5432"
networks:
- app-net
todo-web:
image: diamol/ch06-todo-list
ports:
- "8030:80"
environment:
- Database:Provider=Postgres
depends_on:
- todo-db
networks:
- app-net
secrets:
- source: postgres-connection
target: /app/config/secrets.json
networks:
app-net:
secrets:
postgres-connection:
file: ./config/secrets.json
여기서 볼 것은 2가지이다.
애플리케이션 설정값을 컴포즈 파일에 정의하면, 같은 도커 이미지라도 다양하게 활용할 수 있고 서로 다른 각 환경에 대한 설정을 명시적으로 정의할 수 있다.
docker-compose up -d
docker-compose ps
2번째 명령은 컴포즈 파일에 저으이된 모든 컨테이너의 목록을 보여 준다.
패키징된 어플리케이션과 설정값을 분리할 수 있다는 것도 도커의 핵심적인 장점 중 하나이다.
도커 컴포즈의 목적이 무엇이고 어떤 제한 사항이 있는지를 잘 이해해야 한다.
docker-compose up 명령을 실행하기만 하면 내가 정의한 상태대로 애플리케이션을 실행할 수 있다. 하지만 도커 컴포즈가 할 수 있는 일은 여기까지다. 해당 기능은 애플리케이션이 지속적으로 정의된 상태를 유지하도록 하는 기능이 없다. 일부 컨테이너가 오류 및 강제 종료 되더라도 docker-compose up 명령을 다시 실행하지 않는 한 애플리케이션의 상태를 원래대로 되돌릴 수 없다.
이번 연습 문제는 to-do 애플리케이션을 테스트 환경에서 다음과 깉이 좀 더 신뢰성 있게 실행하는 컴포즈 파일 정의를 작성하는 것이다.
1. 호스트 컴퓨터가 재부팅되거나 도커 엔진이 재시작되면 애플리케이션 컨테이너도 재시작되도록 하라.
2. 데이터베이스 컨테이너는 바운드 마운트에 파일을 저장해 애플리케이션을 재시작하더라도 데이터를 유지할 수 있도록 하라
3. 테스트를 위해 웹 애플리케이션은 80번 포트를 주시해라
힌트 : https://docs.docker.com/compose/compose-file
자 1번 부터 해보자