만쥬의 개발일기
article thumbnail

본 포스트는 Docker에 대해 더 이해하기 위한 글입니다.

개요

도커는 컨테이너 기반의 오픈소스 가상화 플랫폼이다.

서비스 관리자는 도커라이징(Dockerizing) 된 컨테이너가 어떤 런타임을 필요로 하는지, 어떤 라이브러리와 코드를 필요로 하는지 전혀 알 필요가 없다.

다시 말해, 도커를 사용하면 OS 환경설정, 언어, 라이브러리, 시스템 도구 등이 설치된 환경을 그대로 이미지로 빌드할 수 있다. 그저 컨테이너를 어딘가에서 가져와서 서비스를 운영할 컴퓨팅 환경에서 실행하기만 하면 된다. 실행된 서비스는 컴퓨팅 환경과 독립된 가상의 환경에서 실행되며, 일관된 결과를 보장한다.

Docker는 VS Virtual machine

혼동하기 쉽지만, Docker는 virtual machine과는 아예 다른 개념이다.

왼쪽은 도커의 구조를, 오른쪽은 VM의 구조를 나타낸다.

가장 큰 차이점은 하이퍼바이저 (Hypervisor) 의 유무이다.

간단히 짚고 넘어가자면, 하이퍼바이저란 Virtual Box 나 VM Ware 와 같은 가상 머신을 생성하고 실행하는 프로세스이다. 또한 하드웨어를 에뮬레이션하여 하나의 컴퓨터에서 다수의 운영체제를 운영할 수 있게 해주는 소프트웨어이다.

이해를 돕기 위한 예시

만약 우분투 이미지를 다운받아, 컨테이너로 실행시켰다고 생각해보자.

$ docker run --name ubuntu_test ubuntu

만약 도커가 virtual machine이라면, 컨테이너는 우분투 서버를 실행시키고 있어야 한다.

그러나 컨테이너는 바로 종료되었고, 종료된 컨테이너 목록에 Exit status로 남은 것을 확인할 수 있다.

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

 

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

 

/bin/bash 8 seconds ago

 

이 문구로 유추해보면 우분투 컨테이너를 실행하면 우분투 서버가 실행되는 것이 아니라,

우분투에서 /bin/bash 가 실행되는 것 뿐이다.

 

일반적으로 Linux 서버(Ubuntu or CentOS 등)나 Windows와 같은 운영 체제를 실행한다는 의미에는 많은 것을 내포하고 있다. 대략 다음과 같은 기능들이 실행될 것이다.

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

그리고 이 모든 것은 사용자가 임의로 전원을 끄기 전에는 지속적으로 동작하는 특징을 가지고 있다. 흔히 Virtual machine 이라고 하는 컨테이너 들은 이런 속성을 가지고 있다.

다만 여러 하드웨어 자원을 Host OS로 부터 할당 받은 것만 사용하도록 되어 있는 것이다.

 

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

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

 

즉, 앞의 예제에서는 우분투의 docker image에서 설정된 default 실행 명령인 "/bin/bash" 를 실행하고 그 결과를 출력하고 종료된 것이다.

 

"/bin/bash" 명령은 표준 출력(STDOUT) 또는 표준 에러(STDERR)로 아무것도 출력을 하지 않기 때문에 사용자가 보기에는 실행이 안된 것과 같은 느낌으로 다가 온다.

 

그렇다면 STDOUT 이벤트를 발생시키는 ls 옵션을 실행 인자로 두고, 다시 우분투를 실행해보자.

(실행 전에 docker ps -a로 이전에 만든 컨테이너를 지워주는 것을 잊지말자)

이번에는 우분투의 home 경로에서 ls명령을 실행하고, 마찬가지로 컨테이너가 죽었다.

그리고 command로 ls를 주었다는 것도 docker ps -a 를 통해 확인 가능하다.

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

컨테이너의 bash shell에서 명령 실행하기

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

  • i : Interactive 모드로 표준입력과 표준출력을 키보드와 화면을 통해 가능하도록 하는 옵션이다.
  • t: 텍스트 기반의 터미널(TTY)을 애뮬레이션해주는 옵션이다.
  • it(-i + -t)옵션은 컨테이너 내부로 진입하도록 attach 가능한 상태로 설정한다.

실행은 다음과 같이 한다.


$ docker run -it --name ubuntu_test ubuntu
# 다음과 같이 ubuntu의 bash shell로 prompt가 나타나고 해당 container 내에서 명령을 실행할 수 있다.
root@7a08aa924dcd:/# df -k
Filesystem     1K-blocks    Used Available Use% Mounted on
none            61896484 9147280  49581972  16% /
tmpfs              65536       0     65536   0% /dev
tmpfs            1023376       0   1023376   0% /sys/fs/cgroup
/dev/vda2       61896484 9147280  49581972  16% /etc/hosts
shm                65536       0     65536   0% /dev/shm
tmpfs            1023376       0   1023376   0% /sys/firmware

이 부분에서 Docker를 처음 접하는 개발자는 또 착각을 할 수 있다.

 

위 예제와 같이 shell 이 나오고 내가 필요한 명령이 사용 가능하게 되면 마치 우분투 서버가 실행되었다는 착각을 하게 된다. 그리고 해당 shell에서 tomcat이나 rails와 같은 애플리케이션 서버를 설치하고, 실행해본다. 잘 돌아간다.

 

문제는 이렇게 한 다음 shell에서 "exit" 를 입력하여 shell에서 나오는 순간 컨테이너는 다시 중지된다.

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

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

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

$ docker run -d --name ubuntu_test ubuntu                                                      
a7d80d6ce8c239df29a4c88f5e2a9866d116d85ee5c4da2552eb968cbd82e944

$ docker ps -a | grep ubuntu                                                                   
a7d80d6ce8c2   ubuntu                          "/bin/bash"              10 seconds ago   Exited (0) 8 seconds ago

"-d" 옵션은 Docker의 컨테이너를 백그라운드 프로세스로 실행하는 옵션이다.

그러나 백그라운드로 실행 후 docker의 컨테이너 상태를 확인해보면 "Exited" 상태인 것을 알 수 있다.

 

왜 이렇게 되는 것일까?

docker 의 컨테이너를 실행한다는 것은 Host OS에서 프로세스를 실행하는 것과 동일한 개념이기 때문이다.

 

-d 옵션은 명령을 백그라운드에서 실행할 뿐이지, docker 컨테이너에서 실행되는 명령이 계속 실행되고 있는 상황이 아니면 그 명령이 종료됨과 동시에 컨테이너도 종료되기 때문이다.

 

이제 앞에서 실행한 interactive 모드인 "-it" 옵션을 주어 실행해보자.


~/daehoon  docker run -d --rm -it --name ubuntu_test ubuntu                                                                                                                            ok
220faf710458aa8dc70486cc4db5d3c245ea820f4efbab73595d54aab7a813ed

 ~/daehoon  docker ps                                                                                                                                                                   ok
CONTAINER ID   IMAGE                           COMMAND       CREATED         STATUS         PORTS     NAMES
220faf710458   ubuntu                          "/bin/bash"   3 seconds ago   Up 2 seconds             ubuntu_tes

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

 

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

~/daehoon  docker attach ubuntu_test                                                                                                                                                   ok
root@220faf710458:/# ls
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

 

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

 

이것은 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의 컨테이너 내에 애플리케이션 서버를 실행하려면 애플리케이션 서버가 무한루프로 동작하게 해야 한다.

 

하지만 이미 애플리케이션 서버들은 무한루프로 동작하는 프로그램들이다.

따라서 다음 내용만 주의하면 된다.

 

Docker 컨테이너에서 실행되는 애플리케이션 서버(DB 서버 포함)은 back ground 모드가 아닌 fore ground 모드로 실행해야 한다.

 

애플리케이션 서버의 경우 일반적으로 백그라운드 모드로 동작하게 한다. 이유는 서버를 실행시킨 shell이 종료되더라도 tomcat 서버는 정상적으로 계속 동작하게 하기 위해서이다.

 

하지만 docker 환경에서 이렇게 하면 컨테이너가 바로 종료되어 Tomcat 서버가 죽는 것과 동일한 상황을 맞게 된다. 따라서 fore ground로 실행해야 한다.

프로그램에서 출력하는 로그는 어떻게 해야 하나?

보통 서버의 로그는 파일에 저장하도록 설정한다.

 

로그를 파일에 저장하고 롤링하는 방식을 사용하는데, 이는 백그라운드로 서버를 실행할 경우 파일로 저장하는 것이 관리하기 편하기 때문이다.

 

Docker 컨테이너에서 애플리케이션 서버를 실행할 때 로그 설정은 다음 세 가지 방안을 생각할 수 있다.

  1. 이전과 동일하게 파일로 저장한다. 이것은 컨테이너의 특정 디렉토리에 저장하게 한다는 의미인데 이렇게 할 경우 해당 컨테이너가 삭제되면 로그도 같이 삭제되기 때문에 권장하지 않는다.
  2. 모든 로그를 표준 출력(STDOUT) 또는 표준 에러(STDERR) 로 출력한다.이 방식도 문제가 존재하는데 이 로그 파일을 하나의 파일로 관리하게 되면 파일이 너무 커지게 되어 스토리지를 모두 차지하게 되는 문제가 있다. 최근 버전의 Docker에서는 로그 파일을 롤링할 수 있는 기능을 제공하는데 이 방식을 사용할 경우 반드시 이 옵션을 사용하는 것을 권장한다. Docker 는 컨테이너에서 STDOUT나 STDERR로 출력하는 모든 메시지를 Host OS의 특정 디렉토리에 저장하고 이를 쉽게 조회할 수 있는 명령도 제공한다(docker logs 명령). 따라서 모든 로그를 표준 출력으로 보내면 쉽게 로그에 접근할 수 있게 된다.
  3. 1번과 같이 파일에 로그를 저장하지만 로그 디렉토리를 Host OS의 볼륨을 이용한다. 이렇게 하면 1번의 로그가 삭제되는 문제와 2번의 롤링 문제를 해결할 수 있지만 컨테이너 생성 시 볼륨을 붙여줘야 한다.

 

 

reference

profile

만쥬의 개발일기

@KangManJoo

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!