Containers can do ANYTHING!
컨테이너
컨테이너라는 단어를 들으면 어떤 이미지가 떠오르시나요? 보통 뉴스에서 봤던 커다란 배와 크레인, 화물 등이 연상되죠. 앞으로 설명드릴 컨테이너의 개념도 그러한 연상에서 크게 벗어나지 않습니다.
항만에 적재된 컨테이너는 공간 점유와 이동을 효율적으로 하기 위해 나름의 표준화된 규격을 가지고 있습니다. 프로그램이 작동하는 환경인 런타임을 위 그림과 같이 규격화된 형태로 구성하면 각종 종속성 및 자원 관리 문제 등을 해결하고 개발자는 비즈니스 로직에 더 집중할 수 있게 되겠죠. 이러한 아이디어에 착안하여 등장한 컨테이너는 어플리케이션을 빌드하여 실행하기 위해 규격화된 논리적인 공간으로 정의할 수 있습니다.
컴퓨터는 물리 장치인 하드웨어와 명령의 모음인 소프트웨어, 그리고 하드웨어가 명령을 효율적으로 수행할 수 있도록 돕는 운영체제로 구성되어 있습니다. 운영체제의 종류는 전세계적으로 널리 사용되고 있는 MS 사의 Windows, 애플 사의 macOS, 그리고 Ubuntu, CentOS를 비롯한 각종 리눅스 배포판이 있죠. 각 운영체제마다 용도와 로직은 다르지만 한정된 자원을 알맞게 배분하여 명령을 수행할 수 있도록 돕는다는 측면에서 그 목적은 비슷합니다.
운영체제의 구성요소 중 주목해야 하는 것은 바로 커널(Kernel) 입니다. 커널의 동작 원리는 두꺼운 전공책 몇 권은 거뜬히 넘길 정도로 상당히 긴 내용입니다. 일단 어플리케이션이 CPU, SSD, RAM, 그래픽 카드, 입출력 장치 등 하드웨어에 접근할 수 있는 통로를 구성하고 프로세스에 자원을 할당하는 등의 중추적인 역할을 수행하는 것으로 이해하도록 하겠습니다.
위 그림은 어플리케이션이 실행되고 있는 컨테이너와 VM의 구성을 도식화 한 것입니다. 두 그림의 공통점과 차이점을 한 번 찾아볼까요?
공통점은 어플리케이션이 작동하는 공간이 격리되어 있다는 것입니다. 서비스를 개발/운영하는 관점에서 특정 환경에 종속되거나 마이그레이션이 어려운 어플리케이션은 유지보수 측면에서도, 확장성 측면에서도 바람직 하지 않습니다. 특히 기능 단위 개발/배포 주기가 점점 빨라지는 추세에서 어플리케이션의 독립성이 보장되지 않는다면 생산성을 굉장히 저하시키는 요인이 됩니다.
그렇다면 VM과 컨테이너의 차이점은 무엇일까요? 바로 격리된 공간에 커널을 두는지에 대한 여부입니다. 개별 VM은 커널을 비롯한 OS 전체가 설치되어야 합니다. 운영체제가 작동하기 위한 최소 사양을 고려해보면 불필요한 자원 낭비가 불가피합니다. 컨테이너는 이러한 문제를 해결하기 위해 어플리케이션이 실행되는 공간을 나누었고, 필요한 자원을 Docker, containerd, podman 등 컨테이너 런타임 엔진의 규칙에 따라 Host로부터 분배 받도록 하였습니다. 컨테이너에 접속해보면 마치 컨테이너에 운영체제가 설치되어 있는 것처럼 보입니다. 하지만 이는 커널을 갖춘 진짜 운영체제가 아니고 어플리케이션 런타임을 위한 환경과 의존성만 유사하게 조성해 놓은 것이기 때문에 혼동하지 않아야 합니다.
1.1 오버헤드 감소
커널에서 명령을 처리하는 데에는 각종 리소스가 투입됩니다. 출근을 해서 하는 업무에 들어가는 에너지가 100이라고 할 때, 세면을 하고 옷을 입고 출근 지하철을 타는 등의 부가적인 에너지가 10이 든다고 가정해 봅시다. 여기서 본 업무 외에 추가로 소요된 에너지 10을 오버헤드(Overhead) 라고 합니다. 코어 업무가 아닌 오버헤드 성격의 리소스가 늘어나는 것은 바람직하지 않습니다. 처리 속도 지연, 가용 메모리 누수 등의 이슈를 초래하기 때문이죠. 아무리 효율적인 아키텍처라도 오버헤드를 0으로 만들 수는 없지만, 이를 최소화 하여 시스템의 부하를 줄이는 방법에 대해 많은 이들이 고민하여 개선하고 있습니다. 컨테이너를 활용하는 경우 어플리케이션이 개별 커널을 구동하지 않으므로 VM의 방식에 비해 오버헤드를 줄여 서비스를 운영할 수 있습니다.
1.2 개발 안정성 및 효율성 증대
개발(development)과 운영(production) 환경은 엄밀히 분리되어야 합니다. 동시에 운영서버에 반영이 필요한 사항은 절대 누락되어선 안됩니다. 만약 단계별 배포 구조가 잘 설계되어있지 않으면 개발 단계에서 테스트를 거치지 않은 코드가 반영되거나 환경변수 등이 오반영 되어 치명적인 장애를 야기할 수 있습니다.
컨테이너를 활용하면 API key나 환경변수를 어플리케이션의 빌드/런타임 시점에 독립적으로 부여할 수 있기 때문에 위험요인을 효과적으로 줄일 수 있습니다. 실제로 현업에서는 상이한 환경에서 비롯된 리스크로 인해 장애의 발생이 빈번하며, 이를 줄여나가는 것이 안정성 확보를 위한 사업부의 중요 과제로 다뤄지기도 합니다.
1.3 이식성 향상
이식성(Portability)은 서로 다른 환경에서 어플리케이션을 작동시킬 수 있는 특성을 나타내는 용어입니다. 컨테이너 런타임 엔진이 설치된 곳이라면 어디서든 컨테이너를 실행할 수 있기 때문에 이식성을 크게 향상 시킬 수 있습니다. (amd64, arm64 등 아키텍쳐의 영향은 있기 때문에 이에 대해서는 미리 확인이 필요합니다.)
어플리케이션을 실행하기 위한 독립적인 공간에도 프로세스를 띄울 수 있도록 환경을 구축해야 하죠. 여기에 주로 사용되는 것이 리눅스 컨테이너 이미지이고, 어플리케이션을 빌드할 때 베이스 이미지로 활용됩니다. 흔히 알려진 Ubuntu, CentOS, Fedora 등의 배포판 뿐만 아니라 경량화 리눅스인 Alpine, Debian Slim 등이 널리 사용되고 있습니다. 컨테이너 런타임 엔진이 설치된 운영체제가 아니라 이미지 빌드 당시에 정의된 운영체제 이미지의 영향을 받기 때문에 어플리케이션의 이식성을 크게 향상시켰습니다.
[그림 1–5] 은 파이썬 인터프리터가 빌드되어 있는 공식 이미지 페이지를 캡처한 것입니다. 인터프리터를 빌드하기 위해서는 운영체제가 밑바탕이 되어야 하는데, Simple Tags
항목을 보면 각기 다른 운영체제를 베이스 이미지로 삼고 있는 파이썬 이미지들을 확인하실 수 있습니다. 오른쪽 이미지는 alpine 리눅스를 베이스로 삼은 파이썬 이미지의 Dockerfile 을 예시로 첨부한 것입니다.
“어차피 성능과 효율을 위한 것이면 제일 가벼운 리눅스 이미지를 베이스로 쓰면 되는 것이 아닌가?” 하는 의문이 들 수 있습니다. 하지만 각 배포판에서 지원하는 의존성과 EOS(End of Service, 유지보수 연한)가 다르기 때문에 개발 중인 어플리케이션 및 환경에 따라 취사선택하는 지혜가 필요합니다. 대표적인 리눅스 컨테이너 이미지의 비교자료는 다음 링크에서 확인 가능합니다.
💡 클라우드타입에서 배포하는 어플리케이션은 모두 컨테이너라는 공간에서 실행되고 있습니다. 뒤이어 업로드 될 포스팅에서는 Dockerfile을 통해 직접 이미지를 빌드하고 클라우드타입에서 컨테이너 형태로 배포해보는 실습을 다뤄보겠습니다. 감사합니다 :)
📚출처
The Twelve-Factor app : https://12factor.net/
A Comparison Of Linux Container Images : http://crunchtools.com/comparison-linux-container-images/
Python — Official Image | Docker Hub : https://hub.docker.com/_/python
Microservices in Action, 1st ed. by Morgan Bruce, Paulo A. Pereira (Manning, 2018)