이번 포스트에서는 도커 볼륨
을 다루는 방법을 정리하겠습니다.
위의 그림에서 보면 컨테이너 계층
과 이미지 계층
으로 나누어져 있는 것을 볼 수 있습니다.
보통 우리가 도커 이미지를 빌드할 때 docker build -t app [경로] 명령어를 실행하면
Dockerfile이라는 명세서를 기반으로 도커 이미지가 빌드가 됩니다.
이 이미지는 레이어 구조로 되어있는데, Dockerfile
내에 작성되어있는 여러 명령어들이 순차적으로 레이어가 쌓이듯이 저장된다고 보면 됩니다.
예를 들어, 우분투 이미지를 Base로 사용한다고 하면
Layer 1: Base 우분투레이어 설치
Layer 2: 우분투 운영체제에서 필요로하는 패키지들을 설치(ex. apt-get으로 패키지 설치하는 내용)
Layer 3: 패키지를 설치(ex. 파이썬 패키지를 설치한다.)
Layer 4: 소스코드를 복사를 한다면 복사한 내용이 Layer4에 저장(ex. 소스코드의 수정)
Layer 5: Entrypoint 업데이트(ex. 컨테이너의 Entrypoint를 변경을 한다.)
도커 컨테이너와 이미지는 레이어 구조를 취하면서 여러 장점을 가지게 됩니다.
만약, 추후에 이미지에 특정 변경사항을 가한다고 했을 때, 만약 소스코드 상에 변경이 있었다면 Layer 1 ~ Layer 3까지는 변경될 필요가 없습니다.
그러므로, Layer 1 ~ Layer 3까지는 변경을 하지않고 Layer 4, Layer 5가 생성이 됩니다.
이러한 방식으로 도커 이미지
는 이미지의 여러 버전들이 생긴다고 하더라도 저장공간을 차지하지 않는 레이어 구조를 가지고 있습니다.
이 도커 이미지를 도커 컨테이너로 docker run 명령어로 이미지를 실행하면 컨테이너가 됩니다.
실행한 컨테이너는 두가지 레이어를 가지게 됩니다.
첫번째는 이미지 계층(Image Layer)
, 두번째는 컨테이너 계층(Container Layer)
입니다.
이미지 계층(Image Layer)
에서 이미지는 항상 동일하고 Read Only(읽기 전용)으로 사용하게 됩니다. 이 읽기 전용 이미지 레이어에는 변경사항을 적용할 수 없습니다.
컨테이너 계층(Container Layer)
은 컨테이너 상에서 새로운 파일을 쓴다거나 할 때는 기본적으로 Container Layer에 파일을 쓰게 되는데, 이 Container Layer는 Read Write 권한을 가지고 있습니다.
중요한 점은 컨테이너가 종료되고 나면 Container Layer는 컨테이너 종료시 함께 삭제가 됩니다.
도커 이미지
로 컨테이너를 생성하면 이미지는 읽기 전용이 되며 컨테이너의 변경 사항만 별도로 저장해서 각 컨테이너의 정보를 보존합니다. 이미 생성된 이미지는 읽기 전용이므로 어떠한 경우로도 변경되지 않고, 컨테이너 계층에 원래 이미지에서 변경된 파일시스템 등을 저장합니다.
이미지에 mysql을 실행하는 데 필요한 애플리케이션 파일이 들어있다면 컨테이너 계층에는 워드프레스에서 쓴 로그인 정보나 게시글 등과 같이 데이터베이스를 운용하면서 쌓이는 데이터가 저장됩니다. 예를 들면, 위에서 생성했던 mysql 컨테이너는 mysql:5.7라는이미지로 생성됐지만 wordpress 블로그를 위한 데이터베이스 등의 정보는 컨테이너가 갖고 있습니다. 즉, 위의 그림과 같은 구조가 됩니다.
그러나, 여기에는 치명적인 단점이 존재합니다. mysql 컨테이너를 삭제하면 컨테이너 계층에 저장되어 있던 데이터베이스의 정보도 삭제된다는 점입니다. 도커의 컨테이너는 생성과 삭제가 매우 쉬우므로 실수로 컨테이너를 삭제하면 데이터를 복구할 수 없게 됩니다. 이를 방지하기 위해 컨테이너의 데이터를 영속성(Persistent) 데이터로 활용할 수 있는 방법이 몇 가지 있습니다. 그 중 가장 활용하기 쉬운 방법이 바로 볼륨(Volume)을 활용하는 것 입니다.
컨테이너 상에서 볼륨을 영구적으로 사용할 수 있는 방법은 세가지가 있습니다.
1. 호스트 볼륨을 이용하는 방법.
2. 볼륨 컨테이너를 이용하는 방법.
3. 도커 볼륨을 이용하는 방법.
먼저, 호스트 볼륨을 이용하는 방법부터 정리하겠습니다.
호스트 볼륨 공유하기
호스트 볼륨
을 이용한다는 것은 호스트 운영체제의 디렉토리를 컨테이너 내에 마운트 시키는 작업을 말하며 가장 간단하고 직관적인 방법입니다.
아래 명령어를 입력해서 mysql 데이터베이스 컨테이너를 실행해줍니다.
# MYSQL 컨테이너 실행
$ docker run -d --name wordpressdb_hostvolume \
-e MYSQL_ROOT_PASSWORD=password \
-e MYSQL_DATABASE=wordpress \
-v /home/wordpress_db:/var/lib/mysql \
mysql:5.7
아래 명령어를 입력해서 워드프레스 웹 서버 컨테이너를 생성합니다.
$ docker run -d \
-e WORDPRESS_DB_PASSWORD=password \
--name wordpress_hostvolume \
--link wordpressdb_hostvolume:mysql \
-p 80 \
wordpress
워드프레스 컨테이너에 -p 옵션으로 컨테이너의 80 포트를 외부에 노출했으므로 docker ps 명령어에서 확인한 wordpress_hostvolume 컨테이너의 호스트 포트로 워드프레스 컨테이너에 접속할 수 있습니다.
중요한 점은 -v 옵션
으로 /home/wordpress_db:/var/lib/mysql로 설정해준 것입니다. 이는 호스트의 /home/wordpress_db 디렉토리와 컨테이너의 /var/lib/mysql 디렉토리를 공유한다는 뜻입니다.
즉, -v 옵션
에 [호스트의 공유 디렉토리]:[컨테이너의 공유 디렉토리]
를 입력해주면 됩니다.
만약, /home/wordpress_db 디렉토리를 호스트에 생성하지 않았어도 도커는 자동으로 이를 생성해줍니다.
실제로 데이터베이스 관련 파일이 있는지 확인합니다.
mysql을 구동하는 데 필요한 각종 파일이 공유되었습니다. mysql, performance_schema, sys, wp 디렉토리는 mysql에 존재하는 실제 데이터베이스에 대응됩니다.
컨테이너를 삭제하고 데이터베이스의 데이터가 보존되는지 확인합니다. 다음 명령어를 입력해 방금 생성한 두 개의 컨테이너를 삭제합니다.
$ docker stop wordpress_hostvolume wordpressdb_hostvolume
$ docker rm wordpress_hostvolume wordpressdb_hostvolume
다시 /home/wordpress_db 디렉토리를 확인하면 mysql 컨테이너가 사용한 데이터가 그대로 남은 것을 확인할 수 있습니다.
-v 옵션을 써서 컨테이너의 디렉토리를 호스트와 공유한 것을 그림으로 나타내면 아래와 같습니다.
컨테이너의 /var/lib/mysql 디렉토리는 호스트의 /home/wordpress_db 디렉토리와 동기화되는 것이 아니라 완전히 같은 디렉토리입니다.
위 예시의 경우 원래 호스트에는 /home/wordpress_db 디렉토리가 존재하지 않았습니다. -v 옵션
을 사용함으로써 호스트에 /home/wordpress_db 디렉토리가 생성되었고, 이 디렉토리에 파일이 공유되었습니다. 결과적으로 컨테이너의 파일이 호스트로 파일이 복사된 것입니다.
+ 추가)
만약, 호스트에 이미 디렉토리와 파일이 존재하고 컨테이너에도 존재한다면 어떻게 될까요?
예를들면, volume_test라는 이미지가 있고 이 이미지에는 /home/testdir 디렉토리가 존재하고 그 안에 test라는 파일이 존재합니다.
그리고, 아래 명령어를 통해 컨테이너를 생성하고 -v옵션으로 호스트 볼륨을 공유합니다.
$ docker run -i -t \
--name volume_override \
-v /home/wordpress_db:/home/testdir \
seosh817/volume_test
그러면 -v 옵션의 값인 /home/tesdir 디렉토리를 확인하면 원래 존재했던 test 파일이 없어지고 호스트에서 공유된 파일만 존재하게 됩니다.
즉, 이미지에 원래 존재하던 디렉토리에 호스트의 볼륨을 공유하면 컨테이너의 디렉토리 자체가 덮어씌워집니다. 정확히 말하면 -v 옵션을 통한 호스트 볼륨 공유는 호스트의 디렉토리를 컨테이너의 디렉토리에 마운트합니다.
+ 추가)
또한, 아래처럼 디렉토리 단위의 공유뿐 아니라 단일 파일 단위의 공유도 가능하며 동시에 여러개의 -v 옵션을 쓸 수도 있습니다.
$ echo hello >> /home/hello && echo hello2 >> /home/hello2
$ docker run -i -t \
--name file_volume \
-v /home/hello:/hello \
-v /home/hello2:/hello2 \
ubuntu:14.04
볼륨 컨테이너
볼륨 컨테이너
를 이용하는 방법은 볼륨을 특정 어플리케이션 컨테이너에서 마운트를 시키는것이 아니라, 볼륨 컨테이너(위 그림에서 Data-only Container)에서 볼륨 마운트만 진행을 하고 아무것도 하지 않는 컨테이너를 만들어서 어플리케이션 컨테이너가 볼륨 컨테이너를 참조하게 해서 마치 볼륨 컨테이너같이 사용할 수 있습니다. 그러면 Data-only Container의 마운트 목록을 어플리케이션 컨테이너가 공유받을 수 있습니다.
컨테이너를 생성할 때 -v 옵션 대신 --volumes-from
옵션으로 컨테이너를 지정하면 -v 또는 --volume 옵션을 적용한 컨테이너의 볼륨 디렉토리를 공유할 수 있습니다. 이는 직접 볼륨을 공유하는 것이 아닌 -v 옵션을 적용한 컨테이너를 통해 공유하는 것입니다.
먼저 호스트 볼륨을 공유하는 volume_override 라는 이름의 컨테이너를 생성해줍니다.
volume_override 컨테이너는 우분투 포칼 이미지를 이용하고 있고 호스트의 $(pwd)/html를 /user/share/nginx/html에 마운트를 하고 있습니다.
$ docker run -d \
-it \
--name volume_override \
-v $(pwd)/html:/usr/share/nginx/html \
ubuntu:focal
그리고 아래 명령어를 입력하여 --volumes-from 옵션을 사용하여 volume_override 컨테이너에서 볼륨을 공유받는 컨테이너를 생성해줍니다.
$ docker run -d \
--name seunghwan_nginx1 \
--volumes-from volume_override \
-p 80:80 \
nginx
$ docker run -d \
--name seunghwan_nginx2 \
--volumes-from volume_override \
-p 8080:80 \
nginx
docker ps로 확인하면 volume_override, seunghwan_nginx1, seunghwan_nginx2 세 개의 컨테이너가 정상적으로 실행이 된 것을 확인할 수 있습니다.
--volumes-from
옵션을 적용한 컨테이너와 볼륨 컨테이너 사이의 관계를 그림으로 나타내면 위 그림과 같습니다.
여러 개의 컨테이너가 동일한 컨테이너에 --volumes-from 옵션을 사용함으로써 하나의 볼륨을 공유해서 사용할 수 있습니다.
이러한 구조를 활용하여 호스트에서 볼륨만 공유하고 별도의 역할을 하지 않는 일명 '볼륨 컨테이너'로 활용해서 컨테이너를 활용할 수 있습니다. 즉, -v 대신 --volumes-from 옵션을 사용함으로써 볼륨 컨테이너를 연결해 데이터를 간접적으로 공유받는 방식입니다.
docker inspect seunghwan_nginx1를 입력해서 Mounts 목록에 보면 Source와 Destination을 보면
호스트의 /home/ubuntu/html -> 컨테이너의 /usr/share/nginx/html 로 마운트된 것을 확인할 수 있습니다.
docker exec 명령어로 seunghwan_nginx1 컨테이너에 접속해서 /usr/share/nginx/html에 마운트가 잘 되었는지 확인해보겠습니다.
docker exec -it bfaf bash
아래 결과를 보시면 정상적으로 마운트가 잘 된것을 확인할 수 있습니다.
도커 볼륨
볼륨을 사용하는 세 가지 방법 중 도커 볼륨
은 도커에서 제공하는 볼륨 관리 기능입니다.
볼륨을 사용하는 방법 중 보통 호스트 볼륨이나 도커 볼륨을 가장 많이 사용합니다.
호스트 볼륨과의 차이점은 호스트에 마운트할 경로를 지정했어야 했지만
도커 볼륨
은 도커가 제공하는 볼륨 관리 기능을 통해서 볼륨을 생성하고 삭제하고 관리를 할 수 있습니다.
이렇게 생성된 볼륨은 도커가 관리하는 특정 호스트 경로에 데이터가 저장이 됩니다.
기본적으로는 도커에 데이터가 저장되는 /var/lib/docker/volumes/{...}/_data 에 데이터가 저장되는데
구체적으로는 /var/lib/docker/volumes/volume이름/_data에 도커 디렉토리에 볼륨 이름으로 데이터가 생성되고 데이터가 저장됩니다.
도커 볼륨을 다루는 명령어는 docker volume
으로 시작합니다.
도커 볼륨 구조
docker volume
명령어로 생성한 볼륨은 아래와 같은 구조로 활용됩니다. 도커 볼륨도 마찬가지로 여러개의 컨테이너에 공유되어 활용될 수 있습니다.
도커 볼륨 생성
docker volume create
명령어로 도커 볼륨을 생성할 수 있습니다.
$ docker volume create --name [도커 볼륨 이름]
도커 볼륨 목록 확인
docker volume ls
명령어로 생성된 볼륨을 확인할 수 있습니다.
$ docker volume ls
도커 볼륨 정보 확인
docker volume inspect
명령어로 마운트 경로등의 볼륨 정보를 확인할 수 있습니다.
Driver는 볼륨이 쓰는 드라이버를, Label은 볼륨을 구분하는 라벨을 나타내며, Mountpoint는 해당 볼륨이 실제로 호스트의 어디에 저장됐는지를 의미합니다.
$ docker volume inspect [도커 볼륨 이름]
# myvolume이라는 도커 볼륨 생성
$ docker volume create myvolume
# mysql을 실행할 때 도커 볼륨을 /var/lib/mysql에 마운트
$ docker run -d \
--name seunghwan_mysql \
-e MYSQL_ROOT_PASSWORD=password \
-e MYSQL_DATABASE=seunghwan_db \
-p 3306:3306 \
-v myvolume:/var/lib/mysql \
mysql:5.7
위의 명령어를 보면 먼저, docker volume create
명령어를 통해서 myvolume이라는 이름의 볼륨을 생성해줍니다.
그리고 생성한 myvolume 도커볼륨을 mysql 컨테이너를 띄울 때 /var/lib/mysql 컨테이너 경로에다가 마운트해줍니다.
형식은 아래와 같습니다.
[볼륨의 이름]:[컨테이너의 공유 디렉토리]
* 참고로 /var/lib/mysql 경로는 mysql이 데이터를 쌓는 경로.
mysql이 실행되면서 공유 디렉토리인 컨테이너의 /var/lib/mysql 디렉토리도 같이 초기화가 되고
결국, 도커 볼륨도 같이 공유하므로 ls 명령어로 확인해 보면 마찬가지로 초기화되어 있는 것을 확인할 수 있습니다.
seunghwan_mysql 컨테이너가 myvolume 컨테이너를 사용하는지 확인하는 다른 방법은
docker container inspect 명령어를 이용하는 것입니다. docker container inspect 명령어는 컨테이너의 상세한 정보를 출력하는데, 그 중 볼륨 마운트에 대한 정보도 포함되어 있기 때문입니다.
Source 항목에 도커 볼륨의 경로가 출력되고, Destination에 공유 디렉토리의 경로가 출력되었습니다.
+참고)
참고로, 도커 볼륨의 내용물 중 ibdata1을 제거해 주고 실행해주세요. 제거 하지 않으면 아래와 같은 에러가 발생합니다.
발생하는 이유는 mysql 데이터와의 충돌 문제가 발생합니다. 제거해도 어짜피 다시 재생성 되므로 안심하고 삭제하셔도 됩니다.
[ERROR] --initialize specified but the data directory has files in it. Aborting. 0
[ERROR] Aborting site:stackoverflow.com
감사합니다!