도커 교과서-6장

김지수·2023년 5월 25일
0

도커

목록 보기
6/10

도커 볼륨을 이용한 퍼시스턴트 스토리지

해당 장에서는 도커 볼륨과 마운트에 대해 배우고 컨테이너 파일 시스템이 어떻게 동작하는지에 대해 배운다.

컨테이너 속 데이터가 사라지는 이유

도커 컨테이너에도 단일 드라이브로 된 파일 시스템이 있다. 이 파일 시스템의 내용은 이미지 속 파일로부터 만들어진다.
모든 컨테이너는 독립된 파일 시스템을 갖는다. 같은 이미지에서 실행한 여러 개의 컨테이너는 처음에는 디스크의 내용이 모두 같지만, 각 컨테이너가 자신의 파일을 수정해도 다른 컨테이너에는 영향을 받지 않는다.

docker container run --name rn1 diamol/ch06-random-number
docker container run --name rn2 diamol/ch06-random-number

같은 이미지로부터 컨테이너를 두 개 실행한 것이다.(해당 애플리케이션은 컨테이너 속 파일에 무작위 숫자를 쓰는 기능을 한다.)

현재 두 컨테이너는 같은 이미지로부터 실행됐으나 파일 시스템의 내용은 서로 다르다. (2장에서 컨테이너를 종료해도 파일 시스템은 삭제되지 않는다고 배웠다. 즉 컨테이너의 파일과 디렉터리는 그대로 남아있다)

docker container cp rn1:/random/number.txt number1.txt
docker container cp rn2:/random/number.txt number2.txt

해당 명령어는 docker container cp 명령을 사용해 무작위 쓰인 숫자가 쓰인 텍스트파일을 컴퓨터로 복사해 오는 것이다.

이로써 컨테이너의 파일 시스템이 서로 독립적임을 알 수 있다.(덮어쓰지를 않았음을 확인했으니까)

컨테이너의 파일 시스템은 단일 디스크(리눅스 컨테이너는 /dev/sda1, 윈도 C:)다. 하지만 이 디스크는 도커가 여러 출처로부터 합쳐 만들고 컨테이너에 전달한 가상 파일 시스템이다.
출처는 기본적으로 이미지 레이어와 컨테이너의 기록 가능 레이어로 구성되는데, 이미지 레이어는 모든 컨테이너가 공유하자만 기록 가능 레이어는 컨테이너마다 다르다.(이거 이전에 했던 내용이다. 공유하는 이미지 레이어는 변경되어서는 안된다.)

여기서 알아야하는 두 가지 중요한 사실이 있다.
1. 모든 컨테이너가 공유하는 이미지 레이어는 읽기 전용이다.
2. 이미지 레이어는 각 컨테이너가 따로 갖는 기록 기능 레이어와 같은 생애주기를 지닌다.(이미지레이어는 내려받는 순간부터 컨테이너 삭제할 때까지, 쓰기 가능 레이어는 컨테이너를 실행하는 것부터 삭제할 때까지)

기록 가능 레이어를 통해 기존 이미지 레이어에 있는 파일을 수정할 수도 있다.
어? 이미지 레이어는 읽기 전용아닌가?
도커는 기록 중 복사라는 방법을 사용해 읽기 전용 레이어의 파일을 수정한다.
컨테이너에서 이미지 레이어에 포함된 파일을 수정하려 한다면,
1. 도커가 이 파일을 쓰기 가능 레이어로 복사해 온 다음
2. 쓰기 가능 레이어에서 파일을 수정한다.

docker container run --name f1 diamol/ch06-file-display

echo "http://eltonstoneman.com" > url.txt

docker container cp url.txt f1:/input.txt

docker container start --attach f1

해당 파일은 컨테이너가 내용을 출력하는 파일이다.

중간에 https 뭐시기가 http로 바뀐 것이 보이는가?

컨테이너 속 파일을 수정하면 컨테이너의 동작에 영향을 미친다. 그러나 공유하는 다른 컨테이너나 이미지는 영향을 받지 않는다. 수정된 파일은 해당 컨테이너의 기록 가능 레이어에만 존재하기 때문이다.

docker container run --name f2 diamol/ch06-file-display

docker container rm -f f1

docker container cp f1:input.txt .

해당 명령어는 새 컨테이너를 실행해 해당 파일의 내용이 그대로 인지 확인하는 것이다.

이를 통해 새로 실행한 컨테이너는 이미지로부터 받은 원래 내용의 파일을 사용하며, 처음 만든 컨테이너를 삭제하면 그 파일 시스템과 함께 수정된 파일도 사라진다.

유용해보이기도 하지만 사실 되게 위험하다. 엄청 힘들게 만들었는데 실수로 컨테이너를 삭제했다면, 수정된 파일은 모두 날라가고 처음부터 다시 시작하는 것이기 때문이다.
이 때문에 도커볼륨과 마운트를 도커는 이용한다. 이들은 컨테이너와는 별개의 생애주기를 갖는다.

도커 볼륨을 사용하는 컨테이너 실행하기

도커 볼륨은 도커에서 스토리지를 다루는 단위이다. 볼륨은 컨테이너와 독립적으로 존재하며 별도의 생애주기를 갖지만, 컨테이너에 연결할 수 있다.(컨테이너를 위한 USB) 퍼시스턴시가 필요한 유상태 애플리케이션을 컨테이너로 실행하려면 볼륨을 사용해야 한다.
(퍼시스턴시 : 컴퓨터 공학에서 지속성(Persistence)은 프로세스가 생성했지만 별개로 유지되는 상태의 특징 중 한 가지이며, 별도의 기억 장치에 데이터를 보존하는 것을 목적으로 한다. 이 특징으로 인해 프로그래머는 저장 장치로부터 데이터를 전송하는 작업 및 자료 구조 등을 이용해 데이터를 보존하는 것이 가능하다.)

볼륨을 생성해 애플리케이션 컨테이너를 연결하면 컨테이너 파일 시스템의 한 디렉터리가 된다. 나중에 애플리케이션을 업데이트하더라도 새로운 컨테이너에 다시 볼륨을 연결하면 데이터가 그대로 유지된다.

컨테이너에서 볼륨을 사용하는 방법은 2가지이다.
1. 수동으로 직접 볼륨을 생성해 컨테이너에 연결하는 방법
2. Dockerfile 스크립트에서 VOLUME 인스트럭션을 사용하는 방법(VOLUME 인스트럭션의 문법은 VOLUME <\target-directory> 형식이다.)

# app image
FROM diamol/dotnet-aspnet

WORKDIR /app
ENTRYPOINT ["dotnet", "ToDoList.dll"]

# set in the base image - `/data` for Linux, `C:\data` for Windows
VOLUME $SQLITE_DATA_DIRECTORY

이 이미지로부터 컨테이너를 실행하면 자동으로 볼륨을 생성해 컨테이너에 연결해준다. 실행된 컨테이너에는 /data 디렉터리가 있는데(윈도면 C:\data), 이 디렉터리는 다른 디렉터리와 똑같이 사용할 수 있지만 이 디렉터리의 내용은 볼륨에 영구적으로 저장된다.

docker container run --name todo1 -d -p 8010:80 diamol/ch06-todo-list
  
docker container inspect --format '{{.Mounts}}' todo1

docker volume ls  

해당 명령은 todo-list 애플리케이션 이미지로 컨테이너와 연결된 볼륨을 살펴볼 수 있게 한다. 여기서는 컨테이너에 연결된 볼륨만 보여주기 위해 컨테이너 정보에 필터를 적용했다.

웹 페이지에서 할 일(to-do)을 하나 추가하면 이 데이터는 도커 볼륨에 저장된다.

도커 이미지에서 볼륨을 정의하면 컨테이너를 생성할 때마다 새로운 볼륨을 만든다. 하지만 컨테이너가 같은 볼륨을 공유하게 할 수도 있다. volume-from 플래그를 적용하면 된다.

docker container run --name todo2 -d diamol/ch06-todo-list  
  
docker container exec todo2 cmd /C "dir C:\data"

해당 명령어는 to-do 애플리케이션의 두 번째 컨테이너를 실행하고 data 디렉터리의 내용을 확인해보기 위해 하는 것이다.

아아... 울고 싶다.

아까 마운트라는 용어를 봤는데 같은 것일까? 아닐거라 믿고 다른데를 찾아보자.

일단 경로는 찾았고... 봐봐 아까 책에서 데이터를 /data디렉터리에 저장한다고 했다. 아 그냥 우분투 켜서 해보자

와 이건 또 되네...

docker container run -d --name t3 --volumes-from todo1 diamol/ch06-todo-list
  
docker container exec t3 ls /data  

뭔가 그림을 되게 많이 붙였는데 중요한 것은 마지막 사진이다. 두 번째 컨테이너는 새로운 볼륨을 생성해 연결하기 때문에 /data가 비어있지만, 세번째 컨테이너는 첫 번째 컨테이너와 볼륨을 공유하므로 애플리케이션의 데이터를 세 번째 컨테이너의 디렉터리로 볼 수 있다.

볼륨은 컨테이너 간 파일공유보다는 업데이트 간 상태를 보전하기 위한 용도로 사용해야한다.
볼륨에 이름을 붙여 생성하고 업데이트 시 다른 컨테이너로 옮겨 연결하면 된다.

target='/data'
  
docker volume create todo-list  
  
docker container run -d -p 8011:80 -v todo-list:$target --name todo-v1 diamol/ch06-todo-list 

8011에서 데이터를 몇개 추가하자

docker container rm -f todo-v1 

docker 

즉, 볼륨을 사용하면 컨테이너가 교체해 애플리케이션을 업데이트하더라도 데이터를 그대로 보존할 수 있다.

다음 내용으로 넘어가기 전에 한가지 알아야 할 것이 있다.
Dockerfile 스크립트의 VOLUME 인스트럭션과 docker container의 --volume 플래그는 별개의 기능이다.
VOLUME 인스트럭션을 사용해 비드된 이미지로 docker container run 명령에서 볼륨을 지정하지 않으면 항상 새로운 볼륨을 함께 생성한다.(이 때 볼륨은 무작위 식별자를 지닌다.)
--volume 플래그는 이미지에 정의돼 있든 말든 지정한 볼륨을 컨테이너로 마운트한다. 이미지에 볼륨이 정의돼 있더라도 이 정의가 무시되므로 새로운 볼륨이 생기지 않는다.

파일 시스템 마운트를 사용하는 컨테이너 실행하기

바운드 마운트는 호스트의 스토리지를 컨테이너에 좀 더 직접적으로 연결할 수 있는 수단이다.
이는 호스트 컴퓨터 파일 시스템의 디렉터리를 컨테이너 파일 시스템의 디렉터리로 만든다.
바운드 마운트도 볼륨과 마찬가지로 컨테이너의 입장에서는 그냥 평범한 디렉터리이다. 다만 컨테이너가 호스트 컴퓨터의 파일에 직접 접근할 수 있다.

source="$(pwd)/databases" && target='/data'
mkdir ./databases
docker container run --mount type=bind,source=$source,target=$target -d -p 8012:80 diamol/ch06
-todo-list

해당 명령어는 호스트 컴퓨터의 로컬 디렉터리를 컨테이너에 바운드 마운트로 연결하는 것이다.

curl http://localhost:8012

ls ./databases

curl 명령으로 to-do 애플리케이션에 HTTP 요청을 보내는 부분이 있다. 이를 통해 데이터베이스 파일이 생성된다.

바인드 마운트는 양방향으로 동작한다. (서로 수정이 가능하다는 의미이다) 호스트 컴퓨터로의 공격을 막기 위해 Dockerfile 스크립트에서 USER 인스트럭션을 사용해 컨테이너에 관리자 권한을 부여한다.
파일에 쓰기 작업을 할 필요가 없다면 호스트 컴퓨터의 디렉터리를 읽기 전용으로 컨테이너에 연결할 수도 있다.

source="$(pwd)/config" && target='/app/config'

docker container run --name todo-configured --mount type=bind,source=$source,target=$target,readonly -d -p 8013:80 diamol/ch06-todo-list

curl http://localhost:8013

docker container logs todo-configured

위의 명령어는 /app/config 경로가 존재할 경우 이 디렉터리에서 추가 설정 파일을 업로드 한다.

많은 양의 debug 레벨 로그가 출력된 것으로 보아 설정이 변경됐음을 알 수 있다.

파일 시스템 마운트의 한계점

첫 번째 시나리오는 컨테이너의 마운트 대상 디렉터리가 이미 존재하고 이미지 레이어에 이 디렉터리의 파일이 포함돼 있는 경우다.
이 경우 이미 존재하는 대상 디렉터리에 마운트하면 마운트 원복 디렉터리가 기존 디렉터리를 완전히 대체한다. 그래서 이미지에 포함돼 있던 원래 파일은 사용할 수 없다.

docekr container run diamol/ch06-bind-mount

컨테이너를 실행하면 /init 디렉터리 파일 목록을 출력한다. 이 컨테이너는 바인드 마운트가 되지 않았으므로 이미지에 포함된 기존 파일의 목록이 출력된다.

docker container run --mount type=bind,source=$source,target=$target diamol/ch06-bind-mount

/init를 대상으로 바인드 마운트를 마운트해 컨테이너를 실행하면 원래 디렉터리 내용은 숨겨지고 바인드 마운트의 원본 디렉터리가 이를 대체한다.


두 번째 시나리오는 호스트 컴퓨터의 파일 하나를 컨터이너에 이미 존재하는 디렉터리로 마운트하는 경우이다. 이번엔 파일이 합쳐진다. (단, 윈도 컨테이너는 이 기능을 제공하지 않는다)
docker container run diamol/ch06-bind-mount

docker container run --mount type=bind,source="$(pwd)/new/123.txt",target=/init/123.txt diamol/ch06-bind-mount

컨테이너의 파일 시스템은 어떻게 만들어지는가?

모든 컨테이너는 도커가 다양한 출처로부터 모아 만든 단일 가상 디스크로 구성된 파일 시스템을 갖는다.
이 파일 시스템을 유니언 파일 시스템이라 한다.
컨테이너는 유니언 파일 시스템을 통해 물리적 위치가 서로 다른 파일과 디렉터리에 마치 단일 디스크립트를 사용하듯 접근할 수 있다.

컨테이너에서 실행되는 애플리케이션의 입장에서는 단일 디스크만을 볼 수 있지만, 컨테이너나 이미지를 생성해 사용한는 사용자는 여러 출처를 합쳐 이 디스크를 구성할 수 있다. 인지하고 있어야 하는 것은 기록 가능 레이어는 하나 밖에 가질 수 없다는 것이다.
다음은 컨테이너의 스토리지를 구성할 때 고려해야 할 일반론이다.

  • 기록 가능 레이어 : 비용이 비싼 계산이나 네트워크를 통해 저장해야 하는 데이터의 캐싱 등 단기 저장에 적합하다. 컨테이너가 삭제되면 여기 저장된 데이터는 유실된다.

  • 로컬 바인드 마운트 : 호스트 컴퓨터와 컨테이너 간 데이터를 공유하기 위해 사용한다. 개발자의 로컬 컴퓨터에서 컨테이너로 소스 코드를 전달하기 위해 사용하면 로컬 컴퓨터에서 수정한 내용이 이미지 빌드 없이도 즉시 컨테이너로 전달 될 수 있다.

  • 분산 바인드 마운트 : 네트워크 스토리지와 컨테이너 간에 데이터를 공유하기 위해 사용한다. 읽기 전용으로 설정 파일을 전달하거나 공유 캐시로 활용할 수 있으며 읽기 쓰기 기능으로 데이터를 저장해 동일 네트워크상의 모든 컨테이너나 컴퓨터와 데이터를 공유하는 데 적합하다.

  • 볼륨 마운트 : 컨테이너와 도커 객체인 볼륨 간에 데이터를 공유하기 위해 사용한다. 컨테이너를 교체하는 방식으로 애플리케이션을 업데이트해도, 이전 버전 컨테이너의 데이터를 그대로 유지할 수 있다.

  • 이미지 레이어 : 컨테이너의 초기 파일 시스템을 구성한다. 레이어는 적층 구조를 갖는데, 후속 레이어와 이전 레이어의 내용이 충돌하는 경우 후속 레이어 내용이 적용된다. 레이어는 읽기 전용이며 여러 컨테이너가 공유한다.

연습 문제

to-do 애플리케이션을 컨테이너로 실행하되 미리 등록된 할 일이 없는 상태로 애플리케이션이 시작되도록 스토리지를 설정하는 것이다.
1. diamol/ch06-lab 이미지로 컨테이너를 실행해 현재 등록된 할 일을 확인
2. 이때 마운트를 추가해 컨테이너를 실행
3. to-do 애플리케이션의 설정 파일은 앞에서 본 로그 설정보다 좀 더 복잡

자 시작하자.
먼저 1단계를 실행

오케이 이제 2단계 실행

profile
노는게 제일 좋아

0개의 댓글