실습으로 배우는 Docker 입문 | 7. 바인드 마운트 Hello Java
이번에는 Docker 바인딩 마운트 기능에 대해 설명한다. 또한 바인딩 마운트의 구체적인 예로서 Java Hello World 프로그램을 컴파일하고 실행하는 방법을 소개한다.
Docker 마운트 기능
‘마운트’라는 용어는 Linux 나 Windows 등 OS의 용어로 아시는 사람이 많을 것이다. 예를 들어 “하드 디스크를 탑재하는 것"이라고 말했다면 OS가 하드 디스크를 인식하고 사용할 수 있는 상태로 만드는 것을 의미한다.
Docker에서 “마운트"는 컨테이너 외부에있는 데이터를 컨테이너에서 사용할 수 있는 상태가 될 것을 의미한다. 207년 말에 공개된 “Docker v17.12.0-ce"는 다음의 세 가지 마운트 기능이 제공되고 있다.
- 바인드 마운트 (bind)
- 볼륨 마운트 (volume)
- 임시 파일 시스템 마운트 (tmpfs)
이번에 설명하는 “1. 바인드 마운트"는 Docker 호스트 파일이나 디렉터리를 사용할 수 있도록 하는 기능이다. 바인드 마운트를 하면 컨테이너의 외부에 있는 Docker 호스트 파일을 컨테이너 속에서 읽고 쓸 수 있게 된다. 바인드 마운트된 파일은 /home
와 /var
와 같은 경로를 이용하여 아래에서 컨테이너 안에 있는 파일처럼 사용할 수 있다.
“2. 볼륨 마운트"는 Docker 호스트 파일이나 디렉터리 중 Docker가 관리하고 있는 것을 사용할 수 있도록하는 기능이다. “3. 임시 파일 시스템 마운트"는 Docker 호스트에 파일로 저장하고 싶지 않은 데이터를 일시적으로 사용할 수 있도록하는 기능이다. 모두 이번 자세하게 설명하지는 않겠지만, 데이터의 이용 목적에 따라 각각을 구사하면 좋을 것이다.
마운트 개요
컨테이너의 격리성(isolation)
바인드 마운트 Docker의 초기부터 제공되는 기능이며 , 현재에도 잘 이용되고 있다. 바인드 마운트가 필요한 이유는 컨테이너의 특징 중 하나 인 격리성 (isolation)과 강하게 연관되어 있다.
일반적으로 컨테이너는 컨테이너 안에 있는 데이터만 사용할 수 있다. 어떤 컨테이너에서 다른 컨테이너에 있는 데이터와 Docker 호스트의 데이터를 사용할 수 없다. 예를 들어, 컨테이너에서 ls /home
와 같은 명령어를 실행하면 출력되는 것은 컨테이너에 있는 /home
의 정보만 표시된다. 다른 컨테이너의 /home
와 Docker 호스트 /home
의 정보는 전혀 표시되지 않는다. 그런 성격을 컨테이너의 격리성이라고 한다.
컨테이너는 파일이나 디렉터리를 격리할 뿐만 아니라 프로세스와 네트워크 등을 격리해 준다. Docker가 가상화 기술의 일종이라고 말하는 것은 그렇게 여러 가지를 격리하고 가상으로 독립적인 환경을 만들어주는 것이다.
격리성은 Docker에게 매우 중요한 특성이다. 격리성을 높다는 것은 Docker 호스트 등 주변 환경에 대한 의존도를 낮추는 등 다양한 장점이 있다. 그러나 그 반대로 격리 가능성이 높은 것으로 불편을 느끼는 경우도 있다.
예를 들어, 이번에 다르게 될 Java Hello World 프로그램처럼 작은 파일을 생성하고 컨테이너로 이용하는 경우를 생각해 보다. Docker 호스트 파일을 작성한 경우 컨테이너 안에서 그 파일을 읽거나 쓸 수 없다. 컨테이너에서 파일을 작성하면 읽고 쓰기는 당연히 할 수 있지만 평소 친숙한 텍스트 에디터를 컨테이너 안에 있는 것은 없다. 또한 컨테이너에서 만든 파일은 컨테이너를 삭제하면 함께 삭제되어 버린다. 삭제되기 전에 docker container cp
명령을 실행하고 파일을 컨테이너 밖으로 복사 할 수 있지만, 왠지 귀찮은 느낌이 든다.
그런 상황을 바인드 마운트 는 격리성을 낮추므로써 가능하게 해준다. 일반적으로 컨테이너 속에서 읽고 쓸 수 없는 Docker 호스트 파일을 바인드 마운트된 것에 한하여 특별히 읽고 쓸 수 있도록 해주는 것이다.
사전 준비
그럼 여기에서 Java Hello World 프로그램을 예로 바인드 마운트의 구체적인 사용법에 대해 살펴 보겠다. 개요는 다음과 같다.
- 사전 준비
- 격리성 실험 : 바인드 마운트하지 않으면 어떻게 될까
- 바인드 마운트 기본 : mount 옵션
- Java 컴파일 : mount 옵션
- Java 실행 : workdir 옵션 및 mount 옵션
- alias의 활용
- Windows에서 파일의 바인딩 마운트
- volume 옵션
우선 사전 준비로 Docker 호스트를 준비한다. 여기에 내용은 Docker Machine v0.12.2를 사용하여 Windows 10의 VirtualBox 만든 Docker 호스트에서 검증하고 있다. Docker Machine 사용에 대해서는 “Docker Machine으로 Windows에 Docker 포스트 만들기“를 참고해라.
Docker 버전 v17.06 이상으로 해라. 이후 --mount
라는 옵션을 사용하기 때문이다. 본 기사의 Docker 버전은 “v17.12.0- ce"이다.
Docker 호스트가 준비되면 Java 컨테이너를 움직여 보자. 이번에는 Docker의 공식 이미지 인 openjdk 이미지 를 사용한다.
다음 명령을 실행하여 Java 버전을 확인한다.
$ docker run --rm openjdk:9 java --version
openjdk 9.0.1
OpenJDK Runtime Environment (build 9.0.1+11-Debian-1)
OpenJDK 64-Bit Server VM (build 9.0.1+11-Debian-1, mixed mode)
openjdk:9
이미지에서 생성된 컨테이너에서 java --version
라는 명령이 실행되고 정보가 출력되었다. 따라서 이번에는 Docker의 공식 이미지인 openjdk 이미지 중에 JDK 버전 9가 포함된 이미지를 사용한다.
위의 명령에서 --rm
옵션을 지정하고 있는 것에 주목하자. --rm
는 컨테이너가 멈추면 자동으로 삭제하는 옵션이다. 이후 여러번 Java 컨테이너를 생성하고 시작하는데, 아무것도하지 않으면 컨테이너 차례 차례로 쌓여 가기 때문에 이렇게 --rm
옵션으로 자동으로 제거한다.
마지막으로, Docker 호스트에 다음과 같은 Java 파일을 작성한다.
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
이를 /home/docker/Hello.java
로 저장한다. 반복이 되겠지만, 이 “Hello.java"는 Docker 호스트에 작성한다.
사전 준비는 완료되었다.
격리성 실험 : 바인드 마운트하지 않으면 어떻게 될까
그럼, 먼저 소개 바인드 마운트하지 않으면 어떻게 되는지 격리성이란 어떤 것인지 확인해 보자.
다음 명령을 실행하여 보자.
$ ls /home/docker/Hello.java
/home/docker/Hello.java
$ docker run --rm openjdk:9 ls /home/docker/Hello.java
ls: cannot access '/home/docker/Hello.java': No such file or directory
첫 번째의 ls
는 Docker 호스트에서 실행하고 있으므로 Docker 호스트의 /home/docker
디렉터리에 있는 Hello.java
이 보이고 있다.
두 번째의 ls
는 컨테이너에서 실행하고 있기 때문에, 컨테이너 안의 /home/docker
디렉터리를 찾아간다. 컨테이너에 있는 디렉터리는 격리되어 있기 때문에 Docker 호스트의 Hello.java
를 찾을 수 없다. (원래 openjdk 이미지에서 만든 컨테이너에는 /home
디렉터리는 있지만, /home/docker
디렉터리는 없다.)
또한 실험해 보자. 이번에는 cat
명령을 실행하여 Hello.java
내용을 출력해 보자.
$ cat /home/docker/Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
$ docker run --rm openjdk:9 cat /home/docker/Hello.java
cat: /home/docker/Hello.java: No such file or directory
역시 두 번째의 cat
은 컨테이너에서 실행중인 때문에 Docker 호스트의 Hello.java
를 찾을 수 없다.
마지막으로, 컨테이너에서 Java 컴파일 할 수 있는지 시험해 보자.
$ docker run --rm openjdk:9 javac Hello.java
javac: file not found: Hello.java
Usage: javac <options> <source files>
use --help for a list of possible options
역시 오류가 출력되었다. 컨테이너의 javac
명령을 실행하고 Hello.java
컴파일하려고 했지만, Docker 호스트의 Hello.java
은 찾을 수 없다.
억지로 공식 이미지에서 JDK의 컨테이너를 쉽게 만들 수 있지만 이대로는 Hello.java를 컴파일 할 수 없다.
[컨테이너의 격리성]
바인드 마운트 기본 : mount 옵션
그러면 바인딩 마운트를 사용하여 Hello.java
컨테이너에서 사용할 수 있도록 한다.
바인드 마운트 설정은 --mount
옵션으로 한다. 다음과 같이 바인딩 마운트를 설정하고 cat명령을 실행한다.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test openjdk:9 cat /home/test/Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
Hello.java
내용이 출력되었다! 컨테이너에서 실행한 cat
명령으로 Docker 호스트의 Hello.java
가 표시되는 것이다.
--mount
옵션을 살펴 보자. --mount
옵션은 ‘키=값’의 형식으로 마운트 설정을 설명한다. “키=값"은 쉼표로 구분한다. 쉼표의 좌우에 공백을 넣지 않도록 주의해라. 각 “키=값"의 의미는 다음과 같다.
type=bind
- 탑재 유형이다. 사용할 수 있는 값은 처음에 소개한 세가지
bind
,volume
,tmpfs
이다. 여기에서는 바인드 마운트를 수행하기 위해 bind지정하고 있다.
- 탑재 유형이다. 사용할 수 있는 값은 처음에 소개한 세가지
src=/home/docker
- 마운트 Docker 호스트의 디렉터리이다. 여기에서는
Hello.java
이 들어있는/home/docker
로 지정하고 있다. 하나에 대한/home
지정도 좋다. 이 경우/home
아래의/home/docker
와Hello.java
도 함께 마운트되었다.src
대신에,source
라고 써도 같은 의미이다.
- 마운트 Docker 호스트의 디렉터리이다. 여기에서는
dst=/home/test
- 마운트 Docker 호스트 디렉터리, 컨테이너 속에서 경로이다. 컨테이너 안에
/home/test
라는 경로에서 위에src
에 지정된/home/docker
를 사용할 수 있다./home/test
디렉터리는 원래의 용기 안에 존재도 존재하고 있지 않아도 괜찮다.dst
대신에destination
나target
라고 써도 같은 의미이다.
- 마운트 Docker 호스트 디렉터리, 컨테이너 속에서 경로이다. 컨테이너 안에
바인드 마운트
Java 컴파일 : mount 옵션
바인드 마운트 할 수 있게 되었으므로, Hello.java
을 컴파일하여 보다.
다음 명령을 실행하십시오.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test openjdk:9 javac /home/test/Hello.java
이제 컴파일이 되었다. 확인하자.
$ ls /home/docker/Hello*
/home/docker/Hello.class /home/docker/Hello.java
Hello.class가 생성되어 있다. 컴파일 성공이다.
바인드 마운트를 수행하여 Docker 호스트의 Java 파일을 컨테이너로 컴파일하여 클래스 파일을 Docker 호스트에 출력 할 수있는 것을 알 수 있었다.
Java 실행 : workdir 옵션 및 mount 옵션
그런 다음 Hello.class
을 실행하자.
Hello.class를 실행하는 명령 java Hello
로 한다. 일단 방금 컴파일 할 때의 javac …명령을 java Hello에 대체 실행하자.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test openjdk:9 java Hello
Error: Could not find or load main class Hello
Caused by: java.lang.ClassNotFoundException: Hello
ClassNotFoundException
가 발생하였다. Hello.class이 없는 것 같다.
클래스 파일이 없는 경우의 해결 방법은 여러 가지가 있지만, 여기에서는 --workdir
옵션(약어 -w)를 사용하여 java명령을 실행하는 디렉터리를 바꿔 보자.
--workdir
옵션은 컨테이너에서 어떤 디렉터리에서 작업을 수행할지 여부를 지정할 수 있는 옵션이다. 그렇게 작업을 수행할 디렉터리를 다음에서 작업 디렉터리 라고 한다.
--workdir
옵션을 지정하지 않으면 사용하는 이미지의 설정에 따라 작업 디렉터리가 결정된다. 방금 ClassNotFoundException
이 발생한 명령의 작업 디렉터리가 무엇이었을까, pwd
명령으로 확인 해보자.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test openjdk:9 pwd
/
작업 디렉터리는 루트 디렉터리(/
) 였다. Hello.class
이 놓여있는 위치는 루트 디렉터리가 아니라 dst
지정된 /home/test
디렉터리이기 때문에 ClassNotFoundException
이 발생한 것이다. java Hello
를 실행하는 작업 디렉터리를 루트 디렉터리가 아닌 /home/test
디렉터리로 이동하면 Hello.class
찾을 수 있을 것 같다.
그래서 다음과 같이 --workdir
옵션을 /home/test
으로 지정하여 보자.
$ docker run --rm --mount type=bind,src=/home/docker,dst=/home/test --workdir /home/test openjdk:9 java Hello
Hello, World
수행 할 수 있었다. “Hello, World” 성공이다!
--workdir
옵션은 바인드 마운트 및 Java 컨테이너뿐만 아니라 자주 사용하는 유용한 옵션이다. 이번처럼 있어야 할 파일을 찾을 수 없어서 곤란 때 등에 기억 사용해 보면 좋을 것이다.
alias의 활용
바인드 마운트는 유용하지만, 좀 명령이 길었다. 다음과 같이 Linux의 alias 명령으로 짧게 해보자.
$ alias java='docker run --rm --mount type=bind,src=/home/docker,dst=/home/test --workdir /home/test openjdk:9 java'
$ java Hello
Hello, World
두 번째 명령 java Hello의 java는 alias이므로, 실제로는 컨테이너가 동작한다. 이와 같이 alias
를 활용하면 마치 로컬에 설치되는 도구처럼 컨테이너를 사용할 수 있다.
Windows에서 파일의 바인딩 마운트
Docker Machine으로 Windows에 Docker 포스트 만들기 페이지처럼 Docker Machine을 사용하여 Windows에서 VirtualBox에 Docker 호스트를 작성하는 경우 Windows에서 파일을 바인드 마운트 할 수 있다.
무슨 뜻인지 다음과 같이 Docker 호스트의 /c/Users
폴더를 바라 보면 직관적으로 이해할 수 있다.
$ ls /c/Users
All Users Default User Public/
Default/ kimkc/ desktop.ini
귀하의 Windows 시스템에 따라 다르지만 대략 위와 같은 비슷하게 출력 결과가 표시될 것이다. 어쩐지 본 적이 있는 폴더가 나열되어 있지 않은가? Windows 사용자 폴더이다.
이 c/Users
폴더는 Docker Machine이 자동으로 설정해주는 VirtualBox 공유 폴더이다. 공유 폴더 덕분에 Windows 사용자 폴더를 Docker 호스트의 폴더로 사용할 수있는 상태가 되어 있는 것이다. 이 공유 폴더를 예 --mount src=/c/Users/kimkc
처럼 바인딩 마운트하면 Windows 파일이 바인드 마운트된 것처럼 이용할 수 있다는 것이다.
volume 옵션
마지막으로 --volume
옵션(약어 -v)에 대해 살펴 보도록 하다.
이번에 소개한 바인딩 마운트와 대략 같은 처리를 –volume옵션을 사용하여 수행 할 수 있다. --volume
와 --mount
보다 훨씬 이전부터 존재하고 오랫동안 사용할 수있는 옵션이다. 여기에서는 설명하지 않겠지만, --volume
옵션의 샘플 코드와 해설 기사가 다수 존재하기 때문에 검색 해 보면 좋을 것이다.
그런데 Docker사로는 앞으로 마운트 처리를 배우는 사람은 더 다루기 쉬운 --mount
옵션을 이용할 것을 권장하고 있다. 필자는 --mount
옵션의 사용법을 기억하는 경우 Docker Compose를 사용하여 여러 컨테이너 배포 페이지에서 소개한 Docker Compose 및 클러스터링 기능의 Swarm
, 그리고 --volume
옵션을 이해하기 쉬울 거라 생각한다. 따라서 처음에는 --mount
옵션에서 학습 할 것을 권장한다.
결론
이번에는 Docker 바인딩 마운트 기능에 대해 설명하였다. 또한 바인딩 마운트의 구체적인 예로서 Java Hello World 프로그램을 컴파일하고 실행하는 방법을 소개하였다.
바인드 마운트는 컨테이너의 특징 인 고립을 굳이 내려 컨테이너를 편리하게 이용할 수 있도록하는 기능이라고 할 수 있다. 컨테이너의 장점을 크게 해치지 않도록주의하면서 바인딩 마운트를 잘 이용하여 컨테이너를 활용할 수 있도록 하자.