[Docker] 도커 볼륨과 마운트
무상태(stateless)와 유상태(stateful)
무상태 애플리케이션
- 과거의 요청이나 사용자의 상호작용을 기억하지 않는다.
- 각 요청은 독립적이며, 요청 사이에 지속성이 없다.
- 사용량이 증가함에 따라 쉽게 확장할 수 있으며, 신뢰성 있는 처리를 제공한다.
- 롤링 업데이트(서비스를 중단하지 않고 애플리케이션을 업데이트하는 방식)을 가능하게 한다.
유상태 애플리케이션
- 이전 요청이나 사용자 상호작용의 정보를 유지한다.
- ex. 데이터베이스, 파일 시스템
도커 컨테이너는 기본적으로 무상태이다. 그러나 때때로 애플리케이션의 데이터를 저장하거나 캐시를 위해 디스크 공간이 필요할 수 있다. 이때 도커의 볼륨(volume)과 마운트(mount) 기능을 사용하면 컨테이너 내부에 생성된 데이터를 컨테이너 외부에 저장하고 유지할 수 있다. 이는 컨테이너가 종료되거나 삭제되더라도 데이터가 사라지지 않게 해주는 역할을 한다.
도커 컨테이너의 파일 시스템 동작 방식
- 컨테이너에는 독립된 파일 시스템이 있다.
- 즉, 같은 이미지에서 파생된 여러 컨테이너들이 서로 독립적으로 동작하며, 한 컨테이너에서 일어난 파일 시스템의 변화가 다른 컨테이너에 영향을 주지 않는다.
- 또한, 도커 이미지와 유사하게, 컨테이너의 파일 시스템은 여러 레이어로 구성된 가상 파일 시스템이다.
- 이 파일 시스템은 해당 컨테이너의 이미지에서 만들어지며, Dockerfile의 COPY 명령어를 사용하여 이미지에 파일을 포함시킬 수 있다.
- 예를 들어, Dockerfile에서 다음과 같은 COPY 명령을 사용했다고 가정하자.
COPY ./myapp /app
- 이 명령어는 현재 디렉터리의 myapp이라는 파일 또는 디렉토리를 이미지 내 /app 디렉터리에 복사한다.
- 이 이미지를 기반으로 컨테이너를 생성하면, 해당 컨테이너의 파일 시스템에는 /app 디렉터리에 myapp이 존재하게 된다.
- 파일 시스템은 기본적으로 이미지 레이어와 컨테이너의 쓰기 가능한 레이어로 구성된다.
- 이미지 레이어는 모든 컨테이너가 공유하지만, 쓰기 가능한 레이어는 각 컨테이너가 독립적으로 가진다.
- 각 컨테이너의 쓰기 가능한 레이어는 컨테이너의 생애주기와 같다.
- 컨테이너가 종료되어도 해당 컨테이너의 데이터는 삭제되지 않는다.
- copy-on-write 방법을 사용한다.
- 컨테이너가 이미지 레이어에 포함된 파일을 수정하려고 할 때, 해당 파일을 쓰기 가능한 레이어로 복사하고, 이 레이어에서 수정한다.
도커 볼륨(Docker volume)과 마운트(mount)
: stateful 애플리케이션을 컨테이너화할 때
- 도커 볼륨(Docker volume)
- 컨테이너의 일부로 동작하지만, 컨테이너와 별개의 생애주기를 갖는 독립적인 객체이다.
- 볼륨 생성 및 연결하는 방법
- 수동으로 직접 볼륨을 생성해 컨테이너에 연결하는 방법
- Dockerfile 스크립트에서 VOLUME 인스트럭션을 사용하는 방법(ex. VOLUME /data)
docker container inspect
: 실행한 컨테이너에 대한 상세한 정보를 JSON 형식으로 출력(볼륨과 관련된 정보 포함)
docker container inspect --format '{{.Mounts}}' todo1
docker volume ls
: Docker 환경에 존재하는 모든 볼륨의 리스트 출력
docker volume ls
도커 이미지에서 볼륨을 정의하면, 이 이미지를 이용해 컨테이너를 생성할 때마다 새로운 볼륨이 생성된다. 예를 들어, todo1과 todo2라는 두 개의 컨테이너가 같은 이미지에서 생성되었다면, 각 컨테이너는 각각 다른 볼륨에 연결된다.
그러면 여러 컨테이너가 같은 볼륨을 공유하도록 하려면 어떻게 해야 할까? Docker는 '--volume-from' 옵션을 제공하여 여러 컨테이너가 같은 볼륨을 공유하도록 설정할 수 있다.
docker container run -d --name t3 --volumes-from todo1 diamol/ch06-todo-list
위 명령은 t3라는 새로운 컨테이너를 생성하면서, 이 컨테이너가 todo1의 볼륨을 공유하도록 설정한다.
docker container exec
: 컨테이너 내부에서 특정 명령을 실행
docker container exec t3 ls /data
볼륨은 일반적으로 컨테이너 간 파일 공유보다는, 애플리케이션의 상태를 유지하고 보존하는 데 더 적합하다.
다수의 컨테이너 간 볼륨을 공유하면 일반적으로 파일을 효율적으로 공유할 수 있다. 그러나 애플리케이션 컨테이너는 종종 그 컨테이너만이 액세스해야 하는 파일을 필요로 한다.(데이터 충돌, 불일치 등의 문제)
(ex. 특정 프로세스에 대한 임시 파일, 로그 파일, 프로세스에 의해 생성된 데이터 파일)
볼륨은 명시적으로 관리되어야 한다.
Docker 이미지에서 직접 볼륨을 정의하는 것보다는, 볼륨을 직접 생성하고 이름을 지정하는 것이 바람직하다. 특정 볼륨이 어떤 데이터를 저장하고 있는지, 어떤 컨테이너에 의해 사용되고 있는지 명확히 파악할 수 있기 때문이다.
예제: 수동으로 직접 볼륨을 생성해 컨테이너에 연결 후 새 버전으로 업데이트
- 볼륨 생성: docker volume create
docker volume create todo-list # todo-list 볼륨 생성
- 볼륨 연결 및 애플리케이션 실행
- -v : 볼륨을 컨테이너에 연결하기 위해 사용(볼륨 이름:컨테이너 내부 경로)
docker container run -d -p 8011:80 -v todo-list:$target --name todo-v1 diamol/ch06-todo-list
- 현재 실행 중인 컨테이너 삭제
docker container rm -f todo-v1
- 애플리케이션 버전 업데이트
docker container run -d -p 8011:80 -v todo-list:$target --name todo-v2 diamol/ch06-todo-list:v2
Dockerfile의 VOLUME과 docker container의 --volume의 차이
- Dockerfile의 VOLUME
- 이미지가 빌드될 때 이미지에 볼륨을 추가하는 역할
- 컨테이너가 생성될 때 함께 생성
- docker container run 명령에서 별도로 볼륨을 지정하지 않으면
- 도커는 이 볼륨에 대해 새로운 볼륨을 생성
- 무작위로 생성된 고유한 식별자를 가지게 됨 -> 기억하고 있어야 함
- docker container의 --volume
- 이미지에 볼륨이 정의되어 있든 없든 간에, 명시적으로 지정된 볼륨을 컨테이너에 마운트하는 역할을 한다.
- 이미지에 볼륨이 정의되어 있더라도 --volume 플래그에 의해 지정된 볼륨이 우선으로 적용되어, 새로운 볼륨을 생성하지 않는다.
- VOLUME 인스트럭션은 볼륨을 명시적으로 지정하지 않았을 때 데이터 유실을 방지하는 안전장치의 역할
바인드 마운트(bind mount)
- Docker에서 제공하는 다른 스토리지 옵션이다.
- 호스트 컴퓨터의 디렉터리를 컨테이너 파일 시스템의 디렉터리로 연결하는 기능이다.
- 따라서, 컨테이너는 호스트의 특정 파일 또는 디렉토리에 직접 접근하고 사용할 수 있게 된다.
- 볼륨과 달리 호스트 파일 시스템의 일부를 참고하고 있다.
- 컨테이너와 호스트 간에 파일을 공유하거나 호스트 시스템의 특정 자원을 컨테이너에 직접 활용하는 등의 유연한 작업이 가능하다.
- 예를 들어, 서버가 고성능 SSD나 RAID를 적용한 디스크 어레이, 네트워크 분산 스토리지 등과 같은 특별한 스토리지를 가지고 있다면, 이를 바인드 마운트를 통해 컨테이너에서도 활용할 수 있다.
- 즉, 바인드 마운트를 사용하면 컨테이너는 호스트의 고성능 스토리지를 직접 활용해 데이터를 저장하거나 처리할 수 있다.
바인드 마운트 설정 방법
: --mount
- --mount 옵션을 사용하여 바인드 마운드를 설정한다.
- type=bind, source=$source, targey=$target은 바인드 마운트를 설정하는 옵션이며
- source는 호스트의 디렉터리 경로
- target은 컨테이너 내부에서 해당 디렉토리를 참조할 경로
docker container run --mount type=bind,source=$source,target=$target -d -p 8012:80 diamol/ch06-todo-list
- 양방향으로 접근 및 수정이 가능하다.
- 컨테이너에서 생성한 파일을 호스트 컴퓨터에서 접근하거나 수정할 수 있고, 반대로 호스트에서 생성한 파일도 컨테이너에서 접근하거나 수정할 수 있다.
- 컨테이너에서 호스트의 파일에 접근하기 위해선 보통 권한 상승이 필요하다.
- Dockerfile에서 USER 인스트럭션을 사용해 컨테이너에 관리자 권한을 부여한다.
- 리눅스에서는 root, 윈도우에서는 ContainerAdministrator 계정
- 따라서 보안에 주의해야 한다 !
- 읽기 전용으로 설정하여 보안을 강화할 수 있다.
- readonly 플래그 사용
- Dockerfile에서 USER 인스트럭션을 사용해 컨테이너에 관리자 권한을 부여한다.
docker container run --name todo-configured -d -p 8013:80 --mount type=bind,source=$source,target=$target,readonly diamol/ch06-todo-list
파일 시스템 마운트의 한계점
시나리오 1
: Docker 이미지를 생성할 때 이미지 안에 특정 디렉터리가 잇고, 그 안에 파일들이 미리 포함되어 있을 경우
Docker는 이미 존재하는 디렉터리에 다른 디렉터리나 파일을 마운트하면, 마운트한 디렉터리나 파일이 기존 디렉터리의 내용을 완전히 대체한다. 즉, 이미지에 포함되어 있던 원래 파일들은 더 이상 사용할 수 없게 된다.
아래 예시를 보도록 하자.
docker container run diamol/ch06-bind-mount
diamol/ch06-bind-mount라는 Docker 이미지를 기반으로 컨테이너를 생성하고 실행한다. 마운트 옵션이 지정되지 않았으므로, 컨테이너는 이미지에 포함된 파일 시스템을 사용하게 된다.
docker container run --mount type=bind,source="$(pwd)/new/123.txt",target=/init/123.txt diamol/ch06-bind-mount
diamol/ch06-bind-mount 이미지를 기반으로 새로운 Docker 컨테이너를 생성하고 실행하되, 이번에는 특정 파일을 컨테이너에 마운트한다. --mount 옵션을 사용해 호스트의 $(pwd)/new/123.txt 파일을 컨테이너의 /init/123.txt 위치에 바인드 마운트한다.
결과적으로, 원래 이미지 안에 있던 /init/123.txt 파일의 내용은 더 이상 접근할 수 있게 된다. 그 이유는 그 위치는 이제 호스트의 파일을 가리치고 있기 때문이다. 즉, 컨테이너의 디렉터리를 바인드 마운트할 때, 원래 그 디렉터리에 있던 파일은 무시되고, 대신에 호스트의 파일이나 디렉터리가 그 위치를 대신하게 된다.
시나리오 2
: 호스트 컴퓨터의 파일 하나를 컨테이너에 이미 존재하는 디렉터리로 마운트할 경우
운영체제에 따라서 다르게 동작한다.
리눅스 환경에서는 컨테이너의 해당 디렉터리에 호스트의 파일이 추가되는 방식으로 동작한다. 예를 들어, /init 디렉터리에 abc.txt 파일이 있고, 호스트의 123.txt를 /init 디렉터리로 마운드하면, /init 디렉터리 안에는 abc.txt와 123.txt 두 파일이 모두 존재하게 된다.
반면 윈도우 환경에서는 동작이 달라진다.
윈도우와 리눅스는 기본적으로 파일 시스템이 다르므로 동일한 동작을 보장받기 어렵다. Dockerfile 스크립트에서 리눅스 방식으로 경로 문자열을 사용하면 윈도우에서도 잘 동작한다. 그러나, 볼륨이나 바인드 마운트와 같은 파일 시스템을 직접 다루는 동작에서는 항상 보장되지 않는다.
시나리오 3
: 분산 파일 시스템을 컨테이너에 반인드 마운트할 경우(예외적인 상황)
- 분산 파일 시스템
- 네트워크에 연결된 여러 컴퓨터가 공유하며 사용하는 파일 시스템
- 여러 컴퓨터가 동일한 데이터에 접근할 수 있어, 데이터의 중앙 관리와 공유를 용이하게 한다.
- SMB, Azure Files, AWS S3 등과 같은 다양한 기술이 사용된다.
- 이러한 분산 시스템을 컨테이너에 바인드 마운트하면 지원하지 않는 동작이 있을 수 있다.
- 예를 들어, Postgres 데이터베이스를 실행하면서 Azure Files 서비스의 스토리지를 컨테이너 스토리지로 사용한다면
- Azure Files는 읽기와 쓰기 등 기본적인 파일 시스템 동작은 지원하지만, 특정 동작(ex. 파일 링크와 생성)은 지원하지 않는다.
- 따라서 오류가 발생할 수 있다 !
- 심지어 직접 실행하지 않으면 미리 파악할 방법도 없다 ..
- 또한, 디스크 I/O가 빈번한 애플리케이션을 분산 스토리지를 마운트한 컨테이너에서 실행한다면, 모든 파일 입출력이 네트워크를 거치게 되므로 성능 저하가 발생할 수 있다.
컨테이너의 파일 시스템
컨테이너의 파일 시스템은 유니언 파일 시스템을 이용해 구현되며, 이를 통해 다양한 출처에서의 파일과 디렉토리에 마치 하나의 디스크처럼 접근할 수 있다. 이는 컨테이너의 환경이 독립적으로 유지되면서도 필요한 데이터를 효율적으로 공유하거나 저장할 수 있게 한다.
기록 가능 레이어(Writeable layer)
이 레이어는 컨테이너마다 독립적으로 가지며, 컨테이너가 삭제되면 해당 레이어에 저장된 데이터도 함께 삭제된다. 즉, 비휘발성 데이터를 저장하기에는 적합하지 않지만, 단기적인 데이터 캐싱이나 계산 결과를 임시로 저장하기에는 유용할 수 있다.
로컬 바인드 마운트(Local Bind Mount)
컨테이너가 실행되는 동일한 머신의 파일 시스템을 사용하는 경우에 사용된다. 주로 개발 환경에서 소스 코드 변경을 즉시 반영해야 하는 경우나, 컨테이너가 로컬 시스템의 특정 리소스에 접근해야 하는 경우에 사용된다.
분산 바인드 마운트(Distributed Bind Mount)
네트워크에 분산된 여러 머신에서 동작하는 컨테이너들이 동일한 데이터에 접근해야 하는 경우에 사용된다. 네트워크 상의 다른 호스트 머신에서 동작하는 컨테이너도 동일한 데이터에 접근하고, 변경 사항을 공유할 수 있다. 그러나 네트워크 기반의 스토리지이므로 로컬 스토리지에 비해 성능이 떨어질 수 있다.
볼륨 마운트(Volume Mount)
컨테이너와 도커 볼륨 간의 데이터 공유를 위해 사용된다. 볼륨을 사용하면 데이터를 영구적으로 저장하며, 애플리케이션을 업데이트할 때 컨테이너를 교체하더라도 이전 버전 컨테이너의 데이터를 유지할 수 있다.
이미지 레이어(Image Layer)
이미지 레이어는 컨테이너의 초기 파일 시스템을 구성하며, 여러 컨테이너가 공유할 수 있다. 레이어는 후속 레이어가 이전 레이어의 내용과 충돌할 경우 후속 레이어의 내용이 적용된다.