본문 바로가기
Cloud

[2] Faas Platform이 동작하는 방식 (1) Apache Openwhisk

by Riverandeye 2020. 9. 11.

이 문서에서는 어떤 과정으로 서버리스 플랫폼이 동작하는지에 대해 이해해보는 문서입니다. 

다양한 프레임워크가 서로 다른 방식으로 동작하겠지만, 큼직한 Flow만 이해하는 것을 목적으로 작성하였습니다.

위 글은 이 동영상을 중심으로 작성되었습니다.

동영상에 대한 발표 자료 링크는 다음과 같습니다.

What is Serverless Computing?

여러번 짚어본 이야기지만, 서버리스 컴퓨팅이란 Cloud Provider가 사용자에게 컴퓨팅 리소스와 스토리지를 동적으로 할당하여, 

사용자가 리소스를 유지하고 프로비저닝 할 필요 없게, 어플리케이션의 비즈니스 로직만 고민할 수 있게끔 편의를 제공하는 서비스이다. 

 

서버리스 함수는 이벤트 기반으로 동작하고, 이는 요청이 왔을 때 Trigger 되는 것이다. 서비스 제공자는 사용자에게 요금을 매길 때 해당 요청을 수행한, Compute time 에 사용한 리소스에 대해 가격을 책정한다. 일반적으로 Event-Driven 어플리케이션에 적합하며, 서버의 idle time 에 대해 요금을 절약하는데 큰 도움이 된다. 

 

Drawbacks of Serverless Computing

Serverless Function은 stateless 하기 때문에 이전에 실행한 인스턴스에 대한 정보를 유지할 수 없으며, 경우에 따라 코드가 느리게 동작하는 경우가 있어 (cold start), low latency를 요하는 경우에 주의해야 한다. 

 

서버리스 오픈소스가 많지만, 실질적으로 서버리스 마켓은 클라우드 제공 업체에서 거의 지배하고 있다보니 vender에 의존적이게 되어 in-house 개발 프로세스 및 테스트 파이프라인을 구축하기 어렵게 될 수 있다. 

 

Trend

오픈소스 서버리스 플랫폼

다음과 같은 오픈소스 서버리스 플랫폼들이 등장하고 있고

서버리스를 기반으로 어플리케이션을 개발하는 곳들도 속속이 늘어나고 있다고 한다. 

 

서버리스를 이용하는 벤더들

 

서버리스는 컨테이너를 시간축으로 공유하여 쓰는 시간동안만 컨테이너를 점유하여 리소스를 공유한다. 

 

What is Apache Openwhisk

아파치 오픈위스크는 서버리스 오픈소스 플랫폼이고, 개념적인 구성도는 다음과 같다. 

 

Apache Openwhisk의 개념도

보통 서버리스는 외부 이벤트에 의해 반응하기 때문에, 외부 이벤트에 반응하는 Feed 라는 컴포넌트가 있고

Faas에서 실행시켜야 할 사용자의 함수를 여기서는 Action이라 부른다. 각 액션은 서로 다른 언어들로 구성되어 있다. 

이벤트 발생시 이벤트에 반응하여 특정 액션을 수행하기 위해 Trigger라는 컴포넌트가 있다. 

트리거를 어떤 액션과 연결할지를 결정하는 Rule이 있다.

 

모든 기능은 REST API로 구성되어 있다. 

 

Nginx 구조

가장 앞단에 SSL을 담당하는 Nginx가 있고, Rest API를 제공하는 Controller가 있다. Controller가 인증 및 권한을 체크하고 사용자의 액션을 어디로 보낼지에 대한 스케줄링을 한다. CouchDB에 인증 권한을 저장하고 사용자의 함수 정보를 저장한다. 

Controller는 Kafka를 통해 Invoker에게 사용자의 액션 실행 요청을 전달하고, Invoker는 컨테이너를 이용해서 컨테이너 안에서 사용자의 함수를 실행한다. 중요한건 컨트롤러가 스케줄링을 담당하고, Invoker가 컨테이너를 기반으로 사용자의 함수를 실행시킨다는 것이다. 

 

액션의 시작 형태

액션을 실행할 때 다음과 같은 차이가 빚어지는데, 

Cold Start는 액션을 수행하기 위한 컨테이너가 아직 생성되지 않은 상태이고

Prewarmed Start는 코드 초기화가 되지 않은 경우이다.

자연스럽게 큰 속도 차이가 발생한다. 

 

그러다보니 개발자로 하여금 콜드스타트를 방지할 수 있는 여러 방법들이 있다. 프루닝을 한다던지.. 

 

액션의 실행


Openwhisk에 함수 실행 요청을 하게 되면 Invoker가 컨테이너를 생성 혹은 관리한다.

- 먼저 컨테이너가 없으니 컨테이너를 생성한다 Cold Start

- 컨테이너가 준비되어있는 상태가 Prewarmed

- 컨테이너 내에 코드가 초기화되어있는 상태가 Warmed 

 

다음과 같이 많은 Warmed Container가 있다고 하면,

a1 요청이 Invoker0으로 가는 경우 a1에 해당하는 컨테이너가 있기 때문에 빠르게 실행될 수 있을 것이다. 

만약 Invoker2로 가게되면 a1에 해당하는 컨테이너가 없으니 기존 컨테이너를 삭제하고 새로운 컨테이너를 생성 - 초기화 - 액션실행..

여기서 중요한건 동일한 실행인데, 어느 Invoker로 보느냐에 따라 성능이 달라진다

 

최적의 스케줄링이 어려운 이유

 

Openwhisk에서 스케줄링이란 결국 액션 요청을 어느 Invoker로 보낼지로 결정하는 과정을 의미한다.

당연히 이미 구동중인 컨테이너를 재사용하는게 좋겠죠. 

 

그럼 Optimal하게 스케줄링하면 될텐데, 그게 왜 어려울까? 그 이유는 실제 warmed container의 상태를 실시간으로 추적하여 반영하는 것이 매우 어렵기 떄문이다.

warmed container는 속도가 <10ms 단위로 매우 짧아, 컨트롤러가 그 상태를 온전히 인지하기 힘들 뿐더러

여러개의 컨트롤러가 동시에 동작하기 때문에, 동기화의 문제도 있다. 

모든 Invoker로부터 실시간으로 리소스 정보를 수집하며 다른 Controller의 스케줄링을 고려하면서 최적의 위치로 실행 요청을 전송하는 이 모든 동작이 2ms 내로 이루어져야 한다.. -> 힘들다. 

 

Apache Openwhisk의 접근 방식

1. 액션의 위치를 미리 정해둔다. 

개별 액션별로 서로 다른 Invoker에게 전달되도록 해시를 지정하는 것이다. 

해시 함수를 설정하면 어떤 Controller가 실행시켜도 같은 결과가 나오기 때문에 컨트롤러간 동기화가 필요 없다. 

같은 액션 -> 같은 Invoker

 

2. 각 컨트롤러가 리소스를 나누어 가진다.

Invoker의 리소스를 각 컨트롤러에 제한해서 다른 컨트롤러의 스케줄링을 고려할 필요가 없게 끔 한다. 

 

Docker의 성능이 Openwhisk에 미치는 영향

서버의 성능에 따라 다르겠지만, 하나의 docker 데몬이 1초에 10개의 데몬을 만든다. (성능 제약이 있음) -> 작은 VM 여러개가 큰거 하나보다 낫다. 

 

도커 데몬이 run, rm 하는데 0.5~1.3초가 걸리고 pause-unpause 하는데 50~400ms 걸린다고 한다.

데몬은 요청을 Sequential하게 처리한다고 한다. 

 

문제 상황은 다음과 같다. 

 

다음과 같이 Invoker 1개에서 Running Container가 4개 있다고 가정하면

Invoker0에서 A액션 하나가 실행 완료된 후 warmed container로 전환될 때 하필이면 Action B가 실행요청이 온 것이다.

그러면 Running하는걸 지울 수는 없으니 warmed된 애를 제거 할 것이다.

문제는 해싱이기 때문에 항상 동일한 Invoker로 가기 때문에 문제가 지속된다는 점이다.  

이런 우연이 반복되면 성능 저하가 발생하게 된다. 

 

이렇게 되면 정말 이상적인데..

위와같이 하나의 Invoker에 하나의 액션 종류만 생성되면 액션간의 간섭이 없을 것이다. 

무엇보다 중요한건, Docker Daemon이 모든 요청을 Sequential하게 수행하기 때문에 이후의 요청들도 함께 느려진다. 

 

만약 실행 속도가 20ms 밖에 안걸리는걸 알면, 그냥 기다리면 되는데 그렇지 않고 다른 Invoker에게 요청을 해버린다는 것이다. 

그러다 보니, 실행 시간이 500ms 인 액션의 경우 ColdStart보다 이전 실행을 기다리는 것이 더 낫다. 

 

관련 이슈는 이 링크를 통해 확인할 수 있습니다. 

이렇다 보니, 성능 문제 뿐만 아니라 시스템의 성능을 예측하고 결정할 수 없다는 것이 정말 큰 단점이다. 

 

사용자의 수와 유형에 따라 성능이 달라진다..

그렇게 되면 시스템을 유지하기 위해 프로비저닝을 할 수 없게 되므로 아예 사용할 수가 없는 것이다. 

 

성능 이슈 극복하기

다음과 같은 방식으로 이슈를 극복한다

 

 

부터는 너무 어려워서.. 그냥 보기만 했다. 

Kafka 브로커를 그대로 사용하지 않고 (개별 큐를 생성하는 성능상의 이슈), 액션별로 개별 큐를 구성하여 상주하고 있는 컨테이너와 큐가 직접 연결되게 함으로써 Polling 구조를 통해 퍼포먼스를 극대화하고 예측 가능하게 만들었다. (매우 대단하다)

그리고 컨테이너의 생성 정보를 개별 컨트롤러가 아닌 다른 Global한 분산 트랜잭션이 적용된 컴포넌트에서 관리하게끔 하여 컴포넌트 생성을 최적화한다. 

Apache Openwhisk 예시를 통해 이해한 부분

핵심은 특정 함수를 실행시켜주는 컨테이너가 

- 상주하고 있거나

- 새로 생성되거나

 

함으로써 요청한 함수를 실행시켜주는 구조인 것이다.

함수를 실행시키는 것도 결국엔 상주하고 있는 컨테이너 내의 api 요청에 의한 것으로 이해된다.

나머지 컴포넌트는 사용자 편의 + 최적화를 위해 존재하는 것으로 이해된다. 

궁금한 부분

warm container와 pre-waremd container의 차이를 아직 확실하게는 알지 못하겠다. 

함수가 준비된 경우와 준비되지 않은 경우는, 빌드 결과로 생성된 바이너리가 있고 없고의 차이로밖에 지금은 생각되지 않는다. 

node js 같이 인터프리팅하는 경우엔 어떤 차이가 존재하는지 궁금하다. 

다른 플랫폼의 예시를 통해 더 깊이 학습해야 할 것으로 보인다. 

 

Reference

www.infoworld.com/article/3406501/what-is-serverless-serverless-computing-explained.html

tv.naver.com/v/11212848/list/534045

 

'Cloud' 카테고리의 다른 글

[1] 서버리스에 대한 아주 간략한 개요  (2) 2020.07.19

댓글