Docker 입문 | 7. 바인드 마운트 Hello Java


이번에는 Docker 바인딩 마운트 기능에 대해 설명한다. 또한 바인딩 마운트의 구체적인 예로서 Java Hello World 프로그램을 컴파일하고 실행하는 방법을 소개한다.

Docker 마운트 기능

‘마운트’라는 용어는 Linux 나 Windows 등 OS의 용어로 아시는 사람이 많을 것이다. 예를 들어 “하드 디스크를 탑재하는 것"이라고 말했다면 OS가 하드 디스크를 인식하고 사용할 수 있는 상태로 만드는 것을 의미한다.

Docker에서 “마운트"는 컨테이너 외부에있는 데이터를 컨테이너에서 사용할 수 있는 상태가 될 것을 의미한다. 207년 말에 공개된 “Docker v17.12.0-ce"는 다음의 세 가지 마운트 기능이 제공되고 있다.

  1. 바인드 마운트 (bind)
  2. 볼륨 마운트 (volume)
  3. 임시 파일 시스템 마운트 (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/dockerHello.java도 함께 마운트되었다. src 대신에, source 라고 써도 같은 의미이다.
  • dst=/home/test
    • 마운트 Docker 호스트 디렉토리, 컨테이너 속에서 경로이다. 컨테이너 안에 /home/test 라는 경로에서 위에 src에 지정된 /home/docker를 사용할 수 있다. /home/test 디렉토리는 원래의 용기 안에 존재도 존재하고 있지 않아도 괜찮다. dst 대신에 destinationtarget라고 써도 같은 의미이다.

바인드 마운트

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 프로그램을 컴파일하고 실행하는 방법을 소개하였다.

바인드 마운트는 컨테이너의 특징 인 고립을 굳이 내려 컨테이너를 편리하게 이용할 수 있도록하는 기능이라고 할 수 있다. 컨테이너의 장점을 크게 해치지 않도록주의하면서 바인딩 마운트를 잘 이용하여 컨테이너를 활용할 수 있도록 하자.