본 글은 도커 교과서를 읽고 정리한 내용입니다.
도커 컴포즈 파일의 구조
도커 컴포즈 파일은 애플리케이션의 '원하는 상태', 즉 모든 컴포넌트가 실행 중일 때 어떤 상태여야 하는지를 기술하는 파일.
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 문법으로 기술된다.
위 도커 컴포즈 파일은 다음과 같은 세 개의 최상의 문으로 구성된다.
- version은 이 파일에 사용된 도커 컴포즈 파일 형식의 버전을 가리킨다.
- services는 애플리케이션을 구성하는 모든 컴포넌트를 열거하는 부분이다. 도커 컴포즈는 실제 컨테이너 대신 서비스(service)개념을 단위로 삼는다. 하나의 서비스를 같은 이미지로 여러 컨테이너에서 실행할 수 있기 때문.
- networks는 서비스 컨테이너가 연결된 모든 도커 네트워크를 열거하는 부분
도커 컴포즈를 사용해 이 애플리케이션을 실행하면 컨테이너 하나가 실행돼 스크립트에 정의된 구성을 갖춘다.
위 도커 컴포즈 스크립트 파일은
docker container run -p 8020:80 --name todo-web --network nat diamol/ch06-todo-list
명령을 실행한 것과 같은 상태.
서비스 이름 아래로 속성이 기술된다.
- image : 실행할 이미지를 지정하는 필드
- ports : 공개할 포트에 대한 정보
- networks : 컨테이너가 접속할 도커 네트워크를 정의하는 필드
- external 필드의 의미는 nat 네트워크가 이미 존재하므로 새로 생성하지 말라는 의미.
도커 컴포즈를 사용하려면 명령행에서 docker-compose 명령을 실행하면 된다.
도커 컴포즈를 통해 애플리케이션을 실행하려면 up 명령을 실행해야 한다.
docker-compose up
(도커 컴포즈 스크립트의 external 필드에 정의된 네트워크는 애플리케이션 실행 전에 생성돼 있어야 한다. 그러므로 미리 nat 네트워크를 생성해야 한다.)
docker network create nat
도커 컴포즈를 사용해 여러 컨테이너로 구성된 애플리케이션 실행하기
여러 컨테이너로 구성된 애플리케이션을 실행해보자.
4장에서 Java 구현된 웹 프론트엔드, Go로 구현된 REST API, Node.js로 구현된 로그 수집 모듈을 합쳐 애플리케이션을 실행한 적 있었다.
당시엔 차례로 컨테이너를 실행시켜 애플리케이션을 가동했고 모든 컨테이너를 동일한 도커 가상 네트워크에 미리 약속된 이름으로 접속시켜 애플리케이션의 구성 요소가 서로 통신할 수 있도록 했다.
이를 도커 컴포즈로 다시 실행해보자.
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
위 스크립트의 image-gallery의 depends_on을 보면 이 서비스가 다른 두 서비스에 의존한다는 사실이 적혀있다.
이 의존성을 만족하기 위해 컴포즈는 image-gallery 서비스를 실행하기 전 여기 나열된 두 서비스를 먼저 실행하려 한다.
docker-compose up --detach
위 명령을 통해 애플리케이션을 실행해보자.
Running을 보면 image-gallery를 실행하기 전 accesslog와 iotd가 먼저 실행된 것을 볼 수 있다.
이는 우리가 컴포즈 파일에 서비스 간 의존 관계를 설정했기 때문이다.
API 서비스는 상태가 없기에 컨테이너를 늘리는 방법으로 스케일 아웃할 수 있다. 웹 컨테이너가 API에 데이터를 요청하면 도커가 여러 개의 API 컨테이너에 이 요청을 고르게 분배해 준다.
docker-compose up -d --scale iotd=3
localhost:8010에 접속해 몇 번 리프레시를 한 뒤
docker-compose logs --tail=1 iotd
명령어를 사용해 로그를 출력해보자.
(--tail=1 파라미터는 각 iotd 컨테이너의 마지막 로그를 출력하라는 의미)
이제 도커 컴포즈가 나를 대신해 컨테이너를 관리해준다.
도커 컴포즈는 컨테이너를 관리하는 별도의 명령이지만 내부적으론 도커 API를 사용한다.
그러므로 도커 명령행을 통해 도커 컴포즈로 실행한 컨테이너를 관리할 수 있다.
도커 컴포즈를 사용해 애플리케이션을 중지한 다음 재시작해보자.
docker-compose down
docker-compose up -d
docker container ls
down 명령어는 애플리케이션을 제거하는 명령으로, 애플리케이션이 중지되고 컨테이너를 모두 제거한다.(중지만 하려면 stop)
컴포즈 파일에 포함됐으나 external 플래그가 붙지 않았다면 네트워크와 볼륨도 제거 대상이다.
up 명령어는 애플리케이션을 시작하는 명령이다.
--scale을 통해 iotd를 3개로 스케일 아웃 했었다. 그러나 도커 컴포즈 파일엔 스케일 아웃에 대한 정의가 없으므로 재시작시 세개로 늘렸던 컨테이너 수가 다시 하나로 돌아간다.
도커 컴포즈는 YAML 파일에 정의도니 애플리케이션 정의에 의존하는 클라이언트 측 도구이다.
도커 컴포즈로 애플리케이션을 배포하면 애플리케이션을 구성하는 다양한 리소스가 생성되지만, 도커 엔진의 입장에선 이들이 어떤 관계를 갖는지 알 수 없다.
그러므로 컴포즈 파일을 통해 리소스를 관리해야 애플리케이션이 성립할 수 있다.
도커 컨테이너 간의 통신
분산 애플리케이션의 모든 구성 요소는 컴포즈가 도커 컨테이너로 실행한다.
각 컨테이너들은 어떻게 서로 통신할까?
컨테이너는 별도의 네트워크 공간을 가진 가상 환경이다.
컨테이너는 도커 엔진으로부터 자신만의 가상 IP를 부여받고, 모두 같은 도커 네트워크로 연결돼 이 IP 주소를 통해 서로 통신할 수 있다.
그러나 애플리케이션이 켜져있는 동안 컨테이너가 교체되면 IP 주소도 변경된다.
IP주소가 변경되면 어떻게 통신을 할까?
다행히 도커에서 IP주소가 변경돼도 문제가 없도록 DNS를 이용한 서비스 디스커버리 기능을 제공한다.
도커엔 DNS 서비스가 내장돼 있다.
컨테이너에서 실행 중인 애플리케이션도 다른 구성 요소에 접근하기 위해 이 DNS 서비스를 사용한다.
컨테이너 이름을 도메인 삼아 조회하면 해당 컨테이너의 IP 주소를 찾아준다.
만약 도메인이 가리키는 대상이 컨테이너가 아니면, 도커 엔진을 실행 중인 컴퓨터에 요청을 보내 호스트 컴퓨터가 속한 네트워크나 인터넷의 IP 주소를 조회한다.
현재 환경에서 accesslog 컨테이너의 IP 주소는 172.20.0.3 인 것을 알 수 있다.
도커 컴포즈로 애플리케이션 설정값 지정하기
애플리케이션 컨테이너와 데이터베이스 컨테이너를 분리해 분산 애플리케이션을 구동해보자.
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
위 도커 컴포즈 스크립트를 보면 services가 todo-db와 todo-web으로 2개가 있다.
todo-web을 보면 environment와 secrets가 있다.
environment는 컨테이너 안에서 사용될 환경 변수 값이 정의된다. 현재 환경변수의 의미는 애플리케이션이 실행될 때 컨테이너 안의 환경 변수 Database:Provider의 값이 Postgres로 설정된다.
secrets엔 실행 시 컨테이너 내부의 파일에 기록될 비밀값이 정의된다. 이 애플리케이션이 실행되면 컨테이너에 /app/config/secrets.json 파일이 생기며, 이 파일엔 postgres-connection이라는 이름의 비밀값의 값이 기록된다.
대개의 경우 쿠버네티스나 도커 스웜을 통해 비밀값을 전달한다.
도커를 단일 컴퓨터에서 사용하는 경우, 파일을 통해 비밀값을 전달할 수 있다.
(로컬 파일에서 비밀값을 읽어 오는 컴포즈 파일)
secrets:
postgres-connection:
file: ./config/secrets.json
docker-compose ps 명령어는 컴포즈 애플리케이션을 구성하는 컨테이너 목록을 보여준다.
도커 컴포즈도 만능은 아니다.
도커 컴포즈는 도커 스웜이나 쿠버네티스처럼 애플리케이션이 지속적으로 정의된 상태를 유지하도록 하는 기능이 없다.
일부 컨테이너가 오류를 일으키거나 강제로 종료돼도 docker-compose up 명령을 다시 실행하지 않는 한 애플리케이션의 상태를 원래대로 돌릴 수 없다.