아래 링크의 해당 내용을 정리한 내용임을 밝힙니다.
https://www.44bits.io/ko/post/is-docker-container-a-virtual-machine-or-a-process
도커(Docker) 컨테이너는 가상 머신인가요? 프로세스인가요?
도커 컨테이너는 가상 머신과 비슷한 특징을 가지고 있습니다. 독립적인 파일 시스템 환경을 가지고 있고, 프로세스 아이디도 호스트와 다르고, 네트워크도 격리되어 있습니다. 하지만 분명 가
www.44bits.io
0. 도커 컨테이너와 가상머신의 차이점
이번에는 도커 컨테이너와 프로세스가 어떻게 다른지 알아보겠습니다.
도커 컨테이너와 가상머신의 차이에 대해 먼저 정리해보자면, 가상머신은 운영체제 위에 하드웨어를 에뮬레이션하고 그 위에 운영체제를 올리고 프로세스를 실행하는 반면에, 도커 컨테이너는 하드웨어 에뮬레이션 없이 리눅스 커널을 공유해서 바로 프로세스를 실행한다는 차이점이 있습니다.
그렇다면 진짜 도커 컨테이너가 프로세스인지 표면적인 차이를 통해 확인해보도록 하겠습니다.
1. 도커 컨테이너가 사용하는 커널과 파일 시스템 확인하기
도커는 "PYCON 2013 US" 에서 솔로몬 하이크의 라이트닝 토크로 처음 공개되었습니다.
리눅스 컨테이너의 미래라는 제목을 가진 발표에서 솔로몬 하이크는 docker라는 명령어를 통해 hello world를 출력하는 데모를 보여줍니다.
지금도 이를 따라해볼 수 있습니다. -a 만 제외하고 실행하면 최소한 보기에는 동일하게 동작합니다.
※ -a를 포함하고 실행하면 아래와 같이 오류가 발생합니다.
위와 같은 hello world는 아래와 같이 echo를 통해서도 출력이 가능하기도 합니다.
출력된 결과를 살펴보면 정말 docker를 통했을 경우와 그렇지 않을 경우, 정말 아무런 차이가 없습니다.
오히려 도커로 실행하면 처음에 도커 컨테이너가 실행되는 환경(=파일시스템)인 busybox의 이미지를 풀 받는 시간이 걸리기 때문에 오히려 느리게 실행됩니다. 이러한 과정이 필요한 이유는 도커로 실행되는 컨테이너는 위에서 명시한 busybox라는 환경에서 실행되기 때문입니다. 하지만 반면 아래와 같은 echo hello world 명령어를 실행하는 경우 호스트 환경(루트 파일 시스템)에서 프로세스가 실행된다는 차이점이 있습니다.
간단한 예를 통해서 도커 컨테이너들이 서로 다른 환경에서 실행되고 있다는 것을 확인해보겠습니다.
먼저 ubuntu:lastest 이미지에서 커널과 배포판 정보를 확인해보겠습니다.
다음은 centos:latest 이미지에서 확인해보겠습니다.
각각의 컨테이너 안에서 총 2개의 명령어를 실행시켜보았습니다.
먼저 cat /etc/*-release 파일을 출력한 결과를 통해 배포판(파일시스템)이 다르다는 것을 확인 할 수 있습니다. 위에서 실행한 2개의 도커 컨테이너는 분명히 다른 환경에서 실행되고 있습니다.
그리고 uname -a의 출력 결과가 동일한 점 역시 흥미롭습니다. 서로 다른 환경에서 실행되었지만 커널이 같다는 것에 주목해주세요. 위의 경우는 아래와 같은 우분투 22.04.01가 설치된 윈도우의 cmd에서 실행되었습니다.
WSL을 통해 호스트의 커널을 확인해보겠습니다.
호스트 이름만 다른뿐 위의 커널이 거의 똑같다는 것을 확인할 수 있습니다.
마지막으로 windows의 커널과 docker를 실행시켜 확인해보겠습니다.
먼저 호스트 윈도우에서 ver을 통해 커널을 확인해보면 Microsoft Windows가 출력되는 것을 알 수 있습니다. 하지만 docker를 실행하면 5.10.16.3-microsoft-standard-WSL2 커널이 나타납니다. 위의 링크의 글에서는 맥OS에서 실행시켰습니다. 맥OS처럼 윈도우 역시 리눅스가 아니므로 네이티브하게 사용할 수 없습니다. 따라서 도커 데스크탑은 리눅스킷 기반 경량 가상머신 위에서 도커를 사용합니다. 여기서 다시 한 번 확인할 수 있었던 점은 데스크탑과 컨테이너에서 실행한 커널이 다르다는 점입니다!
여기까지 확인한 내용을 정리하면 다음과 같습니다.
1. 컨테이너는 호스트 시스템의 커널을 사용한다.
2. 컨테이너는 이미지에 따라서 실행되는 (파일 시스템)이 달라진다.
2. 도커 컨테이너의 프로세스 아이디는 1번
도커 컨테이너는 가상머신이 아니고, 프로세스다 라는 얘기를 들어보셨을 수도 있습니다. 도커 컨테이너의 PID를 생각해보신적 있으신가요? 이번에는 우분투 이미지에서 실행한 bash 프로세스의 PID를 확인해보겠습니다. 셀의 PID를 확인하려면 $$를 실행해보면 됩니다.
윈도우라서 echo $$로 PID를 확인할 수 없기에 확인하기 위해서는 [작업관리자 - 리소스 모니터 열기] 를 통해서 System이 어떤 PID를 인지 확인할 수 있습니다. (윈도우의 경우 : PID 4가 다른 OS의 1번)
먼저 호스트 상에서의 PID는 4입니다. 하지만 컨테이너로 실행한 bash 셀의 PID는 1번입니다. 일반적으로 리눅스에서 1번 프로세스는 init 프로세스로 특별한 의미를 가지고 있습니다. 도커 컨테이너 안에서 pstree를 실행해보면 정말로 1번 프로세스 bash인것을 확인할 수 있습니다.
컨테이너는 프로세스지만, 프로세스라고 부르기보다는 컨테이너라고 부르는 데는 이유가 있는 법입니다. 컨테이너는 주로 리눅스 커널에 포함된 프로세스 격리 기술들을 사용해서 생성된 특별한 프로세스 입니다. 처음 도커를 보면 가상머신이라고 생각하게 되는 것은 이런 이유 때문입니다. 예를 들어 앞선 예제에서 bash 명령어로 마치 컨테니어라는 가상머신에 접속해서 명령어를 실행하는 것처럼 느껴지지만, 사실 bash는 가상머신에 접속하는 명령어가 아니라 그냥 호스트 상에서는 프로세스일 뿐입니다. 그런데 PID가 1인 특별한 bash 프로세스입니다. 어떻게 호스트 프로세스가 두 개나 존해할 수 있을까요?
일반적인 프로세스와 달리 1번 프로세스는 내부적으로 SIGKULL 시그널로 죽일 수도 없습니다.
3. PID 1번의 비밀, 도커 없이 일반 프로세스으 PID를 1번으로 실행하기
※ 여기부터 아래의 사진은 위 링크의 글의 자료입니다.
어떻게 이게 가능한 걸까요? 이를 구현하는데 사용된 기능이 바로 리눅스 네임스페이스입니다. 정확히는 네임 스페이스가 분리되어있기 때문에 도커 컨테이너의 프로세스는 1번이 됩니다. 도커 없이도 리눅스에서는 unshare 명령어로 프로세스의 PID 네임스페이스를 분리해볼 수 있습니다.
여기서 unshare는 특정 프로세스를 실행할 때 특정 네임스페이스를 분리해주는 기능입니다. 여기서 특정 네임스페이스라고 하는 이유는 네임스페이스에도 UTS, PID, 네트워크, 마운트 등 다야한 종류가 있기 때문입니다. 그 중에서 여기서 분리할 네임스페이스는 PID 네임스페이스로, -p 옵션을 지정해주면 됩니다. 하지만 이것만으로는 잘 되지 않습니다.
분명 PID 네임스페이스는 분리가 되었을 텐데, PID가 1번이 아닙니다. 왜냐하면 unshare 명령어를 사용하더라도 호스트 파일 시스템을 그대로 사용하고 있고 PID 관련 정보는 /proc 아래에 있는 정보를 사용하기 때문입니다. 그렇기에 PID 네임스페이스가 분리되지 않은 것처럼 보입니다. unshare 명령어는 --mount-proc으로 /proc을 분리하는 기능을 지원합니다. 하지만, 호스트의 /proc에 마운트를 시도하므로 당연하게도 작동하지 않습니다.
PID 네임스페이스를 제대로 분리하기 위해서는 독자적인 환경이 필요합니다. 하지만 루트 디렉터리를 바꿔서 프로세스를 실행한다는 게 생각보다 간단한 일이 아닙니다. 독자적인 환경에는 프로세스를 실행하기 위한 모든 파일들이 준비되어 있어야합니다. 이는 직접 준비하는 것도 가능하지만, 도커 이미지를 복사해오는 게 가장 간단합니다. 그래서 여기서는 docker를 사용해 busybox 이미지 내용을 그대로 복사하고, chroot를 사용해 실행 환경(파일시스템)을 격리해 셸을 실행했습니다.
busybox 이미지의 내용을 복사한 디렉터리를 기반으로 chroot를 실행해서 프로세스 실행 경로를 분리하고 unshare의 --mount-proc 옵션으로 /proc을 마운트해주면, 프로세스 아이디를 1번으로 실행이 가능해집니다.
4. 2개의 PID: 프로세스가 바라보는 프로세스 ID, 호스트가 바라보는 프로세스 ID
여기서는 PID 네임스페이스에 대해서 좀 더 알아보겠습니다. 앞에서 살짝 힌트가 나왔습니다만, 단순히 PID를 분리해서 셀을 실행하니 PID가 1이 아니라 87이라는 일반 프로세스 값이 되었습니다.
결론부터 이야기하자면, 1과 87은 둘 다 맞습니다. 여기서는 잘 확인히 안되지만, 이 프로세스의 아이디는 1이기도 하고, 87이기도 합니다. 그럼 다시 한 번 unshare로 PID 네임스페이스를 분리해보겠습니다.
PID가 1번 입니다. 위의 프로세스를 그대로 놔두고, 호스트의 셸을 하나 더 띄워서 ps로 최근에 실행된 프로세스들을 확인해봅시다.
위와 같이 unshare(2386) 명령어가 보입니다. 그리고 그바로 아래 /bin/sh(2387)이 보입니다. 이때 해당 프로세스인 2387을 강제로 kill 합니다.
이렇게 된다면 2387 프로세스를 죽였는데 unshare로 실행된 sh 종료되게 됩니다. 이는 둘이 같은 프로세스라는 의미입니다. 즉, 프로세스에게는 자신의 PID가 1번으로 보이지만, 호스트 입장에서는 PID가 2387인 일반적인 프로세스라는 의미입니다.
5. 프로세스 ID를 격리하는 PID 네임스페이스
모든 프로세스는 PID 네임스페이스를 가지고 있습니다. 리눅스 시스템에 대해서 알고 계신분이라면, /proc 디렉터리에 시스템과 프로세스에 대한 정보가 담겨져있다는 것을 알고 계실 것 입니다.
숫자로 된 위와 같은 디렉터리들에는 특정 프로세스에 대한 정보들이 담겨 있습니다. 여기서 1번 프로세스는 systemd입니다. 또한 모든 프로세스 디렉터리 아래에는 ns라는 디렉터리가 있는데 여기서 ns는 네임스페이스를 줄인 단어입니다. 1번 프로세스 아래의 ns 디렉터리의 내용을 살펴보겠습니다.
앞에서 네임스페이스는 PID 뿐만이 아니라라 UTS, 네트워크, 마운트 등 다양한 네임스페이스가 있다고 했습니다. 이러한 점을 여기서도 확인할수 있습니다. 다양한 네임스페이스 중 PID 네임스페이스를 자세히 살펴보게되면 'pid:[4026531836]'d에 10자리 숫자가 적혀있는 것을 알 수 있습니다. 바로 이 숫자가 현재 프로세스의 PID 네임스페이스를 의미합니다. systemd는 4026531836 PID 네임스페이스의 첫번째 프로세스이며 PID는 1번 입니다. 기본적으로 모든 프로세스는 init 프로세스의 네임스페이스를 그대로 공유하게 됩니다.
다른 프로세스의 PID 네임스페이스를 출력하면 역시 대부분 1번 프로세으의 값과 같은 것을 확인 할 수 있습니다. unshare을 사용하면 PID 네임스페이스가 정말로 분리가 되는지, 다시 한 번 위에서의 과정읕 통해 확인해보겠습니다.
우리가 확인하고 하는 프로세느는 unshare 바로 아래에 있는 /bin/sh 프로세스 입니다. PID는 2682입니다. /proc/2682/ns/pid의 내용을 확인해보겠습니다.
1번 프로세스의 PID 네임스페이스는 40265301836 이지만, 2682번 프로세스의 PID 네임스페이스는 4026532296입니다.
즉, 값이 다르므로 다른 네임스페이스 라는 것을 알 수 있습니다.
6. 도커 컨테이너는 정말 프로세스인가요?
도커 컨테이너의 PID가 1번인 원리도 위에서 살펴본 것과 동일합니다. 이번에는 도커 컨테이너를 찾아가보겠습니다. 먼저 실험용으로 Ngnix 컨테이너를 하나 실행하겠습니다.
curl 명령어로 정상 동작하는지도 살펴보았습니다. 그럼 프로세스를 찾기에 앞서 프로세스 아이디가 1번인지 부터 확인해보겠습니다. nginx:latest 이미지에는 ps도 없어서 확인이 쉽지는 앖습니다만, /pro/1의 명령어를 확인해보겠습니다.
1번 프로세스의 명령어가 ngnix인 것을 확인할 수 있습니다. 이제 컨테이너 = 프로세스 에 대해서 알아보겠습니다.
앞서 실행한 nginx 컨테이너로 추정되는 디렉터리가 보입니다. 컨테이너 아이디와 앞부분이 같습니다. 이 디렉터리 안에는 state.json 파일이 하나 있습니다. 내용이 꽤 많아서 여기서는 jq로 필요한 부분만 추출해보겠습니다
*jq는 커맨드라인에서 JSON 파일 내용을 바로 프로세싱할 수 있는 도구 입니다. 우분투에선 apt instlal jq로 설치할 수 있습니다.
init_process_pid의 내용을 보니 프로세스 아이디가 2899인 것을 확인할 수 있습니다. 여기에는 더 많은 정보들이 있습니다. 그 중에서도 네임스페이스에 관련된 정보들을 한 번 살펴보겠습니다.
네임스페이스들이 /proc/2899 아래에 새로 정의되어있다는 것을 확인할 수 있습니다. 이는 호스트에서 확인할 수 있습니다. 먼저 1번 프로세스와 2899 프로세스의 네임스페이스들을 비교해보겠습니다.
ipc, mnt, net, pid, pid_for_children, uts 네임스페이스가 다르다는 것을 확인할 수 있습니다. 네임스페이스는 다르지만 ps로 일반적인 프로세스라는 것을 한 번 확인해보겠습니다.
이렇게 보면 그냥 nginx 프로세스네요. 이 2899 프로세스가 nginx:latest 이미지로 실행된 컨테이너라는 것을 어떻게 확인할 수 있을까요? 위에서 했던 것 처럼 kill로 SIGKILL 시그널을 보내보면 됩니다!
먼저 docker ps로 컨테이너가 잘 실행중인 것을 확인하고, kill을 실행하고 다시 docker ps -al로 컨테이너의 상태를 확인해보았습니다. 위에서 실행중이던 컨테이너가 SIGKILL 시그널을 받고 사망한 것을 확인할 수 있습니다. ps로 찾아보아도 더 이상 2899 프로세스는 보이지 않습니다.
이제 확실하게 알 수 있게 되었습니다! 컨테이너는 프로세스에 해당합니다. 좀 더 정확히는 호스트 입장에서는 컨테이너도 그냥 하나의 프로세스에 불과합니다.
'IT > 컨테이너 & 오케스트레이션' 카테고리의 다른 글
[대세는 쿠버네티스] 섹션 6. [중급편] 기본 오브젝트 (0) | 2023.02.09 |
---|---|
[컨테이너&오케스트레이션] 컨테이너와 오케스트레이션 (2) | 2022.10.08 |