appleseed.dev

Docker 기본부터 배포까지

DevOps의 현재와 미래

January 21, 2021 · 12분
docker

만약 어떤 초보 개발자가 지금 가장 주목해고 배워야 할 기술이 무엇이냐고 물어보면, 나는 주저없이 docker라고 대답할 것이다. Docker는 작게는 개발 환경과 실제 서비스 환경 사이의 괴리를 최소한으로 좁혀주거나 최소한의 명령어로 백엔드 테스트 서버를 로컬에서 순식간에 구축할 수 있도록 해주어 모든 개발자들의 편의성을 증대시켜주었을 뿐만 아니라, 로드에 따른 무상태 컨테이너 스케일링, 무중단 배포, 오류로 인한 서비스 중단 방지 등 이전에는 DevOps 수 명이 달라붙어 개발하던, 혹은 클라우드에 큰 돈을 지불해가며 사용하던 기능들을 아주 손쉽게 이룰 수 있게 해주었다. Docker로 인해 서비스들은 클라우드 인스턴스 의존적인 구조에서 점점 컨테이너 의존적인 구조로 변화하고 있다. 만약 당신이 docker에 대해 모른다면, 또는 docker를 들어봤지만 아직 쓰지 않는다면, 지금 당장 docker를 공부하는 것을 추천한다. Docker는 당신의 생산력과 가치를 크게 높여줄 것이다.

그래서 docker는 대체 무엇을 하는 기술이고, 어떤 문제를 해결해 주는 것인가? Docker는 Linux 기반 운영체제의 반가상화를 통한 컨테이너 단위의 애플리케이션 배포를 추구한다. 반가상화, 컨테이너 등 생소한 개념들이 많아 초보 개발자는 진입 장벽이 상당하다고 느낄 수 있다. 나 또한 그랬고, kubernetes를 배우려고 시도하고 있는 지금도 새로운 개념이 너무 많이 등장해서 상당한 장벽을 느끼는 중이다. 하지만, 개발자로 길지 않은 기간 여러 다양한 기술들을 배우고 써보고 느낀 점은 신기술을 배우는 것 또한 높은 확률로 등가교환이다. 즉, 어렵게 배운 기술은 당신에게 그 만큼 혹은 그 이상의 가치를 창출해 줄 것이다. Docker에 대해 더 알아보고 싶은 마음이 생겼다면, docker의 모토를 다시 되뇌이며 다음 챕터들을 읽어보자.

Docker는 Linux 기반 운영체제의 반가상화를 통한 컨테이너 단위의 애플리케이션 배포를 추구한다.

Virtualization

가상화(virtualization)는 컴퓨터과학뿐만 아니라 일반적으로도 많이 쓰이는 용어이다. 실제(real) 환경 위에서 만들어지는 가상(virtual) 환경은 실제 환경에 끼치는 영향을 최소화하면서 가상 환경 안에서는 마치 실제 환경인 것처럼 사용할 수 있다. 개발자라면 분야를 불문하고 가상화에 대한 고민을 해야 하는 순간이 온다. Python 개발자라면 여러 애플리케이션에서 사용하는 pip 패키지 버전이 달라 virtualenv를 사용하려 할 것이고, NodeJS 개발자라면 각 프로젝트마다 모든 의존성을 담고 있는 수백MB에서 수GB의 node_modules 디렉토리를 가지고 있을 것이다.

심지어 언어 혹은 패키지 관리자 레벨에서 가상화를 하더라도 개발 환경과 실제 환경이 다르기 때문에 각종 문제가 발생할 수 있다. 이를테면 어떤 패키지는 MacOS에서는 올바르게 구동되지만 프로덕션 서버인 Ubuntu에서는 버그를 가지고 있는데, 이를 제대로 확인하지 않으면 프로덕션에 배포하자마자 경험하지 못한 버그가 발생할 것이다. 애플리케이션의 환경을 최대한 샌드박스 안에서 유지하고 싶고, 이 환경을 어디에서나 동일하게 작동하기를 기대한다면, 소프트웨어의 가장 낮은 레벨인 OS 레벨의 가상화 외에는 별다른 선택지가 없다.

OS-level Virtualization & Virtual Machines

가상머신은 머신, 즉 컴퓨터를 가상화한 것으로 가상머신 내의 프로그램들은 가상머신 소프트웨어가 제공하는 가상 CPU, 가상 메모리, 가상 디스크를 마치 실제 장치들인 것처럼 사용하여 컴퓨터 안의 컴퓨터를 구현해낸다. OS를 로드하는 시동 디스크또한 가상화되기 때문에 가상머신을 사용하면 하나의 컴퓨터에서 완전히 다른 두 OS를 구동할 수 있다. Windows에서 리눅스 기반 OS를 구동하기 위해 많이 사용되는 VirtualBox, VMWare 등의 프로그램들이 가상머신을 구동하는 소프트웨어에 해당한다.

컴퓨터과학에서 가상화는 아주 넓은 의미를 가진다. 메모리 가상화, 네트워크 가상화, 애플리케이션 가상화 등 가상화되어 돌아가는 것들은 아주 많다. Docker의 핵심적인 메리트와 docker가 해결하는 문제들은 모두 OS와 OS에 연결되어 있는 인터페이스에 관련된 것들이므로, 이제부터 가상화는 OS 단계의 가상화를 의미한다.

cf. Emulation

에뮬레이션(emulation)은 가상화와 유사한 개념이지만, 컴퓨터과학에서는 명확히 구분된다. 가상화는 CPU가 처리하는 명령어 자체는 그대로 전달한다. 만약 구동하고자 하는 프로그램이 해당 CPU에 없는 명령어를 포함하고 있다면 그 프로그램은 가상머신을 써도 구동할 수 없다. 에뮬레이션은 명령어의 번역을 동반하기 때문에 완전히 다른 명령어 셋을 가지고 있는 기기도 가상으로 구현할 수 있다. x64 아키텍쳐인 컴퓨터에서 안드로이드 개발을 위해 사용하는 가상 디바이스는 x64 명령어에서 ARM 명령어로의 번역을 동반하므로 이는 가상화가 아닌 에뮬레이션이라고 할 수 있다. 에뮬레이션은 명령어를 번역하는 과정이 추가되므로 원래 아키텍쳐에 맞게 컴파일된 프로그램을 돌릴 때보다 실행 속도의 손실이 크다.

Full Virtualization

VirtualBox, VMWare 등의 소프트웨어로 만들어진 가상머신은 호스트 머신으로부터 완전히 격리된 메모리, 디스크, 네트워크 및 기타 IO 인터페이스를 할당받으며 OS로 가는 시스템 콜 - 이를테면 메모리 할당과 해제, 네트워크 소켓의 할당과 해제 등은 가상머신에 의해 한 차례 번역되어 전달된다. 예를 들어 Windows 7에서 VirtualBox로 Ubuntu를 구동한다고 하면, Ubuntu가 메모리 할당 시스템 콜을 호출하면 이것이 VirtualBox에 의해 Windows의 메모리 할당 콜로 번역되어 실제로는 Windows 상의 가상머신 프로세스가 메모리를 할당받은 것으로 처리되는 것이다. 이러한 가상화를 전가상화(full-virtualization)라고 한다. 전가상화는 아주 특수한 경우를 제외하면 가상머신이 실제 머신과 완전히 동일한 환경을 보장한다. 다만 시스템 콜의 번역 오버헤드 때문에 가상머신이 할당받은 가상 자원 - 컴퓨팅 파워, 메모리 등을 전적으로 사용할 수 없다. 가상머신의 OS가 일종의 번역 계층이 되는 것으로, 서로 다른 가상머신은 서로 다른 번역 계층을 가지게 되며 각자 호스트의 자원을 차지한다.

전가상화
전가상화
1

Paravirtualization

전가상화의 단점을 보완하기 위한 개념으로 주목받은 것이 반가상화(paravirtualization)이다. 반가상화는 기본적으로 많은 운영체제들이 애플리케이션을 위한 시스템 콜로 아주 유사한 인터페이스를 사용하고 있음에 기반한다. 가장 유명하고 사실상의 표준인 POSIX2 API를 사용하는 두 운영체제가 있다고 가정하자. 하나 위에서 다른 하나를 가상 머신으로 구동하고자 할 때, 굳이 호스트의 운영체제 API를 한 차례 번역을 거쳐 전달해야 할까? 예를 들어, 가상 머신 내의 애플리케이션이 네트워크 포트 5432의 할당을 요청했다면, 호스트는 자신의 5432 포트가 사용중이 아닌 이상 굳이 다른 포트를 할당하고 이에 대한 번역 계층을 제공할 필요 없이 직접 자신의 5432 포트를 가상 머신에 전달하면 될 것이다. 디스크에 대한 접근 역시 비슷하다. 파일 시스템 API가 완전히 동일한 이상 호스트에 굳이 고정된 크기의 가상 디스크를 만들어서 그 내부에서 새로운 파일 시스템을 구축할 필요 없이, 호스트의 파일 시스템 중 일부를 가져다 쓰되 그 밖에는 접근하지 못하게 막으면 될 것이다. 반가상화는 전가상화에 비해 현저하게 적은 컴퓨팅, 메모리, 디스크, 네트워크 및 기타 IO 자원을 소모하면서, 가상화의 원래 목적인 샌드박싱동일 실행 환경 보장은 그대로 달성할 수 있다.

Container

컨테이너는 반가상화된 작업의 단위로, 독립적인 운영체제 인터페이스를 가지는 것처럼 행동하지만 그 실체는 호스트 운영체제의 API를 그대로 가져다 쓰는 가상 머신이다. 대개 OS는 여러 작업을 정의하기 위해 설계되어 있다. Windows, MacOS와 같은 운영체제들은 수십 개의 브라우저 탭과 동영상 재생, 사진 편집, 소프트웨어 업데이트 등이 모두 동시에 이루어지고 있더라도 이를 감당할 수 있어야 한다. 그러나 컨테이너는 겉보기는 OS 레벨 가상화여도 하나의 작업만을 정의하는 것이 바람직하다. 하나의 애플리케이션을 온전히 구동하기 위한 환경을 제공하는 것이 목적이기 때문에, 여러 애플리케이션을 동시에 구동하고 싶다면 각 작업들은 서로 다른 컨테이너에 정의되는 것이 반가상화의 목적에 더욱 부합한다. 정리하면, 반가상화된 컨테이너는 외부로부터 샌드박싱된 환경에서 하나의 애플리케이션을 온전히 구동하기 위한 완벽한 환경을 제공하며, 호스트의 운영체제 API를 번역 계층 없이 그대로 쓰기 때문에 실행 오버헤드가 거의 없다.

Docker의 컨테이너 기반 반가상화
Docker의 컨테이너 기반 반가상화
1

Docker

Docker는 Linux의 POSIX API를 공유하는 운영체제들을 활용한 컨테이너들을 관리한다. 그 기술적 기반은 Moby 프로젝트의 컨테이너화에 있으며, docker는 Docker 사의 영리 소프트웨어로 개인 개발자들은 무료 제품인 CE(community edition)를 사용한다. 코드베이스의 대부분이 떠오르는 신성 언어인 Go로 짜여 있고 프로젝트 자체도 나이가 아주 어린 편이지만, 상기한 수많은 DevOps의 고충을 docker는 너무 쉽고 간단하게 해결해준지라 docker 열풍은 순식간에 개발 생태계를 휩쓸었다. moby, kubernetes 등 큼직한 프로젝트에 박힌 star, issue, PR 수만 봐도 그렇지만, 전 세계 모든 산업을 통틀어 가장 수익을 많이 창출하고 있는 IT 기업들, 특히 클라우드 기업들 대부분이 해당 프로젝트들의 backer 또는 leader로써 참전하고 있는 것만 보아도 docker의 어마어마한 인기를 체감할 수 있다.

Docker 구조
Docker 구조
2

이 글에서 docker의 설치에 대해서는 다루지 않는다. 글에 나온 스크립트들을 직접 시도해보고 싶다면, Docker 공식 홈페이지에서 Docker CE를 다운받은 후 적절한 쉘 환경에서 해당 커맨드가 잘 실행되는지 확인해보자. Docker의 설치는 곧 docker 데몬(daemon)의 설치를 의미하며, 데몬이 가동 중인 상태에만 각종 docker 명령어들을 실행할 수 있다.

$ docker --version

Docker image

Docker는 시스템 스냅샷과 비슷한 개념의 docker image를 기반으로 컨테이너를 생성, 명령을 수행한다. Python 3.9로 쓰여진 스크립트를 docker로 실행하고 싶다면, Python 3.9가 설치된 docker image를 기반으로 해당 스크립트 실행 커맨드를 입력하면 된다.

$ docker pull python:3.9 # Where does this pull from?
# ------ ---- ---image--
$ docker run python:3.9 python -c 'print("hello world!")'
# ------ --- ---image-- ---------------cmd---------------
hello world!

위 명령은 python:3.9 이미지를 가져온 후 print("hello world!") 스크립트를 컨테이너 내에서 실행한 후 즉시 해당 컨테이너를 종료한다. 그런데 python:3.9 이미지는 어디서 가져온걸까?

Docker Hub

우리가 모든 Linux 계열 운영체제, 모든 패키지의 컨테이너 이미지를 직접 만들어 관리하는 것은 반가상화의 장점을 뛰어넘는 수고스러움을 유발할 것이다. 다행히도 일반적인 작업들을 위한 컨테이너 이미지는 대부분 공개되어 배포되고 있으며, 일차적으로는 Docker Hub에 올라오고 있다. docker pull 커맨드는 Docker Hub에 등록된 이미지들 중 해당 이름과 태그를 가진 이미지를 가져와 로컬에서 베이스 이미지로 사용할 수 있도록 해준다. 프로그래밍 언어들, 런타임들, DB들, 웹 서버들 등 개발을 위해 필요한 모든 것이 기본으로 설치되어 있는 이미지들이 공식적으로 유지보수되고 주기적으로 업데이트되어 Docker Hub에 올라오므로, 우리는 그저 pull 받아서 해당 이미지들을 잘 사용하기만 하면 된다.

$ docker pull python:latest # pulls latest python image
$ docker pull node:lts # pulls long-term supported node image

npm이 npm만을 레지스트리로 사용하지 않고 pip가 pypi만을 레지스트리로 사용하지 않듯이, docker도 Docker Hub 외의 다른 레지스트리를 사용할 수 있다. AWS의 ECR 등은 기업을 위한 보호된 컨테이너 이미지 레지스트리를 제공한다. 하지만 더 자세히 다루지 않고, 우리는 대부분의 경우 Docker Hub에 있는 이미지들로 충분히 좋은 결과물을 만들 수 있을 것이다.

Virtualized Resources

Docker 컨테이너들은 docker 데몬을 구동하고 있는 호스트의 자원을 가상화된 형태로 할당받는다. 그 중 가장 많이 사용되고 컨테이너의 환경에도 가장 많은 영향을 끼치는 두 자원인 네트워크와 파일 시스템은 잘 이해하는 것이 좋다. 가상 CPU 코어 개수나 메모리 등도 제한할 수 있으나, 실제로 사용할 일은 없는 것 같다. 종종 할당된 모든 코어를 활용하는 애플리케이션들을 쓰다 보면, 호스트에서 직접 실행할 때와 컨테이너 안에서 실행할 때 코어 개수가 다르게 나올 때가 있을 것이다. 해당 기능들이 체감될 때는 딱 이 때를 제외하고는 없다.

Network

Docker 데몬은 여러 개의 네트워크 인터페이스를 가질 수 있으며, 해당 인터페이스들의 호스트 네트워크 접근을 관리한다. 컨테이너들은 자신이 할당받은 네트워크 인터페이스만을 통해 내부 또는 외부와 통신할 수 있다. 격리된 네트워크는 외부로부터 접근을 적절히 차단한다면 각종 공격으로부터 컨테이너를 일차적으로 보호하는 역할을 하기도 한다.

기본적으로 컨테이너들은 bridge 모드의 네트워크 인터페이스를 가진다. bridge 모드는 같은 네트워크 인터페이스 내의 컨테이너들과의 통신은 자유롭지만, 호스트의 네트워크와의 연결은 허가된 포트들을 제외하면 전부 차단하는 방식이다. host 모드는 컨테이너와 호스트가 완전히 동일한 네트워크 인터페이스를 사용하는 것이다. 이외에도 overlay, none 등이 있으나 여기에서는 다루지 않는다.

$ docker run -p 8000:8000 python:3.9 python server.py
# ------ --- -- host:cont ----img--- -------cmd------
# running server.py attached to localhost:8000 <-> container:8000
# defaults to `bridge` mode

Bridge 모드 네트워크는 Docker에 취지에 맞는 다양한 장점을 가지고 있다. 첫 번째로, 네트워크가 호스트로부터 격리되므로 호스트 환경에 따른 예측 불가능한 오류를 낼 가능성이 낮다. 두 번째로, 같은 네트워크 내에 속한 컨테이너들끼리의 통신은 자유롭게 허용될뿐더러 환경에 의존적이지 않은 일정한 결과를 기대할 수 있다: 이는 각 네트워크가 내부적으로 같은 DNS를 사용하며 각 컨테이너가 할당받은 주소를 향한 레코드를 들고 있기 때문이다. 실제 상황에 가까운 예시로 설명하자면, API 서버 컨테이너가 DB 컨테이너에 접근하고자 할 때, 접근 URL로 mysql://database:3306/dbname 과 같은 이름을 쓸 수 있다는 것이다. database의 자리는 원래 IP 주소 또는 공개 DNS 주소가 들어가야 함을 상기하면, docker 네트워크가 내부적인 DNS를 가지는 것은 보안성으로나 일관성 면에서 큰 장점이다.

Volume

볼륨은 파일 시스템을 가상화한 개념이다. 목적지 없이 생성된 볼륨은 다른 가상 머신들의 가상 디스크처럼 활용되며, 호스트와 seamless한 통합을 자랑하는 docker인 만큼 볼륨 역시 호스트의 특정 파일 시스템에 마운트하여 사용할 수 있다. 컨테이너 내부에서 실행할 스크립트나 설정파일 등은 컨테이너 내부로 복사되어 실행되기도 하지만 주로 볼륨의 연결을 통해 전달된다. 다음은 MySQL 공식 이미지에서 제시하는 커스텀 설정파일을 볼륨을 통해 전달하는 방법이다.

$ docker run --name some-mysql -v /my/custom:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

Container Lifecycle

컴퓨터가 부팅, 종료, 강제 종료 등의 상태를 가지듯이 Docker 컨테이너도 생명주기 상태를 가진다. 생성만 된 상태, 현재 작업을 수행 중인 상태, 작업을 임시 중단한 상태, 종료된 상태, 오류나 시스템 시그널로 인해 강제종료된 상태 등이 있으며 ps 커맨드로 그 목록을 확인할 수 있다. ps의 결과가 계속 늘어나는 것을 막고 싶다면 종료된 컨테이너들을 rm으로 자주 지워주는 것이 좋다.

컨테이너 생명주기
컨테이너 생명주기
4

$ docker ps # shows running containers
$ docker ps --all # shows created, running, stopped, ... containers
$ docker run python:3.9 python -c 'print("hello world!")' # starts running container
$ docker rm great_bassi # remove exited container

Application Definition w/ Docker

이제 본 글의 하이라이트, 컨테이너 형태의 애플리케이션 정의에 대해 알아보자. 샌드박싱되고 부작용이 최소화된 컨테이너 형태로 애플리케이션을 구동하기 위해서는 코드, 런타임, 환경 등 모든 것이 포함된 이미지가 필요하다. 그리고 매 배포, 릴리즈마다 비슷한 과정을 거쳐 새로운 이미지를 만들 것이다. Docker는 이러한 과정을 간소화시키기 위해 이미지를 빌드하는 과정을 정의하는 방법을 제시한다.

Dockerfile

Dockerfile은 해당 디렉토리가 docker 이미지를 빌드하기 위한 디렉토리임을 알려주고, 해당 디렉토리로부터 특정 애플리케이션을 빌드하기 위한 과정을 정의한다. Dockerfile의 각 명령들은 환경에 변경이 전혀 없는 경우 캐싱된 이미지를 그대로 가져다 쓰기 때문에 빌드 시간과 용량을 단축할 수 있다. 대개 정의는 베이스 이미지를 나타내는 FROM에서 시작하여 파일을 컨테이너 내부로 복사하는 COPY, 빌드와 관련된 명령어를 수행하는 RUN 등의 명령어의 조합으로 이루어진다. server.js 파일을 실행하는 node 컨테이너는 대략 다음과 같은 Dockerfile을 통해 빌드될 것이다.

FROM node:lts
WORKDIR /srv
# install deps first to cache build
COPY package.json package-lock.json /srv/
RUN npm install
COPY server.js /srv/
# port used by server.js
EXPOSE 8000
CMD ["node", "server.js"]

Docker Compose

Docker compose는 Dockerfile을 이용한 작업 정의에서 한 단계 더 나아가 애플리케이션이 필요로 하는 모든 컨테이너를 정의한다. 계속 강조했듯이 한 컨테이너에 여러 작업을 정의하는 것은 바람직하지 않다. Docker compose는 하나의 파일 안에서 애플리케이션을 구동하기 위해 필요한 모든 컨테이너를 정의한다. 예를 들어 웹 API 서버라면 API 서버만 있다고 해서 서비스를 정의할 수는 없고 데이터를 저장할 DB 컨테이너, 캐시를 저장할 Redis 컨테이너, 웹 트래픽을 직접적으로 받아내는 Apache, NginX 등의 웹 서버 등이 있어야 온전히 하나의 API 애플리케이션을 정의할 수 있다. Docker compose는 docker-compose.yml 파일 안에 모든 컨테이너, 네트워크, 볼륨을 정의하여 하나의 커맨드로 서비스 전체를 가동하거나 중지할 수 있도록 해준다.

$ docker-compose build # build images that is defined by Dockerfile
$ docker-compose up # to detach, add -d
$ docker-compose stop api # do something with specific service
$ docker-compose down # clean all resources

다음은 API 서버와 PostgreSQL 데이터베이스로 이루어진 서비스를 정의하는 docker-compose.yml 파일 예시이다. 작업을 위한 컨테이너뿐만 아니라 두 컨테이너가 공통적으로 속한 네트워크도 같이 정의되어 있는 것을 볼 수 있다. 이 경우 DB는 docker 네트워크 외부에서 접근할 수 있는 방법이 없으므로 임의의 문자열로 비밀번호를 구성해도 공격으로부터 안전하다.

version: '3'
services: # api server that is based on a Dockerfile
  api: # actual api server that runs on localhost:8000
    build: '.' # Dockerfile location
    restart: always
    depends_on:
      - db
    ports:
      - '8000:8000'
    environment:
      - 'DATABASE_URL=postgres://root:password@db:5432/sample-db'
    networks:
      api-net:
        aliases:
          - api
  db: # database
    image: 'postgres:alpine'
    restart: always
    environment:
      - 'POSTGRES_USER=root'
      - 'POSTGRES_PASSWORD=password'
      - 'POSTGRES_DB=sample-db'
    networks:
      api-net:
        aliases:
          - api
networks:
  api-net:

Container Orchestration

컨테이너 오케스트레이션(orchestration)은 반가상화의 장점을 더 높은 단계로 끌어올려 최소한의 리소스로 DevOps의 어려운 문제들을 아주 손쉽게 해결할 수 있도록 해준다. 오케스트레이션을 굳이 번역하자면 지휘 쯤이 될 텐데, 아직 어색하다고 생각하므로 본 글에서는 원어를 그대로 쓰도록 하겠다. 오케스트레이션 서비스들은 하나 또는 여러 개의 docker 데몬을 거느리며 사용자가 정의한 일종의 작업 정의를 바탕으로 판단하여 이미지를 빌드하거나, 새 컨테이너를 시작하거나, 컨테이너를 종료하거나, 볼륨, 네트워크 등을 조율한다. 대표적인 서비스로 Docker사에서 미는 Docker Swarm과 돌풍을 일으키고 있는 Kubernetes가 있다.

Kubernetes의 작업 정의는 마치 사업 계획서와 같다. 사용자는 어떠한 상태를 달성하는 것에 대한 모든 과정을 일일히 나열하지 않고, 다만 **희망 상태(desired state)**를 제시한다. 예를 들면, DB 컨테이너들은 절대 죽으면 안되고 항상 살아있어야 한다, API 컨테이너가 죽은 경우 재시작해야 한다, 새 업데이트가 배포된 경우 이미지 빌드가 완료되는 대로 기존 컨테이너들을 하나씩 끄고 새 이미지 기반 컨테이너로 재시작한다, 컨테이너들이 과부하에 가까워지면 새 컨테이너를 생성하고 로드를 분산해야 한다 와 같은 식이다. Kubernetes는 희망 상태를 항시 달성하는 것을 목적으로 컨테이너들을 조율한다. 각 컨테이너가 무상태이고 가볍게 정의되어 있을 수록 이러한 구조의 애플리케이션은 확장성이 크고, 오류 대응에 강하고, 사용자에게 더 나은 경험을 제공한다.

From Cloud-dependent To Container-dependent Architecture

클라우드 시장의 붐 이후로 여러 거대한 서비스들은 자체 서버 기반 설계에서 점점 사용하는 클라우드 서비스에 의존적인 설계로 옮겨갔다. 이 형태는 기존의 자체 서버를 굴리는 환경에 비해 확장성을 비약적으로 높여주었지만, 더욱 적극적인 확장과 이전을 생각한다면 한계가 분명히 존재하는 구조이다. 예를 들어 API 서버의 로드를 분산하기 위해 AWS의 ELB와 EC2를 연동하여 로드에 따라 디스크 이미지 기반으로 새 인스턴스를 계속 생성하는 형태로 설계했다고 하자. 해당 서비스는 AWS가 서비스 중인 리전에서는 최고의 확장성을 보장하겠지만, AWS에서 다른 클라우드 플랫폼으로 이전을 기획하거나 AWS가 지원되지 않는 리전에서의 서비스를 시도할 경우 문제가 생길 수 있다.

이제 Docker compose나 Swarm stack, Kubernetes 작업 정의의 형태로 같은 설계를 구성했다고 가정하자. 이렇게 정의된 서비스는 docker 컨테이너를 띄울 수 있는 리전이라면 어디든, 어떤 클라우드에서든 서비스를 시작할 수 있다. 뿐만 아니라 클라우드나 DevOps가 환경에 맞게 구성해야 할 기능들도 오케스트레이터에게 위임할 수 있다. 확장성과 이전 용이성에 있어 컨테이너 오케스트레이션을 활용한 설계는 가히 최고라고 할 수 있다.

정리

클라우드 붐에 이어 docker로 촉발된 반가상화 컨테이너 형태의 개발, 배포, 운영이 사실상 표준으로 굳어가고 있다. 실제로 docker를 이용한 로컬에서의 이슈 없는 개발만 체험해보더라도 신세계를 경험할 수 있으며, 적당히 프로젝트가 궤도에 오르면 Dockerfile부터 작성하고 있는 자신을 발견할 수 있을 것이다. 당장 docker를 배우기 시작하고, 개발의 재미와 스스로의 생산성을 다음 단계로 업그레이드하자.


  1. ^

    이미지 출처: Docker

  2. ^

    POSIX는 Unix 계열의 이식 가능 OS 인터페이스(Portable OS Interface - uniX)로, 운영체제가 애플리케이션에 자원을 할당, 해제, 관리하는 인터페이스와 방식을 정의한다. 데스크탑 시장의 Windows와 MacOS, 서버 시장의 Linux, 모바일 시장의 Android, iOS 및 iPadOS는 모두 POSIX를 완전히 또는 거의 구현하고 있다.

  3. ^

    이미지 출처: “Docker overview”, Docker docs

  4. ^

    이미지 출처: “Docker Internals ”, Docker Saigon

© 2020 by appleseedPowered by
React Logo