클라우드 네이티브 프로그래밍 101


클라우드 네이티브 프로그래밍이란 클라우드 환경을 위한 프로그램을 만드는 것을 의미합니다. 다만, 단순히 클라우드 환경에 배포할 수 있는 프로그램의 개발만을 고려하는 것이 아니라 클라우드 환경의 특성에 맞게 동작할 수 있는, 클라우드에서 제공하는 것들을 적극적으로 활용하는 프로그램을 만드는 것을 의미한다고 생각하면 좋을 것 같습니다.

클라우드 컴퓨팅은 개발자의 관점에서는 무한할 정도로 많은 자원들을 가진 클라우드 인프라에서 목적에 맞는 기능을 가진 자원을 필요한 만큼 할당받아서 사용하는 것을 가능하게 했습니다. 그래서 과거에는 직접 물리적인 서버를 들고 가서 랙에 설치하고 랜선을 꽂아야 했던 서버 증설의 과정이 클릭 몇 번으로, 혹은 클릭도 없이 자동으로 이루어질만큼 단순해졌고 그 반대의 경우도 비슷하게 단순해졌습니다.

이러한 클라우드 컴퓨팅이 가지는 특성은 굉장히 많지만, 여기에서는 일단 확장성(Scalability), 효율성(Efficiency), 서비스로의 플랫폼(PaaS – Platform as a Service)의 3가지 부분에 대해서 이야기 해보도록 하겠습니다. 클라우드 네이티브 프로그래밍은 결국 클라우드 환경에서 이러한 확장성과 효율성을 확보하고 PaaS 기반으로 개발할 수 있는 프로그램을 만드는 과정을 의미합니다.

Load balancer with scalable servers

확장성이란 필요에 따라서 사용하는 자원을 늘려나갈 수 있는 것을 의미합니다. 가장 단순하게 생각해보면, 서버의 메모리 사용량이 급증할 경우 서버에 메모리를 추가로 할당할 수 있습니다. 물리적으로는 서버에 램을 추가로 끼울 수도 있고 클라우드 환경에서는 돈을 더 내고 메모리를 더 할당해주도록 요청할 수 있습니다. 이러한 확장을 수직적 확장이라고 부르는데, 서버에 할당할 수 있는 자원은 물리적인 한계를 가지고 있으므로 수직적 확장이 무조건 답이 되지는 않습니다.

때문에 최근에는 동일한 기능을 가진 프로세스나 서버를 추가로 띄워서 작업과 부하를 분산시키는 수평적 확장이 상당히 중요해졌습니다. 클라우드 환경은 이러한 수평적 확장을 지원하기 위한 자원도 서비스도 충분히 제공되고 있습니다. 다만 고민해야 할 것은 저희가 어떻게 개발해야 이러한 수평적인 확장의 장점을 극대화 시킬 수 있는가 입니다.

MicroService Archiecture Example

예를 들어서 확장하기 위한 서버의 단위가 무척 크다면, 부하의 증가로 인하여 서버를 스케일 아웃 할 때 시간이 굉장히 많이 들어가게 됩니다. 서버를 기동시키는 시간도 길어지고 상태를 공유하기 위하여 복사해야 하는 데이터의 양도 많아지고, 한 번에 확보해야 되는 자원의 양도 많아지기 때문에 무척이나 비효율적인 상황이 됩니다.

만약 저희가 만들고자 하는 시스템을 작은 단위로 분할할 수 있다면 어딘가 스케일 아웃이 필요할 때 필요한 서버만 스케일 아웃 할 수 있기 때문에 저희는 시간과 자원을 모두 절약할 수 있습니다. 이렇게 시스템을 작은 서비스 단위로 분할하는 것을 마이크로서비스 아키텍처라고 부릅니다. 그리고 MSA의 각 서비스는 보통 스케일 아웃 가능한 가장 효율적인 단위로 분할하는 것이 좋습니다.

Stateful Service Example

스케일 아웃 가능한 애플리케이션을 만들 때 또 중요하게 고민해봐야 할 점이 ‘상태를 가지고 있는’ 애플리케이션과 ‘상태를 가지지 않는’ 애플리케이션의 구분입니다. 만약 스케일 아웃되는 프로세스나 서버가 독자적인 상태를 가지고 있다면, 즉 저장하고 있는 데이터가 있다면 서버가 확장될 때 각각의 상태를 서로 공유해야 하는 어려움이 생겨나게 됩니다. 때문에 최근의 개발 트랜드에서는 가급적이면 애플리케이션 서버는 어떠한 상태도 가지지 않는 Stateless Application으로 구성하고, 상태 정보들은 모두 별도의 저장소 (Persistence)에 몰아버리는 흐름이 있습니다. 저장소들은 또 다른 백엔드 서비스가 될 수 있기 때문에, 애플리케이션 서버는 그 자체만으로도 충분히 함수적으로 동작할 수 있게 됩니다.

이러한 구조는 서버의 수평적 확장과 축소 과정에서 고민해야 할 내용들을 상당히 단순화 시켜주기 때문에 클라우드 환경에 꽤 잘 어울리는 구조입니다. 사실 데이터를 DB에만 저장하는 구조는 굉장히 고전적인 구조이기 때문에 크게 강조할 필요가 없다고 생각하실 수도 있지만, 당장 서버의 세션 정보를 메모리에 저장하는 것도 그것만으로 상태를 가진 서버를 만들어낼 수 있음을 생각해보면 상태없는 서버를 만드는 과정은 생각보다는 어려울 수 있습니다.

OS Level Virtualization Structure

서버가 특별히 유지해야 할 상태가 없다는 말은 서버 자체의 실행과 종료가 예전에 비해서 더 인스턴트한 개념으로 바뀔 수 있다는 말이고, 이러한 흐름에서 컨테이너 기술의 등장을 언급하지 않을 수 없습니다. 컨테이너 기술은 저희에게 익숙한 하드웨어 레벨의 가상화 기술과 달리, 운영체제의 커널 부분만 공유하고 각각의 프로세스가 실행되는 환경의 저장소 루트, 네트워크 포트, 프로세스, 라이브러리와 환경변수 등을 모두 격리시키는 기술로 하드웨어에 대한 가상화 작업이 포함되지 않기 때문에 훨씬 빠르게 가상화된 인스턴스를 기동하거나 제거할 수 있는 장점이 있습니다. 물론 빠른 인스턴스의 기동이나 제거는 클라우드 인프라의 확장성과도 잘 맞아 떨어지기 때문에 최근에는 적당히 작은 단위로 분할된 마이크로 서비스를, 상태를 유지하지 않는 형태로 만들어서 컨테이너 형태로 배포하는 것을 목표로 개발하는 것을 목표로 할 때가 많습니다.

또한 컨테이너는 할당할 수 있는 자원을 훨씬 세밀하게 구분할 수 있으며, 하나의 노드를 마음껏 분할해서 사용해도 각각의 컨테이너가 격리되는 특성 때문에 부작용 없이 서버를 활용할 수 있다는 장점을 가지게 되는데 이는 효율성의 측면에서 생각해볼 수 있습니다. 예를 들어서 매우 규모가 작은 서버를 띄우기 위해서 최소 1코어를 할당해야 할 필요도 없으며, 가용한 자원들을 모두 모아서 자원 풀(Pool)로 확보한 뒤 여기에 필요한 컨테이너들을 필요할 때만 기동시켜서 사용한 뒤 곧바로 제거하여 자원 사용의 효율성을 높일 수도 있습니다. 생성과 제거가 대단히 빠르다는 컨테이너의 특성은 여기에도 매우 잘 부합합니다. 이러한 서버의 확장과 축소를 측정되는 자원 사용량에 기반하여 자동으로 이루어지게 할 수도 있는데 이러한 기능을 오토 스케일링(Auto Scaling)이라고 부릅니다. 가장 간단한 오토 스케일링의 공식은, 서버 숫자를 (현재 서버들의 사용량 합계) / (목표 사용량)으로 나눈 값을 올림하여 사용하는 것인데, 예를 들어서 현재 2개의 서버가 90%의 자원 사용량을 가지고 있고 목표 사용량이 50%라면 90 * 2 / 50 = 3.6으로 총 4대의 서버를 확보해야 한다는 판단을 내리고 2개의 서버를 추가로 생성할 수 있습니다.

이렇게 서버가 수시로 늘어나고 또 줄어들 수 있는 상황을 가정한다면 개발자들은 ‘어떻게 서버를 잘 죽일 것인가’를 고민해야 하는데 사실 저희는 보통 서버의 기동 과정에만 관심을 집중했기 때문에 서버의 종료 과정을 우아하게 만드는 것은 조금 낯선 일이기도 합니다.

Graceful Shutdown

보통 스케일 인(Scale-In)이 발생하여 서버를 줄여야 할 때는 관리 시스템은 프로세스에 종료 신호 (SIGTERM)를 보내고 서버가 종료할 시간을 몇 초 기다려주는데 이 때 저희가 만든 프로세스가 새로운 작업 요청을 모두 거부하고, 현재 걸려있는 작업들의 응답을 모두 보내준 뒤 사용하고 있던 자원을 다시 돌려주고, 혹시라도 길게 처리해야 하는 작업이 있다면 이를 작업 큐로 돌려보내서 다른 서버가 해당 작업을 처리하도록 만든다면 서버의 종료에도 사용자에게 별다른 오류를 내지 않는 우아한 서버를 만들어낼 수 있습니다.

Event-driven architecture with message queue

또한 전통적인 요청 - 응답 방식의 서버 호출에서 벗어나서 요청자는 별도의 큐에 메시지를 던져넣고, 해당 요청을 처리할 수 있는 서버는 큐를 감시하고 있다가 가용할 때 해당 메시지를 꺼내서 작업을 처리하는 방식의 구성을 시도할 수도 있는데 이러한 개발을 이벤트 기반의 개발이라고 부릅니다. 이는 컴퓨터 공학의 매우 유서깊은 패턴인 생산자 - 소비자 모델 (Publish-Subscribe Model)의 서버 단위의 구현이라고 보실 수 있습니다. 이러한 개발은 서버간의 의존성을 크게 줄여주고 서버의 수량이 수시로 바뀌더라도 작업을 안정적으로 유지할 수 있도록 해줍니다.

Non-blocking I/O with reactive programming

이러한 모델을 조금 더 높은 레벨로 추상화 시키면, 요청과 응답을 선언적이고 비동기 형태로 처리하는 형태를 생각할 수 있는데, 이러한 프로그래밍 기법을 반응형 프로그래밍 (Reactive Programming)이라고 부릅니다. 애플리케이션의 내부와 외부, 클라이언트와 서버를 가리지 않고 크게 부각되고 있는 반응형 프로그래밍의 사상은 요청과 응답이 동기화되어 이루어지면서 생겨나는 자원의 할당과 해제, 스레드의 관리 및 동시성, 부하의 급격한 증가에 대한 복잡한 고민들을 상당부분 해소시켜 주기 때문에 최근에는 그 중요성이 점점 커지고 있습니다.

만약 저희가 서버를 비동기적인 형태로 요청을 받고 콜백을 전달할 수 있는 비동기 형태로 개발해야 한다면, 서버를 구성하고 있는 서비스도 비동기로 만들어야 하며 퍼시스턴트 영역에 대한 접근, 즉 파일이나 데이터베이스에 대한 접근도 요청을 블로킹하지 않는 형태(Non-Blocking)로 만들어야 하는 새로운 요구사항이 나오게 됩니다. 특히나 데이터베이스를 비동기적으로 구성하는 것은 쉽지 않은 일인데, 최근에 등장한 많은 NoSQL 데이터베이스들은 이러한 반응형 프로그래밍을 잘 지원해주는 경우가 많습니다.

Polyglot persistence example

하지만 NoSQL 데이터베이스의 사용은 여전히 높은 진입장벽을 가진 경우가 많습니다. 하지만 만약 개발할 때 서비스들을 적당히 작은 마이크로 서비스로 분할했다면 각각의 서비스가 가장 필요한 형태의 데이터베이스를 각자 사용하도록 구성할 수 있습니다. 이러한 Polyglot Persistence의 사상은 역으로 관리해야 하는 데이터베이스의 종류가 많아져서 개발과 유지보수의 비용이 모두 증가하는 단점을 가지고 있는데, 클라우드 환경에서는 상당수의 데이터베이스들이 관리형 인스턴스, PaaS 형태로 제공되기 때문에 이러한 부담을 상당히 줄일 수 있습니다.

이는 각각의 서비스마다 가장 적합한 언어로 개발하는 Polyglot Language의 트랜드와도 잘 맞아 떨어집니다. 개발환경의 구성이 온전히 저희의 부담이었을 때는 다양한 언어를 사용하는 것이 쉬운 결정은 아니었지만 PaaS 형태로 언어의 실행환경, 개발 및 빌드-배포 환경이 지원되는 상황에서는 조금 더 과감하게 다양한 언어와 프레임워크를 사용할 수 있습니다.

Integrated logging server

하지만 서비스가 많아지면 각각의 서비스가 발생하는 로그가 무수히 많이 분산되어 관리하기 어려워지는 문제점이 생기게 됩니다. 때문에 최근에는 로그를 관리하는 서비스 역시 별도의 서비스로 분리한 뒤, 각각의 서비스에서 발생한 로그를 로그 서버로 보내서 통합 로그를 관리하는 방식이 많이 사용되고 있습니다. 이렇게 로그를 통합하여 관리할 경우 로그의 유지와 관리, 검색 및 분석, 대시보드 구성등을 모두 높은 수준으로 해낼 수 있는 장점이 있습니다.

Platform-based development

말했듯이 PaaS 환경에서는 로그 서버를 포함하여 다양한 기능들이 개발이 아닌 사용의 대상으로 제공되는 특성이 있습니다. 클라우드 환경에서 제공해주는 VM만 사용하는 것이 아닌 오브젝트 저장소, 관리형 데이터베이스, 각종 분석 서비스나 로그 서버등 다양한 자원들을 활용하여 애플리케이션을 개발하면 저희는 온전히 비즈니스 로직의 구현에만 집중할 수 있다는 장점을 가질 수 있습니다. 이렇게 비즈니스 기능을 개발의 대상, 비즈니스와 관련 없는 기능들을 사용의 대상으로 분리할 수 있는데, 비즈니스와 관련 없는 기능들이 공통화된 형태로 제공되는 것이 저희가 익히 들었던 ‘플랫폼’이라는 용어의 정의입니다. 그리고 이러한 플랫폼 기반의 개발은 개발과 운영단계에서 모두 비즈니스의 영역에만 집중할 수 있게 해주는 큰 장점을 가져옵니다.

마지막으로 이 모든 과정들을 고려하여 개발 - 배포 - 테스트 - 반영을 수행할 필요가 생겨나게 되는데, 서비스와 서버의 숫자가 급격히 늘어나고 다양한 언어와 데이터베이스를 사용하는 상황에서 이 모든 것을 수작업으로 관리하는 것은 무척 어려운 일이 됩니다. 때문에 이 파이프라인을 자동화시키고, 기준에 맞추어서 진행될 수 있도록 구성하여 개발자의 작업 내용이 최대한 빠르게 환경에 반영될 수 있도록 하는 준비하는 것을 지속적인 배포(Continuous Delivery)의 사상입니다.

Rest topics of cloud native

위에서 클라우드 기반의 새로운 환경에 적응하기 위한 애플리케이션을 개발하기 위한 몇 가지 토픽들을 언급했었는데, 실제로는 훨씬 더 많고 다양한 주제들을 공부하고 연습하여 대응할 필요가 있습니다. 이러한 토픽들은 트랜드에 따라서 중요도가 바뀌기도 하고, 새로운 것들이 들어오기도, 기존의 것들이 무의미해지기도 하기 때문에 ‘최신 기술 트랜드를 따라간다’라는 단순한 말이 실제 애플리케이션 개발과 소프트웨어 아키텍처에 큰 부담으로 다가오기도 합니다.

하지만 새롭게 등장하고 발전하는 개념과 기술들이 우리 개발을 어떻게 변화시키고 편하게 만들어줄지에 대해 기대하는 마음으로 클라우드 네이티브 프로그래밍이라는 커다란 주제에 대해서 지속적으로 관심을 가지신다면 클라우드라는 커다란 흐름을 놓치지 않고 수행하시는 프로젝트와 과제에 가속을 붙여줄 수 있을것이라 믿습니다.