Skip to content

가상화와 하이퍼바이저, 그리고 도커

도입

애플리케이션을 배포할 때 Dockerfile이나 docker-compose.yml 파일을 사용하는 걸 자주 보게 됩니다. 저 또한 GitHub Actions 또는 CircleCI 등을 활용하여 CI/CD를 구축할 때 도커를 사용하여 AWS에 애플리케이션을 배포하고는 했는데 도커를 왜 사용해야 하는지, 어떻게 하면 여러 애플리케이션에 대한 모니터링 등의 관리를 손쉽고 효율적으로 할 수 있을지 의문이 들었습니다.

도커를 통해서 애플리케이션을 컨테이너로 만들면 쿠버네티스(Kubernetes)를 통해 해당 애플리케이션을, 정확하게는 컨테이너를 관리하게 되며 더 나아가 프로메테우스(Prometheus) 및 그라파나(Grafana) 등을 함께 활용하여 애플리케이션 성능 모니터링(APM_Application Performance Monitoring) 환경을 구축하고는 합니다. 궁극적으로는 여러 애플리케이션을 개발하고 배포하면서 관리하는 체계, 다시 말해 모니터링에 관해 많은 관심을 갖게 되었고 그 과정에서 우선 도커에 관해 공부해야겠다는 생각이 들어 본 글을 작성하게 되었습니다.

정보

시작하세요! 도커/쿠버네티스 책을 바탕으로 작성된 글입니다.

오늘은 그래서 도커에 관해 본격적으로 실습해보기 이전 도커가 등장한 배경이라 할 수 있는 가상화(Virtualization)와 하이퍼바이저(Hypervisor), 그리고 가상 머신(Virtual Machine)에 대해 먼저 알아보고 이후 도커(Docker)에 관해 간단하게 살펴볼 예정입니다.

가상화

도커에 대해서 알기 이전에 우선 도커가 등장한 배경을 이해할 필요가 있습니다. 그러기 위해서는 가상화(Virtualization)를 먼저 이해해야 합니다.

개념

가상화란 물리적인 공간 내에서 논리적인 공간으로 해당 공간을 분리하여 사용하는 걸 의미합니다. 조금 더 풀어서 설명하자면 하나의 운영체제(OS_Operating System)에 종속되어 여러 애플리케이션을 실행하던 방식이 아닌 각각의 애플리케이션이 독립된 공간을 가지고 다른 운영체제 위에서 실행될 수 있게 논리적인 개념으로 공간을 나눈 것을 의미합니다.

장점

가상화의 장점을 나열해보면 아래와 같습니다.

통합성(Server Consolidation)

물리적인 서버의 개수를 줄여 소수의 서버로 애플리케이션을 통합함으로써 하드웨어 공간 비용 등을 절약할 수 있습니다.

독립성(Isolation)

각 기능에 맞게 여러 개의 독립된 공간으로 분리하기 때문에 개별적인 실패(Failure) 상황에 잘 대처할 수 있습니다. 다시 말해 상호 의존적이지 않게 됩니다.

효율성(Efficiency)

컴퓨팅 자원의 사용을 최대화하고 보다 쉽게 관리할 수 있게 됩니다.

유연성(Flexibility)

한 서버의 데이터를 마이그레이션(Migration)하기 용이해집니다.

정보

여기서 마이그레이션(Migration)이란 쉽게 이동을 의미한다고 생각하면 됩니다. 다시 말해 어떤 특정 서버에 존재하던 애플리케이션을 다른 서버로 옮길 때나 어떤 데이터베이스에 존재하던 데이터들을 다른 데이터베이스로 옮기는 경우에도 마이그레이션 한다고 말합니다.

이외에도 앞서 개념을 통해서 살펴봤듯 하나의 운영체제 위에서 다양한 종류의 애플리케이션을 실행할 수 있다는 장점은 물론 새로운 애플리케이션을 프로비저닝(Provisioning)하는 데 소요되는 시간을 줄일 수 있다는 장점 등이 존재합니다.

정보

여기서 프로비저닝(Provisioning)이란 사용자의 요구에 맞게 시스템 자원을 할당, 배치, 배포하였다가 필요할 때 즉시 사용할 수 있는 상태로 미리 준비하는 것을 의미합니다.

다시 말해 위 예시에서는 애플리케이션을 사용가능한 상태로 준비하는 절차로 가상화를 사용할 경우 사용가능한 상태로 준비하는 데 소요되는 시간을 줄일 수 있다는 걸 의미합니다.

종류

정보

가상화에는 서버 가상화, 애플리케이션 가상화, 네트워크 가상화 등 정말 많은 종류가 존재하며 가상화를 실현하는 기술에도 전가상화 방식(Full Virtualization), 반가상화 방식(Para Virtualization), 그리고 컨테이너 방식(Container) 등이 존재합니다.

해당 부분을 전부 이해하기 위해서는 CPU의 동작 레벨인 링(Ring)이나 베어 메탈(Bare Metal) 등에 대해서 언급해야 하기 때문에 생략하도록 하겠습니다.

앞으로 언급할 하이퍼바이저(Hypervisor)가 곧 전가상화 방식을 의미하며 도커(Docker)가 곧 컨테이너 방식을 의미한다고 생각하면 편합니다.

과거 가상화에는 하이퍼바이저(Hypervisor)를 사용한 방식을 주로 사용했으나 최근에는 도커(Docker)를, 정확하게는 도커 엔진(Docker Engine)을 사용합니다.

정보

도커와 관련된 프로젝트에는 도커 컴포즈(Docker Compose), 도커 머신(Docker Machine) 등 다양하게 존재하지만 일반적으로 도커(Docker)라 했을 때 의미하는 건 바로 도커 엔진(Docker Engine)입니다.

도커 엔진은 컨테이너를 생성하고 관리하는 주체로 이를 통해서 컨테이너를 제어할 수 있기 때문입니다. 다른 여러 프로젝트는 이 도커 엔진을 더 편리하고 효율적으로 사용할 수 있게 제공된 방법이라 생각하면 편합니다.

하이퍼바이저

먼저 하이퍼바이저에 관해 알아봅시다. 위에서 언급한 것처럼 물리적으로 공간이 아닌 논리적인 공간으로 분리하여 사용하는 걸 가상화(Virtualization)라 합니다. 이때 가상화 기술은 주로 하이퍼바이저(Hypervisor))를 이용해 여러 개의 운영체제를 하나의 호스트에서 생성하여 사용하는 방식이었습니다.

이때 만들어진 공간을 가상 머신(VM_Virutal Machine)이라 하며 하이퍼바이저에 의해 생성되고 관리되는 운영체제는 게스트 운영체제(Guest OS)라 합니다. 각 게스트 운영체제는 다른 게스트 운영체제와는 완전히 독립된 공간과 시스템 자원을 할당받아 사용합니다.

정보

가상 머신을 만들고 관리할 수 있는 대표적인 도구로 VirtualBox, VMWare 등이 있습니다.

단점

각각의 가상 머신이 전부 하이퍼바이저를 통해 관리되기 때문에 일반 호스트에 비해서 성능에 손실이 존재할 수밖에 없습니다. 더욱이 가상 머신 자체로 게스트 운영체제를 사용하기 위한 라이브러리 및 커널을 포함하기 때문에 배포를 위한 이미지를 만들었을 때 그 크기가 무척 큽니다.

정보

여기서 커널(Kernel)이란 일종의 좁은 의미로써의 운영체제로 메모리에 상주하는 운영체제의 부분을 의미합니다.

운영체제는 컴퓨터의 전원이 들어옴과 동시에 작업을 수행하게 되는데 어떤 프로그램을 수행하기 위해서는 메모리에 그 프로그램이 올라와 있어야 합니다. 이때 모든 프로그램을 메모리에 상주할 경우 한정된 메모리 공간 내에서 낭비가 심하기 때문에 항상 필요한 부분만 메모리에 올려놓고 나머지는 필요할 때 메모리에 올리게 됩니다. 이때 메모리에 상주하는 부분, 다시 말해 운영체제의 핵심적인 역할을 담당하는 부분을 커널이라 합니다.

도커

개념

도커는 리눅스 컨테이너에 여러 기능을 추가함으로써 애플리케이션을 컨테이너로 활용할 수 있게 해줍니다. 이때 가상화된 공간을 생성하기 위해 리눅스 자체 기능인 루트 디렉토리 변경, 네임스페이스, 자원 할당 제어 등을 사용합니다. 따라서 기존 하이퍼바이저를 통해서 가상 머신을 제어하고 사용하는 것과 달리 컨테이너를 사용하는데 있어 성능 손실이 상대적으로 적습니다.

정보

여기서 컨테이너(Container)는 가상 머신(Virtual Machine)과 비슷한 개념으로 호스트 운영체제에서 하나의 논리적인 영역에 해당한다고 생각하면 됩니다. 각 컨테이너에는 커널을 제외하고 애플리케이션을 구동하는데 필요한 라이브러리 등이 존재하여 마치 하나의 서버처럼 작동합니다.

도커는 컨터이너에 필요한 커널을 호스트와 공유하여 사용하기 때문에 컨테이너 내부에는 애플리케이션 구동을 위해 필요한 라이브러리 및 실행 파일만 존재하게 됩니다. 결론적으로 이미지로 만들어 배포할 때 가상 머신과 비교하여 훨씬 빠르며 가상화된 공간을 사용할 때도 성능 손설이 거의 없게 됩니다.

간단하게 언급했던 리눅스의 자체 기능 및 명령어인 루트 디렉토리 변경, 네임스페이스, 자원 할당 제어에 관해 알아보도록 하겠습니다.

루트 디렉토리 변경

리눅스에는 루트 디렉토리를 변경하는 명령어 chroot가 존재합니다. chroot는 단어 그대로 루트 디렉토리 변경(Change Root Directory)의 줄임말로 프로세스가 실행되는 루트를 변경하는 명령어입니다. 보통 시스템에는 /라는 루트 디렉토리가 존재합니다. 따라서 모든 디렉토리 및 파일이 해당 루트 디렉토리의 하위로 존재하기 때문에 어떤 프로세스를 실행하기 위해서는 무조건 루트 디렉토리 경로를 지나가야 합니다.

하지만 chroot 명령어를 사용하여 루트 디렉토리를 변경할 경우 해당 디렉토리를 지나지 않아도 됩니다. 결론적으로 다른 디렉토리와 격리된 공간으로 분리가 되기 때문에 독립적인 환경에서 의존성을 걱정하지 않고 원하는 방식으로 애플리케이션을 구축하고 실행할 수 있습니다.

정보

chroot 명령어를 사용하는 방법은 다음과 같습니다.

chroot [OPTION] NEWROOT COMMAND

명령어 뒤에 루트로 사용할 디렉토리(NEWROOT)와 함께 해당 디렉토리를 루트로 하여 실행할 애플리케이션의 경로(COMMAND)를 지정하게 됩니다.

주의

실제로 chroot를 사용하여 어떤 애플리케이션의 실행 루트 티렉토리를 변경할 경우 해당 애플리케이션에 여러 의존성 파일이 존재하여 쉽게 실행이 안 되는 경우가 많습니다.

만약 chroot 명령어를 통해 실제로 실험을 해보고 싶은 경우 ldd 명령어를 통해 해당 애플리케이션에 존재하는 의존 라이브러리 파일을 파악한 이우 해당 애플리케이션을 포함하여 전부 새로 지정한 루트 디렉토리 경로에 맞게 이동해줘야 합니다.

네임스페이스

네임스페이스(Namespace)는 프로세스를 실행할 때 시스템의 리소스를 분리해서 실행할 수 있도록 도와주는 기능을 의미합니다. 다시 말해 특정 프로세스에 대해 시스템 리소스를 논리적으로 격리하는 기능입니다. 쉽게 설명하며 아파트에 지역 주민들이 각 호에 살면서 개별적이고 격리된 생활을 하고 있는 것이라 할 수 있습니다.

정보

네임스페이스의 종류는 다양합니다. 이때 도커에서 사용하는 네임스페이스는 ipc, mnt, net, pid, pid_for_children, uts입니다. 더 자세한 내용에 관해서는 생략하도록 하겠습니다.

자원 할당 제어

리눅스에는 프로세스 그룹에 대해 자원 할당을 제어할 수 있는 명령어 cgroup이 존재합니다. cgoup은 단어 그대로 그룹 제어(Control Group)의 줄임말로 하드웨어의 자원을 그룹별로 관리할 수 있게 해주는 커널 모듈입니다. 쉽게 설명하면 아파트 호수 별로 사용할 수 있는 수도량을 제한하는 것입니다. 101호, 102호, 103호라는 네임스페이스로 나눠진 공간에 각각 30%씩 수도를 사용할 수 있게 cgroup을 사용하여 제한합니다.

정보

cgroup 또한 네임스페이스의 한 종류입니다.

결론

도커가 리눅스 운영체제 환경에서 각각의 컨테이너가 동일한 호스트 운영체제의 커널을 공유하는 데 독립적인 공간을 할당 받은 것처럼 보일 수 있는 이유는 리눅스의 chroot, 네임스페이스, cgroup 명령어 및 개념을 사용했기 때문입니다. 이를 풀어서 설명하면 다음과 같습니다.

먼저 chroot 명령어를 통해 각 컨테이너에 대한 파일시스템을 격리합니다. 다음으로 네임스페이스를 사용하여 컨테이너가 독립적인 환경을 갖게됩니다. chroot 및 네임스페이스를 통해 컨테이너는 독립적으로 사용할 수 있는 파일시스템과 환경을 갖게 되었습니다. 끝으로 cgroup을 통해 해당 컨테이너가 사용할 수 있는 하드웨의 자원, 다시 말해 CPU 및 메모리 등을 제한하게 됩니다. 이를 통해 각 컨테이너는 다른 컨테이너에 할당된 자원을 사용할 수 없기 때문에 결국 컨테이너가 별도의 운영체제에서 실행되는 것과 비슷해집니다.

장점

먼저 애플리케이션 개발과 배포가 편해집니다. 컨테이너는 호스트 OS 위에서 실행되는 격리된 공간이기 때문에 컨테이너에 어떤 라이브러리를 설치하고 실행하더라도 호스트 OS에 영향을 주지 않습니다. 따라서 호스트 OS와의 의존적인 관계를 고민할 필요가 없기 때문에 독립된 환경 내에서 개발이 수월해지는 것입니다. 또한 애플리케이션을 배포할 때 해당 컨테이너를 이미지(Image)라는 패키지로 만들어 운영하는 서버에 전달하기만 하면 되기 때문에 무척 간편하고 관리가 쉽습니다.

정보

도커는 이미지 내용을 레이어 단위로 구성하기 때문에 중복되는 부분의 경우 해당 레이어를 재사용하는 방식으로 배포 속도를 빠르게 합니다. 관련해서는 추후에 더 살펴보도록 하겠습니다.

다음으로 여러 애플리케이션의 독립성과 확장성이 높아집니다. 소프트웨어의 여러 모듈이 상호 작용하는 로직을 하나의 프로그램 내에서 구동시키는 방식을 모놀리스 애플리케이션(Monolith Application)이라 합니다. 이와 달리여러 모듈을 독립된 형태로 구성하여 애플리케이션이 특정 언어나 라이브러리에 종속되지 않게 구성하고 관리하는 방식을 마이크로서비스 애플리케이션(MSA_Micro Service Application)이라 합니다.

마이크로서비스 애플리케이션의 경우 각 모듈이 독립된 환경으로 구성되어 있기 때문에 개별적인 관리가 쉬워집니다. 이때 독립된 환경을 컨테이너를 통해 구현할 수 있어 결론적으로 도커를 사용하면 애플리케이션의 독립성과 확장성이 높아지게 됩니다.

단점

물론 도커에도 단점은 존재합니다. 먼저 하이퍼바이저에 비해 자원의 격리와 제한이 어렵습니다. 또한 저장소의 성능 또한 변경된 내용을 기록하고, 기록된 내용으로부터 원본 데이터를 복원하는 방식을 사용하기 때문에 훨씬 느립니다. 더욱이 모든 컨테이너가 동일한 커널을 공유하고 있기 때문에 하이퍼바이저를 통한 가상 머신보다 보안에 있어 훨씬 취약합니다. 끝으로 네트워크를 구성할 때 하이퍼바이저를 사용한 각각의 가상 머신은 물리적인 컴퓨터 시스템과 크게 차이가 존재하지 않기 때문에 독릭접인 네트워크를 구성할 수 있는 반면 도커를 사용한 컨테이너의 경우 독립된 네트워크를 구성하기가 훨씬 복잡합니다.

결론

도커는 결국 리눅스 내부의 명령어와 개념을 사용하며 독립된 공간을 구현하는 방식이기 때문에 도커를 통해 만들어진 컨테이너와 가상 머신은 다른 개념입니다. 앞서 가상화의 종류에서 잠깐 언급했던 것처럼 가상 머신을 결국 하이퍼바이저를 사용하여 물리적인 하드웨어를 추상화하는 방법이고 도커는 결국 애플리케이션을 추상화하는 방법이기 때문입니다.

도커는 결국 하이퍼바이저를 사용하여 하드웨어를 추상화하기 때문에 필연적으로 메모리나 속도 면에서 비효율적인 부분을 극복하기 위해 등장한 개념이라 할 수 있습니다. 다음에는 실제로 도커의 컨테이너와 이미지를 다뤄보도록 하겠습니다.


작성자: 이태현

작성일: 2022-01-18