얼마전 Docker에 관한 사내 세미나를 진행했습니다. 개인적으로 Docker자체에 대한 관심도 많았고, 친구와 이야기를 나눠본 결과, Docker라는 기술이 왜 사람들이 많이 필요로하는 기술이 되었는지, Docker라는 기술이 왜 나오게 되었는지에 초점을 맞추어 세미나를 진행해도 괜찮겠다는 결론이 나왔습니다. Docker의 활용법에 관해서 깊게 다루지는 않았기 때문에 그냥 편하게 읽어보시면 좋을듯 싶습니다.
Why 가상화?
가상화의 등장배경
가상화의 등장 배경부터 설명드리겠습니다. 하드웨어 성능은 반도체 집적회로의 성능이 18개월마다 2배로 증가한다는 무어의 법칙을 증명이라도 하듯이 계속해서 성장했습니다. 하지만 하드웨어의 성능이 폭발적으로 증가하는것에 비해 소프트웨어의 성장률은 낮았죠. 그렇기 때문에 하드웨어의 활용률이 낮았습니다.
이러한 현상때문에 x86 아키텍처를 기반으로 하는 서버이든 IBM의 메인프레임이든 서버한대의 평균적인 활용률이 낮아지고 복잡한 컴퓨팅 환경으로인한 인프라 관리 직원 비용 증가, 서버 유지보수와 관련된 수동작업에 과도한 시간과 리소스 소비로 인한 IT 관리비용이 증가하고, 서버가 많아질수록 증가하는 물리적 인프라를 지원하는데 소요되는 운영 비용이 높아졌습니다.
최초의 가상화
특히 IBM의 MainFrame은 그 당시 1960년대에 값비싼 리소스였으므로 투자를 완전하게 활용하기 위해 가상 메모리가 가상화를 실현시키는 개념에서 시작하여 여러 애플리케이션 및 프로세스를 동시에 실행시키기 위한 멀티태스킹을 위해 논리적 파티션 구현, 다중사용자, 다중 워크로드 처리, 다중 시스템 지원을 하였습니다. 여기서 슈퍼바이저라는 말이 처음나오게되었는데 나중에 하이퍼바이저개념으로 이어지게 됩니다.
그 후에 1980년대와 1990년대에 값비싼 Mainframe대신에 x86서버가 업계 표준으로 확립되면서 서버는 더욱 많아지게 되었고 앞에서 말씀드린 낮은 인프라 사용률, 물리적 인프라 비용증가, IT관리비용증가와 같은 문제점들로 인해 가상화에 대한 요구가 더 높아지게 됩니다.
그 요구에 맞춰 1999년에 VMware가 개발되었습니다.
(당시 처음 개발된 VMware는 x86아키텍쳐에서 가상화 시 커널단의 17개의 명령이 x86프로세서와 충돌을 일으켜서 문제가 되었지만, 이러한 명령이 생성될때 명령을 트랩한 후 가상화할 수 있는 안전한 명령으로 변환시키는 기술을 개발함으로서 호스트 하드웨어와 일치하고 전체 소프트웨어 호환성을 유지하는 안정적인
가상 머신이 탄생하게 되었습니다.)
전가상화
처음 가상화 기술을 선보인 VMware는 전가상화였습니다. 전가상화란 말 그대로 전체 하드웨어를 가상화하는 기술로, guestOS에 수정을 가하지않고 실제 머신과 같이 사용하는 것입니다. 따라서 전가상화는 Guest OS자체가 자신이 가상화되어있는 OS인지 인지하지 못합니다. 따라서 게스트os에서 발생하는 모든 os레벨 명령은 하이퍼바이저가 핸들링하고 하이퍼바이저를 통해서 호스트os로 내려가게 되는데 이때에 바이너리 트랜스레이션이 발생하여 상당한 오버헤드가 발생하게됩니다.
반가상화
그래서 이러한 단점을 보완하기 위해 제시된 개념이 반가상화입니다. 여기서 보시는 type 1 - bare metal은 대표적으로 xen서버를 예로 들수 있습니다. 반가상화는 전가상화에서 드러났던 단점을 해결하기 위해 모든 명령을 하이퍼바이저가 트랩 핸들링하지 않습니다. 필요한 부분만 딱 처리하고 성능상의 이점을 도모하기 위해 하이퍼콜이라는 개념을 도입합니다. 하이퍼콜은 시스템콜과 비슷한 개념인데 전가상화처럼 os레벨 명령이 필요할때 바이너리 트랜스레이션을 하지 않고 하이퍼바이저에 하이퍼콜을 호출해 다이렉트로 통신하는 형태입니다.
하지만 단점이 Guest OS단에서 자체적으로 처리해야 할 명령은 처리하고 필요한 부분만 하이퍼콜을 이용해 하이퍼바이저에게 넘겨줘야하는데 기존 OS들은 이걸 스스로할 능력이 없습니다. 따라서 이것이 가능하도록 GusetOS를 수정해줘야합니다. 그래서 반가상화는 오픈소스인 리눅스 진영에서 처음으로 제공되기 시작했고 발전해 왔습니다. 그 대표주자가 바로 Xen이죠.
이렇게 전가상화와 반가상화에 대해 말씀드렸는데요, 보기에는 bare-metal이 성능면에서 유리하지만 요즘 OS들은 KVM처럼 커널레벨에서 가상화를 지원하기 때문에 어느 타입의 하이퍼바이저를 선택하든지 성능의 차이가 모호해지고 있습니다.
하지만 KVM과 같은 커널레벨가상화든 전가상화든 반가상화든 사라지지않는 단점이 있습니다. 말그대로 가상머신이기때문에 이미지안에 OS가포함되어야 하고 그 때문에 이미지 용량이 커진다는 것입니다. 아무리 네트워크와 인터넷 속도가 빨라졌다 하더라도 가상화 이미지를 주고받는 것은 꽤 부담스럽습니다. 그리고 또한, 오픈 소스 가상화 소프트웨어는 OS를 가상화하는 것에만 초점이 맞춰져있기 때문에 배포와 관리 기능이 부족합니다.
그 이유 때문에 이제 사람들의 관심은 컨테이너 가상화 기술로 옮겨가게 되었습니다. 드디어 Docker에 관해 말씀드리게 되었는데요, Docker에 대해 설명드리기전에 리눅스 컨테이너에 대해 설명을 먼저 드려야 할것 같습니다.
Why LXC?
chroot
오래 전부터 리눅스/유닉스 환경은 chroot라는 명령을 제공했습니다. chroot는 파일시스템에서 루트 디렉터리를 변경하는 명령입니다. chroot로 특정 디렉터리를 루트 디렉터리로 설정하면 chroot jail이라는 환경이 생성되는데, chrrot jail안에서는 바깥의 파일과 디렉터리에 접근할수 없습니다. 이처럼 chroot는 디렉터리 경로를 격리하기 때문에 서버 정보 유출과 피해를 최소화하는데 주로 사용되었습니다. 하지만 chroot jail에 들어갈 실행 파일과 공유라이브러리를 직접 준비해야 하고 설정이 복잡합니다. 그리고 완벽한 가상 환경이 아니기 때문에 chroot에서 제어할수있는 파일이나 디렉토리에 대한 엑세스만으로 네트워크 및 프로세스 등을 컨트롤 할 수 없습니다.
Linux Container
이후 리눅스는 chroot를 발전시켜 리눅스 컨테이너라는 시스템 레벨 가상화를 제공했습니다. Namespaces에서 제공하는 namespace isolation으로 프로세스 트리, 사용자 계정, 파일시스템, IPC, 네트워크 장치 등의 운영환경을 고립시킵니다. 그리고 cgroups를 통해 각각의 고립된 환경에 cpu, memory, disk, network와 같은 자원을 할당합니다. 이러한 방식으로 LXC는 격리된 컨테이너를 생성하고 제공했습니다.
사실 컨테이너만 필요하다면 LXC만 사용해도 되지만 LXC는 컨테이너의 생성 및 관리, 배포에 대한 부가 기능이 없기 때문에 실제 서비스를 운용하기에는 부족함이 있습니다. Docker는 이를 보충하여 컨테이너에 대한 부가 기능을 제공하고 컨테이너의 상태를 이미지화하여 배포를 손쉽게 해줍니다.
Why Docker?
그렇게 Docker가 탄생하게 되었습니다. Docker의 로고는 컨테이너들을 나르는 귀여운 고래입니다.
이 도커의 로고가 도커를 잘 설명하고 있습니다. 컨테이너는 애플리케이션과 서비스 운영을 위한 환경들이 담겨있다는 것을 의미하고, 고래는 서버와 그 서버위에 컨테이너의 배포를 의미합니다. 즉, 도커는 서비스 운영환경을 묶어서 손쉽게 배포하고 실행하는 경량 컨테이너 기술입니다. Docker가 처음 개발될 당시에는 LXC를 기반으로 구현을 하였지만 버전 0.9부터는 LXC를 대신하는 libcontainer를 개발하여 사용하고 있습니다.
Immutable Infrastructure
과거와 다르게 서버시장이 클라우드 시장으로 넘어오면서 1대가되었든 1000대가 되었든 클릭 몇번만으로 가상서버를 만들어낼수 있게 되었습니다. 이렇게 생성된 가상 서버에 각종 소프트웨어를 설치하고 설정해야 하는데 서버가 한 두대라면 쉽게 설정할 수 있지만 서버 개수가 많아지면서 사람이 하기가 어려워졌습니다. 따라서 클라우드 환경에서 설치와 배포가 큰 어려움으로 다가왔습니다.
셸 스크립트로 설치 및 설정 자동화를 구현해도 되지만 중앙 관리 기능이나 복잡한 기능은 구현하기 힘듭니다. 또한 리눅스 환경은 설치해야 할 응용프로그램도 많고 설정도 복잡하여, 사소한 설정 하나가 운영체제와 서비스의 안정성에 큰 영향을 미칩니다.
이런 상황에서 나온것이 Immutable Infrastructure입니다. Immutable Infrastructure는 호스트 os화 서비스 운영환경(서버 프로그램, 소스코드, 컴파일된 바이너리) 를 분리하고 한번 설정한 운영 환경은 변하지 않는다는 개념입니다. 클라우드 플랫폼에서 서버를 쓰고 버리는 것처럼 이것도 서비스 운영 환경 이미지를 한번 쓰고 버립니다. 이렇게 하게 되면 서비스 운영 환경을 이미지로 생성했기 때문에 이미지 자체만 관리하면 되서 관리가 편합니다. 그리고 이미지 하나로 서버를 계속 찍어낼 수 있기 때문에 클라우드 플랫폼의 자동확장 기능과 연동하면 손쉽게 서비스를 확장할수있습니다. 또한, os를 제외한 서비스 운영환경이미지만 가지고 있기 때문에 가볍습니다. Docker는 이러한 Immutable Infrastructure를 구현한 프로젝트입니다. 이제 Docker를 이해하기 위한 핵심적인 부분을 간략하게 보시겠습니다.
이미지와 컨테이너
첫번째로 이미지와 컨테이너입니다. 먼저 베이스 이미지가 있습니다. 이 베이스 이미지는 리눅스 배포판의 유저랜드만 설치된 파일을 뜻합니다. os는 커널공간과 사용자공간으로 분리되는데 이 사용자 공간에서 실행되는 실행파일과 라이브러리를 유저랜드라고 합니다. 즉 유저랜드란 커널과 상호 작용하기 위해 사용하는 다양한 프로그램과 라이브러리를 가리킵니다.(입출력을 수행하고 파일 시스템 오브젝트를 처리하는 소프트웨어를 아우릅니다.) 베이스 이미지에 필요한 프로그램과 라이브러리, 소스를 설치한 뒤 파일 하나로 만든것을 Docker이미지라고 합니다.
매번 베이스 이미지에 필요한 프로그램과 라이브러리, 소스를 설치하면 용량이 큰 이미지가 중복되어 생성될 것이라고 생각하기 쉽지만, Docker이미지는 현재 이미지에서 바뀐부분만 이미지로 생성하고 실행할 때는 현재이미지와 바뀐 부분을 합쳐서 실행합니다.
이미지 의존관계
다음 그림은 이미지 간의 의존관계를 보여줍니다.
Docker이미지는 16진수로 된 ID로 구분하고, 각각의 이미지는 독립적입니다. 즉, Docker는 이미지를 통째로 생성하지 않고, 바뀐 부분만 생성한 뒤 부모 이미지를 계속 참조하는 방식으로 동작합니다. centos7이미지 설명 centos6에 서비스 운영에 필요한 프로그램을 설치한 뒤 Docker 이미지를 생성하면 example:0.1과 같은 형태가 됩니다. 위의 예를 보시면 centos7의 이미지는 511136ea3c5a와 34e94e67e63a 그리고 b157b77bla65이미지가 합쳐진 이미지입니다. 그리고 centos6의 이미지에서 라이브러리를 추가해서 빌드하면 4281f6de5d39 이미지가 생성되고 이 이미지에 추가작업을 한 뒤 빌드하면, 28b806eeedb8의 ID를 가진 example:0.1 이미지가 생성됩니다. Docker에서는 이를 Layer라고 합니다. 처음에는 자식이미지와 부모이미지를 함께올리고 받을때도 함께받고 그 다음부터는 내용이 바뀐 이미지만 주고받습니다.
Docker hub
도커는 또한 Docker 공식 이미지를 제공하고, 사용자들끼리 이미지를 공유하는 Docker hub라는 사이트를 제공합니다.
위의 이미지는 GitHub 또는 Bitbucket 저장소와 연동하여 Dockerfile을 Push했을 때 이미지를 자동으로 빌드하는 기능을 나타내고 있습니다. 도커파일을 github이나 bitbucket에 커밋하면 도커허브에서 그 파일을 가져와서 자동으로 빌드를하여 이미지를 생성합니다.
Dockerfile작성
앞장에서 언급한 도커파일에 대해 설명드리겠습니다. Dockerfile은 이미지를 빌드하기 위해서 작성해야 하는 파일입니다. 위의 이미지에서 nginx설정에서는 nginx config파일에 daemon off를 추가해주고 nginx를 포함한 그 하위 디렉토리와 파일의 소유자와 소유그룹을 변경해줍니다.
볼륨으로는 호스트와 공유할 디렉터리 목록을 적어줍니다. CMD는 컨테이너가 시작되었을 때 실행할 실행 파일 또는 셸 스크립트이고 WORKDIR은 CMD에서 설정한 실행파일이 실행될 디렉터리입니다. 그리고 EXPOSE는 호스트와 연결할 포트번호입니다.
Docker Image 빌드
이제 이미지를 생성해보고 실행시켜보겠습니다. docker build 옵션 이미지경로입니다. 위의 이미지에서는 –tag로 이미지 이름과 태그설정하여 이미지를 빌드했습니다.
Docker run으로 이미지를 실행할수있습니다. 이미지를 실행하면 이제 컨테이너라 불립니다. 여기서 컨테이너의 이름을 hello-nginx로하고 -d 옵션을통해 백그라운드로 실행합니다. 그리고 -p옵션으로 호스트포트80에 컨테이너포트 80을연결시키고 앞에서봤던 volume에서 /data디렉터리에 호스트의 /root/data디렉터리를 연결시킵니다.
이제 보시는 바와 같이 연결한 포트로 접속을 하면 실행중인 컨테이너에 접속할 수 있습니다.
마무리하며…
세미나에서는 이 뒤에 빌드한 이미지를 Docker Hub에 올려서 관리하는 방법과 Docker의 활용 시나리오에 대해 더 다루었습니다만, 이번 포스팅 자체가 Docker의 활용법 보다는 Docker가 왜 나오게 되었는지, 왜 현재 클라우드 환경에서 주목을 받고 있는지에 대해 초점을 맞추었기 때문에 더 깊게 다루지는 않았습니다.
이 글은 다음을 참고하여 개인 공부 목적으로 정리한 글입니다.
가장 빨리 만나는 Docker
Docker official Documentation