본문 바로가기

서버운영 (TA, ADMIN)/인프라

[Container] 제대로 배우는 도커(1)

Containers are a lightweight and portable store for an application and its dependencies. - 컨테이너는 응용프로그램과 응용프로그램의 종속성을 위한 가벼우면서 이동 가능한 저장소를 의미한다.

 

컨테이너(Containers)는 소프트웨어를 개발, 배포, 실행하는 방법을 근본적으로 뒤바꾸고 있다. 개발자들은 로컬에서 소프트웨어를 개발하면서 소프트웨어가 실행될 호스팅 환경을 고려하지 않아도 된다. 즉, 소프트웨어가 실행될 호스팅 환경이 IT 부서에서 관리하는 랙, 사용자의 랩탑, 클라우드에 있는 클러스터 어디에 있더라도 소프트웨어는 동일하게 실행될 수 있음을 의미한다.

 

컨테이너는 응용프로그램의 종속성과 함께 응용프로그램 자체를 캡슐화한 것이다. 언뜻 컨테이너는 가상 머신(VM, Virtual Machine)의 가벼운 형태처럼 보일 것이다. 일반적인 VM처럼, 컨테이너는 운영체제(OS, Operating System)의 격리된 인스턴스 형태로, 해당 환경에서 응용프로그램을 실행한다. 하지만 컨테이너는 전통적인 VM에서는 어렵거나 불가능한 것들을 가능하게 해주는 여러 장점들을 제공한다.

  - 컨테이너는 호스트 OS에 있는 자원들을 공유하여, 자원을 좀 더 효율적으로 사용할 수 있게 해준다. 컨테이너는 수 초 이내에 시작되거나 중지될 수 있다. 컨테이너에서 실행되는 응용프로그램은 호스트 OS에서 네이티브로 실행되는 응용프로그램에 비해 발생시키는 오버헤드는 거의 없다.

  - 컨테이너는 운영 환경의 변경으로 인해 발생하는 모든 버그들을 없앨 수 있는 이식성(portability)을 제공한다. 이를 통해 "내 컴퓨터에서는 잘 동작하는데!"라는 불평을 없앨 수 있다.

  - 컨테이너는 매우 가볍다는 특성을 갖는다. 개발자로 하여금 많은 수의 컨테이너를 동시에 실행할 수 있도록 하여, 상용 분산 시스템을 애뮬레이트할 수 있도록 해 준다. 운영 엔지니어는 VM 자체를 운영하는 것이 아니라 하나의 호스트 머신에서 여러개의 컨테이너를 운영한다.

  - 또한 컨테이너는 최종 사용자와 개발자가 클라우드에도 배포할 수 있다는 장점을 제공한다. 사용자는 구성과 설치 과정에서 발생되는 문제들로 시간을 허비하거나 시스템에 필요한 변경 사항에 신경 쓸 필요 없이 복잡한 응용프로그램을 다운로드하여 실행할 수 있다. 결국 응용프로그램 개발자들은 사용자 환경 간에 다른 점과 종속성에 대해서 걱정할 필요가 없다.

 

VM은 외부 환경을 완전하게 에뮬레이트하는 데 목적을 둔 데 반해, 컨테이너는 응용프로그램의 이식성과 독립성에 목적을 두고 있다.

컨테이너와 VM

하이퍼바이저(hypervisor)는 VM들을 생성하고 실행해야 하며 필요한 경우 시스템 호출을 "인터프리팅"하는 것은 물론이고 기본 OS와 하드웨어에 대한 접근도 제어하게 된다. 각각의 VM들은 OS 전체, 실행할 응용프로그램과 지원되는 모든 라이브러리들을 모두 가지고 있어야 한다.

 

하이퍼바이저는 Virtualbox나 VMWare Workstation과 같은 타입2 하이퍼바이저로, 호스트 OS 위에서 실행된다. Xen과 같은 타입1 하이퍼바이저 역시 사용할 수 있으며, 베어 메탈(bare metal) 위에서 직접적으로 실행되는 하이퍼바이저를 의미한다.

 

컨테이너형 시스템 안에서의 응용프로그램 실행은 VM과는 달리, 호스트의 커널은 실행되는 컨테이너들 사이에 공유된다. 즉 컨테이너들으 항상 호스트와 같은 커널에서 실행된다는 것을 의미한다. 응용프로그램 Y와 Z는 같은 라이브러리들을 사용하며, 각각 복사본을 가지지 않고 데이터를 공유하여 사용한다. 컨테이너 엔진은 VM의 하이퍼바이저와 비슷한 방식으로 컨테이너의 시작과 중지에 대한 책임을 진다. 하지만 커넽이너 내부에서 실행되는 프로세스는 호스트에 있는 네이티브 프로세스들과 동일하며 하이퍼바이저 실행과 관련된 오버헤드는 발생하지 않는다.

 

커널은 OS의 핵심 구성 요소로, 메모리, CPU, 장치 접근과 관련된 필수 시스템 기능들이 포함된 응용프로그램을 제공하는 책임을 진다. OS는 커널과 함께 (init 시스템, 컴파일러, 윈도우 관리자 등과 같은) 다양한 프로그램들로 구성된다.

 

VM과 컨테이너는 응용프로그램을 같은 호스트에서 실행되는 다른 응용프로그램으로 부터 격리시키는데 사용할 수 있다. VM은 오랜 기간 동안 축적된 기술로 하이퍼바이저에서 격리 수준이 추가된다. 컨테이너는 비교적 새로운 기술로, 검증된 부분이 적은 관계로 많은 기업들이 컨테이너의 격리 기능을 전적으로 신뢰하지는 않는 상태이다. 때문에 일반적으로 두 기술의 장점을 모두 활용할 수 있는 VM 안에서 컨테이너를 운영하는 하이브리드 시스템을 찾게 된다.

도커와 컨테이너

구글이 리눅스 커널을 위한 CGroups의 개발을 시작하였고 구글의 인프라를 컨테이너로 옮기기 시작했다. 2008년에 시작된 리눅스 컨테이너(LXC) 프로젝트는 CGroups, kernel namespaces, chroot 기술과 합쳐져서 완벽한 컨테이너화 솔루션을 제공하게 되었다. 2013년 Docker는 컨테이너화 퍼즐의 마지막 조각을 맞추면서 주류 기술로 인정받게 되었다.

 

도커는 기존 리눅스 컨테이너 기술을 차용하였으며 다양한 방식(주로 이식 가능한 이미지와 사용자에게 친숙한 인터페이스를 통하여)으로 포장하고 확장하여 컨테이너의 생성 및 배포를 위한 완벽한 솔루션을 만들게 되었다. 도커 플랫폼은 두 개의 독립된 컴포넌트로 구성된다. 먼저 도커 엔진은 컨테이너를 생성하고 실행하는 역할을 수행하며, 도커 허브(Docker Hub)는 컨테이너 배포를 위한 클라우드 서비스를 제공한다.

 

도커 엔진(Docker Engine)은 컨테이너를 운영하기 위한 빠르고 간편한 인터페이스를 제공한다. 도커가 발표되기 전에는 LXC와 같은 기술을 이용하여 컨테이너를 실행하였는데, LXC는 전문적인 지식은 물론이고 일일이 작업해야 한다는 불편함이 존재했다.

 

도커 허브는 다운로드할 수 있는 막대한 양의 컨테이너 이미지를 제공하여, 새로 접하는 사용자들이 쉽게 사용할 수 있도록 해주고, 다른 사람들이 이미 작업해 둔 내용을 그대로 이용할 수 있다는 장점을 제공한다. 게다가 도커가 개발한 도구들도 제공이 되는데, 클러스터링 관리자인 Swarm, 컨테이너를 이용한 작업을 할 수 잇는 GUI를 제공하는 카이트매틱, 도커 호스트를 생성하기 위한 명령 유틸리티인 machine 등이 있다.

 

도커 엔진을 오픈 소스화함으로써, 도커는 대형 커뮤니티로 성장할 수 있게 되었으며, 버그 수정과 기능 향상 등과 관련되어서 많은 사람들의 도움을 받을 수 있게 되었다. 컨테이너는 주로 개발자들이 사용하였다. 컨테이너를 효율적으로 사용할 수 있는 도구들이 개발자에게 처음으로 제공되었기 때문이다. 코드 변경 대한 결과를 재빨리 확인할 수 있으면서 빠르고 반복적인 개발 사이클을 필요로 하는 개발자에게 도커 컨테이너를 빠른 시간내에 시작할 수 있는 기능은 반드시 필요하다. 컨테이너가 제공하는 이동성과 독립성의 보장은 다른 개발자와의 협업은 물론이고 운영도 편리하게 해준다. 개발자는 자신의 코드가 환경에 상관없이 실행됨을 확신할 수 있으며, 운영자는 컨테이너에서 실행되는 코드에 대해서 신경 쓸 필요 없이, 컨테이너를 호스팅하고 운영하는 것에만 집중할 수 있게 한다.

 

도커는 소프트웨어를 개발하는 방법에 큰 변화를 가져왔다.

 

선적의 비유

통합된 컨테이너가 개발됨에 따라 운송 업계는 혁신적으로 발전했다. 해당 컨테이너는 표준화된 크기로 만들어지고, 운송 수단간 이동 시에 수작업을 최소화할 수 있도록 설계되었다. 표준화로 얻을 수 있는 장점은 컨테이너의 라벨이나 봉인과 같은 다른 지원 시스템으로 확장되었다. 따라서 생산자는 컨테이너의 내용물에만 신경을 쓰고, 운송 업계는 컨테이너 자체에 대한 이동과 보관에만 집중할 수 있게 되었다.

 

도커의 목적은 컨테이너의 표준화를 통해서 얻을 수 잇는 장점을 IT로 가져오는 것이다. 최근 몇 년동안 소프트웨어 시스템은 다양성 측면에서 폭발적으로 증가했다. 단일 머신에서 운영되는 LAMP 스택의 시대는 갔다. 근간의 일반적인 시스템은 자바스크립트 프레임워크, NoSQL 데이터베이스, 메시지 큐, REST API들과 다양한 프로그래밍 언어들로 작성된 백엔드 등으로 구성이 된다. 이러한 스택은 다양한 하드웨어 상에서 일부가 운영되거나 전체가 운영되어야 한다. 이러하 환경들은 각양각색이라고 할 수 있다. 다른 하드웨어 위에 다른 버전의 라이브러리가 설치된 서로 다른 우영 시스템에서 실행된다. 요컨대 운송 업계에서 본 것과 유사한 이슈를 겪어 온 셈이다. 환경 간에 코드를 이동하려면 상당한 노력을 지속적으로 기울여야만 한다.

 

표준화된 컨테이너가 상품 운송을 단순화한 것처럼, 도커 컨테이너는 소프트웨어 응용프로그램의 이동을 단순화시켜 준다. 개발자들은 응용프로그램 개발에만 집주아혹, 환경 간의 차이와 종속성은 걱정하지 않으면서 테스트 환경과 운영계 환경에 코드를 배포할 수 있다. 시스템 운영자는 컨테이너를 운영하는 주요 이슈(자원 할당, 컨테이너 시작과 종료, 서버 간의 마이그레이션 등)에만 집중하면 된다.

도커의 역사

2008년, 솔로몬 하익스는 언어에 중립적인 Platform-as-a-Service(PaaS)를 제공하는 닷클라우드(dotCloud)사를 설립했다. 2013년 3월 닷클라우드는 자사의 핵심 구성 요소인 도커를 오픈 소스화했다. 다른 기업들은 자사의 핵심 기술을 누군가에게 주는 것을 두려워했던 반면, 닷클라우드는 도커를 커뮤니티 주도의 프로젝트로 전환함으로써 얻을 수 잇는 장점이 엄청나게 많은 것이라고 여겼다.

 

도커의 초기 버전들은 Union 파일시스템과 짝지어진 LXC를 감싸는 래퍼(wrapper)에 지나지 않았다. 하지만 개발 활용도와 속도는 믿을 수 없을 정도로 빨랐다. 6개월만에 깃허브에서 6,700개 이상의 스타가 기록되었으며, 직원이 아닌 175명의 기여자들이 참여했다. 이로인해 닷클라우드의 회사명을 도커로 바꾸게 되었고 사업 모델의 중점을 바꾸게 되는 계기가 되기도 했다. 도커는 컨테이너들의 공용 저장소인 도커 허브를 출시하여, 단순히 컨테이너 엔진만 제공하는 것이 아니라 완벽한 플랫폼으로의 진화를 시작했다.

 

20144년 12월에 있었던 도커콘 EU에서 도커 스웜(Docker Swarm)을 발표했다. 도커 스웜은 도커와 도커 머신(Docker Machine)을 위한 클러스터 관리자로, 도커 호스트를 프로비저닝하는 CLI 도구도 제공한다. 이는 도커가 도커 엔진만을 제공하는데 그치지 않고 컨테이너 운영을 위한 통합 솔루션을 제공하는데 목적을 두고 있음을 잘 보여주는 사례라고 할 수 있다.

 

도커 1.8 버전이 발표되면서, 도커는 도커 이미지의 무결성과 게시자를 확인할 수 있는 콘텐츠 트러스트(content trust)라는 기능을 내놓았다. 콘텐트 트러스트는 도커 레지스트리(registries)로부터 받은 이미지들을 받아서 작업하는 경우에 반드시 필요하면서 중요한 구성 요소라고 할 수 있다.

플러그인(Plugins)과 플러밍(Plumbing)

도커사는 네트워크, 저장소, 기능 조정 등과 같은 기능들을 기본적으로 갖추고 있는 완벽한 플랫폼을 제공할 준비를 하게 되었다. 소프트웨어 생태계의 성장이 지속되도록 장려하고 다양한 방법으로 솔루션을 사용할 수 있도록 하기 위해 도커사는 모듈 방식의 확장성 있는 도커 프레임워크를 개발할 것이라고 발표했다. 해당 프레임워크는 서드 파티 기능으로 옮기거나 확장할 수 있는 구성 요소들을 제공하게 된다. 도커사는 이를 "배터리가 포함되어 있지만, 교체할 수 있는" 철학이라고 불렀다. 풀이하자면 완벽한 솔루션이 제공될 수 있지만, 일부분은 서드 파티 기능도 사용할 수 있음을 의미한다.

 

또한 도커는 "Infrastructure Plumbing Manifesto"라는 원칙을 따르고 있다. "Infrastructure Plumbing Manifesto"에서는 가능하면 기존 인프라 구성 요소들을 재사용하고 개선할 것이라는 점과 새로운 도구들이 필요하게 되면 재사용 가능한 구성 요소들을 커뮤니티가 사용할 수 있도록 기여하겠다는 점을 강조하고 있다. 컨테이너를 실행하기 위한 하위 수준 코드는 OCI 관리 하의 runC 프로젝트로 만들어지게 되었으며, 이는 다른 컨테이너 플랫폼의 기반으로 재사용될 수 있다.

마이크로서비스와 모노리스

내부적으로 컨테이너를 사용하면서 컨테이너를 강력하게 이끌어가는 견인차는 마이크로서비스(microservices)라고 할 수 있다. 마이크로서비스는 소프트웨어 시스템을 개발하고 구성하는 방법 중 하나로 네트워크를 통해 통신하는 작고, 독립된 구성요소들로 만들어진다. 이는 일반적으로 C++ 또는 자바와 같은 언어로 작성된 하나의 큰 프로그램으로 구성되는, 전통적인 모노리식(monolithic) 소프트웨어 개발 방법과는 대조적이라고 할 수 있다.

 

마이크로서비스는 여러 대의 머신을 생성하여 생성된 여러 대의 머신으로 부하를 분산시켜서 처리하는 방식인 규모 확장(scale out)에 맞추어 설계되었다. 마이크로서비스 아키텍처에서는 특정 서비스에서 필요한 자원들만 규모를 확장할 수 있으며, 시스템의 병목에만 집중하면 된다. 모노리스 환경에서는 전체의 규모를 키울 수 밖에 없기 때문에 자원 낭비를 초래한다.

 

복잡도 측면에서 보면 마이크로서비스는 양날의 검과 같다. 각각의 마이크로서비스는 이해와 변경이 용이해야 한다. 하지만 수십 또는 수백 개의 서비스들로 구성된 시스템에서는 각각의 구성 요소들이 상호작용한다는 점 때문에 전체적인 복잡도가 증가하게 된다.

 

컨테이너의 가볍고 빠르다는 특성은 특히 마이크로서비스 아키텍처에서 운영될때 빛을 발한다. VM에 비해서 컨테이너는 엄청나게 작고 빠르게 배포할 수 있기 때문에 마이크로서비스 아키텍처를 통하여 최소한의 자원만 사용할 수 있고, 필요에 따른 변경에도 재빨리 대응할 수 있다.