Docker는 Virtual machine이 아니다!

처음 docker를 실행할 때 다음과 같은 명령을 실행하면 우분투 서버가 실행된다고 생각했다. 즉,  Virtual machine과 같이 컨테이너 내에 우분투 서버가 실행되는 줄 알았다.

 

$ docker run --name ubuntu_test ubuntu

 

위 명령을 실행하면 그냥 아무것도 실행하지 않은 것처럼 아무런 변화가 없다.

다만 다음과 같이 실행되지 않는 docker container를 보는 옵션(-a)을 주고 docker container의 목록을 보면 종료(Exit)되었다고 나타나기는 한다.

$ docker ps -a
d8f31b2635d9    ubuntu    "/bin/bash"   19 seconds ago  Exited (0) 17 seconds ago   ubuntu_test

필자의 경우 여기서부터 혼란스러웠다.

우분투 image를 실행했는데 왜 아무것도 실행되지 않고 바로 Exit 되었을까?

 

결론은 Docker의 컨테이너는 Virtual machine과 같이 하나의 온전한 서버를 제공하는 것이 아니라 명령을 실행하는 환경만 제공하고 그 명령을 실행할 뿐이다

위의 예제에서 보면 "docker ps -a" 명령으로 나타난 컨테이너 목록에서 다음과 같은 내용을 볼 수 있다.

 

이 문구로 유추해보면 우분투 컨테이너를 실행하면 우분투 서버가 실행되는 것이 아니라 "/bin/bash" 가 실행되는 것 뿐이다. 이것이 Virtual machine의 컨테이너와 Docker 컨테이너의 가장 큰 차이점이다. 일반적으로 Linux 서버(Ubuntu or CentOS 등)나  Windows와 같은 운영 체제를 실행한다는 의미에는 많은 것을 내포하고 있다. 대략 다음과 같은 기능들이 실행될 것이다.

 

  • 프로그램 실행 기능
    • Memory, CPU 등의 하드웨어 자원을 이용하여 프로그램을 실행할 수 있는 환경 제공
  • 네트워크 서비스 제공
    • NIC 등을 하드웨어 자원을 인식해서 네트워크 처리가 가능한 환경을 제공
  • 키보드, 모니터, 마우스 등과 같은 주변 장치의 입출력에 대한 처리
    • 사용자로부터의 입력과 결과를 출력해주는 기능 제공
  • 외부에서 접속할 수 있는 환경
    • sshd 등과 같은 데몬을 실행하여 서버 외부에서 네트워크를 이용하여 원격에서 접속할 수 있는 기능 제공

 

그리고 이 모든 것은 사용자가 임의로 전원을 끄기 전에는 지속적으로 동작하는 특징을 가지고 있다. 흔히 Virtual machine 이라고 하는 컨테이너 들은 이런 속성을 가지고 있다. 다만 여러 하드웨어 자원을 Host OS로 부터 할당 받은 것만 사용하도록 되어 있는 것이다.

그러면 컨테이너를 실행(run)하면 bash가 실행되어 prompt가 container의 bash prompt가 나타나야 하는게 정상 아닌가? 왜 Exit 되어 버리는가?


Docker 컨테이너는 단지 명령만 실행하고 그 결과만 보여주는 기능을 수행한다.

즉, 앞의 예제에서는 우분투의 docker image에서 설정된 default 실행 명령[2]인 "/bin/bash" 를 실행하고 그 결과를 출력하고 종료된 것이다. "/bin/bash" 명령은 표준 출력(STDOUT) 또는 표준 에러(STDERR)로 아무것도 출력을 하지 않기 때문에 사용자가 보기에는 실행이 안된 것과 같은 느낌으로 다가 온다. 다음을 실행해보면 무엇을 말하는 것인지 알 수 있다.

 

$ docker run --name ubuntu_test ubuntu "env"
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=52c48f363166
no_proxy=*.local, 169.254/16
HOME=/root

 

docker run 명령에서 마지막에 주는 인자 값은 이 컨테이너가 실행할 명령을 전달하는 인자이다. 우분투 컨테이너의 경우 아무런 값을 입력하지 않으면 "/bin/bash"가 실행되고, 인자를 전달하면 그 값이 실행된다. 예제에서는 "env" 명령을 실행하도록 하였는데 "env" 명령은 시스템의 환경 정보를 출력하는 명령이다. 디렉토리 목록을 출력하는 "ls" 명령도 실행할 수 있다.

 

$ docker run --name ubuntu_test ubuntu "ls"
bin
boot
dev
etc
home
lib
...

 

bash란건 쉘에 종류중 한가지이다.  ksh csh 등등 많은 쉘이 존재한다. 쉘들은 각기 기능이 다르고 모양도 다르다. 딱 짤라 쉽게 생각하자면 명령어 해석기라고 생각하시면 되겠다. 내가 쓰는 명령어를 해석해서 운영체제에게 알려주는 것이다. 이 사람이 지금 컴터를 종료하라고 한다 그걸 컴터가 알아듣는 말로 해석해 주는것이다.

 

docker 컨테이너를 실행한 다음 동일한 명령을 실행하면 다음과 같은 에러가 나타나는데 이것은 docker run 명령이 "create" 와 "start" 명령을 한번에 실행시키는 명령이기 때문에 create 시 이미 동일한 이름의 컨테이너가 존재하기 때문에 발생하는 문제이다. 이 경우 docker rm <id or name>으로 삭제한 후 다시 실행하면 된다.

docker: Error response from daemon: Conflict. The container name "/ubuntu_test" is already in use by container "065b413106d50d69b01077daccc5f7c1c406d98f993c71e6834d6e22318b93e4". You have to remove (or rename) that container to be able to reuse that name. See 'docker run --help'.

우분투의 bash shell에서 명령을 실행하려면

위 예제에서 Docker run  명령행이 아닌 우분투 image의 bash shell에서 "ls", "cat" 등과 같은 명령을 실행하려면 어떻게 해야 할까? Docker 컨테이너를 실행할 때 다음 두 옵션을 추가하면 가능한데 대략 다음과 같은 의미이다.

  • i : Interactive 모드로 표준입력과 표준출력을 키보드와 화면을 통해 가능하도록 하는 옵션이다.
  • t:  텍스트 기반의 터미널(TTY)을 애뮬레이션해주는 옵션이다.
$ docker run -it --name ubuntu_test ubuntu

 

이 부분에서 Docker를 처음 접하는 개발자는 또 한번 혼란스러움을 겪을 수 있다. 위 예제와 같이 shell 이 나오고 내가 필요한 명령이 사용 가능하게 되면 마치 우분투 서버가 실행되었다는 착각을 하게 된다. 그리고 해당 shell에서 tomcat이나 rails와 같은 애플리케이션 서버를 설치하고, 실행해본다. 잘 돌아간다. 문제는 이렇게 한 다음 shell에서 "exit" 를 입력하여 shell에서 나오는 순간 컨테이너는 다시 중지된다.


Docker 컨테이너를 백그라운드로 실행하면?

여기까지 진행해보면 이런 생각을 하게 된다.

 

Docker의 컨테이너도 Host OS의 입장에서 보면 하나의 프로세스이기 때문에 프로세스가 종료(위에서는 shell에서 exit를 입력) 되면 컨테이너가 종료되겠지. 그러면 종료되지 않게 Docker 컨테이너 프로세스를 백그라운드로 실행하면 되지 않을까?

 

Docker 명령 옵션에 보면 다음과 같은 옵션이 있다.

$ docker help run
-d, --detach       Run container in background and print container ID

$ docker run -d --name ubuntu_test ubuntu
981c0ec37c33631a8625549027ca644bb064d5e00eecb4d890b011d4396dbdb9

$ docker ps -a | grep ubuntu
981c0ec37c33        ubuntu         "/bin/bash"     7 seconds ago       Exited (0) 6 seconds ago        ubuntu_test

 

"-d" 옵션이 Docker의 컨테이너를 백그라운드 프로세스로 실행하는 옵션이다.  위 예제에서 처럼 실행하면 컨테이너 ID가 출력되는데 이 ID나 --name 옵션으로 입력한 이름을 이용하여 컨테이너에 접근할 수 있다. "-d" 옵션을 주고 실행해도 docker의 컨테이너 상태를 확인해보면 "Exited" 상태인 것을 알 수 있다.

 

왜 이렇게 되는 것일까? docker 의 컨테이너를 실행한다는 것Host OS에서 프로세스를 실행하는 것과 동일한 개념이기 때문에 docker  컨테이너에서 실행되는 명령이 계속 실행되고 있는 상황이 아니면 그 명령이 종료됨과 동시에 컨테이너도 종료되기 때문이다. 이제 앞에서 실행한 interactive 모드인 "-it" 옵션을 주어 실행해보자.

 

$ docker run -d -it --name ubuntu_test ubuntu
8e7e71b7b692c7f293593d3f504bacec70e5541bae2021ebc0dbb28d9b8add21

$ docker ps -a | grep ubuntu
8e7e71b7b692        ubuntu         "/bin/bash"     2 seconds ago       Up 2 seconds      ubuntu_test

이렇게 해서 일단 1차 원하는 목적이었던 shell을 백그라운드로 실행하는데는 성공하는 듯 보였다.

 

일반적으로 Virtual machine 으로 우분투를 실행한 경우, 이 서버에 접속하려면 ssh 와 같은 리모트쉘을 이용하지만 docker 컨테이너의 경우 일반적으로는 sshd를 실행하지 않는다. 대신 Host OS에서 docker attach 명령을 이용하여 컨테이너에 접속할 수 있다. 다음 명령을 이용하여  docker의 컨테이너에 접속할 수 있다.

 

$ docker attach ubuntu_test
root@2b206e1f3c07:/#
root@2b206e1f3c07:/# ls -al
total 72
drwxr-xr-x  34 root root 4096 Dec 17 13:02 .
drwxr-xr-x  34 root root 4096 Dec 17 13:02 ..
-rwxr-xr-x   1 root root    0 Dec 17 13:02 .dockerenv

하지만 여기서도 여전히 문제가 발생한다. 이 shell에서 "exit" 명령을 이용하여 shell을 나오게 되면 컨테이너도 같이 종료하게 된다.

 

root@2b206e1f3c07:/# exit
$ docker ps -a | grep ubuntu
8e7e71b7b692        ubuntu         "/bin/bash"     5 seconds ago       Exited (0) 5 seconds ago       ubuntu_test

이것은 run -it 옵션과 attach 명령의 내용을 조금만 보면 예측할 수 있다.

 

"-it" 옵션은 컨테이너의 입출력을 interactive 하게 하는 옵션과 TTY 터미널을 애뮬레이션 해주는 옵션이다. 이것을 백그라운드로 실행시킨 것이다. 그리고 attach 명령은 Virtual machine의 리모트쉘 접속과 같은 개념이 아니라 컨테이너의 현재 Host OS shell(local)의 stdout, stderr을 docker 컨테이너에 붙이는 명령인 것 뿐이다.

 

-it 옵션을 이용하여 interactive 모드로 실행하고 이것을 다시 -d 옵션을 주어 백드라운드로 실행하게 되면 interactive 쉘이 백그라운드로 동작하고 있는 것이다. 여기에 attach 명령으로 접속하여 exit 명령을 실행하면 interactive 쉘(/bin/bash)이 종료되고 이 쉘이 종료되면 결국 docker 컨테이너도 종료하게 되는 것이다.


Docker에 애플리케이션 서버 실행하기

여기까지 확인한 상태에서 필자가 내뱉은 한마디는 "이런 황당한 시츄에이션이! 이런 방식이면 어떻게 애플리케이션 서버를 실행하냐고?" 였다. 하지만 정답은 위에서 이미 다 나와 있었다. Docker의 컨테이너에서 실행되는 명령(위 예제에서는 /bin/bash)을 영원히 실행되게 하면 된다. 예를 들어 다음과 같이 컨테이너를 실행한다.

 

$ docker run -d --name ubuntu_test ubuntu /bin/bash -c "while true; do echo "still live"; sleep 100; done"
eb3b9e69b18d826dcc8788fc01930b4c411dabee4cbdfb646af79cb2cfbeacba

$ docker ps -a | grep ubuntu
eb3b9e69b18d        ubuntu             "/bin/bash -c 'whi..."   7 seconds ago       Up 8 seconds       ubuntu_test

 

위 실행 옵션은 /bin/bash를 실행하면서 -c 옵션에 있는 명령을 실행하게 하는 것이다.

 

-c 에 있는 옵션은 무한 루프를 돌면서 100초마다 한번씩 "still live"를 출력하는 기능을 수행하는 shell script 이다. 이렇게 무한 루프를 도는 명령을 -d 옵션을 이용하여 백드라운드로 실행하기 때문에 컨테이너 실행 후 바로 Host OS의 Prompt로 돌아오지만 컨테이너는 여전히 살아 있는 것을 확인할 수 있다.

 

이 컨테이너에 이제 attach 해보자. attach 명령을 실행해보면 아무 반응도 없고 100초마다 "still live"만 출력하는 것을 볼 수 있다. 그렇다고 shell prompt가 나타나지도 않는다. 이것은 attach 명령어가 stdout, stderr을 가져오는 것이기 때문에 당연한 것이다. Shell에 접속하기 위해서는 attach가 아닌 exec 명령을 이용해서 컨테이너의 쉘 환경에 접속할 수 있다.

 

출처

 

 

'🚦 Server > Docker' 카테고리의 다른 글

Docker - tutorial  (0) 2021.01.18
Docker - Dockerhub에 image 올리기  (0) 2021.01.13
Docker - container 제거하기  (0) 2021.01.13
Docker 로 python 실행하기  (0) 2021.01.08
Docker - image가 저장되는 방식  (0) 2021.01.08
복사했습니다!