[도커] 7장 도커 컴포즈로 분산 애플리케이션 실행하기

공효은·2023년 6월 6일
1

도커

목록 보기
1/12
post-thumbnail

📦 각 컴포넌트는 자신만의 경량 컨테이너에서 실행되며 도커가 표준 네트워크 프로토콜을 통해 이들 컨테이너를 엮어낸다. 도커 컴포즈를 사용하면 이렇게 여러 컨테이너에 걸쳐 실행되는 애플리케이션을 정의 하고 관리할 수 있다.

7.1 도커 컴포즈 파일의 구조

Dockerfile

  • 애플리케이션을 패키징 하기 위한 스크립트.
  • 애플리케이션의 한 부분을 패키징하는 수단
  • FE, BE API, DB를 갖춘 애플리케이션을 패키징하려면 각 컴포넌트에 하나씩 세개의 Dockerfile 스크립트가 필요하다.

어떻게 실행할 것인가?
1. 각각의 컨테이너를 일일이 옵션을 지정해가며 실행.
ㄴ 오류의 원천이 된다.
ㄴ 조금만 옵션을 잘못지정해도 애플리케이션이 정상동작하지 않고, 컨테이너간 통신에 문제가 생긴다.
2. 도커 컴포즈 파일에 애플리케이션의 구조를 정의한다.

Docker compose file

  • 도커 컴포즈 파일은 애플리케이션의 '원하는 상태', 모든 컴포넌트가 실행중일 때 어떤 상태여야 하는지 기술하는 파일이다.
  • docker container run 명령으로 컨테이너를 실행할 때 지정하는 모든 옵션을 한데 모아 놓은 단순한 형식의 파일이다.
  • 도커 컴포즈 파일을 작성하고 나면 도커 컴포즈 도구를 사용해 애플리케이션을 실행한다. 그러면 도커 컴포즈가 컨테이너, 네트워크,볼륨 등 필요한 모든 도커 객체를 만들도록 도커 API에 명령을 내린다.
// to-do 애플리케이션을 실행하는 도커 컴포즈 파일 스크립트
version: '3.7'

services:
  
  todo-web: //서비스 이름은 컨테이너의 이름이자 도커 네트워크상에서 다른 컨테이너들이 해당 컨테이너를 식별하기 위한 DNS 네임으로 쓰인다.
    image: diamol/ch06-todo-list // 실행할 이미지를 지정
    ports:
      - "8020:80" // 공개할 포트에 대한 정보
    networks:
      - app-net // 컨테이너가 접속할 도커 네트워크를 정의한다.

networks:
  app-net: // 서비스가 구성될 네트워크 이름
    external:  // nat 네트워크가 이미 존재하므로 새로 생성하지 말라는뜻
      name: nat // app-net은 nat이라는 이름의 외부 네트워크로 연결됨

도커 네트워크에 도커 컨테이너가 하나 연결된 간단한 애플리케이션을 기술한 것이다.

  • 도커 컴포즈는 사람도 쉽게 읽고 이해할 수 있으며(API의 표준 언어인)JSON으로 변환하기도 쉬운 YAML 문법으로 기술된다.
  • YAML 문법은 들여쓰기를 통해 구조를 정의하기 때문에 들여쓰기가 중요하다.
  • 위의 도커 컴포즈 파일은 세가지 최상위 문(statement)으로 구성된다.
    • version
      • 이 파일에 사용된 도커 컴포즈 파일 형식의 버전을 가리킨다.
    • services
      • 애플리케이션을 구성하는 모든 컴포넌트를 열거하는 부분이다. 도커 컴포즈에서는 실제 컨테이너 대신 서비스(service) 개념을 단위로 삼는다.하나의 서비스를 같은 이미지로 여러 컨테이너에서 실행할 수 있기 때문이다.
    • networks
      • 서비스 컨테이너가 연결될 모든 도커 네트워크를 열거한다.

도커 컴포즈를 사용해 이 애플리케이션을 실행하면 컨테이너 하나가 실행돼 스크립트에 정의된 구성을 갖춘다!

todo-web 이름의 서비스는 diamol/ch06-todo-lst 이미지로 부터 단일 컨테이너로 실행된다.
이 컨테이너는 호스트 컴퓨터의 80번 포트로 자신의 8002포트를 공개한다.
app-net 이라는 도커 네트워크에 연결된다,

최종적인 결과는 아래 명령어를 실행한 것과 같은 상태가 된다.

docker container run -p 8020:80 --name todo-web --network nat diamol/ch06-todo-list

도커 컴포즈를 사용하려면 명령행 에서 docker-compose 명령을 실행한다.
애플리케이션을 시작하려면 up 명령을 실행해야한다. 그러면 도커 컴포즈가 컴조프 파일을 체크하고 애플리케이션을 정의된 상태로 실행하기 위해 필요한 요소를 준비하기 시작한다.

// 컴포즈 스크립트의 external 필드에 정의된 네트워크는 애플리케이션 실행 전에 생성돼 있어야 한다. 아직 해당 네트워크가 존재하지 않는다면 이 명령으로 nat이라는 이름의 도커 네트워크를 생성한다.

docker network create nat 

cd ./ch07/exercises/todo-list

// 이 명령으로 애플리케이션이 실행된다. 도커 컴포즈는 현재 있는 리소스와 애플리케이션을 구성하는 리소스를 비교해 더 필요한 요소를 생성한다.
docker-compose up

docker-compose 실행과정

  1. docker-compose 명령을 실행하면 먼저 현재 작업 데렉터리에서 docker-compose.yml 파일을 찾는다.
  2. 해당 파일을 발견하면 to-do 애플리케이션의 정의를 읽어들인다

도커 컴포즈 파일은 애플리케이션의 소스코드. Dockerfile 스크립트와 함께 형상관리 도구로 관리된다.
이 파일에 애플리케이션의 모든 실행 옵션이 기술된다. 그러므로 README 파일에 애플리케이션 이미지 이름이나 공개해야 할 포트 번호를 문서화 할 필요가 없다.
=> 도커 컴포즈 파일이 간접적인 문서 역할을 한다.

7.2 도커 컴포즈를 사용해 여러 컨테이너로 구성된 애플리케이션 실행하기

NASA의 오늘의 천문 사진을 보여 주는 분산 애플리케이션을 만든다.
이 애플리케이션은 자바로 구현된 웹 프론트엔드, 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

도커 컴포즈를 사용해 이 애플리케이션을 분리 모드(detached mode)로 실행한다.
컴포즈가 우리 대신 컨테이너 로그를 수집하지만, 컨테이너는 백그라운드로 동작한다.

cd ./ch07/exercise/image-of-the-day

docker-compose up --detach

image-gallery 서비스를 시작하기 전에 accesslog와 iotd 서비스를 먼저 시작한다. 이것은 컴포즈 파일에 서비스 간의 의존 관계를 설정했기 때문이다.

컴포즈 파일을 사용해 여러 개의 컨테이너로 구성된 애플리케이션을 마치 한 덩어리처럼 다룰 수 있다.
API 서비스는 상태가 없으므로 컨테이너를 늘리는 방법으로 스케일 아웃할 수 있다. 웹 컨테이너가 API에 데이터를 요청하면 도커가 여러 개의 API 컨테이너에 이 요청을 고르게 분배해준다.

// 도커 컴포즈를 사용해 iotd 서비스의 컨테이너 수를 늘리고, 웹 페이지를 리프레시 하며 iotd 컨테이너의 로그를 살펴본다.
docker-compose up -d --scale iotd=3

# https://localhost8018 페이지를 웹 브라우저로 접근해 몇 차례 리프레시한다.

docker-compose logs --tail=1 iotd

API 서비스의 컨테이너를 두 개 늘려 애초의 세 배로 만든다.
웹 페이지를 리프레시하면 웹 애플리케이션이 API에 데이터를 요청하고, 이들 요청은 늘어난 컨테이너가 고르게 나눠 처리한다.
로그로 확인할 수 있다.
도커 컴포즈 명령은 모든 컨테이너 혹은 원하는 컨테이너의 로그만 골라 출력할 수 있다. --tail=1 파라미터는 각 iotd 컨테이너의 마지막 로그를 출력하라는 의미다.

도커 컴포즈로 실행한 컨테이너라도 똑같이 도커 명령행으로 관리할 수 있다.

도커 컴포즈를 사용해 애플리케이션을 중지했다가 재시작한 다음 도커 명령행을 사용해 현재 실행중인 컨테이너의 목록을 확인하라.

docker-compose stop

docker-coponse start

docker container ls

컨테이너가 생성되고 30분이 지났지만 방금 실행된 것으로 나온다. 새 컨테이너를 만드는 대신 중지 됐던 기존의 컨테이너가 다시 실행된 것임을 알 수 있다.

도커 컴포즈는 클라이언트 측에서 동작하는 도구이다. 도커 컴포즈 명령을 실행하면 컴포즈 파일의 내용에 따라 도커 API로 지시를 보낸다.

도커 엔진 자체는 컨테이너를 실행할 뿐, 여러 컨테이너가 하나의 애플리케이션으로 동작하는지 여부는 알지 못한다. 이를 아는 것은 YAML로 적힌 컴포즈 파일을 읽어 애플리케이션의 구조를 이해한 컴포즈 뿐이다.

그러므로 컴포즈를 사용해 애플리케이션을 관리하려면 컴포즈 파일을 작성하고 이 파일을 읽을 수 있게 해야한다.

컴포즈 파일을 수정하거나 도커 명령행으로 직접 애플리케이션을 수정하면, 애플리케이션이 컴포즈 파일에 기술된 구조와 불일치할 수 있다. 이 상태에서 도커 컴포즈로 다시 애플리케이션을 관리하려 하면 비정상적인 동작을 볼 수 있다.

(우리는 이미 컴포즈 수정없이 iotd 서비스를 컨테이너 세 개로 스케일링 했다.. 이 상태에서 컴포즈로 애플리케이션을 재시작하면 iotd 서비스는 다시 한 개의 컨테이너만으로 동작한다.)

//down은 애플리케이션을 제거하는 명령으로 애플리케이션이 중지되고 컨테이너를 모두 제거한다. 컴포즈 파일에 포함됐으나 external 플래그가 붙지 않았다면 네트워크와 볼륨도 제거대상이다.
docker-compose down

//up은 애플리케이션을 시작하는 명령이다. 지금은 실행 중인 컨테이너가 없으므로 컴포즈 파일에 정의된 내용대로 모든 서비스를 다시 생성한다. 그러나 컴포즈 파일에는 스케일 아웃에 대한 정의가 없으므로 세개로 늘렸던 컨테이너 수가 다시 하나로 돌아간다.
의도치 않게 스케일 다운되었다.
docker-compose up -d

docker container ls

docker compose down 하니 Removed 표시됨

iotd 3개에서 1개가 됨

도커 컴포즈로 애플리케이션을 배포하면 애플리케이션을 구성하는 다양한 리소스가 생성되지만, 도커 엔진의 입장에서는 이들이 어떤 관계를 갖는지 알 수 없다. 컴포즈 파일을 통해 리소스를 관리해야 애플리케이션이 성립할 수 있다.

7.3 도커 컨테이너 간의 통신

분산 애플리케이션의 모든 구성요소는 컴포즈가 도커 컨테이너로 실행한다. 그런데 이들 컨테이너는 서로 어떻게 통신할까?

  • 컨테이너는 도커 엔진으로 부터 부여받은 자신만의 가상 IP 주소를 가지며 모두 같은 도커 네트워크로 연결돼 이 IP 주소를 통해 서로 통신한다.

  • 애플리케이션 생애 주기 동안 컨테이너가 교체되면 IP 주소도 변경된다. 도커는 DNS를 이용해 서비스 디스커버리 기능을 제공한다.

  • 도커에는 DNS 서비스가 내장돼있다. 컨테이너에서 실행중인 애플리케이션도 다른 구성요소에 접근하기 위해 이 DNS 서비스를 사용한다. 컨테이너 이름을 도메인 삼아 조회하면 해당 컨테이너의 IP 주소를 찾아준다.

DNS 는 IP 주소를 도메인과 연결하는 기능을 제공하는 시스템으로, 인터넷과 사설 네트워크에서 모두 동작한다. 웹브라우저에 입력하는 blog.sizeyed.com 같은 주소가 바로 도메인이다. 이 주소를 입력하면 나의 블로그를 운영하는 도커 서버에 연결된 IP 주소를 찾아간다.

image-gallery 애플리케이션에서 이 과정이 동작하는 것을 실제로 볼 수 있다.
도커의 DNS 서비스로 부터 받은 응답에는 서비스를 구성하는 컨테이너의 IP 주소가 있따. 컨테이너는 모두 IP 주소를 받는다.

// 컨테이너 수를 세 배로 늘려 도커 컴포즈로 애플리케이션을 실행하고 웹 컨테이너에서 DNS 조회 명령을 실행해 본다.

//이 명령으로 애플리케이션이 정의된 상태대로 다시 실행된다. 이번에는 iotd 서비스의 컨테이너를 세 개로 늘렸다.
docker-compose up -d scale --iotd=3

//이 명령으로 웹 어플리케이션 컨테이너에 대화식 셸이 실행된다.
docker container exec -it image-of-the-day-image-gallery-1 sh

// nslookup 명령은 컨테이너 이미지에 포함돼 있다. 이 명령은 도메인을 DNS에 조회한다. accesslog 서비스를 DNS에 조회해 보니 해당 컨테이너의 IP 주소가 조회 됐다.
nslookup accesslog

exit

nslookup은 웹 애플리케이션 컨테이너 기반 이미지에 들어 있는 유틸리티 이다. 명령의 인자로 도메인을 지정하면 해당 도메인을 DNS서비스에서 조회하고 그 결과를 출력한다. 출력왼 로그를 보면 대상 컨테이너의 IP 주소가 있다.(172.18.0.3)

도커 네트워크에 연결된 모든 컨테이너는 이 네트워크의 범위에 포함되는 IP 주소를 부여 받는다. 그리고 이 네트워크를 통해 컨테이너 간 통신이 가능하다. DNS 조회를 사용하면 컨테이너가 교체돼 IP 주소가 변경되더라도 항상 새로 만들어진 컨테이너에 접근할 수 있다.

// accesslog 컨테이너 삭제.
// accesslog 컨테이너를 강제로 삭제하면 애플리케이션의 상태가 컴포즈 파일에 정의된 것과 달라진다. 컴포즈는 새로운 accesslog 컨테이너를 만들어 이러한 상황에 대응한다.
docker container rm -f image-of-the-day-accesslog-1

// 도커 컴포즈로 애플리케이션 재실행
docker-compose up -d --scale iotd=3

//웹 컨테이너에서 다시 DNS 조회를 실행한다.
docker container exec -it image-of-the-day-image-gallery-1 sh

// DNS 조회 결과를 보면 accesslog 서비스에 새로 배정된 컨테이너가 기존 컨테이너의 IP를 그대로 유지했다. 기존 컨테이너가 삭제되면서 부여했던 IP 주소도 재사용이 가능해졌기 때문이다.
nslookup accesslog

// iotd API 서비스는 세 개의 컨테이너로 동작한다. 따라서 DNS 조회 결과에도 세 컨테이너의 IP 주소가 모두 포함된다.

nslookup iotd

exit

새로 만들어지거나 삭제된 컨테이너가 더 이상 없었으므로 새로 만들어진 accesslog 컨테이너도 기존과 같은 IP주소가 부여 되었다.

하나의 도메인에 대해 DNS 조회 결과에 여러 개의 IP 주소가 나올 수 있다. 도커 컴포즈는 이 점을 활용해 간단한 로드 밸런싱을 구현할 수 있다.

여러 개의 IP 주소가 담긴 조회 결과를 어떻게 활용할지는 전적으로 애플리케이션이 결정한다.

간단하게 첫 번째 IP 주소만 사용할 수도 있다.

모든 컨테이너에 고르게 부하가 분배되도록 두커의 DNS 시스템은 조회 결과의 순서를 매번 변화시킨다.

도커 컴포즈는 컨테이너를 실행할 때 지정하는 모든 옵션값을 기억하며, 이들 옵션값을 통해 컨테이너 간의 통신을 처리한다.

7.4 도커 컴포즈로 애플리케이션 설정값 지정하기

6장에서 살펴봤던 to-do 애플리케이션을 다른 형태로 실행할 수 있다.
예를 들어 애플리케이션을 단일 컨테이너로 실행하고 데이터는 SQLite 데이터베이스에 저장한다면 어떨까?

to-do 애플리케이션도 원격 컨테이너에서 동작하는 PostgreSQL 데이터 베이스를 사용하도록 해보자.

애플리케이션 컨테이너와 데이터베이스 컨테이너를 따로 실행하며 분산 애플리케이션을 구동하는 데 사용할 수 있다.

to-do 애플리케이션의 이미지에는 개발 환경의 설정값이 기본으로 들어있으나, 다른 환경을 위한 설정값을 따로 적용할 수 있다. 이번에는 도커 컴포즈를 사용해 다른 설정값을 적용해 보자.

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

데이터 베이스의 대한 정의

  • diamol/postgres:11.5 이미지를 사용함
  • PostgreSQL 데이터베이스의 표준 포트인 5432번 포트를 호스트의 5433번 포트를 통해 공개함
  • 서비스 이름이자 도메인 네임을 todo-db로 함

웹 서비스에 대한 정의

  • environment에는 컨테이너 안에서 사용될 환경 변수 값이 정의된다. 애플리케이션이 실행되면 컨테이너 안의 환경 변수 Database:Provider의 값이 Postgres로 설정된다.
  • secrets에는 실행 시 컨테이너 내부의 파일에 기록될 비밀값을 정의한다. 이 애플리케이션이 실행되면 컨테이너에 /app/config/secrets.json 파일이 생기고, 이 파일에는 postgres-connection 이라는 이름의 비밀값이 기록된다.

비밀값은 주로 클러스터 환경에서 쿠버네티스나 도커 스웜 같은 컨테이너 플랫폼을 통해 제공된다. 평소에는 클러스터 데이터베이스에 암호화돼 있기 떄문에 데이터베이스 패스워드, 인증서, API키 등 민감한 정보로 구성된 설정값을 전달하는 데 적합하다.

도커를 단일 컴퓨터에 실행하는 상황이라면 비밀값을 보관하는 클러스터 데이터베이스가 없을 것이므로 파일을 통해 비밀값을 전달해도 된다.

컴포즈 파일 마지막 부분에는 secrets 항목이 있다.

secrets:
  postgres-connection:
    file: ./config/secrets.json

이 스크립트는 비밀값 postgres-connection의 값을 secrets.json 파일에서 읽어 오라는 의미다.

애플리케이션 설정값을 컴포즈 파일에 정의하면, 같은 도커 이미지라도 다양하게 활용할 수 있고, 서로 다른 각 환경에 대한 설정을 명시적으로 정의할 수 있다.

개발 환경과 테스트 환경의 컴포즈 파일을 별도로 작성해 두면 공개하는 포트를 환경에 따라 달리하거나 애플리케이션의 기능을 선택적으로 활성화 할 수 있다.

위의 컴포즈 파일은 환경 변수와 비밀값을 정의해 to-do 애플리케이션을 PostgreSQL을 사용하는 모드로 실행하고 데이터베이스 접속 정보를 주입하는 예다.

애플리케이션을 실행하면 앞서 본 바와 변함없이 동작하지만, 이제는 데이터가 PostgresSQL 데이터베이스 컨테이너에 저장되며 이 컨테이너를 별도로 관리할 수 있다.

// 이 경로에는 도커 컴포즈 파일과 애플리케이션 컨테이너에서 사용할 비밀값이 담긴 JSON 파일이 있다.
cd ./ch07/exercises/todo-list-postgres

docker-compose up -d


컴포즈로 애플리케이션이 실행된다.
PostgresSQL 데이터베이스와 to-do 웹 애플리케이션이 대상이다.

docker-compose ps

컴포즈 애플리케이션을 구성하는 컨테이너 목록을 보여준다.
다른 애플리케이션을 구성하는 컨테이너는 이 목록에 포함되지 않는다.

http://localhost:8030 접속

sqlectron을 사용해서 localhost:5433(데이터베이스 컨테이너의 포트를 호스트 컴퓨터의 포트 5433으로 공개했다) 을 통해 데이터베이스 서버에접근할 수 있다.

패키징된 애플리케이션과 설정값을 분리할 수 있다는 것도 도커의 핵심적인 장점 중 하나다.

7.5 도커 컴포즈도 만능은 아니다.

도커 컴포즈의 장점

  • 도커 컴포즈는 복잡한 분산 애플리케이션의 설정을 짧고 명료한 포맷의 파일로 나타낼수 있다.
  • YAML 형식으로 작성된 컴포즈 파일은 그 자체가 애플리 케이션 배포 과정이므로 변경에 맞춰 따로 수정할 필요가 없고 문서가 된다.
  • 애플리케이션 전체를 일관적인 요소들의 조합으로 만들 수 있다. 애플리케이션의 모든 구성 요소가 각자의 Dockerfile 스크립트와 컴포즈 파일을 갖게 되고 항상 동일한 도구로 애플리케이션을 배포하고 관리할
    수 있다.

도커 컴포즈의 기능과 목적

  • 컴포저를 사용하면 애플리케이션을 정의하고 이 정의를 도커 엔진을 실행중인 단일 컴퓨터에 적용할 수 있다. 이 컴퓨터에 실제 활성 상태인 도커 리소스를 파악하고 컴포즈 파일에 기재된 리소스와 비교하여 추가로 필요한 리소스를 도커 API를 통해 요청해서 수정하거나 성한다.

도커 컴포즈의 한계

  • docker-compose up 명령을 실행하면 내가 정의한 상태대로 애플리케이션을 실행할 수 있다. 하지만 도커 컴포즈는 도커 스웜이나 쿠버네티스같은 완전한 컨테이너 플랫폼이 아니다.
  • 도커 컴포즈에는 이들과 달리 애플리케이션이 지속적으로 정의된 상태를 유지하도록 하는 기능이 없다.
  • 일부 컨테이너가 오류를 일으키거나 강종 돼도 dockder-compose up 명령을 다시 실행하지 않는 한 애플리케이션의 상태를 원래대로 돌릴 수 없다.
  • 고가용성, 로드 밸런싱, 이중화 같은 기능이 없다.

애플리케이션의 생애 주기 중 도커 컴포즈를 사용하기 적합한 주기
Dev : 개발자는 자신의 컴퓨터에서 애플리케이션을 실행하고 엔드 투 엔드 테스트를 수행하는 데 컴포즈를 사용한다.

CI: 지속적 통합 프로세스 중 빌드 및 자동회된 테스트에 컴포즈를 사용해 애플리케이션을 실행한다.

Test: 단일 서버에서 컴포즈를 실행해 테스트를 진행하면 테스트 환경을 최소한으로 유지할 수 있다.

Production: 운영 환경에는 컴포즈보다는 도커 스웜이나 쿠버네티스가 쓰인다. 그러나 애플리케이션 정의에는 컴포즈 파일 포맷을 사용한다.
(그림에 컴퓨터 3대)

profile
잼나게 코딩하면서 살고 싶어요 ^O^/

0개의 댓글