[Docker in Action] Working with storage and volumes (1)

2022. 4. 23. 12:16DevOps/Docker & Kubernetes

 

 

 

Overview

도커 컨테이너를 통해 애플리케이션을 운영하다 보면, "데이터"를 적절하게 관리할 방법 이 필요합니다. 이를테면 데이터베이스 인스턴스를 컨테이너 안에서 동작시키는 상황에서 인스턴스나 호스트 머신에 문제가 생겨서 데이터베이스 컨테이너를 내려야 하거나, 업데이트를 위해 컨테이너를 재시작해야 한다면, 그러한 경우에도 불구하고 기존에 저장되어 있던 데이터는 유지되어야 합니다. 또한 여러 개의 웹 애플리케이션을 운영하는 경우, 모니터링이나 성능 최적화 등을 위해 다양한 로그들을 남겨야 할 텐데 마찬가지로 이 로그들은 컨테이너가 내려갈 때 같이 사라져서는 안 되기 때문에 별도의 관리 방법이 필요할 것입니다. 이번 포스팅에서는 도커가 파일 시스템을 다루는 방법들을 살펴봄으로써, 실제로 데이터를 어떻게 관리하는지에 대해서 살펴보도록 하겠습니다.

 

 

 

File trees and mount points

다른 운영체제(OS)들과는 다르게, 리눅스(이전 포스팅에서 살펴보았듯, 도커는 기본적으로 리눅스 운영체제 위에서 동작하며 윈도우나 맥에서 도커를 동작시키는 경우 VM을 사용해서 리눅스 환경을 만들고, 그 위에서 동작합니다.) 시스템은 모든 저장장치들을 하나의 트리에 통합시킵니다.(unifies all storage into a single tree) 여기서의 저장장치란, 디스크 파티션(disk partition)이나 USB 디스크 파티션 등을 의미하며, 각각의 저장장치들은 단일 파일 트리의 특정한 위치(Location)에 부착(Attach)됩니다. 이렇게 부착된 각각의 위치를 mount point라고 합니다. (아래 첨부된 그림처럼 /var /tmp /media 등이 마운트 포인트라고 할 수 있습니다.)

 

 

정리하면, "마운트(mount)"라는 개념은 물리적인 장치(디스크나, USB, 가상 디스크 등)를 파일 시스템의 특정한 위치(디렉터리)에 연결시켜주는 것을 의미하며, 리눅스 운영체제에서는 파일 트리가 하나이므로, 모든 마운트 과정은 이 단일 파일 시스템의 특정한 위치에서 진행됩니다. 그리고 이 연결된 지점, 즉 특정한 path 정보를 바로 mount point라고 합니다. 이 마운트 포인트는 소프트웨어나 사용자가 파일 트리에 어떤 저장장치가 매핑되어 있는지를 정확하게 알지 못하더라도 파일 시스템을 사용할 수 있도록 도와주며, 이러한 장점은 컨테이너 환경을 관리하는데 특히 유용합니다. (실제로 도커는 이 리눅스 파일 트리의 특성을 사용해서 컨테이너의 데이터를 관리합니다)

 

컨테이너는 고유의 파일 트리를 가지고 있으며 마운트 네임스페이스(MNT namespace)를 갖기 때문에 리눅스 파일 시스템 하에서 위에서 설명한 일종의 "저장장치"처럼 기능할 수 있습니다. 각각의 저장장치는 호스트의 파일 트리의 특정한 위치 (e.g /var)에 마운트 될 수 있으며, 반대로 저장장치 안의 파일 트리의 특정한 위치에 마운트 할 수도 있습니다. 실제로 도커 컨테이너는 이런 방식으로 파일 시스템을 관리하게 됩니다.

 

도커에서 파일 시스템을 관리하는 방법을 자세히 살펴보기 위해서는 다음 3가지 개념을 주의 깊게 살펴보아야 합니다.

 

  • Bind mounts
  • In-memory storage
  • Docker volumes

각각의 개념은 도커 컨테이너에 마운트 될 수 있는 스토리지 타입을 의미합니다.

 

Bind Mounts

Bind mounts are mount points used to remount parts of a filesystem tree onto other locations.

Bind Mounts는 호스트 파일 시스템의 일부분을 컨테이너 파일 트리의 특정한 포인트로 "Bind"하는 형태의 마운트 방식입니다. 즉 호스트 파일 시스템의 파일이나 디렉터리가 Bind 대상인 도커 컨테이너의 특정한 path에 마운트 되는 것을 의미하며, 이 파일 또는 디렉터리는 호스트 시스템에서 참조할 수 있습니다.

 

Bind Mounts는 아래에서 살펴볼 Docker Volume과는 다르게 별도의 공간을 생성할 필요가 없이, 바로 호스트 파일 시스템의 일부를 컨테이너에 바로 마운트 할 수 있다는 장점이 있습니다. 호스트 시스템의 어느 곳에나 저장할 수 있고, 저장되는 대상들은 비 도커 프로세스나 도커 컨테이너에서 언제든지 수정이 가능합니다.

 

하지만 이러한 장점 때문에 도커 컨테이너 이외에 해당 디렉터리를 사용하는 비 도커 프로세스(Non-Docker Process)가 예상치 못한 영향을 받을 수 있으며, 호스트가 도커 컨테이너가 요구하는 특정 디렉터리 구조를 사용해야 하므로 호스트와 도커 컨테이너 간에 의존성이 생기게 됩니다. 또한 컨테이너의 비어있지 않은 디렉터리에 Bind Mount를 실행하게 되면 기존 디렉터리의 내용이 Overwrite 되므로 주의해야 합니다.

 

 

 

 

In-memory storage

대부분의 서비스 소프트웨어는 private key file, database passwords, API keyfiles 등의 민감한 보안 정보들을 사용합니다. 이를 이미지나 디스크에 저장하는 것은 일반적으로 보안상의 이유로 권장되지 않기 때문에 다른 방법이 필요하며, 도커는 tmpfs mount를 통해 in-memory에서 이를 관리할 수 있는 방법을 제공합니다.

 

mount type을 tmpfs로 지정하고, dst(destination) 경로를 지정하게 되면 도커 컨테이너가 생성되는 시점에 컨테이너 내부의 dst 디렉터리에 in-memory storage가 마운트 되며, 해당 디렉터리 하위에 생성되는 모든 파일들은 전부 메모리에 저장됩니다. 따라서 이를 사용하면 앞서 이야기한 민감한 정보들을 이미지나 반영구적인 저장장치를 사용하지 않고 비교적 안전한 방법으로 관리할 수 있습니다.

docker run --rm \
  --mount type=tmpfs,dst=/tmp \
  --entrypoint mount \
  alpine:latest -v

 

Docker volumes

도커 볼륨은 "도커에서 관리하는" 파일 시스템 트리를 의미합니다. 호스트 파일 시스템의 일부를 그대로 도커 컨테이너의 파일 시스템에 바인드 하는 Bind Mounts와는 다르게 도커에서 직접 관리하고 생성하고 제거하기 때문에 docker volume 명령어를 통해 CLI로 관리할 수 있으며, 백업하거나 이동하기도 쉽습니다. 도커 볼륨을 생성하면 /var/lib/docker/volumes/~ 와 같이 호스트 파일 시스템의 일부이지만 도커에서 관리하는 영역에 저장되기 때문에 Bind Mount와는 다르게 Non-Docker 프로세스들이 실수로 해당 파일 시스템을 수정하는 일을 어느 정도는 방지할 수 있습니다. 

 

도커 볼륨을 컨테이너에 마운트 하면 위에서 생성된 /var/lib/docker/volumes/~ 의 디렉터리가 탑재되며 이 볼륨은 여러 컨테이너에 동시에 탑재될 수도 있습니다. 

 

 

Volume provide container-independent data management

볼륨을 사용했을 때의 가장 큰 장점 중 하나는 "관심사의 분리"를 쉽게 할 수 있다는 것입니다.

  • Database software vs Database data
  • Web application vs Log data
  • Data processing Application vs Input & Output data
  • Web server vs Static content
  • Product vs Support Tools

 

이를테면, Apache Tomcat은 Network 위에서 HTTP interface를 제공하는 애플리케이션입니다. 실제로 이 애플리케이션은 "인터페이스를 제공하는" 역할까지만 이미지로 제공하고, 실제로 어떤 역할을 수행할 것인지에 대해서는 Volume을 통해 주입(Inject)하게 됩니다. 마찬가지로 MongoDB는 데이터베이스 드라이버와 인터페이스만을 제공하는 애플리케이션이고, 실제로 어떤 데이터를 관리하고 저장할 것인지에 대해서는 Volume을 통해 주입하게 됩니다. 즉 Volume은 이미지가 근본적으로 다형성(polymorphic behavior)을 가질 수 있도록 도와주어 다양한 곳에서 재사용할 수 있도록 아키텍처적인 도움을 주는 역할도 수행하게 되는 것입니다.

 

Using volumes with a NoSQL database

Cassandra라는 NoSQL database image에 Volume을 마운트 해보면서 Docker Volume의 동작에 대해서 살펴보도록 하겠습니다. 우선 위에서 언급했듯, docker volume은 도커에서 관리하기 때문에 다음과 같이 CLI를 통해 쉽게 볼륨을 생성할 수 있습니다. 아래와 같이 'cass-shared'라는 이름의 볼륨을 생성해줍니다. 

docker volume create \
  --driver local \
  --label example=cassandra \
  cass-shared

 

다음으로는 cassandra:2.2 이미지를 base로 하는 컨테이너를 detach 모드로 실행시켜 줍니다. 이때 --volume 플래그를 사용해서 볼륨을 컨테이너 내의 /var/lib/cassandra/data라는 디렉터리에 마운트 해줍니다.

docker run -d \
--volume cass-shared:/var/lib/cassandra/data \
--name cass1 \
cassandra:2.2

 

마운트 한 이후에 다음과 같이 cassandra client를 실행하고 쿼리를 날려보면 아무것도 없는 빈 테이블이 보이는 것을 확인할 수 있습니다. 여기에 다음과 같은 명령어를 입력해서 keyspace와 value를 추가해줍니다.

create keyspace docker_hello_world
with replication = {
    'class' : 'SimpleStrategy',
    'replication_factor': 1
};

 

 

실제로 다시 쿼리를 날려서 확인해보면 정상적으로 값이 들어간 것을 확인할 수 있습니다.

 

 

이제 동일한 볼륨을 새로운 컨테이너에 마운트 해서 확인해보도록 하겠습니다. 다음과 같이 cass2라는 이름의 컨테이너를 동일한 볼륨을 마운트 하도록 생성해보겠습니다.

docker run -d \
--volume cass-shared:/var/lib/cassandra/data \
--name cass2 \
cassandra:2.2

 

동일하게 Client를 통해서 쿼리를 날려보면 실제로 해당 컨테이너에서는 아무런 작업을 하지 않았음에도 cass1에서 업데이트한 Volume의 데이터가 cass2에서도 동일하게 보이는 것을 확인할 수 있습니다. 

 

 

도커에서 관리하는 볼륨은 기본적으로 호스트 파일 시스템의 일부분에 위치하고 있고, 이를 여러 개의 도커 컨테이너에 마운트하고 있기 때문에 서로 다른 컨테이너에서 동일한 볼륨을 마운트 하게 된다면 동일한 값을 참조하게 되는 것을 확인할 수 있습니다. 

 

반응형